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.
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:
- Null converter
- Delegating Database converter
- 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.