Getting to this stage has taken much longer than I first anticipated. Turns out that SQLite’s “liteness” was turning into a blocker and at one point I even thought we would abandon the project altogether. I should have known this though. The SQLite website says to “Think of SQLite not as a replacement for Oracle but as a replacement for fopen()” and provides a list of situations where other RDBMS work better. In developing this adapter, we were actually pitting SQLite up against the likes of Oracle, PostgreSQL, MySQL, SQL server and other enterprise SQL servers.
First to be tackled was SQLite’s implementation of SQL92 or rather lack thereof; granted though, the documentation for supported features/syntax is very good. We needed to come up with work-arounds to do things that are taken for granted when developing for an enterprise level RDBMSs. For example it didn’t support altering a column’s definition or table constraint, dropping a column or constraint and there is no procedures. Most of the work-arounds required renaming the old table, creating the new table and copying data from the old to the new and then finally deleting the old table.
There was also the case where it was required to convert columns containing hex string values into BLOBs. SQLite provides the
x'ABC123' syntax for this, but it is limited. It does not take columns as input. For this we developed an extension function
binary(). It pretty much does what the
x'' does only that it is exposed as a function call.
It was also required to provide a ranking of results for FTS queries. Again an extension function
rank() was developed. The rank for a returned row is calculated as:
number of local hits / sum of global hits. The source of the extension functions is can be found here sqliteNexjExtension.c.
Next up was selecting a JDBC driver. SQLite does not provide an offical JDBC driver but their wiki provides a list of available drivers. The decision was taken to go with Xerial SQLite JDBC Driver (a fork of SQLiteJDBC). We found this driver to be the most up-to-date and its easy of use of native SQLite libraries was a big plus. The driver did not provide implementations of
XAConnection. The team at NexJ Systems ended up providing us with implementations (nexj.core.persistence.sql.pseudoxa) that wraps
Connection and exposes them as
XAResource‘s. (But just last week, I came across SQLiteConnectionPoolDataSource which I provides XA implementations for the SQLite JDBC driver. We may later on switch to using this.)
We found the driver to be buggy with setting: an encoding on a
SQLiteDataSource and query time-out on a statement. Turns out that calling
SQLiteDataSource.setEncoding("UTF8") causes an exception. The driver tries to execute the statement,
pragma encoding = UTF-8. Note the hypen (-), SQLite expects that to be quoted. The javadoc on
Statement.setQueryTimeout(int seconds) reads “Sets the number of seconds the driver will wait for a
Statement object to execute to the given number of seconds“. Internal though, the driver calls
sqlite3_busy_timeout which “sets a busy handler that sleeps for a specified amount of time when a table is locked”. This ends up having the effect that if a database is locked when first executed, the statement will sleep for the given number of seconds then try a second time instead of trying several times until the given number of seconds have past. The solution for this was to use a TimerTask (
nexj.core.util.sql.StatementCancelationTask) that was already implemented.
Next week I will have a post about how the dreaded
SQLITE_BUSY were overcome so that SQLite could be used in an JavaEE application server. The source code for the adapter is shared on bitbucket.org/cwdesautels/nexj-express-sqlite-adapter in the ‘sqlite’ branch. Check out the
SQLite* classes in the package nexj.core.persistence.sql.