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.
- Not yet implemented.