mirror of
https://github.com/EiffelSoftware/eiffel-org.git
synced 2025-12-06 23:02:28 +01:00
Author:Roman
Date:2014-01-16T22:47:41.000000Z git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@1244 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
This commit is contained in:
BIN
documentation/current/solutions/_images/object_graph.png
Normal file
BIN
documentation/current/solutions/_images/object_graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,3 @@
|
||||
title=Child object graph
|
||||
author=Roman
|
||||
path=content/child-object-graph
|
||||
@@ -0,0 +1,350 @@
|
||||
[[Property:title|Accessing an existing database]]
|
||||
[[Property:weight|3]]
|
||||
[[Property:uuid|683da4a5-2939-890e-8c71-2d59e5ebabe4]]
|
||||
== Introduction ==
|
||||
|
||||
ABEL has a special backend to read and write existing databases.
|
||||
This backend was designed to use ABEL alongside EiffelStore with very little setup.
|
||||
|
||||
The drawback of this approach is that the backend has some of the limitations of EiffelStore:
|
||||
|
||||
* Only flat classes can be stored and retrieved.
|
||||
* The class name must match the type name in lowercase, and each attribute must match a column name.
|
||||
* ABEL treats all objects as values without identity (like expanded types). There is a mechanism however to override this default.
|
||||
* There is no concept of a root status.
|
||||
|
||||
== The setup ==
|
||||
|
||||
Let's assume a simple EiffelStore application for managing a (very simple) MySQL customer database.
|
||||
The database consists of a single table created by the following SQL statement:
|
||||
|
||||
<code>
|
||||
CREATE TABLE customer (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
first_name VARCHAR (100),
|
||||
last_name VARCHAR (100),
|
||||
age INTEGER
|
||||
)
|
||||
</code>
|
||||
|
||||
In your Eiffel code you have a class which matches the table:
|
||||
|
||||
<code>
|
||||
class
|
||||
CUSTOMER
|
||||
|
||||
inherit
|
||||
ANY redefine out end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access
|
||||
|
||||
id: INTEGER
|
||||
-- The customer ID.
|
||||
|
||||
first_name: STRING
|
||||
-- The customer's first name.
|
||||
|
||||
last_name: STRING
|
||||
-- The customer's last name.
|
||||
|
||||
age: INTEGER
|
||||
-- The age of the customer.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_age (an_age: like age)
|
||||
-- Set `age' to `an_age'
|
||||
do
|
||||
age := an_age
|
||||
end
|
||||
|
||||
feature -- Output
|
||||
|
||||
out: STRING
|
||||
-- String output of `Current'.
|
||||
do
|
||||
Result := id.out + ": " + first_name + " " + last_name + " " + age.out + "%N"
|
||||
end
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (an_id: like id; first: like first_name; last: like last_name; an_age: like age)
|
||||
-- Initialization for `Current'.
|
||||
do
|
||||
id := an_id
|
||||
first_name := first
|
||||
last_name := last
|
||||
age := an_age
|
||||
end
|
||||
|
||||
end
|
||||
</code>
|
||||
|
||||
== Connection setup ==
|
||||
|
||||
Because we're using an existing MySQL database, we need to choose the <e>PS_MYSQL_RELATIONAL_REPOSITORY_FACTORY</e> for initialization.
|
||||
|
||||
class
|
||||
TUTORIAL
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access
|
||||
|
||||
repository: PS_REPOSITORY
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Set up the repository.
|
||||
local
|
||||
factory: PS_MYSQL_RELATIONAL_REPOSITORY_FACTORY
|
||||
do
|
||||
create factory.make
|
||||
|
||||
-- Feel free to change the login credentials.
|
||||
factory.set_database ("my_database")
|
||||
factory.set_user ("root")
|
||||
factory.set_password ("1234")
|
||||
|
||||
repository := factory.new_repository
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
That's it. You're now ready to read and write table records using the repository.
|
||||
|
||||
== Querying objects ==
|
||||
|
||||
With the newly created repository, we can now query for <e>CUSTOMER</e> objects.
|
||||
The procedure is the same as seen in [[Basic operations]].
|
||||
|
||||
<code>
|
||||
|
||||
print_customers
|
||||
-- Print all customers.
|
||||
local
|
||||
query: PS_QUERY [CUSTOMER]
|
||||
do
|
||||
create query.make
|
||||
repository.execute_query (query)
|
||||
|
||||
across
|
||||
query as cursor
|
||||
loop
|
||||
print (cursor.item)
|
||||
end
|
||||
|
||||
if query.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
|
||||
query.close
|
||||
end
|
||||
</code>
|
||||
|
||||
== Writing objects ==
|
||||
|
||||
You can also use ABEL to write or update customers, although the semantics are a bit different compared to other backends.
|
||||
ABEL by default treats every object as a value tuple without identity (like an expanded type).
|
||||
The reason is that the primary key of an object is usually stored directly inside the object,
|
||||
and a user may change it and thus mess with ABEL's internal data structures.
|
||||
|
||||
The implication of this is that a user is only allowed to call <e>{PS_TRANSACTION}.insert</e> to write an object.
|
||||
The semantics of <e>insert</e> is to insert a new record if no other record with the same primary key exists, or else to update the existing record.
|
||||
This might be confusing at first sight, but it is in line with the semantics of ABEL as seen in [[Dealing with references]].
|
||||
|
||||
The following code shows how to insert and update objects.
|
||||
|
||||
<code>
|
||||
insert_customer
|
||||
-- Insert a new customer.
|
||||
local
|
||||
albo: CUSTOMER
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
-- Assume 42 is an valid, unused primary key.
|
||||
create albo.make (42, "Albo", "Bitossi", 1)
|
||||
|
||||
transaction := repository.new_transaction
|
||||
|
||||
if not transaction.has_error then
|
||||
-- This results in an insert, because
|
||||
-- according to our previous assumption
|
||||
-- there is no record with primary key 42
|
||||
transaction.insert (albo)
|
||||
end
|
||||
|
||||
-- Cleanup and error handling.
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
if transaction.has_error then
|
||||
print ("An error occurred.%N")
|
||||
end
|
||||
end
|
||||
|
||||
update_customer
|
||||
-- Update an existing customer.
|
||||
local
|
||||
factory: PS_CRITERION_FACTORY
|
||||
query: PS_QUERY [CUSTOMER]
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
create query.make
|
||||
query.set_criterion (factory ("id", "=", 42))
|
||||
|
||||
transaction := repository.new_transaction
|
||||
|
||||
if not transaction.has_error then
|
||||
transaction.execute_query (query)
|
||||
end
|
||||
|
||||
across
|
||||
query as cursor
|
||||
loop
|
||||
cursor.item.set_age (2)
|
||||
-- The result is an update, because an object
|
||||
-- with primary key 42 is already present.
|
||||
transaction.insert (cursor.item)
|
||||
end
|
||||
|
||||
-- Cleanup and error handling
|
||||
query.close
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
if transaction.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
== Managed types==
|
||||
|
||||
Maybe you realized the weak spot in the previous section:
|
||||
We assumed that a primary key does not exist yet.
|
||||
This is a very dangerous assumption, especially in a multi-user setting.
|
||||
The way to resolve this issue is to usually to declare the primary key column as auto-incremented and let the database handle primary key generation.
|
||||
|
||||
It is possible to use this facility in ABEL by declaring a type as "managed" and specifying the primary key column.
|
||||
This only works for tables which actually have an auto-incremented integer primary key column.
|
||||
|
||||
There are some changes when declaring a type as managed:
|
||||
|
||||
* ABEL will keep track of object identity. Thus it is possible (and recommended) to use {PS_TRANSACTION}.update.
|
||||
* As ABEL now takes care of primary keys, it is not allowed to change the primary key of an object. If it happens anyway, an error will be returned.
|
||||
* To insert a new object, you can just set the primary key attribute to zero. The database will then generate a new key.
|
||||
* After a successful insert, ABEL will update the Eiffel object with the new primary key.
|
||||
|
||||
Our customer database table fulfills all requirements for ABEL to manage its primary key handling, thus we can rewrite the above examples:
|
||||
|
||||
<code>
|
||||
class
|
||||
TUTORIAL
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access
|
||||
|
||||
repository: PS_REPOSITORY
|
||||
|
||||
generated_id: INTEGER
|
||||
-- The ID generated by the database.
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Set up the repository.
|
||||
local
|
||||
factory: PS_MYSQL_RELATIONAL_REPOSITORY_FACTORY
|
||||
do
|
||||
create factory.make
|
||||
factory.set_database ("my_database")
|
||||
factory.set_user ("root")
|
||||
factory.set_password ("1234")
|
||||
|
||||
-- Tell ABEL to manage the `CUSTOMER' type.
|
||||
factory.manage ({CUSTOMER}, "id")
|
||||
|
||||
repository := factory.new_repository
|
||||
|
||||
insert_customer
|
||||
update_customer
|
||||
end
|
||||
|
||||
feature -- Tutorial functions
|
||||
|
||||
insert_customer
|
||||
-- Insert a new customer.
|
||||
local
|
||||
albo: CUSTOMER
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
-- Note that the ID is now set to 0.
|
||||
create albo.make (0, "Albo", "Bitossi", 1)
|
||||
|
||||
transaction := repository.new_transaction
|
||||
|
||||
if not transaction.has_error then
|
||||
-- The next statement will be an insert in any case.
|
||||
transaction.insert (albo)
|
||||
end
|
||||
|
||||
-- The generated ID is now stored in `albo'
|
||||
generated_id := albo.id
|
||||
|
||||
-- Cleanup and error handling.
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
if transaction.has_error then
|
||||
print ("An error occurred.%N")
|
||||
end
|
||||
end
|
||||
|
||||
update_customer
|
||||
-- Update an existing customer.
|
||||
local
|
||||
factory: PS_CRITERION_FACTORY
|
||||
query: PS_QUERY [CUSTOMER]
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
create factory
|
||||
create query.make
|
||||
query.set_criterion (factory ("id", "=", generated_id))
|
||||
|
||||
transaction := repository.new_transaction
|
||||
|
||||
if not transaction.has_error then
|
||||
transaction.execute_query (query)
|
||||
end
|
||||
|
||||
across
|
||||
query as cursor
|
||||
loop
|
||||
cursor.item.set_age (3)
|
||||
-- It is possible to call update.
|
||||
transaction.update (cursor.item)
|
||||
end
|
||||
|
||||
-- Cleanup and error handling
|
||||
query.close
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
if transaction.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
[[Property:title|Advanced Queries]]
|
||||
[[Property:weight|-9]]
|
||||
[[Property:uuid|a630a4bb-6b25-54ef-085b-7e18050a21a2]]
|
||||
==The query mechanism==
|
||||
|
||||
As you already know from [[Basic Operations]], queries to a database are done by creating a <e>PS_QUERY[G]</e> object
|
||||
and executing it against a <e>PS_TRANSACTION</e> or <e>PS_REPOSITORY</e>.
|
||||
The actual value of the generic parameter <e>G</e> determines the type of the objects that will be returned.
|
||||
By default objects of a subtype of <e>G</e> will also be included in the result set.
|
||||
|
||||
ABEL will by default load an object completely, meaning all objects that can be reached by following references will be loaded as well (see also [[Dealing with References]]).
|
||||
|
||||
==Criteria==
|
||||
|
||||
You can filter your query results by setting criteria in the query object, using feature <e>set_criterion</e> in <e>PS_QUERY</e>.
|
||||
There are two types of criteria: predefined and agent criteria.
|
||||
|
||||
===Predefined Criteria===
|
||||
When using a predefined criterion you pick an attribute name, an operator and a value.
|
||||
During a read operation, ABEL checks the attribute value of the freshly retrieved object against the value set in the criterion, and filters away objects that don't satisfy the criterion.
|
||||
|
||||
Most of the supported operators are pretty self-describing (see class <e>PS_CRITERION_FACTORY</e> below).
|
||||
An exception could be the '''like''' operator, which does pattern-matching on strings.
|
||||
You can provide the '''like''' operator with a pattern as a value. The pattern can contain the wildcard characters '''*''' and '''?'''.
|
||||
The asterisk stands for any number (including zero) of undefined characters, and the question mark means exactly one undefined character.
|
||||
|
||||
You can only use attributes that are strings or numbers, but not every type of attribute supports every other operator.
|
||||
Valid combinations for each type are:
|
||||
|
||||
* Strings: =, like
|
||||
* Any numeric value: =, <, <=, >, >=
|
||||
* Booleans: =
|
||||
|
||||
Note that for performance reasons it is usually better to use predefined criteria, because they can be compiled to SQL and hence the result can be filtered in the database.
|
||||
|
||||
===Agent Criteria===
|
||||
An agent criterion will filter the objects according to the result of an agent applied to them.
|
||||
|
||||
The criterion is initialized with an agent of type <e>PREDICATE [ANY, TUPLE [ANY]]</e>.
|
||||
There should be either an open target or a single open argument, and the type of the objects in the query result should conform to the agent's open operand.
|
||||
|
||||
==Creating criteria objects==
|
||||
The criteria instances are best created using the <e>PS_CRITERION_FACTORY</e> class.
|
||||
|
||||
The main features of the class are the following:
|
||||
|
||||
<code>
|
||||
class
|
||||
PS_CRITERION_FACTORY
|
||||
create
|
||||
default_create
|
||||
|
||||
feature -- Creating a criterion
|
||||
|
||||
new_criterion alias "()" (tuple: TUPLE [ANY]): PS_CRITERION
|
||||
-- Creates a new criterion according to a `tuple'
|
||||
-- containing either a single PREDICATE or three
|
||||
-- values of type [STRING, STRING, ANY].
|
||||
|
||||
new_agent (a_predicate: PREDICATE [ANY, TUPLE [ANY]]): PS_CRITERION
|
||||
-- Creates an agent criterion.
|
||||
|
||||
new_predefined (object_attribute: STRING; operator: STRING; value: ANY): PS_CRITERION
|
||||
-- Creates a predefined criterion.
|
||||
|
||||
feature -- Operators
|
||||
|
||||
equals: STRING = "="
|
||||
|
||||
greater: STRING = ">"
|
||||
|
||||
greater_equal: STRING = ">="
|
||||
|
||||
less: STRING = "<"
|
||||
|
||||
less_equal: STRING = "<="
|
||||
|
||||
like_string: STRING = "like"
|
||||
|
||||
end
|
||||
</code>
|
||||
|
||||
Assuming you have an object <e>factory: PS_CRITERION_FACTORY</e>, to create a new criterion you have two possibilities:
|
||||
|
||||
* The "traditional" way
|
||||
** <e> factory.new_agent (agent an_agent) </e>
|
||||
** <e> factory.new_predefined (an_attr_name, an_operator, a_val) </e>
|
||||
* The "syntactic sugar" way
|
||||
** <e> factory (an_attr_name, an_operator, a_value) </e>
|
||||
** <e> factory (agent an_agent) </e>
|
||||
|
||||
<code>
|
||||
create_criteria_traditional : PS_CRITERION
|
||||
-- Create a new criteria using the traditional approach.
|
||||
do
|
||||
-- for predefined criteria
|
||||
Result:= factory.new_predefined ("age", factory.less, 5)
|
||||
|
||||
-- for agent criteria
|
||||
Result := factory.new_agent (agent age_more_than (?, 5))
|
||||
end
|
||||
|
||||
create_criteria_parenthesis : PS_CRITERION
|
||||
-- Create a new criteria using parenthesis alias.
|
||||
do
|
||||
-- for predefined criteria
|
||||
Result:= factory ("age", factory.less, 5)
|
||||
|
||||
-- for agent criteria
|
||||
Result := factory (agent age_more_than (?, 5))
|
||||
end
|
||||
|
||||
age_more_than (person: PERSON; age: INTEGER): BOOLEAN
|
||||
-- An example agent
|
||||
do
|
||||
Result:= person.age > age
|
||||
end
|
||||
|
||||
</code>
|
||||
|
||||
===Combining criteria===
|
||||
|
||||
You can combine multiple criterion objects by using the standard Eiffel logical operators.
|
||||
For example, if you want to search for a person called "Albo Bitossi" with <e>age <= 20</e>, you can just create a criterion object for each of the constraints and combine them:
|
||||
|
||||
<code>
|
||||
composite_search_criterion : PS_CRITERION
|
||||
-- Combining criterion objects.
|
||||
local
|
||||
first_name_criterion: PS_CRITERION
|
||||
last_name_criterion: PS_CRITERION
|
||||
age_criterion: PS_CRITERION
|
||||
do
|
||||
first_name_criterion:= factory ("first_name", factory.equals, "Albo")
|
||||
|
||||
last_name_criterion := factory ("last_name", factory.equals, "Bitossi")
|
||||
|
||||
age_criterion := factory (agent age_more_than (?, 20))
|
||||
|
||||
Result := first_name_criterion and last_name_criterion and not age_criterion
|
||||
|
||||
-- Shorter version:
|
||||
Result := factory ("first_name", "=", "Albo")
|
||||
and factory ("last_name", "=", "Bitossi")
|
||||
and not factory (agent age_more_than (?, 20))
|
||||
end
|
||||
</code>
|
||||
|
||||
ABEL supports the three standard logical operators <e>and</e>, <e>or</e> and <e>not</e>.
|
||||
The precedence rules are the same as in Eiffel, which means that <e>not</e> is stronger than <e>and</e>, which in turn is stronger than <e>or</e>.
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
[[Property:title|Basic operations]]
|
||||
[[Property:weight|-12]]
|
||||
[[Property:uuid|8f8179e4-c9dc-7749-ce88-cde695f32d53]]
|
||||
==Inserting==
|
||||
|
||||
You can insert a new object using feature <e>insert</e> in <e>PS_TRANSACTION</e>.
|
||||
As every write operation in ABEL needs to be embedded in a transaction, you first need to create a <e>PS_TRANSACTION</e> object.
|
||||
Let's add three new persons to the database:
|
||||
|
||||
<code>
|
||||
insert_persons
|
||||
-- Populate the repository with some person objects.
|
||||
local
|
||||
p1, p2, p3: PERSON
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
-- Create persons
|
||||
create p1.make (...)
|
||||
create ...
|
||||
|
||||
-- We first need a new transaction.
|
||||
transaction := repository.new_transaction
|
||||
|
||||
-- Now we can insert all three persons.
|
||||
if not transaction.has_error then
|
||||
transaction.insert (p1)
|
||||
end
|
||||
if not transaction.has_error then
|
||||
transaction.insert (p2)
|
||||
end
|
||||
if not transaction.has_error then
|
||||
transaction.insert (p3)
|
||||
end
|
||||
|
||||
-- Commit the changes.
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
-- Check for errors.
|
||||
if transaction.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
==Querying==
|
||||
A query for objects is done by creating a <e>PS_QUERY [G]</e> object and executing it using features of <e>PS_REPOSITORY</e> or <e>PS_TRANSACTION</e>.
|
||||
The generic parameter <e>G</e> denotes the type of objects that should be queried.
|
||||
|
||||
After a successful execution of the query, you can iterate over the result using the <e>across</e> syntax.
|
||||
The feature <e>print_persons</e> below shows how to get and print a list of persons from the repository:
|
||||
|
||||
<code>
|
||||
print_persons
|
||||
-- Print all persons in the repository
|
||||
local
|
||||
query: PS_QUERY[PERSON]
|
||||
do
|
||||
-- First create a query for PERSON objects.
|
||||
create query.make
|
||||
|
||||
-- Execute it against the repository.
|
||||
repository.execute_query (query)
|
||||
|
||||
-- Iterate over the result.
|
||||
across
|
||||
query as person_cursor
|
||||
loop
|
||||
print (person_cursor.item)
|
||||
end
|
||||
|
||||
-- Check for errors.
|
||||
if query.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
|
||||
-- Don't forget to close the query.
|
||||
query.close
|
||||
end
|
||||
</code>
|
||||
|
||||
In a real database the result of a query may be very big, and you are probably only interested in objects that meet certain criteria, e.g. all persons of age 20.
|
||||
You can read more about it in [[Advanced Queries]].
|
||||
|
||||
Please note that ABEL does not enforce any kind of order on a query result.
|
||||
|
||||
==Updating==
|
||||
|
||||
Updating an object is done through feature <e>update</e> in <e>PS_TRANSACTION</e>.
|
||||
Like the insert operation, an update needs to happen within a transaction.
|
||||
Note that in order to <e>update</e> an object, we first have to retrieve it.
|
||||
|
||||
Let's update the <e>age</e> attribute of Berno Citrini by celebrating his birthday:
|
||||
|
||||
<code>
|
||||
update_berno_citrini
|
||||
-- Increase the age of Berno Citrini by one.
|
||||
local
|
||||
query: PS_QUERY[PERSON]
|
||||
transaction: PS_TRANSACTION
|
||||
berno: PERSON
|
||||
do
|
||||
print ("Updating Berno Citrini's age by one.%N")
|
||||
|
||||
-- Create query and transaction.
|
||||
create query.make
|
||||
transaction := repository.new_transaction
|
||||
|
||||
-- As we're doing a read followed by a write, we
|
||||
-- need to execute the query within a transaction.
|
||||
if not transaction.has_error then
|
||||
transaction.execute_query (query)
|
||||
end
|
||||
|
||||
-- Search for Berno Citrini
|
||||
across
|
||||
query as cursor
|
||||
loop
|
||||
if cursor.item.first_name ~ "Berno" then
|
||||
berno := cursor.item
|
||||
|
||||
-- Change the object.
|
||||
berno.celebrate_birthday
|
||||
|
||||
-- Perform the database update.
|
||||
transaction.update (berno)
|
||||
end
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
query.close
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
if transaction.has_error then
|
||||
print ("An error occurred.%N")
|
||||
end
|
||||
end
|
||||
|
||||
</code>
|
||||
|
||||
To perform an update the object first needs to be retrieved or inserted within the same transaction.
|
||||
Otherwise ABEL cannot map the Eiffel object to its database counterpart.
|
||||
|
||||
==Deleting==
|
||||
|
||||
ABEL does not support explicit deletes any longer, as it is considered dangerous for shared objects.
|
||||
Instead of deletion it is planned to introduce a garbage collection mechanism in the
|
||||
|
||||
==Dealing with Known Objects==
|
||||
|
||||
Within a transaction ABEL keeps track of objects that have been inserted or queried.
|
||||
This is important because in case of an update, the library internally needs to map the object in the current execution of the program to its specific entry in the database.
|
||||
|
||||
Because of that, you can't update an object that is not yet known to ABEL.
|
||||
As an example, the following functions will fail:
|
||||
|
||||
<code>
|
||||
failing_update
|
||||
-- Trying to update a new person object.
|
||||
local
|
||||
bob: PERSON
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
create bob.make ("Robert", "Baratheon")
|
||||
transaction := repository.new_transaction
|
||||
-- Error: Bob was not inserted / retrieved before.
|
||||
-- The result is a precondition violation.
|
||||
transaction.update (bob)
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
update_after_commit
|
||||
-- Update after transaction committed.
|
||||
local
|
||||
joff: PERSON
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
create joff.make ("Joffrey", "Baratheon")
|
||||
transaction := repository.new_transaction
|
||||
transaction.insert (joff)
|
||||
transaction.commit
|
||||
|
||||
joff.celebrate_birthday
|
||||
|
||||
-- Prepare can be used to restart a transaction.
|
||||
transaction.prepare
|
||||
|
||||
-- Error: Joff was not inserted / retrieved before.
|
||||
-- The result is a precondition violation.
|
||||
transaction.update (joff)
|
||||
|
||||
-- Note: After commit and prepare,`transaction'
|
||||
-- represents a completely new transaction.
|
||||
end
|
||||
</code>
|
||||
|
||||
The feature <e>is_persistent</e> in <e>PS_TRANSACTION</e> can tell you if a specific object is known to ABEL and hence has a link to its entry in the database.
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
[[Property:title|Dealing with references]]
|
||||
[[Property:weight|-6]]
|
||||
[[Property:uuid|ebf8e495-8180-bad2-f063-54fb949298e0]]
|
||||
In ABEL, a basic type is an object of type <e>STRING</e>, <e>BOOLEAN</e>, <e>CHARACTER</e> or any numeric class like <e>REAL</e> or <e>INTEGER</e>.
|
||||
The <e>PERSON</e> class only has attributes of a basic type.
|
||||
However, an object can contain references to other objects. ABEL is able to handle these references by storing and reconstructing the whole object graph
|
||||
(an object graph is roughly defined as all the objects that can be reached by recursively following all references, starting at some root object).
|
||||
|
||||
==Inserting objects with references==
|
||||
Let's look at the new class <e>CHILD</e>:
|
||||
|
||||
<code>
|
||||
class
|
||||
CHILD
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (first, last: STRING)
|
||||
-- Create a new child.
|
||||
require
|
||||
first_exists: not first.is_empty
|
||||
last_exists: not last.is_empty
|
||||
do
|
||||
first_name := first
|
||||
last_name := last
|
||||
age := 0
|
||||
ensure
|
||||
first_name_set: first_name = first
|
||||
last_name_set: last_name = last
|
||||
default_age: age = 0
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
first_name: STRING
|
||||
-- The child's first name.
|
||||
|
||||
last_name: STRING
|
||||
-- The child's last name.
|
||||
|
||||
age: INTEGER
|
||||
-- The child's age.
|
||||
|
||||
father: detachable CHILD
|
||||
-- The child's father.
|
||||
|
||||
feature -- Element Change
|
||||
|
||||
celebrate_birthday
|
||||
-- Increase age by 1.
|
||||
do
|
||||
age := age + 1
|
||||
ensure
|
||||
age_incremented_by_one: age = old age + 1
|
||||
end
|
||||
|
||||
set_father (a_father: CHILD)
|
||||
-- Set a father for the child.
|
||||
do
|
||||
father := a_father
|
||||
ensure
|
||||
father_set: father = a_father
|
||||
end
|
||||
|
||||
invariant
|
||||
age_non_negative: age >= 0
|
||||
first_name_exists: not first_name.is_empty
|
||||
last_name_exists: not last_name.is_empty
|
||||
end
|
||||
</code>
|
||||
|
||||
This adds in some complexity:
|
||||
Instead of having a single object, ABEL has to insert a <e>CHILD</e>'s mother and father as well, and it has to repeat this procedure if their parent attribute is also attached.
|
||||
The good news are that the examples above will work exactly the same.
|
||||
|
||||
However, there are some additional caveats to take into consideration.
|
||||
Let's consider a simple example with <e>CHILD</e> objects ''Baby Doe'', ''John Doe'' and ''Grandpa Doe''.
|
||||
From the name of the object instances you can already guess what the object graph looks like:
|
||||
|
||||
[[Image: Child object graph | center | 700px]]
|
||||
|
||||
Now if you insert ''Baby Doe'', ABEL will by default follow all references and insert every single object along the object graph, which means that ''John Doe'' and ''Grandpa Doe'' will be inserted as well.
|
||||
This is usually the desired behavior, as objects are stored completely that way, but it also has some side effects we need to be aware of:
|
||||
|
||||
* Assume an insert of ''Baby Doe'' has happened to an empty database. If you now query the database for <e>CHILD</e> objects, it will return exactly the same object graph as above, but the query result will actually have three items, as the object graph consists of three single <e>CHILD</e> objects.
|
||||
* The insert of ''John Doe'' and ''Grandpa Doe'', after inserting ''Baby Doe'', is internally changed to an update operation because both objects are already in the database. This might result in some undesired overhead which can be avoided if you know the object structure.
|
||||
|
||||
In our main tutorial class <e>START</e> we have the following two features that show how to deal with object graphs.
|
||||
You will notice it is very similar to the corresponding routines for the flat <e>PERSON</e> objects.
|
||||
|
||||
<code>
|
||||
insert_children
|
||||
-- Populate the repository with some children objects.
|
||||
local
|
||||
c1, c2, c3: CHILD
|
||||
transaction: PS_TRANSACTION
|
||||
do
|
||||
-- Create the object graph.
|
||||
create c1.make ("Baby", "Doe")
|
||||
create c2.make ("John", "Doe")
|
||||
create c3.make ("Grandpa", "Doe")
|
||||
c1.set_father (c2)
|
||||
c2.set_father (c3)
|
||||
|
||||
print ("Insert 3 children in the database.%N")
|
||||
transaction := repository.new_transaction
|
||||
|
||||
-- It is sufficient to just insert "Baby Joe",
|
||||
-- as the other CHILD objects are (transitively)
|
||||
-- referenced and thus inserted automatically.
|
||||
if not transaction.has_error then
|
||||
transaction.insert (c1)
|
||||
end
|
||||
|
||||
if not transaction.has_error then
|
||||
transaction.commit
|
||||
end
|
||||
|
||||
if transaction.has_error then
|
||||
print ("An error occurred during insert!%N")
|
||||
end
|
||||
end
|
||||
|
||||
print_children
|
||||
-- Print all children in the repository
|
||||
local
|
||||
query: PS_QUERY[CHILD]
|
||||
do
|
||||
create query.make
|
||||
repository.execute_query (query)
|
||||
|
||||
-- The result will also contain
|
||||
-- all referenced CHILD objects.
|
||||
across
|
||||
query as person_cursor
|
||||
loop
|
||||
print (person_cursor.item)
|
||||
end
|
||||
|
||||
query.close
|
||||
end
|
||||
</code>
|
||||
|
||||
==Going deeper in the Object Graph==
|
||||
ABEL has no limits regarding the depth of an object graph, and it will detect and handle reference cycles correctly.
|
||||
You are welcome to test ABEL's capability with very complex objects, however please keep in mind that this may impact performance significantly.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
[[Property:title|Error handling]]
|
||||
[[Property:weight|0]]
|
||||
[[Property:uuid|5f3afec1-939d-b1f5-3715-8bbd2f44ce79]]
|
||||
As ABEL is dealing with I/O and databases, a runtime error may happen at any time.
|
||||
ABEL will inform you of an error by setting the <e>has_error</e> attribute to True in <e>PS_QUERY</e> or <e>PS_TUPLE_QUERY</e> and,
|
||||
if available, in <e>PS_TRANSACTION</e>.
|
||||
The attribute should always be checked in the following cases:
|
||||
|
||||
* Before invoking a library command.
|
||||
* After a transaction commit.
|
||||
* After iterating over the result of a read-only query.
|
||||
|
||||
ABEL maps database specific error messages to its own representation for errors, which is a hierarchy of classes rooted at <e>PS_ERROR</e>.
|
||||
In case of an error, you can find an ABEL error description in the <e>error</e> attribute in all classes suppoorting the <e>has_error</e> attribute.
|
||||
The following list shows all error classes that are currently defined with some examples (the <e>PS_</e> prefix is omitted for brevity):
|
||||
|
||||
* CONNECTION_SETUP_ERROR: No internet link, or a deleted serialization file.
|
||||
* AUTHORIZATION_ERROR: Usually a wrong password.
|
||||
* BACKEND_ERROR: An unrecoverable error in the storage backend, e.g. a disk failure.
|
||||
* INTERNAL_ERROR: Any error happening inside ABEL.
|
||||
* PS_OPERATION_ERROR: For invalid operations, e.g. no access rights to a table.
|
||||
* TRANSACTION_ABORTED_ERROR: A conflict between two transactions.
|
||||
* MESSAGE_NOT_UNDERSTOOD_ERROR: Malformed SQL or JSON statements.
|
||||
* INTEGRITY_CONSTRAINT_VIOLATION_ERROR: The operation violates an integrity constraint in the database.
|
||||
* EXTERNAL_ROUTINE_ERROR: An SQL routine or triggered action has failed.
|
||||
* VERSION_MISMATCH: The stored version of an object isn't compatible any more to the current type.
|
||||
|
||||
For your convenience, there is a visitor pattern for all ABEL error types.
|
||||
You can just implement the appropriate functions and use it for your error handling code.
|
||||
|
||||
<code>
|
||||
class
|
||||
MY_PRIVATE_VISITOR
|
||||
|
||||
inherit
|
||||
PS_DEFAULT_ERROR_VISITOR
|
||||
redefine
|
||||
visit_transaction_aborted_error,
|
||||
visit_connection_setup_error
|
||||
end
|
||||
|
||||
feature -- Visitor features
|
||||
|
||||
visit_transaction_aborted_error (transaction_aborted_error: PS_TRANSACTION_ABORTED_ERROR)
|
||||
-- Visit a transaction aborted error
|
||||
do
|
||||
print ("Transaction aborted")
|
||||
end
|
||||
|
||||
visit_connection_setup_error (connection_setup_error: PS_CONNECTION_SETUP_ERROR)
|
||||
-- Visit a connection setup error
|
||||
do
|
||||
print ("Wrong login")
|
||||
end
|
||||
|
||||
end
|
||||
</code>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
[[Property:title|Getting started]]
|
||||
[[Property:weight|-15]]
|
||||
[[Property:uuid|60a4120c-3994-c9b9-d22e-af7c6fe12147]]
|
||||
== Setting things up ==
|
||||
|
||||
This tutorial only works with ABEL shipped with EiffelStudio 14.05.
|
||||
You can find the code in the ''unstable'' directory.
|
||||
|
||||
It is also possible to get the latest code from the [https://svn.eiffel.com/eiffelstudio/trunk/Src/unstable/library/persistency/abel SVN directory] .
|
||||
|
||||
== Getting started ==
|
||||
We will be using <e>PERSON </e> objects to show the usage of the API.
|
||||
In the source code below you will see that ABEL handles objects "as they are",
|
||||
meaning that to make them persistent you don't need to add any dependencies to their class source code.
|
||||
|
||||
<code>
|
||||
class PERSON
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (first, last: STRING)
|
||||
-- Create a newborn person.
|
||||
require
|
||||
first_exists: not first.is_empty
|
||||
last_exists: not last.is_empty
|
||||
do
|
||||
first_name := first
|
||||
last_name := last
|
||||
age:= 0
|
||||
ensure
|
||||
first_name_set: first_name = first
|
||||
last_name_set: last_name = last
|
||||
default_age: age = 0
|
||||
end
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
celebrate_birthday
|
||||
-- Increase age by 1.
|
||||
do
|
||||
age:= age + 1
|
||||
ensure
|
||||
age_incremented_by_one: age = old age + 1
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
first_name: STRING
|
||||
-- The person's first name.
|
||||
|
||||
last_name: STRING
|
||||
-- The person's last name.
|
||||
|
||||
age: INTEGER
|
||||
-- The person's age.
|
||||
|
||||
invariant
|
||||
age_non_negative: age >= 0
|
||||
first_name_exists: not first_name.is_empty
|
||||
last_name_exists: not last_name.is_empty
|
||||
end
|
||||
</code>
|
||||
|
||||
There are three very important classes in ABEL:
|
||||
* The deferred class <e>PS_REPOSITORY</e> provides an abstraction to the actual storage mechanism. It can only be used for read operations.
|
||||
* The <e>PS_TRANSACTION</e> class represents a transaction and can be used to execute read, insert and update operations. Any <e>PS_TRANSACTION</e> object is bound to a <e>PS_REPOSITORY</e>.
|
||||
* The <e>PS_QUERY [G]</e> class is used to describe a read operation for objects of type <e>G</e>.
|
||||
|
||||
To start using the library, we first need to create a <e>PS_REPOSITORY</e>.
|
||||
For this tutorial we are going to use an in-memory repository to avoid setting up any external database.
|
||||
Each ABEL backend will ship a repository factory class to make initialization easier.
|
||||
The factory for the in-memory repository is called <e>PS_IN_MEMORY_REPOSITORY_FACTORY</e>.
|
||||
|
||||
<code>
|
||||
class START
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Initialization for `Current'.
|
||||
local
|
||||
factory: PS_IN_MEMORY_REPOSITORY_FACTORY
|
||||
do
|
||||
create factory.make
|
||||
repository := factory.new_repository
|
||||
|
||||
create criterion_factory
|
||||
explore
|
||||
end
|
||||
|
||||
repository: PS_REPOSITORY
|
||||
-- The main repository.
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
</code>
|
||||
We will use <e>criterion_factory</e> later in this tutorial.
|
||||
The feature <e>explore</e> will guide us through the rest of this API tutorial and show the possibilities in ABEL.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[[Property:title|Tutorial]]
|
||||
[[Property:weight|0]]
|
||||
[[Property:uuid|d24ce584-52d5-36bb-adb2-68c67c843072]]
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
[[Property:title|Tuple queries]]
|
||||
[[Property:weight|-3]]
|
||||
[[Property:uuid|69046f75-708f-fc1e-b0be-1a17e22751e4]]
|
||||
Consider a scenario in which you just want to have a list of all first names of <e>CHILD</e> objects in the database.
|
||||
Loading every attribute of each object of type <e>CHILD</e> might lead to a very bad performance, especially if there is a big object graph attached to each <e>CHILD</e> object.
|
||||
|
||||
To solve this problem ABEL allows queries which return data in <e>TUPLE</e> objects.
|
||||
Tuple queries are executed by calling <e>execute_tuple_query (a_tuple_query)</e> in either <e>PS_REPOSITORY</e> or <e>PS_TRANSACTION</e>,
|
||||
where <e>a_tuple_query</e> is of type <e>PS_TUPLE_QUERY [G]</e>.
|
||||
The result is an iteration cursor over a list of tuples in which the attributes of an object are stored.
|
||||
|
||||
==Tuple queries and projections==
|
||||
The <e>projection</e> feature in a <e>PS_TUPLE_QUERY</e> defines which attributes shall be included in the result <e>TUPLE</e>.
|
||||
Additionally, the order of the attributes in the projection array is the same as the order of the elements in the result tuples.
|
||||
|
||||
By default, a <e>PS_TUPLE_QUERY</e> object will only return values of attributes which are of a basic type, so no references are followed during a retrieve.
|
||||
You can change this default by calling <e>set_projection</e>.
|
||||
If you include an attribute name whose type is not a basic one, ABEL will actually retrieve and build the attribute object, and not just another tuple.
|
||||
|
||||
==Tuple queries and criteria==
|
||||
You are restricted to use predefined criteria in tuple queries, because agent criteria expect an object and not a tuple.
|
||||
You can still combine them with logical operators, and even include a predefined criterion on an attribute that is not present in the projection list.
|
||||
These attributes will be loaded internally to check if the object satisfies the criterion, and then they are discarded for the actual result.
|
||||
|
||||
==Example==
|
||||
|
||||
<code>
|
||||
explore_tuple_queries
|
||||
-- See what can be done with tuple queries.
|
||||
local
|
||||
query: PS_TUPLE_QUERY [CHILD]
|
||||
transaction: PS_TRANSACTION
|
||||
projection: ARRAYED_LIST [STRING]
|
||||
do
|
||||
-- Tuple queries are very similar to normal queries.
|
||||
-- I.e. you can query for CHILD objects by creating
|
||||
-- a PS_TUPLE_QUERY [CHILD]
|
||||
create query.make
|
||||
|
||||
-- It is also possible to add criteria. Agent criteria
|
||||
-- are not supportedfor tuple queries however.
|
||||
-- Lets search for CHILD objects with last name Doe.
|
||||
query.set_criterion (criterion_factory ("last_name", criterion_factory.equals, "Doe"))
|
||||
|
||||
-- The big advantage of tuple queries is that you can
|
||||
-- define which attributes should be loaded.
|
||||
-- Thus you can avoid loading a whole object graph
|
||||
-- if you're just interested in e.g. the first name.
|
||||
create projection.make_from_array (<<"first_name">>)
|
||||
query.set_projection (projection)
|
||||
|
||||
-- Execute the tuple query.
|
||||
repository.execute_tuple_query (query)
|
||||
|
||||
-- The result of the query is a TUPLE containing the
|
||||
-- requested attribute.
|
||||
|
||||
print ("Print all first names using a tuple query:%N")
|
||||
|
||||
across
|
||||
query as cursor
|
||||
loop
|
||||
-- It is possible to downcast the TUPLE
|
||||
-- to a tagged tuple with correct type.
|
||||
check attached {TUPLE [first_name: STRING]} cursor.item as tuple then
|
||||
print (tuple.first_name + "%N")
|
||||
end
|
||||
end
|
||||
|
||||
-- Cleanup and error handling.
|
||||
query.close
|
||||
if query.has_error then
|
||||
print ("An error occurred!%N")
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
Reference in New Issue
Block a user