PHP Driver

High-performance PHP driver for Stoolap. Built as a native PHP extension (C) for minimal overhead.

Installation

Prebuilt Binaries

Download a package matching your PHP version and platform from GitHub Releases. Each archive contains both the PHP extension and the stoolap library.

tar xzf stoolap-v0.3.4-php8.4-linux-x86_64.tar.gz
cd stoolap-v0.3.4-php8.4-linux-x86_64

# Install the shared library
sudo cp libstoolap.so /usr/local/lib/
sudo ldconfig  # Linux only

# Install the PHP extension
sudo cp stoolap.so $(php-config --extension-dir)/

Using PIE

Requires the stoolap shared library (libstoolap.so / libstoolap.dylib) installed on your system.

pie install stoolap/stoolap-php

From Source

Requires a C compiler and the stoolap shared library.

cd ext
phpize
./configure --with-stoolap=/path/to/libstoolap
make
sudo make install

Enable the Extension

; php.ini or conf.d/stoolap.ini
extension=stoolap

Or load it per-invocation:

php -d extension=stoolap.so your_script.php

Quick Start

use Stoolap\Database;

$db = Database::open(':memory:');

$db->exec('
    CREATE TABLE users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT
    )
');

// Insert with positional parameters ($1, $2, ...)
$db->execute(
    'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
    [1, 'Alice', 'alice@example.com']
);

// Insert with named parameters (:key)
$db->execute(
    'INSERT INTO users (id, name, email) VALUES (:id, :name, :email)',
    ['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com']
);

// Query rows as associative arrays
$users = $db->query('SELECT * FROM users ORDER BY id');
// [['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'], ...]

// Query single row
$user = $db->queryOne('SELECT * FROM users WHERE id = $1', [1]);
// ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']

// Query in raw columnar format (faster, no per-row key creation)
$raw = $db->queryRaw('SELECT id, name FROM users ORDER BY id');
// ['columns' => ['id', 'name'], 'rows' => [[1, 'Alice'], [2, 'Bob']]]

$db->close();

Opening a Database

// In-memory
$db = Database::open(':memory:');
$db = Database::open('');
$db = Database::openInMemory();

// File-based (data persists across restarts)
$db = Database::open('./mydata');
$db = Database::open('file:///absolute/path/to/db');

Database Methods

Method Returns Description
Database::open($dsn) Database Open a database
Database::openInMemory() Database Open an in-memory database
exec($sql) int Execute DDL/DML, returns affected rows
execute($sql, $params) int Execute parameterized DML, returns affected rows
executeBatch($sql, $paramsArray) int Execute with multiple param sets in a transaction
query($sql, $params?) array Query rows as associative arrays
queryOne($sql, $params?) ?array Query first row or null
queryRaw($sql, $params?) array Query in columnar format
prepare($sql) Statement Create a prepared statement
begin() Transaction Begin a read-write transaction
beginSnapshot() Transaction Begin a snapshot (read) transaction
clone() Database Clone the database handle
close() void Close the database
version() string Get stoolap engine version

queryOne() automatically appends LIMIT 1 when the SQL has no LIMIT clause. For prepared statements, add LIMIT 1 yourself since the SQL is fixed at prepare time.

Persistence

File-based databases persist data to disk using WAL (Write-Ahead Logging) and periodic snapshots. Data survives process restarts.

$db = Database::open('./mydata');

$db->exec('CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT)');
$db->execute('INSERT INTO kv VALUES ($1, $2)', ['hello', 'world']);
$db->close();

// Reopen: data is still there
$db2 = Database::open('./mydata');
$row = $db2->queryOne('SELECT * FROM kv WHERE key = $1', ['hello']);
// ['key' => 'hello', 'value' => 'world']
$db2->close();

Raw Query Format

queryRaw() returns ['columns' => [...], 'rows' => [[...], ...]] instead of an array of associative arrays. Faster when you don’t need named keys.

$raw = $db->queryRaw('SELECT id, name, email FROM users ORDER BY id');
// $raw['columns'] => ['id', 'name', 'email']
// $raw['rows']    => [[1, 'Alice', 'alice@example.com'], [2, 'Bob', 'bob@example.com']]

Batch Execution

Execute the same SQL with multiple parameter sets in a single atomic transaction. SQL is parsed once and reused for every row.

$db->executeBatch(
    'INSERT INTO users (id, name, email) VALUES ($1, $2, $3)',
    [
        [1, 'Alice', 'alice@example.com'],
        [2, 'Bob', 'bob@example.com'],
        [3, 'Charlie', 'charlie@example.com'],
    ]
);
// Returns total affected rows (3)

On error, all changes are rolled back (atomic). Also available on transactions via $tx->executeBatch().

Prepared Statements

Prepared statements parse SQL once and reuse the execution plan on every call. No parsing overhead per execution.

$insert = $db->prepare('INSERT INTO users VALUES ($1, $2, $3)');
$insert->execute([1, 'Alice', 'alice@example.com']);
$insert->execute([2, 'Bob', 'bob@example.com']);

$lookup = $db->prepare('SELECT * FROM users WHERE id = $1');
$user = $lookup->queryOne([1]);
// ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com']

Methods

Method Returns Description
execute($params?) int Execute DML, returns affected rows
query($params?) array Query rows as associative arrays
queryOne($params?) ?array Query single row or null
queryRaw($params?) array Query in columnar format
sql() string Get the SQL text
finalize() void Release the prepared statement

Statements are automatically finalized on garbage collection.

Transactions

$tx = $db->begin();
try {
    $tx->execute(
        'INSERT INTO users VALUES ($1, $2, $3)',
        [1, 'Alice', 'alice@example.com']
    );
    $tx->execute(
        'INSERT INTO users VALUES ($1, $2, $3)',
        [2, 'Bob', 'bob@example.com']
    );

    // Read within the transaction (sees uncommitted changes)
    $rows = $tx->query('SELECT * FROM users');
    $one = $tx->queryOne('SELECT * FROM users WHERE id = $1', [1]);
    $raw = $tx->queryRaw('SELECT id, name FROM users');

    $tx->commit();
} catch (\Exception $e) {
    $tx->rollback();
    throw $e;
}

Transactions auto-rollback on garbage collection if not committed.

Batch in Transaction

$tx = $db->begin();
$tx->executeBatch(
    'INSERT INTO users VALUES ($1, $2, $3)',
    [
        [1, 'Alice', 'alice@example.com'],
        [2, 'Bob', 'bob@example.com'],
    ]
);
$tx->commit(); // or $tx->rollback() to undo

Transaction Methods

Method Returns Description
exec($sql) int Execute DDL/DML without params
execute($sql, $params) int Execute parameterized DML
executeBatch($sql, $paramsArray) int Execute with multiple param sets
query($sql, $params?) array Query rows as associative arrays
queryOne($sql, $params?) ?array Query single row or null
queryRaw($sql, $params?) array Query in columnar format
commit() void Commit the transaction
rollback() void Rollback the transaction

Parameters

Both positional and named parameters are supported across all methods:

// Positional ($1, $2, ...)
$db->query('SELECT * FROM users WHERE id = $1 AND name = $2', [1, 'Alice']);

// Named (:key)
$db->query(
    'SELECT * FROM users WHERE id = :id AND name = :name',
    ['id' => 1, 'name' => 'Alice']
);

Error Handling

All methods throw Stoolap\StoolapException (extends \RuntimeException) on errors:

use Stoolap\StoolapException;

try {
    $db->execute('INSERT INTO users VALUES ($1, $2)', [1, null]); // NOT NULL violation
} catch (StoolapException $e) {
    echo $e->getMessage();
}

Type Mapping

PHP Stoolap Notes
int INTEGER  
float FLOAT  
string TEXT  
bool BOOLEAN  
null NULL  
array / object JSON Auto-encoded/decoded
DateTimeInterface TIMESTAMP Converted to nanoseconds

Building from Source

Requires:

  • PHP >= 8.1 with development headers (php-dev / php-devel)
  • C compiler (gcc, clang, or MSVC)
  • The stoolap shared library (libstoolap.dylib / libstoolap.so / stoolap.dll), either from Stoolap releases or built from source
git clone https://github.com/stoolap/stoolap-php.git
cd stoolap-php/ext
phpize
./configure --with-stoolap=/path/to/libstoolap
make