Tutorial

Create a local database
and run SQL.

Follow these steps with netbadb-sdk. The default feature is embedded in-process. Enable the remote feature only when you need a Protocol v2 client. Toolchain 1.97.1; MSRV 1.85.0.

You will

  1. Add netbadb-sdk to a Rust crate
  2. Create a Heap file, insert a row, and query it
  3. Optionally inspect the catalog, then start netbadbd for a remote client

Engine characteristics

  • Typed SQL subset with nominal semantic types
  • Heap MVCC, optional LSM, WAL, and crash recovery
  • One writer per open database; Read Committed or Repeatable Read

1. Add the dependency

The crate is published from the workspace repository. Cargo resolves the netbadb-sdk package in that git workspace.

[dependencies]
netbadb-sdk = { git = "https://github.com/sskycn/netbadb" }
[dependencies]
netbadb-sdk = { git = "https://github.com/sskycn/netbadb", default-features = false, features = ["remote"] }

2. Create, insert, and query

Database::create refuses to overwrite an existing database or WAL slot. insert and execute run as implicit transactions. create_index backfills current rows and registers a non-unique single-column index. analyze writes a fresh optimizer snapshot; DML does not refresh it automatically.

use netbadb_sdk::{
    ColumnDef, ColumnId, Database, DatabaseError, PhysicalType, ScalarValue, TableDef, TableId,
    TypeSpec,
};

fn users() -> TableDef {
    TableDef::new(
        TableId(1),
        "users",
        vec![
            ColumnDef::new(
                ColumnId(1),
                "id",
                TypeSpec::Semantic {
                    name: "UserId".into(),
                    physical: PhysicalType::UInt64,
                },
            ),
            ColumnDef::new(ColumnId(2), "name", TypeSpec::Physical(PhysicalType::Text)),
        ],
    )
}

fn main() -> Result<(), DatabaseError> {
    let mut database = Database::create("users.db", users())?;
    database.insert(&[
        ScalarValue::UInt64(1),
        ScalarValue::Text("Ada".into()),
    ])?;
    database.create_index(TableId(1), ColumnId(1))?;
    database.analyze(TableId(1))?;
    let _rows = database.query("SELECT id, name FROM users WHERE id = 1")?;
    database.close()
}

3. Inspect the catalog and plan

Inspection compiles and plans a statement without executing it. It does not scan heaps, refresh ANALYZE, acquire the writer, or append WAL.

use netbadb_sdk::inspection;

let catalog = database.inspect_catalog()?;
println!("{}", inspection::render_catalog(&catalog));

let statement = database.inspect_statement(
    "SELECT id FROM users WHERE id = 1",
)?;
println!("{}", inspection::render_statement(&statement));

4. Start netbadbd

The server opens existing heap files declared by deployment manifest v11. Versions 1 through 10 are rejected. Loopback plaintext requires exactly one local_plaintext principal. Non-loopback listening requires mutual TLS.

{
  "version": 11,
  "listen": "127.0.0.1:7878",
  "authorization": {
    "local_plaintext": {
      "tables": [
        { "table_id": 1, "read": true, "write": true, "transaction": true, "analyze": true }
      ]
    },
    "clients": []
  },
  "tables": [
    {
      "path": "users.db",
      "id": 1,
      "name": "users",
      "columns": [
        { "id": 1, "name": "id", "physical_type": "uint64", "semantic_type": "UserId", "nullable": false },
        { "id": 2, "name": "name", "physical_type": "text", "nullable": false }
      ]
    }
  ]
}
cargo run -p netbadbd -- --manifest server.json

5. Connect a remote client

Plaintext is accepted only when the resolved TCP peer is loopback. Remote deployments require verified mutual TLS. There is no connection pool, automatic retry, or multiplexing.

use netbadb_sdk::remote;

let mut client = remote::Client::connect(
    remote::Config::new("127.0.0.1:7878"),
)?;
client.ping()?;
let mut rows = client.query("SELECT id, name FROM users ORDER BY id")?;
while let Some(values) = rows.next_row()? {
    println!("{values:?}");
}

Isolation

begin_transaction uses Read Committed. Repeatable Read is available through begin_transaction_with_isolation. IsolationLevel is exported by netbadb-core. Serializable isolation is not available.

use netbadb_core::IsolationLevel;

let mut tx = database.begin_transaction_with_isolation(
    IsolationLevel::RepeatableRead,
)?;

SQL DDL

Heap CREATE TABLE accepts Physical Types v2 names, including BOOLEAN, integer widths, TEXT/VARCHAR, REAL/DOUBLE, BYTEA, and native UINT*. DROP TABLE binds identity at prepare time. ALTER TABLE supports rename table/column, nullable ADD, restricted DROP, and SET/DROP NOT NULL. CREATE INDEX / DROP INDEX manage a single-column non-unique Heap BTree. Network DDL requires schema_admin. PostgreSQL wire compatibility is not available.

CREATE TABLE projects (
    id BIGINT NOT NULL,
    name TEXT,
    active BOOLEAN NOT NULL
);
ALTER TABLE projects ADD COLUMN notes TEXT;
ALTER TABLE projects ALTER COLUMN name SET NOT NULL;
CREATE INDEX projects_id_idx ON projects (id);
DROP INDEX projects_id_idx;
ALTER TABLE projects DROP COLUMN notes;
DROP TABLE projects;

LSM, partitions, catalog, and vacuum

Database::create_storages can create Heap or LSM tables. create_with_placements attaches RANGE partitions. open_catalog reopens a published schema catalog without external TableDefs. vacuum reclaims dead Heap versions that no active snapshot can see. Columnar projections are derived, opt-in, and stale after committed DML until refresh.

use netbadb_sdk::{ColumnId, Database, TableStorageCreateSpec};

let mut database = Database::create_storages(vec![
    TableStorageCreateSpec::lsm("users-lsm", users(), ColumnId(1)),
])?;
database.vacuum(TableId(1))?;
let mut reopened = Database::open_catalog("catalog")?;

Local operator plane

NBOP v7 is a Unix-domain operator protocol configured by manifest v11. It is not the database wire protocol. Native Protocol v2 remains the only network database frontend.

{
  "operator": {
    "unix_socket": "run/netbadb-operator.sock",
    "io_timeout_ms": 5000,
    "allow_physical_index_apply": false,
    "allow_physical_columnar_apply": false,
    "allow_physical_design_receipt_read": false,
    "physical_index_admission": {"mode": "unadmitted"},
    "physical_columnar_snapshot_admission": {"mode": "unadmitted"},
    "physical_columnar_incremental_admission": {"mode": "unadmitted"}
  }
}

6. Inspect files from the command line

Stop netbadbd and any embedded process using the same files first. The CLI opens tables with normal startup recovery and never executes the inspected SQL. JSON output uses Inspection JSON v7.

cargo run -p netbadb -- inspect catalog --manifest server.json

cargo run -p netbadb -- inspect statement \
  --manifest server.json \
  --sql "SELECT id FROM users WHERE id = 1" \
  --format json

Go client

The Go module is an independent Protocol v2 client. It uses no cgo or Rust FFI. Dial performs Hello automatically. Int128 and UInt128 result types are rejected by the Go client.

client, err := netbadb.Dial(ctx, netbadb.Config{
    Address: "localhost:7878",
})
if err != nil { /* handle */ }
defer client.Close()

rows, err := client.Query(ctx, "SELECT id, name FROM users ORDER BY id")

Editor diagnostics

netbadb-lsp --schema schema.json is a diagnostics-only stdio language server. It loads SDK Schema Spec v1 or v2 once. It does not open database files or report physical plans.

netbadb-lsp --schema schema.json

Build from source

git clone https://github.com/sskycn/netbadb.git
cd netbadb
make test

Operating constraints

  • One writer per open database object. Read-only transactions do not reserve the writer.
  • Explicit transactions support Read Committed and Repeatable Read. Implicit statements use Read Committed. Serializable isolation is not available.
  • A successful commit means the Commit record is durable; heap pages may remain buffered until flush, vacuum, or close.
  • SQL CREATE TABLE, DROP TABLE, ALTER TABLE, CREATE INDEX, and DROP INDEX are available for Heap tables. PRIMARY KEY, IF EXISTS, and general ALTER COLUMN TYPE are not.
  • Multi-storage writes commit through the coordinator log. Concurrent writers and cross-process file locks are not available.
  • Experimental on-disk formats reject older versions. There is no migration path.

License

NetbaDB is licensed under AGPL-3.0-or-later. If you modify the program and let users interact with it over a network, you must provide the corresponding source.

Questions

How do I embed NetbaDB in a Rust process?

Add netbadb-sdk from the GitHub workspace, call Database::create, then insert and query in-process. The default feature is embedded.

What protocol does netbadbd speak?

Native Protocol v2 only. Manifest v11 is the current startup contract. PostgreSQL wire and netbadbd --postgres were removed.

Which isolation levels exist?

Explicit transactions support Read Committed and Repeatable Read. Implicit statements use Read Committed. Serializable isolation is not available.