Single File Database Library

Introduction

ofdl is a library which lets you access a relational database stored in a single file. It provides an SQL-like language to access and query data. Actually the language is much more imperative and thus powerful than SQL. ofdl has an (almost) complete C expression parser and an interpreter for the generated byte code.

ofdl provides interfaces for both C and lua. Since the languages are different, the interfaces are also a little different. However, the general idea is the same.

Motivation

I currently am working on a couple of projects and they require functions you would find in a relational db. However, these programs need to be very transportable (i.e. users shouldn't be forced to run db software such as MySQL or Oracle). So, a single-file solution is the best.

Why not just use Berkeley DB library? As far I have read, the standard db library only provides a 'key' column and a 'data' column for the table and is not able to store more than one table in a file. Although it seems to go to great depths in optimizing the access and memory usage like a real db server does, it doesn't provide much functionality.

Another alternative is to embed a MySQL server into your application. I certainly would use it for a big project but I think that it's overkill for the job ofdl was designed for.

The Database File Format

A database file consists of instructions and their data. Each instruction is seperated by a single whitespace from it's arguments. The arguments are also seperated by one whitespace. There can be any amount of whitespace between two instructions. The instructions are :

General Concepts

An ofdl database is just a set of tables (relations) that reside wholly in memory. A pointer to the opaque database structure is given to the user when a database is read from a file. This pointer is similar to database connection handles found in most RDBMS APIs.

When you want to manipulate/query data in an ofdl db, you do so by using a query. In other SQL APIs, a query is usually a string that is constructed by the user which then gets sent to the server for execution. ofdl uses functional programming techniques to let the user express very powerful predicates and operations. This relieves the user from constructing query strings, and re-encoding of data.

All ofdl functions can operate on a single table at a time. Although ofdl does not support joins, you can emulate them by executing a new select query inside a where clause.

There are 3 steps involved in executing an ofdl query.

The C Interface

Compiling and Linking with ofdl

All ofdl functions and macros are defined in <db/db.h> . If you want errors to terminate your application with a SIGSEGV, then don't

  #include <db/error.h>
  ..
  main() {
  	...
  	errstream= memsopen();
  	...
  }
in your main file. I personally use it without initializing the error stream and catch errors as soon as they occur.

To link with ofdl, just adding libdb.a to your objects should be enough. ofdl doesn't depend on anything else.

Queries

Queries may be composed of three different elements: string literals that generally contain the code to execute, C values that contain the values to be passed to ofdl, and addresses of variables in which you expect values to be returned. The latter two have to be rewritten in special forms which allows ofdl functions to identify them. Since ofdl is only a library and not a preprocessor system, these conventions must be followed.

A query is a list of elements mentioned above seperated by commas. This structure makes it possible to define ofdl functions that take a query argument as variadic functions and to make the C compiler pass the values to the functions, as opposed to translating the query to another form before the C compiler gets its hands on the code. Here are the rules for writing a valid query:

Expressions in a query can also contain function calls. A function called from ofdl can take any number and type of arguments but each argument must be 32-bits long when promoted for function call. When used as a boolean expression, a function must return an integer value. If an arithmetic operator is to be applied to the return value, then it should return a value matching the other operands of the operator. Example usage of a function call:

int isgood(char *name,int salary) {
	if ..some_condition..
		return 1;
	else
		return 0;
}
char *strtoupper(char *txt, allocator *context) {
	char *r = context_alloc(strlen(txt)+1,context);
	..uppercase txt into r..
	return r;
}

...
db_select(db,ca(upname),"=",cv(strtoupper), "(name,",cv(context),")",
              "from employee where", cv(isgood),"( name, salary );");
...

All this probably looks like gibberish, but it's the easiest way I could think of, without resorting to a preprocessor.

Data Types

Following variable types are used with ofdl functions.
Data Type Description
DB Handle for an ofdl database. Variables of this type are always used by value, since the type is a pointer.
DBCURSOR Handle for an ofdl operation. Again, this is a pointer type.
char * The usual C strings are used for communicating string values between ofdl and your program. You should not store a string returned by ofdl since it may be deallocated by ofdl functions later.
int C ints are used for communicating integers. You must always use the correct type for assigning to/from db fields. So, passing a string value when an integer is required or vice-versa will mess things up.
FSTREAM There is only one variable with this type. See error handling below.

Functions

Error Handling

There are 5 classes of errors that can occur while using ofdl:

When an error is caught, the reason for it is written to the global variable FSTREAM *errstream, using the functions in io.h. Also, the called ofdl function will return a zero. This will change in the future to return a negative numeric constant identifying the error.

As it can be seen from the list above, almost all errors can be checked for at compile time, were a preprocessor/checker was implemented for ofdl functions. This may be done in future, depending on how many programs use ofdl and how badly it is needed.

The Lua Interface

DB handles and DBCURSOR's are represented as strings inside lua. These strings should not be used for anything else. The proper way to do it would be to use userdata types but this works and is implemented easily. The lua interface is not pure. It is designed for an application that embeds lua. So you will have to do a little preparation before you can use ofdl from lua.

Compiling and Linking

Before you can use ofdl functions, you need to let ofdl register it's functions with the lua interpreter. The function to do this is:
void db_register_lua_functions(lua_State *l);
which is defined in <db/dblua.h> .

You will also need to link with dblua.o in addition to libdb.a

Queries

Lua predicates and operations are just lambda functions. Unlike C counterparts, ofdl functions for lua always take a fixed number of arguments. ofdl does no parsing on these arguments.

For a function used as a where clause, the prototype is:

function (R)
The argument is a record from the table being filtered. Each field in the record is stored inside the luatable R as key-value pairs. The return value is non-nil if this record should be included in the set, nil otherwise. Note that where clauses are not optional, you should use
function (R) return 1 end
when you want all records to be operated on.

Lua Functions

In all of the function declarations below, db is a string holding the database handle. tablename is the name of a table inside the database. This name should always be valid. It's likely that you will get a SIGSEGV if not so. whereclauses are predicates as explained in the
queries section.

Bugs and Limitations

Well, there is almost no error handling. See the associated section.

This library should be used only with small data sets, which fit in the memory because it's the way it works. Files are just for storing the stuff for reading later as opposed to being used as a medium from which to access data directly. If you are dealing with so much data that ofdl can't handle well, you are better off using a real db server anyway. ofdl was written in a couple of days and will never reach the maturity of a real server.

An interface for creating and modifying databases should be created. ofdl misses functions to create a db and create/modify/destroy tables in it.

Currently there is no way to include a null character inside a variable-length text field. This prohibits storing binary data (BLOBs) inside these fields.

Download and Installation

You can download the latest version from my homepage. You will also need tlib. You need to modify the Makefile in order to build it. Needless to say, you need the lua library if you want to use the lua interface.

Included are two test programs for the two interfaces.

Contact

If you use ofdl and have questions, you can contact me from
  cinarus at yahoo dot com
Hosted by www.Geocities.ws

1