Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GitHub

This documentation is part of the "Projects with Books" initiative at zenOSmosis.

The source code for this project is available on GitHub.

SqlEngine API

Loading…

SqlEngine API

Relevant source files

This document describes the SqlEngine struct, which is the primary user-facing interface for executing SQL statements in LLKV. The SqlEngine parses SQL text using sqlparser-rs, converts it into execution plans, and delegates to the RuntimeEngine for execution. For details on how SQL syntax is normalized before parsing, see SQL Preprocessing and Dialect Handling. For information about the INSERT batching optimization, see INSERT Buffering System.

Sources: llkv-sql/src/sql_engine.rs:441-486 llkv-sql/src/lib.rs:1-52


SqlEngine Struct and Core Components

The SqlEngine struct wraps a RuntimeEngine and adds SQL parsing, statement caching, INSERT buffering, and dialect preprocessing capabilities.

Diagram: SqlEngine component structure and user interaction

graph TB
    SqlEngine["SqlEngine"]
RuntimeEngine["RuntimeEngine"]
RuntimeContext["RuntimeContext<Pager>"]
InsertBuffer["InsertBuffer"]
StatementCache["Statement Cache"]
InfoSchema["Information Schema"]
SqlEngine -->|contains| RuntimeEngine
 
   SqlEngine -->|maintains| InsertBuffer
 
   SqlEngine -->|maintains| StatementCache
 
   SqlEngine -->|tracks| InfoSchema
 
   RuntimeEngine -->|contains| RuntimeContext
 
   RuntimeContext -->|manages| Tables["Tables"]
RuntimeContext -->|manages| Catalog["System Catalog"]
User["User Code"] -->|execute sql| SqlEngine
 
   User -->|prepare sql| SqlEngine
 
   User -->|sql query| SqlEngine
 
   SqlEngine -->|execute_statement| RuntimeEngine
 
   RuntimeEngine -->|Result| SqlEngine
 
   SqlEngine -->|RecordBatch[]| User

Sources: llkv-sql/src/sql_engine.rs:572-620

SqlEngine Fields

FieldTypePurpose
engineRuntimeEngineCore execution engine that manages tables, catalog, and transactions
default_nulls_firstAtomicBoolControls default sort order for NULL values in ORDER BY clauses
insert_bufferRefCell<Option<InsertBuffer>>Accumulates literal INSERT statements for batch execution
insert_buffering_enabledAtomicBoolFeature flag controlling cross-statement INSERT batching
information_schema_readyAtomicBoolTracks whether the information_schema has been initialized
statement_cacheRwLock<FxHashMap<String, Arc<PreparedPlan>>>Cache for prepared statement plans

Sources: llkv-sql/src/sql_engine.rs:572-586


Construction and Configuration

Creating a SqlEngine

Diagram: SqlEngine construction paths

Sources: llkv-sql/src/sql_engine.rs:751-757 llkv-sql/src/sql_engine.rs:627-633

Constructor Methods

MethodSignatureDescription
new<Pg>(pager: Arc<Pg>)Creates engine with new RuntimeContextStandard constructor for fresh database instances
with_context(context, default_nulls_first)Creates engine from existing contextUsed when sharing storage across engine instances

Configuration Options:

  • default_nulls_first : Controls whether NULL values sort first (true) or last (false) in ORDER BY clauses when NULLS FIRST/LAST is not explicitly specified
  • INSERT buffering : Disabled by default; enable via set_insert_buffering(true) for bulk loading workloads

Sources: llkv-sql/src/sql_engine.rs:751-757 llkv-sql/src/sql_engine.rs:627-633 llkv-sql/src/sql_engine.rs:1431-1449


Statement Execution

Primary Execution Methods

Diagram: Statement execution flow through SqlEngine

Sources: llkv-sql/src/sql_engine.rs:1109-1194 llkv-sql/src/sql_engine.rs:1196-1236 llkv-sql/src/sql_engine.rs:1715-1765

Execution Method Details

MethodReturn TypeUse Case
execute(&self, sql: &str)Vec<RuntimeStatementResult>Execute one or more SQL statements (DDL, DML, SELECT)
sql(&self, query: &str)Vec<RecordBatch>Execute a single SELECT query and return Arrow results
execute_single(&self, sql: &str)RuntimeStatementResultExecute exactly one statement, error if multiple
prepare(&self, sql: &str)PreparedStatementParse and cache a parameterized statement
execute_prepared(&self, stmt, params)Vec<RuntimeStatementResult>Execute a prepared statement with bound parameters

Sources: llkv-sql/src/sql_engine.rs:1109-1194 llkv-sql/src/sql_engine.rs:1196-1236 llkv-sql/src/sql_engine.rs:1238-1272 llkv-sql/src/sql_engine.rs:1715-1765 llkv-sql/src/sql_engine.rs:1767-1816

RuntimeStatementResult Variants

The RuntimeStatementResult enum represents the outcome of executing different statement types:

VariantFieldsProduced By
CreateTabletable_name: StringCREATE TABLE
CreateViewview_name: StringCREATE VIEW
CreateIndexindex_name: String, table_name: StringCREATE INDEX
DropTabletable_name: StringDROP TABLE
DropViewview_name: StringDROP VIEW
DropIndexindex_name: StringDROP INDEX
Inserttable_name: String, rows_inserted: usizeINSERT
Updatetable_name: String, rows_updated: usizeUPDATE
Deletetable_name: String, rows_deleted: usizeDELETE
Selectexecution: SelectExecution<P>SELECT (when using execute())
BeginTransactionkind: TransactionKindBEGIN TRANSACTION
CommitTransactionCOMMIT
RollbackTransactionROLLBACK
AlterTabletable_name: StringALTER TABLE
Truncatetable_name: StringTRUNCATE
Reindexindex_name: Option<String>REINDEX

Sources: llkv-runtime/src/lib.rs (inferred from context), llkv-sql/src/lib.rs50


Prepared Statements and Parameter Binding

Parameter Placeholder Syntax

LLKV supports multiple parameter placeholder styles for prepared statements:

StyleExampleDescription
Positional (?)SELECT * FROM t WHERE id = ?Auto-numbered starting from 1
Numbered (?)SELECT * FROM t WHERE id = ?2Explicit position
Numbered ($)SELECT * FROM t WHERE id = $1PostgreSQL style
Named (:)SELECT * FROM t WHERE name = :usernameNamed parameters

Sources: llkv-sql/src/sql_engine.rs:94-133 llkv-sql/src/sql_engine.rs:258-268

Parameter Binding Flow

Diagram: Prepared statement lifecycle with parameter binding

Sources: llkv-sql/src/sql_engine.rs:1715-1765 llkv-sql/src/sql_engine.rs:1767-1816 llkv-sql/src/sql_engine.rs:223-256

SqlParamValue Types

The SqlParamValue enum represents typed parameter values:

Conversion methods:

  • as_literal(&self) -> Literal - Converts to internal Literal type for expression evaluation
  • as_plan_value(&self) -> PlanValue - Converts to PlanValue for INSERT operations

Sources: llkv-sql/src/sql_engine.rs:285-316


graph TB
    subgraph "Statement Processing"
        Parse["parse_statement()"]
Canonical["canonicalize_insert()"]
PreparedInsert["PreparedInsert"]
end
    
    subgraph "Buffer Management"
        BufferDecision{"Can Buffer?"}
BufferAdd["buffer.push_statement()"]
BufferFlush["flush_buffer()"]
BufferCheck{"buffer.should_flush()?"}
end
    
    subgraph "Execution"
        BuildPlan["InsertPlan::from_buffer"]
Execute["execute_insert_plan()"]
end
    
 
   Parse -->|INSERT statement| Canonical
 
   Canonical -->|VALUES only| PreparedInsert
 
   PreparedInsert --> BufferDecision
    
 
   BufferDecision -->|Yes: same table, same columns, same on_conflict| BufferAdd
 
   BufferDecision -->|No: different params| BufferFlush
    
 
   BufferAdd --> BufferCheck
 
   BufferCheck -->|Yes: >= 8192 rows| BufferFlush
 
   BufferCheck -->|No: continue| Continue["Continue processing"]
BufferFlush --> BuildPlan
 
   BuildPlan --> Execute
 
   Execute -->|Multiple Insert results| Results["Vec<RuntimeStatementResult>"]

INSERT Buffering System

The INSERT buffering system batches multiple literal INSERT ... VALUES statements together to reduce planning overhead during bulk loading operations.

Buffering Architecture

Diagram: INSERT buffering decision tree and execution flow

Sources: llkv-sql/src/sql_engine.rs:486-546 llkv-sql/src/sql_engine.rs:1869-2008

InsertBuffer Configuration

Constant/FieldValue/TypePurpose
MAX_BUFFERED_INSERT_ROWS8192Maximum rows to accumulate before forcing a flush
insert_buffering_enabledAtomicBoolGlobal enable/disable flag (default: false)
table_nameStringTarget table for buffered inserts
columnsVec<String>Column list (must match across statements)
on_conflictInsertConflictActionConflict resolution strategy (must match)
statement_row_countsVec<usize>Tracks row counts per original statement
rowsVec<Vec<PlanValue>>Accumulated literal rows

Buffer acceptance rules:

  1. Table name must match exactly
  2. Column list must match exactly
  3. on_conflict action must match
  4. Only literal VALUES are buffered (subqueries flush immediately)

Flush triggers:

  1. Row count reaches MAX_BUFFERED_INSERT_ROWS
  2. Next INSERT targets different table/columns/conflict action
  3. Non-INSERT statement encountered
  4. execute() completes (flush remaining)
  5. SqlEngine is dropped

Sources: llkv-sql/src/sql_engine.rs:486-546 llkv-sql/src/sql_engine.rs:1431-1449


graph LR
    Input["Raw SQL String"]
TPCH["preprocess_tpch_connect_syntax"]
CreateType["preprocess_create_type_syntax"]
Exclude["preprocess_exclude_syntax"]
TrailingComma["preprocess_trailing_commas_in_values"]
EmptyIn["preprocess_empty_in_lists"]
IndexHints["preprocess_index_hints"]
Reindex["preprocess_reindex_syntax"]
TriggerShorthand["preprocess_sqlite_trigger_shorthand"]
BareTable["preprocess_bare_table_in_clauses"]
Parser["sqlparser::Parser"]
AST["Vec<Statement>"]
Input --> TPCH
 
   TPCH --> CreateType
 
   CreateType --> Exclude
 
   Exclude --> TrailingComma
 
   TrailingComma --> EmptyIn
 
   EmptyIn --> IndexHints
 
   IndexHints --> Reindex
 
   Reindex --> TriggerShorthand
 
   TriggerShorthand --> BareTable
 
   BareTable --> Parser
 
   Parser --> AST

SQL Preprocessing

Before parsing with sqlparser, the SQL text undergoes multiple normalization passes to handle dialect-specific syntax variations. This allows LLKV to accept SQL from SQLite, DuckDB, PostgreSQL, and other sources.

Preprocessing Pipeline

Diagram: SQL preprocessing transformation pipeline

Sources: llkv-sql/src/sql_engine.rs:1024-1103

Preprocessing Transformations

MethodTransformationExample
preprocess_tpch_connect_syntaxStrip CONNECT TO <db>;CONNECT TO tpch;(removed)
preprocess_create_type_syntaxCREATE TYPECREATE DOMAINCREATE TYPE myint AS INTEGERCREATE DOMAIN myint AS INTEGER
preprocess_exclude_syntaxQuote qualified EXCLUDE identifiersEXCLUDE (schema.table.col)EXCLUDE ("schema.table.col")
preprocess_trailing_commas_in_valuesRemove trailing commasVALUES (1, 2,)VALUES (1, 2)
preprocess_empty_in_listsConvert empty IN to boolean exprcol IN ()(col = NULL AND 0 = 1)
preprocess_index_hintsStrip SQLite index hintsFROM t INDEXED BY idxFROM t
preprocess_reindex_syntaxConvert to VACUUM REINDEXREINDEX idxVACUUM REINDEX idx
preprocess_sqlite_trigger_shorthandAdd explicit timing/FOR EACH ROWCREATE TRIGGER t INSERT ON tCREATE TRIGGER t AFTER INSERT ON t FOR EACH ROW
preprocess_bare_table_in_clausesWrap table in subquerycol IN tablenamecol IN (SELECT * FROM tablename)

Sources: llkv-sql/src/sql_engine.rs:759-1006


Session and Transaction Management

Accessing the RuntimeSession

The SqlEngine::session() method provides access to the underlying RuntimeSession, which manages transaction state and constraint enforcement:

Key session methods:

  • begin_transaction(kind) - Start a new transaction
  • commit_transaction() - Commit the current transaction
  • rollback_transaction() - Abort the current transaction
  • set_constraint_enforcement_mode(mode) - Control when constraints are checked
  • execute_insert_plan(plan) - Direct plan execution (bypasses SQL parsing)
  • execute_select_plan(plan) - Direct SELECT execution

Sources: llkv-sql/src/sql_engine.rs:1412-1424

Constraint Enforcement Modes

The session supports two constraint enforcement modes:

ModeBehavior
ConstraintEnforcementMode::ImmediateConstraints checked after each statement
ConstraintEnforcementMode::DeferredConstraints checked only at transaction commit

Deferred mode is useful for bulk loading when referential integrity violations may occur temporarily during the load process.

Sources: llkv-table/src/catalog/manager.rs (inferred from TpchToolkit usage), llkv-tpch/src/lib.rs:274-278


stateDiagram-v2
    [*] --> Uninitialized: SqlEngine::new()
    Uninitialized --> Checking : Query references information_schema
    Checking --> Initializing : ensure_information_schema_ready()
    Initializing --> Ready : refresh_information_schema()
    Ready --> Ready : Subsequent queries
    Ready --> Invalidated : DDL statement (CREATE/DROP TABLE, etc.)
    Invalidated --> Checking : Next information_schema query
    Checking --> Ready : Already initialized

Information Schema

The SqlEngine lazily initializes the information_schema tables on first access to avoid startup overhead. Querying information_schema.tables, information_schema.columns, or other information schema views triggers initialization.

Information Schema Lifecycle

Diagram: Information schema initialization and invalidation lifecycle

Key methods:

  • ensure_information_schema_ready() - Initialize if not already ready
  • invalidate_information_schema() - Mark as stale after schema changes
  • refresh_information_schema() - Delegate to RuntimeEngine for rebuild

Invalidation triggers:

  • CREATE TABLE, DROP TABLE
  • ALTER TABLE
  • CREATE VIEW, DROP VIEW

Sources: llkv-sql/src/sql_engine.rs:648-660 llkv-sql/src/sql_engine.rs:706-745


graph LR
    Execute["execute_plan_statement"]
RuntimeEngine["RuntimeEngine::execute_statement"]
Error["Error::NotFound or\nInvalidArgumentError"]
MapError["map_table_error"]
UserError["Error::CatalogError:\n'Table does not exist'"]
Execute -->|delegates| RuntimeEngine
 
   RuntimeEngine -->|returns| Error
 
   Execute -->|table name available| MapError
 
   MapError -->|transforms| UserError
 
   UserError -->|propagates| User["User Code"]

Error Handling and Diagnostics

Error Mapping

The SqlEngine transforms generic storage errors into user-friendly SQL errors, particularly for table lookup failures:

Diagram: Error mapping for table lookup failures

Error transformation rules:

  1. Error::NotFoundError::CatalogError("Table '...' does not exist")
  2. Generic “unknown table” messages → Catalog error with table name
  3. View-related operations (CREATE VIEW, DROP VIEW) skip mapping

Sources: llkv-sql/src/sql_engine.rs:677-745

Statement Expectations (Test Harness)

For test infrastructure, the SqlEngine supports registering expected outcomes for statements:

Expectation types:

  • StatementExpectation::Ok - Statement should succeed
  • StatementExpectation::Error - Statement should fail
  • StatementExpectation::Count(n) - Statement should affect n rows

Sources: llkv-sql/src/sql_engine.rs:65-391


Advanced Usage Examples

Bulk Loading with INSERT Buffering

Sources: llkv-sql/src/sql_engine.rs:1431-1449 llkv-sql/src/sql_engine.rs:2010-2027

Prepared Statements with Named Parameters

Sources: llkv-sql/src/sql_engine.rs:1715-1816

Direct Plan Execution (Advanced)

For performance-critical code paths, bypass SQL parsing by executing plans directly:

Sources: llkv-sql/src/sql_engine.rs:1412-1424 llkv-runtime/src/lib.rs (InsertPlan structure inferred)


Key Constants

ConstantValuePurpose
PARSER_RECURSION_LIMIT200Maximum recursion depth for sqlparser (prevents stack overflow)
MAX_BUFFERED_INSERT_ROWS8192Flush threshold for INSERT buffering
PARAM_SENTINEL_PREFIX"__llkv_param__"Internal marker for parameter placeholders
PARAM_SENTINEL_SUFFIX"__"Internal marker suffix
DROPPED_TABLE_TRANSACTION_ERR"another transaction has dropped this table"Error message for concurrent table drops

Sources: llkv-sql/src/sql_engine.rs:78-588

Dismiss

Refresh this wiki

Enter email to refresh