pub struct Engine { /* private fields */ }Expand description
- The connection is bound to the ephemeral primary at
Self::ephemeral_path. Unqualified SQL routes here. - When
Self::persistent_pathisSome, the server attaches that file as"persistent"after engine construction. WhenNone, no persistent storage is available this session (--ephemeral-only).
Implementations§
Source§impl Engine
impl Engine
Sourcepub const PERSISTENT_ALIAS: &'static str = "persistent"
pub const PERSISTENT_ALIAS: &'static str = "persistent"
Reserved alias under which the persistent database is attached
when Self::persistent_path is set. Visible to the LLM via the
database parameter and via list_attached_databases.
Sourcepub fn new(persistent_db_path: Option<String>) -> Result<Self, McpError>
pub fn new(persistent_db_path: Option<String>) -> Result<Self, McpError>
Create a new Engine. The connection is bound to a fresh ephemeral
primary in a temp directory. If persistent_db_path is Some,
the path is recorded so the server can ATTACH it post-construction;
passing None means --ephemeral-only.
Connects to the shared daemon if available, falling back to a local hyperd.
§Errors
- Returns
ErrorCode::PermissionDeniedif the persistent 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 backing hyperd process is still alive.
In daemon mode, checks the daemon health port.
Sourcepub fn hyperd_endpoint(&self) -> Result<String, McpError>
pub fn hyperd_endpoint(&self) -> Result<String, McpError>
host:port endpoint of the hyperd 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 endpoint is unavailable.
Sourcepub fn ephemeral_path(&self) -> &Path
pub fn ephemeral_path(&self) -> &Path
Absolute path to the ephemeral primary .hyper file on disk.
Sourcepub fn persistent_path(&self) -> Option<&Path>
pub fn persistent_path(&self) -> Option<&Path>
Absolute path to the persistent .hyper file, or None when the
session is --ephemeral-only.
Sourcepub fn primary_db_name(&self) -> String
pub fn primary_db_name(&self) -> String
Unqualified database name Hyper uses for the ephemeral primary —
the stem of Self::ephemeral_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.
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 resolve_target_db(
&self,
requested: Option<&str>,
) -> Result<String, McpError>
pub fn resolve_target_db( &self, requested: Option<&str>, ) -> Result<String, McpError>
Resolve a tool’s optional database parameter to a concrete
alias suitable for fully-qualifying SQL. None and Some("")
mean “the primary (ephemeral)”; Some("persistent") requires the
persistent attachment exists; any other value is returned
verbatim and assumed to be a user-attached alias.
Returns the database alias to qualify against, or None to mean
“use the primary’s name”. This lets callers build qualified SQL
uniformly: format!("\"{}\".\"public\".\"{}\"", alias_or_primary, table).
§Errors
Returns ErrorCode::InvalidArgument when Some("persistent")
is passed but Self::persistent_path is None
(--ephemeral-only mode).
Sourcepub fn scoped_search_path(
&self,
alias: &str,
) -> Result<ScopedSearchPath<'_>, McpError>
pub fn scoped_search_path( &self, alias: &str, ) -> Result<ScopedSearchPath<'_>, McpError>
Temporarily redirect the schema search path to alias for the
duration of a tool call. Returns an RAII guard that restores the
search path to the primary when dropped.
The engine Mutex is held by the caller (with_engine closure),
so concurrent tool calls cannot observe the redirected path.
§Errors
Returns McpError if the SET statement fails (e.g. invalid alias
or connection lost).
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 has_persistent(&self) -> bool
pub fn has_persistent(&self) -> bool
true if a persistent database is attached to this session.
Equivalent to Self::persistent_path being Some.
Sourcepub fn persistent_was_just_created(&self) -> bool
pub fn persistent_was_just_created(&self) -> bool
true when this engine just created the persistent .hyper file
during construction. The server consumes this signal once to
decide whether to seed _table_catalog; subsequent reads stay
true (the flag isn’t reset — it’s a fact about the engine’s
startup, not a one-shot signal).
Sourcepub fn catalog_present_in<F>(
&self,
alias: &str,
prober: F,
) -> Result<bool, McpError>
pub fn catalog_present_in<F>( &self, alias: &str, prober: F, ) -> Result<bool, McpError>
Returns whether _table_catalog exists in alias, caching
the per-DB result on first call so subsequent catalog read/
write paths skip the pg_catalog.pg_tables probe.
prober is the SQL-side existence check; the cache layer here
is intentionally generic so the catalog module can keep its
probe SQL in one place.
§Errors
Propagates whatever error prober returns on the first call.
On subsequent calls, the cached value is returned without
re-running the probe.
Sourcepub fn mark_catalog_present_for(&self, alias: &str)
pub fn mark_catalog_present_for(&self, alias: &str)
Synchronously set the catalog-presence cache to true for
alias — used by table_catalog::ensure_exists_in after a
successful CREATE TABLE IF NOT EXISTS so subsequent reads/
writes against that DB skip the existence probe.
Sourcepub fn clear_catalog_cache_for(&self, alias: &str)
pub fn clear_catalog_cache_for(&self, alias: &str)
Drop the cached probe result for alias. Called by
detach_database so that re-attaching the same alias to a
different file (or with different writability) doesn’t reuse a
stale entry.
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 create_table_in(
&self,
table_name: &str,
columns: &[ColumnSchema],
replace: bool,
target_db: Option<&str>,
) -> Result<(), McpError>
pub fn create_table_in( &self, table_name: &str, columns: &[ColumnSchema], replace: bool, target_db: Option<&str>, ) -> Result<(), McpError>
Create a table, optionally in a non-primary database. When
target_db is Some, the table identifier is fully qualified as
"db"."public"."table"; when None, it’s just "table".
§Errors
Same as Self::create_table.
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 column_metadata_in(
&self,
target_db: Option<&str>,
table: &str,
) -> Result<Vec<ColumnSchema>, McpError>
pub fn column_metadata_in( &self, target_db: Option<&str>, table: &str, ) -> Result<Vec<ColumnSchema>, McpError>
Like Self::column_metadata but for a table in target_db.
None falls back to column_metadata (primary). Some(alias)
reads via the qualified pg_catalog.pg_attribute join used by
describe_columns_via_pg_catalog — the connection-bound
Catalog API can’t see attached databases.
§Errors
Returns ErrorCode::TableNotFound when no rows come back from
the qualified probe. Propagates connection errors.
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 table_exists_in(
&self,
target_db: Option<&str>,
table: &str,
) -> Result<bool, McpError>
pub fn table_exists_in( &self, target_db: Option<&str>, table: &str, ) -> Result<bool, McpError>
Like Self::table_exists but for a table in target_db.
None falls back to table_exists (primary). Some(alias)
probes the qualified pg_catalog.pg_tables of the attached
database — the connection-bound Catalog API can’t see
attached databases.
§Errors
Propagates connection errors from the probe query.
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 alter_table_add_columns_in(
&self,
target_db: Option<&str>,
table: &str,
cols: &[ColumnSchema],
) -> Result<(), McpError>
pub fn alter_table_add_columns_in( &self, target_db: Option<&str>, table: &str, cols: &[ColumnSchema], ) -> Result<(), McpError>
Like Self::alter_table_add_columns but for a table in
target_db. None keeps the unqualified identifier; Some(alias)
emits "db"."public"."table" so the ALTER lands in the attached
database.
§Errors
Same as Self::alter_table_add_columns.
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 sample_table_in(
&self,
target_db: Option<&str>,
table_name: &str,
n: u64,
) -> Result<Value, McpError>
pub fn sample_table_in( &self, target_db: Option<&str>, table_name: &str, n: u64, ) -> Result<Value, McpError>
Sample rows from a table in target_db (or the primary when None).
§Errors
Same as Self::sample_table.
Sourcepub fn describe_table_in(
&self,
target_db: Option<&str>,
table_name: &str,
) -> Result<Value, McpError>
pub fn describe_table_in( &self, target_db: Option<&str>, table_name: &str, ) -> Result<Value, McpError>
Describe a single table in target_db (or the primary when None).
§Errors
Same as Self::describe_table.
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