Largest Provider of Commercial Smalltalk
Cincom is one of the largest commercial providers of Smalltalk, with twice as many customers and partners as other commercial providers.

Tom Nies

Get Started

GLORP Database Converters

While working on the new Cincom® ObjectStudio® mapping tool that generates code for the object-relational mapping framework GLORP, we built some GUI for handling database converters.

GLORP Database Converter
GLORP Database Converter

Let’s take a quick look at today’s GLORP database converters.  Database converters are used for converting back and forth between database and object representations. They are used by Direct Mappings where one column of a table is mapped with one instance variable of a class. A Direct Mapping in GLORP is normally coded like this:

(aDescriptor newMapping: DirectMapping)
              from: #catalogID
              to: (table fieldNamed: ‘CATALOG_ID’).

Depending on the information in the table and class model descriptor, GLORP automatically assigns a database converter to the mapping. In this particular case, since catalogID is a string and the field CATALOG_ID is defined as a varchar:, 255 GLORP will assign it the DelegatingDatabaseConverter named stringToString.  So if you want different behavior, you can write your own converter and assign it to the Direct Mapping with the converter: setting method.

Types of Converters

In GLORP, there are three kinds of database converters:

  1. Null converter
  2. Delegating Database converter
  3. Pluggable Database converter

Null Converter

This is a no-op converter. It simply returns the value that it receives from Smalltalk or the database.

Delegating Database Converter

Here we delegate the conversion to another object; typically this will be the database platform.  The creation of the converter is also done on the instance side of the DatabasePlatform. Let’s take a look at the dateConverter. On DatabasePlatform, it’s defined as:

^DelegatingDatabaseConverter
                             named: #date
 hostedBy: self
fromStToDb: #toDate:for:
                             fromDbToSt: #readDate:for:

Each database platform can have its own implementation of the fromStToDb- and fromDbToSt-defined methods, but a platform can also override the entire delegating database converter by its own definition.

The methods defined in the fromStToDb and fromDbToSt have two arguments. The first one is the object that needs to be converted, and the second one is the database field type definition.

If we look at the implementers of toDate:for:, we see that two classes have this method implemented.

DatabasePlatform>>toDate:for:
toDate: anObject for: aType
              anObject isNil ifTrue: [^nil].
              anObject class = Date ifTrue: [^anObject].
              ^anObject asDate.

and

SQLite3Platform>>toDate:for:
toDate: anObject for: aType
              | stream |
              anObject isNil ifTrue: [^nil].
              stream := String new writeStream.
              self
                             printDate: (super toDate: anObject for: aType)
                             isoFormatOn: stream.
              ^stream contents

SQLite doesn’t support timestamps, and all Smalltalk objects must be converted to strings before they can be handed to the database.

On the other hand, readDate:for: only has one implementer since all data from the database is normally  received as a String, and the same conversion has to be done to convert to a Smalltalk date object.

You can also override the entire converter in a database platform. For the dateConverter, this is done in the SQLServerPlatform.

^DelegatingDatabaseConverter
                             named: #date
                             hostedBy: self
                             fromStToDb: #dateToTimestampConversion:for:
                             fromDbToSt: #readDate:for:.       

Since SQLServer doesn’t have date types, the date must be converted to a timestamp.

Pluggable Database Converter

The Pluggable Database Converter is a custom-defined conversion specified by two blocks. There’s a dbToSt and a stToDb block. Both blocks take one argument, which is the value that needs to be converted.  We’ll illustrate this with a very simple example. Let’s assume we need to store a value in the db, and for some odd compatibility reason, we need this to be shown in uppercase since some stored procedures were made to process these as uppercase values.  In Smalltalk, we prefer to have the value in lowercase since our users find it better to have it on the GUI in lowercase. You would define a converter like this:

| obj |
obj := Glorp.PluggableDatabaseConverter new.
obj
              name: 'Lowercase';
              dbToStConverter: [ :aValue | aValue asUppercase];
              stToDbConverter: [ :aValue | aValue asLowerCase].
^obj.

These converters can be defined in your own classes and accessed there by the descriptor system, or you can define the converters directly in your descriptor system.

Using Converters

You notice that converters have names.  These are just for information; they’re not used as a key in some global dictionary. If you want to use a specific converter for your mapping, define it as follows:

(aDescriptor newMapping: DirectMapping)
              from: #isCorrect to: (table fieldNamed: 'IS_CORRECT');
              converter: (self platform converterNamed: #booleanToStringYesNo)

or

(aDescriptor newMapping: DirectMapping)
from: #description to: (table fieldNamed: ‘ART_DESCRIPTION);
converter: (PluggableDatabaseConverter new
name: ‘Lowercase’;
dbToStConverter: [:aValue |aValue asUppercase];
stToDbConverter: [:aValue |aValue asLowercase];
yourself).

Next to the converters we also have a mapping that can do its own specific conversions―AdHocMapping …  but that’s for a future article.