vantage-sql 0.6.21

Vantage extension for SQL databases (Postgres, MySQL, SQLite)
Documentation
# Changelog

## 0.6.21 — 2026-08-25

- **Observation args for `rhai:` vistas.** A query-sourced table's script can
  now read values supplied at open time through an `args` map
  (`if "lob" in args { … }`), so a caller filters *before* aggregation instead
  of wrapping a finished aggregate in an outer `WHERE`. Values the script
  embeds bind as query parameters like any other scalar. The script can also
  route between source tables on argument presence.
- **PostgreSQL vista parity with SQLite.** `PostgresTableShell` gained
  `clone_shell`, `add_search`, `clear_search`, `set_page_size`, `fetch_page`
  and `fetch_next`, and now advertises `can_search`, `can_set_page_size`,
  `can_fetch_page` and `can_fetch_next`. Postgres tables previously
  under-advertised, so consumers fell back to the slowest honest path: no
  server-side quicksearch, and no declared page size. Postgres quicksearch
  also matches case-insensitively (`ILIKE`), as SQLite's `LIKE` already did.
- **Identifiers escape embedded quotes.** `Identifier` renders `a"b` as
  `"a""b"` rather than interpolating it raw. Identifiers were code-defined by
  construction until observation args made them reachable from runtime values
  (`ident(args.col)`).
- `from_as(<select>, alias)` in the Rhai vocabulary — derived tables, for
  rank-then-regroup and top-N-plus-Other shapes.
- **Paged Postgres reads are a stable window.** `fetch_page`, `fetch_next` and
  `fetch_window` order by the id column when the caller has set no order of
  its own. `LIMIT`/`OFFSET` without `ORDER BY` lets PostgreSQL return rows in
  any order — a parallel or bitmap scan readily does — so successive pages
  could repeat one row and skip another with nothing to signal it.
- **Bug fix (data loss):** a Postgres cursor scan ended early on a table whose
  id column is not unique. Rows are keyed by id, so duplicates collapse and a
  full SQL page can arrive under-length; the scan read that as the end of the
  set and dropped every remaining page. Exhaustion is now an EMPTY page, at
  the cost of one extra round trip per scan. The source also warns when the
  collapse happens, since it silently shrinks any read.

## 0.6.20 — 2026-08-22

- **Bug fix (data loss):** `delete_all` on a conditioned table deleted the whole
  table. The SQLite, Postgres and MySQL sources built `DELETE FROM <table>` from
  the table name alone and never read the table's conditions, so a caller that
  narrowed a table and then deleted it lost every row. The conditions are now
  applied, and an unconditioned table still truncates as before.

## 0.6.19 — 2026-08-16

- The SQLite, Postgres and MySQL shells implement `preview_query`, returning the
  SELECT as it stands with values rendered inline. The executed form binds those
  same values as parameters.

## 0.6.18 — 2026-08-12

- **Bug fix:** the Postgres, MySQL and SQLite shells resolve relations whose
  target constructor returns a typed table. `get_ref_target` went through an
  entity-typed downcast that failed, so a reference field's list of eligible
  rows came back as an error.

## 0.6.17 — 2026-08-07

- **Bug fix:** a search keeps the conditions of the table. The search
  looks in each column, and `AND` binds more tightly than `OR`. Thus
  `role = 'admin' AND <first branch> OR <second branch>` let each row
  that matched a later branch ignore the role condition. The branches
  are now one group with brackets.
- `or_` and `and_` are also methods on the operation trait:
  `price.gt(100).or_(featured.eq(true))`. The functions stay available.
- **Behaviour change:** `or_` and `and_` give a `ConditionGroup`, which
  writes one set of brackets around the chain and no brackets around
  each operand. `or_(a, b)` gave `(a) OR (b)`, and it now gives
  `(a OR b)`. A chain stays flat: `a.or_(b).or_(c)` gives
  `(a OR b OR c)`, not a nest. To make a different group, nest the
  calls: `a.or_(b.or_(c))`.

## 0.6.16 — 2026-07-26

- **Behaviour change:** a Postgres vista no longer advertises `can_subscribe`
  just because it is writable. `LISTEN` on a channel no trigger ever feeds
  succeeds and then blocks forever, so the old flag promised a feed that may
  never arrive — indistinguishable, to a consumer, from a table where nothing
  happens. Watchability is now opt-in via
  `PostgresVistaFactory::with_notify(true)`, by which the application declares it
  installed the `{table}_changed` trigger. Postgres cannot be asked whether that
  trigger exists, so the application is the only party that can answer honestly.
  Callers relying on the implicit flag add one builder call; nothing else changes.
- Reference traversal carries that declaration across. `get_ref`,
  `get_ref_target` and contained-reference traversal each build a fresh factory
  internally, so without this a relation target came back unwatchable even on a
  database whose tables all have triggers.

## 0.6.15 — 2026-07-23

- `add_op_condition` pushes non-equality filters into the SQL query for all three
  flavours (SQLite/Postgres/MySQL): `!=`, `<`, `<=`, `>`, `>=`, and `in` /
  `not in` (via `in_list` / `not_in_list` over a `Value::Array` operand). The
  factories advertise `can_filter_operators`.

## 0.6.14 — 2026-07-23

- SQLite binds lower `Tag(8)` record references (`["table", id]` or
  `"table:id"`) to their id — a numeric id binds as INTEGER, so
  reference values from dropdowns insert cleanly into integer FK
  columns instead of panicking `bind_by_cbor: unexpected CBOR value`.
- Binding a value SQLite can't express is now a typed error naming the
  parameter position and value shape — never a panic (these values come
  straight from user forms).
- Failed SQLite queries carry context: the SQL (truncated at 500 chars)
  and a parameter-type summary (`?1=Text ?2=Integer …`, types only) —
  enough to see which table/column a `datatype mismatch` points at.

## 0.6.13 — 2026-07-22

- Vista factories (SQLite/Postgres/MySQL) lower **dotted spec columns**
  (`author.name`) as implicit-reference imports through
  `with_active_columns`, after the spec's references are registered. A
  dotted column previously went through the plain `add_column` path,
  selecting a nonexistent identifier and reading empty. Declared-column
  YAML now matches the typed API: dotted names traverse the has-one
  chain and project as read-only correlated imports.

## 0.6.12 — 2026-07-21

- Implicit-references review fixes: vista factories flag computed columns
  (implicit-reference imports, expression, lazy) `calculated` in metadata.
  Quicksearch skips imported columns — the dotted identifier is not a real
  field in a WHERE clause (SQLite would degrade it to a string literal and
  match wrong rows; Postgres/MySQL would error at fetch time).

## 0.6.11 — 2026-07-21

- SQLite / Postgres / MySQL advertise `TableSource::supports_traversal`, so
  implicit references (`Table::with_active_columns`) lower into nested
  correlated scalar subqueries on their existing `related_correlated_condition`
  — no native-path override needed. The vista factories advertise
  `can_traverse_in_columns`.

## 0.6.10 — 2026-07-16

- CBOR↔JSON bridge is the shared `vantage-types` walker under a local `SqlDialect`;
  behavior unchanged (NaN/Infinity as strings, UTF-8-else-hex bytes, `Tag(10)` decimals
  as numbers). The `From<JsonValue>` conversions in all three drivers no longer carry an
  `.expect()` panic path.

## 0.6.9 — 2026-07-16

- Postgres live subscriptions. The Postgres Vista advertises `can_subscribe` and
  implements `watch_vista` via `PgListener`, streaming a coarse
  `VistaChange::Invalidated` on every `LISTEN/NOTIFY` on the `{table}_changed`
  channel (which the application feeds from a trigger). This lets `dio.watch()`
  drive a Postgres-backed reactive view with no hand-written listener — the same
  call the SurrealDB path uses.

## 0.6.8 — 2026-07-15

- SQLite vista factory honours the column spec's `lazy: <rhai script>` form —
  the script sees the record built so far as `row`, chaining in declaration
  order (requires the `rhai` feature, which now also enables
  `vantage-vista/rhai`).

## 0.6.7 — 2026-07-02

- SQLite's shell is now `Clone` and implements `TableShell::clone_shell`, so a
  consumer (e.g. a diorama paged scenery) can build a per-view ordered copy —
  `clone_shell()``add_order``fetch_window` — to page rows in server order.
  The clone is cheap: it copies the wrapped table's query state (the same clone
  `fetch_window` already does per call) while the connection pool stays shared.

## 0.6.6 — 2026-06-28

- TLS support for the Postgres and MySQL connection pools: sqlx is now built with the `tls-rustls`
  feature (rustls + ring + webpki), so a `DATABASE_URL` with `sslmode=require` negotiates an
  encrypted connection. This is required against servers that enforce SSL — e.g. Amazon RDS for
  PostgreSQL 15+, whose default parameter group sets `rds.force_ssl = 1` and rejects unencrypted
  sessions. No API change; existing non-SSL connections keep working (sqlx defaults to
  `sslmode=prefer`).

## 0.6.5 — 2026-06-28

- The Postgres table source honors `Table::with_text_id()` (vantage-table 0.6.9): a text-keyed
  table binds its id as text on every by-id insert/get/replace/patch/delete, instead of coercing an
  all-digit id to `bigint`. Tables without the flag keep the integer-coercing default.
- The Postgres Vista shell implements `fetch_window(offset, limit)` (and advertises
  `can_fetch_window`), so windowed pagination works through the Vista facade, matching the SQLite
  shell.

## 0.6.4 — 2026-06-26

- A NULL id read back from a row is no longer coerced to the literal string `"Null"`: rows with a
  NULL id are skipped on read, and an insert whose `RETURNING` id comes back NULL (a `PRIMARY KEY`
  with no `DEFAULT`/sequence and no supplied id) fails with an explanatory error instead of a bogus
  id. Pair such tables with `Table::with_generated_id` to mint the id client-side. Applies to the
  SQLite, Postgres, and MySQL table sources.
- On the explicit-id insert/replace paths the id the caller passes is now applied after the record,
  so it stays authoritative even when a `before_insert` hook (e.g. an id generator) also wrote an id
  into the record.

## 0.6.3 — 2026-06-25

- `AnySqliteType` implements `InvariantValue` (via the type-system macro's `null_when:
  ciborium::Value::Null`), so SQLite participates in vantage-table's backend-agnostic set-invariant
  enforcement (see 0.6.6): a row written into an equality-scoped set, such as a traversed has-many
  relation, carries its foreign key automatically.

## 0.6.2

### Added

- `SqliteOperation`, `PostgresOperation` and `MysqlOperation` gained `not_in` and `not_in_list`,
  mirroring the existing `in_` / `in_list` pair.

## 0.6.1

- SQLite Vista now implements `fetch_window` (advertised via `can_fetch_window`), serving an
  arbitrary `[offset, offset + limit)` row window through `Pagination::window`. Previously only
  page-indexed `fetch_page` was available, so random-access window fetches were refused.
- Regression test `vista_get_ref_preserves_with_expression_columns` covers `vantage-table` 0.6.4's
  fix for computed columns surviving `get_ref` entity erasure — a typed child with a
  `with_expression` now keeps that column when reached through a parent vista's `get_ref`.

## 0.6.2 — 2026-06-21

- Coordinated 0.6 release; internal dependencies realigned to 0.6. No public API changes.

## 0.5.9 — 2026-06-07

### Changed

- `register_engine!` is split so its registrations live in a reusable
  `__register_engine_onto(&mut Engine)`; the macro and `__create_engine` call it. Prepares the SQL
  backend for the conventional Rhai-scripted reference traversal added in `vantage-vista` 0.5.4. No
  behavior change for existing engine call sites.

## 0.5.8 — 2026-06-06

### Changed

- Tracks the `vantage-vista` thin refactor (0.5.3): dropped the obsolete `with_foreign` vista
  integration test now that cross-persistence traversal lives in `vantage-vista-factory`. No
  functional change to the SQL backends.

## 0.5.7 — 2026-06-02

### Added

- Tables can be sourced from a sub-`SELECT` via `vantage-table`'s new `SelectSource`
  (`type Source = SelectSource<SqliteSelect>`), rendering `FROM (<select>) AS <alias>`.
- SQLite vista specs accept a `sqlite.rhai:` block: a Rhai script that builds the vista's source
  `SELECT` instead of pointing at a physical table. The resulting vista is read-only
  (insert/update/delete capabilities are cleared). Requires the `rhai` feature.

## 0.5.6 — 2026-06-01

### New Features

- **Rhai DSL Engine**: Write SQL queries in Rhai scripting language with full cross-database
  support. The new `rhai` feature flag enables a high-level DSL that compiles to vendor-specific SQL
  for SQLite, PostgreSQL, and MySQL. Example:

  ```rust
  let users = table("users").alias("u");
  select()
      .from(users)
      .expression(users["name"])
      .where(users["age"] >= 18)
      .order_by(users["name"], "asc")
  ```

  - Automatic identifier quoting (backticks for MySQL, double quotes for PostgreSQL/SQLite)
  - Dialect-aware primitives: `date_format()` translates to `strftime()`/`TO_CHAR()`/`DATE_FORMAT()`
  - New `group_concat()` primitive with DISTINCT support (maps to `GROUP_CONCAT`/`STRING_AGG`)
  - Comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`) work across all backends
  - Test runner with `--fix` mode for generating SQL snapshots

- **GroupConcat Primitive**: Cross-database string aggregation with optional DISTINCT. Renders as:
  - SQLite/MySQL: `GROUP_CONCAT(DISTINCT expr, ',')`
  - PostgreSQL: `STRING_AGG(DISTINCT expr, ',')`

### Internal Changes

- Added `SelectBuilder` and `JoinBuilder` traits for database-specific select/join operations
- Refactored select builder methods into dedicated module (`src/rhai_engine/select_methods.rs`)
- Implemented comparison operators module (`src/rhai_engine/operators.rs`)
- New test infrastructure: `examples/rhai_test.rs` runner with snapshot testing support
- Added `tests/rhai-tests/` directory with `.rhai` query files and `.sql`/`.err` snapshots for all
  three backends

## 0.5.5 — 2026-05-31

- Contained relations on SQLite, PostgreSQL, and MySQL: embedded collections stored as JSON columns
  surface as editable sub-Vistas, with eager writeback patching the host column. Postgres and MySQL
  share the SQLite path verbatim. Also lowers a YAML `contained:` section in `table_from_spec`. See
  the
  [contained relations guide]https://romaninsh.github.io/vantage/new-persistence/step9-contained-relations.html.

## 0.5.4 — 2026-05-30

- The SQLite, PostgreSQL, and MySQL shells implement
  [`TableShell::get_ref_target`]https://docs.rs/vantage-vista/0.5.1/vantage_vista/trait.TableShell.html,
  and their factories populate `VistaMetadata::references` — enabling
  [vantage-vista 0.5.1]https://docs.rs/vantage-vista/0.5.1/vantage_vista/'s nested insert through
  relations. Tracks [vantage-table 0.5.4]https://docs.rs/vantage-table/0.5.4/vantage_table/.

## 0.5.3 — 2026-05-23

- Align all internal dependency versions to 0.5+. No public API changes.

## 0.5.2 — 2026-05-23

- Drops the `vantage_table::any::AnyTable` re-export from `prelude``AnyTable` is deleted upstream
  in [vantage-table 0.5.2]https://docs.rs/vantage-table/0.5.2/vantage_table/. Use the driver's
  `vista_factory().from_table(...)` for cross-driver wrapping.

## 0.5.1 — 2026-05-23

- Tracks [vantage-dataset 0.5.0]https://docs.rs/vantage-dataset/0.5/vantage_dataset/'s `ImTable` /
  `ImDataSource` parametrization. No public API change in this crate.

## 0.5.0 — 2026-05-23

- Bumped to the 0.5 line to track
  [vantage-table 0.5.0]https://docs.rs/vantage-table/0.5.0/vantage_table/'s opening of the
  `AnyTable` decommission cycle. No code changes beyond the dependency pin.

## 0.4.9 — 2026-05-18

- Tracks [vantage-vista 0.4.10]https://docs.rs/vantage-vista/0.4.10/vantage_vista/'s
  schema-on-source refactor. Each SQL `*TableShell` now owns its
  [`VistaMetadata`]https://docs.rs/vantage-vista/0.4.10/vantage_vista/struct.VistaMetadata.html
  and implements the new `columns` / `references` / `id_column` shell methods. Factory entry points
  (`db.vista_factory().from_table(...)` / `from_yaml(...)`) are unchanged.
- Pins `vantage-vista = "0.4.10"`.

## 0.4.8 — 2026-05-17

- All three SQL `*TableShell`s ship the full Stage 5 query surface:
  [`add_order`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/struct.Vista.html#method.add_order
  on any column (every column gets the
  [`ORDERABLE`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/flags/constant.ORDERABLE.html
  flag at factory time),
  [`add_search`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/struct.Vista.html#method.add_search
  via the existing `search_table_condition`, and offset-style pagination
  ([`set_page_size`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/struct.Vista.html#method.set_page_size +
  [`fetch_page`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/struct.Vista.html#method.fetch_page
  /
  [`fetch_next`]https://docs.rs/vantage-vista/0.4.9/vantage_vista/struct.Vista.html#method.fetch_next,
  encoding the cursor as a 1-based page number).
- Capabilities updated: `can_order`, `can_search`, `can_set_page_size`, `can_fetch_page`,
  `can_fetch_next` all `true`. The retired `paginate_kind` flag is gone — drop it from any direct
  `VistaCapabilities` construction.
- Pins `vantage-vista = "0.4.9"`, `vantage-table = "0.4.12"`.

## 0.4.7 — 2026-05-16

- Internal dependency version refresh; no public API changes.

## 0.4.6 — 2026-05-16

- All three SQL `*TableShell`s implement
  [`TableShell::get_ref`]https://docs.rs/vantage-vista/0.4.7/vantage_vista/trait.TableShell.html#method.get_ref
  and `get_ref_kinds`: row-based reference traversal at the Vista layer. Each shell converts the
  CBOR parent row into the driver's `Any*Type` map, calls `Reference::resolve_from_row` on the
  wrapped typed table, and re-wraps the result via the driver's own `VistaFactory`.
- `eq_value_condition` implemented on `SqliteDB`, `PostgresDB`, `MysqlDB` via their respective
  `*Operation::eq` traits, returning the driver's native condition type.
- Integration tests in `tests/sqlite/6_vista.rs` exercise the new path end-to-end against in-memory
  SQLite: same-driver `has_many` traversal, `Vista::list_references` cardinality, and the
  `Vista::with_foreign` lazy-closure invariant.
- Pins `vantage-vista = "0.4.7"`, `vantage-table = "0.4.10"`.

## 0.4.5 — 2026-05-09

- Pins `vantage-types` to `>= 0.4.2`. The `RichText`-returning `TerminalRender` impls landed in
  0.4.4 alongside `vantage-types 0.4.2`; without an explicit floor, cargo could resolve
  `vantage-types` to 0.4.0/0.4.1 and fail to compile against the old trait shape.

## 0.4.4 — 2026-05-04

- New optional `vista` feature wires SQLite, Postgres, and MySQL into
  [`vantage-vista`]https://docs.rs/vantage-vista. Call `db.vista_factory().from_table(table)` to
  expose any typed `Table<…>` as a `Vista`, or load a YAML spec via `build_from_spec` for
  config-driven setups.
- Each backend ships its own `*VistaSpec` / `*VistaFactory` / `*TableShell` triple under
  `mysql::vista`, `postgres::vista`, and `sqlite::vista`, with full read/write/count capabilities
  and `eq` filtering through the existing typed-column path.
- Backend-specific `sqlite:` / `postgres:` / `mysql:` blocks in the YAML spec let you override table
  and column names without leaving the spec.
- `from_table` now preserves the original entity type instead of erasing to `EmptyEntity`  `Table<Db, E>` survives the wrap so user-defined `with_expression` closures parameterised over `E`
  still typecheck. The boxed `TableShell` in `Vista` keeps the dyn-erasure boundary at one place.
- `*TableShell` implements
  [`driver_name`]https://docs.rs/vantage-vista/0.4.4/vantage_vista/trait.TableShell.html#method.driver_name
  so
  [`Vista::driver()`]https://docs.rs/vantage-vista/0.4.4/vantage_vista/struct.Vista.html#method.driver
  reports `"sqlite"` / `"postgres"` / `"mysql"` for diagnostics.
- Bumps minimum [`vantage-vista`]https://docs.rs/vantage-vista/0.4.4/ requirement to 0.4.4.

## 0.4.3 — 2026-04-19

- SQL `is_null` / `is_not_null` operations rendered as `{} IS NULL` / `{} IS NOT NULL` for sqlite,
  postgres, mysql.
- Doc fixes in `docs4`.