vantage-sql 0.10.0

Vantage extension for SQL databases (Postgres, MySQL, SQLite)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# Changelog

## 0.10.0 — 2026-10-18

Requires vantage-table 3.0.0 and vantage-vista 2.1.0.

### Breaking changes

- `MysqlTableShell` is `SqlTableShell<MysqlDB, E, MysqlVistaFactory>`: the shell holds its whole
  factory, so scripts it runs inherit the factory's resolver and injector. Code that names the
  alias with its default parameters is unaffected.

### Other changes

- `with_injector(TableInjector<Db>)` on the SQLite, PostgreSQL and MySQL vista factories. The
  injector runs on the root table, each reference target, each `base:` table, and tables built
  in `modify:` and reference scripts.
- `in_values_condition` builds `col IN (v1, v2)`; an empty list renders `col IN (NULL)` and
  matches no row. `describe_condition` and `condition_column` are implemented.
- The shells' `count` and `watch` run the table's `BeforeSelect` hooks first.
- Writable SQL vistas advertise `writes_reach_hooks`.
- `mysql::vista::register_rule_conditions(engine)` (`rhai` feature): the `sql::` vocabulary and
  `table.with_condition(<expr>)` for rule scripts, which narrows the table by a `MysqlCondition`
  built as a `modify:` script's is. SQLite and Postgres have no `modify:` vocabulary and get no
  rule `with_condition`; there it is "function not found".

## 0.9.1 — 2026-10-15

- The in-crate unit tests move to the blackbox suite under `tests/`, duplicate tests are dropped,
  and public behaviour the docs promise but nothing tested gets a test. No API or behaviour change.

## 0.9.0 — 2026-10-14

### Breaking changes

- The SQLite, PostgreSQL and MySQL shells implement `TableShell` 2.0: `fetch_page`, `fetch_next`
  and `set_page_size` are gone, `fetch_window` returns a `Page`, and no SQL vista advertises
  `Cursor`.
- `SqliteTableShell`, `PostgresTableShell` and `MysqlTableShell` are aliases of one generic
  `SqlTableShell<DB, E>`.
- `SqliteSpecResolver`, `PostgresSpecResolver` and `MysqlSpecResolver` are removed;
  `with_resolver` takes `Arc<dyn SpecResolver<XVistaSpec>>`. A closure passed there needs an
  annotated parameter (`|name: &str| ..`).
- The `DriverBlockArgs` trait is removed from `{sqlite,postgres,mysql}::vista::spec`.
- Join traversal is strict: the target must declare the join column (a has-many's foreign key).
  A join that can't narrow its target is an error naming the relation and the column.
- A condition or order on an undeclared column is an error, and the shell is left unchanged.
- A dotted column, whether a nested path or an imported column such as `author.name`, is refused
  as `Unimplemented` ("this shell doesn't evaluate a dotted path"), so `Vista::with_query` hands
  it back in the residual.
- `get_ref` on an undeclared relation is "unknown relation (relation: x)", still `is_not_found()`.
- `get_ref(rel, None)` on a contained relation returns the bare target without write
  capabilities; it was `IncorrectUsage`.
- A `rhai:` or `base:` vista advertises no writes, and Vista refuses them before the shell.
- `insert(None)` reads the stored row back through the set and errors when it can't; it returned
  the record as sent.
- `preview()` is `{query}`: the SELECT with every condition in it. The driver name is
  `vista.driver_name()`.
- Rhai query scripts call free functions through one `sql::` namespace for all three dialects
  (`sql::select()`, `sql::ident("x")`, `sql::window()`, `sql::case_when()`, …): in `rhai:` and
  `base:` sources, `mysql: { modify }`, `mysql: { rhai }` reference scripts and
  `mysql::vista::eval_select`. A bare call is "Function not found".
- The `table("x")` alias for `ident` is removed; write `sql::ident("x")`.
- `register_constructors!`, `register_select!` and `register_window_case!` take the namespace
  after the engine. `register_engine!` is unchanged.
- A MySQL script's `table(name)` for a name the spec resolver doesn't know is `is_not_found()`.
- A `mysql: { rhai }` reference build-script without the `rhai` feature fails the build.
- Quicksearch reads the columns flagged `Searchable`, or every column when none is. A table with
  `name` flagged no longer searches every column.
- A spec reference with `keys:` narrows on every declared key; only `foreign_key` was used before.

### Other changes

- Vista 2.0 driver cleanup: one generic spec builder for the three dialects, with `base:` handled
  one way through `resolve_base_spec`.
- Spec references lower through `ReferenceSpec::to_reference`, and column-level `references:`
  reach the vista metadata as has-one references.
- A YAML reference's `target` is its spec name and a typed relation's is its table name, so
  `catalog.vista(&reference.target)` resolves; it was the Rust type name.
- Typed and YAML column flags (`mandatory`, `hidden`, `searchable`, `indexed`, `label`,
  `request_param`) reach the vista column.
- `upsert` is one native statement on each dialect (`INSERT OR REPLACE`, `ON CONFLICT DO UPDATE`,
  `ON DUPLICATE KEY UPDATE`).
- PostgreSQL `watch` and MySQL `import` are refused by Vista without `Subscribe` and `Import`.
- MySQL native conditions are keyed by their rendered SQL; a condition of a foreign type is
  `IncorrectUsage`.

Migration guide: https://vantage-ui.com/book/migration-2.0.html

## 0.8.0 — 2026-10-10

- MySQL Vistas: ordering, quicksearch, page size, `fetch_page`, `fetch_window` and `fetch_next`
  (stable tiebreak on the id column).
- MySQL: transactional `import_vista_values` (`can_import`); ids match under the column collation;
  statements are chunked under the 65,535-placeholder limit; set checks are batched.
- MySQL: Rhai-scripted references (`mysql: { rhai: … }` on a reference), `with_condition`,
  `add_raw_condition` and `mysql.modify:`.
- **Breaking:** `MysqlVistaSpec`'s reference extras type is now `MysqlReferenceExtras`.
- MariaDB 11 is supported and tested in CI.
- MySQL JSON extraction renders `JSON_UNQUOTE(JSON_EXTRACT(…))` / `JSON_EXTRACT(…)` instead of
  `->>` / `->`.
- MariaDB rejects `DateTime<FixedOffset>` binds (documented).
- `base:`-derived specs keep their own `references:` (SQLite, PostgreSQL, MySQL).
- Requires vantage-table 1.3.0.

## 0.7.1 — 2026-10-07

- Fixed: on PostgreSQL an operator condition on a column the written record omits (`price > 10`) is a
  `Conflict`, not a type error; the set probe types an absent column's NULL from the column.

## 0.7.0 — 2026-10-05

- Removed the uncompiled `rhai_engine/dialect_functions.rs` (`strftime`, a second `group_concat`);
  use `date_format()` and the registered `group_concat()`.
- **Breaking:** by-id writes follow the write contract on SQLite, PostgreSQL and MySQL: delete of a
  missing row or one outside the table's conditions is `Ok` (undoes the 0.6.26 NotFound); insert of
  an existing id in the set returns the stored row; an id held outside the set is `Conflict`.
- Patch, replace and delete are confined to the table's conditions in the statement; a written row
  must still satisfy them (checked by a one-row probe), else `Conflict`.
- Fixed: a patch or insert outside the set ran and only then reported "not found" / "Inserted row
  disappeared".
- `condition_equality`: `column = value` conditions fill on insert.
- Vista shells advertise `can_confine_writes`.

## 0.6.28 — 2026-10-04

- Builds without the `vista` feature again (the vista `Like` pattern helper is gated on it).

## 0.6.27 — 2026-10-04

- SQLite, Postgres and MySQL share one statement path (deferred parameters, placeholders,
  binding, `execute_affected`) and one patch re-read.
- Postgres and MySQL query errors carry the SQL, as SQLite's do.
- Postgres and MySQL share one `defer`, and `aggregate` uses the same first-cell rule.
- MySQL insert-return-id errors carry the SQL.
- SQLite-only builds compile without dead-code warnings.
- The vista factories use the spec helpers of `vantage-table` 1.1.1.
- Depends on `vantage-expressions` 1.0.1, `vantage-table` 1.1.1, `vantage-vista` 1.1.1.

## 0.6.26 — 2026-10-03

- Deleting a missing row fails with `ErrorKind::NotFound` on SQLite, Postgres and MySQL.
- `execute_affected` on `SqliteDB`, `PostgresDB` and `MysqlDB` returns the affected-row count;
  its errors keep the sqlx error as their source.
- Spec `lazy:` columns become Vista computed columns, on Postgres and MySQL too.
- Depends on `vantage-table` 1.1.0, `vantage-vista` 1.1.0.

## 0.6.25 — 2026-10-03

- A patch that finds no row fails with `ErrorKind::NotFound` on SQLite, Postgres and MySQL.
- Depends on `vantage-core` 1.0.0, `vantage-types` 1.0.0, `vantage-expressions` 1.0.0,
  `vantage-dataset` 1.0.0, `vantage-table` 1.0.0, `vantage-vista` 1.0.0, `vantage-rhai` 1.0.0.

## 0.6.24 — 2026-09-19

- `PostgresDB::connect` and `MysqlDB::connect` fail immediately on a refused
  or unreachable server instead of retrying for the pool's 30-second acquire
  deadline: one probe connection, then a lazily built pool.

## 0.6.23 — 2026-09-08

- `add_op_condition` handles `FilterOp::Like`: `LIKE '%…%' ESCAPE '$'` on
  SQLite and MySQL, `ILIKE` on Postgres — matching what quicksearch already
  does on each dialect.
- The `%…%` escape lives in one place (`like_pattern_str`), shared by
  quicksearch and by the new operator, so the two cannot drift.

## 0.6.22 — 2026-09-05

- The `rhai` feature runs on a `vantage-rhai` host: `register_engine!` emits
  a `SqlVocab` for the dialect plus one lazily built, bounded host
  (`__host()`), and `__create_engine` is gone. Query-source scripts compile
  through the host's cache — a table's script parses once, not per build.
- `eval_to_select_args` binds `args` and `base` through an `Env`;
  `eval_to_select` delegates to it. The `rhai` crate is reached as
  `vantage_sql::rhai_engine::rhai` (re-exported from `vantage-rhai`), so a
  consumer no longer needs its own `rhai` dependency to match versions.
- An unknown identifier in a script now reads "unknown name `x`" (the host's
  message) rather than rhai's variable-not-found text.

## 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`.