Smalltalk Object Sink - Documentation

1. Table of Contents


     1. Table of Contents
     2. Introduction
     3. Installation
          3.1. General
          3.2. Installations for the backend strategy
          3.3. Choose the right strategy
     4. Direct Usage
          4.1. Configuration
               4.1.1. General
               4.1.2. File strategy
               4.1.3. SQLite strategy
               4.1.4. PostgreSQL strategy
          4.2. Open and close a database
          4.3. Transactions
          4.4. Navigation
          4.5. Queries
     5. Tiny Components
          5.1. Database
          5.2. Transaction
     6. Samples
          6.1. Users
          6.2. Same using Tiny Components

2. Introduction

The Tideland Smalltalk Object Sink (SOS) is a Smalltalk framework providing an ODBMS. It is able to store and retrieve all types of classes in a transactional context. They can be reached by navigation and simple queries.

The storrage is done by different backend strategies. A simple one is the integrated in-memory database which writes the data asynchronously into a flat file. More scaling backend strategies are using an RDBMS. Currently SQLite and PostgreSQL are supported.

3. Installation

3.1. General

The Tideland SOS is distributed as a monticello package. It can be retrieved out of the WebDAV repository http://downloads.tideland.biz/software/. This repository also contains the Tideland Common Smalltalk Library (CSL) package. It has to be installed before, because SOS uses some of the CSL functionalities. No further installations are needed if the in-memory database is used as backend strategy.

3.2. Installations for the backend strategy

Depending on the type of the backend strategy additional packages have to be installed. Additionally external software has to be installed and configured.

SQLite
The usage of SQLite needs the SQLite 3 library inside the library path of the virtual machine and additionally the SQLite FFI wrapper installed into the image. There are two different ones, one is especially for Mac OS X. But both provide the same functionality.
PostgreSQL
Users of the PostgreSQL backend strategy need a reachable database together with a technical useer, who is allowed to create tables. You should also configure a password for this technical user. Additionally the PostgreSQL Client for Squeak has to be installed into the image.

3.3. Choose the right strategy

Each strategy has its own strength and weakness. The usage of all three is equal but the've got differences in speed and scaleability.

In-Memory
Simple, fast, but only working for one image at a time.
SQLite
Simple, not as fast as the in-memory strategy, but lower memory consumption, only working for one image and one transaction at a time.
PostgreSQL
Fast, some overhead for the datase installation, lower memory consumption than the in-memory strategy, multiple accessing images and parallel transactions.

So for small and performant systems the in-memory strategy should be used. Larger databases can use the SQLite strategy, but they should only have small or no parallel transaction. Larger, scaleable applications should use the PostgreSQL strategy. In this case several application instances can access the same database.

4. Direct Usage

4.1. Configuration

4.1.1. General

A common variable for all configurations is the initializationBlock which is called after the opening of the database. The block is evaluated with an open transaction. E.g. it can be used for the init of special users.

cfg initializationBlock: [:tx |
    (tx root: 'UserAccounts' includesKey: 'Administrator')
        ifFalse: [tx root: 'UserAccounts' at: 'Administrator' put: self createAdministrator]].

4.1.2. File strategy

The file strategy is configured by the SosFileConfiguration. It needs a relative or full qualified filename. The configuration will be created through

cfg := SosFileConfiguration fileName: '/var/data/myApp.sdb'.

In case of this filename a backup named .../myApp.sdb.save will be maintained.

4.1.3. SQLite strategy

The SQLite strategy is configured by the SosSqliteConfiguration. It needs a relative or full qualified filename. The configuration will be created through

cfg := SosSqliteConfiguration fileName: '/var/data/myApp.db'.

4.1.4. PostgreSQL strategy

The PostgreSQL strategy is configured by the SosPgConfiguration. It needs the database hostname, the port number, the name of the database, the user for the connection and this users password.

cfg := SosPgConfiguration 
        hostname: 'localhost' 
        portno: 5432 
        databaseName: 'tsos' 
        userName: 'tsos' 
        password: 'tsos'.

4.2. Open and close a database

The database will be opened through the statement

db := SosDatabase open: cfg.

The db variable now acts as a provider for new transactions and for the closing of the database at the end of the application through

db close.

4.3. Transactions

A new transaction instance can be obtained through

tx := db newTransaction.

The instance can be used for one or more database transactions. Each has to be started through

tx begin.

or shorter during the retrieving with

tx := db newTransaction begin.

Added or changed instances will be persisted after a

tx commit.

If nothing has to be persisted, e.g. after a read only access to the objects, the transaction can be aborted through

tx abort.

4.4. Navigation

The typical usage of an ODBMS is through navigation. The database is seen as a large network of objects. To get a starting point for a navigational access to these objects the ODMG standard provides root objects. Those are associations between a name and the entry level object.

Often those named objects are collections to manage a larger amount of objects, e.g. customers. But those collections aren't optimized for the usage with an ODBMS. If they use arrays for storrage this will be loaded completely. But often only one special instance is wanted. A customer by a given id or a user by a given login. So the SOS changed the idea of root objects a bit and provides root objects with a name and a key. The name is the same as it would be used for a collection, the key leads to the concrete object.

"Retrieve the user out of the user dictionary."

userAccount := tx root: 'UserAccounts' at: login.

The root can be seen as a Dictionary of Dictionaries with strings as keys. Beside the retrieving shown above also an automatic adding in case of a missing key is possible.

admin := tx 
            root: 'UserAccounts' 
            at: 'Administrator' 
            ifAbsentPut: [self createAdministrator].

A direct test for a key can be done using

(tx root: 'UserAccounts' includesKey: 'Administrator')
    ifTrue: [...]
    ifFalse: [...].

and a direct setting with

tx root: 'UserAccounts' at: 'Administrator' put: self createAdministrator.

Also enumerations are possible with

tx root: 'Orders' keysAndValuesDo: [:id :order |
    Transcript 
        show: id; 
        show: ' - ';
        show: order date;
        show: ' - ';
        show: order customer name;
        show: ' - ';
        show: order amount;
        cr].

All other instances can be reached via navigation. Value holders as replacements of instance variables will load their value dynamicly when they are accessed. So in

street := userAccount person address street.

first the person will be loaded, followed by the address and then the street. Other instance variables will remain value holders. If the prefetching is enabled inside the configuration all instance variables of a complex object will be loaded directly one step ahead [1].

4.5. Queries

Another way to retrieve objects, e.g. when they are not accessable directly as root objects, is to create a query and let it execute through the transaction. SOS supports simple queries for instances of one class or an array of classes. Such a query will return all instances of the given class(es). But the result can also be narrowed by passing an instance variable name, an operator and a value to the query.

"Retrieve all addresses where the city is Oldenburg."

query := SosQuery type: Address instVarName: #city is: 'Oldenburg'.
results := tx query: query.

results do: [:each |
    Transcript 
        show: each street;
        show: ' - ';
        show: each zipCode;
        show: ' - ';
        show: each city;
        cr].

Possible operators for the query are:

  • is:
  • isNot:
  • startsWith:
  • endsWith:
  • contains:
  • greaterThan:
  • greaterEqual:
  • lowerThan:
  • lowerEqual:

The query result is not ordered. This can be switched on with

query := SosQuery type: Address instVarName: #city is: 'Oldenburg'; order.
results := tx query: query.

or again switched off with

query dontOrder.

5. Tiny Components

The SOS also provides two tiny components for the usage inside a tico container. Both ticos are very simple. The first one is the database tico, registered as a singleton and providing the access to the container database. The second one is the transaction tico, which is the only interface for using classes or ticos.

5.1. Database

The database tico has to be configured as a singleton with the id database. It only needs another tico with the id configuration. This tico has to provide a method named databaseConfiguration which answers with an instance of the database configuration to use.

5.2. Transaction

The transaction tico is the main component for the interaction with the SOS via tiny components. It expects the database tico registered with the id database. Registered with the id transaction it can be controlled externaly via

tx := container tico: #transaction.
userManagement := container tico: #userManagement.

tx begin.
userManagement addUser: aUser.
tx commit.

The transaction tico above can be reused and all components needing a transaction tico just need to implement transaction: as tico-setter. Each send of begin starts a new nested database transaction, commit and abort work on the top one. Additionally commitAll, abortAll and abortAllButLast are provided for transaction stack control.

The access methods are the same as in the standard transaction class. You can use

  • root:at:
  • root:at:ifAbsentPut:
  • root:at:put:
  • root:includesKey:
  • root:removeAt:
  • query:

They all work in the context of the top transaction.

6. Samples

6.1. Users

The application uses the in-memory database and stores user accounts in the root named UserAccounts. During the following sample the password of the user which login is stored in the variable aLogin will be checked.

cfg := SosFileConfiguration fileName: '/var/data/myApp.sdb'.
db := SosDatabase open: cfg.

tx := db newTransaction begin.
userAccount := tx root: 'UserAccounts' at: aLogin.

access := (userAccount passwordIs: aPassword)
            ifTrue: [#granted]
            ifFalse: [#denied].

...

tx abort.
db close.

6.2. Same using Tiny Components

container := CslContainer with: configuration.

...

tx := container tico: #transaction.
userManagement := container tico: #userManagement.

tx begin.

userAccount := tx root: 'UserAccounts' at: aLogin.

access := (userAccount passwordIs: aPassword)
            ifTrue: [#granted]
            ifFalse: [#denied].

...

tx abort.

Back ...


  1. Not yet implemented.