Gluex

Elixir bindings for the GlueSQL Rust engine, independent of Ecto.

Current release: 0.1.1.

The Ecto adapter will live in a separate ecto_gluex package.

Installation

Add Gluex to mix.exs:

defp deps do
[{:gluex, "~> 0.1.1"}]
end

Usage

storage = Gluex.Storages.Memory.new()
database = Gluex.new(storage)
Gluex.query(database, "CREATE TABLE users (id INT, name TEXT)")
Gluex.query(database, "INSERT INTO users VALUES (1, 'Jan')")
Gluex.query(database, "SELECT * FROM users")
# [%{"type" => "SELECT", "columns" => ["id", "name"],
# "rows" => [[1, "Jan"]]}]

Values are converted directly to BEAM terms by the Rustler NIF. The binding does not use JSON or Jason at the Elixir API boundary.

Storage support

Storage Status Notes
Memory Supported Native GlueSQL storage.
JSON/JSONL Supported Persistent file storage.
ETS Supported Volatile BEAM storage through the batch NIF bridge.
Mnesia Supported Transactional BEAM storage through the batch NIF bridge.
Composite Supported Routes JSON tables to JSON and new tables to ETS or Mnesia.
CSV Not included in 0.1.1 Requires a dedicated Store implementation and write policy.

GlueSQL is storage-agnostic. Additional adapters can be implemented either as a native Rust Store or as a BEAM bridge using the same callback protocol as ETS and Mnesia. CSV import can also be handled by loading rows into ETS or Mnesia when a full SQL/write-capable backend is required.

JSON storage

database = Gluex.new(Gluex.Storages.Json.new("priv/data"))
Gluex.query(database, "CREATE TABLE users (id INT, name TEXT)")
Gluex.query(database, "INSERT INTO users VALUES (1, 'Jan')")

This creates priv/data/users.jsonl and, for schemaful tables, the table schema file as well.

ETS and Mnesia storage

ETS is volatile and local to the bridge process. Mnesia uses a configurable Mnesia table and defaults to :ram_copies:

ets_db = Gluex.new(Gluex.Storages.Ets.new())
mnesia_db = Gluex.new(Gluex.Storages.Mnesia.new(copy_type: :ram_copies))

Both adapters execute SQL through the same GlueSQL engine and support schema changes, joins, indexes, and explicit transactions.

ETS/Mnesia bridge benchmark

The repository includes a reproducible baseline for the cost of crossing from direct BEAM storage access into the current bridge process:

mix run bench/ets_mnesia_overhead.exs

It compares direct :ets.tab2list / :mnesia.match_object with the equivalent bridge operation over 10,000 rows. The row count can be changed with GLUEX_BENCH_ROWS=100000, and the number of timed repetitions with GLUEX_BENCH_RUNS=100. The benchmark warms up each path and reports the median, avoiding misleading one-shot cold-start results. The reported percentage is calculated as:

(bridge_time / direct_time - 1) * 100

This is a BEAM bridge baseline. SQL execution adds planning and result materialization on top of these bridge timings.

Both bridges can execute full SQL, including joins between tables in the same storage:

db = Gluex.new(Gluex.Storages.Ets.new())
Gluex.query(db, "CREATE TABLE users (id INT, name TEXT)")
Gluex.query(db, "CREATE TABLE orders (id INT, user_id INT, total INT)")
Gluex.query(db, "INSERT INTO users VALUES (1, 'Jan')")
Gluex.query(db, "INSERT INTO orders VALUES (10, 1, 50)")
Gluex.query(db, """
SELECT users.name, orders.total
FROM users JOIN orders ON users.id = orders.user_id
""")

Gluex.Storages.Composite routes existing JSON tables to JSON and newly created tables to ETS (or Mnesia). For Mnesia, pass backend: :mnesia. This allows a normal GlueSQL JOIN across both backends.

ETS and Mnesia also support CREATE INDEX and DROP INDEX. Indexed scans are currently implemented in the adapter and use the bridge-backed rows; a later optimization can move the index lookup itself into native ETS/Mnesia tables.

Both bridges support explicit GlueSQL transactions:

Gluex.query(db, "START TRANSACTION")
Gluex.query(db, "INSERT INTO orders VALUES (10, 1, 50)")
Gluex.query(db, "COMMIT") # or ROLLBACK