Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
527 changes: 241 additions & 286 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/coln-js-runtime/src/rust/handles.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Coln contributors
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use coln_flir_rs::ir;
use coln_store::{
commit::hash::CommitHash as StoreCommitHash,
Expand Down
6 changes: 6 additions & 0 deletions packages/coln-store/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ tmp/
.claude/
.codex
.agent/

# colnstore
*.colnstore

# compressed data
*.zst
20 changes: 10 additions & 10 deletions packages/coln-store/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Quick examples:

## Required Validation

Run these checks for any non-trivial change:
Run these checks for non-trivial change when you are told to. Otherwise there is no need to run checks.

`just check`

Expand All @@ -95,9 +95,9 @@ Use this sequence for your first change:
1. Read `src/lib.rs`, and the relevant module files.
2. Implement the smallest possible code change.
3. Add or update tests that fail before and pass after.
4. Run `cargo test --workspace --all-targets`.
5. Run `cargo clippy --workspace --all-targets --all-features -- -D warnings`.
6. Run `cargo fmt --all --check`.
4. Run `cargo test -p coln-store --all-targets`.
5. Run `cargo clippy -p coln-store --all-targets --all-features -- -D warnings`.
6. Run `cargo fmt -p coln-store --check`.
7. Update docs if public API or user-facing behavior changed.

Example scopes that are good first tasks:
Expand Down Expand Up @@ -135,9 +135,9 @@ Before coding:

Before submitting:

1. Passing `cargo test --workspace --all-targets`.
2. Passing `cargo clippy --workspace --all-targets --all-features -- -D warnings`.
3. Passing `cargo fmt --all --check`.
1. Passing `cargo test -p coln-store --all-targets`.
2. Passing `cargo clippy -p coln-store --all-targets --all-features -- -D warnings`.
3. Passing `cargo fmt -p coln-store --check`.
4. Tests added or updated where behavior changed.
5. Docs updated where public behavior changed.

Expand Down Expand Up @@ -184,6 +184,6 @@ Use this review format:
Suggested PR checklist:

- [ ] Tests added or updated for behavior changes
- [ ] `cargo test --workspace --all-targets` passes
- [ ] `cargo clippy --workspace --all-targets --all-features -- -D warnings` passes
- [ ] `cargo fmt --all --check` passes
- [ ] `cargo test -p coln-store --all-targets` passes
- [ ] `cargo clippy -p coln-store --all-targets --all-features -- -D warnings` passes
- [ ] `cargo fmt -p coln-store --check` passes
7 changes: 6 additions & 1 deletion packages/coln-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ exclude = [
]

[dependencies]
anyhow.workspace = true
blake3 = "1.8.5"
clap = { version = "4.6.1", features = ["derive"], optional = true }
coln-flir-rs = { path = "../coln-flir-rs" }
csv = "1.4.0"
# coln-query = { path = "../coln-query" }
hex = "0.4.3"
hexane = { git = "https://github.com/automerge/automerge", branch = "hexane_v1", package = "hexane" }
js-sys = { version = "0.3.83", optional = true }
leb128 = "0.2.6"
petgraph = "0.8.3"
postcard = { version = "1.1.3", features = ["alloc"] }
rustyline = { version = "18.0.0", optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
shlex = "2.0.1"
sqlparser = "0.62.0"
thiserror = "2.0.18"
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = [
Expand Down Expand Up @@ -64,7 +69,7 @@ tokio = { version = "1.52.3", features = [

[features]
default = ["native"]
native = ["dep:rustyline", "dep:tracing-subscriber"]
native = ["dep:rustyline", "dep:tracing-subscriber", "dep:clap"]
wasm = ["dep:js-sys"]

[[bin]]
Expand Down
34 changes: 25 additions & 9 deletions packages/coln-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Coln Store is the storage engine for Coln. Roughly it ought to support:
This repo lets you give the store a compiled Coln theory, load it, and then add
data to its tables.

Example of loading a schema and inserting several related rows. Type `/help`
for the full command list (for example `load-schema`, `add`, `dump-table`,
`dump-store`, and the batch form below).
Example of loading a schema and inserting several related rows. Type `.help`
for the full command list (for example `.load`, `.schema`, `.rules`,
`.dump`, `.tables`, `.open`, `.save`, `add`, and the batch form below).

## Transaction

Expand All @@ -27,12 +27,12 @@ The snippet below loads the `paths` theory, creates two graphs (`g0`, `g1`),
records `g1` as the designated graph for the `Path.G0` and `Path.G1` indices,
adds two vertices on `g0`, and adds one edge between them on `g0`.

After `commit`, inspect what landed with `dump-table <table>;`. For example,
`dump-table Path.Graphs;`, `dump-table Path.G.V;`, or `dump-table Path.G.E;`,
or print the entire store with `dump-store;`.
After `commit`, inspect what landed with `.dump <table>`. For example,
`.dump Path.Graphs`, `.dump Path.G.V`, or `.dump Path.G.E`, print every
table with `.tables`, or print all compiled rules with `.rules`.

```text
coln-store> load-schema tests/data/Path.json;
coln-store> .load tests/data/Path.json
begin transact;
g0 = add Path.Graphs values ();
g1 = add Path.Graphs values ();
Expand All @@ -47,8 +47,24 @@ begin transact;

commit;

persist paths.bin;
load-store paths.bin;
.save paths.bin
.open paths.bin

.load tests/data/ExprRealm.json
begin transact;
t0 = add ExprRealm.exprs.t values ();
t1 = add ExprRealm.exprs.t values ();
t2 = add ExprRealm.exprs.t values ();
va = add ExprRealm.v0 values ();
vb = add ExprRealm.v0 values ();

ta = add ExprRealm.exprs.vars values (va t0);
tb = add ExprRealm.exprs.vars values (vb t1);

apb = add ExprRealm.exprs.plus values (t0 t1 t2);

commit;

```

To get a violation of the law, say (`Path.Hom.V.total`), change the line
Expand Down
3 changes: 2 additions & 1 deletion packages/coln-store/src/commit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,15 @@ fn collect_op_hashes(pending: &[PendingOp], hash_mapper: &mut HashMapper) {

#[cfg(test)]
mod tests {
use std::sync::LazyLock;

use super::*;
use crate::commit::chunk::{Chunk, hash};
use crate::commit::hash::HASH_SIZE;
use crate::commit::wire::root::{RootCommitData, RootTableEntry};
use crate::ir::{BuiltinTy, ColType, ColumnEntry, EntityVariant, Path};
use crate::table::RowId;
use crate::txn::ops::{RowRef, TempRowId};
use std::sync::LazyLock;

fn zero_hash() -> CommitHash {
CommitHash([0u8; HASH_SIZE])
Expand Down
3 changes: 2 additions & 1 deletion packages/coln-store/src/commit/pst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,15 @@ fn decode_store_chunks(next_oid: TableOid, chunks: Vec<Chunk>) -> Result<Store,

#[cfg(test)]
mod tests {
use coln_flir_rs::ir::{BuiltinTy, ColType, ColumnEntry, EntityVariant};

use super::*;
use crate::commit::author::Author;
use crate::commit::chunk::{ChunkType, Header};
use crate::commit::hash::{CommitHash, HASH_SIZE};
use crate::commit::wire::CommitData;
use crate::ir::{FlatRealm, Path, Schema, TableEntry};
use crate::table::CellValue;
use coln_flir_rs::ir::{BuiltinTy, ColType, ColumnEntry, EntityVariant};

fn int_schema() -> Schema {
Schema {
Expand Down
3 changes: 2 additions & 1 deletion packages/coln-store/src/commit/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::commit::error::CodecError;
use std::io::Write;

use crate::commit::error::CodecError;

// ── Write helpers ───────────────────────────────────────────────────────────

pub(crate) fn write_u8(buf: &mut Vec<u8>, value: u8) -> Result<(), CodecError> {
Expand Down
36 changes: 14 additions & 22 deletions packages/coln-store/src/commit/wire/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use hexane::v1::{Column, DeltaColumn};
use std::io::Write;

use hexane::v1::{Column, DeltaColumn};

use crate::commit::author::Author;
use crate::commit::leb128 as commit_leb128;
use crate::commit::wire::prim::{ValueMeta, decode_prim_value, encode_prim_value};
use crate::commit::wire::prim::{
self, ValueMeta, decode_prim_value, encode_path, encode_prim_value,
};
use crate::{
commit::{
error::CodecError,
Expand Down Expand Up @@ -307,9 +310,8 @@ fn encode_op_group(
}

let mut buf = Vec::new();
let path = table.to_string();
commit_leb128::write_len(&mut buf, path.len());
buf.write_all(path.as_bytes())?;
let path = encode_path(table);
buf.write_all(&path)?;

commit_leb128::write_len(&mut buf, rows.len());

Expand Down Expand Up @@ -470,12 +472,7 @@ where
{
let mut pos = 0usize;

let path_len = commit_leb128::read_len(data, &mut pos, "op group table path length")?;
let path_bytes = read_slice(data, &mut pos, path_len, "op group table path")?;
let table =
Path::from(std::str::from_utf8(path_bytes).map_err(|_| {
CodecError::DataFormatError("op group table path: invalid utf-8".into())
})?);
let table = prim::decode_path(data, &mut pos)?;

let row_count = commit_leb128::read_len(data, &mut pos, "op group row count")?;
let column_count = commit_leb128::read_len(data, &mut pos, "op group column count")?;
Expand Down Expand Up @@ -862,9 +859,8 @@ mod tests {

let encoded = encode_op_group(&table, &schema, &ops, &hash_mapper).expect("encode group");
let mut pos = 0usize;
let path_len = commit_leb128::read_len(&encoded, &mut pos, "path length").unwrap();
let path_bytes = read_slice(&encoded, &mut pos, path_len, "path").unwrap();
assert_eq!(std::str::from_utf8(path_bytes).unwrap(), "T");
let path = prim::decode_path(&encoded, &mut pos).unwrap();
assert_eq!(path.to_string(), "T");
assert_eq!(
commit_leb128::read_len(&encoded, &mut pos, "row count").unwrap(),
2
Expand Down Expand Up @@ -1016,10 +1012,8 @@ mod tests {
let group_a =
commit_leb128::read_len_prefixed_bytes(&encoded, &mut pos, "group a").unwrap();
let mut group_pos = 0usize;
let path_len =
commit_leb128::read_len(group_a, &mut group_pos, "group a path length").unwrap();
let path = read_slice(group_a, &mut group_pos, path_len, "group a path").unwrap();
assert_eq!(std::str::from_utf8(path).unwrap(), "A");
let path = prim::decode_path(group_a, &mut group_pos).unwrap();
assert_eq!(path.to_string(), "A");
assert_eq!(
commit_leb128::read_len(group_a, &mut group_pos, "group a rows").unwrap(),
2
Expand All @@ -1032,10 +1026,8 @@ mod tests {
let group_b =
commit_leb128::read_len_prefixed_bytes(&encoded, &mut pos, "group b").unwrap();
let mut group_pos = 0usize;
let path_len =
commit_leb128::read_len(group_b, &mut group_pos, "group b path length").unwrap();
let path = read_slice(group_b, &mut group_pos, path_len, "group b path").unwrap();
assert_eq!(std::str::from_utf8(path).unwrap(), "B");
let path = prim::decode_path(group_b, &mut group_pos).unwrap();
assert_eq!(path.to_string(), "B");
assert_eq!(
commit_leb128::read_len(group_b, &mut group_pos, "group b rows").unwrap(),
1
Expand Down
36 changes: 35 additions & 1 deletion packages/coln-store/src/commit/wire/prim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

use std::io::Write;

use coln_flir_rs::ir::BuiltinTy;
use coln_flir_rs::ir::{self, BuiltinTy};
use hexane::{PackError, lebsize};

use crate::commit::leb128 as commit_leb128;
use crate::{commit::error::CodecError, txn::ops::TxnCellValue};

/// Number of low bits reserved for the [`ValueType`] code in a [`ValueMeta`].
Expand Down Expand Up @@ -141,6 +142,8 @@ impl hexane::v1::PrefixValue for ValueMeta {
}
}

/// Writes to the buffer `out` the encoded data, and returns the corresponding
/// `ValueMeta` representation
pub(crate) fn encode_prim_value(
value: &TxnCellValue,
prim: &BuiltinTy,
Expand Down Expand Up @@ -208,6 +211,37 @@ pub(crate) fn decode_prim_value(
}
}

pub(crate) fn encode_path(path: &ir::Path) -> Vec<u8> {
let mut out = Vec::new();
commit_leb128::write_len(&mut out, path.0.len());
for qname in &path.0 {
commit_leb128::write_len(&mut out, qname.len());
for part in qname {
commit_leb128::write_len_prefixed_bytes(&mut out, part.as_bytes());
}
}
out
}

pub(crate) fn decode_path(data: &[u8], pos: &mut usize) -> Result<ir::Path, CodecError> {
let qname_count = commit_leb128::read_len(data, pos, "path name count")?;
let mut path = Vec::with_capacity(qname_count);

for _ in 0..qname_count {
let part_count = commit_leb128::read_len(data, pos, "part count")?;
let mut qname = Vec::with_capacity(part_count);
for _ in 0..part_count {
let part_bytes = commit_leb128::read_len_prefixed_bytes(data, pos, "qname part")?;
let part = std::str::from_utf8(part_bytes)
.map_err(|_| CodecError::DataFormatError("path part invalid utf-8".into()))?;
qname.push(part.to_owned())
}
path.push(qname);
}

Ok(ir::Path(path))
}

#[allow(dead_code)]
fn encode_bigint(value: &[u8], out: &mut Vec<u8>) -> Result<ValueMeta, CodecError> {
out.write_all(value)?;
Expand Down
3 changes: 2 additions & 1 deletion packages/coln-store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

pub use coln_flir_rs::ir;
use coln_flir_rs::ir;
pub mod commit;
#[cfg(feature = "native")]
pub mod repl;
mod roweq;
pub mod solver;
pub mod store;
pub mod table;
Expand Down
20 changes: 17 additions & 3 deletions packages/coln-store/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use clap::Parser;
use coln_store::repl;
use tracing_subscriber::EnvFilter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Parser, Debug)]
struct Args {
#[arg(long, default_value_t = false, hide = true)]
enable_sql_mode: bool,
}

fn main() -> anyhow::Result<()> {
const DEFAULT_FILTER: &str = if cfg!(debug_assertions) {
"coln_store=debug"
} else {
"coln_store=info"
};

tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("coln_store=info")),
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(DEFAULT_FILTER)),
)
.init();

repl::run()
let args = Args::parse();
repl::run(args.enable_sql_mode)
}
Loading
Loading