Packages

A compile-time safe database library for Gleam - Ecto-inspired schemas, composable queries, and adapter-based persistence

Current section

Files

Jump to
cquill src bench memory_adapter_bench.gleam
Raw

src/bench/memory_adapter_bench.gleam

// Memory Adapter Benchmarks
//
// Measures performance of the in-memory adapter operations.
// Includes insert, query, update, delete, and transaction operations.
import bench/bench
import cquill/adapter/memory
import cquill/schema
import cquill/schema/field
import gleam/dynamic
import gleam/int
import gleam/io
import gleam/list
import gleam/string
// ============================================================================
// TEST FIXTURES
// ============================================================================
/// Create a user schema for benchmarking
fn user_schema() -> schema.Schema {
schema.new("users")
|> schema.field(field.integer("id") |> field.primary_key)
|> schema.field(field.string("email") |> field.not_null)
|> schema.field(field.string("name"))
|> schema.field(field.boolean("active"))
|> schema.field(field.integer("age"))
|> schema.single_primary_key("id")
}
/// Create a store with the user table
fn create_store() -> memory.MemoryStore {
memory.new()
|> memory.create_table_from_schema(user_schema())
}
/// Create a store pre-populated with n users
fn create_populated_store(count: Int) -> memory.MemoryStore {
let store = create_store()
let rows =
list.range(1, count)
|> list.map(fn(i) {
let row = [
dynamic.int(i),
dynamic.string("user" <> int.to_string(i) <> "@example.com"),
dynamic.string("User " <> int.to_string(i)),
dynamic.bool(True),
dynamic.int(20 + { i % 50 }),
]
#(int.to_string(i), row)
})
case memory.insert_all(store, "users", rows) {
Ok(#(new_store, _)) -> new_store
Error(_) -> store
}
}
/// Create a single user row
fn create_user_row(id: Int) -> memory.MemoryRow {
[
dynamic.int(id),
dynamic.string("user" <> int.to_string(id) <> "@example.com"),
dynamic.string("User " <> int.to_string(id)),
dynamic.bool(True),
dynamic.int(25),
]
}
// ============================================================================
// INSERT BENCHMARKS
// ============================================================================
/// Benchmark single row insert
fn bench_single_insert() -> Nil {
let store = create_store()
let row = create_user_row(1)
let _ = memory.insert_row(store, "users", "1", row)
Nil
}
/// Benchmark batch insert of 10 rows
fn bench_batch_insert_10() -> Nil {
let store = create_store()
let rows =
list.range(1, 10)
|> list.map(fn(i) { #(int.to_string(i), create_user_row(i)) })
let _ = memory.insert_all(store, "users", rows)
Nil
}
/// Benchmark batch insert of 100 rows
fn bench_batch_insert_100() -> Nil {
let store = create_store()
let rows =
list.range(1, 100)
|> list.map(fn(i) { #(int.to_string(i), create_user_row(i)) })
let _ = memory.insert_all(store, "users", rows)
Nil
}
/// Benchmark batch insert of 1000 rows
fn bench_batch_insert_1000() -> Nil {
let store = create_store()
let rows =
list.range(1, 1000)
|> list.map(fn(i) { #(int.to_string(i), create_user_row(i)) })
let _ = memory.insert_all(store, "users", rows)
Nil
}
// ============================================================================
// QUERY BENCHMARKS
// ============================================================================
/// Benchmark get single row by key
fn bench_get_by_key() -> Nil {
let store = create_populated_store(1000)
let _ = memory.get_row(store, "users", "500")
Nil
}
/// Benchmark get all rows from small table (100 rows)
fn bench_get_all_100() -> Nil {
let store = create_populated_store(100)
let _ = memory.get_all_rows(store, "users")
Nil
}
/// Benchmark get all rows from medium table (1000 rows)
fn bench_get_all_1000() -> Nil {
let store = create_populated_store(1000)
let _ = memory.get_all_rows(store, "users")
Nil
}
/// Benchmark get all rows from large table (10000 rows)
fn bench_get_all_10000() -> Nil {
let store = create_populated_store(10_000)
let _ = memory.get_all_rows(store, "users")
Nil
}
/// Benchmark row count operation
fn bench_row_count() -> Nil {
let store = create_populated_store(1000)
let _ = memory.row_count(store, "users")
Nil
}
// ============================================================================
// UPDATE BENCHMARKS
// ============================================================================
/// Benchmark single row update
fn bench_single_update() -> Nil {
let store = create_populated_store(100)
let updated_row = [
dynamic.int(50),
dynamic.string("updated@example.com"),
dynamic.string("Updated User"),
dynamic.bool(False),
dynamic.int(30),
]
let _ = memory.update_row(store, "users", "50", updated_row)
Nil
}
/// Benchmark update all rows matching predicate
fn bench_update_all_matching() -> Nil {
let store = create_populated_store(100)
// Update all users to inactive
let _ =
memory.update_all(store, "users", fn(_key, _row) { True }, fn(row) {
case row {
[id, email, name, _, age] -> [
id,
email,
name,
dynamic.bool(False),
age,
]
_ -> row
}
})
Nil
}
// ============================================================================
// DELETE BENCHMARKS
// ============================================================================
/// Benchmark single row delete
fn bench_single_delete() -> Nil {
let store = create_populated_store(100)
let _ = memory.delete_row(store, "users", "50")
Nil
}
/// Benchmark delete all rows matching predicate
fn bench_delete_all_matching() -> Nil {
let store = create_populated_store(100)
// Delete half the users (even IDs)
let _ =
memory.delete_all(store, "users", fn(key, _row) {
case int.parse(key) {
Ok(id) -> id % 2 == 0
Error(_) -> False
}
})
Nil
}
/// Benchmark truncate table
fn bench_truncate_table() -> Nil {
let store = create_populated_store(1000)
let _ = memory.delete_all_rows(store, "users")
Nil
}
// ============================================================================
// TRANSACTION BENCHMARKS
// ============================================================================
/// Benchmark transaction with commit
fn bench_transaction_commit() -> Nil {
let store = create_populated_store(100)
let _ =
memory.execute_transaction(store, fn(tx_store) {
let row = create_user_row(999)
case memory.insert_row(tx_store, "users", "999", row) {
Ok(new_store) -> Ok(#(new_store, Nil))
Error(e) -> Error(e)
}
})
Nil
}
/// Benchmark transaction with rollback
fn bench_transaction_rollback() -> Nil {
let store = create_populated_store(100)
// Create a transaction that will fail due to duplicate key
let _ =
memory.execute_transaction(store, fn(tx_store) {
let row = create_user_row(50)
// This will fail - key 50 already exists
case memory.insert_row(tx_store, "users", "50", row) {
Ok(new_store) -> Ok(#(new_store, Nil))
Error(e) -> Error(e)
}
})
Nil
}
/// Benchmark nested transactions (savepoints)
fn bench_nested_transaction() -> Nil {
let store = create_populated_store(100)
let _ =
memory.execute_transaction(store, fn(tx_store) {
case memory.create_savepoint(tx_store, "sp1") {
Ok(sp_store) -> {
let row = create_user_row(999)
case memory.insert_row(sp_store, "users", "999", row) {
Ok(new_store) ->
case memory.release_savepoint(new_store, "sp1") {
Ok(final_store) -> Ok(#(final_store, Nil))
Error(_) -> Ok(#(sp_store, Nil))
}
Error(e) -> Error(e)
}
}
Error(_) -> Ok(#(tx_store, Nil))
}
})
Nil
}
// ============================================================================
// CONSTRAINT CHECKING BENCHMARKS
// ============================================================================
/// Benchmark unique constraint check on insert
fn bench_unique_constraint_check() -> Nil {
let store = create_populated_store(100)
// Try to insert duplicate key (will fail constraint)
let row = create_user_row(50)
let _ = memory.insert_row(store, "users", "50", row)
Nil
}
// ============================================================================
// MAIN ENTRY POINT
// ============================================================================
pub fn main() {
io.println("")
io.println("cquill Memory Adapter Benchmarks")
io.println("=================================")
io.println("")
// Use quick config for expensive operations
let quick = bench.quick_config()
let default = bench.default_config()
let insert_suite =
bench.run_suite(
"Insert Operations",
[
#("Single insert", bench_single_insert),
#("Batch insert (10 rows)", bench_batch_insert_10),
#("Batch insert (100 rows)", bench_batch_insert_100),
],
default,
)
// Expensive batch insert with fewer iterations
let batch_insert_suite =
bench.run_suite(
"Large Batch Insert",
[
#("Batch insert (1000 rows)", bench_batch_insert_1000),
],
quick,
)
let query_suite =
bench.run_suite(
"Query Operations",
[
#("Get by key (1000 rows)", bench_get_by_key),
#("Get all (100 rows)", bench_get_all_100),
#("Get all (1000 rows)", bench_get_all_1000),
#("Row count", bench_row_count),
],
default,
)
// Large query with fewer iterations
let large_query_suite =
bench.run_suite(
"Large Query Operations",
[
#("Get all (10000 rows)", bench_get_all_10000),
],
quick,
)
let update_suite =
bench.run_suite(
"Update Operations",
[
#("Single update", bench_single_update),
#("Update all matching", bench_update_all_matching),
],
default,
)
let delete_suite =
bench.run_suite(
"Delete Operations",
[
#("Single delete", bench_single_delete),
#("Delete matching", bench_delete_all_matching),
#("Truncate (1000 rows)", bench_truncate_table),
],
default,
)
let transaction_suite =
bench.run_suite(
"Transaction Operations",
[
#("Transaction + commit", bench_transaction_commit),
#("Transaction + rollback", bench_transaction_rollback),
#("Nested transaction (savepoint)", bench_nested_transaction),
],
default,
)
let constraint_suite =
bench.run_suite(
"Constraint Checking",
[
#("Unique constraint check", bench_unique_constraint_check),
],
default,
)
// Print summaries
bench.print_suite_summary(insert_suite)
bench.print_suite_summary(batch_insert_suite)
bench.print_suite_summary(query_suite)
bench.print_suite_summary(large_query_suite)
bench.print_suite_summary(update_suite)
bench.print_suite_summary(delete_suite)
bench.print_suite_summary(transaction_suite)
bench.print_suite_summary(constraint_suite)
// Print markdown
io.println("")
io.println("Markdown Output:")
io.println("-" |> string.repeat(60))
io.println(bench.to_markdown(insert_suite))
io.println(bench.to_markdown(query_suite))
io.println(bench.to_markdown(update_suite))
io.println(bench.to_markdown(delete_suite))
io.println(bench.to_markdown(transaction_suite))
}