Release notes

The canonical changelog lives in CHANGELOG.md at the repository root and is mirrored here. Format follows Keep a Changelog; versions follow Semantic Versioning.

Unreleased

No unreleased changes yet.

0.9.0 - 2026-05-17

Added

  • Read-only database LOAD support. LOAD semantic_views now succeeds on a read-only DuckDB database. Previously-defined semantic views can be queried via list_semantic_views(), describe_semantic_view(), and FROM semantic_view(...) against a database opened with read_only=True (Python) or --readonly (CLI). On a read-only database that was never bootstrapped, the catalog is treated as empty rather than raising a missing-table error: list_semantic_views() returns zero rows, and describe_semantic_view('x') / FROM semantic_view('x', ...) return the standard semantic view 'x' does not exist error for any name.

  • Read-only DDL surfaces DuckDB’s standard error. CREATE SEMANTIC VIEW, DROP SEMANTIC VIEW, and ALTER SEMANTIC VIEW against a read-only database fail with DuckDB’s standard Cannot execute statement of type "..." on database "..." which is attached in read-only mode! error rather than the previous confusing schema-create failure at LOAD time.

  • examples/readonly_load.py demonstrating the open-writable → bootstrap → close → reopen-readonly → query → catch-DDL-error workflow.

  • just test-readonly recipe + test/integration/test_readonly_load.py Python integration test (three scenarios: fresh read-only file, bootstrapped reopen, DDL rejection) and test/sql/readonly_load.test writable-side smoke fixture. Wired into just test-all.

Fixed

  • Quoted identifier handling in CREATE / DROP / ALTER / DESCRIBE / SHOW COLUMNS SEMANTIC VIEW. All five DDL forms now accept any combination of quoted, partially-quoted, and unquoted fully-qualified names — e.g. CREATE OR REPLACE SEMANTIC VIEW "memory"."main"."orders_sv" AS ..., CREATE SEMANTIC VIEW main."orders_sv" AS ..., and CREATE SEMANTIC VIEW orders_sv AS ... all store the same bare key, and FROM semantic_view('orders_sv', ...) resolves them uniformly. ALTER ... RENAME TO normalises both the source name and the new-name target. Error messages reference the unquoted bare name. Previously, a quoted FQN was stored verbatim (quotes and all) as the lookup key, which made any subsequent semantic_view('orders_sv', ...) call return “view does not exist”.

  • Triple-quoted identifiers in expanded SQL. When a TABLES (o AS "memory"."main"."orders" ...) clause used a quoted source-table reference, the expansion path re-quoted each part producing strings like """memory"""."""main"""."""orders""" in the generated FROM clause. The expansion now operates on parsed identifier parts and emits exactly one pair of quotes per part regardless of input shape, restoring EXPLAIN SEMANTIC VIEW legibility and (in the rare case the source-table reference had embedded special chars) correctness of the generated SQL.

Known limitations

  • The v0.1.0 → v0.2.0 companion-file migration cannot run on a read-only database (the migration INSERTs into semantic_layer._definitions which requires write access). Practical impact is near-zero — the companion-file format is four milestone versions stale and any database last opened with v0.2.0+ has already been migrated. If you have a v0.1.0-era database that has never been opened by any newer release, open it once writable to complete the migration before reverting to read-only.

0.8.0 - 2026-05-06

Added

  • Transactional DDL. CREATE, DROP, and ALTER SEMANTIC VIEW now participate in the caller’s transaction. BEGIN ... ROLLBACK rolls back uncommitted catalog changes and BEGIN ... COMMIT persists them, matching the contract that ADBC, dbt, and other transaction-aware clients expect.

  • parser_override extension hook. Recognised DDL is rewritten into native INSERT / UPDATE / DELETE against semantic_layer._definitions and executed on the caller’s connection. Non-matching statements fall through to DuckDB’s default parser unchanged.

  • All four CREATE forms transactional: inline AS keyword body, inline FROM YAML $$ ... $$, FROM YAML FILE '<path>' (including https:// and S3 paths via httpfs), and CREATE OR REPLACE / CREATE IF NOT EXISTS variants.

  • DROP / ALTER race guards. Non-IF EXISTS DROP SEMANTIC VIEW and ALTER SEMANTIC VIEW RENAME / SET COMMENT / UNSET COMMENT now emit a snapshot-consistent existence check on the caller’s connection before the DML. If a concurrent commit lands between the catalog pre-check (committed-state read on a separate connection) and the DML, the user sees semantic view '<name>' was concurrently dropped instead of a silent no-op. IF EXISTS variants keep their silent-no-op contract.

  • CatalogReader RAII. prepared_lookup and execute_list_all use internal PreparedStmt and QueryResult guards. Manual duckdb_destroy_* calls along error paths are gone.

  • ParserOptions size assert. A static assert pins sizeof(ParserOptions) == 32 against DuckDB v1.5.2 (the upstream version pinned via the =1.10502.0 duckdb-rs crate). Silent layout drift previously surfaced as garbage parser errors at position 0; future DuckDB bumps now fail fast at compile time.

  • Actionable error when allow_parser_override_extension is DEFAULT or STRICT (e.g. after CALL disable_peg_parser() resets the setting). Issuing semantic DDL on such a connection now produces Parser Error: semantic_views: parser_override is not active for this connection (allow_parser_override_extension is 'DEFAULT' or 'STRICT'). Re-enable with: SET allow_parser_override_extension='FALLBACK'; with caret positioned at the start of the statement.

  • ADBC end-to-end test (test/integration/test_adbc_transactions.py, runnable via just test-adbc) exercising autocommit=False rollback / commit semantics for inline, FROM YAML FILE, ALTER, and DROP forms — proves the original ADBC bug is fixed end-to-end.

  • Concurrent-CREATE Python integration test (test/integration/test_concurrent_ddl.py, runnable via just test-concurrent).

  • INSERT OR REPLACE row-count, byte-identical rollback (MD5), and same-txn list_semantic_views visibility cases in v080_transactional_ddl.test.

  • Type-inference under BEGIN/COMMIT in test_type_inference.py.

  • Arbitrary-bytes FFI fuzz target (fuzz_parser_override_ffi).

  • Caret-rendering sqllogictest fixtures pinning caret alignment across CREATE / DROP / ALTER / multi-line / UTF-8 / multi-DB / extension-reload paths.

  • peg_compat.test regression coverage that the override path keeps working under DuckDB’s experimental PEG parser, so v0.8.0’s transactional DDL survives the upcoming parser switch. Under PEG, every DDL form (including DESCRIBE and SHOW) works because parser_override fires before whichever parser is active.

Changed

  • Architectural unification. parser_override is the sole DDL entry point. Every recognised form — CREATE (all four variants), DROP, ALTER, DESCRIBE, SHOW SEMANTIC *, GET_DDL, READ_YAML_FROM_SEMANTIC_VIEW — is rewritten by a single Rust dispatch and re-parsed by DuckDB on the caller’s connection. The legacy parse_function / sv_ddl_internal table-function fallback was retired (~1500 LOC net deletion). One execution path means transactional semantics, error reporting, and PEG/Bison compatibility are all uniform.

  • CatalogState HashMap removed. All catalog reads now query _definitions directly through a single shared CatalogReader. This eliminates the divergence risk between the HashMap and the on-disk table that the old write-through-both pattern carried.

Fixed

  • FFI UTF-8 hardening. sv_parser_override_rust now validates input bytes with checked from_utf8 instead of from_utf8_unchecked. Malformed input cleanly defers to the default parser instead of triggering UB.

  • parse_table_function_call tightening. The internal helper now rejects foo(,), foo('a',) (trailing comma), and foo('a' 'b') (missing comma between args). Previously these silently parsed as zero-arg or merged-arg calls.

  • Validation errors arrive as parse-time errors with caret rendering. CREATE, DROP, and ALTER SEMANTIC VIEW validation failures (e.g. semantic view 'X' does not exist, unknown clause) surface as Parser Error: ... LINE 1: ... ^ with the caret aligned to the offending token, matching DuckDB’s native parser-error rendering. Internally, parser_override keeps the success / transactional path (rewrite to native SQL, re-parse on caller’s connection); validation failures defer (DISPLAY_ORIGINAL_ERROR), the default parser fails on the unrecognised DDL prefix, and DuckDB calls parse_function, which re-runs validation and returns DISPLAY_EXTENSION_ERROR with error_location set to the offending byte offset.

Known limitations

  • semantic_view(...) queries do not see uncommitted writes to user tables in the same transaction. Expansion runs on a separate query_conn, which only sees committed state. Workaround: commit the user-table writes before querying. Inline expansion will be revisited when DuckDB 2.0’s PEG grammar-extension API ships.

  • A CREATE SEMANTIC VIEW issued in the same uncommitted transaction is not visible to subsequent reads in that transaction (e.g. SHOW SEMANTIC VIEWS will not list it until commit). With the HashMap gone, reads see only committed catalog state. Workaround: commit before reading. See TECH-DEBT item 19.

  • CALL disable_peg_parser() resets allow_parser_override_extension to default, which silently bypasses parser_override hooks. Workaround: re-issue SET allow_parser_override_extension='FALLBACK' after disabling PEG. The extension installs FALLBACK on load, so a process that never enables PEG never sees this. See TECH-DEBT item 21.

  • CREATE SEMANTIC VIEW IF NOT EXISTS is silent-no-op only against rows visible in the caller’s MVCC snapshot. Two parallel processes that each see the row absent will both attempt the INSERT and the loser sees ConstraintException: Duplicate key "name: <view>" violates primary key constraint at commit — the same shape plain CREATE produces under contention. Multi-process bootstrap scripts should catch this and treat it as success. See TECH-DEBT item 23.

0.7.2 - 2026-05-01

Fixed

  • Parser hook now strips leading SQL comments before matching CREATE / ALTER / DROP / SHOW SEMANTIC VIEW DDL. Previously, any statement preceded by a /* ... */ block comment or -- ... \n line comment was misclassified as not-our-statement and DuckDB surfaced Parser Error: syntax error at or near "SEMANTIC". This made the extension unusable through dbt-duckdb (which unconditionally prepends a query annotation comment to every statement) and any other tool that prefixes annotations (sqlfluff, BI tools that prepend session/user metadata, etc.). Reported and diagnosed by an external user. Block comments are non-nesting, matching PostgreSQL/DuckDB semantics. Error-position byte offsets are preserved across the consumed comment span, so error carets continue to reference the original query string.

0.7.1 - 2026-04-26

Added

  • DDL-time type inference for dimensions and metrics: data_type / DATA_TYPE columns in SHOW and DESCRIBE output now display inferred types (VARCHAR, BIGINT, DOUBLE, DATE, etc.) instead of empty strings

  • Type inference runs automatically at CREATE SEMANTIC VIEW time on file-backed databases via a LIMIT 0 probe query

  • Supported types: VARCHAR, BOOLEAN, integer types (TINYINT through UBIGINT), FLOAT, DOUBLE, DATE, TIME, TIMESTAMP (all variants), INTERVAL, UUID, BLOB, BIT; DECIMAL and parameterized types intentionally left empty to avoid lossy CAST

  • Derived metrics also receive inferred types when resolvable

  • In-memory databases continue to show empty data_type (no persist connection available)

0.7.0 - 2026-04-24

Added

  • YAML definition format: CREATE SEMANTIC VIEW name FROM YAML $$ ... $$ as an alternative to SQL DDL keyword body

  • YAML file loading: CREATE SEMANTIC VIEW name FROM YAML FILE '/path/to/file.yaml' with DuckDB enable_external_access security enforcement

  • Dollar-quoting for inline YAML: both untagged ($$...$$) and tagged ($yaml$...$yaml$) forms

  • YAML export: READ_YAML_FROM_SEMANTIC_VIEW('name') scalar function with lossless round-trip fidelity

  • Materialization declarations: MATERIALIZATIONS clause in SQL DDL and YAML for declaring pre-aggregated tables

  • Materialization routing engine: transparent query redirection to pre-aggregated tables on exact dimension/metric match

  • Semi-additive and window function metrics excluded from materialization routing (always expand from raw sources)

  • explain_semantic_view() now includes materialization routing decision (-- Materialization: <name> or -- Materialization: none)

  • DESCRIBE SEMANTIC VIEW includes MATERIALIZATION rows with table, dimensions, and metrics properties

  • SHOW SEMANTIC MATERIALIZATIONS [IN view_name] command with LIKE/STARTS WITH/LIMIT filtering

0.6.0 - 2026-04-14

Added

  • Metadata annotations: COMMENT, SYNONYMS (aliases), PRIVATE/PUBLIC access modifiers on views, tables, dimensions, metrics, and facts

  • ALTER SEMANTIC VIEW SET COMMENT / UNSET COMMENT DDL for modifying view-level comments after creation

  • GET_DDL(‘SEMANTIC_VIEW’, ‘name’) scalar function for reconstructing re-executable CREATE OR REPLACE DDL from stored definitions

  • SHOW TERSE SEMANTIC VIEWS for reduced-column introspection output

  • SHOW COLUMNS IN SEMANTIC VIEW for a unified list of all dims, facts, and metrics with a kind column

  • IN SCHEMA / IN DATABASE scope filtering for all SHOW SEMANTIC commands

  • Wildcard selection (table_alias.*) in dimensions and metrics query parameters, expanding to all matching PUBLIC items

  • Queryable FACTS via facts := [...] parameter in the table function for row-level unaggregated results

  • Semi-additive metrics via NON ADDITIVE BY (dimension [ASC|DESC] [NULLS FIRST|LAST]) for snapshot-style aggregation using CTE-based ROW_NUMBER

  • Window function metrics via PARTITION BY EXCLUDING for non-aggregated, partition-aware computation

  • Synonyms and comment columns in all SHOW SEMANTIC command output

  • Comment and access_modifier properties in DESCRIBE SEMANTIC VIEW output

  • Mutual exclusion: facts + metrics in same query produces a blocking error

  • Mutual exclusion: window function metrics + aggregate metrics in same query produces a blocking error

  • SHOW SEMANTIC DIMENSIONS FOR METRIC shows required=TRUE for window partition dimensions

Changed

  • FFI catch_unwind wrapping on all 25 entry points (Rust panics no longer unwind through C++ stack frames)

  • Graceful lock-poison handling across all catalog and query paths (error return instead of panic)

  • Cycle detection and MAX_DERIVATION_DEPTH=64 limit for derived metrics and facts

  • DimensionName/MetricName newtypes with case-insensitive semantics replace bare strings in query resolution

  • Resolution loop deduplication via generic resolve_names helper

0.5.5 - 2026-04-05

Added

  • Snowflake-aligned column schemas for all SHOW SEMANTIC commands (VIEWS, DIMENSIONS, METRICS, FACTS)

  • Snowflake-aligned DESCRIBE SEMANTIC VIEW property-per-row format

  • Metadata fields: created_on timestamp, database_name, schema_name on semantic view model

  • Per-fact output_type metadata

Changed

  • Refactored expand.rs into expand/ module directory (7 submodules)

  • Refactored graph.rs into graph/ module directory (5 submodules)

  • Extracted shared util.rs and errors.rs as leaf modules to break circular dependencies

0.5.4 - 2026-03-31

Added

  • UNIQUE constraints on tables in TABLES clause with automatic cardinality inference for relationships

  • Implicit PK reference resolution (REFERENCES target without column list resolves to target’s PRIMARY KEY)

  • ALTER SEMANTIC VIEW RENAME TO for renaming views

  • SHOW SEMANTIC DIMENSIONS / METRICS / FACTS introspection commands

  • LIKE, STARTS WITH, and LIMIT filtering for all SHOW SEMANTIC commands

  • Documentation site (Sphinx + Shibuya theme on GitHub Pages)

  • Community Extension Registry descriptor (description.yml)

  • MAINTAINER.md contributor documentation

Changed

  • DuckDB version support: 1.5.x (latest) + 1.4.x LTS with dual CI matrix

  • Relationship cardinality inferred from PK/UNIQUE constraints instead of explicit keywords

Removed

  • Explicit cardinality keywords on relationships (breaking: views must be recreated)

0.5.3 - 2026-03-15

Added

  • FACTS clause for named reusable row-level sub-expressions in semantic view definitions

  • Derived metrics (metric-on-metric composition with DAG resolution and cycle detection)

  • Fan trap detection with blocking errors for one-to-many aggregation fan-out

  • Role-playing dimensions (same table via multiple join paths)

  • USING RELATIONSHIPS clause for explicit join path selection in queries

  • Multi-level fact inlining with proper parenthesization for operator precedence

0.5.2 - 2026-03-13

Added

  • SQL keyword DDL body: TABLES, RELATIONSHIPS, DIMENSIONS, METRICS clauses replace function-call syntax

  • PK/FK relationship model with table aliases and graph-validated JOIN synthesis

  • Alias-based query expansion with qualified column names (direct FROM+JOIN instead of CTE flattening)

  • Parser robustness: token-based keyword matching tolerates arbitrary whitespace

  • Adversarial input hardening (null bytes, embedded semicolons, Unicode homoglyphs, control characters)

Removed

  • Function-call DDL body syntax (breaking: define_semantic_view() interface retired)

0.5.1 - 2026-03-09

Added

  • DROP SEMANTIC VIEW and DROP SEMANTIC VIEW IF EXISTS

  • CREATE OR REPLACE SEMANTIC VIEW

  • CREATE SEMANTIC VIEW IF NOT EXISTS

  • DESCRIBE SEMANTIC VIEW

  • SHOW SEMANTIC VIEWS

  • Error location reporting with character positions (caret indicators in DuckDB output)

  • Clause-level error hints and “did you mean?” fuzzy suggestions for misspelled clause/view names

  • Parser property-based tests (proptests) for DDL parsing

0.5.0 - 2026-03-08

Added

  • Native CREATE SEMANTIC VIEW DDL syntax via C++ parser extension hook

  • Parser fallback hook registration (C_STRUCT entry + C++ helper)

  • Rust FFI trampoline for detecting CREATE SEMANTIC VIEW prefix

  • Statement rewriting pipeline (native DDL to function-based execution)

  • Dedicated DDL connection to avoid lock conflicts

0.4.0 - 2026-03-03

Changed

  • Time truncation expressed via dimension expr directly (e.g., date_trunc('month', created_at))

  • DDL simplified from 6 to 4 named parameters

  • Query function simplified from 3 to 2 named parameters

Removed

  • time_dimensions DDL parameter (breaking)

  • granularities query parameter (breaking)

0.3.0 - 2026-03-03

Changed

  • Replaced binary-read dispatch with zero-copy vector references (duckdb_vector_reference_vector)

  • Streaming chunk-by-chunk output instead of collect-all-then-write

  • Type mismatches handled at SQL generation time via build_execution_sql cast wrapper

Removed

  • ~600 LOC of per-type read/write dispatch code

0.2.0 - 2026-03-03

Added

  • C++ shim infrastructure for Rust+C++ boundary (vendored DuckDB amalgamation via cc crate)

  • Time dimensions with granularity coarsening and per-query granularity override

  • pragma_query_t catalog persistence (replaced sidecar file with DuckDB-native table persistence)

  • Scalar function DDL interface (define_semantic_view())

  • Snowflake-aligned STRUCT/LIST DDL syntax

  • EXPLAIN support for expanded SQL inspection

  • Typed output columns (zero-copy vector reference with runtime type validation)

  • DuckDB type-mapping with property-based tests

  • DuckLake integration test suite and CI

Removed

  • Sidecar file persistence (replaced by pragma_query_t)

0.1.0 - 2026-02-28

Added

  • Initial extension scaffold using duckdb/extension-template-rs

  • Multi-platform CI build matrix (Linux x86_64/arm64, macOS x86_64/arm64, Windows x86_64)

  • Scheduled DuckDB version monitor with automated PR creation

  • Code quality gates: rustfmt, clippy (pedantic), cargo-deny, 80% coverage

  • Developer task runner (just) with just setup one-command dev environment

  • Pre-commit hooks via cargo-husky (rustfmt + clippy)

  • Semantic view definition storage and round-trip persistence across DuckDB restarts

  • Expansion engine: automatic GROUP BY and JOIN generation from dimension/metric declarations

  • Query interface via table function semantic_view('view', dimensions := [...], metrics := [...])

  • list_semantic_views() and describe_semantic_view() introspection functions

  • Fuzz targets for FFI boundary testing