From 7b73a19a9e0bec3448b5e75d6241f12deadc691d Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 16 Jan 2014 22:47:41 +0000 Subject: [PATCH] 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 --- .../solutions/_images/object_graph.png | Bin 0 -> 25273 bytes .../solutions/_images/object_graph.png.data | 3 + .../tutorial/accessing-existing-database.wiki | 350 ++++++++++++++++++ .../abel/tutorial/advanced-queries.wiki | 151 ++++++++ .../abel/tutorial/basic-operations.wiki | 202 ++++++++++ .../abel/tutorial/dealing-references.wiki | 150 ++++++++ .../abel/tutorial/error-handling.wiki | 58 +++ .../abel/tutorial/getting-started.wiki | 106 ++++++ .../database-access/abel/tutorial/index.wiki | 5 + .../abel/tutorial/tuple-queries.wiki | 77 ++++ 10 files changed, 1102 insertions(+) create mode 100644 documentation/current/solutions/_images/object_graph.png create mode 100644 documentation/current/solutions/_images/object_graph.png.data create mode 100644 documentation/current/solutions/database-access/abel/tutorial/accessing-existing-database.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/advanced-queries.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/basic-operations.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/dealing-references.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/error-handling.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/getting-started.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/index.wiki create mode 100644 documentation/current/solutions/database-access/abel/tutorial/tuple-queries.wiki diff --git a/documentation/current/solutions/_images/object_graph.png b/documentation/current/solutions/_images/object_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..f32608d00a828cb7593d580a90a76c0bc267eacd GIT binary patch literal 25273 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 Q^DnO`t0q(X-aPF80gWZu6#xJL literal 0 HcmV?d00001 diff --git a/documentation/current/solutions/_images/object_graph.png.data b/documentation/current/solutions/_images/object_graph.png.data new file mode 100644 index 00000000..47d96df3 --- /dev/null +++ b/documentation/current/solutions/_images/object_graph.png.data @@ -0,0 +1,3 @@ +title=Child object graph +author=Roman +path=content/child-object-graph diff --git a/documentation/current/solutions/database-access/abel/tutorial/accessing-existing-database.wiki b/documentation/current/solutions/database-access/abel/tutorial/accessing-existing-database.wiki new file mode 100644 index 00000000..eb6d14c6 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/accessing-existing-database.wiki @@ -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: + + +CREATE TABLE customer ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + first_name VARCHAR (100), + last_name VARCHAR (100), + age INTEGER +) + + +In your Eiffel code you have a class which matches the table: + + +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 + + +== Connection setup == + +Because we're using an existing MySQL database, we need to choose the PS_MYSQL_RELATIONAL_REPOSITORY_FACTORY 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 CUSTOMER objects. +The procedure is the same as seen in [[Basic operations]]. + + + + 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 + + +== 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 {PS_TRANSACTION}.insert to write an object. +The semantics of insert 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. + + + 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 + + +== 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: + + +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 + + + diff --git a/documentation/current/solutions/database-access/abel/tutorial/advanced-queries.wiki b/documentation/current/solutions/database-access/abel/tutorial/advanced-queries.wiki new file mode 100644 index 00000000..a2914690 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/advanced-queries.wiki @@ -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 PS_QUERY[G] object +and executing it against a PS_TRANSACTION or PS_REPOSITORY. +The actual value of the generic parameter G determines the type of the objects that will be returned. +By default objects of a subtype of G 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 set_criterion in PS_QUERY. +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 PS_CRITERION_FACTORY 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 PREDICATE [ANY, TUPLE [ANY]]. +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 PS_CRITERION_FACTORY class. + +The main features of the class are the following: + + +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 + + +Assuming you have an object factory: PS_CRITERION_FACTORY, to create a new criterion you have two possibilities: + +* The "traditional" way +** factory.new_agent (agent an_agent) +** factory.new_predefined (an_attr_name, an_operator, a_val) +* The "syntactic sugar" way +** factory (an_attr_name, an_operator, a_value) +** factory (agent an_agent) + + + 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 + + + +===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 age <= 20, you can just create a criterion object for each of the constraints and combine them: + + + 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 + + +ABEL supports the three standard logical operators and, or and not. +The precedence rules are the same as in Eiffel, which means that not is stronger than and, which in turn is stronger than or. + diff --git a/documentation/current/solutions/database-access/abel/tutorial/basic-operations.wiki b/documentation/current/solutions/database-access/abel/tutorial/basic-operations.wiki new file mode 100644 index 00000000..35015054 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/basic-operations.wiki @@ -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 insert in PS_TRANSACTION. +As every write operation in ABEL needs to be embedded in a transaction, you first need to create a PS_TRANSACTION object. +Let's add three new persons to the database: + + + 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 + + +==Querying== +A query for objects is done by creating a PS_QUERY [G] object and executing it using features of PS_REPOSITORY or PS_TRANSACTION. +The generic parameter G denotes the type of objects that should be queried. + +After a successful execution of the query, you can iterate over the result using the across syntax. +The feature print_persons below shows how to get and print a list of persons from the repository: + + + 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 + + +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 update in PS_TRANSACTION. +Like the insert operation, an update needs to happen within a transaction. +Note that in order to update an object, we first have to retrieve it. + +Let's update the age attribute of Berno Citrini by celebrating his birthday: + + + 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 + + + +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: + + + 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 + + +The feature is_persistent in PS_TRANSACTION can tell you if a specific object is known to ABEL and hence has a link to its entry in the database. + + diff --git a/documentation/current/solutions/database-access/abel/tutorial/dealing-references.wiki b/documentation/current/solutions/database-access/abel/tutorial/dealing-references.wiki new file mode 100644 index 00000000..facc2347 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/dealing-references.wiki @@ -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 STRING, BOOLEAN, CHARACTER or any numeric class like REAL or INTEGER. +The PERSON 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 CHILD: + + +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 + + +This adds in some complexity: +Instead of having a single object, ABEL has to insert a CHILD'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 CHILD 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 CHILD 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 CHILD 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 START 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 PERSON objects. + + + 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 + + +==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. + diff --git a/documentation/current/solutions/database-access/abel/tutorial/error-handling.wiki b/documentation/current/solutions/database-access/abel/tutorial/error-handling.wiki new file mode 100644 index 00000000..f040e781 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/error-handling.wiki @@ -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 has_error attribute to True in PS_QUERY or PS_TUPLE_QUERY and, +if available, in PS_TRANSACTION. +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 PS_ERROR. +In case of an error, you can find an ABEL error description in the error attribute in all classes suppoorting the has_error attribute. +The following list shows all error classes that are currently defined with some examples (the PS_ 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. + + +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 + + diff --git a/documentation/current/solutions/database-access/abel/tutorial/getting-started.wiki b/documentation/current/solutions/database-access/abel/tutorial/getting-started.wiki new file mode 100644 index 00000000..35d2f8ca --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/getting-started.wiki @@ -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 PERSON 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. + + +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 + + +There are three very important classes in ABEL: +* The deferred class PS_REPOSITORY provides an abstraction to the actual storage mechanism. It can only be used for read operations. +* The PS_TRANSACTION class represents a transaction and can be used to execute read, insert and update operations. Any PS_TRANSACTION object is bound to a PS_REPOSITORY. +* The PS_QUERY [G] class is used to describe a read operation for objects of type G. + +To start using the library, we first need to create a PS_REPOSITORY. +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 PS_IN_MEMORY_REPOSITORY_FACTORY. + + +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 + +We will use criterion_factory later in this tutorial. +The feature explore will guide us through the rest of this API tutorial and show the possibilities in ABEL. + diff --git a/documentation/current/solutions/database-access/abel/tutorial/index.wiki b/documentation/current/solutions/database-access/abel/tutorial/index.wiki new file mode 100644 index 00000000..8dcc6447 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/index.wiki @@ -0,0 +1,5 @@ +[[Property:title|Tutorial]] +[[Property:weight|0]] +[[Property:uuid|d24ce584-52d5-36bb-adb2-68c67c843072]] + + diff --git a/documentation/current/solutions/database-access/abel/tutorial/tuple-queries.wiki b/documentation/current/solutions/database-access/abel/tutorial/tuple-queries.wiki new file mode 100644 index 00000000..e3ba5d82 --- /dev/null +++ b/documentation/current/solutions/database-access/abel/tutorial/tuple-queries.wiki @@ -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 CHILD objects in the database. +Loading every attribute of each object of type CHILD might lead to a very bad performance, especially if there is a big object graph attached to each CHILD object. + +To solve this problem ABEL allows queries which return data in TUPLE objects. +Tuple queries are executed by calling execute_tuple_query (a_tuple_query) in either PS_REPOSITORY or PS_TRANSACTION, +where a_tuple_query is of type PS_TUPLE_QUERY [G]. +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 projection feature in a PS_TUPLE_QUERY defines which attributes shall be included in the result TUPLE. +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 PS_TUPLE_QUERY 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 set_projection. +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== + + + 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 + +