Litestore library is a lightweight, embeddable, zero config, key-value style storage system build on SQLite3 database (http://www.sqlite.org/).
Litestore is quite low level. It can be used as is but is intended to be built upon. More about the design in section 'Implementation details'.
It's not a full document database or key-value store server system. Little like comparing SQLite to Mysql or PotgreSQL (actually SQLite compares better than Litestore to CouchDB or MongoDB...).
- If you just want things to work and don't really care about SQL or playing with files.
- You want to save multiple values in a consistent transactional way that regular files just can't do.
- Full blown document databases like, CouchDB, MongoDB or Redis are just too huge and have too much dependencies.
MIT See LICENSE.txt
SQLite is released in the public domain, see: http://www.sqlite.org/copyright.html This library does not modify SQLite, only links against it.
Please inform me if you use the library ([email protected])!
mkdir build cd build cmake ../ make litestore [make unit_tests && ./tests/unit_tests]
The library:
-
C99 compiler (compile)
Should be adaptable to older standard.
Tested with gcc (7.2 64bit, Linux and 6.x 64bit in TravisCI)
-
CMake supported build system or your own build system (compile)
For tests:
- C++14 compiler
NOTE: sqlite3 and Google test are statically linked and part of this repo. Theres no reason why one couldn't link against shared sqlite3 if one chooses to.
Semantic versioning 2.0: http://semver.org/spec/v2.0.0.html See file VERSION, for current version.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "litestore/litestore.h"
/* Callback for raw data, that allocates a new string. */
static
int alloc_str(litestore_blob_t value, void* user_data)
{
char** res = (char**)user_data;
*res = strndup((const char*)value.data, value.size);
return LITESTORE_OK;
}
/* Callback for internal errors. */
static
void err(const int error_code, const char* error_str, void* user_data)
{
printf("ERR: (%d): %s\n", error_code, error_str);
}
int main(const int argc, const char** argv)
{
if (argc < 2)
{
printf("Must provide DB file name!\n");
return 0;
}
else
{
printf("Opening DB to '%s'.\n", argv[1]);
}
/* Open the connection */
litestore* ls = NULL;
litestore_opts opts = {&err, NULL};
if (litestore_open(argv[1], opts, &ls) != LITESTORE_OK)
{
litestore_close(ls);
printf("ERROR 1!\n");
return -1;
}
/* Create raw data */
const char* key = "Hello";
const char* value = "World!";
if (litestore_create_raw(ls,
litestore_slice_str(key),
litestore_make_blob(value, strlen(value)))
!= LITESTORE_OK)
{
litestore_close(ls);
printf("ERROR 2!\n");
return -1;
}
/* Read the created data, note callback usage */
char* res = NULL;
if (litestore_read_raw(ls,
litestore_slice_str(key),
&alloc_str, &res)
== LITESTORE_OK)
{
printf("%s %s\n", key, res);
free(res);
}
else
{
printf("ERROR 3!\n");
}
/* Clean up, close the connection */
litestore_close(ls);
return 0;
}
Litestore can create objects. Each object has a key and a value. The value can have different types. The key is a user defined UTF-8 encoded string. It can have arbitrary length, though this naturally affects performance since objects are accessed based on the key.
Litestore can create two (2) different types of objects. These are:
- null
- raw
Simplest is the null type. Basically it can be used as boolean type that either exists or it doesn't. It has no real value and only the key is stored.
The raw type is just a binary blob and is saved as is. It can be used for any user defined type that can be saved as bytes, either directly or by serializing. Format is totally user dependent.
The API aims to be concise. All functions return values indicate the success status of the function and possible output parameters are given as the right most function parameters. All API functions and types are prefixed with litestore_. The implementation is not leaked to the user.
The only memory Litestore library itself allocates, is during open, for the litestore context object. The underlying implementation (SQLite3) does allocations on it's own and these can't be avoided. How ever all the data is passed as directly as possible and possible allocations are left for the user. This enables the usage of custom allocators.
This is also the reasoning behind the callback style API, that admittedly is more cumbersome to use.
On some systems the length of a string is know after constuction (C++ for instance). This is the reason for using the slice_t type for strings. SQLite needs to know the length of the string (or blob) and it is more efficient if explicit strlen() calls can be avoided.
Litestore can be used with explicit transactions or implicit transactions. Explicit transactions mean that the user calls the _tx functions explicitely and creates an explicit transaction scope.
If no explicit transaction is created, one will be created on each API call. And commited if the call succeeds.
For better performance, always use explicit transactions to group API calls.
The API is not thread safe. I.E. using the same connection/context in multiple threads is not safe. How ever all the state is stored in the context so it should be safe to create multiple connections, one for each thread, to the same DB. SQLite it self is thread safe, if configured properly. See: http://www.sqlite.org/threadsafe.html