@crvouga/mockingbird-service-postgres 3.6.0 → 3.7.0
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/CHANGELOG.md +7 -1
- package/COMPATIBILITY.md +31 -0
- package/README.md +5 -0
- package/dist/admin.js +216 -26
- package/dist/admin.js.map +3 -3
- package/dist/api/schema-report.d.ts +32 -0
- package/dist/ast/nodes.d.ts +2 -0
- package/dist/executor/relation.d.ts +1 -0
- package/dist/expressions/context.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +405 -27
- package/dist/index.js.map +4 -4
- package/dist/server.js +229 -27
- package/dist/server.js.map +3 -3
- package/dist/storage/database-state.d.ts +7 -0
- package/dist/types/regnamespace.d.ts +5 -0
- package/dist/types/value.d.ts +2 -0
- package/dist/unstable.js +388 -198
- package/dist/unstable.js.map +4 -4
- package/dist/wire/cli.js +457 -82
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/index.js +229 -27
- package/dist/wire/index.js.map +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-postgres
|
|
2
2
|
|
|
3
|
-
## 3.
|
|
3
|
+
## 3.7.0 (2026-10-17)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- resolve fleet and emulator issue batch ([26d2f8c](https://github.com/crvouga/mockingbird/commit/26d2f8cee99fefed93192204bac44fb1be35c1da))
|
|
8
|
+
|
|
9
|
+
## 3.6.0 (2026-10-15)
|
|
4
10
|
|
|
5
11
|
### Features
|
|
6
12
|
|
package/COMPATIBILITY.md
CHANGED
|
@@ -141,3 +141,34 @@ PostgreSQL; clock profiles are supported. This adds fleet orchestration around e
|
|
|
141
141
|
snapshot semantics, not a broader SQL compatibility claim. Acceptance evidence is
|
|
142
142
|
`packages/adapters/node/fleet.management.test.ts` using the unmodified `pg` wire client.
|
|
143
143
|
See [Fleets](../../../docs/FLEETS.md).
|
|
144
|
+
|
|
145
|
+
## Schema preflight and row-version metadata
|
|
146
|
+
|
|
147
|
+
`analyzeSchema(sql, options)` (the package's main export) checks plain schema SQL in a discarded
|
|
148
|
+
in-memory database and returns a JSON-serializable report, including the installed build version,
|
|
149
|
+
statement offsets/lines/columns, object names, SQLSTATEs, independent failures and identifiable
|
|
150
|
+
missing prerequisites. Defaults cap input at 1 MiB, statements at 1,000 and diagnostics at 100;
|
|
151
|
+
configurable hard caps are 4 MiB, 10,000 and 1,000. It does not execute data statements, contact an
|
|
152
|
+
oracle or change an existing database. Accepted statements remain explicitly **unknown** for full
|
|
153
|
+
semantic compatibility, including deferred SQL/PL/pgSQL bodies and dynamic SQL. A detected gap sets
|
|
154
|
+
`compatible: false`; otherwise it stays `null`, never a claim of a verified import. Source-tree
|
|
155
|
+
execution identifies the version as `source-tree`; published builds embed their package version.
|
|
156
|
+
Lexical errors that prevent reliable statement recovery terminate the scan.
|
|
157
|
+
|
|
158
|
+
SQL and PL/pgSQL function definitions retain comma-separated `SET` lists (including `search_path`),
|
|
159
|
+
apply them during calls and restore the caller's settings on return or error. Portable snapshots
|
|
160
|
+
retain these settings. Sequence functions accept explicit `regclass` input, and
|
|
161
|
+
`pg_get_serial_sequence` finds owned serial, identity and explicitly owned sequences using exact
|
|
162
|
+
column-name case; ownership follows sequence/table/column renames and snapshots.
|
|
163
|
+
|
|
164
|
+
Stored tables expose `xmin` and `alias.xmin` as hidden `xid` system columns. INSERT, UPDATE, upsert,
|
|
165
|
+
COPY and referential-action writes stamp row versions with a local transaction identity; explicit
|
|
166
|
+
transactions share that identity, including wire workspace replay and commit. Rollback restores
|
|
167
|
+
committed row metadata. Snapshots preserve identities and continue allocation above retained IDs;
|
|
168
|
+
legacy PGMM snapshots without metadata expose frozen xid `2`. Each database/namespace owns its
|
|
169
|
+
allocator. These identities are version markers, not permanent globally unique row identifiers.
|
|
170
|
+
The implementation does not add full MVCC, VACUUM/freeze maintenance, physical tuple metadata or
|
|
171
|
+
xid wraparound management; it fails when the 32-bit allocation space is exhausted.
|
|
172
|
+
|
|
173
|
+
Evidence: `tests/contract/regressions/issue-queue.test.ts` (PostgreSQL 18.3 differential contracts),
|
|
174
|
+
`tests/contract/api/schema-report.test.ts`, and `tests/wire/issue-queue.test.ts`.
|
package/README.md
CHANGED
|
@@ -529,6 +529,11 @@ search, `ON CONFLICT DO NOTHING/UPDATE`, `RETURNING`, `PREPARE`/`EXECUTE`/`DEALL
|
|
|
529
529
|
|
|
530
530
|
## API
|
|
531
531
|
|
|
532
|
+
`analyzeSchema(sql, options?)` returns bounded, JSON-serializable schema compatibility diagnostics
|
|
533
|
+
without changing an application database. Accepted statements remain semantically unverified;
|
|
534
|
+
see [schema preflight and row-version metadata](COMPATIBILITY.md#schema-preflight-and-row-version-metadata)
|
|
535
|
+
for limits, the report contract and `xmin` support.
|
|
536
|
+
|
|
532
537
|
Stable runtime exports of the main entry:
|
|
533
538
|
|
|
534
539
|
| Export | Description |
|
package/dist/admin.js
CHANGED
|
@@ -5051,6 +5051,12 @@ var invalidName = () => {
|
|
|
5051
5051
|
throw pgError("syntax", "invalid name syntax", "42602");
|
|
5052
5052
|
};
|
|
5053
5053
|
function parseNamespaceInput(text) {
|
|
5054
|
+
const parsed = parseRelationInput(text);
|
|
5055
|
+
if ("oid" in parsed) return parsed;
|
|
5056
|
+
if (parsed.parts.length !== 1) invalidName();
|
|
5057
|
+
return { name: parsed.parts[0] };
|
|
5058
|
+
}
|
|
5059
|
+
function parseRelationInput(text) {
|
|
5054
5060
|
if (text === "-") return { oid: 0 };
|
|
5055
5061
|
if (/^\d+$/.test(text)) return { oid: parseOidText(text) };
|
|
5056
5062
|
const names = [];
|
|
@@ -5086,8 +5092,8 @@ function parseNamespaceInput(text) {
|
|
|
5086
5092
|
skipSpace();
|
|
5087
5093
|
if (at === text.length) invalidName();
|
|
5088
5094
|
}
|
|
5089
|
-
if (names.length
|
|
5090
|
-
return {
|
|
5095
|
+
if (names.length === 0 || names.length > 3) invalidName();
|
|
5096
|
+
return { parts: names };
|
|
5091
5097
|
}
|
|
5092
5098
|
function namespaceText(oid, name) {
|
|
5093
5099
|
if (oid === 0) return "-";
|
|
@@ -5160,6 +5166,7 @@ var TYPE_ALIASES = {
|
|
|
5160
5166
|
regtype: "regtype",
|
|
5161
5167
|
regproc: "regproc",
|
|
5162
5168
|
regnamespace: "regnamespace",
|
|
5169
|
+
xid: "xid",
|
|
5163
5170
|
oid: "oid",
|
|
5164
5171
|
void: "void",
|
|
5165
5172
|
record: "record",
|
|
@@ -5236,6 +5243,7 @@ var TYPE_OIDS = {
|
|
|
5236
5243
|
regproc: 24,
|
|
5237
5244
|
text: 25,
|
|
5238
5245
|
oid: 26,
|
|
5246
|
+
xid: 28,
|
|
5239
5247
|
json: 114,
|
|
5240
5248
|
float4: 700,
|
|
5241
5249
|
float8: 701,
|
|
@@ -5554,6 +5562,7 @@ function recordText(rec, ctx) {
|
|
|
5554
5562
|
return `(${parts.join(",")})`;
|
|
5555
5563
|
}
|
|
5556
5564
|
function datumText(t, v, ctx) {
|
|
5565
|
+
if (t === "regclass") return v === 0 ? "-" : ctx.relationName?.(v) ?? String(v);
|
|
5557
5566
|
if (t === "regnamespace") return namespaceText(v, ctx.namespaceName?.(v));
|
|
5558
5567
|
if (v === null) throw pgError("internal", "datumText called with null");
|
|
5559
5568
|
if (isArrayType(t)) return arrayText(v, ctx);
|
|
@@ -5564,6 +5573,7 @@ function datumText(t, v, ctx) {
|
|
|
5564
5573
|
case "int2":
|
|
5565
5574
|
case "int4":
|
|
5566
5575
|
case "oid":
|
|
5576
|
+
case "xid":
|
|
5567
5577
|
return String(v);
|
|
5568
5578
|
case "int8":
|
|
5569
5579
|
return String(v);
|
|
@@ -5628,6 +5638,12 @@ function datumText(t, v, ctx) {
|
|
|
5628
5638
|
}
|
|
5629
5639
|
var UTC_INPUT = { zoneOffsetForNaive: () => 0 };
|
|
5630
5640
|
function datumFromText(t, text, ctx) {
|
|
5641
|
+
if (t === "regclass") {
|
|
5642
|
+
const parsed = parseRelationInput(text);
|
|
5643
|
+
if ("oid" in parsed) return parsed.oid;
|
|
5644
|
+
if (!ctx.relationOid) throw unsupported("regclass input without a catalog");
|
|
5645
|
+
return ctx.relationOid(parsed.parts);
|
|
5646
|
+
}
|
|
5631
5647
|
if (t === "regnamespace") {
|
|
5632
5648
|
const parsed = parseNamespaceInput(text);
|
|
5633
5649
|
if ("oid" in parsed) return parsed.oid;
|
|
@@ -5649,6 +5665,7 @@ function datumFromText(t, text, ctx) {
|
|
|
5649
5665
|
case "int8":
|
|
5650
5666
|
return parseIntegerText(text, t);
|
|
5651
5667
|
case "oid":
|
|
5668
|
+
case "xid":
|
|
5652
5669
|
return parseOidText(text);
|
|
5653
5670
|
case "float4":
|
|
5654
5671
|
return Math.fround(parseFloatText(text, "float4"));
|
|
@@ -5805,6 +5822,8 @@ var TableData = class _TableData {
|
|
|
5805
5822
|
schema;
|
|
5806
5823
|
columns;
|
|
5807
5824
|
rows;
|
|
5825
|
+
rowXmins = /* @__PURE__ */ new WeakMap();
|
|
5826
|
+
slabXmins = null;
|
|
5808
5827
|
/** Frozen columnar storage after snapshot hydrate; `rows` stays empty until materialized. */
|
|
5809
5828
|
slab = null;
|
|
5810
5829
|
constraints;
|
|
@@ -5847,6 +5866,11 @@ var TableData = class _TableData {
|
|
|
5847
5866
|
materializeSlab() {
|
|
5848
5867
|
if (!this.slab) return;
|
|
5849
5868
|
this.rows = this.slab.materialize();
|
|
5869
|
+
if (this.slabXmins)
|
|
5870
|
+
this.rows.forEach((row, index) => {
|
|
5871
|
+
this.rowXmins.set(row, this.slabXmins[index] ?? 2);
|
|
5872
|
+
});
|
|
5873
|
+
this.slabXmins = null;
|
|
5850
5874
|
this.slab = null;
|
|
5851
5875
|
this.indexStores = null;
|
|
5852
5876
|
}
|
|
@@ -5880,6 +5904,10 @@ var TableData = class _TableData {
|
|
|
5880
5904
|
} else {
|
|
5881
5905
|
t.rows = this.rows.map((r) => r.slice());
|
|
5882
5906
|
}
|
|
5907
|
+
t.rows.forEach((row, index) => {
|
|
5908
|
+
const xmin = this.slabXmins?.[index] ?? this.rowXmins.get(this.rows[index]);
|
|
5909
|
+
if (xmin !== void 0) t.rowXmins.set(row, xmin);
|
|
5910
|
+
});
|
|
5883
5911
|
t.unlogged = this.unlogged;
|
|
5884
5912
|
t.constraints = this.constraints.map((c) => ({ ...c }));
|
|
5885
5913
|
t.triggers = this.triggers.map((tr) => ({ ...tr }));
|
|
@@ -5965,6 +5993,14 @@ var DatabaseState = class _DatabaseState {
|
|
|
5965
5993
|
clock;
|
|
5966
5994
|
changes = 0;
|
|
5967
5995
|
oidCounter = 16384;
|
|
5996
|
+
/** Local monotonic xid allocator; 2 denotes a row from a legacy snapshot without metadata. */
|
|
5997
|
+
xidCounter = 3;
|
|
5998
|
+
transactionXid = null;
|
|
5999
|
+
allocateXid() {
|
|
6000
|
+
if (this.xidCounter > 4294967295)
|
|
6001
|
+
throw pgError("program_limit_exceeded", "transaction ID space exhausted; xid wraparound is not modeled", "54000");
|
|
6002
|
+
return this.xidCounter++;
|
|
6003
|
+
}
|
|
5968
6004
|
inTransaction = false;
|
|
5969
6005
|
/** Transaction-only events: included in rollback clones, never in exported snapshots. */
|
|
5970
6006
|
pendingTriggers = [];
|
|
@@ -6178,6 +6214,8 @@ var DatabaseState = class _DatabaseState {
|
|
|
6178
6214
|
s.sequenceCurrval = new Map(this.sequenceCurrval);
|
|
6179
6215
|
s.tableStats = new Map(this.tableStats);
|
|
6180
6216
|
s.oidCounter = this.oidCounter;
|
|
6217
|
+
s.xidCounter = this.xidCounter;
|
|
6218
|
+
s.transactionXid = this.transactionXid;
|
|
6181
6219
|
for (const [name, overloads] of this.systemFunctions) s.systemFunctions.set(name, overloads.slice());
|
|
6182
6220
|
return s;
|
|
6183
6221
|
}
|
|
@@ -6197,6 +6235,8 @@ var DatabaseState = class _DatabaseState {
|
|
|
6197
6235
|
s.sequenceCurrval = new Map(this.sequenceCurrval);
|
|
6198
6236
|
s.tableStats = new Map(this.tableStats);
|
|
6199
6237
|
s.oidCounter = this.oidCounter;
|
|
6238
|
+
s.xidCounter = this.xidCounter;
|
|
6239
|
+
s.transactionXid = this.transactionXid;
|
|
6200
6240
|
for (const [name, overloads] of this.systemFunctions) s.systemFunctions.set(name, overloads.slice());
|
|
6201
6241
|
return s;
|
|
6202
6242
|
}
|
|
@@ -6272,6 +6312,8 @@ var DatabaseState = class _DatabaseState {
|
|
|
6272
6312
|
this.sequenceCurrval = other.sequenceCurrval;
|
|
6273
6313
|
this.tableStats = other.tableStats;
|
|
6274
6314
|
this.oidCounter = other.oidCounter;
|
|
6315
|
+
this.xidCounter = Math.max(this.xidCounter, other.xidCounter);
|
|
6316
|
+
this.transactionXid = other.transactionXid;
|
|
6275
6317
|
}
|
|
6276
6318
|
/** @internal Snapshot codec access to the oid allocator. */
|
|
6277
6319
|
snapshotOidCounter() {
|
|
@@ -6348,6 +6390,11 @@ function zoneOffsetForNaive(zone, naiveMicros) {
|
|
|
6348
6390
|
// src/expressions/context.ts
|
|
6349
6391
|
var EngineCtx = class {
|
|
6350
6392
|
state;
|
|
6393
|
+
writeXid = null;
|
|
6394
|
+
rowVersionXid() {
|
|
6395
|
+
if (this.state.inTransaction) return this.state.transactionXid ??= this.state.allocateXid();
|
|
6396
|
+
return this.writeXid ??= this.state.allocateXid();
|
|
6397
|
+
}
|
|
6351
6398
|
/** transaction timestamp (now(), current_timestamp) — UTC micros since PG epoch */
|
|
6352
6399
|
txNow;
|
|
6353
6400
|
/** statement timestamp */
|
|
@@ -6362,6 +6409,23 @@ var EngineCtx = class {
|
|
|
6362
6409
|
timezone() {
|
|
6363
6410
|
return this.state.getSetting("timezone") ?? "UTC";
|
|
6364
6411
|
}
|
|
6412
|
+
relationOid(parts) {
|
|
6413
|
+
const target = this.state.resolveRelationSchema(parts);
|
|
6414
|
+
const schema = target ? this.state.schemas.get(target.schema) : void 0;
|
|
6415
|
+
const relation = target ? schema?.sequences.get(target.name) ?? schema?.tables.get(target.name) ?? schema?.views.get(target.name) : void 0;
|
|
6416
|
+
if (!relation) throw pgError("undefined_table", `relation "${parts.join(".")}" does not exist`, "42P01");
|
|
6417
|
+
return relation.oid;
|
|
6418
|
+
}
|
|
6419
|
+
relationName(oid) {
|
|
6420
|
+
for (const schema of this.state.schemas.values()) {
|
|
6421
|
+
for (const relation of [...schema.tables.values(), ...schema.sequences.values(), ...schema.views.values()]) {
|
|
6422
|
+
if (relation.oid !== oid) continue;
|
|
6423
|
+
const visible = this.state.resolveRelationSchema([relation.name])?.schema === schema.name;
|
|
6424
|
+
return (visible ? [relation.name] : [schema.name, relation.name]).map(quoteIdentifier).join(".");
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
return null;
|
|
6428
|
+
}
|
|
6365
6429
|
namespaceOid(name) {
|
|
6366
6430
|
const oid = builtinNamespaceOid(name) ?? this.state.schemas.get(name)?.oid;
|
|
6367
6431
|
if (oid === void 0) throw pgError("undefined_object", `schema "${name}" does not exist`, "3F000");
|
|
@@ -6662,7 +6726,7 @@ function castTo(env, value, target, opts = {}) {
|
|
|
6662
6726
|
return tv(target, makeArray(toElem, items, arr.dims.slice(), arr.lbs.slice()));
|
|
6663
6727
|
}
|
|
6664
6728
|
if (from === "oid" && target === "regnamespace") return tv(target, v);
|
|
6665
|
-
if (from === "regnamespace" && (target === "oid" || target === "int4" || target === "int8")) {
|
|
6729
|
+
if ((from === "regnamespace" || from === "regclass") && (target === "oid" || target === "int4" || target === "int8")) {
|
|
6666
6730
|
return tv(target, target === "int8" ? BigInt(v) : v);
|
|
6667
6731
|
}
|
|
6668
6732
|
if ((from === "int4" || from === "int8" || from === "int2") && (target === "oid" || target === "regnamespace")) {
|
|
@@ -7363,6 +7427,8 @@ function datumCompare(t, a, b, ctx = DEFAULT_COMPARE_CTX) {
|
|
|
7363
7427
|
case "int2":
|
|
7364
7428
|
case "int4":
|
|
7365
7429
|
case "oid":
|
|
7430
|
+
case "regclass":
|
|
7431
|
+
case "xid":
|
|
7366
7432
|
case "regnamespace":
|
|
7367
7433
|
case "date":
|
|
7368
7434
|
return expectNumber(a, t) - expectNumber(b, t);
|
|
@@ -7482,6 +7548,8 @@ function datumKey(t, v) {
|
|
|
7482
7548
|
case "int2":
|
|
7483
7549
|
case "int4":
|
|
7484
7550
|
case "oid":
|
|
7551
|
+
case "regclass":
|
|
7552
|
+
case "xid":
|
|
7485
7553
|
case "date":
|
|
7486
7554
|
return `i${v}`;
|
|
7487
7555
|
case "float4":
|
|
@@ -10387,7 +10455,7 @@ function databaseCatalogContext(state) {
|
|
|
10387
10455
|
var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
|
|
10388
10456
|
function findSequenceForCall(ctx, name) {
|
|
10389
10457
|
const parts = parseQualifiedName(name);
|
|
10390
|
-
const seq = ctx.state.findSequence(parts);
|
|
10458
|
+
const seq = /^\d+$/.test(name) ? [...ctx.state.schemas.values()].flatMap((schema) => [...schema.sequences.values()]).find((sequence) => sequence.oid === Number(name)) : ctx.state.findSequence(parts);
|
|
10391
10459
|
if (!seq) {
|
|
10392
10460
|
throw pgError("undefined_table", `relation "${name}" does not exist`, "42P01");
|
|
10393
10461
|
}
|
|
@@ -10611,14 +10679,16 @@ function getMiscFunctions() {
|
|
|
10611
10679
|
"pg_get_serial_sequence",
|
|
10612
10680
|
strict("text", (ctx, args) => {
|
|
10613
10681
|
const tableName2 = argText(ctx, args[0]);
|
|
10614
|
-
const column = argText(ctx, args[1])
|
|
10682
|
+
const column = argText(ctx, args[1]);
|
|
10615
10683
|
const table = ctx.state.findTable(parseQualifiedName(tableName2));
|
|
10616
10684
|
if (!table) throw pgError("undefined_table", `relation "${tableName2}" does not exist`, "42P01");
|
|
10617
10685
|
const col = table.columns.find((c) => c.name === column);
|
|
10618
10686
|
if (!col)
|
|
10619
10687
|
throw pgError("undefined_column", `column "${column}" of relation "${table.name}" does not exist`, "42703");
|
|
10620
|
-
|
|
10621
|
-
|
|
10688
|
+
const seq = [...ctx.state.schemas.get(table.schema)?.sequences.values() ?? []].find(
|
|
10689
|
+
(candidate) => candidate.ownedBy?.table === table.name && candidate.ownedBy.column === column
|
|
10690
|
+
);
|
|
10691
|
+
return tv("text", seq ? [seq.schema, seq.name].map(quoteIdent).join(".") : null);
|
|
10622
10692
|
})
|
|
10623
10693
|
);
|
|
10624
10694
|
m.set("format_type", (ctx, args) => {
|
|
@@ -15562,7 +15632,7 @@ var RowScope = class {
|
|
|
15562
15632
|
let count3 = 0;
|
|
15563
15633
|
for (let i = 0; i < this.columns.length; i++) {
|
|
15564
15634
|
const c = this.columns[i];
|
|
15565
|
-
if (c.name === name2 && !c.hidden) {
|
|
15635
|
+
if (c.name === name2 && (!c.hidden || c.system)) {
|
|
15566
15636
|
count3++;
|
|
15567
15637
|
if (count3 > 1) {
|
|
15568
15638
|
throw pgError("ambiguous_column", `column reference "${name2}" is ambiguous`);
|
|
@@ -15600,7 +15670,7 @@ var RowScope = class {
|
|
|
15600
15670
|
const names = [];
|
|
15601
15671
|
for (let i = 0; i < this.columns.length; i++) {
|
|
15602
15672
|
const c = this.columns[i];
|
|
15603
|
-
if (c.table === rangeVar) {
|
|
15673
|
+
if (c.table === rangeVar && !c.system) {
|
|
15604
15674
|
types.push(c.type);
|
|
15605
15675
|
values.push(this.row[i] ?? null);
|
|
15606
15676
|
names.push(c.name);
|
|
@@ -16043,7 +16113,7 @@ function tryIndexedFromItem(env, item, where) {
|
|
|
16043
16113
|
if (!table) return null;
|
|
16044
16114
|
const alias = item.alias ?? item.name[item.name.length - 1];
|
|
16045
16115
|
table.materializeSlab();
|
|
16046
|
-
return tryIndexedTableRows(env, table, alias, where);
|
|
16116
|
+
return tryIndexedTableRows(env, table, alias, where)?.map((row) => [...row, table.rowXmins.get(row) ?? 2]) ?? null;
|
|
16047
16117
|
}
|
|
16048
16118
|
function joinKeyFromRow(row, colIdxs, srcTypes, unifiedTypes, ctx) {
|
|
16049
16119
|
const parts = [];
|
|
@@ -18533,6 +18603,7 @@ var Parser = class {
|
|
|
18533
18603
|
let sqlBody = null;
|
|
18534
18604
|
let volatility = null;
|
|
18535
18605
|
let strict2 = false;
|
|
18606
|
+
const settings = {};
|
|
18536
18607
|
for (; ; ) {
|
|
18537
18608
|
if (this.eatKw("language")) {
|
|
18538
18609
|
language = this.peek().type === "string" ? this.next().value : this.ident(true);
|
|
@@ -18590,8 +18661,23 @@ var Parser = class {
|
|
|
18590
18661
|
continue;
|
|
18591
18662
|
}
|
|
18592
18663
|
if (this.eatKw("set")) {
|
|
18593
|
-
this.ident(true);
|
|
18594
|
-
if (this.
|
|
18664
|
+
const key = this.ident(true).toLowerCase();
|
|
18665
|
+
if (this.eatKw("from")) {
|
|
18666
|
+
this.expectKw("current");
|
|
18667
|
+
settings[key] = "__from_current__";
|
|
18668
|
+
} else {
|
|
18669
|
+
if (!this.eatOp("=")) this.expectKw("to");
|
|
18670
|
+
const values = [];
|
|
18671
|
+
do {
|
|
18672
|
+
const value = this.next();
|
|
18673
|
+
if (!["string", "ident", "quoted_ident", "number"].includes(value.type))
|
|
18674
|
+
this.errorAt(value, "expected setting value");
|
|
18675
|
+
values.push(
|
|
18676
|
+
key === "search_path" && /[^a-z0-9_]/.test(value.value) ? `"${value.value.replaceAll('"', '""')}"` : value.value
|
|
18677
|
+
);
|
|
18678
|
+
} while (this.eatPunct(","));
|
|
18679
|
+
settings[key] = values.join(", ");
|
|
18680
|
+
}
|
|
18595
18681
|
continue;
|
|
18596
18682
|
}
|
|
18597
18683
|
if (this.eatKw("window")) throw unsupported("window functions via CREATE FUNCTION");
|
|
@@ -18671,7 +18757,8 @@ var Parser = class {
|
|
|
18671
18757
|
body,
|
|
18672
18758
|
sqlBody,
|
|
18673
18759
|
volatility,
|
|
18674
|
-
strict: strict2
|
|
18760
|
+
strict: strict2,
|
|
18761
|
+
settings
|
|
18675
18762
|
};
|
|
18676
18763
|
}
|
|
18677
18764
|
parseCreateTrigger(orReplace, constraint) {
|
|
@@ -18780,8 +18867,8 @@ var Parser = class {
|
|
|
18780
18867
|
type: "alter_sequence",
|
|
18781
18868
|
name,
|
|
18782
18869
|
ifExists,
|
|
18783
|
-
options: {
|
|
18784
|
-
|
|
18870
|
+
options: {},
|
|
18871
|
+
renameTo: to
|
|
18785
18872
|
};
|
|
18786
18873
|
}
|
|
18787
18874
|
if (this.atKw("set") && (this.atKw("logged", 1) || this.atKw("unlogged", 1))) {
|
|
@@ -21357,7 +21444,20 @@ function runFunctionBody(env, fn, args) {
|
|
|
21357
21444
|
}
|
|
21358
21445
|
return last;
|
|
21359
21446
|
}
|
|
21447
|
+
function withFunctionSettings(env, fn, run) {
|
|
21448
|
+
const state = env.ctx.state;
|
|
21449
|
+
const saved = new Map(state.localSettings);
|
|
21450
|
+
for (const [name, value] of Object.entries(fn.settings ?? {})) state.localSettings.set(name, value);
|
|
21451
|
+
try {
|
|
21452
|
+
return run();
|
|
21453
|
+
} finally {
|
|
21454
|
+
state.localSettings = saved;
|
|
21455
|
+
}
|
|
21456
|
+
}
|
|
21360
21457
|
function callSqlFunctionScalar(env, fn, args) {
|
|
21458
|
+
return withFunctionSettings(env, fn, () => callSqlFunctionScalarBody(env, fn, args));
|
|
21459
|
+
}
|
|
21460
|
+
function callSqlFunctionScalarBody(env, fn, args) {
|
|
21361
21461
|
const retT = fn.returns ?? "text";
|
|
21362
21462
|
if (fn.language === "plpgsql") {
|
|
21363
21463
|
return callPlpgsqlScalar(env, fn, args);
|
|
@@ -21387,6 +21487,9 @@ function callSqlFunctionScalar(env, fn, args) {
|
|
|
21387
21487
|
return castTo(env.ctx, tv(rawT, raw), retT, {});
|
|
21388
21488
|
}
|
|
21389
21489
|
function callSqlFunctionSet(env, fn, args) {
|
|
21490
|
+
return withFunctionSettings(env, fn, () => callSqlFunctionSetBody(env, fn, args));
|
|
21491
|
+
}
|
|
21492
|
+
function callSqlFunctionSetBody(env, fn, args) {
|
|
21390
21493
|
if (fn.language === "plpgsql") {
|
|
21391
21494
|
return callPlpgsqlSet(env, fn, args);
|
|
21392
21495
|
}
|
|
@@ -21541,15 +21644,17 @@ function executeRecursiveCte(env, cte, query) {
|
|
|
21541
21644
|
return rel3;
|
|
21542
21645
|
}
|
|
21543
21646
|
function renameWithAliases(rel3, label, colAliases, what) {
|
|
21544
|
-
|
|
21647
|
+
const visibleWidth = rel3.columns.filter((column) => !column.system).length;
|
|
21648
|
+
if (colAliases && colAliases.length > visibleWidth) {
|
|
21545
21649
|
throw pgError(
|
|
21546
21650
|
"invalid_column_reference",
|
|
21547
|
-
`table "${what}" has ${
|
|
21651
|
+
`table "${what}" has ${visibleWidth} columns available but ${colAliases.length} columns specified`,
|
|
21548
21652
|
"42P10"
|
|
21549
21653
|
);
|
|
21550
21654
|
}
|
|
21551
21655
|
const columns = rel3.columns.map((c, i) => ({
|
|
21552
|
-
|
|
21656
|
+
...c,
|
|
21657
|
+
name: c.system ? c.name : colAliases?.[i] ?? c.name,
|
|
21553
21658
|
type: c.type === UNKNOWN ? "text" : c.type,
|
|
21554
21659
|
table: label
|
|
21555
21660
|
}));
|
|
@@ -21570,8 +21675,11 @@ function materializeItem(env, item, scope) {
|
|
|
21570
21675
|
if (table) {
|
|
21571
21676
|
table.materializeSlab();
|
|
21572
21677
|
const rel3 = {
|
|
21573
|
-
columns:
|
|
21574
|
-
|
|
21678
|
+
columns: [
|
|
21679
|
+
...table.columns.map((c) => ({ name: c.name, type: c.type.id, table: label })),
|
|
21680
|
+
{ name: "xmin", type: "xid", table: label, hidden: true, system: true }
|
|
21681
|
+
],
|
|
21682
|
+
rows: table.rows.map((row) => [...row, table.rowXmins.get(row) ?? 2])
|
|
21575
21683
|
};
|
|
21576
21684
|
return { rel: renameWithAliases(rel3, label, item.colAliases, label), rangeVars: /* @__PURE__ */ new Set([label]) };
|
|
21577
21685
|
}
|
|
@@ -22201,7 +22309,7 @@ function resolveColIdx(columns, parts) {
|
|
|
22201
22309
|
let found = -1;
|
|
22202
22310
|
for (let i = 0; i < columns.length; i++) {
|
|
22203
22311
|
const c = columns[i];
|
|
22204
|
-
if (!c.hidden && c.name === name2) {
|
|
22312
|
+
if ((!c.hidden || c.system) && c.name === name2) {
|
|
22205
22313
|
if (found !== -1) return null;
|
|
22206
22314
|
found = i;
|
|
22207
22315
|
}
|
|
@@ -22785,7 +22893,7 @@ function expandTargets(core, columns) {
|
|
|
22785
22893
|
let any = false;
|
|
22786
22894
|
for (let i = 0; i < columns.length; i++) {
|
|
22787
22895
|
const c = columns[i];
|
|
22788
|
-
if (c.table === label) {
|
|
22896
|
+
if (c.table === label && !c.system) {
|
|
22789
22897
|
any = true;
|
|
22790
22898
|
items.push({ name: c.name, kind: "col", colIdx: i, expr: null, hidden: false });
|
|
22791
22899
|
}
|
|
@@ -23390,6 +23498,7 @@ var TransactionManager = class {
|
|
|
23390
23498
|
this.base = null;
|
|
23391
23499
|
this.savepoints = [];
|
|
23392
23500
|
this.state.inTransaction = false;
|
|
23501
|
+
this.state.transactionXid = null;
|
|
23393
23502
|
this.state.localSettings.clear();
|
|
23394
23503
|
this.state.pendingTriggers = [];
|
|
23395
23504
|
this.state.constraintModes.clear();
|
|
@@ -23402,6 +23511,7 @@ var TransactionManager = class {
|
|
|
23402
23511
|
this.base = null;
|
|
23403
23512
|
this.savepoints = [];
|
|
23404
23513
|
this.state.inTransaction = false;
|
|
23514
|
+
this.state.transactionXid = null;
|
|
23405
23515
|
this.state.localSettings.clear();
|
|
23406
23516
|
this.state.pendingTriggers = [];
|
|
23407
23517
|
this.state.constraintModes.clear();
|
|
@@ -23436,6 +23546,7 @@ var TransactionManager = class {
|
|
|
23436
23546
|
this.base = null;
|
|
23437
23547
|
this.savepoints = [];
|
|
23438
23548
|
this.state.inTransaction = false;
|
|
23549
|
+
this.state.transactionXid = null;
|
|
23439
23550
|
this.freezeDepth = 0;
|
|
23440
23551
|
}
|
|
23441
23552
|
};
|
|
@@ -23639,6 +23750,7 @@ function applyOnConflictUpdate(env, table, label, existingIdx, newRow, sets, whe
|
|
|
23639
23750
|
const updated = existing.slice();
|
|
23640
23751
|
applyUpdateSets(env, table, sets, scope, updated);
|
|
23641
23752
|
computeGeneratedColumns(env, table, updated);
|
|
23753
|
+
table.rowXmins.set(updated, env.ctx.rowVersionXid());
|
|
23642
23754
|
rows[existingIdx] = updated;
|
|
23643
23755
|
indexUpdateRow(env, table, existingIdx, existing, updated);
|
|
23644
23756
|
checkNotNull(env, table, updated);
|
|
@@ -23724,6 +23836,7 @@ function executeInsert(env0, stmt) {
|
|
|
23724
23836
|
checkChecks(env, table, newRow);
|
|
23725
23837
|
checkUnique(env, table, newRow, rows.length);
|
|
23726
23838
|
checkForeignKeys(env, table, newRow);
|
|
23839
|
+
table.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
23727
23840
|
rows.push(newRow);
|
|
23728
23841
|
indexInsertRow(env, table, rows.length - 1, newRow);
|
|
23729
23842
|
insertedCount++;
|
|
@@ -23841,6 +23954,7 @@ function executeUpdate(env0, stmt) {
|
|
|
23841
23954
|
if (fired.row === null) continue;
|
|
23842
23955
|
const finalRow = fired.row;
|
|
23843
23956
|
computeGeneratedColumns(env, table, finalRow);
|
|
23957
|
+
table.rowXmins.set(finalRow, env.ctx.rowVersionXid());
|
|
23844
23958
|
rows[ri] = finalRow;
|
|
23845
23959
|
try {
|
|
23846
23960
|
checkNotNull(env, table, finalRow);
|
|
@@ -23960,6 +24074,7 @@ function handleReferencedDelete(env, table, row, depth) {
|
|
|
23960
24074
|
for (const c of con.columns) {
|
|
23961
24075
|
newRow[child.columnIndex(c)] = null;
|
|
23962
24076
|
}
|
|
24077
|
+
child.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
23963
24078
|
childRows[i] = newRow;
|
|
23964
24079
|
checkNotNull(env, child, newRow);
|
|
23965
24080
|
checkChecks(env, child, newRow);
|
|
@@ -23973,6 +24088,7 @@ function handleReferencedDelete(env, table, row, depth) {
|
|
|
23973
24088
|
const ci = child.columnIndex(c);
|
|
23974
24089
|
newRow[ci] = columnDefault(env, child, child.columns[ci]);
|
|
23975
24090
|
}
|
|
24091
|
+
child.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
23976
24092
|
childRows[i] = newRow;
|
|
23977
24093
|
checkNotNull(env, child, newRow);
|
|
23978
24094
|
checkChecks(env, child, newRow);
|
|
@@ -24010,6 +24126,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
|
|
|
24010
24126
|
const refIdx = table.columnIndex(con.refColumns[c]);
|
|
24011
24127
|
newRow[localIdx] = newRows[r][refIdx] ?? null;
|
|
24012
24128
|
}
|
|
24129
|
+
child.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
24013
24130
|
childRows[i] = newRow;
|
|
24014
24131
|
checkChecks(env, child, newRow);
|
|
24015
24132
|
}
|
|
@@ -24019,6 +24136,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
|
|
|
24019
24136
|
for (const i of idxs) {
|
|
24020
24137
|
const newRow = childRows[i].slice();
|
|
24021
24138
|
for (const c of con.columns) newRow[child.columnIndex(c)] = null;
|
|
24139
|
+
child.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
24022
24140
|
childRows[i] = newRow;
|
|
24023
24141
|
checkNotNull(env, child, newRow);
|
|
24024
24142
|
}
|
|
@@ -24031,6 +24149,7 @@ function handleReferencedUpdate(env, table, oldRows, newRows) {
|
|
|
24031
24149
|
const ci = child.columnIndex(c);
|
|
24032
24150
|
newRow[ci] = columnDefault(env, child, child.columns[ci]);
|
|
24033
24151
|
}
|
|
24152
|
+
child.rowXmins.set(newRow, env.ctx.rowVersionXid());
|
|
24034
24153
|
childRows[i] = newRow;
|
|
24035
24154
|
checkForeignKeys(env, child, newRow);
|
|
24036
24155
|
}
|
|
@@ -27075,13 +27194,63 @@ function executeCreateSequence(env, stmt) {
|
|
|
27075
27194
|
schema.sequences.set(name, seq);
|
|
27076
27195
|
return commandResult("CREATE SEQUENCE", 0);
|
|
27077
27196
|
}
|
|
27197
|
+
function renameSequenceReferences(env, seq, newName) {
|
|
27198
|
+
const state = env.ctx.state;
|
|
27199
|
+
const matches3 = (value) => {
|
|
27200
|
+
try {
|
|
27201
|
+
const input = parseRelationInput(value);
|
|
27202
|
+
if ("oid" in input) return input.oid === seq.oid;
|
|
27203
|
+
return state.findSequence(input.parts)?.oid === seq.oid;
|
|
27204
|
+
} catch {
|
|
27205
|
+
return false;
|
|
27206
|
+
}
|
|
27207
|
+
};
|
|
27208
|
+
const rewrite = (value) => {
|
|
27209
|
+
if (Array.isArray(value)) return value.map(rewrite);
|
|
27210
|
+
if (value === null || typeof value !== "object") return value;
|
|
27211
|
+
const node = value;
|
|
27212
|
+
const updated = Object.fromEntries(Object.entries(node).map(([key, child]) => [key, rewrite(child)]));
|
|
27213
|
+
const rewriteLiteral = (literal2) => {
|
|
27214
|
+
if (literal2 && typeof literal2 === "object" && "type" in literal2 && literal2.type === "string_lit" && "value" in literal2 && typeof literal2.value === "string" && matches3(literal2.value)) {
|
|
27215
|
+
return { ...literal2, value: [seq.schema, newName].map(quoteIdent).join(".") };
|
|
27216
|
+
}
|
|
27217
|
+
return literal2;
|
|
27218
|
+
};
|
|
27219
|
+
if (node.type === "func" && Array.isArray(node.name) && ["nextval", "currval", "setval"].includes(String(node.name.at(-1))) && Array.isArray(updated.args))
|
|
27220
|
+
updated.args[0] = rewriteLiteral(updated.args[0]);
|
|
27221
|
+
if (node.type === "cast" && node.target && typeof node.target === "object" && "parts" in node.target && Array.isArray(node.target.parts) && node.target.parts.at(-1) === "regclass")
|
|
27222
|
+
updated.expr = rewriteLiteral(updated.expr);
|
|
27223
|
+
return updated;
|
|
27224
|
+
};
|
|
27225
|
+
for (const schema of state.schemas.values()) {
|
|
27226
|
+
for (const table of [...schema.tables.values()]) {
|
|
27227
|
+
const writable = state.ensureWritableTable(table);
|
|
27228
|
+
for (const column of writable.columns) {
|
|
27229
|
+
if (column.defaultExpr) column.defaultExpr = rewrite(column.defaultExpr);
|
|
27230
|
+
if (column.identity?.sequence === `${seq.schema}.${seq.name}`)
|
|
27231
|
+
column.identity = { ...column.identity, sequence: `${seq.schema}.${newName}` };
|
|
27232
|
+
}
|
|
27233
|
+
}
|
|
27234
|
+
}
|
|
27235
|
+
}
|
|
27078
27236
|
function executeAlterSequence(env, stmt) {
|
|
27079
27237
|
const state = env.ctx.state;
|
|
27080
|
-
const
|
|
27081
|
-
if (!
|
|
27238
|
+
const foundSequence = state.findSequence(stmt.name);
|
|
27239
|
+
if (!foundSequence) {
|
|
27082
27240
|
if (stmt.ifExists) return commandResult("ALTER SEQUENCE", 0);
|
|
27083
27241
|
throw pgError("undefined_table", `relation "${stmt.name.join(".")}" does not exist`, "42P01");
|
|
27084
27242
|
}
|
|
27243
|
+
const seq = state.ensureWritableSequence(foundSequence);
|
|
27244
|
+
if (stmt.renameTo) {
|
|
27245
|
+
const schema = state.getSchema(seq.schema);
|
|
27246
|
+
if (schema.hasRelation(stmt.renameTo))
|
|
27247
|
+
throw pgError("duplicate_table", `relation "${stmt.renameTo}" already exists`, "42P07");
|
|
27248
|
+
renameSequenceReferences(env, seq, stmt.renameTo);
|
|
27249
|
+
schema.sequences.delete(seq.name);
|
|
27250
|
+
seq.name = stmt.renameTo;
|
|
27251
|
+
schema.sequences.set(seq.name, seq);
|
|
27252
|
+
return commandResult("ALTER SEQUENCE", 0);
|
|
27253
|
+
}
|
|
27085
27254
|
const o = stmt.options;
|
|
27086
27255
|
if (o.logged !== void 0) {
|
|
27087
27256
|
setSequenceLogged(state, seq, o.logged);
|
|
@@ -27139,7 +27308,7 @@ function buildColumn(env, schema, tableName2, def, out, taken) {
|
|
|
27139
27308
|
col.defaultExpr = {
|
|
27140
27309
|
type: "func",
|
|
27141
27310
|
name: ["nextval"],
|
|
27142
|
-
args: [{ type: "string_lit", value:
|
|
27311
|
+
args: [{ type: "string_lit", value: [schema.name, seqName].map(quoteIdent).join(".") }]
|
|
27143
27312
|
};
|
|
27144
27313
|
}
|
|
27145
27314
|
for (const c of def.constraints) {
|
|
@@ -27392,7 +27561,11 @@ function executeCreateTableAs(env, stmt) {
|
|
|
27392
27561
|
const table = new TableData(schema.name, name, columns, state.nextOid(), stmt.temp);
|
|
27393
27562
|
table.unlogged = stmt.unlogged === true;
|
|
27394
27563
|
if (stmt.withData) {
|
|
27395
|
-
for (const row of rel3.rows)
|
|
27564
|
+
for (const row of rel3.rows) {
|
|
27565
|
+
const stored = row.slice();
|
|
27566
|
+
table.rowXmins.set(stored, env.ctx.rowVersionXid());
|
|
27567
|
+
table.mutableRows().push(stored);
|
|
27568
|
+
}
|
|
27396
27569
|
}
|
|
27397
27570
|
schema.tables.set(name, table);
|
|
27398
27571
|
const count2 = stmt.withData ? table.rowCount() : 0;
|
|
@@ -27661,6 +27834,12 @@ function executeCreateFunction(env, stmt) {
|
|
|
27661
27834
|
language: lang,
|
|
27662
27835
|
body,
|
|
27663
27836
|
rawBody: stmt.body,
|
|
27837
|
+
settings: Object.fromEntries(
|
|
27838
|
+
Object.entries(stmt.settings ?? {}).map(([key, value]) => [
|
|
27839
|
+
key,
|
|
27840
|
+
value === "__from_current__" ? state.getSetting(key) ?? "" : value
|
|
27841
|
+
])
|
|
27842
|
+
),
|
|
27664
27843
|
strict: stmt.strict,
|
|
27665
27844
|
oid: state.nextOid()
|
|
27666
27845
|
};
|
|
@@ -27845,6 +28024,7 @@ function executeAlterTable(env, stmt) {
|
|
|
27845
28024
|
}
|
|
27846
28025
|
const next = row.slice();
|
|
27847
28026
|
next[idx] = nv;
|
|
28027
|
+
table.rowXmins.set(next, table.rowXmins.get(rows[i]) ?? 2);
|
|
27848
28028
|
rows[i] = next;
|
|
27849
28029
|
}
|
|
27850
28030
|
col.type = resolved.column;
|
|
@@ -27946,6 +28126,11 @@ function executeAlterTable(env, stmt) {
|
|
|
27946
28126
|
);
|
|
27947
28127
|
}
|
|
27948
28128
|
table.columns[idx].name = action.to;
|
|
28129
|
+
for (const seq of [...schema.sequences.values()]) {
|
|
28130
|
+
if (seq.ownedBy?.table === table.name && seq.ownedBy.column === action.from) {
|
|
28131
|
+
state.ensureWritableSequence(seq).ownedBy = { table: table.name, column: action.to };
|
|
28132
|
+
}
|
|
28133
|
+
}
|
|
27949
28134
|
for (const con of table.constraints) {
|
|
27950
28135
|
if (con.kind === "primary_key" || con.kind === "unique" || con.kind === "foreign_key") {
|
|
27951
28136
|
con.columns = con.columns.map((c) => c === action.from ? action.to : c);
|
|
@@ -27982,6 +28167,11 @@ function executeAlterTable(env, stmt) {
|
|
|
27982
28167
|
throw pgError("duplicate_table", `relation "${action.to}" already exists`, "42P07");
|
|
27983
28168
|
}
|
|
27984
28169
|
const oldName = table.name;
|
|
28170
|
+
for (const seq of [...schema.sequences.values()]) {
|
|
28171
|
+
if (seq.ownedBy?.table === oldName) {
|
|
28172
|
+
state.ensureWritableSequence(seq).ownedBy = { ...seq.ownedBy, table: action.to };
|
|
28173
|
+
}
|
|
28174
|
+
}
|
|
27985
28175
|
schema.tables.delete(table.name);
|
|
27986
28176
|
table.name = action.to;
|
|
27987
28177
|
schema.tables.set(action.to, table);
|