Ralf Junker <[EMAIL PROTECTED]> wrote:
> Is there more information available on saving and loading prepared statements 
> and omitting the parser from the library?
> 

OVERVIEW

The SQLite Stored Statement Extension consists of twelve
source code files that are added to a standard public-domain SQLite
release.  The added files are proprietary and are used under license.
But the bulk of the code is from SQLite which continues in the public
domain.

The Stored Statement Extension (hereafter called SSE) provides the
programmer with two new APIs:

   int sqlite3_serialize(sqlite3_stmt*, void**, int*);
   int sqlite3_deserialize(sqlite3*, void*, int, sqlite3_stmt**);

The first routine takes an SQL statement that was generated by
sqlite3_prepare() and converts it into a form that can be stored
on disk or compiled into a program.  The second routine does the
reverse; it takes the serialization of a statement and converts it
back into a working SQL statement that can be used just like any
other statement created by sqlite3_prepare().

In addition to the new APIs, the SSE comes with instructions and
example Makefiles that allow the developer to compile SQLite so
that the parser and code generator are omitted.  Such a build will
typically be much smaller than a standard SQLite build - less than
half size of a standard SQLite build.  The sqlite3_prepare()
API will be unavailable in a build of SQLite that omits the parser,
so the sqlite3_deserialize() API must be used instead.

The point of the SSE is to allow manufacturers of low-power
embedded devices to make use of the SQLite backend without having
to host the SQL parser and code generator on the device.  The
parser and code generator are on a developers desktop where
they generate serialized SQL statements which are then loaded
into the embedded device and executed by the backend.  This
provides the developer with the full power of a transactional
SQL database engine but with a much smaller code footprint.

MEMORY AND DISK USAGE

The size of the SQLite library without the parser component will be
between 65 and 90KiB depending on what features are included in the
build.  (Example:  The built-in date/time functions, if included, add
about 8KiB to the library footprint.)  An additional 50 to 100KiB of
stack and heap space are require to run common queries.  More heap
space may be required for complex queries.  There is often a trade-off
between memory utilization and performance;  the database will
typically run faster if more heap memory is available.

The size of a serialized statement is typically about 5 times the size
of the original SQL text, though this ratio can vary significantly
depending on the database schema.  For example, the serialization of
"SELECT * FROM table1" will be much larger if table1 contains 100
columns than if it contains only 1 column.  In one typical application,
the size of 81 serialized statements ranged from 25 to 1000 bytes
with an average size of 275 bytes.  We expect other applications to
show similar results.  These figures assume that the  binary encoding
for serialized statements is selected.  When an ASCII text encoding is
used, the sizes grow by about 25%.

LIMITATIONS

There are limitations to this approach.  Compiled statements in
SQLite depend heavily on details of how specific tables are arranged
in the database file.  Those details change whenever the database
schema changes (due to a CREATE or DROP or ALTER statement) or when
the VACUUM command is run.  Thus, statements that have been created
using sqlite3_serialize() will only work on a database that has
not been modified by CREATE, DROP, ALTER TABLE, or VACUUM since
the statement was created.  If new tables or indices are added
to a database or if the database is vacuumed, then all statements
must be recompiled.

The sqlite3_serialize() routine must be used on a freshly compiled
statement.  sqlite3_serialize() will not work after a statement
has been passed to sqlite3_step().

The exact same version of SQLite must be used to serialize and
deserialize statements.  It will not work to serialize statements
from one version of SQLite and then attempt to deserialize and
run them using a different version.

The sqlite3_deserialize() routine does little to no error checking
of its input.  If the input is malformed, the result could be a
serious software malfunction such as a segmentation fault, an
infinite loop, or database corruption.  Developers should take care
to verify that input to sqlite3_deserialize() is exactly the same
as the output from a prior sqlite3_serialize().

ADDITIONAL FEATURES

The sqlite3_serialize() routine can generate either a binary or
an ASCII text serialization of a statement.  This choice must be
made at compile-time.   The default is to use a binary encoding.
If the -DSQLITE_SSE_ASCII option is added to the compiler command-
line then an ASCII encoding is used.  The advantage of an ASCII text
serialization is that it is easier to insert into a compiled program
The encoding is just a string that can be enclosed in "...".  The
disadvantage is that the ASCII text serialization is larger (by 
about 25%) than the equivalent binary serialization.

When SQLite with the SSE is compiled on a desktop (when it has a
parser) a special pragma is provided that allows the user to create
a table as follows:

   CREATE TABLE sqlite_statement(
      id INTEGER PRIMARY KEY,
      sql TEXT,
      serial BLOB
   );

A pragma is required to create this table because SQLite normally
reserves the sqlite_* table namespace for itself.  The special
pragma is as follows:

   PRAGMA create_sqlite_statement_table;

A new API is available that will automatically extract and deserialize
an SQL statement from the sqlite_statement table given its id number:

   int sqlite3_fetch_statement(sqlite3*, int id, sqlite3_stmt**);

The idea here is that the SQL statements needed by an application can
be inserted as plain text into the sqlite_statement table.   For
example:

   INSERT INTO sqlite_statement(sql) VALUES('SELECT * FROM table1');

After many such statements are inserted, they can all be serialized
as follows:

   UPDATE sqlite_statement SET serial = sqlite_serialize(sql,id);

Then the complete database can be moved from the development platform
over to the embedded device and the embedded device can use the
sqlite3_fetch_statement() API to extract the statements it needs to
execute.

The original design was to have the VACUUM command automatically run
the above UPDATE on the sqlite_statement table.  But that design was
changed when we realized that manually running the UPDATE provides
more flexibility.  For example, to reduce the size of the sqlite_statement
table, you might decide to delete the original SQL text.  To do so,
create a copy of the database that is to be transferred to the embedded
device (so that the original SQL is not deleted from your development
database) and use this command:

   UPDATE sqlite_statement SET serial=sqlite_serialize(sql), sql=NULL;

The software development process is simplified by using this approach
for storing serialized statements.  Because the serialized queries
are stored in the database itself, a database schema can be modified 
and then sent over to the embedded device and all of the queries will 
still work without having to modify and recompile the software on the 
embedded device.  Also, the administrative burden of tracking which
SQL statements correspond to which serializations is greatly reduced.
And, RAM usage might be reduced since serializations are stored on
disk until needed rather than being compiled into the program and
loading into RAM with the program.  (This last point assumes the
embedded device does not implement demand paging - a reasonable
assumption.)

It is believed that the sqlite_statement table will be very useful
to developers.  But its use is completely optional.  If omitted, there
is no library footprint penalty on the embedded side.

MEMORY ONLY DATABASES

For embedded devices that never lose power and do not require that
two or more processes have access to the same database, it is possible 
to store the complete database in memory.  If SQLite is compiled with
the -DSQLITE_OMIT_DISKIO command-line option, then the code used
for writing the database to disk (or flash) is omitted, thus reducing
the library footprint.  (Estimated savings: 9KiB depending on CPU
architecture.)  In addition, most database operations will be much
faster if information does not need to be moved in and out
of Flash memory or disk.  

When this option is used, a new API will be available that can be used
to initialize the in-memory database from a database image contained 
in either RAM or Flash or loaded over a socket.

   int sqlite3_raw_write(sqlite3*, int nByte, int iOffset, 
                         unsigned const char*);

After the database connection is opened using sqlite3_open() the content
of the in-memory database is initialized by one or more calls to the
sqlite3_raw_write() routine above.  Raw database bytes, such as might
be taken from the disk image of a database file prepared on a 
workstation, are added to the in-memory database image.  The second
parameter is the number of bytes of data to be added.  The third
parameter is the index of the first byte to be written.  The fourth
parameter is a pointer to the beginning of the data.  This routine
can be called multiple times if the original database image is not
contiguous. 

This routine makes a copy of the original database image in internal
data structures.  Memory to hold the copy is obtained from malloc().
After this routine has been used to initialize the internal data 
structures, the memory space used to hold the original database image
can be released and reused for other purposes, if desired.

The sqlite_raw_write() routine is efficient even if the nByte parameter
is small.  This allows the in-memory database to be initialized by
a data transfer over a network connection.  Each packet received over
the network can be moving into the database as it arrives at the
device, minimizing the need to buffer space.

AVAILABILITY

The SSE license is a one-time fee that allows unlimited redistribution
in compiled binary form when linked into the licensees embedded
application.  Full source code is provided.  The standard license
package includes 90 days of technical support.  Additional technical
support can be acquired separately.


Reply via email to