SochDB SQL Surface & Compatibility
This document defines SochDB's SQL dialect support and the canonical pipeline for SQL execution.
Reflects the SochDB core engine v2.0.3. Support levels below were verified against the
volcano executor (sochdb-query/src/executor/) and the storage-backed SqlBridge
(sochdb-query/src/sql/bridge.rs), which are the two production execution paths.
SochDB has more than one SQL code path. The SqlBridge (storage-backed dispatcher) has
the fullest statement coverage (DDL/DML, CREATE/DROP INDEX, ALTER TABLE, scopes and
permissions). The volcano executor is the row-at-a-time operator engine that handles
SELECT planning (JOIN, GROUP BY/HAVING, ORDER BY, EXPLAIN). A separate in-memory
SqlExecutor (sochdb-query/src/sql/mod.rs) is a reference implementation only and is not
the production path β it rejects multi-table FROM. Support levels in this page describe the
bridge + volcano executor.
Architecture Overviewβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SQL Query Lifecycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β SQL Text β --> β Lexer β --> β Parser β β
β β (any dialect)β β (tokenize) β β (parse) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β
β v β
β βββββββββββββββββββββββββββββββββββββββββ β
β β Canonical AST β β
β β (dialect-normalized representation) β β
β βββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββΌββββββββββββββββββββββ β
β β β β β
β v v v β
β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ βββββββββ β
β β Validator β β Planner β βExecutorβ β
β β (semantic checks) β β (optimize + plan) β β (run) β β
β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ βββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Core SQL Support (Guaranteed)β
Data Manipulation Language (DML)β
| Statement | Support | Example |
|---|---|---|
SELECT | β Full | SELECT id, name FROM users WHERE age > 21 |
INSERT | β Full | INSERT INTO users (id, name) VALUES (1, 'Alice') |
UPDATE | β Full | UPDATE users SET name = 'Bob' WHERE id = 1 |
DELETE | β Full | DELETE FROM users WHERE id = 1 |
Data Definition Language (DDL)β
| Statement | Support | Example |
|---|---|---|
CREATE TABLE | β Full | CREATE TABLE users (id INT PRIMARY KEY, name TEXT) |
DROP TABLE | β Full | DROP TABLE users |
ALTER TABLE | π Partial | ALTER TABLE users ADD COLUMN email TEXT (ADD/DROP COLUMN only) |
CREATE INDEX | β Full | CREATE INDEX idx_name ON users (name) |
DROP INDEX | β Full | DROP INDEX idx_name |
CREATE INDEX and DROP INDEX are dispatched by the SqlBridge. ALTER TABLE currently
supports ADD COLUMN and DROP COLUMN.
Idempotent DDLβ
| Statement | Support | Behavior |
|---|---|---|
CREATE TABLE IF NOT EXISTS | β Full | No-op if table exists |
DROP TABLE IF EXISTS | β Full | No-op if table doesn't exist |
CREATE INDEX IF NOT EXISTS | β Full | No-op if index exists |
DROP INDEX IF EXISTS | β Full | No-op if index doesn't exist |
Transactionsβ
| Statement | Support | Notes |
|---|---|---|
BEGIN | β Full | Start transaction |
COMMIT | β Full | Commit transaction |
ROLLBACK | β Full | Rollback transaction |
SAVEPOINT / RELEASE | β Not supported | Bridge returns NotImplemented |
Dialect Compatibility (Conflict/Upsert Family)β
SochDB normalizes dialect-specific INSERT variants to a canonical AST representation.
| Conflict action | Support |
|---|---|
ON CONFLICT DO NOTHING, INSERT IGNORE, INSERT OR IGNORE | β Full |
ON CONFLICT DO UPDATE, INSERT OR REPLACE, ON DUPLICATE KEY UPDATE | π Partial |
PostgreSQL Styleβ
-- Do nothing on conflict
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT DO NOTHING;
-- Update on conflict (specific columns)
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO UPDATE SET name = 'Bob';
MySQL Styleβ
-- Ignore on duplicate (equivalent to ON CONFLICT DO NOTHING)
INSERT IGNORE INTO users (id, name) VALUES (1, 'Alice');
-- Update on duplicate key
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON DUPLICATE KEY UPDATE name = 'Bob';
SQLite Styleβ
-- Ignore on conflict
INSERT OR IGNORE INTO users (id, name) VALUES (1, 'Alice');
-- Replace on conflict (delete + insert)
INSERT OR REPLACE INTO users (id, name) VALUES (1, 'Alice');
-- Abort on conflict (default behavior)
INSERT OR ABORT INTO users (id, name) VALUES (1, 'Alice');
-- Fail on conflict (fail but continue batch)
INSERT OR FAIL INTO users (id, name) VALUES (1, 'Alice');
Internal Representationβ
All dialect forms normalize to:
InsertStmt {
on_conflict: Some(OnConflict {
target: Option<ConflictTarget>, // (id) or ON CONSTRAINT name
action: ConflictAction, // DoNothing, DoUpdate(...), DoReplace, etc.
})
}
Parameterized Queriesβ
SochDB supports two placeholder styles:
Positional Placeholders ($1, $2, ...)β
SELECT * FROM users WHERE id = $1 AND name = $2
Question Mark Placeholders (?)β
SELECT * FROM users WHERE id = ? AND name = ?
Question marks are automatically indexed (1, 2, 3...) during lexing.
Parameter Bindingβ
use sochdb_client::ast_query::AstQueryExecutor;
use sochdb_core::soch::SochValue;
let executor = AstQueryExecutor::new(&conn);
let result = executor.execute_with_params(
"SELECT * FROM users WHERE id = $1",
&[SochValue::Int(42)],
)?;
Query Featuresβ
SELECT Clausesβ
| Clause | Support | Notes |
|---|---|---|
FROM with table aliases | β Full | |
WHERE with complex predicates | β Full | Three-valued NULL logic |
GROUP BY | β Full | HashAggregate, preserves insertion order |
HAVING | β Full | Filter on aggregate output |
ORDER BY (ASC/DESC/NULLS FIRST/NULLS LAST) | β Full | nulls_first defaults to !asc |
LIMIT / OFFSET | β Full | Must be integer literals |
DISTINCT | β Not supported | No DistinctNode in the planner |
UNION | π Partial | |
INTERSECT / EXCEPT | π Planned |
Aggregate Functionsβ
| Function | Support | Notes |
|---|---|---|
COUNT (incl. COUNT(DISTINCT ...)) | β Full | |
SUM, AVG (alias MEAN), MIN, MAX | β Full | |
MEDIAN | β Full | Via the sql/aggregate.rs path only |
STDDEV (aliases STDDEV_SAMP, STDEV, SD) | β Full | Sample (n-1), Welford online variance; sql/aggregate.rs path only |
There are two aggregate implementations. The volcano operator
(executor/aggregate.rs) covers COUNT/SUM/AVG/MIN/MAX (and COUNT DISTINCT).
MEDIAN and STDDEV are implemented only in the separate sql/aggregate.rs engine. STDDEV
is the sample standard deviation (matching R's sd() and DuckDB's stddev). NULL inputs are
skipped by all aggregates except COUNT(*).
Expressionsβ
- Arithmetic:
+,-,*,/,%(division by zero errors) - Comparison:
=,!=,<>,<,<=,>,>= - Logical:
AND,OR,NOT(short-circuit) - String concatenation:
|| - Bitwise:
&,|,^,<<,>> IS NULL,IS NOT NULLIN (...),NOT IN (...)BETWEEN ... AND ...(andNOT BETWEEN)LIKEwith wildcards (case-sensitive;%= zero-or-more,_= exactly one)CASE WHEN ... THEN ... ELSE ... ENDCOALESCE,NULLIF- Function calls:
COUNT(),SUM(),AVG(), etc.
CAST is a pass-throughCAST(expr AS type) parses and runs, but the executor currently passes the inner value
through unchanged β there is no real type coercion yet. Do not rely on CAST to convert
between SQL types at runtime.
LIKELIKE is case-sensitive (SQL-92). All characters other than % and _ match literally,
including regex metacharacters such as ., *, and [ β there is no regex engine behind
LIKE, so 'file.txt' LIKE 'file.txt' matches but 'fileXtxt' does not.
SochDB Extensionsβ
VECTOR(dimensions)data type β e.g.VECTOR(768)EMBEDDING(dimensions)data type β e.g.EMBEDDING(1536)VECTOR_SEARCH(column, query_vector, k, metric)function, wheremetricis one ofCOSINE,EUCLIDEAN, orDOT_PRODUCTCONTEXT_WINDOW(tokens, priority_expr)for LLM context management
-- k-nearest-neighbor search over an embedding column
SELECT id, title
FROM documents
ORDER BY VECTOR_SEARCH(embedding, $1, 10, COSINE);
VECTOR_SEARCH is the SQL entry point for vector search and is routed to the vector index.
The lower-level SimilarTo (column SIMILAR TO 'query text') predicate is a SochQL
construct, not a raw SQL token. In scalar (row-by-row) expression evaluation, VECTOR_SEARCH,
subqueries, EXISTS, JSON access, and graph traversal operators (->, <-, <->) are not
evaluated and return errors; vector search must go through the index path.
JOINsβ
The volcano executor implements the full set of join types, and the SqlBridge handles
TableRef::Join (emitting NULLs for unmatched outer rows).
| Join type | Support | Operator |
|---|---|---|
INNER JOIN | β Full | HashJoin (equi), NestedLoopJoin (theta) |
LEFT JOIN | β Full | HashJoin |
RIGHT JOIN | β Full | HashJoin |
FULL [OUTER] JOIN | β Full | HashJoin |
CROSS JOIN (and implicit comma-join) | β Full | NestedLoopJoin |
NATURAL JOIN | β οΈ Approximate | Falls back to CROSS JOIN, not a true natural join |
The planner routes ON a = b to a HashJoin, non-equi ON predicates to a
NestedLoopJoin, and USING (col) to a HashJoin on col = col.
The engine's internal compatibility.rs feature matrix still reports multi-table JOINs as
Partial/Planned. That matrix is out of date relative to the executor and bridge, which fully
implement INNER/LEFT/RIGHT/FULL/CROSS joins. The table above reflects the actual code.
EXPLAINβ
EXPLAIN is supported through the volcano path. The planner emits a textual plan tree
(under a QUERY PLAN column) covering SeqScan, Filter (WHERE), HashAggregate, Filter
(HAVING), Project, Sort, Limit, and JOIN nodes (INNER/LEFT/RIGHT/FULL/CROSS).
EXPLAIN is not served by the SqlBridge β the bridge returns NotImplemented for
Statement::Explain. Route EXPLAIN queries through the volcano executor path.
Explicit Limitationsβ
| Feature | Status | Notes |
|---|---|---|
DISTINCT | β Not supported | No DistinctNode in the planner |
| Window functions | π Planned | |
CTEs (WITH clause) | π Planned | |
Subqueries in WHERE / SELECT | π Planned | Scalar/correlated subqueries not evaluated |
Subquery in FROM | π Partial | |
UNION | π Partial | |
INTERSECT / EXCEPT | π Planned | |
Real CAST coercion | β Not supported | CAST is a value pass-through |
True NATURAL JOIN | β οΈ Approximate | Falls back to CROSS JOIN |
Graph traversal operators (->, <-, <->) | β Not supported | Error in scalar evaluation |
| Stored procedures | β | Out of scope |
| Triggers | β | Out of scope |
Complexity Analysisβ
| Operation | Time Complexity | Notes |
|---|---|---|
| Lexing | O(n) | n = input length |
| Parsing | O(n) | n = token count |
| AST rewriting | O(|AST|) | Linear in AST size |
| SELECT (no index) | O(N) | N = table rows |
| SELECT (with index) | O(log N + K) | K = result rows |
| INSERT | O(log N) | B-Tree index update |
| INSERT (conflict check) | O(log N) | Uniqueness check |
| UPDATE (no index) | O(N) | Full scan |
| UPDATE (with index) | O(log N + K) | K = affected rows |
Client Usageβ
Using the AST-Based Query Executorβ
The recommended way to execute SQL is via the AST-based query executor, which uses the real SQL parser (not string heuristics) and normalizes all supported dialects to one canonical AST.
use sochdb_client::connection::SochConnection;
use sochdb_client::ast_query::{AstQueryExecutor, QueryResult};
use sochdb_core::soch::SochValue;
// open_persistent() gives a durable, on-disk connection at the given path.
// (SochConnection::open() exists too, but it is ephemeral β it ignores the
// path and uses a temporary directory cleaned up on drop.)
let conn = SochConnection::open_persistent("./data")?;
let executor = AstQueryExecutor::new(&conn);
// SELECT returns QueryResult::Select(Vec<HashMap<String, SochValue>>)
match executor.execute("SELECT * FROM users WHERE active = true")? {
QueryResult::Select(rows) => {
for row in rows {
println!("{:?}", row);
}
}
_ => {}
}
// Execute with parameters ($1, $2, ... or ?)
let result = executor.execute_with_params(
"INSERT INTO users (id, name) VALUES ($1, $2)",
&[SochValue::Int(1), SochValue::Text("Alice".to_string())],
)?;
// INSERT/UPDATE/DELETE return QueryResult::Insert/Update/Delete with affected-row info
let result = executor.execute("DELETE FROM users WHERE id = 1")?;
QueryResult variants: Select, Insert, Update, Delete, CreateTable, DropTable,
CreateIndex, and Empty (for BEGIN/COMMIT/SET, etc.).
Dialect Supportβ
The AST-based executor automatically normalizes dialect-specific syntax:
// All of these normalize to the same canonical AST:
// MySQL style
executor.execute("INSERT IGNORE INTO users VALUES (1, 'Alice')")?;
// PostgreSQL style
executor.execute("INSERT INTO users VALUES (1, 'Alice') ON CONFLICT DO NOTHING")?;
// SQLite style
executor.execute("INSERT OR IGNORE INTO users VALUES (1, 'Alice')")?;
Filesβ
sochdb-query/src/sql/compatibility.rs- Internal feature matrix and dialect detection (note: its JOIN entries are stale)sochdb-query/src/sql/token.rs- Token types including dialect keywordssochdb-query/src/sql/lexer.rs- Tokenizer with placeholder indexingsochdb-query/src/sql/ast.rs- Canonical AST definitions (incl.VECTOR/EMBEDDINGtypes,VECTOR_SEARCH)sochdb-query/src/sql/parser.rs- Recursive descent parsersochdb-query/src/sql/bridge.rs- Storage-backed dispatcher (fullest statement coverage)sochdb-query/src/sql/aggregate.rs- Aggregate engine withMEDIAN/STDDEVsochdb-query/src/executor/- Volcano operator engine (SELECTplanning, JOINs,EXPLAIN)sochdb-query/src/sql/error.rs- Error typessochdb-query/src/sql/mod.rs- Module exports + in-memory referenceSqlExecutorsochdb-client/src/ast_query.rs- AST-based client query executor