You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4c5c3b1
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/protocol/vgi-protocol-proposed-changes.md
+54-8Lines changed: 54 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -453,7 +453,34 @@ Triggers are different. Supporting a trigger in a virtual catalog would require
453
453
454
454
The same principle applies to secure views, composable COPY, remote-plan pushdown, coordinate systems, and any catalog object VGI does not currently virtualize: they are not protocol requirements simply because DuckDB implements them. They should be gated through explicit catalog capabilities and fail early.
455
455
456
-
### 6.1 New catalog kinds in DuckDB 2.0
456
+
### 6.1 Writable result modes and OLD/NEW images
457
+
458
+
VGI 2.0 replaces the four `TableInfo` booleans `supports_insert`,
459
+
`supports_update`, `supports_delete`, and `supports_returning` with one required,
460
+
non-null Arrow map:
461
+
462
+
```text
463
+
write_result_modes: map<utf8, utf8>
464
+
```
465
+
466
+
The only operation keys are `insert`, `update`, and `delete`; absence means the
467
+
operation is unsupported. Each value is the maximum supported mode in the
468
+
ordered lattice `count < rows < changes`, and therefore promises every lower
469
+
mode. Duplicate keys and unknown operations or modes are protocol errors.
470
+
471
+
The serialized `write_options` batch replaces `return_chunks: bool` with the
472
+
required `result_mode: utf8`. `count` returns `(count int64 not null)`. `rows`
473
+
returns NEW rows for INSERT/UPDATE and OLD rows for DELETE. `changes` returns
474
+
two nullable structs, `old` and `new`, each shaped exactly like the table:
475
+
INSERT populates only `new`, DELETE only `old`, and UPDATE both. The operation
476
+
is known from the invoked write function and is not repeated in each result row.
477
+
478
+
The DuckDB 1.5 adapter requests `count` for ordinary DML and `rows` for DML with
479
+
RETURNING. It validates and preserves `changes` capability metadata but never
480
+
requests that mode. A DuckDB 2.0 adapter can request `changes` when trigger
481
+
planning needs OLD/NEW images without another protocol revision.
482
+
483
+
### 6.2 New catalog kinds in DuckDB 2.0
457
484
458
485
The `CatalogType` enum has exactly two additions relative to DuckDB 1.5.5:
459
486
@@ -464,7 +491,7 @@ The `CatalogType` enum has exactly two additions relative to DuckDB 1.5.5:
464
491
465
492
Nested schemas are recursively contained `SCHEMA_ENTRY` objects, not another kind. Secure views remain `VIEW_ENTRY`; changes to aggregate-state representation belong to the logical type system rather than the catalog enum.
466
493
467
-
### 6.2 How native DuckDB stores triggers
494
+
### 6.3 How native DuckDB stores triggers
468
495
469
496
Triggers are first-class `TriggerCatalogEntry` objects, but they do not live in the schema's ordinary catalog set. Each `DuckTableEntry` owns a separate transactional `CatalogSet`:
470
497
@@ -496,7 +523,7 @@ The dependency manager has special trigger identity/lookup handling because sche
496
523
497
524
At bind time DuckDB scans the table's triggers and expands them into the DML plan. Statement triggers form a materialized CTE chain (BEFORE actions, base DML, AFTER actions), including materialized OLD/NEW transition tables when requested. Supported row triggers use `LogicalTrigger` and correlated `NEW.col`/`OLD.col` bindings. The current catalog set yields triggers in case-insensitive alphabetical name order.
498
525
499
-
### 6.3 DuckDB changes needed by custom table implementations
526
+
### 6.4 DuckDB changes needed by custom table implementations
500
527
501
528
DuckDB 2.0 already exposes virtual `TableCatalogEntry::CreateTrigger`, `ScanTriggers`, and `GetTrigger` hooks, and its trigger binder consumes the generic scan hook. Two remaining native-table assumptions should be fixed upstream:
502
529
@@ -509,7 +536,7 @@ Upstream PR: [duckdb/duckdb#25539 — Support triggers on custom table catalog e
509
536
510
537
The `ScanTriggers` contract also implies that returned catalog entries remain alive beyond the callback: both trigger selection and introspection retain references. VGI should materialize trigger entries in a table-owned metadata cache, invalidated through the existing catalog-version mechanism, rather than returning callback-local objects.
511
538
512
-
### 6.4 Proposed VGI trigger protocol
539
+
### 6.5 Proposed VGI trigger protocol
513
540
514
541
The remote VGI catalog should be authoritative for persistent trigger definitions. Keeping definitions only in a local extension cache would lose them on detach and would give different clients inconsistent catalogs.
515
542
@@ -529,13 +556,22 @@ TriggerInfo
529
556
for_each: statement | row
530
557
referencing_new_table: optional<string>
531
558
referencing_old_table: optional<string>
532
-
action_sql: string
559
+
definitions: map<string, string>
533
560
dependencies: list<TriggerDependency>
534
561
comment: optional<string>
535
562
tags: map<string, string>
536
563
```
537
564
538
-
Use structured fields plus DuckDB-dialect action SQL. Do not put DuckDB's binary `QueryNode` serialization on the VGI wire: it is an internal, version-coupled representation. The local extension can parse the action and create cached `TriggerCatalogEntry` objects. Each resolved dependency should carry its qualified object reference plus ownership, drop-blocking, and alter-blocking flags so the remote catalog can enforce DDL integrity across clients.
565
+
Use structured fields plus engine-specific textual definitions keyed by stable
566
+
lowercase engine identifiers such as `duckdb`, `datafusion`, `spark`, and
567
+
`sqlite`. An engine executes only its matching definition; a missing entry means
568
+
that trigger is unavailable on that engine, with no implicit SQL-dialect
569
+
fallback. Do not put DuckDB's binary `QueryNode` serialization on the VGI wire:
570
+
it is an internal, version-coupled representation. The DuckDB extension parses
571
+
the `duckdb` definition and creates cached `TriggerCatalogEntry` objects. Each
572
+
resolved dependency should carry its qualified object reference plus ownership,
573
+
drop-blocking, and alter-blocking flags so the remote catalog can enforce DDL
574
+
integrity across clients.
539
575
540
576
Suggested methods:
541
577
@@ -545,7 +581,10 @@ Suggested methods:
545
581
-`catalog_table_trigger_drop`
546
582
- optional trigger comment/tag alteration
547
583
548
-
Add explicit catalog/table capability metadata such as `supports_triggers`. A trigger-capable table must also support exact affected-row RETURNING, because DuckDB's statement-trigger expansion internally materializes base DML with `RETURNING *` even when the user did not write a RETURNING clause. UPDATE must additionally expose captured OLD columns before OLD transition tables can be enabled.
584
+
Add explicit catalog/table capability metadata such as `supports_triggers`. A
585
+
DuckDB-managed trigger-capable table must advertise `changes` for every event it
586
+
uses, because DuckDB's statement-trigger expansion needs exact OLD/NEW images
587
+
even when the user did not write a RETURNING clause.
549
588
550
589
All base and trigger-body writes must use the same VGI transaction token and roll back atomically. Stable statement/sub-operation identifiers are recommended so transport retries cannot apply a trigger side effect twice. Trigger support should be rejected when the VGI catalog cannot provide transactional atomicity.
551
590
@@ -673,4 +712,11 @@ The first protocol-breaking phase is now implemented in Python:
673
712
674
713
`CatalogAttachResult.default_schema` intentionally remains a scalar root-schema identifier for the reason described in section 2.
675
714
676
-
This phase does **not** implement the other proposed protocol areas: `vgi.filters.v2`, logical-type identity annotations, trigger RPCs, or standalone window-function catalog entries. The sibling C++, C#, Go, Java, Rust, and TypeScript schema-path migrations were completed afterward. The vendored browser `vgi-client.js` remains generated from the TypeScript SDK and must never be edited manually in the Python repository.
715
+
The writable-result phase replaces the legacy write booleans and
716
+
`return_chunks` option with the `write_result_modes`/`result_mode` contract from
717
+
section 6.1. Python implements and directly tests all three modes. The DuckDB
718
+
1.5 extension consumes only `count` and `rows`; `changes` is reserved for the
This phase does **not** implement the other proposed protocol areas: logical-type identity annotations, trigger RPCs, or standalone window-function catalog entries. The sibling C++, C#, Go, Java, Rust, and TypeScript schema migrations are maintained in their corresponding repositories. The vendored browser `vgi-client.js` remains generated from the TypeScript SDK and must never be edited manually in the Python repository.
0 commit comments