Created 18.01 branch of the documentation.

git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@1942 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
This commit is contained in:
eiffel-org
2018-02-08 15:00:31 +00:00
parent 265a446dab
commit b5d5c80911
2923 changed files with 61520 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
[[Property:title|ABEL]]
[[Property:weight|0]]
[[Property:uuid|585002f7-4f2c-e575-f3a2-0a339f349980]]
== Overview ==
ABEL is intended as an easy-to-use persistence library. It provides a high-level interface for storing and retrieving objects, selection criteria when querying objects, lazy loading of results, and transactions. ABEL can work with different storage backends such as MySQL or CouchDB transparently.
== Features ==
* Store and retrieve objects
* Filter retrieved objects based on criteria
* Transaction support
* Read only and read-write access
* Lazy loading of query results
* Support for different backends
* Support for access to relational databases created by EiffelStore.
== When to use it? ==
ABEL can be used whenever an application needs some basic persistency without the developer having to think about the data format or object-relational mapping. It may also be used to read and write table records for an existing database, provided that there is a set of classes matching the database tables.
It is not a good idea to use ABEL in a high-performance setting. Due to its high level of abstraction there are a lot of internal data conversions to enable automatic object-relational mapping.
== Current limitations ==
* The CouchDB backend has some severe limitations, e.g. it doesn't support polymorphism, SPECIAL, or transactions.
* The garbage collector currently only works for single-user systems and is rather primitive, as it needs to load the whole database into memory.
* The ESCHER plugin, which handles class schema evolution, has never been properly tested. Use at your own risk.
== Future work ==
* Support for caching for performance reasons.
* Lazy retrieval of referenced objects in an object graph. This requires support in the compiler however.
* Some handy features for the backend that deals with existing databases:
** Allow to provide a custom class name to table name mapping.
** A way to automatically resolve 1:N or M:N relations.
** Support automatic creation of classes from tables, or tables from classes.
* Support other backends like Oracle, Microsoft SQL or EiffelStore.

View File

@@ -0,0 +1,352 @@
[[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.
<code>
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
</code>
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>

View File

@@ -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 [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 [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>.

View File

@@ -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.

View File

@@ -0,0 +1,150 @@
[[Property:title|Dealing with references]]
[[Property:weight|-6]]
[[Property:uuid|1190C592-50E0-4834-9D60-E1E357921335]]
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 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 is 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.

View File

@@ -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>

View File

@@ -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.

View File

@@ -0,0 +1,5 @@
[[Property:title|Tutorial]]
[[Property:weight|0]]
[[Property:uuid|d24ce584-52d5-36bb-adb2-68c67c843072]]

View File

@@ -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>

View File

@@ -0,0 +1,85 @@
[[Property:link_title|SQL injection]]
[[Property:uuid|438C838C-C115-44B4-8480-05A825FE1047]]
[[Property:weight|4]]
[[Property:title|Defending against SQL injections with EiffelStore]]
[[Property:weight|4]]
= Introduction =
In this article, we will explain to you how to use EiffelStore API to avoid SQL injections.
= What is an SQL injection? =
An SQL injection attack is a coding technique that inserts, or "injects", an SQL query via the input data, passing unsafe input from the client to the application. A successful SQL injection can enable the attacker to read sensitive data from the database, modify database data (Insert/Update/Delete), or become administrator of the database server. To learn more about SQL injection, read the following articles.
{{SeeAlso| To learn more about SQL injection, read the following articles. }}
* [https://en.wikipedia.org/wiki/SQL_injection https://en.wikipedia.org/wiki/SQL_injection]
* [https://www.owasp.org/index.php/SQL_injection https://www.owasp.org/index.php/SQL_injection]
= Template Query =
A template query is a string containing the fixed parts of the query and placeholders for the variable parts, and you can later substitute in values into those placeholders. (Bind variables to the query.). A template query could be static or dynamic.
{{Note|the way you bind variables to the query is quite important and it will define if your query is safe and avoid a SQL Injection attack.}}
== How to define placeholders (variables) in a SQL Template query? ==
Variables syntax is simple: the ':' special character followed by the variable name, for example <code>:value</code>
<code>SELECT * FROM TABLE_NAME WHERE field1 = :value</code>
{{SeeAlso| To learn more about EiffelStore query variables read the following article}}
* [https://www.eiffel.org/doc/solutions/Query%20variables Query Variables]
==How to bind variables/placeholders to a template query.==
To avoid SQL Injections you will need to map variables names to values using the EiffelStore API (using EiffelStore supported connectors)
* Queries returning a result will need to use: <code>DB_SELECTION</code>
* Queries updating the database (Insert, Update, Delete) will need to use: <code>DB_CHANGE</code>
=== Safe binding ===
The following example shows an attempt to do an SQL Injection attack, but as we are using EiffelStore API to bind the parameters the unsafe data will be escaped.
<code>
safe_query
local
l_connection: DATABASE_CONNECTION
db_selection: DB_SELECTION
l_query: STRING
do
...
create db_selection.make
db_selection.set_query ("SELECT * FROM new_users where datetime = :datetime")
db_selection.set_map_name ("\''; DROP TABLE new_users; --", ":datetime")
db_selection.execute_query
db_selection.unset_map_name (":datetime")
....
end
</code>
As you can observe in the previous example the binding to map the variable name <code>:datetime</code> to their value is done
using feature <code> BD_SELECTION.set_map_name</code> and the API is responsible to do the necessary encoding.
=== Unsafe binding ===
If you use your own binding to map variables names to values, for example using String replacement, EiffelStore does not ensure that your query is safe, because it will depend on how do you handle escaping inputs before adding them to the query.
The following example shows how we can bypass the EiffelStore API to bind placeholders using an unsafe String replacement, in this case, is up to the developer to escape the input value. The example is unsafe and subject to SQL Injections attacks when the input is unsafe.
<code>
unsafe_query
local
l_connection: DATABASE_CONNECTION
db_selection: DB_SELECTION
l_query: STRING
do
...
check l_connection.is_connected end
create l_query.make_from_string ("SELECT * FROM new_users where datetime = :datetime")
l_query.replace_substring_all (":datetime", "\''; DROP TABLE new_users; --" )
create db_selection.make
db_selection.set_query (l_query)
db_selection.execute_query
...
end
</code>

View File

@@ -0,0 +1,5 @@
[[Property:title|EiffelStore Class Reference]]
[[Property:weight|2]]
[[Property:uuid|a21dc8dc-51d8-09c5-3afd-d62effb3c23a]]
==View the [[ref:libraries/store/reference/index|EiffelStore Class Reference]]==

View File

@@ -0,0 +1,66 @@
[[Property:title|Esql Sample]]
[[Property:weight|0]]
[[Property:uuid|04d03117-fdb2-8b73-677f-9b1a0c333dc4]]
This sample consists of a command line SQL parser. SQL statements are filtered through a monitor and sent to the RDBMS.
==Compiling==
To compile the example:
* Launch EiffelStudio.
* Click '''Add project'''
* Browse to ''$ISE_EIFFEL\examples\store\esql\''.
* Choose ''esql.ecf''
* Choose the location where the project will be compiled, by default the same directory containing the configuration file.
* Choose the targe according to the installed DBMS.
* Click '''OK'''.
==Running==
This sample lets you interact with your database through a console.
First you have to enter the database connection information:
* For ODBC:
<code>
Database user authentication:
Data Source Name: handson
Name: smith
Password: mypass
</code>
{{note|''Name'' and ''Password'' are not required with ODBC. If you don't need ''Name'' and ''Password'', you can simply hit ''Return'' when prompted. }}
* For Oracle:
<code>
Database user authentication:
Name: smith@HANDSON
Password: mypass
</code>
{{note|You must specify the Oracle User Name and Net Service Name with the syntax ''<user>@<service> ''where ''<user>'' stands for the User Name and ''<service>'' stands for the Net Service Name. }}
Then you can enter standard SQL queries to interact with your database, for instance:
<code>
SQL> select firstname, lastname from CONTACTS where lastname = 'Smith'
John Smith
SQL>
</code>
{{note|Enter ''exit'' to quit the application. }}
==Under the Hood==
This sample showcases the use of the 3 basic classes to interact with your database:
* [[ref:libraries/store/reference/db_control_chart|DB_CONTROL]] to connect and disconnect to your database.
* [[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] to perform 'select' queries.
* [[ref:libraries/store/reference/db_change_chart|DB_CHANGE]] to perform SQL queries that output no result.
The whole sample code is contained in the root class:
* ESQL for Oracle.
* ESQL_ODBC for ODBC.

View File

@@ -0,0 +1,13 @@
[[Property:title|EiffelStore Samples]]
[[Property:weight|3]]
[[Property:uuid|65a0aab3-bc1d-28b8-a6f0-6d71b4c6d54d]]
=EiffelStore Samples=
The following basic examples showcases the different EiffelStore capabilities:
* [[Esql Sample|esql]] : A command line SQL parser: very useful to test your Database connection!
* [[Selector Sample|select]] : A basic example to perform smart database queries.
* [[Inserter Sample|insert]] : A basic example to easily insert data in your database.

View File

@@ -0,0 +1,81 @@
[[Property:title|Inserter Sample]]
[[Property:weight|2]]
[[Property:uuid|fa0b8601-ca7a-b1cc-384d-f366be33ac40]]
This sample lets the user insert a DB_BOOK object in your database using EiffelStore insertion facilities.
==Compiling==
To compile the example:
* Launch EiffelStudio.
* Click '''Add project'''
* Browse to ''$ISE_EIFFEL\examples\store\insert\''.
* Clicking open loads the selected project
* Choose inserter.ecf
* Select the target according to the installed DBMS
* Choose the location where the project will be compiled, by default the same directory containing the configuration file.
* Click '''OK'''.
==Running==
This sample lets you interact with your database through a console.
First you have to enter the database connection information:
* For ODBC:
<code>
Database user authentication:
Data Source Name: handson
Name: smith
Password: mypass
</code>
{{note|''Name'' and ''Password'' are not required with ODBC. If you don't need ''Name'' and ''Password'', you can simply hit '''Return''' when prompted ''.'' }}
* For Oracle:
<code>
Database user authentication:
Name: smith@HANDSON
Password: mypass
</code>
{{note|You must specify the Oracle User Name and Net Service Name with the syntax ''<user>@<service> ''where ''<user>'' stands for the User Name and ''<service>'' stands for the Net Service Name. }}
Then you can insert a book in the database, for instance:
<code>
What new book should I create?
Author? John Smith
Title? My book
Quantity? 1
Price? 50
Year? 2001
Double value? 12.675
Object inserted
</code>
{{note|If your database does not contain a <eiffel>DB_BOOK</eiffel> table, this example will create it. }}
==Under the Hood==
This sample showcases the use of the [[ref:libraries/store/reference/db_store_chart|DB_STORE]] class to [[Data Object Coupling| perform database insertions]] where the SQL language is totally abstracted.
The whole sample code is contained in the root class:
* INSERTER for Oracle.
* INSERTER_ODBC for ODBC.

View File

@@ -0,0 +1,89 @@
[[Property:title|Selector Sample]]
[[Property:weight|1]]
[[Property:uuid|3d608710-5537-04e4-fa89-a608ee6864cd]]
This sample creates a <eiffel>DB_BOOK</eiffel> table in your database, enters sample data in it and lets you select rows from this table with author's name.
==Compiling==
To compile the example:
* Launch EiffelStudio.
* Click '''Add project'''
* Browse to ''$ISE_EIFFEL\examples\store\select\''.
* Clicking open loads the selected project
* Choose select.ecf
* Select the target according to the installed DBMS
* Choose the location where the project will be compiled, by default the same directory containing the configuration file.
* Click '''OK'''.
==Running==
In order to run this example, you must first create a file named <code>data.sql</code> from one of the files in the <code lang="text">select</code> example directory. You will see that the <code>data.sql</code> files in the example directory have names which include the names of the available DBMS handles. This is because the SQL can differ from one DBMS to another. For example, you'll see <code>data.sql.oracle</code> and <code>data.sql.odbc</code>. If you are using the Oracle DBMS, you should rename or make a copy of <code>data.sql.oracle</code> as <code>data.sql</code> and make sure it's in your execution directory before running the example.
{{note|The <code>data.sql</code> file must be available in the sample application current directory. If you run the sample from EiffelStudio, this directory should be the ''EIFGENs/target/W_CODE/'' directory of your project (where target is one of the installed DBMS). }}
This sample lets you interact with your database through a console.
First you have to enter the database connection information:
* For ODBC:
<code>
Database user authentication:
Data Source Name: handson
Name: smith
Password: mypass
</code>
{{note|''Name'' and ''Password'' are not required with ODBC. If you don't need ''Name'' and ''Password'', you can simply hit ''Return'' when prompted. }}
* For Oracle:
<code>
Database user authentication:
Name: smith@HANDSON
Password: mypass
</code>
{{note|You must specify the Oracle User Name and Net Service Name with the syntax ''<user>@<service> ''where ''<user>'' stands for the User Name and ''<service>'' stands for the Net Service Name. }}
Then you can select rows from this table with author's name, for instance:
<code>
Author? ('exit' to terminate):Bertrand Meyer
Seeking for books whose author's name match: Bertrand Meyer
Author:Bertrand Meyer
Title:Eiffel The Libraries
Quantity:11
Price:20
double_value:435.60000610351562
First publication:07/01/1994 12:00:00.0 AM
Author:Bertrand Meyer
Title:Eiffel The Language
Quantity:9
Price:51
double_value:775.22998046875
First publication:07/01/1992 12:00:00.0 AM
Author? ('exit' to terminate):
</code>
{{note|Enter ''exit'' to quit the application. }}
==Under the Hood==
This sample showcases the use of the [[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] class to perform smart ''select'' queries, using:
* [[Data Object Coupling|Database data and Eiffel objects Coupling]] .
* [[Query variables|Variables Binding]] .
The whole sample code is contained in the root class:
* SELECTOR for Oracle.
* SELECTOR_ODBC for ODBC.

View File

@@ -0,0 +1,432 @@
[[Property:title|EiffelStore DataView Cluster]]
[[Property:weight|3]]
[[Property:uuid|75f8cc72-7ccf-28a4-f6b2-beeb2219dc43]]
==1. Introduction==
DataView cluster helps the programmer creating a GUI for a RDBMS. It gives a basic solution for a RDBMS GUI and also enables the developer to customize his GUI from this basic interface.
This cluster is client of EiffelStore to interface with a RDBMS and EiffelVision2 to create a GUI. However, the use of EiffelStore and EiffelVision 2 is sufficiently encapsulated to let the programmer use other database/graphic libraries.
Notice finally that DataView is based on some common O-O ''design patterns''. Knowing these patterns will help you understand how the library works. It can also give an example of patterns use.
==2. Specifications==
This part draws the main capabilities that can be expected from the DataView cluster. These capabilities are not exhaustive since the cluster architecture enables to add easily new capabilities to it.
===2.1. Required database structure===
The cluster has been designed to work well with relational databases on Third Normal Form. Database tables must also have an unique numeric ID.
* The cluster automatically performs associations between related tables. This is required to work fine with 3rd NF architectures.
* The unique ID enables to update the database content. If you only intend to display database table content, unique IDs are not required.
* The numeric ID enables to directly give IDs to new table rows. If you don't intend to create table rows, this is not necessary. Notice that with only a couple of redefinitions, the numeric ID requirement problem can be overcome.
===2.2. Database content display===
The cluster provides facilities to:
* display a set of table rows.
* select a (current) table row in the set.
* display the current table row so that it can be edited.
For instance, an interface can display a multi-column list of table rows. A given row can be selected in the list and its information can be then edited through a set of text fields and combo-boxes.
The standard cluster usage is to define a GUI that will associate a given frame/window to a given database table, that is, the information that is displayed in a GUI area will be determined at compile-time. This enables to adapt the GUI display to the type of information displayed, which is recommended when creating a GUI for non-developer users (or any people that should not be aware of the database structure and functioning). Nevertheless, determining the type of information displayed by a frame/window at runtime is still possible as not hard-coded.
Abstracting database in the GUI might not be as easy as only changing database attribute fields names. Information to display may not match the database tables structure. However, for consistency reasons, we can assume that he information to display within a GUI area belongs to a set of associated tables. The easiest solution is to create database views that directly contain the information to display on the GUI area. This implies though that the database has to be modified for the GUI needs.
DataView cluster affords a second solution:
* Model-View separation enables to merge graphically information that is separated in the process part (i.e. that is from a different table).
* Table associations facilities enable to specify to display automatically content of table rows associated to a given table row. For instance, with a CONTACTS table associated to a COMPANIES table, the cluster can retrieve automatically a COMPANIES table row associated to a selected CONTACTS table row.
===2.3. Actions performed on the database===
The cluster provides facilities for the following actions:
* Creating a table row
* Deleting a table row
* Updating the content of a table row
* Selecting a set of table rows
Other capabilities can be added to these ones, for instance by writing descendants of DataView cluster classes that would handle more database operations.
Operations relative to the database structure modification, for instance creating a database table, may be more difficult to add since the database structure is hard-coded. But these advanced capabilities might not be necessary in a GUI for non-developer users.
==3. General description==
===3.1. Global architecture===
The DataView cluster is based on 1 class called [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] that represents the interface for 1 relational database table. An architecture using ''DataView'' is centered on database table structure rather than the GUI structure. The basic idea is to have:
* 1 database relational table
* 1 GUI window or frame
* 1 [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] object
This is then possible to adapt the code to have a GUI meeting the specifications, database structure can be totally abstracted in the interface, which might be more convenient for non-developer GUI users.
===3.2. Library structure===
The cluster can be separated into 3 main parts:
* The '''model''': processes the information and interfaces with an abstract graphic interface (the handle) and an abstract database interface.
* The '''handle''': defines an abstract graphic interface for the model.
* The '''view''': implements the handle interface with EiffelVision2 widgets.
The abstract database interface is defined in the EiffelStore generation.tables_access cluster. This cluster can indeed been used independently from the DataView cluster.
===3.3. Model cluster structure===
The '''model''' cluster processes the information retrieved from the GUI and the database and update then both GUI and database.
The cluster is based on the [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] class which objects represents a database relational table (or view).
DV_TABLE_COMPONENT objects can be interconnected to match the table associations. The [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] class has been designed to work with 3rd Normal Form relational databases. [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] achieves most of the work to retrieve associated table rows for a 3NF database. For instance, when deleting a table row, the component ensures that every associated table row is also deleted.
[[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] objects can be customized by adding some subcomponents to it. Subcomponents enable to display table rows content on screen, to navigate among table rows and to perform different database queries.
[[Image:sub-component-objects]]
Process objects structure for a GUI
===3.4. Design patterns===
This cluster adapts several well-known O-O design patterns.
====3.4.1. Model-View separation pattern====
The GUI appearance is totally abstracted in the GUI processing part, this enables to change the GUI display without changing any part of the model part.This is implemented with 2 sets of classes:
* A set of interfaces that corresponds to each type of abstract widgets needed by the model.
* A set of classes that implements these interfaces. Notice that an implementation class can implement several interfaces and several classes can implement the same interface.
Let's see an example through a BON diagram:
[[Image:model-view-relationship]]
Model-View separation pattern implementation in ''DataView''
* Light blue classes represents the model cluster.
* Orange class represents the handle.
* Yellow and green classes represents the view cluster.
* Pink classes represents the EiffelVision2 library.
====3.4.2. Strategy pattern====
DataView cluster provides the developer with a basic GUI implementation AND lets them customize their application. This is possible with a strategy pattern:
The developer assigns different subcomponents to a [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] object to define its behavior. The component object only uses the interface of each subcomponent.
A default implementation is written for each interface to let the user use the cluster as quick as possible. To adapt components behavior to their needs, the developer can then create a new subcomponent class inheriting from the abstract interface.
This BON diagram illustrates this for [[ref:libraries/store/reference/dv_creator_chart|DV_CREATOR]] and [[ref:libraries/store/reference/dv_searcher_chart|DV_SEARCHER]] subcomponents:
[[Image:dv-table-component-strategy]]
Strategy pattern used in ''DataView'' model cluster
==4. Cluster interface==
This part describes how to use the table component class and its subcomponents classes:
* The [[ref:libraries/store/reference/dv_searcher_chart|DV_SEARCHER]] class to [[#dv_searcher|select table rows from the database]] .
* The [[ref:libraries/store/reference/dv_tablerows_navigator_chart|DV_TABLEROW_NAVIGATOR]] class to [[#dv_tablerow_navigator|navigate among selected table rows]] .
* The [[ref:libraries/store/reference/dv_creator_chart|DV_CREATOR]] class to [[#dv_creator|create new table rows in the database]] .
* The [[ref:libraries/store/reference/dv_tablerow_fields_chart|DV_TABLEROW_FIELDS]] class to [[#dv_tablerow_fields|edit a table row content]] .
===4.1. DV_TABLE_COMPONENT class===
This class is responsible for the management of a database table. Its behavior is determined by its assigned subcomponents.
To create a valid and functional [[ref:libraries/store/reference/dv_tablerows_component_chart|DV_TABLE_COMPONENT]] object, follow these steps:
# Call set_tablecode to specify which table the component will deal with.
# Specify [[#output_handler|handlers to output messages]] .
# Set the [[#database_handler|database handler]] .
# Add different [[#controllers|controllers corresponding to actions to perform]] .
# Set [[#subcomponents|subcomponents]] .
# Set [[#associated_components|associated components]] .
# Call activate to let the component work. This will basically set different default values for non required information not set during the creation process.
The component can then be used on an interface:
* Input interactions are done via component and subcomponents controllers (4).
* Output interactions are done via output handlers (2).
====4.1.1. Output handlers====
Output handlers are specific to the [[ref:libraries/store/reference/dv_tablerows_component_chart|DV_TABLE_COMPONENT]] object, that is, you can output messages in a different way within your GUI. However, the same handlers will be used for subcomponents.
3 handlers can be set:
* `status_handler` to display status information
* `warning_handler` to display warning information. Warnings usually correspond to database errors, they are called warnings because the database error is "caught" and the message should enable the user to round the problem.
* `confirmation_handler` to ask for confirmation before an action.
These handlers have default values, which are:
* For `status_handler` and `warning_handler`, messages are displayed on standard output (with {ANY}.io.put_string)
* For `confirmation_handler`, action is executed without confirmation.
====4.1.2. Database handler====
This handler is specific to the application. It must inherit from <eiffel>ABSTRACT_DB_TABLE_MANAGER</eiffel>. Since it is specific to the program, it can be set before creating any [[ref:libraries/store/reference/dv_table_component_chart|DV_TABLE_COMPONENT]] object through {DV_DATABASE_HANDLER}.set_database_handler.The [[ref:libraries/store/reference/db_table_manager_chart|DB_TABLE_MANAGER]] class is the default database handler for EiffelStore.
4.1.3. Action controllers
No subcomponent is associated to 'write', 'refresh' and 'delete' actions since these actions does not require specific behavioral choices.
To perform 'write', 'refresh' and 'delete' at runtime, a controller is associated to each of these actions. This controller triggers the action when a determined user event is grabbed, for instance, when the user clicks a button.
[[#dv_s_control|Controllers]] are implemented by the abstract class [[ref:libraries/store/reference/dv_sensitive_control_chart|DV_SENSITIVE_CONTROL]] of cluster user_interactions (handle).
====4.1.4. Subcomponents====
Subcomponents can be assigned to a table component to specify its behavior to create table rows, select table rows from the database and navigate among selected table rows. A special subcomponent enable to display the ''current'' table row, i.e. the table row that can be edited to update the database. The default behavior for these subcomponents is that the functionality is not available, that is, subcomponents are not mandatory.
These components share the table component output handlers. They are automatically activated when table component is activated.
====4.1.5. Associated components====
Table components can be associated to reflect relation of database tables represented. Associated table components are organized:
* 1 master component enables to manually select database table rows.
* Slave components automatically select table rows that are associated to the current table row of the master component.
{{note|Notice that table associations can be '''nested'''. }}
2 types of associations are possible to reflect table relations:
* The slave table is dependent on the master table (1:N relationship)
* The slave table is necessary for the master table (N:1 relationship)
Let us see an example with 3 relational tables:
[[Image:table-objects-associations]]
Tables architecture and corresponding component objects
The object architecture leads to a GUI where the user can select a company and see the company country information and contacts in this company.
Finally, notice that by default slave components have the same output handlers as their master and slave components are activated when the master component is.
===4.2. DV_SEARCHER class===
====4.2.1. Overview====
[[ref:libraries/store/reference/dv_searcher_chart|DV_SEARCHER]] is responsible for retrieving table rows from the database. Let us see how it interacts with a table component:
[[Image:component-search-relation]]
Basic relationship between table component class and search class
* `display` assigns a set of table rows to the table component.
* `refresh` asks to refresh the table rows from the same database query.
[[ref:libraries/store/reference/dv_searcher_chart|DV_SEARCHER]] component does not afford an extended interface. This interface is defined in its descendants. The implemented [[ref:libraries/store/reference/dv_searcher_chart|DV_SEARCHER]] descendants are:
* [[ref:libraries/store/reference/dv_typed_searcher_chart|DV_TYPED_SEARCHER]] performs different [[#dv_typed_searcher|basic searches]] used by the cluster.
* [[ref:libraries/store/reference/dv_interactive_searcher_chart|DV_INTERACTIVE_SEARCHER]] enables to create a graphic interface to [[#dv_interactive_searcher|let user set search parameters]] .
====4.2.2. DV_TYPED_SEARCHER class====
This class provides 3 types of searches:
* "Every row" search: every rows of a table are fetched.
* "ID selection" search: the selection is qualified by an ID.
* "Qualified selection" search: the selection is qualified.
=====4.2.2.1. "Every row" search=====
Call read to set table rows on the associated table component.
=====4.2.2.2. "ID selection" search=====
Call read_from_tablerow to set table rows on the associated table component. Qualification ID is the ID of the table row in parameter. Table of row in parameter must be the table of rows to select.
This capability is used by DataView cluster in [[ref:libraries/store/reference/dv_choice_creator_chart|DV_CHOICE_CREATOR]] to select a just-created table row and display it on the table component.
=====4.2.2.3. "Qualified selection" search=====
Call read_from_table row to set table rows on the associated table component. Table of row in parameter may not be the table of rows to select.
To extract the qualifier, the search component needs additional information:
* The location of the qualifying value in the table row passed in parameter (set_row_attribute_code)
* The qualifying attribute location in the table rows to select (set_criterion)
This capability is used in [[ref:libraries/store/reference/dv_tablerows_component_chart|DV_TABLE_COMPONENT]] when a table row is selected to set associated table rows to slave components. Take a look at add_necessary_table and add_dependent_table.
====4.2.3. DV_INTERACTIVE_SEARCHER class====
This class enables to create a graphic interface to let user perform basic searches. These searches are qualified by one table attribute. This interface has 5 parts:
* A [[#dv_s_control| controller]] that enables to launch the search
* A [[#dv_s_string| text input field]] to set qualifying attribute
* A [[#dv_s_string| text input field]] to set qualifying value
* A [[#dv_s_integer| typed input field]] to set qualification type
* A [[#dv_s_check| Boolean input field]] to set case sensitivity
Text input fields correspond to handle class [[ref:libraries/store/reference/dv_sensitive_string_chart|DV_SENSITIVE_STRING]] , typed input fields corresponds to handle class [[ref:libraries/store/reference/dv_sensitive_integer_chart|DV_SENSITIVE_INTEGER]] and Boolean input fields corresponds to handle class [[ref:libraries/store/reference/dv_sensitive_check_chart|DV_SENSITIVE_CHECK]] .
===4.3. DV_TABLEROW_NAVIGATOR class===
====4.3.1. Overview====
Table component class contains a set of table rows. This class lets table component class know which of these rows is the current one.
[[Image:component-navigate-relation]]
Basic relationship between table component class and navigation class
[[ref:libraries/store/reference/dv_choice_creator_chart|DV_CHOICE_CREATOR]] also uses the class to enable to select associated table rows when creating a new table row (for instance, when creating a company, an existing country should be selected). Let us see how this is designed:
[[Image:dv-tablerows-navigator-clients]]
DV_TABLEROWS_NAVIGATOR clients
{{note|DV_TABLEROWS_COMPONENT class merely carries a set of table rows and enables to select one table row. }}
DV_CONTROL_NAVIGATOR affords a way to navigate among searched table rows.
====4.3.2. DV_CONTROL_NAVIGATOR class====
This class enables 2 navigation systems:
* Navigating among table rows with "previous" and "next" controllers.
* Navigating among table rows through a display list.
{{tip|Notice that both systems can be used. }}
You can directly set [[#dv_s_control| controllers]] for "previous" and"next" actions. A 3rd controller, "edit list", enables to show or raise the display list.
{{caution|Notice that DV_CONTROL_NAVIGATOR only manages this controller sensitivity. }}
You can assign a [[#dv_tablerow_list| display list]] to the navigator with a [[ref:libraries/store/reference/dv_tablerow_list_chart|DV_TABLEROW_LIST]] component.
===4.4. DV_CREATOR class===
====4.4.1. Overview====
This class enables to create database table rows.
[[Image:component-create-relation]]
Basic relationship between table component class and navigation class
[[ref:libraries/store/reference/dv_creator_chart|DV_CREATOR]] class contains minimum information to interact with [[ref:libraries/store/reference/dv_tablerows_component_chart|DV_TABLE_COMPONENT]] : when a table row is created, a creator component may display it on the table component. In this case, when the table component needs to refresh the table rows set, this refreshing action need to be managed by the creator component:
* `set_just_created` informs a table component that displayed table row set comes from the creator component.
* `refresh` lets the creation component refresh table component display.
Much of the work, that is row creation, is totally abstracted in DV_CREATOR.DV_CHOICE_CREATOR implements DV_CREATOR and thus affords a creation procedure.
====4.4.2. DV_CHOICE_CREATOR class====
=====4.4.2.1. Overview=====
This class creates a new table row and sets its key values:
* Database handle gives the primary key value (ID) .
* The class asks the user for foreign key values (for table associations) by displaying available values in a list.
DV_TABLEROW_NAVIGATOR is used to select a foreign key value, let us see how this is implemented:
[[Image:dv-choice-creator-fkeys-selection]]
[[ref:libraries/store/reference/dv_choice_creator_chart|DV_CHOICE_CREATOR]] suppliers for foreign keys selection
[[ref:libraries/store/reference/dv_tablerow_id_provider_chart|DV_TABLEROW_ID_PROVIDER]] inherits from [[ref:libraries/store/reference/dv_tablerows_component_chart|DV_TABLEROWS_COMPONENT]] to interface with [[ref:libraries/store/reference/dv_tablerows_navigator_chart|DV_TABLEROWS_NAVIGATOR]] .
Relation between [[ref:libraries/store/reference/dv_choice_creator_chart|DV_CHOICE_CREATOR]] and [[ref:libraries/store/reference/dv_tablerow_id_provider_chart|DV_TABLEROW_ID_PROVIDER]] is basically:
[[Image:creator-provider-relation]]
<eiffel>DV_CHOICE_CREATOR/DV_TABLEROW_ID_PROVIDER</eiffel> basic interactions
Creation process and [[ref:libraries/store/reference/dv_choice_creator_chart|DV_CHOICE_CREATOR]] objects creation procedure can help you use this class.
=====4.4.2.2. Creation process=====
Table row creation process is:
# Table row creation is triggered by a [[#dv_s_control| controller]] ("create")
# <eiffel>DV_CHOICE_CREATOR</eiffel> creates a table row object
# <eiffel>DV_CHOICE_CREATOR </eiffel> requests a first foreign key value to [[ref:libraries/store/reference/dv_tablerow_id_provider_chart|DV_TABLEROW_ID_PROVIDER]] (through `select_from_table`)
# <eiffel>DV_TABLEROW_ID_PROVIDER </eiffel> loads the available table rows that can be referenced
# <eiffel>DV_TABLEROW_ID_PROVIDER</eiffel> assigns the table rows to <eiffel>DV_TABLEROWS_NAVIGATOR</eiffel> and pops up the interface with the table rows
# Table row selection is triggered by a [[#dv_s_control| controller]] ("ok")
# <eiffel>DV_TABLEROW_ID_PROVIDER</eiffel> retrieves the selected table row ID and gives it back to <eiffel>DV_CHOICE_CREATOR</eiffel> (through `add_foreign_key_value`)
# <eiffel>DV_CHOICE_CREATOR</eiffel> requests other foreign key values to <eiffel>DV_TABLEROW_ID_PROVIDER</eiffel>
# <eiffel>DV_CHOICE_CREATOR</eiffel> creates the database row with a new ID through the database handle
=====4.4.2.3. Objects creation procedure=====
To create a DV_CHOICE_CREATOR, follow these steps:
# Create an object conforming to DV_TABLEROWS_NAVIGATOR
# Create a <eiffel>DV_TABLEROW_ID_PROVIDER</eiffel> object and assign the <eiffel>DV_TABLEROWS_NAVIGATOR</eiffel> object to it
# Set a [[#dv_s_control| controller]] to trigger foreign key selection
# Set the action to perform to pop up the interface to select the foreign key
# Create a <eiffel>DV_CHOICE_CREATOR</eiffel> object and assign the <eiffel>DV_TABLEROW_ID_PROVIDER</eiffel> object to it
# Set a [[#dv_s_control| controller]] to trigger table row creation
===4.5. DV_TABLEROW_FIELDS class===
====4.5.1. Overview====
This class enable to display and edit the current table row of a table component. Let us see first how it interacts with the table component:
[[Image:component-fields-relation]]
DV_TABLE_COMPONENT/DV_TABLEROW_FIELDS basic interactions
* refresh_tablerow refreshes display with a new table row
* update_tablerow requests an updated table row for database update. Unchanged values are kept from a default table row
* updated_tablerow is the last updated table row
The class contains a list of fields that represent editable table attributes.The design is simple:
[[Image:dv-tablerow-fields-design]]
Table row edition capability design
====4.5.2. DV_TABLEROW_FIELD class====
This class enables to edit a table row attribute value. The view is abstracted using the handle cluster [[ref:libraries/store/reference/dv_sensitive_string_chart|DV_SENSITIVE_STRING]] class that [[#dv_s_string|represents the editable text value]] .
This class manages a field value but can also provide field name and type if [[#dv_s_string|graphic fields]] are provided. Notice that standard [[ref:libraries/store/reference/dv_tablerow_field_chart|DV_TABLEROW_FIELD]] objects can be generated through the [[ref:libraries/store/reference/dv_factory_chart|DV_FACTORY]] class, which is a component factory.
==5. Handle cluster==
This cluster provides the model with an interface to input or output data on the GUI. This enables to remove any link to a graphic implementation in the model, following the [[#model-view_sep|Model-View separation]] design pattern. The cluster contains a set of interface classes to design this:
* The [[ref:libraries/store/reference/dv_sensitive_control_chart|DV_SENSITIVE_CONTROL]] class to [[#dv_s_control|let the user trigger an action]] .
* The [[ref:libraries/store/reference/dv_sensitive_string_chart|DV_SENSITIVE_STRING]] class to [[#dv_s_string|input or output a text value]] .
* The [[ref:libraries/store/reference/dv_sensitive_string_chart|DV_SENSITIVE_INTEGER]] class to [[#dv_s_integer|input or output a quantity value]] .
* The [[ref:libraries/store/reference/dv_sensitive_check_chart|DV_SENSITIVE_CHECK]] class to [[#dv_s_check|input or output a tag value]] .
* The [[ref:libraries/store/reference/dv_tablerow_list_chart|DV_TABLEROW_LIST]] class to [[#dv_tablerow_list|display a set of table rows and grab events on it]] .
===5.1. DV_SENSITIVE_CONTROL class===
The [[ref:libraries/store/reference/dv_sensitive_control_chart|DV_SENSITIVE_CONTROL]] class lets a model class trigger a specific action on a determined user event. Furthermore, the model class lets the user know when its state enables to trigger the action, by setting the controller sensitivity (i.e. if the controller is insensitive, the action cannot be triggered).
{{note|sensitivity excepted, these controllers could have been implemented by Eiffel ''agents''. }}
{{note|sensitivity enables to let the user know ''before''triggering an action if this is possible or not. The other possibility is to let the user know ''after'' trying to trigger the action that it was not possible(with a warning for instance): this is often less convenient. }}
The standard controllers are buttons or menu items: the specific action is triggered when button is clicked or menu item selected.
[[ref:libraries/store/reference/dv_sensitive_control_chart|DV_SENSITIVE_CONTROL]] is inherited by [[ref:libraries/store/reference/dv_button_chart|DV_BUTTON]] that implements an EiffelVision2 button. Other implementations can be added, such as a menu item.
===5.2. DV_SENSITIVE_STRING class===
The [[ref:libraries/store/reference/dv_sensitive_string_chart|DV_SENSITIVE_STRING]] class lets a model class input or output a text graphically. As for controllers, the model class lets the user know when a text value can be input by setting the widget sensitivity.
The standard graphical widgets to perform this are text fields, but several other widgets can be used:
* A combo-box so that the interface can suggest different values.
* A label if the text only need to be output.
{{note|customized, specific widgets can be defined, you can for instance take a look at the DV_STRING_LIST class. }}
===5.3. DV_SENSITIVE_INTEGER class===
This class lets a model class input or output an <eiffel>INTEGER</eiffel> value graphically. As for controllers, the model class lets the user know when an integer value can be input by setting the widget sensitivity.
Different widgets can be used to implement this:
* A text field. Notice that the value entered should be checked to ensure it is an <eiffel>INTEGER </eiffel>value.
* A combo-box. Each combo-box option is associated to an integer.
* A scroll button.
===5.4. DV_SENSITIVE_CHECK class===
This class lets a model class input or output a <eiffel>BOOLEAN </eiffel>value graphically. As for controllers, the model class lets the user know when a Boolean value can be input by setting the widget sensitivity.
The standard widget to implement this is a check box.
===5.5. DV_TABLEROW_LIST class===
The [[ref:libraries/store/reference/dv_tablerow_list_chart|DV_TABLEROW_LIST]] class provides an interface to display a set of table rows so that the user can select a particular row.
* The model can be informed of a row selection or deselection: the class accepts actions (implemented by ''agents'') that are triggered when a row is selected or deselected.
* The model can retrieve the currently selected row: the class yields the current index position in the list.
[[ref:libraries/store/reference/dv_tablerow_multilist_chart|DV_TABLEROW_MULTILIST]] implements DV_TABLEROW_LIST with an EiffelVision2 multi-column list.
{{note|This class is used for the [[#dv_control_navigator|standard implementation]] of [[ref:libraries/store/reference/dv_tablerows_navigator_chart|DV_TABLEROW_NAVIGATOR]] to [[#dv_tablerow_navigator|navigate among table rows]] selected from the database. }}

View File

@@ -0,0 +1,648 @@
[[Property:title|Data structures creation]]
[[Property:weight|1]]
[[Property:uuid|d8845cb9-2581-f85d-aad1-460816711356]]
==1. Overview==
EiffelStore enables to create data structures mapped to your own database. This part describes how the EiffelStore wizard generates these structures mapped to your database and how you can map your own classes to your database with EiffelStore.
'''Tip:''' We recommend that you read [[Data structures use|what are and how to use]] these data structures before reading this section.
The EiffelStore wizard generates the database-specific classes from an EiffelStore generation system based on template files containing tags.
Let us see first [[#generation_system| how the EiffelStore generation system works]] . Let us see then [[#wizard_generation| how the EiffelStore wizard uses this system]] for the data structures generation.
==2. EiffelStore generation system==
This part describes the class creation in 4 steps:
* [[#capabilities|Mapping system possibilities]]
* [[#mapping_system|General mapping system]]
* [[#tags_descr|Tags description]]
* [[#gen_impl_overview|Implementation overview]]
===2.1. Capabilities===
Let us see first what information you can map into your classes.
2 kinds of classes can be generated:
* Classes that represents a database table.
* Classes that represents the whole database.
====2.1.1. Database table classes====
You can insert information about a database table:
* The table name
* The number of attributes of table
You can also select a piece of code, map it to a table attribute and repeat the operation for every attribute. Information that can be inserted is:
* The attribute names
* The attribute types
* The attribute default creation values
* The attribute positions in the table
Thus you can get for instance class attributes that correspond to database attributes:
<code>
class CONTACTS
feature
id: INTEGER
lastname: STRING
firstname: STRING
</code>
You can also modify the piece of code to map for the first and last attributes, for cosmetics reasons. You can also choose to only map a piece of code to attributes of a given type.
====2.1.2. Database content classes====
Database content classes can basically store the list of your database tables.
You can select a piece of code and map it to database table information:
* Database table name.
* Database table position in the database.
The mapped pieces of code are then concatenated in your file.
===2.2. General mapping system===
EiffelStore follows this sequence of steps to generate a class:
# You provide meta-data about the table or view
# You provide the template
# It parses the template to find the tags
# It replaces each tag by the meta-data value corresponding to the tag
Let us take an example:
{| border="1"
|-
| template file extract
| corresponding result file extract
|-
| attribute_count: <code><ACNT></code>
| attribute_count: 5
|}
This works for meta-data on the class or view. For meta-data on class (or view) attributes, a second tag type enables to specify text areas that are mapped to specific table (or view) attributes.
Let us take an example:
{| border="1"
|-
| template file extract
| corresponding result file extract
|-
| <code><A:A:A><AN:L>: <TN:U></code> <br/><code></A></code>
| <code>companyid: DOUBLE</code> <br/><code>companyname: STRING</code>
|}
Text contained in the tag 'A' is mapped to each table (or view) attribute and the resulting texts are concatenated. Let us see now the details about each tag.
===2.3. Tags description===
====2.3.1. Database table tags description====
The available tags for database table classes generation can be separated into 3 types:
* Tags corresponding to [[#table_meta-data|table meta-data]]
* Tags corresponding to [[#attribute_meta-data|attribute meta-data]]
* Tags to [[#enclosing_tag|enclose attribute meta-data]]
=====2.3.1.1. Table meta-data tags=====
{| border="1"
|-
| '''Tag name'''
| '''Tag description'''
| '''Options'''
|-
| '''Option name'''
| '''Option description'''
|-
| <code><CN:?></code>
| Table name
| U
| in uppercase
|-
| I
| with initial capital
|-
| L
| in lowercase
|-
| <code><ACNT></code>
| Attribute count
| N/A
|}
=====2.3.1.2. Attribute meta-data tags=====
{| border="1"
|-
| '''Tag name'''
| '''Tag description'''
| '''Options'''
|-
| '''Option name'''
| '''Option description'''
|-
| <code><AN:?></code>
| Attribute name
| U
| in uppercase
|-
| I
| with initial capital
|-
| L
| in lowercase
|-
| <code><IT></code>
| Attribute position in the table
| N/A
|-
| <code><TN:?></code>
| Attribute type name
| U
| in uppercase
|-
| I
| with initial capital
|-
| L
| in lowercase
|-
| <code><TDV></code>
| Attribute type default value
| N/A
|}
'''Note''': Attribute tags are only valid within an enclosing tag.
=====2.3.1.3. Enclosing tags=====
The tag <code><A:?:?> </A></code> encloses text that will be mapped to attributes matching criteria. These criteria are specified by the tag options:
* First option: attribute type criterion
{| border="1"
|-
| '''Option name'''
| '''Option description'''
|-
| A
| All attributes
|-
| I
| INTEGER attributes
|-
| S
| STRING attributes
|-
| D
| DATE attributes
|-
| B
| BOOLEAN attributes
|-
| C
| CHARACTER attributes
|-
| F
| DOUBLE attributes
|}
* Second option: attribute position criterion
{| border="1"
|-
| '''Option name'''
| '''Option description'''
|-
| A
| All attributes
|-
| F
| First attribute
|-
| I
| Intermediate attributes
|-
| L
| Last attribute
|}
* '''Note''': this option is generally used to have a valid and nice layout or indentation.
'''Note''': several options can be selected for one criterion.
====2.3.2. Database content tags description====
The tags described above can be reused for database content:database content class mapping is equivalent to the previous mapping but within a different scope:
* class corresponds to the database rather than tables
* class content deals with tables rather than table attributes
The meaning of available tags is so modified:
* [[#system_meta-data_ext|System meta-data tags]]
* [[#table_meta-data_ext| Table meta-data tags]]
* [[#enclosing_tag_ext| Enclosing tags]]
'''Note''': every tag is not available for this mapping.
=====2.3.2.1. System meta-data tags=====
{| border="1"
|-
| '''Tag name'''
| '''Tag description'''
|-
| <code><ACNT></code>
| Table count
|}
=====2.3.2.2. Table meta-data tags=====
{| border="1"
|-
| '''Tag name'''
| '''Tag description'''
| '''Options'''
|-
| '''Option name'''
| '''Option description'''
|-
| <code><CN:?></code>
| Table name
| U
| in uppercase
|-
| I
| with initial capital
|-
| L
| in lowercase
|-
| <code><IT></code>
| Table position in the database
| N/A
|}
=====2.3.2.3. Enclosing tags=====
The tag <code><A:A:?> </A></code> encloses text that will be mapped to tables matching a position criterion. This criterion is specified by the tag options:
{| border="1"
|-
| '''Option name'''
| '''Option description'''
|-
| A
| All tables
|-
| F
| First table
|-
| I
| Intermediate tables
|-
| L
| Last table
|}
===2.4. Implementation overview===
The data structure generation system is implemented with 4 EiffelStore classes:
* DB_CLASS_GENERATOR abstractly generates a class mapped to database meta-data.
* DB_TABLE_CLASS_GENERATOR generates a class mapped to a database table.
* DB_ACCESS_CLASS_GENERATOR generates a class mapped to the database.
* DB_TEMPLATE_TAGS defines available tags for mapping and their meaning.
[[Image:estore-generation.generator]]
Generation classes BON diagram
==3.EiffelStore wizard==
The EiffelStore wizard uses the [[#generation_system| EiffelStore generation system]] described above to generate the data structures mapped to your database.
The wizard generates 3 types of classes:
* [[#wiz_table_classes|Classes storing database table rows content]]
* [[#wiz_descr_classes|Classes describing a database table]]
* [[#wiz_access_class|A class describing the database and giving access to the previous types of classes]]
The wizard uses one different template for each class.
===3.1. Table classes===
For each selected database table, a class is created from the same template, mapping the database table. This template is:
<code>
indexing
description: "Class which allows EiffelStore to retrieve/store%
%the content of a table row from database table <CN:U>"
author: "EiffelStore Wizard"
date: "$Date: 2004-12-09 23:29:16 +0100 (Thu, 09 Dec 2004) $"
revision: "$Revision: 46989 $"
class
<CN:U>
inherit
DB_TABLE
-- redefine
-- out
-- end
undefine
Tables,
is_valid_code
end
DB_SPECIFIC_TABLES_ACCESS_USE
create
make
feature -- Access
<A:A:A> <AN:L>: <TN:U>
</A> table_description: DB_TABLE_DESCRIPTION is
-- Description associated to the <CN:L>.
do
tables.<CN:L>_description.set_<CN:L> (Current)
Result := tables.<CN:L>_description
end
feature -- Initialization
set_default is
do
<A:A:A> <AN:L> := <TDV>
</A>
end
feature -- Basic operations
<A:A:A> set_<AN:L> (a_<AN:L>: <TN:U>) is
do
<AN:L> := a_<AN:L>
end
</A>feature -- Output
out: STRING is
-- Printable representation of current object.
do
Result := ""
<A:A:A>
if <AN:L> /= Void then
Result.append ("<AN:I>: " + <AN:L>.out + "%N")
end
</A>
end
end -- class CODES
</code>
'''Note''': the template content can be adjusted, for instance to add comments or change the indexing. However, the fundamental template structure should not be changed to use data structures as described in the [[Data structures use|corresponding section]] .
===3.2. Description classes===
For each selected database table, a class is also created from a unique template, mapping the database table. This template is:
<code>
indexing
description: "Description of class <CN:U>"
author: "EiffelStore Wizard"
date: "$Date: 2004-12-09 23:29:16 +0100 (Thu, 09 Dec 2004) $"
revision: "$Revision: 46989 $"
class
<CN:U>_DESCRIPTION
inherit
DB_TABLE_DESCRIPTION
-- rename
-- Tables as Abstract_tables
undefine
Tables,
is_valid_code
end
DB_SPECIFIC_TABLES_ACCESS_USE
create
{DB_SPECIFIC_TABLES_ACCESS} make
feature -- Access
Table_name: STRING is "<CN:U>"
Table_code: INTEGER is <CI>
Attribute_number: INTEGER is <ACNT>
-- Number of attributes in the table.
Id_code: INTEGER is
-- Table ID attribute code.
do
Result := <IC>
end
<A:A:A> <AN:I>: INTEGER is <IT>
</A> attribute_code_list: ARRAYED_LIST [INTEGER] is
-- Feature code list
once
create Result.make (Attribute_number)
<A:A:A> Result.extend (<AN:I>)
</A> end
description_list: ARRAYED_LIST [STRING] is
-- Feature name list. Can be interpreted as a list
-- or a hash-table.
once
create Result.make (Attribute_number)
<A:A:A> Result.extend ("<AN:I>")
</A> end
type_list: ARRAYED_LIST [INTEGER] is
-- Feature type list. Can be interpreted as a list
-- or a hash-table.
once
create Result.make (Attribute_number)
<A:A:A> Result.extend (<TN:I>_type)
</A> end
to_delete_fkey_from_table: HASH_TABLE [INTEGER, INTEGER] is
-- List of tables depending on this one and their
-- foreign key for this table.
-- Deletion on this table may imply deletions on
-- depending tables.
once
<DH> end
to_create_fkey_from_table: HASH_TABLE [INTEGER, INTEGER] is
-- List of associated necessary tables and the
-- linking foreign keys.
-- Creation on this table may imply creations on
-- associated necessary tables.
once
<CH> end
attribute (i: INTEGER): ANY is
-- Get feature value of feature whose code is 'i'.
do
inspect i
<A:A:A>
when <AN:I> then
Result := <CN:L>.<AN:L>
</A>
end
end
set_attribute (i: INTEGER; value: ANY) is
-- Set feature value of feature whose code is `i' to `value'.
-- `value' must be of type STRING, INTEGER, BOOLEAN, CHARACTER,
-- DOUBLE or DATE_TIME. References are made automatically from
-- expanded types.
local
integer_value: INTEGER_REF
double_value: DOUBLE_REF
boolean_value: BOOLEAN_REF
character_value: CHARACTER_REF
date_time_value: DATE_TIME
string_value: STRING
do
inspect i
<A:S:A>
when <AN:I> then
string_value ?= value
<CN:L>.set_<AN:L> (string_value)
</A><A:F:A> when <AN:I> then
double_value ?= value
if double_value /= Void then
<CN:L>.set_<AN:L> (double_value.item)
else
<CN:L>.set_<AN:L> (0.0)
end
</A><A:I:A>
when <AN:I> then
integer_value ?= value
if integer_value /= Void then
<CN:L>.set_<AN:L> (integer_value.item)
else
<CN:L>.set_<AN:L> (0)
end
</A><A:D:A>
when <AN:I> then
date_time_value ?= value
<CN:L>.set_<AN:L> (date_time_value)
</A><A:C:A>
when <AN:I> then
character_value ?= value
if character_value /= Void then
<CN:L>.set_<AN:L> (character_value.item)
else
<CN:L>.set_<AN:L> ('%U')
end
</A><A:B:A>
when <AN:I> then
boolean_value ?= value
if boolean_value /= Void then
<CN:L>.set_<AN:L> (boolean_value.item)
else
<CN:L>.set_<AN:L> (False)
end
</A>
end
end
feature {<CN:U>} -- Basic operations
set_<CN:L> (a_<CN:L>: <CN:U>) is
-- Associate the description to a piece of <CN:L>.
require
not_void: a_<CN:L> /= Void
do
<CN:L> := a_<CN:L>
ensure
<CN:L> = a_<CN:L>
end
feature {NONE} -- Implementation
<CN:L>: <CN:U>
-- Piece of <CN:L> associated with the description
end -- class CODES_DESCRIPTION
</code>
'''Note''': As for the table class generation, the template content can be adjusted, for instance to add comments or change the indexing. However, the fundamental template structure should not be changed.
Some additional tags are directly replaced by the wizard:
* The <code><CR></code> and <code><DR></code> tags are replaced with information on associated tables.
* The <code><IC></code> tag is replaced by information on the primary key (table ID).
* The <code><CI></code> tag is replaced by the table position in the database.
===2.3.3. Access class===
The <code>DB_SPECIFIC_TABLES_ACCESS</code> class is mapped to the database from the following template:
<code>
indexing
description: "Description of database tables.%
%Use this class through DB_SPECIFIC_TABLES_ACCESS_USE."
author: "EiffelStore Wizard"
date: "$Date: 2004-12-09 23:29:16 +0100 (Thu, 09 Dec 2004) $"
revision: "$Revision: 46989 $"
class
DB_SPECIFIC_TABLES_ACCESS
inherit
DB_TABLES_ACCESS
creation
make
feature -- Access
<A:A:A> <CN:I>: INTEGER is <IT>
</A> Table_number: INTEGER is <ACNT>
code_list: ARRAYED_LIST [INTEGER] is
-- Table code list.
once
create Result.make (Table_number)
<A:A:A> Result.extend (<CN:I>)
</A> end
name_list: ARRAYED_LIST [STRING] is
-- Table name list. Can be interpreted as a list
-- or a hash-table.
once
create Result.make (Table_number)
<A:A:A> Result.extend ("<CN:I>")
</A> end
obj (i: INTEGER): DB_TABLE is
-- Return instance of table with code `i'.
do
inspect i
<A:A:A>
when <CN:I> then
create {<CN:U>} Result.make
</A>
end
end
description (i: INTEGER): DB_TABLE_DESCRIPTION is
-- Return description of table with code `i'.
do
inspect i
<A:A:A>
when <CN:I> then
Result := <CN:L>_description
</A>
end
end
<A:A:A> <CN:L>_description: <CN:U>_DESCRIPTION is
-- Unique description of table `<CN:U>'.
once
create Result.make
end
</A>
end -- class DB_SPECIFIC_TABLES_ACCESS
</code>

View File

@@ -0,0 +1,223 @@
[[Property:title|Data structures use]]
[[Property:weight|0]]
[[Property:uuid|25885469-d6c8-5d1c-bbf8-c5fca5524d36]]
==1. Overview==
EiffelStore affords a context that optimizes and facilitates the use of the classes that maps your database content.
The main advantage of database-specific structures is the static checking: you can determine at compile-time the type of information you are accessing or modifying.
However, it can be more flexible to determine the type of data you are dealing with at run-time. This can be particularly useful for GUI applications, take a look at the [[EiffelStore DataView Cluster|DataView cluster]] .
Each data structure carries some meta-data about its type so that the run-time typing hazards can be avoided with assertions based on this meta-data.
The advantage of this system is two-fold:
* You can choose to use compile-time type checking or not, depending on your own needs.
* If you choose run-time type checking, assertions ensure that each object type is valid and prevent cat calls.
Let us see first [[#capabilities|what you can do]] with data structures and their context, then [[#implementation|how it is implemented]] .
==2. Data structure capabilities==
Database-specific classes and their context let you perform the following operations:
* [[#cap_storage|Storing table/view rows content]]
* [[#cap_manipulation|Manipulating abstract table/view rows content]]
* [[#cap_objects_metadata|Getting objects metadata]]
* [[#cap_database_metadata|Accessing database metadata]]
* [[#cap_more|More interesting features]]
===2.1. Storing table/view rows content===
You can store table/view rows content into classes that have the table or view name: one database table/view row correspond to one Eiffel object. Each table attribute will correspond to a class attribute with the same name. ''set'' commands enable to change the object content to insert rows in the database or update rows. [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] class can directly map database results into these objects, and you can directly create a table/view row from one object with the [[ref:libraries/store/reference/db_store_flatshort|DB_STORE]] class. Take a look at the [[Data Object Coupling|data-object coupling]] section.
===2.2. Manipulating abstract table/view rows content===
Each table/view storage structure inherits from the [[ref:libraries/store/reference/db_table_flatshort|DB_TABLE]] abstract class. This enables to handle [[ref:libraries/store/reference/db_table_flatshort|DB_TABLE]] objects as abstract database table/view structures.
You can then access or modify [[ref:libraries/store/reference/db_table_flatshort|DB_TABLE]] attributes: instead of accessing attributes with their ''name'', which implies that the object type is known at compile-time, attributes can then be accessed with a ''code''.
<code>
tablerow: DB_TABLE
...
display_attribute (code: INTEGER)
-- Display attribute with `code'.
do
io.putstring (tablerow.table_description.attribute (code).out)
end
</code>
{{note|to access attributes data with ''code'', you need to use the [[ref:libraries/store/reference/db_table_description_flatshort|DB_TABLE_DESCRIPTION]] object associated to your [[ref:libraries/store/reference/db_table_flatshort|DB_TABLE]] object. }}
===2.3. Getting objects metadata===
While manipulating <eiffel>DB_TABLE</eiffel> objects, you can easily get:
* Which database table/view the object references.
* What are the types of its attributes.
'''Note:''' you also get objects metadata through the <eiffel>DB_TABLE_DESCRIPTION</eiffel> object associated to your DB_TABLE object.
Objects metadata used in assertions ensures objects type validity. To illustrates this, let's look at the contract form of a class that manipulates "abstract" table/view rows:
<code>
set_table_code (code: INTEGER)
-- Assign `code' to `tablecode'.
tablecode: INTEGER
-- Table code.
compute (tablerow: DB_TABLE)
-- Compute `tablerow'.
require
type_is_ok: tablerow.table_description.Table_code = tablecode
</code>
===2.4. Accessing database metadata===
Basic database metadata is also available: the <eiffel>DB_SPECIFIC_TABLES_ACCESS_USE</eiffel> class (generated), stores INTEGER codes for each database table/view. These codes afford related table/view name and related new storing objects (i.e. that conforms to DB_TABLE class).
<code>
tables: DB_SPECIFIC_TABLES_ACCESS
...
new_object (code: INTEGER): DB_TABLE
-- New object of table with `code'.
do
Result := tables.obj (code)
end
</code>
===2.5. More interesting features===
The <eiffel>DB_TABLE_DESCRIPTION</eiffel> class offers more features to get table row attributes as conveniently as possible:
* The table/view row primary key value (ID)
* The list of table/view row attributes
* A selection of table/view row attributes
* The list of table/view row attributes mapped to a function.
* Printable attribute values (i.e. the associated STRING values)
==3. Implementation==
Database-specific classes can be divided into 3 types:
* Classes holding database table rows content (inheriting from <eiffel>DB_TABLE</eiffel>)
* Classes describing database tables (inheriting from <eiffel>DB_TABLE_DESCRIPTION</eiffel>)
* A class describing the database and giving access to the previous types of classes (inheriting from <eiffel>DB_TABLES_ACCESS</eiffel>)
One database table is hence associated to one table class and one description class. Both classes are closely [[#table-descr_relationship| interrelated]] to provide what the developer need. The [[#table_access_classes|table access classes]] describes the database tables and gives access to both table and description classes.
Each database-specific (generated) class inherits from an abstract class. These abstract classes gathers all the facilities that are not specific to your database, and so that can be inherited by all the database-specific classes.
Let us see abstract and database-specific classes relationship:
[[Image:tables-access-inherit]]
General and generated classes relationships
* Yellow classes are abstract.
* Green classes are database-specific.
==2. Table and description classes relationship==
Table classes, that inherit from <eiffel>DB_TABLE</eiffel>, and description classes, that inherit from <eiffel>DB_TABLE_DESCRIPTION</eiffel>, both deals with database tables. This section explains what are their own role and their relationship.
===2.1. Table classes===
As seen in the previous section, table classes merely store table rows attribute values. Their objects can be considered as database table rows, or more precisely, database table rows on the Eiffel side. These classes inherit from <eiffel>DB_TABLE</eiffel>.
Each of these classes are associated to a description class.
'''Tip:''' Use table classes to ''carry'' data.
===2.2. Description classes===
The description classes goal is 3-fold:
* [[#cap_objects_metadata|Getting meta-data]] about the table represented at run-time.
* [[#cap_manipulation|Getting table rows data]] dynamically.
* [[#cap_more|Facilitating data management]] .
These descriptions inherit from [[ref:libraries/store/reference/db_table_description_flatshort|DB_TABLE_DESCRIPTION]] .
Since they only describes a table and provide tools, description objects can be unique. EiffelStore ensures their unicity for resources optimization.
'''Tip:''' Use description classes to ''access and modify'' data.
===2.3. Relationship===
Each table class is associated to the description class corresponding to the same database table.
A table class object provides the associated table description:
<code>
row: DB_TABLE
description: DB_TABLE_DESCRIPTION
...
row := db_manager.database_result
description := row.table_description
</code>
As descriptions are unique, every table row object is associated to the same description. The following figure illustrates this:
[[Image:table-descr-objects]]
table and description objects relationship
As seen in the previous section, to manipulate abstract table/view rows content, you have to use the description class. The following example shows how to output a table row ID value.
<code>
row: DB_TABLE
description: DB_TABLE_DESCRIPTION
...
-- Link description unique object to `row' content.
description := row.table_description
io.putstring (description.attribute (description.id_name) + ": ")
io.putstring (description.attribute (description.id_code).out)
</code>
As descriptions are unique, this means that description objects are also associated to a specific table object to deal with it (i.e. access or modify its content). Actually, the table_description feature associates the description with the current object and then returns this description.
{{note|The table_description feature is still a query as the association part should not be visible at the interface level. }}
On the whole, you have to pay attention to always execute table_descriptionon your table/view row to get the appropriate description.
==3. Table access classes==
===3.1. Overview===
Table access classes provide facilities to manage table row and table description objects. They also give basic database table meta-data.
The following figure shows table access classes and their relations.
* Yellow classes are EiffelStore classes
* Green class is generated
* Pink class is an application class
[[Image:db-specific-tables-access-use]]
Table access classes BON diagram
[[Image:table-descr-access-objects]]
Relationship between the tables access object, description and table objects
===3.2. DB_SPECIFIC_TABLES_ACCESS class===
The <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> class stores the unique table description object. It also provides the following facilities:
* Every database table code
* Table descriptions from a table code
* Sample table class objects from a table code
* Table names from a table code
'''Note''': database table codes given in the class match the table codes provided by <eiffel>DB_TABLE_DESCRIPTION</eiffel>.
===3.3. Abstract access class===
The DB_TABLES_ACCESS class provides an interface for the <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> class which is non-specific to the database. This can be used by non database-specific code (for instance the [[EiffelStore DataView Cluster|DataView cluster]] ) to access database tables.
Unique table description objects and table codes are of course not directly available from this class, but the following features are still available:
* Table descriptions from a table code
* Sample table class objects from a table code
* Table names from a table code
===3.4. Use classes===
The <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> object can be accessed as a kind of "global variable" by any class which inherits from <eiffel>DB_SPECIFIC_TABLES_ACCESS_USE</eiffel>. This class also ensures <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> object unicity.
The DB_TABLES_ACCESS_USE class affords the same possibility but with no reference to the <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> object. The unique <eiffel>DB_SPECIFIC_TABLES_ACCESS</eiffel> should be set to this class as of type <eiffel>DB_TABLES_ACCESS</eiffel>.
<br/>

View File

@@ -0,0 +1,12 @@
[[Property:title|EiffelStore Generation Cluster]]
[[Property:weight|2]]
[[Property:uuid|f443ed73-14bb-9a3a-b7c7-35c2660e7784]]
The EiffelStore library lets the user associate database elements with Eiffel objects. These database elements are basically database table rows, database view rows or more generally sets of database attribute values. The easiest way to manipulate database elements is to insert their content into Eiffel objects and to work on these Eiffel objects as database elements.
A first solution to implement this is to use some adaptable structures that will fit to any database element. This is done in EiffelStore through the [[ref:libraries/store/reference/db_tuple_flatshort|DB_TUPLE]] class, which contains mainly an <code>ARRAY [STRING]</code> containing element attribute names and an <code>ARRAY [ANY]</code> containing element attribute values. This solution has one major drawback: any static checking is impossible: the developer cannot be sure at compile time of the nature of a [[ref:libraries/store/reference/db_tuple_flatshort|DB_TUPLE]] , i.e. what it represents, and cannot know if attributes number, names and types are correct. To overcome this problem, a second solution is to use data structures that statically fits to the expected database element, as introduced in the [[Data Object Coupling|DataObject Coupling]] section.
The major problem of this technique is that structures are static: one structure, so one class instead of one object, should be created for each database element.

View File

@@ -0,0 +1,90 @@
[[Property:title|EiffelStore Implementation Layer]]
[[Property:weight|1]]
[[Property:uuid|80cd0938-6837-3c17-8515-b4ac60a51d09]]
Each interface class has an implementation counterpart that implements specific DBMS handling. The class prefix indicates clearly its layer, for instance:
* [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] is the interface to perform a database selection query.
* [[ref:libraries/store/reference/database_selection_flatshort|DATABASE_SELECTION]] [ [[ref:libraries/store/reference/database_flatshort|DATABASE]] ] is its implementation.
The abstract [[ref:libraries/store/reference/database_flatshort|DATABASE]] class represents a DBMS, i.e. it is the Eiffel-side database call interface. It is inherited for instance by the instantiable <eiffel>ORACLE</eiffel> and <eiffel>ODBC</eiffel> classes.
EiffelStore enables to link common interface objects with database-specific implementation objects using a '''handle'''. This handle also enables to switch between different databases.
Let us see in 4 steps how EiffelStore implements this handle system:
==The active database handle==
The <eiffel>HANDLE_USE </eiffel>class provides access to the unique <eiffel>HANDLE</eiffel> instance. This object stores all the information about the active database, mainly:
* Database login information:
<code>
login: LOGIN [DATABASE]
-- Session login
</code>
* Database status information:
<code>
status: DB_STATUS
-- Status of active database
</code>
==Association between interface and implementation==
The association between an interface object and its implementation counterpart is done at the interface object creation.
Every interface class inherits from the <eiffel>HANDLE_USE</eiffel> class and can access the active database handle. This handle contains a <eiffel>DB</eiffel> [<eiffel>DATABASE</eiffel>] object that provides implementation objects corresponding to every interface objects.
The creation procedure for a <eiffel>DB_CHANGE</eiffel> object is for instance:
<code>
make
-- Create an interface object to change active base.
do
implementation := handle.database.db_change
create ht.make (name_table_size)
implementation.set_ht (ht)
end
</code>
==Access to the DBMS call interface==
Every implementation class can access the active database call interface by inheriting from the <eiffel>HANDLE_SPEC</eiffel> [<eiffel>DATABASE</eiffel>] class. This class provides access to the <eiffel>DATABASE</eiffel> object, i.e. an instance of class ORACLE or ODBC.
<eiffel>DATABASE </eiffel>descendants are actually wrappers for the DBMS call interfaces written in C. More precisely, call interfaces as delivered by the DBMS companies are called in an EiffelStore C library. The C libraries are then wrapped into Eiffel classes, <eiffel>DATABASE</eiffel> descendants.
==Active database selection==
As seen in the [[Database Connection|interface connection]] part, active database selection is performed by the {<eiffel>DATABASE_APPL</eiffel>}.<eiffel>set_base</eiffel> feature: when a <eiffel>DATABASE_APPL</eiffel> object calls set_base, database represented by this object is activated.
Let us give some details about the set_base feature:
* Database call interface global reference is updated through {<eiffel>HANDLE_SPEC</eiffel>}.<eiffel>update_handle</eiffel>.
* All information about the database is set to the handle, mainly:
** Login information.
** Status information.
{{note|When database is inactive, its information is stored in the <eiffel>DATABASE_APPL</eiffel> object. }}
The corresponding code looks like:
<code>
session_status: DB_STATUS
-- A session management object reference.
...
set_base
...
update_handle
if session_status = Void then
create session_status.make
end
handle.set_status (session_status)
...
</code>
{{seealso|<br/>
[[EiffelStore Interface Layer|The interface layer]] <br/>
}}

View File

@@ -0,0 +1,40 @@
[[Property:title|Data Modification]]
[[Property:weight|1]]
[[Property:uuid|ef5568ff-dc7c-4c85-0174-335fdab1bd84]]
Use the [[ref:libraries/store/reference/db_change_flatshort|DB_CHANGE]] class to perform any operation on your database that does not require access to a result. You can for instance modify table row content, drop table rows, create and delete tables.
{{note|Take a look at the [[Data Object Coupling|Data Storing]] capability if you want to '''insert''' table rows. }}
[[ref:libraries/store/reference/db_change_flatshort|DB_CHANGE]] allows you to modify the database data using the SQL language:
* Prepare your SQL query and use modify:
<code>
modification: DB_CHANGE
-- Modification tool.
...
create modification.make
modification.modify ("Update CONTACTS set Firstname = ' John'")
</code>
* Commit your changes with your session control:
<code>
session_control: DB_CONTROL
-- Session control.
...
session_control.commit
</code>
{{tip|It is always better to check the database status for errors before committing changes. }}
{{seealso|<br/>
[[Data Object Coupling|Data storing]] <br/>
[[Stored Procedures|Stored procedures]] <br/>
[[EiffelStore Implementation Layer|Implementation]] <br/>
}}

View File

@@ -0,0 +1,102 @@
[[Property:title|Data Object Coupling]]
[[Property:weight|4]]
[[Property:uuid|c51311cd-8782-0bc3-3ef7-000ea6eee37c]]
A smart way to work with relational databases is to have Eiffel objects directly mapping relational tables. Three EiffelStore classes enable this coupling:
* [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] objects [[#describe|describe a relational table]] and allow Eiffel to create objects mapping database tables.
* [[ref:libraries/store/reference/db_store_flatshort|DB_STORE]] works directly with [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] objects to [[#insert|insert data into relational tables]] .
* [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] can [[#map|map a database query result into Eiffel objects]] .
==Describing relational tables with DB_REPOSITORY==
A [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] object stores available information about a table. To access this information, you mainly have to give the table name and load the table description:
<code>
repository: DB_REPOSITORY
...
create repository.make ("CONTACTS")
repository.load
if repository.exists then
...
end
</code>
{{tip|Loading a table description is often a costly operation: table has to be fetched among existing tables then every table column description must be loaded. Hence it is better to store and reuse a repository (maybe with a HASH_TABLE) once it has been loaded. }}
Using the table information, [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] then helps generating Eiffel classes mapping relational tables:
* You can directly use {[[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] }.generate_class. Generated class may look like:
<code>
class CONTACTS
feature -- Access
id: INTEGER
...
feature -- Settings
set_id (an_id: INTEGER)
-- Set an_id to id.
do
id := an_id
end
...
</code>
{{note|The EiffelStore Wizard uses the generation.generator cluster to generate the classes mapped to your database. }}
==Inserting data in the database==
[[ref:libraries/store/reference/db_store_flatshort|DB_STORE]] lets you easily insert rows into a table using:
* the table description provided by [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] .
* a class mapping the relational table.
This is straight-forward since you only have to give [[ref:libraries/store/reference/db_store_flatshort|DB_STORE]] the object filled with the table values. Suppose you want to add a contact into your database:
<code>
storage: DB_STORE
contacts_rep: DB_REPOSITORY
a_contact: CONTACTS
...
create storage.make
-- contacts_rep is loaded and exists.
storage.set_repository (contacts_rep)
-- a_contact carries values to insert into the database.
storage.put (a_contact)
</code>
==Accessing database content with Eiffel objects==
[[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] lets you map data retrieved from the database into Eiffel objects: Result column names must match object attributes names so you can use for instance classes created by [[ref:libraries/store/reference/db_repository_flatshort|DB_REPOSITORY]] . Class <eiffel>DB_ACTION</eiffel> redefines ACTION and can be used to retrieve Eiffel objects directly into an ARRAYED_LIST:
<code>
selection: DB_SELECTION
list_filling: DB_ACTION [CONTACTS]
contact: CONTACTS
...
selection.object_convert (contact)
create list_filling.make (selection, contact)
selection.set_action (list_filling)
...
selection.load_result
if selection.is_ok then
Result := list_filling.list
end
</code>
{{note|You can see how actions are used in [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] . }}
<br/>
{{seealso|<br/>
[[Database Selection|Performing a database selection.]] <br/>
[[Data structures use|Database-specific structures use.]] <br/>
}}

View File

@@ -0,0 +1,44 @@
[[Property:title|Database Connection]]
[[Property:weight|0]]
[[Property:uuid|2cf2cb7c-e28d-5d06-b03e-e2b17c1f6879]]
* To connect to your database, you have to create a '''handle''': this handle actually links the interface classes with the corresponding implementation classes mapped to your DBMS. This handle is implemented by the DATABASE_APPL class:
<code>
database_appl: DATABASE_APPL [ODBC]
-- Database handle.
...
create database_appl.login (a_name, a_psswd)
database_appl.set_base
</code>
<br/>
{{note|Calling set_base links the EiffelStore interface to this specific handle. }}
{{tip|You can manage handles to many databases: as an instance of <eiffel>DATABASE_APPL</eiffel> stands for a specific database handle, you only have to create one instance of <eiffel>DATABASE_APPL</eiffel> for every DBMS handle you wish to create. Do not forget to call set_base to activate appropriate handle. }}
{{note|The generic parameter of <eiffel>DATABASE_APPL</eiffel> specifies the actual DBMS used. }}
* Once your handle is created, you have to create a session manager which will allow you to manage your database; specifically, to establish a connection, disconnect and also handle errors. The class <eiffel>DB_CONTROL</eiffel> enables your application to control the functioning and status of your database and to request any information about it.
<code>
session_control: DB_CONTROL
-- Session control.
...
create session_control.make
session_control.connect
</code>
{{note|Take a look at the [[Database control|database control]] part to see how to use <eiffel>DB_CONTROL</eiffel> capabilities. }}
{{seealso|<br/>
[[Database control|Database control and error handling]] <br/>
[[EiffelStore Implementation Layer|Implementation]] <br/>
}}

View File

@@ -0,0 +1,73 @@
[[Property:title|Database control]]
[[Property:weight|7]]
[[Property:uuid|432c6e51-36d5-b469-c924-eb821713256a]]
Use the [[ref:libraries/store/reference/db_control_flatshort|DB_CONTROL]] class to check or change database status and behavior. The main operations you are likely to use are:
* Handling database errors.
* Connecting to the database.
* Committing changes in the database.
==Handling database errors==
Every EiffelStore interface class has an is_ok feature. This enables to check directly if the last database operation has been successful.
When an error is detected, you can use [[ref:libraries/store/reference/db_control_flatshort|DB_CONTROL]] to obtain further information about the error:
* error_message provides a description of the error that occurred.
* error_code returns a code corresponding to the error type. This code enables to handle specific errors within your code without parsing the error_message.
* warning_message provides a warning message about the last transaction performed.
Once you have handled your error, for instance by displaying the error_message on screen, you can call reset to perform new database transactions.
==Managing database connection==
[[ref:libraries/store/reference/db_control_flatshort|DB_CONTROL]] lets you connect, check your connection, and disconnect from the database.
The following example sum up these capabilities:
<code>
session_control: DB_CONTROL
...
session_control.connect
if session_control.is_connected then
-- Perform your transactions here.
session_control.disconnect
end
</code>
==Committing changes in the database==
With many database systems, modifications you make to the database are usually not saved immediately. Rather, changes are accumulated in a "transaction". At some point the entire transaction is committed to the database (i.e., the changes are actually made to the data) or the entire transaction is rolled back (i.e., absolutely no changes are made to the data.) You can manage database modification through 2 commands:
* ''Commit'' saves the changes in the database.
* ''Rollback'' restores the database content to its state after the most recent commit.
The following example illustrates the use of these commands:
<code>
session_control: DB_CONTROL
...
from
until
transaction_completed or else not session_control.is_ok
loop
-- Perform your transaction here.
end
if session_control.is_ok then
session_control.commit
else
session_control.rollback
end
</code>
The loop performs a multi-step transaction. If transaction is not carried out entirely, the database could stay in an invalid state: this code ensures that database remains in a valid state.
{{caution|Some databases can be in an auto-commit mode. Furthermore, some special database commands can automatically commit database changes. }}
{{seealso|<br/>
[[Database Connection|Database connection]] <br/>
[[EiffelStore Implementation Layer|Implementation]] <br/>
}}

View File

@@ -0,0 +1,40 @@
[[Property:title|Database Selection]]
[[Property:weight|2]]
[[Property:uuid|de759a74-b3e1-c937-e620-67526c116925]]
Use the [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] class to select data from the database. Once you have selected the data, you can [[Selection Access|access]] it with convenience using adaptative EiffelStore structures.
{{note|Take a look at the [[Data Object Coupling|Database/Eiffel objects Coupling]] if you need information about your database structure. }}
[[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] enables your application to get database content using SQL 'select' queries:
* You can carry out 'select' queries in an intuitive way using directly the SQL language:
<code>
selection: DB_SELECTION
-- Selection tool
...
create selection.make
selection.set_query ("select * from CONTACTS where firstname = 'John'") -- Set query text
selection.execute_query -- Execute query
... -- Process query results
selection.terminate -- Clear database cursor
</code>
{{tip|<br/>
1) Always check the database status for errors after your 'select' query.<br/>
2) EiffelStore supports a finite number of concurrent queries. Call <code>{DB_SELECTION}.terminate</code> to free resources from a completed query. }}
* You can also customize your selection using [[Query variables|bind variables]] .
{{seealso|<br/>
[[Query variables|Binding variables in a database query.]] <br/>
[[Data Object Coupling|Coupling database objects with Eiffel objects.]] <br/>
[[Selection Access|Accessing selected data from the database.]] <br/>
}}

View File

@@ -0,0 +1,9 @@
[[Property:title|EiffelStore Interface Layer]]
[[Property:weight|0]]
[[Property:uuid|abd6c880-e0d8-0961-8cd2-d2ca43c1ce28]]
The Interface layer gathers a set of classes covering all the capabilities an application needs to interface efficiently with a DBMS.
Each of the following sections describes a particular database capability accessed through the EiffelStore interface layer. In general, each of these capabilities is made available in the interface layer by one class.

View File

@@ -0,0 +1,54 @@
[[Property:title|Query variables]]
[[Property:weight|5]]
[[Property:uuid|37222a07-a8f8-e57d-45f0-3b28cd66d660]]
If you have to execute database queries which only differ in the expression values, you can create a template query and then bind variables into this template for each execution.
This mechanism can be applied for requests with a result ([[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] ) or without ([[ref:libraries/store/reference/db_change_chart|DB_CHANGE]] ). Hence both classes inherits from STRING_HDL that actually handle variable binding.
To use variable binding:
* [[#create|Create]] a template query.
* [[#bind|Bind]] variables to the query.
==Creating a template query==
Template queries are parsed to replace each variable by its bound value. To create a template query, you have hence to put directly variables where the values would take place.
Variables syntax is simple: the ':' special character followed by the variable name.
<code>
selection: DB_SELECTION
Bind_var: STRING = "firstname"
...
create selection.make
selection.set_query ("Select * from CONTACTS where Firstname = ':" + Bind_var + "'")
</code>
{{note|The code example shows how to bind variables to a [[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] object but the mechanism is exactly the same for [[ref:libraries/store/reference/db_change_chart|DB_CHANGE]] objects. }}
==Binding variables to a query==
Once you have created your query, you can map variable names to values and execute the query:
<code>
selection: DB_SELECTION
Bind_var: STRING is "firstname"
...
loop
io.read_line
selection.set_map_name (io.laststring, Bind_var)
selection.execute_query
...
selection.unset_map_name (Bind_var)
end
</code>
{{seealso|<br/>
[[Database Selection|Performing a database selection.]] <br/>
[[Data Modification|Modifying database content.]] <br/>
}}

View File

@@ -0,0 +1,116 @@
[[Property:title|Selection Access]]
[[Property:weight|3]]
[[Property:uuid|3b4fdde3-d903-55c8-0068-cee2407db280]]
Once you have [[Database Selection|selected data]] from the database, it returns a set of rows containing queried columns values. Each row loaded with DB_SELECTION is stored in a DB_RESULT object. The easiest way to access the data is thus to refer to DB_RESULT objects themselves.
{{note|Take a look at the [[Data Object Coupling|Database/Eiffel objects Coupling]] to learn advanced data handling features. }}
To use DB_RESULT, process in 2 steps:
* [[#retrieve|retrieve]] DB_RESULT objects.
* [[#access|access]] DB_RESULT content.
==Retrieving DB_RESULT objects==
[[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] class provides different ways to customize result loading:
* You want to access an '''unique''' row: [[ref:libraries/store/reference/db_result_flatshort|DB_RESULT]] object is accessible via cursor:
<code>
selection: DB_SELECTION
my_result: DB_RESULT
...
selection.query ("...")
if selection.is_ok then
selection.load_result
my_result := selection.cursor
end
</code>
* You want to load a '''complete list''' of rows: [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] can store [[ref:libraries/store/reference/db_result_flatshort|DB_RESULT]] objects in a list. To do this, you have mainly to provide a LIST object to DB_SELECTION with set_container:
<code>
selection: DB_SELECTION
container: ARRAYED_LIST [DB_RESULT]
...
create container.make (Max_results)
...
selection.set_container (container)
selection.load_result
...
from
container.start
until
container.after
loop
...
end
</code>
{{tip|Provide [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] with the LIST structure convenient for what you need to do with the results. }}
* You want to '''select part''' of the result set: you can set an action in [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] that will be executed each time a row is loaded. This action can for instance manipulate current row and define a stop condition.
** You need to define a descendant of class ACTION and set it to [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] :
<code>
class
MY_ACTION
inherit
ACTION
redefine
execute, found
end
...
execute
do
i := i + 1
end
...
found: BOOLEAN
do
Result := i >= Max_result
end
</code>
** Then set action to [[ref:libraries/store/reference/db_selection_flatshort|DB_SELECTION]] :
<code>
selection: DB_SELECTION
action: MY_ACTION
...
selection.set_action (action)
selection.query ("...")
if selection.is_ok then
selection.load_result
end
</code>
==Accessing content of DB_RESULT==
A DB_RESULT object merely carries data retrieved from the database. You have to convert it to a DB_TUPLE to access data within the retrieved row conveniently, i.e. mostly the column values:
<code>
selection: DB_SELECTION
tuple: DB_TUPLE
...
create tuple.make_from_cursor (selection.cursor)
if tuple.count >= 2 and then tuple.column_name (2).is_equal ("Firstname") then
io.putstring (tuple.item (2).out)
end
</code>
{{seealso|<br/>
[[Database Selection|Performing a database selection.]] <br/>
[[Data Object Coupling|Coupling database data and Eiffel objects.]] <br/>
}}

View File

@@ -0,0 +1,81 @@
[[Property:title|Stored Procedures]]
[[Property:weight|6]]
[[Property:uuid|9979af90-07c3-8b20-448e-04454a75c801]]
When sending requests to the database, the request is first parsed then executed. Instead of parsing many times the same requests, i.e. with only changes in expression values, most of RDBMS enable to '''precompile''' requests. These requests can then be executed as routines and are identified by a name and a signature.
EiffelStore lets you use stored procedures with [[ref:libraries/store/reference/db_proc_flatshort|DB_PROC]] class to:
* [[#execute|Execute]] a stored procedure.
* [[#create|Create]] a stored procedure.
==Executing a stored procedure==
To execute a stored procedure:
* Create a [[ref:libraries/store/reference/db_proc_flatshort|DB_PROC]] object and load the stored procedure you want to use:
<code>
procedure: DB_PROC
...
create procedure.make ("UPDATE")
procedure.load
if procedure.exists then
...
end
</code>
* Execute the procedure through a [[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] (if a result is expected) or a [[ref:libraries/store/reference/db_change_chart|DB_CHANGE ]] object (otherwise).
{{note|Requests with a result ([[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] ) or without ([[ref:libraries/store/reference/db_change_chart|DB_CHANGE]] ) are both abstract '''expressions'''. DB_PROC executes an abstract expression using an object of [[ref:libraries/store/reference/db_expression_chart|DB_EXPRESSION]] type, which corresponds to an abstract expression. [[ref:libraries/store/reference/db_selection_chart|DB_SELECTION]] and [[ref:libraries/store/reference/db_change_chart|DB_CHANGE]] inherits from [[ref:libraries/store/reference/db_expression_chart|DB_EXPRESSION]] . }}
You can execute your request mostly like a basic one:
** Create your request.
** Bind request variables. Variables are stored procedure arguments.
{{note|Take a look at how to [[Query variables|bind variables]] to a query. }}
** Execute the query through the DB_PROC object.
<code>
procedure: DB_PROC
expr: DB_CHANGE
...
procedure.execute (expr)
expr.clear_all
</code>
** Check for errors and load result if any.
==Creating a stored procedure==
DB_PROC also enables you to create or drop stored procedures:
* Use store to create a procedure.
* Use drop to delete one.
The following example shows how to overwrite a procedure in the database:
<code>
procedure: DB_PROC
...
create procedure.make ("NEW_PROCEDURE")
procedure.load
if procedure.exists then
procedure.drop
end
procedure.load
if not procedure.exists then
procedure.set_arguments (<<"one_arg">>, <<"">>)
procedure.store ("update contacts set firstname = one_arg where contactid = 1")
end
</code>
{{seealso|<br/>
[[Database Selection|Performing a database selection.]] <br/>
[[Data Object Coupling|Coupling database data and Eiffel objects.]] <br/>
}}

View File

@@ -0,0 +1,11 @@
[[Property:title|EiffelStore Tutorial]]
[[Property:weight|1]]
[[Property:uuid|542caa33-5dc1-d2ea-414b-b1739045edd9]]
EiffelStore's main classes are grouped into 2 main layers:
* The [[EiffelStore Interface Layer|interface layer]] provides a high-level, unique interface for every DBMS.
* The [[EiffelStore Implementation Layer|implementation layer]] customizes high-level routines to various DBMS handles.
Other clusters can facilitate your database management:
* The [[EiffelStore Generation Cluster|generation cluster]], with the EiffelStore wizard, generates a facilitated and dynamic interface to your database.
* The [[EiffelStore DataView Cluster|dataview cluster]] lets you create a customized database GUI.

View File

@@ -0,0 +1,10 @@
[[Property:title|EiffelStore]]
[[Property:weight|1]]
[[Property:uuid|f43ab3e6-4551-632a-384b-4964d1436730]]
==EiffelStore Library==
Type: Library <br/>
Platform: Any <br/>
EiffelStore provides facilities for interfacing an application with various DataBase Management Systems (DBMS). It contains a powerful set of classes grouped into layers representing different levels of abstraction.

View File

@@ -0,0 +1,6 @@
[[Property:title|Database access]]
[[Property:weight|-8]]
[[Property:uuid|f5d26942-de52-76cc-0f41-66b959a78999]]
== Database access solutions ==