uSQLite
An SQLite network wrapper
What is it?
uSQLite is a network wrapper for SQLite. It turns SQLite into an RDBMS
but puts the emphasis on the 'Lite'. In fact it works in a somewhat
unconventional mmanner in order to make both servers and clients as
light,
portable and simple as possible. Readers who are not familiar with
SQLite are advised to visit www.sqlite.org.
uSQLite uses the TechFell protocol
for communications between clients and servers. This protocol was
originally designed for allowing embedded systems to directly access a
database, but has proved efficient in more general applications and in
conjunction with a suitable server (such as uSQLite server) may also be
used for inter-process and inter systems communications.
In automation systems it may be used to provide both database and
communications in a single package, and in many cases can eliminate the
need for communications middleware.
In a more general scenario it has the advantage that custom client
software may be built into the applications (it is very easy to write
and customize drivers) such that applications may be deployed without
the need for a separate database / database driver layer to install
and/or configure, in essence a distributed embedded database.
Features
- Server supports concurrent users on a single mono threaded
database connection, all queries are executed in strict sequential
order.
- Server can be compiled onto anything that has libc and Berkleyish
sockets (including winsock2). No threading or filesystem locking is
required.
- Server startup parameters put limits on server resource usage.
Client queries can be clamped.
- Uses a simple but effective built-in access control system that
assigns access levels to both users and networks (or individual IP's).
- Client access has a "Human" mode which allows telenetting into
the server (primarily for commissioning and trouble shooting).
- Clients are very simple, users can easily roll their own bindings
into just about anything programmable with a TCP/IP stack.
- Users may share (actually must share)
temporary tables and memory based tables (useful for IPC).
- No dependency on external mechanisms.
Drawbacks
- You may not use transactions (or more correctly, transactions
must BEGIN and END in the same query or group of queries sent as a
single batch)
- Poor latency when another user is running a very big query
(although clients may be forced to limit their queries, which IMHO is
a good thing in any database scenario).
So is it a fork of SQLite or a new version?
Niether. It is a wrapper, an add-on adapter, in fact nearly all the
functionality comes from a standard SQLite library. The program makes a
minimalistic use of the SQLite C API in the hope that users can
link it against their desired SQLite library. Alternatively both SQLite
and the uSQLite wrapper code may be linked into an application.
Why do I need this wrapper?
You probably don't. SQLite is an embedded database. Embedded in this
sense has nothing to do with microcontrollers, it means that database
functionality is built into the program such that it can directly
access it's database files. If you have one program accessing a local
database file then you probably do not need this (but see next
question!).
When multiple programs need to access a database file, it is necessary
to "lock" the file while it is being modified. Major platforms such as
Windows and Macs have no problems here, but obscure systems may not be
able to do this. Using a uSQLite server ensures that this is not a
problem, but you will need to run the server as an additional process.
When sharing a file across a network locking problems are much more
commonplace (see SQLite docs for what is known to work or not). If
clients and servers are all on major platforms (*nix boxes, PC's
windows, Mac etc.) you should perhaps be looking at a "real" RDBMS
server like MySQL, Oracle or PostGres. If however you require the
minimalistic requirements of SQLite then this wrapper is for you!
An alternative method of remote access to SQLite is to telnet into the
sqlite console program that is supplied in the main SQLite distro. This
works great but has two drawbacks. Firstly the interface is designed
for human use, and it is not necessarily easy to implement in a
program. The second drawback is that networking is not built in, you
will require inetd or an equivalent program on non *nix platforms and
you will rely on external access control. Also, an inetd solution
requires a process for each client connection, which may be a problem
on some limited systems (uSQLite allows you to limit the resources used
by each client connection).
Why (else) might I need this wrapper?
SQLite has ready made bindings (libraries) that allow it to be used
from most common languages. However, if your development tools are not
supported, then you will need to implement your own. In many cases
(particularly where it is not easy to make calls to external C
libraries) you will probably find it easier to implement the TechFell protocol
What platform does it run on?
uSQLite can be compiled anywhere that SQLite can (which is most
places), except that it also requires a TCP/IP stack. Berkeley sockets
should run out of the box, but once again a minimalistic use of
function calls has been made such that it is
easy to adapt it to compile "not quite Berkeley" sockets. Winsock2 is
already supported.
Client requirements are even simpler. All you need are streams to a
TCP/IP network and the ability to manipulate and parse strings.
Providing the sockets/streams are available it would be quite feasible
to make a client using the macro/scripting language of many
applications.
What's the difference between using uSQLite and SQlite proper?
Well obviously the interface is different! Also, you only get to use
the Execute function, however SQLite's #PRAGMA commands allow complete
control of the database. uSQLite adds a :PPRAGMA (pre-pragma) command
which is used for uSQLite specific functionality such as access
control, these commands are looped back directly. All connections to
uSQLite use a single SQLite connection, so you cannot use transactions
unless they BEGIN and END in the same query or group of queries sent as
a
single batch. In addition, temporary tables and memory based tables
will appear to all users, this must be born in mind when naming
temorary tables......some sort of prefixing convention may not be a bad
idea.
Another limitation is the data that can be sent/received from the
server. SQLite nominally uses ASCII strings but will accept most
control characters. uSQLite on the other hand will not handle
newline characters, they are used as sync characters and as it does not
scan or elaborate the internals of queries it cannot tell if they are
quoted. The quick solution would be to escape them, for example C style
'\r''s or to uuencode a field that may contain newline characters much
as one would do with binary blobs. The primary reason for using newline
characters do delimit packets (as apposed to say a 'Null') is that is
makes the interface usable from environments that deal strictly with
strings, which includes quite a lot of 'scriptable' environments.
In the future there will be a 'NULL' mode (Try :PPRAGMA NULL to see if
you already have it!), this mode will use a NULL instead of a newline
on both input and output, so there is no newline problem but it is
incompatible with telnet and strictly text environments.
Do ready made clients/libraries exist?
uSQLite is distributed with a VB client and a C client. The VB client
is a very poor example, but it works. It implements a simple form which
may be used to login to a database, execute queries, and review result
sets. The form is invisible by default when loaded, and has some
additional functions which allow it to be used as an interface library
(the form become a db connection class!).
The Cclient is a portable C library which roughly mimics the SQLite
exec command and callback function. Allthougth not a drop in
replacement for the SQLite functions, it is easy to incorporate into
existing programs which use the exec command. This library is used in
an example application which consists of a command line utility which
you launch with the query as an argument and get a result returned as a
CSV type file. The connection parameters should be placed in
environment variables, launch Cclient with no parameters to get a list
of the environment variables required and the current values. See
main.c for more info. The uSQLcClient.h file contains more info on
using the library. Result go to stdout and messages to stderr, so you
can pipe stdout to get a file.
There is also a CGI script written using the same C client library
which allows queries to be made over HTTP.
And the License?
SQLite, which is what provides uSQLite with the bulk of it's
functionality, has no license or copyright, just the following message:
**
The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good
and not evil.
** May you find
forgiveness for yourself and forgive others.
** May you share
freely, never taking more than you give.
**
This concept may be extended to uSQLiteServer and the supplied example
clients, thus allowing me to comply with the 3rd article of the SQLite
blessing. The TechFell protocol
which uSQLite uses to communicate between database clients and servers
is in the public domain, there is nothing to stop you rolling your own
wrappers and clients. Both the uSQLiteServer and the client software
are based on commonplace techniques and pratices that have been in use
for at least 20 years, indeed in technology terms it represents a 'step
backwards towards simplicity', and I sincerely hope that is not a
patentable concept ;-)
BTW, at there time of writing there is no server source, which might
sound ironic as the binaries exist. Truth is that the uSQLiteServer was
incubated in an application which already had the TechFell protocol,
but was hacked about with #ifdefs and all to get a stand alone server.
My next task is to try and clean this up into a standalone app/library.
This will be written in C and be as portable as possible (coding is
similar to the Cclient example). The aim of the first server source
release will be to have a clean and simple codebase which may be
compiled on as many platforms as possible, I will not be adding
functionality or changing the interface.
Credits
Apart from the obvious credit to SQLite, the following resources where
used in the development of uSQLite:
- The GNU toolchain and glibc
- The MinGW port of GNU for the windows version
- CodeBlocks IDE for both Windows and Linux
- The SHA hash used for storing passwords was written by Oliver
Gay, see the header file for a full manifest.
- Thanks to Massimo Benella who wrote and rewrote client software
which tested the server during it's evolution.
Quickstart guide - The server
I would suggest that you do not try to modify the configurations or the
contents of the db3 files until you have tried the examples, or they
may not work!
Connections from localhost and 192.168.x.x will work out of the box,
see security to configure for other networks.
The instructions are based on Windows, Linux is pretty much identical
(use telnet for a text terminal, remember to configure for a linefeed
after CR).
Linux executables have the same names as the windows ones, but without
the .exe extension.
- Unpack the package in an empty directory, the application is
staticly linked so their are no pre-requisites, setups or registrations
to do.
- From a command shell in the root directory launch
uSQLiteServer.exe uSQL.ini (Linux version has no .exe extension!), you
should get 3 messages to tell you that the service is starting, the sec
db as been opened, and the db has been opened.
- Launch a text terminal to connect to the server (a hyperterminal
config may be found in db.ht)
- Log in and run queries, in the following session sample user
supplied input is in bold and
the response from the server is in italics.
(You will need to be familiar with SQL and the TechFell
protocol to understand what is going on:).
uSQLite
0.2
:OK
:PPRAGMA USER Level5
:PPRAGMA USER Level5
:OK
:PPRAGMA PASS newp2
:PPRAGMA USELEVEL 4
:OK
:PPRAGMA HUMAN
:PPRAGMA HUMAN MODE
:OK
select * from tst
====( 1 )====
foo = 10
baa = NULL
====( 2 )====
foo = 65
baa = This is a text field
====( 3 )====
foo = 20
baa = ::This field will be formalized
in machine mode
:OK
:PPRAGMA MACHINE
:PPRAGMA MACHINE MODE
:OK
select * from tst
:H1:3 foo
:H2:3 baa
:R
10
!
65
This is a text field
20
:F47 ::This field will be formalized
in machine mode
:OK
Security
You should read the section on Access control in the TechFell protocol first. The
uSQLiteServer implementation uses a 'sec.db3' file. This table contains
2 tables. The networks table maps networks to maximum users levels
using IP address and masks, the second table maps users to maximum user
levels and stores a hash of the password. User levels are actually bit
fields, and the netlevel will be AND'ed with the user level to get the
effective level. Open it up and have a look
(you will need a general purpose sqlite client here). It is pretty
obvious.
As passwords are hashed you cannot set them using a normal sqlite
client program, you must use the :PPRAGMA NEWPASS command via
uSQLiteServer. New users should be added with blank passwords, to login
the :PPRAGMA PASS command should be used with 1 space character after
the PASS. You may also clear the password field to set a new password.
Note that using uSQLite means that the sec.db3 file (as well as the
main db file) need only be visible to the server application, you do
not need to export them on a network filesystem or make them accessible
to any user other than the server process.
Configuration - the uSQL.ini file
The uSQL.ini file contains configuration parameters (yes, they will be
in sqlite format in the future!). This a simple list of parameters, 1
per line (anything after the parameter id considered a comment and
ignored.
The parameters must be given in the order shown. The uSQL.ini file
supplied looks like this:
prime.db3 Main database
sec.db3 Sec database
3002 Service portnumber
10 Maxnumber of client connections
100000 Max size of query reply
buffer (maxbuffer)
10000 Maximum size of records in
reply (MaxRetRec)
4096 Maximum length of a query
(MaxQlength) - To the server
10000 Maximum records in a reply
(MaxRepRecs)
200 Init stored query start (o for
none)
202 Init stored query end (=start
for one query)
Most of the parameters are pretty obvious. Memory is dynamically
allocated so there is no penalty for oversizing. The last 2 parameters
are special. The database may contain a table called uSQL_P (there is
an example in the supplied prime.db3). This table contains a list of
SQL queries and an Index number. It is possible to execute a sequence
of queries at start up in order to configure the database (why? see the
section IPC Example). At startup, uSQLiteServer will execute "Select
Query from uSQL_P where Idx between <start> and <end> order
by Idx". It will then execute the queries in sequential order.
BTW, there is an unofficial :PPRAGMA EXEC <start> <end>
command that does much the same.
IPC Example
A database is a convenient mechanism for inter process and inter system
communications. Convenient, but not efficient, as data value changes
get written back to the disk. This can be avoided by using memory based
tables. In sqlite, memory based tables are only visible to the db
connection which created them, however uSQLiteServer uses a single db
connection for all clients, hence these tables will be visible to all
uSQL clients, but not to other programs which open the db file.
Typically, it would be nice to have some disk based tables, and a few
memory tables for IPC in the same database. This can be achieved by
creating a memory table and using the attach command to join it to the
main database schema. uSQLiteServer has an 'Init stored query' facility
(see the Configuration section) that can do this set up automatically
every time the server is started.
The supplied example configuration and database is configured to
automatically generate a memory based table called tagvals based on
information in the normal table tagdefs by automatically executing the
following queries from uSQL_P:
Attach ':memory:' as mem
Create Table mem.Tagvals(Idx Integer
Primary Key,Val Not Null,Err Not Null Default
-1,Updated,WriteVal,WriteErr);
insert into Tagvals(Idx,Val) Select
Idx,InitVal from TagDefs
Remember
that one of the advantages of using SQL for IPC is that "polling" data
may be selective. For example. if you need to poll the state of some
alarms you need only poll the state of alarms which are active.
Likewise if data is updated with a timestamps it is possible to query
data which has changed since the time of the most recent update the
client has received. Using selective queries not only allows a drastic reduction in the quantity
of data which flies around, it may also reduce the amount of code you
need to write.
Quickstart guide - The VB client example
Disclaimer. The VB example is not particularly good. If you VB experts
out there would like to fix it or send something better......
Launch the executable and hit the "Launch a client" button. A client
form will be loaded and made visible. Hit the connect button and the
client will try to connect using the default parameters given in the
text boxes at the top of the form. These should work with the supplied
db, and you should see the login results in the lowwer panel. Clear the
trash out of the upper pane and write a query like "select * from
tagdefs", then hit the Exec button. The lowwer pane should
automatically toggle to a grid when the result set arrives, this grid
is VERY slow, it is not the server or the protocol but my poor VB
skills!
You may launch as many client panes as you like, but note that the
uSQL.ini file will have defined a limit on the max number of client
connections.
The client form is invisible on creation, so you may create a form and
use it as a connection class. There are a few functions which enable
this. Hit the 'Updates' button and a new connection will be made which
updates the allarm states (the black boxes on the right) every 50mS. DO
NOT HIT THE Allarm BUTTONS YET! The 3rd button makes another connection
object to connect the Allarm buttons. After you have clicked this you
can toggle the allarm states by clicking the Allarm button. This does a
round trip, there is no direct connection between the buttons and the
allarm states, the button simply writes the 'opposite' of the Allarm
state to the ActVal column of the corresponding record in tagdefs, this
is then read by the other connection to update the display.