orez 0.4.29 → 0.4.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/fk-cascade.d.ts +102 -0
- package/dist/fk-cascade.d.ts.map +1 -0
- package/dist/fk-cascade.js +233 -0
- package/dist/fk-cascade.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -49
- package/dist/index.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts +1 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +114 -0
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/pglite-manager.d.ts +12 -9
- package/dist/pglite-manager.d.ts.map +1 -1
- package/dist/pglite-manager.js +61 -31
- package/dist/pglite-manager.js.map +1 -1
- package/dist/recovery.d.ts +10 -12
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +24 -13
- package/dist/recovery.js.map +1 -1
- package/dist/replication/change-tracker.d.ts.map +1 -1
- package/dist/replication/change-tracker.js +36 -5
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +20 -17
- package/dist/replication/handler.js.map +1 -1
- package/dist/worker/cf-patches.d.ts +4 -2
- package/dist/worker/cf-patches.d.ts.map +1 -1
- package/dist/worker/cf-patches.js +15 -2
- package/dist/worker/cf-patches.js.map +1 -1
- package/dist/worker/shims/postgres.d.ts.map +1 -1
- package/dist/worker/shims/postgres.js +112 -0
- package/dist/worker/shims/postgres.js.map +1 -1
- package/dist/worker/zero-cache-embed.d.ts.map +1 -1
- package/dist/worker/zero-cache-embed.js +4 -0
- package/dist/worker/zero-cache-embed.js.map +1 -1
- package/dist/zero-changelog-cleanup-patch.d.ts +18 -0
- package/dist/zero-changelog-cleanup-patch.d.ts.map +1 -0
- package/dist/zero-changelog-cleanup-patch.js +63 -0
- package/dist/zero-changelog-cleanup-patch.js.map +1 -0
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { deparseSync, loadModule, parseSync } from 'pgsql-parser';
|
|
3
3
|
import { TX_MANIFEST_DDL, TX_MANIFEST_TABLE } from './cf-do/tx-journal.js';
|
|
4
4
|
import { RETURNING_INTERNAL_PREFIX } from './do-sql-tracking.js';
|
|
5
|
+
import { expandDelete, FkCascadeRegistry, recordAlterTableForeignKeys, recordCreateTableForeignKeys, } from './fk-cascade.js';
|
|
5
6
|
import { Mutex } from './mutex.js';
|
|
6
7
|
import { signalReplicationChange } from './replication/handler.js';
|
|
7
8
|
import { markSQLiteKeywordIdentifiers, restoreSQLiteKeywordIdentifierMarkers, } from './sqlite-keyword-identifiers.js';
|
|
@@ -939,6 +940,14 @@ function flattenSchemaName(schema, name) {
|
|
|
939
940
|
return `_zero_${name}`;
|
|
940
941
|
return `${schema}_${name}`;
|
|
941
942
|
}
|
|
943
|
+
// canonical FK-registry key for a table: schema-qualified PG name (un-flattened),
|
|
944
|
+
// quoted. used for BOTH capture (CREATE TABLE child + parent) and lookup (DELETE
|
|
945
|
+
// target), so they always agree. expansion emits PG SQL under these names and
|
|
946
|
+
// each child re-enters rewriteParsedStatement, which flattens + tracks it exactly
|
|
947
|
+
// like a normal delete — so cascade tracking is identical to hand-written deletes.
|
|
948
|
+
function fkTableKey(ref) {
|
|
949
|
+
return `${quoteIdentifier(ref.schemaname ?? 'public')}.${quoteIdentifier(ref.relname)}`;
|
|
950
|
+
}
|
|
942
951
|
function flattenRangeVar(rangeVar) {
|
|
943
952
|
if (!rangeVar?.schemaname)
|
|
944
953
|
return rangeVar?.relname;
|
|
@@ -3085,6 +3094,28 @@ function deparseExpressionSQL(version, expr) {
|
|
|
3085
3094
|
return null;
|
|
3086
3095
|
return sql.slice(selectIndex + 'SELECT'.length).trim();
|
|
3087
3096
|
}
|
|
3097
|
+
// expand a DELETE on `target` into its cascade child statements (leaves-first).
|
|
3098
|
+
// expansion emits PG SQL under un-flattened names, then each child re-enters
|
|
3099
|
+
// rewriteParsedStatement (suppressFkCascade so it doesn't re-expand) — so each
|
|
3100
|
+
// returns a normal RewrittenStatement, flattened + change-tracked identically to
|
|
3101
|
+
// a hand-written delete. `whereClause` is the parent's already-normalized clause
|
|
3102
|
+
// (deparsed back to SQL, $N params intact); its $N bind to the parent's params
|
|
3103
|
+
// at execute time. unconditional deletes (no WHERE) cascade every child row.
|
|
3104
|
+
function buildCascadeStatements(version, target, whereClause, context) {
|
|
3105
|
+
const whereSql = whereClause ? deparseExpressionSQL(version, whereClause) : null;
|
|
3106
|
+
const childContext = { ...context, suppressFkCascade: true };
|
|
3107
|
+
const out = [];
|
|
3108
|
+
for (const childSql of expandDelete(target, whereSql, context.fkRegistry)) {
|
|
3109
|
+
for (const raw of parseSync(childSql).stmts) {
|
|
3110
|
+
const rewritten = rewriteParsedStatement(version, raw, childContext);
|
|
3111
|
+
if (Array.isArray(rewritten))
|
|
3112
|
+
out.push(...rewritten.filter((s) => !!s));
|
|
3113
|
+
else if (rewritten)
|
|
3114
|
+
out.push(rewritten);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
return out;
|
|
3118
|
+
}
|
|
3088
3119
|
function parseExpressionTarget(source) {
|
|
3089
3120
|
try {
|
|
3090
3121
|
const parsed = parseSync(`SELECT ${source}`);
|
|
@@ -3439,7 +3470,14 @@ function rewriteParsedStatement(version, rawStmt, context) {
|
|
|
3439
3470
|
let changeTracking;
|
|
3440
3471
|
let writeTable = null;
|
|
3441
3472
|
let serialTriggers = [];
|
|
3473
|
+
let cascadeStatements;
|
|
3474
|
+
let fkEdgesAdded = false;
|
|
3442
3475
|
if (nodeType === 'AlterTableStmt') {
|
|
3476
|
+
// capture FK cascade/set-null edges before normalizeAlterTable drops the
|
|
3477
|
+
// ADD CONSTRAINT — drizzle emits FKs as a separate ALTER, not inline.
|
|
3478
|
+
if (context?.fkRegistry && !context.suppressFkCascade) {
|
|
3479
|
+
fkEdgesAdded = recordAlterTableForeignKeys(node, context.fkRegistry, fkTableKey) > 0;
|
|
3480
|
+
}
|
|
3443
3481
|
alterMetadata = normalizeAlterTable(node);
|
|
3444
3482
|
if (!node.cmds?.length) {
|
|
3445
3483
|
const statements = [];
|
|
@@ -3456,6 +3494,12 @@ function rewriteParsedStatement(version, rawStmt, context) {
|
|
|
3456
3494
|
}
|
|
3457
3495
|
else if (nodeType === 'CreateStmt') {
|
|
3458
3496
|
schemaColumns = schemaColumnsForCreateTable(node);
|
|
3497
|
+
// capture FK cascade/set-null edges BEFORE normalizeCreateTable drops the
|
|
3498
|
+
// CONSTR_FOREIGN nodes. flattened keys (fkTableKey) match the DELETE lookup.
|
|
3499
|
+
if (context?.fkRegistry) {
|
|
3500
|
+
fkEdgesAdded =
|
|
3501
|
+
recordCreateTableForeignKeys(node, context.fkRegistry, fkTableKey) > 0;
|
|
3502
|
+
}
|
|
3459
3503
|
// capture serial columns before normalizeCreateTable rewrites the type to integer
|
|
3460
3504
|
const serialCols = serialColumnNames(node);
|
|
3461
3505
|
normalizeCreateTable(node);
|
|
@@ -3490,6 +3534,15 @@ function rewriteParsedStatement(version, rawStmt, context) {
|
|
|
3490
3534
|
else if (nodeType === 'DeleteStmt') {
|
|
3491
3535
|
const table = publicationTableRefForRangeVar(node.relation);
|
|
3492
3536
|
writeTable = table;
|
|
3537
|
+
// build the cascade from the ORIGINAL delete (before normalizeDelete mutates
|
|
3538
|
+
// node), so each child re-enters the rewrite and is flattened + translated
|
|
3539
|
+
// uniformly. suppressFkCascade guards against re-expanding the children.
|
|
3540
|
+
if (context?.fkRegistry?.hasEdges && !context.suppressFkCascade && node.relation) {
|
|
3541
|
+
const cascadeTarget = fkTableKey(node.relation);
|
|
3542
|
+
if (context.fkRegistry.childrenOf(cascadeTarget).length) {
|
|
3543
|
+
cascadeStatements = buildCascadeStatements(version, cascadeTarget, node.whereClause, context);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3493
3546
|
skipIfTableEmpty = normalizeDelete(node, context);
|
|
3494
3547
|
changeTracking = changeTrackingForDML(version, stmt, nodeType, table, 'DELETE');
|
|
3495
3548
|
}
|
|
@@ -3590,6 +3643,8 @@ function rewriteParsedStatement(version, rawStmt, context) {
|
|
|
3590
3643
|
...(skipIfColumnExists ? { skipIfColumnExists } : null),
|
|
3591
3644
|
...(skipIfColumnMissing ? { skipIfColumnMissing } : null),
|
|
3592
3645
|
...(skipIfTableEmpty ? { skipIfTableEmpty } : null),
|
|
3646
|
+
...(cascadeStatements?.length ? { cascadeStatements } : null),
|
|
3647
|
+
...(fkEdgesAdded ? { fkEdges: true } : null),
|
|
3593
3648
|
};
|
|
3594
3649
|
return serialTriggers.length ? [mainStatement, ...serialTriggers] : mainStatement;
|
|
3595
3650
|
}
|
|
@@ -4735,6 +4790,7 @@ export class DoBackend {
|
|
|
4735
4790
|
this.schemaMetadata = new Map();
|
|
4736
4791
|
this.publications = new Map();
|
|
4737
4792
|
this.rewriteCache = new Map();
|
|
4793
|
+
this.fkRegistry = new FkCascadeRegistry();
|
|
4738
4794
|
}
|
|
4739
4795
|
get waitReady() {
|
|
4740
4796
|
return this.ensureReady();
|
|
@@ -4827,6 +4883,9 @@ export class DoBackend {
|
|
|
4827
4883
|
if (publication)
|
|
4828
4884
|
this.publications.set(key, publication);
|
|
4829
4885
|
}
|
|
4886
|
+
else if (kind === 'fk_edge') {
|
|
4887
|
+
this.fkRegistry.add(key, JSON.parse(value));
|
|
4888
|
+
}
|
|
4830
4889
|
}
|
|
4831
4890
|
if (await this.repairShardMetadataPublications()) {
|
|
4832
4891
|
await this.persistDurableMetadata();
|
|
@@ -4904,6 +4963,16 @@ export class DoBackend {
|
|
|
4904
4963
|
for (const [name, publication] of this.publications) {
|
|
4905
4964
|
rows.push(['publication', name, '', this.publicationToJSON(publication)]);
|
|
4906
4965
|
}
|
|
4966
|
+
// FK cascade edges — durable so the registry survives DO eviction (the
|
|
4967
|
+
// app tables persist and CREATE TABLE never re-runs to repopulate it).
|
|
4968
|
+
for (const [parentKey, child] of this.fkRegistry.entries()) {
|
|
4969
|
+
rows.push([
|
|
4970
|
+
'fk_edge',
|
|
4971
|
+
parentKey,
|
|
4972
|
+
`${child.table}|${child.columns.join(',')}`,
|
|
4973
|
+
JSON.stringify(child),
|
|
4974
|
+
]);
|
|
4975
|
+
}
|
|
4907
4976
|
if (rows.length === 0)
|
|
4908
4977
|
return;
|
|
4909
4978
|
// single multi-row INSERT OR REPLACE per chunk. previously this was one
|
|
@@ -5251,6 +5320,13 @@ export class DoBackend {
|
|
|
5251
5320
|
this.applyPublicationChange(change);
|
|
5252
5321
|
changed = true;
|
|
5253
5322
|
}
|
|
5323
|
+
if (statement.fkEdges) {
|
|
5324
|
+
// edges were already added to this.fkRegistry at rewrite time; persist
|
|
5325
|
+
// them and drop cached rewrites so DELETEs issued before this CREATE
|
|
5326
|
+
// TABLE now expand their cascade.
|
|
5327
|
+
changed = true;
|
|
5328
|
+
this.rewriteCache.clear();
|
|
5329
|
+
}
|
|
5254
5330
|
}
|
|
5255
5331
|
if (changed) {
|
|
5256
5332
|
this.publicationTableInfoCache = null;
|
|
@@ -5536,6 +5612,9 @@ export class DoBackend {
|
|
|
5536
5612
|
const tracking = statement ? this.trackingForStatement(statement) : undefined;
|
|
5537
5613
|
const bound = this.sqliteBoundSQL(tracking?.returningSQL ?? sql, portal.params, portal.arrayParamNumbers, portal.jsonParamNumbers, portal.timestampParamNumbers, portal.epochMillisParamNumbers, portal.booleanParamNumbers);
|
|
5538
5614
|
const exec = await this.materializePublishedSchemaFunctions(bound.sql, statement, bound.params);
|
|
5615
|
+
if (statement?.cascadeStatements?.length) {
|
|
5616
|
+
await this.runCascadeStatements(statement.cascadeStatements, portal);
|
|
5617
|
+
}
|
|
5539
5618
|
const result = await this.doExecResult(exec.sql, exec.params, tracking ? this.trackingRequest(tracking) : undefined);
|
|
5540
5619
|
if (portal.schemaColumns?.length) {
|
|
5541
5620
|
await this.applyStatementMetadata([{ sql, schemaColumns: portal.schemaColumns }]);
|
|
@@ -5628,6 +5707,9 @@ export class DoBackend {
|
|
|
5628
5707
|
}
|
|
5629
5708
|
if (statement)
|
|
5630
5709
|
await this.snapshotTransactionWrite(statement);
|
|
5710
|
+
if (statement?.cascadeStatements?.length) {
|
|
5711
|
+
await this.runCascadeStatements(statement.cascadeStatements, {});
|
|
5712
|
+
}
|
|
5631
5713
|
const tracking = statement ? this.trackingForStatement(statement) : undefined;
|
|
5632
5714
|
const result = await this.doExecResult(tracking?.returningSQL ?? rewritten, undefined, tracking ? this.trackingRequest(tracking) : undefined);
|
|
5633
5715
|
await this.applyStatementMetadata(statements);
|
|
@@ -5705,6 +5787,16 @@ export class DoBackend {
|
|
|
5705
5787
|
? this.sqliteBoundSQL(tracking.returningSQL, params, arrayParamNumbers, jsonParamNumbers, timestampParamNumbers, epochMillisParamNumbers, booleanParamNumbers)
|
|
5706
5788
|
: bound;
|
|
5707
5789
|
const exec = await this.materializePublishedSchemaFunctions(execBound.sql, statement, execBound.params);
|
|
5790
|
+
if (statement?.cascadeStatements?.length) {
|
|
5791
|
+
await this.runCascadeStatements(statement.cascadeStatements, {
|
|
5792
|
+
params,
|
|
5793
|
+
arrayParamNumbers,
|
|
5794
|
+
jsonParamNumbers,
|
|
5795
|
+
timestampParamNumbers,
|
|
5796
|
+
epochMillisParamNumbers,
|
|
5797
|
+
booleanParamNumbers,
|
|
5798
|
+
});
|
|
5799
|
+
}
|
|
5708
5800
|
const result = await this.doExecResult(exec.sql, exec.params, tracking ? this.trackingRequest(tracking) : undefined);
|
|
5709
5801
|
await this.applyStatementMetadata(statements);
|
|
5710
5802
|
// metadata for the returned columns must be derived from the ORIGINAL SQL,
|
|
@@ -5743,6 +5835,7 @@ export class DoBackend {
|
|
|
5743
5835
|
arrayParamNumbers,
|
|
5744
5836
|
jsonParamNumbers,
|
|
5745
5837
|
epochMillisParamNumbers,
|
|
5838
|
+
fkRegistry: this.fkRegistry,
|
|
5746
5839
|
});
|
|
5747
5840
|
if (this.canCacheRewrite(statements)) {
|
|
5748
5841
|
this.rememberRewrite(key, statements);
|
|
@@ -5781,6 +5874,7 @@ export class DoBackend {
|
|
|
5781
5874
|
arrayParamNumbers,
|
|
5782
5875
|
jsonParamNumbers,
|
|
5783
5876
|
epochMillisParamNumbers,
|
|
5877
|
+
fkRegistry: this.fkRegistry,
|
|
5784
5878
|
});
|
|
5785
5879
|
}
|
|
5786
5880
|
normalizedHighLevelResult(sql, result) {
|
|
@@ -5969,6 +6063,9 @@ export class DoBackend {
|
|
|
5969
6063
|
if (statement.isDDL)
|
|
5970
6064
|
this.publicationTableInfoCache = null;
|
|
5971
6065
|
await this.snapshotTransactionWrite(statement);
|
|
6066
|
+
if (statement.cascadeStatements?.length) {
|
|
6067
|
+
await this.runCascadeStatements(statement.cascadeStatements, {});
|
|
6068
|
+
}
|
|
5972
6069
|
const tracking = this.trackingForStatement(statement);
|
|
5973
6070
|
const exec = await this.materializePublishedSchemaFunctions(tracking?.returningSQL ?? statement.sql, statement);
|
|
5974
6071
|
return this.doExecResult(exec.sql, exec.params, tracking ? this.trackingRequest(tracking) : undefined);
|
|
@@ -5980,6 +6077,23 @@ export class DoBackend {
|
|
|
5980
6077
|
}
|
|
5981
6078
|
return result;
|
|
5982
6079
|
}
|
|
6080
|
+
// run a parent DELETE's cascade children (leaves-first) as their own bound
|
|
6081
|
+
// execs BEFORE the parent. each child's returningSQL embeds the full parent
|
|
6082
|
+
// WHERE, so the SAME params bind unchanged (sqliteBoundSQL maps $N
|
|
6083
|
+
// positionally); the tracking request captures every deletion so it
|
|
6084
|
+
// replicates like any other write. publication gating matches the parent:
|
|
6085
|
+
// unpublished (e.g. private) child tables cascade in the store but don't
|
|
6086
|
+
// stream — correct, since clients don't see those rows anyway.
|
|
6087
|
+
async runCascadeStatements(cascades, bind) {
|
|
6088
|
+
for (const child of cascades) {
|
|
6089
|
+
if (await this.shouldSkipStatement(child))
|
|
6090
|
+
continue;
|
|
6091
|
+
await this.snapshotTransactionWrite(child);
|
|
6092
|
+
const gated = this.trackingForStatement(child);
|
|
6093
|
+
const bound = this.sqliteBoundSQL(gated?.returningSQL ?? child.sql, bind.params, bind.arrayParamNumbers, bind.jsonParamNumbers, bind.timestampParamNumbers, bind.epochMillisParamNumbers, bind.booleanParamNumbers);
|
|
6094
|
+
await this.doExecResult(bound.sql, bound.params, gated ? this.trackingRequest(gated) : undefined);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
5983
6097
|
async doBatchExec(statements) {
|
|
5984
6098
|
let sqls = [];
|
|
5985
6099
|
const flush = async () => {
|