XSQL_RootBase - Implementing an access package (internal class - not supported as interface)
Implementing an access package for supporting another not yet supported type of relational database means implementing an RDB access package, which inherits from RootBase_RDB . The SQL_RootBase package provides some basic functionality that is helpful for most RDB access packages (conversion tables, link cache etc).
The typical implementation of an access package is documented in XSQL_RootBase class, which provides a list of functions to be implemented in order to support an SQL access package.
Access packages to relational databases support two ways of accessing columns. One is by column or attribute name. Since several systems support internal column numbers when accessing data from within a program, columns might be accessed via column number, also. In order to access columns by number, the GetColumnNumber() function has to be overloaded in order to provide the proper column number for each table column/attribute.
When using column numbers, GetMemo() and InsertMemo() have to be overloaded as well, since those functions are referring to column names rather than to column numbers.
The access logic is mainly managed by the SQL_RootBase and SQLTable base class. In order to provide package (RDB) specific functionality, those classes have to be overloaded in corresponding OR-Mappers.
Implementing an OR-Mapper requires overloading the following functions in SQL_RootBase :
- [ StartCommit ]
- [ StopCommit ]
Moreover, following functions have to be overloaded in the SQLTable class:
Instance operations are introduced by a row function ( SelectRow() , InsertRow() , UpdateRow() , DeleteRow() ), which usually locate the requested row for the operation. After locating a row in a table, several column function calls are made ( GetColumn() , SetColumn() , UpdateColumn() ) in order to read, create or update column values. Column values are provided as character data. Finally, the Finish...() functions are called in order to indicate the end of row processing. The functions usually have to be be overloaded in order to perform final row processing.
Apart from updating object attribute values, link information will be updated before and after updating attribute values in instances. In order to update link information, LinkInstance() or LinkOwner() and UnlinkInstance() or UnlinkOwner() have to be implemented in order to maintain M:N or 1:M relationships. Both functions are called only ones for a table row in order to create or delete a parent (reference) or relationship link.
In case of parent (owner) links, the link value has to be updated in the attribute passed to the function. In case of a relationship (instance) link, a mapping row has to be inserted into the M:N relationship table.
Data but also table column names require conversion. In order to convert column and table names properly, the base class SQL_RootBase provides a name conversion function Name() . From the name and the database specific maximum name length, the function constructs an appropriate database specific table or attribute name, which correspond to the name generated as table or attribute name when generating the table definition. All functions receiving table or attribute names, receive the original ODABA type or property names, which have to be converted to table or attribute names.
Attribute values are always passed in string formats (ASCII or Latin1) with a terminating 0. Following data formats are passed:
- string - ASCII string (latin1)
- integer - "[-]n*[.n*]" (decimal point according precision definition)
- float - "[-]n*[.n*][E[-]n*]"
- time - "hh:mm:ss,hs"
- date - "yy-mm-dd"
- datetime - "yy-mm-dd hh:mm:ss,hs"
- guid - "A-xxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxxxxxx"
Values have to be passed in both directions referring to the same format, i.e. the access package will obtain values in the format above when updating columns and has to return values in an appropriate format when reading values.
Transaction management is mainly organized on ODABA level, i.e. a request of storing instances to the database is submitted by ODABA only, when committing a transaction. Thus, all update requests are send to the root base in the commit phase.
There are, however, RDB specific requirements passed to SQL_RootBase while a transaction is running. Thus, LinkInstance() and UnlinkInstance() requests are sent while running a transaction and will be cached by SQL_RootBase .
When committing a transaction, StartCommit() is called in order to indicate the beginning of the commit request. StopCommit() indicates, that committing data has been finished. StartCommit() is called in order to maintain table links. All links to be removes are reset here. When reimplementing or overloading the function, one has to take into account, that links have to be removed, before instances can be stored to the transaction. Between StartCommit() and StopCommit() all updated requests are submitted by ODABA calling UpdateInstance() or UpdateMemo() . After storing instances, StopCommit() is called, which will setup new links created during the transaction. This can be done, after instances have been created within the database.
In order to update the relational database properly, at least the following functions have to be implemented:
As long as StartCommit() has not been called, the access package can assume, that access is read-only. This is true also after StopCommit() .
By default, link requests are submitted in the EndCommit() function. The function reads all link and unlink requests from the cache (link_cache) and call LinkInstance() or UnlinkInstance() in order to handle the request. Those functions must be overloaded in the appropriate access package.
For write optimization, it might, however, be more efficient processing the link cache in the access package. in this case, outstanding link requests must be written to database before terminating the commit phase. Link requests can be obtained from the link cache ( link_cache.RemoveHead() ).
New entries are usually created via an update request. In order to distinguish new instances from old instances, the data position (acb::GetPosition()) can be checked. In case the position is 0, the instance is considered as new instance. In order to mark the instance as existing after creating is, the position should be set to a positive value (loid is suggested).