pub struct Engine { /* private fields */ }Expand description
Owns a running HyperProcess and the single Connection to its workspace
.hyper file. All SQL execution flows through this struct.
Two workspace modes are supported:
- Persistent — caller supplies a path; the
.hyperfile survives across sessions so tables can be built up incrementally. - Ephemeral — a temp directory is created per process; everything is discarded when the server exits.
Implementations§
Source§impl Engine
impl Engine
Sourcepub fn new(workspace_path: Option<String>) -> Result<Self, McpError>
pub fn new(workspace_path: Option<String>) -> Result<Self, McpError>
Create a new Engine. If workspace_path is Some, use that path (persistent mode).
If None, use a temp file (ephemeral mode).
Logs from hyperd are written to the directory returned by
resolve_log_dir. The same directory should be used by the MCP
binary for its own client-side log so operators can find everything
in one place when debugging.
§Errors
- Returns
ErrorCode::PermissionDeniedif the workspace parent directory or the log directory cannot be created. - Returns
ErrorCode::InternalErrorif the ephemeral temp directory cannot be created, if thepublicschema bootstrap fails, or if the initial connection tohyperdfails. - Returns
ErrorCode::HyperdNotFoundwhenHyperProcess::newreports thehyperdexecutable is missing or unreachable viaHYPERD_PATH.
Sourcepub fn is_running(&self) -> bool
pub fn is_running(&self) -> bool
Whether the hyperd child process is still alive.
Sourcepub fn hyperd_endpoint(&self) -> Result<String, McpError>
pub fn hyperd_endpoint(&self) -> Result<String, McpError>
host:port endpoint of the hyperd child process. Used by the
watcher to build additional async connections via hyperdb_api::pool
without touching the primary sync connection this engine holds.
§Errors
Returns ErrorCode::InternalError if the underlying
HyperProcess::require_endpoint call fails — typically when
hyperd has exited or never successfully reported an endpoint.
Sourcepub fn workspace_path(&self) -> &Path
pub fn workspace_path(&self) -> &Path
Absolute path to the .hyper workspace file on disk.
Sourcepub fn primary_db_name(&self) -> String
pub fn primary_db_name(&self) -> String
Unqualified database name Hyper uses for the primary workspace —
the stem of Self::workspace_path. Matches what
hyperdb_api::Connection::new registers when it issues its implicit
ATTACH DATABASE, so fully-qualified SQL built with this value
resolves to the primary workspace.
Also the correct value for SET schema_search_path = '…' while
additional databases are attached: Hyper’s default search path
("$single") only covers the implicit primary when no other
databases are attached, and starts resolving unqualified names to
nothing the moment an ATTACH DATABASE runs.
Sourcepub fn log_dir(&self) -> &Path
pub fn log_dir(&self) -> &Path
Directory where hyperd writes its log files. The MCP binary should
also drop its own client-side log here so debugging starts in one
place.
Sourcepub fn hyperd_log_path(&self) -> Option<PathBuf>
pub fn hyperd_log_path(&self) -> Option<PathBuf>
Best-guess path to the most recent hyperd log file, useful when
something in the engine misbehaves and we want to surface the server
log to the caller. Picks the newest hyperd*.log file in log_dir.
Returns None if no matching file exists yet.
Sourcepub fn is_persistent(&self) -> bool
pub fn is_persistent(&self) -> bool
true if the workspace was created from a user-supplied path
(data survives across sessions).
Sourcepub fn connection(&self) -> &Connection
pub fn connection(&self) -> &Connection
Direct access to the underlying connection for operations not
wrapped by Engine (e.g. export_csv, execute_query_to_arrow).
Sourcepub fn execute_command(&self, sql: &str) -> Result<u64, McpError>
pub fn execute_command(&self, sql: &str) -> Result<u64, McpError>
Execute a DDL/DML command. Returns affected row count.
§Errors
Converts any hyperdb_api::Error from the underlying connection
into an McpError — typical causes are SQL syntax errors,
constraint violations, permission failures, or
ErrorCode::ConnectionLost when the link to hyperd has
dropped.
Sourcepub fn execute_in_transaction<F, T>(&self, f: F) -> Result<T, McpError>
pub fn execute_in_transaction<F, T>(&self, f: F) -> Result<T, McpError>
Run the given closure inside a database transaction.
Issues BEGIN TRANSACTION before calling f. If f returns Ok,
commits the transaction; if it returns Err, rolls back and returns
the original error. A failed rollback is logged via tracing::warn!
and the original error is still surfaced (rollback failure usually
means the transaction was already aborted by the server, which is
functionally equivalent to a successful rollback).
This is the correctness primitive for ingest operations: it lets
per-row INSERT loops (Parquet, Arrow, JSON) leave zero partial data
on failure. The CSV COPY FROM path is already atomic at the
statement level, but wrapping it in a transaction costs nothing and
makes per-row INSERT loops atomic across the whole batch.
§DDL is auto-committed
Hyper treats DROP TABLE and CREATE TABLE as auto-committed even
when issued inside a transaction. This means replace-mode ingest
cannot roll back the original table once DDL has run. The guarantee
is weaker than it looks: on failure, the new (empty) table stays
in place rather than being replaced by partial data. Append-mode
ingest is fully atomic because it doesn’t issue DDL on existing
tables.
§Known wire protocol quirk
After a mid-transaction Hyper-level error (e.g. a NOT NULL violation
on INSERT), the first SELECT after rollback may return an empty
result set due to residual bytes on the connection. Retrying the
query once restores normal behavior. The rollback itself is always
correct — this is a read-side symptom only. See the query_resilient
helper in tests/transaction_tests.rs for a robust pattern.
§Errors
- Returns any
McpErrorraised byBEGIN TRANSACTIONor byCOMMIT(typical causes: connection loss, serialization conflict, DDL auto-commit contention). - Returns whatever error
fproduces (rollback is performed first; a rollback failure is only logged, never surfaced).
§Panics
Does not introduce new panic sites. If f panics, the transaction
is rolled back (best-effort) and the original panic is re-raised
via std::panic::resume_unwind, preserving the panic payload.
Sourcepub fn execute_query_to_json(&self, sql: &str) -> Result<Vec<Value>, McpError>
pub fn execute_query_to_json(&self, sql: &str) -> Result<Vec<Value>, McpError>
Execute a SELECT query and materialize all result rows as a JSON array
of {column_name: value} objects.
Results are consumed chunk-by-chunk to avoid holding the entire result
set in protocol buffers, though the final Vec<Value> does accumulate
in memory. For truly huge results, prefer export to a file instead.
§Errors
Returns any McpError produced by Connection::execute_query
or subsequent next_chunk calls — SQL errors, connection loss,
and decoding failures all surface through this path.
Sourcepub fn create_table(
&self,
table_name: &str,
columns: &[ColumnSchema],
replace: bool,
) -> Result<(), McpError>
pub fn create_table( &self, table_name: &str, columns: &[ColumnSchema], replace: bool, ) -> Result<(), McpError>
Create a table from a schema definition.
replace = true: drops the existing table (if any) and recreates it. Old rows are lost. Schema is defined bycolumns.replace = false(append mode): creates the table only if it doesn’t already exist. If it does exist, the schema defined here is ignored and subsequent inserts must match the existing schema.
Uses CREATE TABLE IF NOT EXISTS / DROP TABLE IF EXISTS so the
operation is idempotent without needing a separate has_table probe.
This is important for the watcher path, where a racy has_table check
(false negative due to protocol desync) would otherwise attempt a bare
CREATE TABLE that fails with “42P07 table already exists” and leaves
the connection in an aborted state.
§Errors
- Returns
ErrorCode::EmptyDataifcolumnsis empty. - Returns
ErrorCode::SchemaMismatchif any column’shyper_typecannot be resolved bycrate::schema::map_hyper_type. - Propagates any Hyper error from
DROP TABLE(whenreplaceis true) orCREATE TABLE IF NOT EXISTS.
Sourcepub fn column_metadata(
&self,
table: &str,
) -> Result<Vec<ColumnSchema>, McpError>
pub fn column_metadata( &self, table: &str, ) -> Result<Vec<ColumnSchema>, McpError>
Returns (name, hyper_type, nullable) for every column of table,
in declaration order, by reading the catalog (the same path
describe_table uses). Used by the merge ingest path to
compare incoming-file schema against the existing table.
§Errors
- Propagates
Catalog::get_table_definitionerrors. Callers that need a “table missing” sentinel should pre-check viaCatalog::get_table_names("public")(seedescribe_tablefor the precedent) —get_table_definitionerrors with a variable wording across Hyper versions.
Sourcepub fn table_exists(&self, table: &str) -> Result<bool, McpError>
pub fn table_exists(&self, table: &str) -> Result<bool, McpError>
Returns true if table exists in the public schema. Avoids
the per-version error-string ambiguity of
Catalog::get_table_definition by listing names instead.
§Errors
Propagates errors from Catalog::get_table_names (typically
connection loss).
Sourcepub fn alter_table_add_columns(
&self,
table: &str,
cols: &[ColumnSchema],
) -> Result<(), McpError>
pub fn alter_table_add_columns( &self, table: &str, cols: &[ColumnSchema], ) -> Result<(), McpError>
Issue a single ALTER TABLE "<table>" ADD COLUMN "<n1>" <t1>, ADD COLUMN "<n2>" <t2>, … statement that adds all columns
atomically. Hyper supports the multi-column form (verified
2026-05-07 against the pinned hyperd release), so partial-add
failures don’t leave the schema half-widened.
New columns are always added nullable — existing rows have no
value to satisfy NOT NULL. nullable on the input is ignored
for that reason.
cols must be non-empty; an empty input is a no-op (returns
Ok(()) without issuing SQL) so callers can pass the
“columns missing from target” set directly without a length
pre-check.
§Errors
- Returns
ErrorCode::SchemaMismatchif any element’shyper_typeis not a known Hyper type (same validation ascreate_table). - Propagates the underlying SQL error from the single ALTER statement. Because Hyper executes a multi-column ADD atomically, a failure leaves the table schema unchanged — no partial widening.
Sourcepub fn describe_tables(&self) -> Result<Vec<Value>, McpError>
pub fn describe_tables(&self) -> Result<Vec<Value>, McpError>
List all tables in the public schema with their column definitions
and row counts. Returned as a JSON-serializable Vec for direct use
in MCP tool responses.
§Errors
- Propagates any error from
Catalog::get_table_names(typically connection loss or SQL errors from the underlying catalog probe). - Propagates any error from
describe_table_with_catalogfor individual tables — a single failing describe aborts the whole listing.
Sourcepub fn describe_table(&self, table_name: &str) -> Result<Value, McpError>
pub fn describe_table(&self, table_name: &str) -> Result<Value, McpError>
Describe a single table by name. Returns the same JSON shape as an
element of Self::describe_tables (name, columns, row_count).
Errors with ErrorCode::TableNotFound when the table doesn’t exist
or is an internal _hyperdb_* bookkeeping table (callers should not
be able to probe infrastructure via this path; it stays consistent
with the full-list variant that hides them).
Uses get_table_names("public") as the authoritative existence check
rather than pattern-matching the error string from
get_table_definition, because the latter’s wording varies across
Hyper versions and can slip past translate_table_missing.
§Errors
- Returns
ErrorCode::TableNotFoundiftable_nameis an internal_hyperdb_*table or does not appear inpublic. - Propagates any error from
Catalog::get_table_namesor fromdescribe_table_with_catalog(connection loss, catalog probe failures).
Sourcepub fn sample_table(&self, table_name: &str, n: u64) -> Result<Value, McpError>
pub fn sample_table(&self, table_name: &str, n: u64) -> Result<Value, McpError>
Sample rows from a table along with its schema and total row count.
Returns a single JSON object with table, row_count, sample_size,
schema, and rows. n is clamped to the range 1..=100.
Returns ErrorCode::TableNotFound if the table doesn’t exist.
Avoids the Catalog::has_table probe entirely — we just run the sample
SELECT first and translate a Hyper “table does not exist” error into
our own ErrorCode::TableNotFound. This sidesteps the old pattern
where a racy has_table silently returning Err would be rewritten
to false and surface as a spurious TableNotFound for tables that
actually exist.
§Errors
- Returns
ErrorCode::TableNotFound(viatranslate_table_missing) if the sampleSELECTsurfaces a Hyper “table does not exist” error. - Propagates any other
McpErrorfrom the sample query — SQL errors, permission failures, or connection loss. - The subsequent
COUNT(*)andget_table_definitioncalls are best-effort: their errors are swallowed so the sample payload is still returned when available.
Sourcepub fn status(&self) -> Result<Value, McpError>
pub fn status(&self) -> Result<Value, McpError>
Collect workspace health and size metrics for the status MCP tool.
Includes logs with paths to the hyperd log file (if one exists yet)
and the MCP client log. These are the first files to check when
something misbehaves.
§Errors
Propagates any error from Catalog::get_table_names. Per-table
row counts and disk usage fall back to 0 on read failure, so
these do not bubble up.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Engine
impl !RefUnwindSafe for Engine
impl Send for Engine
impl Sync for Engine
impl Unpin for Engine
impl UnsafeUnpin for Engine
impl !UnwindSafe for Engine
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request