A gentil introduction to using Pliant databases
Defining the database
In this first sample, we define the structure of our initial sample database.
The 'public' statement is intended to make all data types (Article, Order, Custoomer and Shop) and variables (shop_database) available to the external. This sample listing is assume to be the listing of /pliant/sample/shop1/database.pli module, and we'll see the importance of public a bit later when introducting the user interface sample module for handling the database..
Database model digression
What might look strange to you at first if you are familiar with the now standard database relational model, is that 'Set' is used both for declaring tables in the 'Shop' list of tables, and to declare articles subtable in orders records. This is because Pliant database model is tree. Just like in any tree data structure, we have nodes and leafes. 'Shop' is defining the root node of the tree. Nodes are either types (records) or set field (table or subtable). Leafes are non set fields such as 'ref' or 'count'.
If we want to model the database according to the relational model, we would write:
The difference is that, under the database relational model, a database is a set of tables, each table is a set of records and each record contains a well defined set of fields. So, the 1 to n relations, in our sample the fact that one order contains n articles is coded
Honesty makes me say that the reliational model, from the design point of view, is a better model than the tree model used by Pliant database engine, because it's more flexible. On the partical side, it's another issue. Please refer to the 'An introduction to Pliant storage' for extra details.
Back to defining the database
Back to our initial sample, the effective database is declared on the line:
(gvar Database:Shop shop_database) load "data:/my_corp/shop/shop.pdb" mount "/my_org/shop"
'Database:xxx' says that we want a Pliant database stored in XML like file (ASCII), where xxx is a data type, here 'Shop', that provides the layout for the database content.
'load' is defining the file where database content will be stored on disk.
'mount' is defining where the database tree will be in the Pliant global databases tree.
On most applications, an extra 'log' option will be used:
(gvar Database:Shop shop_database) load "data:/my_corp/shop/shop.pdb" log "data:/my_corp/shop/shop.log" mount "/my_org/shop"
The 'log' option defines where the database infinit log will be strored.
The infinit log of your database might become huge over time, so that you might want to compress it at some point. This can be done automatically through:
shop_database configure "encoding zlib"
The last line means: no more than 64 MB of uncompressed datas at the end of the shop.log file.
One last point about the database files. If the file:/pliant_data/my_corp/shop/ directory does not exist, the changes applied to the database will not be logged, so lost in the end. The directory can be created either using a file browser or through a Pliant instruction:
A first use of the database
The following sample might be executed within Pliant interpreter:
The result, in file file:/pliant_data/my_corp/shop/shop.pdb looks like:
<plogin timestamp="Sat, 31 May 2008 12:23:25 GMT" />
In this first sample, we see that Pliant databases are mostly handled through pointers, and that database pointers (as opposed to standard pointers that have type Pointer:xxx or Link:xxx) have type Data:xxx
In our sample, 'shop_database' is the database object and 'data' method is used to get a pointer to the root of the database tree. So, the first line
var (Data Set:Order) shop_order :> shop_database:data order
is the same as:
var Data:Shop shop_root :> shop_database data
The first line get's the pointer to the root of the database,
The two next lines are easy to understand:
shop_order create "101"
The first line creates a new record associated with key "101" in the 'Order'' table,
On the last lines:
a ref := "r2735"
we see that changing some database content is fairly simple at application level: we can use the standard ':=' operator just like for ordinary variables, and the reason is that the database machinery is concentrated in the 'Data:xxx' special pointers.
A first user interface for accessing the database
Here is the /pliant/sample/shop1/index.ui sample module that provides a first, incomplete, user interface for the sample shopping database:
When reading the following explainations, you are assumed to be mastering the Pliant UI (user interface) basic concepts introduced in other articles about the UI.
The homepage of the sample database user interface is defined by:
If you are using a single Pliant process for the server and the client, you should be abble to connect the sample database using loopback:/pliant/sample/shop1/menu URL. How to properly map URLs to modules when using a different process, or a different computer, for the client and the server, and maybe even using a standard web browser for the client, is outside the scope of this article. Only the configuration files and final URL would change, not the code.
Then, each order is mapped to an URL so that it could be accessed directly from another application through a link. In the single process configuration, the URL for order 'o1' would be:
More on records keys and scanning
'each' instruction is used to scan all the records in a table or subtable. In the first sample, we wrote:
each a o:article
The first parameter (here 'a') provided to each is an identifier that will automatically declare a local variable which is a data pointer to a record of the scanned table or subtable (here Data:Order).
When we have a pointer to a record, we can get the associated key with 'keyof':
each a o:article
The key associated with each record in a table or subtable cannot be changed. This is a serious limitation of Pliant database engine over the relational model. There would be no difficulty to implement the ability to change the key associated with a record, but it would not be a good idea. Changing the key works great on a closed database single server configuration, but is a nightmare in a multi servers configuration. So, Pliant code does not provide the feature so that people are forced to cope with the contrain at database planification time (early in the process) rather than having to cope with it when the project get's large and a lot of code is already written and needing the feature.
'each' can have two extra options, 'filter' and 'sort':
each o shop_database:data:order filter o:delivered=undefined sort o:ordered
here, 'filter' is used to skip all orders that have already been delived (the delivery timestamp is defined) and 'sort' is used to scan records according to the order confirmation date as opposed to scanning according to order key.