ParaQL is a mad-science experiment combining libSQL (an open-contribution SQLite fork with native vector support) with Autobase (or to be more precise, it's next-gen iteration Autobee) for massively-parallel multi-writer access.
It logs every write in a per-instance append-only log (oplog) and applies them in deterministic order to a shared view (the database) ensuring no corruption can occur. Performance is impressive, it blows vanilla SQLite out of the water, in part due to flushing to disk less often. That's not an issue though, because by the time database write occurs the operation is already in the oplog, so in case crashes or corruption it can simply be reapplied. Peers who haven't written to the database simply fast-forward (download) the latest database version which on a good connection is even faster.
The oplog based design means that disk space used is roughly double (after compaction) that of vanilla SQLite. Operating requirements are considerably higher, because Autobee uses RocksDB as the storage backend and RocksDB doesn't free deleted data immediately as a performance optimization. Periodic compaction, both automatic, and manual, makes this an easily solvable issue.
For a better idea of how ParaQL performs and compares to other solutions see the benchmark.
ParaQL is developed on Bare but it's also tested on Node. Because Bare is multi-platform and supports mobile operating systems as first class citizens, ParaQL runs on recent versions of Android, iOS, macOS, Linux, and Windows.
ParaQL supports encrypting the database with a 256-bit key, both on disk and in transport (meaning remote peers need to know the key to read or write into the database). The local-only temporary files are currently encrypted with unique but not random nonces. This is something we're still working on.
ParaQL has native support for vector data types and vector search functions with optional indexing. This is courtesy of libSQL and one of the primary reasons ParaQL was made: to support vector similarity search in P2P context.
npm i @bullet./paraqlconst Corestore = require("corestore")
const ParaQL = require("@bullet./paraql")
const store = new Corestore("./paraql")
const db = new ParaQL(store)
await db.exec(`
CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO people (name) VALUES ('Alice'), ('Bob');
`)
const select = await db.prepare("SELECT id, name FROM people ORDER BY id")
for await (const row of select.iterate()) {
console.log(row)
}
await select.finalize()
await db.close()Prints:
{ id: 1, name: "Alice" }
{ id: 2, name: "Bob" }Creates a new database or an instance of an existing database if key is provided. store is an instance of Corestore used for storage. key is either null or 32-byte key of the database.
options include:
options = {
name: "paraql.db",
authorize: null,
keyPair: null,
encrypted: false,
encryptionKey: null,
}These options should only be used at database creation and set for every instance of the database. Option mismatch may lead to database corruption and/or sync issues.
name is the name of the database file, also used as a prefix for temporary files.
authorize is either null or an (asynchronous) authorizer callback with the following signature:
;(
key: Buffer,
action: ParaQL.AUTHORIZE_ACTION,
param1: string | null,
param2: string | null,
param3: string | null,
param4: string | null,
) => Promise<boolean>where key is the local key of the writer attempting the operation, action is numeric action code defined in ParaQL.AUTHORIZE_ACTION enum, and params 1 through 4 are action specific strings (e.g. table name) or null.
Do note that operations initiated by initial instance (root node) and all read-only operations are always allowed and authorizer callback is NOT invoked for them.
keyPair, if provided, is the signing key pair for the local writer in the form { publicKey: <32-byte Buffer>, secretKey: <32-byte Buffer> } .
If encrypted is true and encryptionKey is provided as 32-byte buffer, it is used to encrypt the database.
The name of the database file.
The database key used for replication. null before db.ready() is called.
The key of the local writer. Pass this to db.addWriter() to grant write access. null before db.ready() is called.
The discovery key of the database, can be used e.g. as the topic for replicating over Hyperswarm. null before db.ready() is called.
The key used to encrypt the database or null. null before db.ready() is called.
Whether this instance has write access to the database. false if db.addWriter() hasn't been called with this instances local key or if db.ready() hasn't been called.
Whether this database is encrypted.
Initialized the database. All methods call this implicitly, so unless you need to access some instance property early, there's no need to call this yourself.
Closes the database and cleans up all used resources. Does not close the Corestore instance.
Grant another instance of the database write access. key should be the local key of the remote instance. db needs to be writable.
Revoke write access from another instance of the database. key should be the local key of the remote instance. db needs to be writable.
Creates a replication stream that can be piped over any streamable transport. isInitiatorOrStream can be a boolean indicating whether this instance initiated Noise handshake, or another replication stream.
Compacts database and removes stale data. This operation is local only and can reduce disk space usage by up to 30x or more depending on the data stored and settings of the database. Compaction happens automatically but you might want to run this periodically, when idle.
Get information about disk space usage. Returned object has all properties in bytes and looks like this:
{
database: number,
temporary: number,
total: number,
}Execute given SQL statement(s) without checking return values.
This is a convenience method.
Prepare SQL statement stmt from the first statement in sql. If sql contains more than one statement tailing statements are discarded.
The SQL string used to initialize this prepared statement.
Finalize a statement freeing up resources used.
Execute a statement with given positional params. First param may optionally be an object containing named params.
Returns an array of row objects keyed by column name.
Same as stmt.all() except it only returns the first row.
Execute a statement with given params and return an object in the form { changes: number, lastInsertRowid: number }.
Execute a statement with given params and return rows one by one.
Return an unencrypted, uncompressed, serialized version of the database that can be (re-)used with any SQLite3 compatible application or library.
Initiate a new ParaQL instance with any SQLite3 compatible database contained in the buffer.
Apache-2.0