///|
/// Runtime backend capability model (see issue #65).
///
/// `Backend` names the concrete engine a connection runs on, and
/// `BackendCapabilities` records which features that engine supports.
/// The same table drives `DuckDBError::Unsupported` for gated operations and
/// the generated README support matrix (`support_matrix_markdown`).
// ============================================================================
// Backend identity
// ============================================================================
///|
/// A concrete backend a `Connection` runs on. Unlike `JsBackend` this has no
/// `Auto` selector — every variant names a real engine.
pub(all) enum Backend {
/// Native target, linked against `libduckdb`.
Native
/// MoonBit JS target using `@duckdb/node-api`.
Node
/// MoonBit JS target using `@duckdb/duckdb-wasm` in the browser.
Wasm
/// A MoonBit target with no working binding (wasm/wasm-gc stubs).
Unsupported
} derive(Eq, Compare, Show)
///|
/// All concrete backends, in display order.
pub fn Backend::all() -> Array[Backend] {
[Native, Node, Wasm, Unsupported]
}
///|
/// Short stable name used in `DuckDBError::Unsupported(backend~)` and
/// generated documentation.
pub fn Backend::name(self : Backend) -> String {
match self {
Native => "native"
Node => "node"
Wasm => "wasm"
Unsupported => "unsupported target"
}
}
///|
/// The capabilities this backend provides.
pub fn Backend::capabilities(self : Backend) -> BackendCapabilities {
match self {
Native =>
{
backend: Native,
connect: true,
prepared_statements: true,
streaming: true,
appender: true,
appender_chunk: true,
appender_date_timestamp: true,
arrow: true,
persistent_storage: true,
quack: true,
decimal_bind: true,
decimal_append: true,
interval_bind: true,
interval_append: true,
blob_bind: true,
blob_append: true,
nested_bind: true,
nested_append: true,
nested_typed: false,
}
Node =>
{
backend: Node,
connect: true,
prepared_statements: true,
streaming: true,
appender: true,
appender_chunk: true,
appender_date_timestamp: false,
arrow: true,
persistent_storage: true,
quack: true,
decimal_bind: true,
decimal_append: true,
interval_bind: true,
interval_append: true,
blob_bind: true,
blob_append: true,
nested_bind: true,
nested_append: false,
nested_typed: false,
}
Wasm =>
{
backend: Wasm,
connect: true,
prepared_statements: true,
streaming: true,
appender: false,
appender_chunk: false,
appender_date_timestamp: false,
arrow: true,
persistent_storage: false,
quack: false,
decimal_bind: true,
decimal_append: false,
interval_bind: true,
interval_append: false,
blob_bind: false,
blob_append: false,
nested_bind: false,
nested_append: false,
nested_typed: false,
}
Unsupported =>
{
backend: Unsupported,
connect: false,
prepared_statements: false,
streaming: false,
appender: false,
appender_chunk: false,
appender_date_timestamp: false,
arrow: false,
persistent_storage: false,
quack: false,
decimal_bind: false,
decimal_append: false,
interval_bind: false,
interval_append: false,
blob_bind: false,
blob_append: false,
nested_bind: false,
nested_append: false,
nested_typed: false,
}
}
}
// ============================================================================
// Backend capabilities
// ============================================================================
///|
/// A single capability flag, for feature checks that need a stable name
/// (error reporting, conformance tests, documentation generation).
/// List/Struct/Map prepared binds share the `nested_bind` capability but keep
/// distinct feature variants so error messages stay specific.
pub(all) enum BackendFeature {
/// `connect` can open a connection at all.
Connect
/// Prepared statements.
PreparedStatements
/// `query_stream` / `ResultStream` chunked reads.
Streaming
/// `Connection::create_appender` bulk loading.
Appender
/// `Appender::append_chunk` vectorized data-chunk ingestion.
AppenderChunk
/// `Appender::append_date` / `Appender::append_timestamp` helpers.
AppenderDateTimestamp
/// Arrow query results (`query_arrow`).
Arrow
/// File-backed databases (any path other than `:memory:`).
PersistentStorage
/// Quack extension helpers (the `f4ah6o/duckdb/quack` package).
Quack
/// Decimal prepared-statement binds (WASM via string params + SQL cast).
DecimalBind
/// Decimal appender appends.
DecimalAppend
/// Interval prepared-statement binds (WASM via string params + SQL cast).
IntervalBind
/// Interval appender appends.
IntervalAppend
/// Blob prepared-statement binds.
BlobBind
/// Blob appender appends.
BlobAppend
/// Direct LIST prepared binds (VARCHAR elements).
ListBind
/// Direct STRUCT prepared binds (VARCHAR fields).
StructBind
/// Direct MAP prepared binds (VARCHAR keys/values).
MapBind
/// LIST/STRUCT/MAP appender appends (VARCHAR elements).
NestedAppend
/// Nested element types beyond VARCHAR.
NestedTyped
} derive(Eq, Compare, Show)
///|
/// All capability features, in display order.
pub fn BackendFeature::all() -> Array[BackendFeature] {
[
Connect,
PreparedStatements,
Streaming,
Appender,
AppenderChunk,
AppenderDateTimestamp,
Arrow,
PersistentStorage,
Quack,
DecimalBind,
DecimalAppend,
IntervalBind,
IntervalAppend,
BlobBind,
BlobAppend,
ListBind,
StructBind,
MapBind,
NestedAppend,
NestedTyped,
]
}
///|
/// Human-readable feature name used in `DuckDBError::Unsupported(feature~)`.
pub fn BackendFeature::name(self : BackendFeature) -> String {
match self {
Connect => "connect"
PreparedStatements => "prepared statements"
Streaming => "streaming results"
Appender => "appender"
AppenderChunk => "appender data chunk ingestion (append_chunk)"
AppenderDateTimestamp => "appender date/timestamp helpers"
Arrow => "arrow integration"
PersistentStorage => "persistent storage"
Quack => "quack extension"
DecimalBind => "decimal prepared parameters"
DecimalAppend => "decimal appender appends"
IntervalBind => "interval prepared parameters"
IntervalAppend => "interval appender appends"
BlobBind => "direct blob prepared parameters"
BlobAppend => "blob appender appends"
ListBind => "direct list prepared parameters"
StructBind => "direct struct prepared parameters"
MapBind => "direct map prepared parameters"
NestedAppend => "nested appender appends"
NestedTyped => "nested types beyond varchar"
}
}
///|
/// What a backend supports. The bool fields map 1:1 to `BackendFeature`
/// variants via `supports`; the struct is the single source of truth for the
/// README feature matrix and for structured `Unsupported` errors.
pub struct BackendCapabilities {
/// The backend these capabilities describe.
backend : Backend
/// `connect` can open a connection at all.
connect : Bool
/// Prepared statements.
prepared_statements : Bool
/// `query_stream` / `ResultStream` chunked reads.
streaming : Bool
/// `Connection::create_appender` bulk loading.
appender : Bool
/// `Appender::append_chunk` vectorized data-chunk ingestion.
appender_chunk : Bool
/// `Appender::append_date` / `Appender::append_timestamp` helpers.
appender_date_timestamp : Bool
/// Arrow query results (`query_arrow`).
arrow : Bool
/// File-backed databases (any path other than `:memory:`).
persistent_storage : Bool
/// Quack extension helpers (the `f4ah6o/duckdb/quack` package).
quack : Bool
/// Decimal prepared-statement binds (WASM via string params + SQL cast).
decimal_bind : Bool
/// Decimal appender appends.
decimal_append : Bool
/// Interval prepared-statement binds (WASM via string params + SQL cast).
interval_bind : Bool
/// Interval appender appends.
interval_append : Bool
/// Blob prepared-statement binds.
blob_bind : Bool
/// Blob appender appends.
blob_append : Bool
/// Direct LIST/STRUCT/MAP prepared binds (VARCHAR elements only).
nested_bind : Bool
/// LIST/STRUCT/MAP appender appends (VARCHAR elements only).
nested_append : Bool
/// Nested element types beyond VARCHAR.
nested_typed : Bool
}
///|
/// Whether this backend supports `feature`.
pub fn BackendCapabilities::supports(
self : BackendCapabilities,
feature : BackendFeature,
) -> Bool {
match feature {
Connect => self.connect
PreparedStatements => self.prepared_statements
Streaming => self.streaming
Appender => self.appender
AppenderChunk => self.appender_chunk
AppenderDateTimestamp => self.appender_date_timestamp
Arrow => self.arrow
PersistentStorage => self.persistent_storage
Quack => self.quack
DecimalBind => self.decimal_bind
DecimalAppend => self.decimal_append
IntervalBind => self.interval_bind
IntervalAppend => self.interval_append
BlobBind => self.blob_bind
BlobAppend => self.blob_append
ListBind | StructBind | MapBind => self.nested_bind
NestedAppend => self.nested_append
NestedTyped => self.nested_typed
}
}
///|
/// A structured `Unsupported` error for `feature` on this backend.
pub fn BackendCapabilities::unsupported_error(
self : BackendCapabilities,
feature : BackendFeature,
) -> DuckDBError {
unsupported_error(feature.name(), self.backend.name())
}
///|
/// A structured `Unsupported` error for a named operation on this backend,
/// for sites that carry a more specific feature string than `BackendFeature`
/// variants (e.g. "appender append_date").
pub fn BackendCapabilities::unsupported(
self : BackendCapabilities,
feature : String,
) -> DuckDBError {
unsupported_error(feature, self.backend.name())
}
///|
/// `Ok(())` when this backend supports `feature`, otherwise the structured
/// `Unsupported` error — used to gate operations before hitting the FFI
/// layer.
pub fn BackendCapabilities::require(
self : BackendCapabilities,
feature : BackendFeature,
) -> Result[Unit, DuckDBError] {
if self.supports(feature) {
Ok(())
} else {
Err(self.unsupported_error(feature))
}
}
// ============================================================================
// Runtime inspection
// ============================================================================
///|
/// The capabilities of the backend `self` is connected to. Runtime access to
/// the matrix: `conn.capabilities()` answers "can I use feature X here?"
/// without reaching for the docs.
pub fn Connection::capabilities(self : Connection) -> BackendCapabilities {
self.backend().capabilities()
}
///|
/// The capabilities of the backend `self` was prepared on.
pub fn PreparedStatement::capabilities(
self : PreparedStatement,
) -> BackendCapabilities {
self.backend().capabilities()
}
///|
/// The capabilities of the backend `self` appends to.
pub fn Appender::capabilities(self : Appender) -> BackendCapabilities {
self.backend().capabilities()
}
// ============================================================================
// Support matrix rendering
// ============================================================================
///|
/// Markdown cell for a supported/unsupported flag with per-cell notes.
fn matrix_cell(ok : Bool, yes : String, no : String) -> String {
if ok {
yes
} else {
no
}
}
///|
/// Summary status for the "Advanced Types" row: full support requires every
/// bind/append flag plus non-VARCHAR nested types; partial when at least one
/// advanced bind works.
fn matrix_advanced_status(caps : BackendCapabilities) -> String {
let binds = caps.decimal_bind &&
caps.interval_bind &&
caps.blob_bind &&
caps.nested_bind
let appends = !caps.appender ||
(
caps.decimal_append &&
caps.interval_append &&
caps.blob_append &&
caps.nested_append
)
if binds && appends && caps.nested_typed {
"✅"
} else if caps.decimal_bind ||
caps.interval_bind ||
caps.blob_bind ||
caps.nested_bind {
"⚠️"
} else {
"❌"
}
}
///|
/// One row of the top-level feature table.
fn matrix_feature_row(
name : String,
native : String,
node : String,
wasm : String,
) -> String {
"| \{name} | \{native} | \{node} | \{wasm} |\n"
}
///|
/// One row of the advanced types table.
fn matrix_type_row(
name : String,
native_bind : String,
native_append : String,
node_bind : String,
node_append : String,
wasm_bind : String,
) -> String {
"| \{name} | \{native_bind} | \{native_append} | \{node_bind} | \{node_append} | \{wasm_bind} |\n"
}
///|
/// Render the README "Feature Support Matrix" section from
/// `BackendCapabilities`. `src/cmd/support_matrix` prints this and
/// `scripts/support_matrix.sh` splices it between the README's
/// `support-matrix` markers; CI diffs the result so the docs cannot drift
/// from the runtime capability table.
pub fn support_matrix_markdown() -> String {
let native = Backend::Native.capabilities()
let node = Backend::Node.capabilities()
let wasm = Backend::Wasm.capabilities()
let sb = StringBuilder::new()
ignore(
sb
..write_view("| Feature | Native | JS (Node) | JS (WASM) |\n")
..write_view("|---------|--------|-----------|-----------|\n")
.write_view(
matrix_feature_row(
"Connection & Query",
matrix_cell(native.connect, "✅", "❌"),
matrix_cell(node.connect, "✅", "❌"),
matrix_cell(wasm.connect, "✅", "❌"),
),
),
)
ignore(
sb
..write_view(
matrix_feature_row(
"Prepared Statements",
matrix_cell(native.prepared_statements, "✅", "❌"),
matrix_cell(node.prepared_statements, "✅", "❌"),
matrix_cell(wasm.prepared_statements, "✅", "❌"),
),
)
..write_view(
matrix_feature_row(
"Streaming Results",
matrix_cell(native.streaming, "✅", "❌"),
matrix_cell(node.streaming, "✅", "❌"),
matrix_cell(wasm.streaming, "✅", "❌"),
),
)
..write_view(
matrix_feature_row(
"Appender",
matrix_cell(native.appender, "✅", "❌"),
matrix_cell(node.appender, "✅ (Node only)", "❌"),
matrix_cell(wasm.appender, "✅", "❌"),
),
)
..write_view(
matrix_feature_row(
"Appender DataChunk",
matrix_cell(native.appender_chunk, "✅", "❌"),
matrix_cell(node.appender_chunk, "✅ (Node only)", "❌"),
matrix_cell(wasm.appender_chunk, "✅", "❌"),
),
)
..write_view(
matrix_feature_row(
"Arrow Integration",
matrix_cell(native.arrow, "✅", "❌"),
matrix_cell(node.arrow, "✅", "❌"),
matrix_cell(wasm.arrow, "✅", "❌"),
),
)
.write_view(
matrix_feature_row(
"Advanced Types",
matrix_advanced_status(native),
matrix_advanced_status(node),
matrix_advanced_status(wasm),
),
),
)
ignore(
sb
..write_view("\n")
..write_view(
"**Legend:** ✅ Full support | ⚠️ Partial support | ❌ Not supported\n",
)
..write_view("\n### Advanced Types Detailed Support\n\n")
..write_view(
"| Type | Native Bind | Native Append | Node Bind | Node Append | WASM Bind |\n",
)
..write_view(
"|------|-------------|---------------|-----------|-------------|-----------|\n",
)
..write_view(
matrix_type_row(
"Decimal",
matrix_cell(native.decimal_bind, "✅ 128-bit", "❌"),
matrix_cell(native.decimal_append, "✅ 128-bit", "❌"),
matrix_cell(node.decimal_bind, "✅ 128-bit", "❌"),
matrix_cell(node.decimal_append, "✅ 128-bit", "❌"),
matrix_cell(
wasm.decimal_bind,
"✅ direct string param + SQL cast",
"❌ unsupported",
),
),
)
..write_view(
matrix_type_row(
"Interval",
matrix_cell(native.interval_bind, "✅", "❌"),
matrix_cell(native.interval_append, "✅", "❌"),
matrix_cell(node.interval_bind, "✅", "❌"),
matrix_cell(node.interval_append, "✅", "❌"),
matrix_cell(
wasm.interval_bind,
"✅ direct string param + SQL cast",
"❌ unsupported",
),
),
)
..write_view(
matrix_type_row(
"Blob",
matrix_cell(native.blob_bind, "✅", "❌"),
matrix_cell(native.blob_append, "✅", "❌"),
matrix_cell(node.blob_bind, "✅", "❌"),
matrix_cell(node.blob_append, "✅", "❌"),
matrix_cell(wasm.blob_bind, "✅", "❌ unsupported"),
),
)
..write_view(
matrix_type_row(
"List",
matrix_cell(native.nested_bind, "✅ VARCHAR", "❌"),
matrix_cell(native.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(node.nested_bind, "✅ VARCHAR (Node only)", "❌"),
matrix_cell(node.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(wasm.nested_bind, "✅ VARCHAR", "❌ unsupported"),
),
)
..write_view(
matrix_type_row(
"Struct",
matrix_cell(native.nested_bind, "✅ VARCHAR", "❌"),
matrix_cell(native.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(node.nested_bind, "✅ VARCHAR (Node only)", "❌"),
matrix_cell(node.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(wasm.nested_bind, "✅ VARCHAR", "❌ unsupported"),
),
)
.write_view(
matrix_type_row(
"Map",
matrix_cell(native.nested_bind, "✅ VARCHAR", "❌"),
matrix_cell(native.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(node.nested_bind, "✅ VARCHAR (Node only)", "❌"),
matrix_cell(node.nested_append, "✅ VARCHAR", "❌"),
matrix_cell(wasm.nested_bind, "✅ VARCHAR", "❌ unsupported"),
),
),
)
sb.to_string()
}