okmodel 0.1.0 → 0.2.1
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 +237 -19
- package/dist/adapters/capabilities.d.ts +34 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +17 -0
- package/dist/adapters/pg/bunsql.d.ts +60 -0
- package/dist/adapters/pg/call.d.ts +2 -12
- package/dist/adapters/pg/nodepostgres-extra.d.ts +18 -0
- package/dist/adapters/pg/nodepostgres.d.ts +57 -0
- package/dist/adapters/pg/postgres-extra.d.ts +50 -1
- package/dist/adapters/pg/postgresjs.d.ts +0 -4
- package/dist/adapters/pg/session.d.ts +94 -0
- package/dist/adapters/pg/watch.d.ts +18 -0
- package/dist/contracts/catalog/object.d.ts +1 -0
- package/dist/contracts/catalog/types.d.ts +15 -8
- package/dist/contracts/connection.d.ts +13 -0
- package/dist/contracts/driver.d.ts +18 -5
- package/dist/contracts/error.d.ts +17 -1
- package/dist/contracts/generator.d.ts +63 -0
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/internal.js +11 -9
- package/dist/contracts/location.d.ts +24 -0
- package/dist/contracts/rows.d.ts +15 -0
- package/dist/dialects/pg/archive-bind.d.ts +22 -0
- package/dist/dialects/pg/column.d.ts +126 -9
- package/dist/dialects/pg/compile.d.ts +1 -0
- package/dist/dialects/pg/custom.d.ts +6 -0
- package/dist/dialects/pg/filter-parse.d.ts +19 -0
- package/dist/dialects/pg/filters.d.ts +19 -0
- package/dist/dialects/pg/index.d.ts +11 -2
- package/dist/dialects/pg/index.js +159 -1063
- package/dist/dialects/pg/keys.d.ts +19 -3
- package/dist/dialects/pg/model.d.ts +223 -2
- package/dist/dialects/pg/operator-fit.d.ts +86 -0
- package/dist/dialects/pg/operators.d.ts +39 -1
- package/dist/dialects/pg/ops/arr-append.d.ts +9 -0
- package/dist/dialects/pg/ops/arr-ns.d.ts +7 -0
- package/dist/dialects/pg/ops/arr-remove.d.ts +9 -0
- package/dist/dialects/pg/ops/containedBy.d.ts +9 -0
- package/dist/dialects/pg/ops/contains.d.ts +8 -4
- package/dist/dialects/pg/ops/hasAnyKey.d.ts +8 -0
- package/dist/dialects/pg/ops/hasKey.d.ts +8 -0
- package/dist/dialects/pg/ops/json-ns.d.ts +6 -0
- package/dist/dialects/pg/ops/json-set.d.ts +12 -0
- package/dist/dialects/pg/ops/matches.d.ts +16 -0
- package/dist/dialects/pg/ops/overlaps.d.ts +9 -0
- package/dist/dialects/pg/ops/path.d.ts +13 -0
- package/dist/dialects/pg/preset.d.ts +76 -0
- package/dist/dialects/pg/relations.d.ts +59 -2
- package/dist/dialects/pg/schema.d.ts +109 -5
- package/dist/dialects/pg/table.d.ts +130 -11
- package/dist/dialects/pg/temporal.d.ts +9 -7
- package/dist/dialects/pg/tenancy.d.ts +94 -0
- package/dist/dialects/pg/through.d.ts +21 -0
- package/dist/dialects/pg/trait.d.ts +141 -0
- package/dist/dialects/pg/where.d.ts +37 -0
- package/dist/runtime/aggregate.d.ts +25 -0
- package/dist/runtime/archive-handle.d.ts +18 -0
- package/dist/runtime/archive-rules.d.ts +12 -0
- package/dist/runtime/archive.d.ts +73 -0
- package/dist/runtime/batch.d.ts +20 -0
- package/dist/runtime/client.d.ts +114 -5
- package/dist/runtime/conflict.d.ts +2 -1
- package/dist/runtime/exposure.d.ts +27 -0
- package/dist/runtime/hookm.d.ts +39 -0
- package/dist/runtime/ids/index.d.ts +11 -0
- package/dist/runtime/ids/index.js +193 -0
- package/dist/runtime/ids/okid-extended.d.ts +19 -0
- package/dist/runtime/ids/okid-generator.d.ts +21 -0
- package/dist/runtime/ids/okid-shared.d.ts +89 -0
- package/dist/runtime/ids/okid.d.ts +24 -0
- package/dist/runtime/ids/uuid.d.ts +21 -0
- package/dist/runtime/lock.d.ts +22 -0
- package/dist/runtime/operator-sql.d.ts +8 -0
- package/dist/runtime/page.d.ts +25 -0
- package/dist/runtime/pg/bun.d.ts +30 -0
- package/dist/runtime/pg/bun.js +52 -0
- package/dist/runtime/pg/pg.d.ts +29 -0
- package/dist/runtime/pg/pg.js +52 -0
- package/dist/runtime/pg/pglite.js +16 -17
- package/dist/runtime/pg/postgresjs.js +14 -24
- package/dist/runtime/plan.d.ts +146 -7
- package/dist/runtime/preset-stack.d.ts +17 -0
- package/dist/runtime/presets.d.ts +48 -0
- package/dist/runtime/read-stream.d.ts +26 -0
- package/dist/runtime/safety/index.d.ts +159 -0
- package/dist/runtime/safety/index.js +294 -0
- package/dist/runtime/safety-hook.d.ts +42 -0
- package/dist/runtime/server-check.d.ts +34 -0
- package/dist/runtime/tenancy/index.d.ts +37 -0
- package/dist/runtime/tenancy/index.js +365 -0
- package/dist/runtime/trait-read.d.ts +30 -0
- package/dist/runtime/traits/index.d.ts +128 -0
- package/dist/runtime/traits/index.js +509 -0
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/tx.d.ts +22 -7
- package/dist/runtime/types.d.ts +390 -25
- package/dist/runtime/validate/bind.d.ts +11 -0
- package/dist/runtime/validate/closed.d.ts +16 -0
- package/dist/runtime/validate/engine.d.ts +41 -0
- package/dist/runtime/validate/hook.d.ts +17 -0
- package/dist/runtime/validate/index.d.ts +21 -0
- package/dist/runtime/validate/index.js +175 -0
- package/dist/runtime/validate/load.d.ts +13 -0
- package/dist/runtime/validate/places.d.ts +41 -0
- package/dist/runtime/validate/rules.d.ts +108 -0
- package/dist/runtime/validate/surface.d.ts +102 -0
- package/dist/runtime/write.d.ts +30 -5
- package/dist/shared/{fgqgsb5z.js → 0qkxbm1f.js} +2 -2
- package/dist/shared/0v1g424p.js +26 -0
- package/dist/shared/10tjenmd.js +210 -0
- package/dist/shared/{tb4ze1tr.js → 1dp5knt7.js} +95 -10
- package/dist/shared/38crxbgn.js +229 -0
- package/dist/shared/3a5ne750.js +158 -0
- package/dist/shared/4b9da9d7.js +138 -0
- package/dist/shared/4e4c6vq2.js +62 -0
- package/dist/shared/{ckb1p0a4.js → 4jzzghr6.js} +416 -83
- package/dist/shared/{ycwz42az.js → 52szzakm.js} +6 -26
- package/dist/shared/{kynx7vbm.js → 5z4q2zda.js} +604 -21
- package/dist/shared/672t5vym.js +12 -0
- package/dist/shared/7m88x3e9.js +45 -0
- package/dist/shared/7mht49yd.js +1091 -0
- package/dist/shared/{mtkwv8wx.js → 7p2wbz7f.js} +1 -1
- package/dist/shared/83eaefwr.js +10 -0
- package/dist/shared/96ps4g1z.js +32 -0
- package/dist/shared/97g2jnrw.js +76 -0
- package/dist/shared/{xy79j5hq.js → 9k6vetvf.js} +25 -36
- package/dist/shared/{w8samvk1.js → a1gdp7j9.js} +1 -1
- package/dist/shared/a5sxz52y.js +13 -0
- package/dist/shared/{dynq16vb.js → b066g80h.js} +8 -6
- package/dist/shared/cstgkzdx.js +154 -0
- package/dist/shared/{ax16zpvm.js → d04j53v8.js} +6 -5
- package/dist/shared/{y9m15dfv.js → d2whjz4k.js} +15 -9
- package/dist/shared/dd4f8mnx.js +307 -0
- package/dist/shared/e6wg00wd.js +164 -0
- package/dist/shared/e7a268fy.js +195 -0
- package/dist/shared/{vkm3q4x1.js → eaxwrgby.js} +4 -3
- package/dist/shared/{7ck0dtry.js → ecdzej21.js} +160 -81
- package/dist/shared/{px90pf9p.js → edwqqryd.js} +21 -16
- package/dist/shared/{g2v6gnc8.js → eqmtn1bj.js} +57 -5
- package/dist/shared/g1b5zzj9.js +75 -0
- package/dist/shared/g9f0zqce.js +376 -0
- package/dist/shared/ge03bfca.js +64 -0
- package/dist/shared/{79ggp945.js → jns2wx4t.js} +55 -12
- package/dist/shared/k3ted12y.js +33 -0
- package/dist/shared/k7dwkhvq.js +31 -0
- package/dist/shared/{hd293j9g.js → kyxb62mc.js} +14 -2
- package/dist/shared/m9tv7xn7.js +404 -0
- package/dist/shared/mrq77sgw.js +55 -0
- package/dist/shared/mxmcdsh5.js +450 -0
- package/dist/shared/n442h54c.js +14 -0
- package/dist/shared/n47rd821.js +442 -0
- package/dist/shared/n4jqhpqz.js +27 -0
- package/dist/shared/{mkjmqjv4.js → nfzc5z0w.js} +2 -104
- package/dist/shared/nsva2a6b.js +42 -0
- package/dist/shared/ps43134h.js +69 -0
- package/dist/shared/{8b04d28c.js → py3nh7jy.js} +2 -2
- package/dist/shared/qa5dsc7z.js +86 -0
- package/dist/shared/{g5fhqmwe.js → qxa0yb9j.js} +6 -4
- package/dist/shared/r6nwkxnt.js +86 -0
- package/dist/shared/{4j72449d.js → rtnaqre0.js} +3 -3
- package/dist/shared/rwmrejjz.js +26 -0
- package/dist/shared/{zhc5vmgf.js → s6gqfn5j.js} +2 -2
- package/dist/shared/sacqha1y.js +70 -0
- package/dist/shared/{qk3hxfja.js → sfh4pk75.js} +5 -5
- package/dist/shared/{j5cn2963.js → t4tbsy9h.js} +13 -64
- package/dist/shared/{ngk8gj61.js → tbszx8mz.js} +15 -1
- package/dist/shared/v18jva5b.js +90 -0
- package/dist/shared/x9v1qgeb.js +201 -0
- package/dist/shared/xatfg0gn.js +193 -0
- package/dist/shared/xkz8g6by.js +225 -0
- package/dist/shared/{f5thc1s0.js → xrfnq5k8.js} +237 -418
- package/dist/shared/ytg3xf9p.js +149 -0
- package/dist/shared/yw5c610b.js +48 -0
- package/dist/shared/zd93g3rq.js +31 -0
- package/dist/shared/zt8y0wzb.js +132 -0
- package/dist/shared/{p0cbr3g2.js → zwmqxb0g.js} +1 -1
- package/dist/tooling/cli.js +14 -10
- package/dist/tooling/migrate/commands.d.ts +10 -0
- package/dist/tooling/migrate/engine.d.ts +36 -0
- package/package.json +56 -4
- package/dist/shared/gk7cs7sp.js +0 -566
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runWrite
|
|
3
|
+
} from "./n4jqhpqz.js";
|
|
4
|
+
import {
|
|
5
|
+
stack
|
|
6
|
+
} from "./672t5vym.js";
|
|
7
|
+
import {
|
|
8
|
+
withRowFilters,
|
|
9
|
+
archiveRules,
|
|
10
|
+
emitWhere,
|
|
11
|
+
rejectKeys,
|
|
12
|
+
indexes,
|
|
13
|
+
quote,
|
|
14
|
+
isRecord,
|
|
15
|
+
registerFailFix,
|
|
16
|
+
fail
|
|
17
|
+
} from "./xrfnq5k8.js";
|
|
18
|
+
import {
|
|
19
|
+
safetyInstalled,
|
|
20
|
+
runSafety
|
|
21
|
+
} from "./a5sxz52y.js";
|
|
22
|
+
import {
|
|
23
|
+
isOperator
|
|
24
|
+
} from "./cn0n68ft.js";
|
|
25
|
+
import {
|
|
26
|
+
OkmError2
|
|
27
|
+
} from "./52szzakm.js";
|
|
28
|
+
|
|
29
|
+
// src/runtime/archive.ts
|
|
30
|
+
registerFailFix("OKM1102", "Pass where, or call .all(reason) to match every row.");
|
|
31
|
+
var OPTIONS = ["allow", "expect", "returning", "signal", "timeout"];
|
|
32
|
+
async function executeArchive(host, op, table, input, options, mods, scope) {
|
|
33
|
+
const prepared = prepareArchive(host.schema, op, table, input, options, mods, scope);
|
|
34
|
+
const results = await runWrite(host, prepared.statements, prepared.options);
|
|
35
|
+
return prepared.finish(results);
|
|
36
|
+
}
|
|
37
|
+
function prepareArchive(schema, op, table, input, options, mods, scope) {
|
|
38
|
+
const planned = planArchive(schema, op, table, input, options, mods, scope);
|
|
39
|
+
return {
|
|
40
|
+
statements: [{ text: planned.text, params: planned.params }],
|
|
41
|
+
options: planned.call,
|
|
42
|
+
...op === "restore" ? { checked: "restore" } : planned.expect !== undefined ? { checked: "expect" } : {},
|
|
43
|
+
finish: (results) => finish(op, table, results[0], planned)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function explainArchive(schema, op, table, input, options, mods, scope) {
|
|
47
|
+
const planned = planArchive(schema, op, table, input, options, mods, scope);
|
|
48
|
+
return { statements: [{ text: planned.text, params: planned.params }] };
|
|
49
|
+
}
|
|
50
|
+
function planArchive(schema, op, tableName, input, options, mods, scope) {
|
|
51
|
+
const table = indexes(schema).get(tableName);
|
|
52
|
+
if (table === undefined)
|
|
53
|
+
fail("OKM1120", `Table ${tableName} is not in the schema.`);
|
|
54
|
+
const archive = table.model.archive;
|
|
55
|
+
if (archive === undefined) {
|
|
56
|
+
throw new OkmError2("OKM1052", `Table ${tableName} is not archivable.`, {
|
|
57
|
+
fix: { summary: "Call archive and restore only on an archivable table." }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const record = isRecord(options) ? options : {};
|
|
61
|
+
rejectKeys(record, OPTIONS, op);
|
|
62
|
+
const expect = expectOf(record.expect ?? mods.expect);
|
|
63
|
+
const read = readTarget(op, table, input, mods);
|
|
64
|
+
const target = { ...read, where: stack(read.where, mods.presets) };
|
|
65
|
+
const view = op === "restore" ? "only" : undefined;
|
|
66
|
+
noteArchive(table, view);
|
|
67
|
+
const statement = withRowFilters(scope, view, () => op === "archive" ? archiveSql(schema, table, target.where) : restoreSql(schema, table, target));
|
|
68
|
+
return {
|
|
69
|
+
text: statement.text,
|
|
70
|
+
params: statement.params,
|
|
71
|
+
expect,
|
|
72
|
+
archiveId: op === "archive" ? statement.archiveId : undefined,
|
|
73
|
+
call: callOptions(record)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function noteArchive(table, view) {
|
|
77
|
+
if (!safetyInstalled())
|
|
78
|
+
return;
|
|
79
|
+
const rules = archiveRules(table.model, view);
|
|
80
|
+
if (rules === undefined)
|
|
81
|
+
return;
|
|
82
|
+
runSafety(rules, undefined);
|
|
83
|
+
}
|
|
84
|
+
function readTarget(op, table, input, mods) {
|
|
85
|
+
if (!isRecord(input))
|
|
86
|
+
fail("OKM1121", `${op} expects { where }.`);
|
|
87
|
+
const keys = Object.keys(input);
|
|
88
|
+
if (op === "restore" && input.archiveId !== undefined) {
|
|
89
|
+
if (input.where !== undefined)
|
|
90
|
+
fail("OKM1120", "restore takes where or archiveId, not both.");
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
if (key !== "archiveId") {
|
|
93
|
+
fail("OKM1120", "restore accepts where or archiveId.");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (typeof input.archiveId !== "string" || input.archiveId.length === 0) {
|
|
97
|
+
fail("OKM1121", "restore archiveId must be a uuid.");
|
|
98
|
+
}
|
|
99
|
+
const idField = table.model.archive?.idField ?? "archiveId";
|
|
100
|
+
return { where: { [idField]: input.archiveId }, archiveId: input.archiveId };
|
|
101
|
+
}
|
|
102
|
+
for (const key of keys) {
|
|
103
|
+
if (key !== "where")
|
|
104
|
+
fail("OKM1120", `${op} accepts where.`);
|
|
105
|
+
}
|
|
106
|
+
requireFilter(table, input.where, mods, op);
|
|
107
|
+
return { where: input.where, archiveId: undefined };
|
|
108
|
+
}
|
|
109
|
+
function archiveSql(schema, table, where) {
|
|
110
|
+
const archive = table.model.archive;
|
|
111
|
+
if (archive === undefined) {
|
|
112
|
+
throw new OkmError2("OKM1052", `Table ${table.model.name} is not archivable.`);
|
|
113
|
+
}
|
|
114
|
+
const archiveId = globalThis.crypto.randomUUID();
|
|
115
|
+
const sql = new Sql;
|
|
116
|
+
const cascade = archive.cascade;
|
|
117
|
+
if (cascade.length === 0) {
|
|
118
|
+
sql.text("update ");
|
|
119
|
+
sql.text(quote(table.model.sql));
|
|
120
|
+
sql.text(" as t set ");
|
|
121
|
+
sql.text(quote(archive.at));
|
|
122
|
+
sql.text(" = now(), ");
|
|
123
|
+
sql.text(quote(archive.id));
|
|
124
|
+
sql.text(" = ");
|
|
125
|
+
sql.param(archiveId);
|
|
126
|
+
sql.text("::uuid");
|
|
127
|
+
emitWhere(schema, table, where, sql, "t", 0);
|
|
128
|
+
return { text: sql.join(), params: sql.params, archiveId };
|
|
129
|
+
}
|
|
130
|
+
const returned = returnedColumns(table, cascade);
|
|
131
|
+
sql.text("with archived as (update ");
|
|
132
|
+
sql.text(quote(table.model.sql));
|
|
133
|
+
sql.text(" as t set ");
|
|
134
|
+
sql.text(quote(archive.at));
|
|
135
|
+
sql.text(" = now(), ");
|
|
136
|
+
sql.text(quote(archive.id));
|
|
137
|
+
sql.text(" = ");
|
|
138
|
+
sql.param(archiveId);
|
|
139
|
+
sql.text("::uuid");
|
|
140
|
+
emitWhere(schema, table, where, sql, "t", 0);
|
|
141
|
+
sql.text(" returning ");
|
|
142
|
+
sql.text(returned.map((column) => `t.${quote(column)}`).join(", "));
|
|
143
|
+
sql.text(")");
|
|
144
|
+
for (let index = 0;index < cascade.length; index += 1) {
|
|
145
|
+
const child = cascade[index];
|
|
146
|
+
if (child === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
sql.text(", c");
|
|
149
|
+
sql.text(String(index));
|
|
150
|
+
sql.text(" as (");
|
|
151
|
+
childUpdate(schema, sql, child, "archived");
|
|
152
|
+
sql.text(")");
|
|
153
|
+
}
|
|
154
|
+
sql.text(" select count(*)::text from archived");
|
|
155
|
+
return { text: sql.join(), params: sql.params, archiveId };
|
|
156
|
+
}
|
|
157
|
+
function restoreSql(schema, table, target) {
|
|
158
|
+
const archive = table.model.archive;
|
|
159
|
+
if (archive === undefined) {
|
|
160
|
+
throw new OkmError2("OKM1052", `Table ${table.model.name} is not archivable.`);
|
|
161
|
+
}
|
|
162
|
+
const parents = archive.parents;
|
|
163
|
+
const cascade = archive.cascade;
|
|
164
|
+
const sql = new Sql;
|
|
165
|
+
if (parents.length === 0 && cascade.length === 0) {
|
|
166
|
+
sql.text("update ");
|
|
167
|
+
sql.text(quote(table.model.sql));
|
|
168
|
+
sql.text(" as t set ");
|
|
169
|
+
sql.text(quote(archive.at));
|
|
170
|
+
sql.text(" = null, ");
|
|
171
|
+
sql.text(quote(archive.id));
|
|
172
|
+
sql.text(" = null");
|
|
173
|
+
emitWhere(schema, table, target.where, sql, "t", 0);
|
|
174
|
+
return { text: sql.join(), params: sql.params, archiveId: "" };
|
|
175
|
+
}
|
|
176
|
+
const pk = primarySql(table);
|
|
177
|
+
const selected = new Set(pk);
|
|
178
|
+
for (const parent of parents)
|
|
179
|
+
for (const column of parent.local)
|
|
180
|
+
selected.add(column);
|
|
181
|
+
for (const child of cascade)
|
|
182
|
+
for (const column of child.remote)
|
|
183
|
+
selected.add(column);
|
|
184
|
+
selected.delete(archive.id);
|
|
185
|
+
sql.text("with target as (select ");
|
|
186
|
+
sql.text([...selected].map((column) => `t.${quote(column)}`).join(", "));
|
|
187
|
+
sql.text(", t.");
|
|
188
|
+
sql.text(quote(archive.id));
|
|
189
|
+
sql.text(' as "archive_id" from ');
|
|
190
|
+
sql.text(quote(table.model.sql));
|
|
191
|
+
sql.text(" t");
|
|
192
|
+
emitWhere(schema, table, target.where, sql, "t", 0);
|
|
193
|
+
sql.text(")");
|
|
194
|
+
if (parents.length > 0) {
|
|
195
|
+
sql.text(", blocked as (");
|
|
196
|
+
for (let index = 0;index < parents.length; index += 1) {
|
|
197
|
+
const parent = parents[index];
|
|
198
|
+
if (parent === undefined)
|
|
199
|
+
continue;
|
|
200
|
+
if (index > 0)
|
|
201
|
+
sql.text(" union all ");
|
|
202
|
+
sql.text("select ");
|
|
203
|
+
sql.text(quoteLiteral(parent.table));
|
|
204
|
+
sql.text(" as parent from target x join ");
|
|
205
|
+
sql.text(quote(parent.sql));
|
|
206
|
+
sql.text(" p on ");
|
|
207
|
+
sql.text(joinOn("p", parent.remote, "x", parent.local));
|
|
208
|
+
sql.text(" where p.");
|
|
209
|
+
sql.text(quote(parent.at));
|
|
210
|
+
sql.text(" is not null");
|
|
211
|
+
}
|
|
212
|
+
sql.text(")");
|
|
213
|
+
}
|
|
214
|
+
sql.text(", restored as (update ");
|
|
215
|
+
sql.text(quote(table.model.sql));
|
|
216
|
+
sql.text(" as u set ");
|
|
217
|
+
sql.text(quote(archive.at));
|
|
218
|
+
sql.text(" = null, ");
|
|
219
|
+
sql.text(quote(archive.id));
|
|
220
|
+
sql.text(" = null from target x where ");
|
|
221
|
+
sql.text(joinOn("u", pk, "x", pk));
|
|
222
|
+
if (parents.length > 0)
|
|
223
|
+
sql.text(" and not exists (select 1 from blocked)");
|
|
224
|
+
sql.text(" returning ");
|
|
225
|
+
sql.text([...new Set([...pk, ...cascade.flatMap((child) => [...child.remote])])].map((column) => `x.${quote(column)}`).join(", "));
|
|
226
|
+
sql.text(', x."archive_id")');
|
|
227
|
+
for (let index = 0;index < cascade.length; index += 1) {
|
|
228
|
+
const child = cascade[index];
|
|
229
|
+
if (child === undefined)
|
|
230
|
+
continue;
|
|
231
|
+
sql.text(", c");
|
|
232
|
+
sql.text(String(index));
|
|
233
|
+
sql.text(" as (");
|
|
234
|
+
childRestore(sql, child, parents.length > 0);
|
|
235
|
+
sql.text(")");
|
|
236
|
+
}
|
|
237
|
+
if (parents.length > 0) {
|
|
238
|
+
sql.text(" select coalesce((select parent from blocked order by parent limit 1), ''), (select count(*)::text from restored)");
|
|
239
|
+
} else {
|
|
240
|
+
sql.text(" select '', count(*)::text from restored");
|
|
241
|
+
}
|
|
242
|
+
return { text: sql.join(), params: sql.params, archiveId: "" };
|
|
243
|
+
}
|
|
244
|
+
function childUpdate(schema, sql, child, parent) {
|
|
245
|
+
const indexed = indexes(schema).get(child.table);
|
|
246
|
+
sql.text("update ");
|
|
247
|
+
sql.text(quote(child.sql));
|
|
248
|
+
sql.text(" as c set ");
|
|
249
|
+
sql.text(quote(child.at));
|
|
250
|
+
sql.text(" = now(), ");
|
|
251
|
+
sql.text(quote(child.id));
|
|
252
|
+
sql.text(" = $1::uuid from ");
|
|
253
|
+
sql.text(parent);
|
|
254
|
+
sql.text(" a where ");
|
|
255
|
+
sql.text(joinOn("c", child.local, "a", child.remote));
|
|
256
|
+
if (indexed === undefined) {
|
|
257
|
+
sql.text(" and c.");
|
|
258
|
+
sql.text(quote(child.at));
|
|
259
|
+
sql.text(" is null");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
emitWhere(schema, indexed, undefined, sql, "c", 0, true);
|
|
263
|
+
}
|
|
264
|
+
function childRestore(sql, child, blocked) {
|
|
265
|
+
sql.text("update ");
|
|
266
|
+
sql.text(quote(child.sql));
|
|
267
|
+
sql.text(" as c set ");
|
|
268
|
+
sql.text(quote(child.at));
|
|
269
|
+
sql.text(" = null, ");
|
|
270
|
+
sql.text(quote(child.id));
|
|
271
|
+
sql.text(" = null from restored r where ");
|
|
272
|
+
sql.text(joinOn("c", child.local, "r", child.remote));
|
|
273
|
+
sql.text(" and c.");
|
|
274
|
+
sql.text(quote(child.id));
|
|
275
|
+
sql.text(' = r."archive_id" and c.');
|
|
276
|
+
sql.text(quote(child.at));
|
|
277
|
+
sql.text(" is not null");
|
|
278
|
+
if (blocked)
|
|
279
|
+
sql.text(" and not exists (select 1 from blocked)");
|
|
280
|
+
}
|
|
281
|
+
function returnedColumns(table, cascade) {
|
|
282
|
+
const names = [...primarySql(table)];
|
|
283
|
+
for (const child of cascade) {
|
|
284
|
+
for (const column of child.remote)
|
|
285
|
+
if (!names.includes(column))
|
|
286
|
+
names.push(column);
|
|
287
|
+
}
|
|
288
|
+
return names;
|
|
289
|
+
}
|
|
290
|
+
function primarySql(table) {
|
|
291
|
+
const names = [];
|
|
292
|
+
for (const field of table.model.primary) {
|
|
293
|
+
const column = table.columns.get(field);
|
|
294
|
+
if (column !== undefined)
|
|
295
|
+
names.push(column.sql);
|
|
296
|
+
}
|
|
297
|
+
return names;
|
|
298
|
+
}
|
|
299
|
+
function joinOn(left, leftColumns, right, rightColumns) {
|
|
300
|
+
const parts = [];
|
|
301
|
+
for (let index = 0;index < leftColumns.length; index += 1) {
|
|
302
|
+
const local = leftColumns[index];
|
|
303
|
+
const remote = rightColumns[index];
|
|
304
|
+
if (local === undefined || remote === undefined)
|
|
305
|
+
continue;
|
|
306
|
+
parts.push(`${left}.${quote(local)} = ${right}.${quote(remote)}`);
|
|
307
|
+
}
|
|
308
|
+
return parts.length === 0 ? "true" : parts.join(" and ");
|
|
309
|
+
}
|
|
310
|
+
function finish(op, table, result, planned) {
|
|
311
|
+
if (op === "restore") {
|
|
312
|
+
const parent = result.rows[0]?.[0];
|
|
313
|
+
if (typeof parent === "string" && parent.length > 0) {
|
|
314
|
+
throw new OkmError2("invalid", `restore on ${table} references archived ${parent}. Restore ${parent} first.`, {
|
|
315
|
+
kind: "invalid",
|
|
316
|
+
table,
|
|
317
|
+
fix: { summary: `Restore ${parent} before this row.` }
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const count = countOf(op, result, planned);
|
|
322
|
+
if (planned.expect !== undefined && count !== planned.expect) {
|
|
323
|
+
throw new OkmError2("not_found", `${op} on ${table} changed ${String(count)} rows. expect was ${String(planned.expect)}.`, {
|
|
324
|
+
kind: "not_found",
|
|
325
|
+
table,
|
|
326
|
+
fix: { summary: "Change the filter, or pass the count this write produced." }
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (op === "archive")
|
|
330
|
+
return { count, archiveId: planned.archiveId ?? "" };
|
|
331
|
+
return { count };
|
|
332
|
+
}
|
|
333
|
+
function countOf(op, result, planned) {
|
|
334
|
+
if (op === "restore" && result.rows.length > 0) {
|
|
335
|
+
const cell = result.rows[0]?.[1] ?? result.rows[0]?.[0];
|
|
336
|
+
if (cell !== undefined && cell !== null && result.rows[0]?.length === 2)
|
|
337
|
+
return readCount(cell);
|
|
338
|
+
}
|
|
339
|
+
if (result.rows.length === 1 && result.rows[0]?.length === 1) {
|
|
340
|
+
const cell = result.rows[0][0];
|
|
341
|
+
if (cell !== undefined && cell !== null && planned.text.includes("count(*)"))
|
|
342
|
+
return readCount(cell);
|
|
343
|
+
}
|
|
344
|
+
return result.count;
|
|
345
|
+
}
|
|
346
|
+
function readCount(cell) {
|
|
347
|
+
const count = Number(cell);
|
|
348
|
+
return Number.isInteger(count) ? count : 0;
|
|
349
|
+
}
|
|
350
|
+
function expectOf(value) {
|
|
351
|
+
if (value === undefined)
|
|
352
|
+
return;
|
|
353
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
354
|
+
throw new OkmError2("invalid", "expect must be an integer from 0 up.", { kind: "invalid" });
|
|
355
|
+
}
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
function requireFilter(table, where, mods, op) {
|
|
359
|
+
if (mods.all !== undefined) {
|
|
360
|
+
if (mods.all.trim().length === 0) {
|
|
361
|
+
fail("OKM1102", `${op} on ${table.model.name} needs a where. .all needs a reason.`);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (isOperator(where))
|
|
366
|
+
return;
|
|
367
|
+
if (isRecord(where) && Object.keys(where).length > 0)
|
|
368
|
+
return;
|
|
369
|
+
fail("OKM1102", `${op} on ${table.model.name} needs a where. Pass a filter, or .all(reason).`);
|
|
370
|
+
}
|
|
371
|
+
function callOptions(input) {
|
|
372
|
+
const signal = input.signal;
|
|
373
|
+
const timeout = input.timeout;
|
|
374
|
+
if (signal === undefined && timeout === undefined)
|
|
375
|
+
return;
|
|
376
|
+
return {
|
|
377
|
+
...signal !== undefined ? { signal } : {},
|
|
378
|
+
...typeof timeout === "number" ? { timeout } : {}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function quoteLiteral(value) {
|
|
382
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
class Sql {
|
|
386
|
+
parts = [];
|
|
387
|
+
params = [];
|
|
388
|
+
text(value) {
|
|
389
|
+
this.parts.push(value);
|
|
390
|
+
}
|
|
391
|
+
param(encoded) {
|
|
392
|
+
this.params.push(encoded);
|
|
393
|
+
this.parts.push(`$${String(this.params.length)}`);
|
|
394
|
+
}
|
|
395
|
+
mark() {}
|
|
396
|
+
join() {
|
|
397
|
+
return this.parts.join("");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
export {
|
|
401
|
+
executeArchive,
|
|
402
|
+
explainArchive,
|
|
403
|
+
prepareArchive
|
|
404
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeRows,
|
|
3
|
+
readHandle,
|
|
4
|
+
readCall,
|
|
5
|
+
attachHttp
|
|
6
|
+
} from "./mxmcdsh5.js";
|
|
7
|
+
import {
|
|
8
|
+
fail
|
|
9
|
+
} from "./xrfnq5k8.js";
|
|
10
|
+
import {
|
|
11
|
+
OkmError2,
|
|
12
|
+
throwNamed2
|
|
13
|
+
} from "./52szzakm.js";
|
|
14
|
+
|
|
15
|
+
// src/runtime/lock.ts
|
|
16
|
+
var LOCKS = ["update", "share"];
|
|
17
|
+
var WAITS = ["nowait", "skip"];
|
|
18
|
+
function shown(value) {
|
|
19
|
+
return typeof value === "string" ? value : JSON.stringify(value) ?? "undefined";
|
|
20
|
+
}
|
|
21
|
+
function build(session, table, options, mods, view) {
|
|
22
|
+
try {
|
|
23
|
+
return plan(session, table, options, mods, view);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw error instanceof OkmError2 ? attachHttp(session.http, error) : error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function plan(session, table, options, mods, view) {
|
|
29
|
+
const { lock, wait, ...rest } = options;
|
|
30
|
+
if (session.tx === undefined) {
|
|
31
|
+
throw new OkmError2("OKM1830", "A row lock needs a transaction. find was called outside tx().", {
|
|
32
|
+
fix: { summary: "Move the find into tx(): the lock lasts until the transaction ends." }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (lock === undefined)
|
|
36
|
+
fail("OKM1121", "wait needs lock: pass lock: update or lock: share.");
|
|
37
|
+
if (typeof lock !== "string" || !LOCKS.includes(lock)) {
|
|
38
|
+
throwNamed2("OKM1120", shown(lock), LOCKS, `lock must be one of: ${LOCKS.join(", ")}.`);
|
|
39
|
+
}
|
|
40
|
+
if (wait !== undefined && (typeof wait !== "string" || !WAITS.includes(wait))) {
|
|
41
|
+
throwNamed2("OKM1120", shown(wait), WAITS, `wait must be one of: ${WAITS.join(", ")}.`);
|
|
42
|
+
}
|
|
43
|
+
const clause = ` for ${lock} of t${wait === undefined ? "" : wait === "skip" ? " skip locked" : " nowait"}`;
|
|
44
|
+
const call = {
|
|
45
|
+
...readCall("find", table, rest, mods.all, session.scope, session.schema.model[table], session.schema, view),
|
|
46
|
+
tail(sink) {
|
|
47
|
+
sink.text(clause);
|
|
48
|
+
sink.mark(`lock:${clause}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return readHandle(session, table, call, mods, (compiled, rows) => decodeRows(compiled, rows, table));
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
build
|
|
55
|
+
};
|