okmodel 0.6.0 → 0.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/README.md +100 -1131
- package/dist/adapters/capabilities.d.ts +3 -0
- package/dist/adapters/failure.d.ts +13 -2
- package/dist/adapters/lazy-errors.d.ts +22 -0
- package/dist/adapters/pg/bunsql-extra.d.ts +54 -3
- package/dist/adapters/pg/bunsql.d.ts +14 -1
- package/dist/adapters/pg/cursor.d.ts +17 -0
- package/dist/adapters/pg/no-inflight-timeout.d.ts +11 -8
- package/dist/adapters/pg/nodepostgres-extra.d.ts +22 -1
- package/dist/adapters/pg/nodepostgres.d.ts +12 -0
- package/dist/adapters/pg/pglite-wire.d.ts +24 -0
- package/dist/adapters/pg/pglite.d.ts +10 -0
- package/dist/adapters/pg/postgres-extra.d.ts +27 -11
- package/dist/adapters/pg/reserved.d.ts +42 -0
- package/dist/adapters/pg/session.d.ts +0 -18
- package/dist/contracts/connection.d.ts +7 -6
- package/dist/dialects/pg/floor.d.ts +68 -0
- package/dist/dialects/pg/index.d.ts +6 -1
- package/dist/dialects/pg/index.js +49 -159
- package/dist/dialects/pg/keys.d.ts +11 -8
- package/dist/dialects/pg/model.d.ts +40 -0
- package/dist/dialects/pg/morph.d.ts +43 -0
- package/dist/dialects/pg/operator-fit.d.ts +2 -2
- package/dist/dialects/pg/operators.d.ts +14 -7
- package/dist/dialects/pg/ops/bucket.d.ts +65 -0
- package/dist/dialects/pg/ops/inc.d.ts +2 -2
- package/dist/dialects/pg/ops/matches.d.ts +9 -12
- package/dist/dialects/pg/ops/relevance.d.ts +13 -0
- package/dist/dialects/pg/ops/search-query.d.ts +22 -0
- package/dist/dialects/pg/ops/window.d.ts +103 -0
- package/dist/dialects/pg/preset.d.ts +1 -1
- package/dist/dialects/pg/relations.d.ts +100 -3
- package/dist/dialects/pg/schema.d.ts +31 -3
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/runtime/aggregate.d.ts +8 -2
- package/dist/runtime/bucket-group.d.ts +70 -0
- package/dist/runtime/conflict.d.ts +22 -4
- package/dist/runtime/include.d.ts +0 -8
- package/dist/runtime/inflight.d.ts +1 -1
- package/dist/runtime/page.d.ts +4 -1
- package/dist/runtime/pg/bun.js +12 -6
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.d.ts +1 -0
- package/dist/runtime/pg/pglite.js +9 -9
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +95 -5
- package/dist/runtime/read-stream.d.ts +13 -16
- package/dist/runtime/result-name.d.ts +17 -0
- package/dist/runtime/server-check.d.ts +3 -3
- package/dist/runtime/sort-bind.d.ts +21 -0
- package/dist/runtime/sort.d.ts +56 -0
- package/dist/runtime/sync.d.ts +70 -0
- package/dist/runtime/tenancy/index.js +7 -5
- package/dist/runtime/text-search.d.ts +20 -0
- package/dist/runtime/traits/index.d.ts +80 -0
- package/dist/runtime/traits/index.js +175 -11
- package/dist/runtime/tree.d.ts +38 -0
- package/dist/runtime/types.d.ts +218 -22
- package/dist/runtime/validate/index.js +2 -2
- package/dist/runtime/version.d.ts +39 -0
- package/dist/runtime/window.d.ts +21 -0
- package/dist/runtime/write.d.ts +54 -2
- package/dist/shared/{wkme6tep.js → 14vx95q4.js} +8 -16
- package/dist/shared/1est4ezg.js +313 -0
- package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
- package/dist/shared/{2cg3wnxc.js → 1w508jbr.js} +593 -130
- package/dist/shared/2c13q96h.js +18 -0
- package/dist/shared/{b4v57v3x.js → 2d3xq4ps.js} +2 -2
- package/dist/shared/{1nw1f3vs.js → 2shdbn8q.js} +7 -7
- package/dist/shared/3v7t1yrv.js +208 -0
- package/dist/shared/{x3jc53kg.js → 5e3tf63j.js} +2 -2
- package/dist/shared/5fe3kbsh.js +10 -0
- package/dist/shared/5v3xf80k.js +37 -0
- package/dist/shared/{c58secgj.js → 63qtkrca.js} +38 -4
- package/dist/shared/{2z7wv5tj.js → 67txcrcw.js} +55 -34
- package/dist/shared/79dt02vc.js +12 -0
- package/dist/shared/{8x72s86k.js → 7k9grvf6.js} +10 -4
- package/dist/shared/7kms6fvj.js +20 -0
- package/dist/shared/7mnme5tn.js +8 -0
- package/dist/shared/7n7ddwt4.js +6 -0
- package/dist/shared/{5j36z8dn.js → 81ebdx9p.js} +39 -70
- package/dist/shared/{knmes2kh.js → 8skf0cam.js} +1 -1
- package/dist/shared/9a4ae4p8.js +31 -0
- package/dist/shared/9y3mmmh1.js +436 -0
- package/dist/shared/ab9gmbwy.js +213 -0
- package/dist/shared/{f0f1n281.js → ax7qv55k.js} +2 -2
- package/dist/shared/{g90m9b0g.js → aytqh5y2.js} +6 -5
- package/dist/shared/{vjpnzwjz.js → b5pftnn2.js} +1 -1
- package/dist/shared/{ag4c7516.js → b76b8d95.js} +2 -2
- package/dist/shared/{vmfkhszw.js → bg3rym3g.js} +33 -26
- package/dist/shared/{mb2ycs1p.js → bm22fvzr.js} +15 -15
- package/dist/shared/{q3j1tq7a.js → bz84tkzm.js} +148 -23
- package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
- package/dist/shared/cn957f8d.js +179 -0
- package/dist/shared/{gwn6v667.js → cnkky55j.js} +25 -14
- package/dist/shared/{ys0xzyaj.js → dkxgzaqd.js} +224 -19
- package/dist/shared/dzj09cye.js +10 -0
- package/dist/shared/exn3sd90.js +359 -0
- package/dist/shared/g6evj1j3.js +66 -0
- package/dist/shared/ga9j92qj.js +25 -0
- package/dist/shared/{t3g0k88x.js → gs74dbj5.js} +16 -12
- package/dist/shared/gzaveazc.js +285 -0
- package/dist/shared/{2jd1sn0e.js → hyh73qk6.js} +1 -1
- package/dist/shared/{r1fdd0f6.js → je4axnm8.js} +5 -2
- package/dist/shared/{1f0xa3cb.js → jvd0mqag.js} +3 -3
- package/dist/shared/jznfax0y.js +155 -0
- package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
- package/dist/shared/{wt5rzv1h.js → kr0jzbw2.js} +5 -5
- package/dist/shared/{p6bjzq9q.js → ktz42yez.js} +12 -4
- package/dist/shared/{p248q79y.js → m6p12kbr.js} +2 -2
- package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
- package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
- package/dist/shared/{929h19xp.js → mtaszv5a.js} +40 -10
- package/dist/shared/{b3awxn7h.js → n816peh1.js} +1 -1
- package/dist/shared/nbw1z4yd.js +18 -0
- package/dist/shared/{nx9hh281.js → p6qc5gcp.js} +12 -11
- package/dist/shared/{pjr6xrm5.js → pbp1ss85.js} +20 -17
- package/dist/shared/pt94kz7f.js +6 -0
- package/dist/shared/q5c6ngn3.js +13 -0
- package/dist/shared/{xq0mvhts.js → rs73fca9.js} +4 -4
- package/dist/shared/{r11n8grd.js → sfz1ksn6.js} +7 -4
- package/dist/shared/{wynzeggs.js → svncdeh5.js} +6 -6
- package/dist/shared/{j60w0dvr.js → tbrvgw6z.js} +57 -38
- package/dist/shared/te5as7z7.js +622 -0
- package/dist/shared/{ttmg09vd.js → tsvww22q.js} +1 -1
- package/dist/shared/{y2t3jktd.js → ttsqgp4p.js} +19 -12
- package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
- package/dist/shared/{sbdme3vy.js → vb6qfyve.js} +60 -10
- package/dist/shared/{5d8t84hg.js → vczs8gd4.js} +14 -14
- package/dist/shared/{5yv4k6a6.js → vqrjpcqn.js} +144 -2
- package/dist/shared/{3fqwmr09.js → w72kgg4q.js} +4 -4
- package/dist/shared/{1z04fa85.js → w79hsckn.js} +12 -57
- package/dist/shared/{09p049xa.js → wa07eas9.js} +42 -14
- package/dist/shared/{32cnm7kn.js → x933k9a3.js} +74 -38
- package/dist/shared/{n076ydsc.js → xhfsfkgk.js} +62 -28
- package/dist/shared/xjk7hnn5.js +272 -0
- package/dist/shared/{hazqqecq.js → xnxsed62.js} +50 -21
- package/dist/shared/y6njgyda.js +249 -0
- package/dist/shared/{8etn7d8w.js → z7s6fpxv.js} +1 -1
- package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
- package/dist/shared/zm0ptry0.js +40 -0
- package/dist/shared/{nsva2a6b.js → znm0bpp5.js} +3 -0
- package/dist/shared/zp9fmend.js +145 -0
- package/dist/tooling/cli.js +11 -11
- package/dist/tooling/migrate/engine.d.ts +3 -2
- package/dist/tooling/migrate/index.js +7 -7
- package/dist/tooling/testing/index.js +1 -1
- package/package.json +4 -2
- package/dist/shared/3qnhc7w5.js +0 -38
- package/dist/shared/7m88x3e9.js +0 -45
- package/dist/shared/8dqhth8c.js +0 -12
- package/dist/shared/d5azex6s.js +0 -6
- package/dist/shared/j3rkk835.js +0 -99
- package/dist/shared/k68eedmy.js +0 -85
- package/dist/shared/venqrjna.js +0 -216
- package/dist/shared/wxr8cwns.js +0 -6
- package/dist/shared/zm0mafk4.js +0 -92
|
@@ -1,19 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stack2
|
|
3
|
+
} from "./mss6ctgv.js";
|
|
1
4
|
import {
|
|
2
5
|
effectivePredicate
|
|
3
|
-
} from "./
|
|
6
|
+
} from "./n816peh1.js";
|
|
4
7
|
import {
|
|
5
8
|
assertOperatorFits,
|
|
6
9
|
textArray,
|
|
7
10
|
arrayElementType
|
|
8
|
-
} from "./
|
|
9
|
-
import {
|
|
10
|
-
stack
|
|
11
|
-
} from "./672t5vym.js";
|
|
12
|
-
import {
|
|
13
|
-
touchFields,
|
|
14
|
-
fieldSealed,
|
|
15
|
-
sealingTrait
|
|
16
|
-
} from "./7m88x3e9.js";
|
|
11
|
+
} from "./zchaghkf.js";
|
|
17
12
|
import {
|
|
18
13
|
runWrite
|
|
19
14
|
} from "./n4jqhpqz.js";
|
|
@@ -36,8 +31,8 @@ import {
|
|
|
36
31
|
quote,
|
|
37
32
|
isRecord,
|
|
38
33
|
registerFailFix,
|
|
39
|
-
|
|
40
|
-
} from "./
|
|
34
|
+
fail2
|
|
35
|
+
} from "./67txcrcw.js";
|
|
41
36
|
import {
|
|
42
37
|
safetyInstalled,
|
|
43
38
|
runSafety
|
|
@@ -52,10 +47,54 @@ import {
|
|
|
52
47
|
throwNamed2
|
|
53
48
|
} from "./52szzakm.js";
|
|
54
49
|
|
|
50
|
+
// src/runtime/trait-read.ts
|
|
51
|
+
function touchFields(model) {
|
|
52
|
+
const traits = model.traits;
|
|
53
|
+
if (traits === undefined)
|
|
54
|
+
return;
|
|
55
|
+
const names = [];
|
|
56
|
+
for (const trait of traits) {
|
|
57
|
+
const touch = trait.touch;
|
|
58
|
+
if (touch === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
for (const field of touch)
|
|
61
|
+
names.push(field);
|
|
62
|
+
}
|
|
63
|
+
return names.length === 0 ? undefined : names;
|
|
64
|
+
}
|
|
65
|
+
function fieldSealed(model, field) {
|
|
66
|
+
const traits = model.traits;
|
|
67
|
+
if (traits === undefined)
|
|
68
|
+
return false;
|
|
69
|
+
for (const trait of traits) {
|
|
70
|
+
const sealed = trait.sealed;
|
|
71
|
+
if (sealed === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
for (const name of sealed)
|
|
74
|
+
if (name === field)
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
function sealingTrait(model, field) {
|
|
80
|
+
const traits = model.traits;
|
|
81
|
+
if (traits === undefined)
|
|
82
|
+
return;
|
|
83
|
+
for (const trait of traits) {
|
|
84
|
+
const sealed = trait.sealed;
|
|
85
|
+
if (sealed === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
for (const name of sealed)
|
|
88
|
+
if (name === field)
|
|
89
|
+
return trait.name;
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
55
94
|
// src/runtime/write.ts
|
|
56
95
|
registerFailFix("OKM1102", "Pass where, or call .all(reason) to match every row.");
|
|
57
96
|
registerFailFix("OKM1190", "Remove the guarded field. Input cannot set it.");
|
|
58
|
-
var
|
|
97
|
+
var WRITE_PARAM_BUDGET2 = 2048;
|
|
59
98
|
var REDACTED = "[redacted]";
|
|
60
99
|
function encodeColumn(column, value) {
|
|
61
100
|
if (!column.hidden && !column.sensitive)
|
|
@@ -102,17 +141,21 @@ var INSERT_OPTIONS = [
|
|
|
102
141
|
"validate"
|
|
103
142
|
];
|
|
104
143
|
var FILTER_OPTIONS = ["allow", "expect", "returning", "signal", "timeout", "validate"];
|
|
105
|
-
var UPDATE_KEYS = ["set", "where"];
|
|
106
|
-
var UPDATE_ROW_KEYS = ["id", "set", "where"];
|
|
144
|
+
var UPDATE_KEYS = ["set", "where", "limit", "lock", "version"];
|
|
145
|
+
var UPDATE_ROW_KEYS = ["id", "set", "where", "version"];
|
|
107
146
|
var NO_ALLOW = new Set;
|
|
108
|
-
async function
|
|
147
|
+
async function executeWrite2(host, op, table, input, options, mods, scope, plan) {
|
|
109
148
|
const prepared = await prepareWrite(host, op, table, input, options, mods, scope);
|
|
110
149
|
if (plan !== undefined)
|
|
111
150
|
return prepared;
|
|
112
151
|
if (prepared.statements.length === 0)
|
|
113
152
|
return prepared.finish([]);
|
|
114
153
|
const results = await runWrite(host, prepared.statements, prepared.options);
|
|
115
|
-
|
|
154
|
+
const more = prepared.follow?.(results);
|
|
155
|
+
if (more === undefined || more.length === 0)
|
|
156
|
+
return prepared.finish(results);
|
|
157
|
+
const extra = await runWrite(host, more, prepared.options);
|
|
158
|
+
return prepared.finish(results.concat(extra));
|
|
116
159
|
}
|
|
117
160
|
async function prepareWrite(host, op, table, input, options, mods, scope) {
|
|
118
161
|
try {
|
|
@@ -121,13 +164,16 @@ async function prepareWrite(host, op, table, input, options, mods, scope) {
|
|
|
121
164
|
statements: planned.statements,
|
|
122
165
|
options: callOptions(options),
|
|
123
166
|
...planned.parentGuard === true ? { checked: "parent" } : planned.expect !== undefined ? { checked: "expect" } : {},
|
|
124
|
-
|
|
167
|
+
...planned.recover === undefined ? {} : {
|
|
168
|
+
follow: (results) => planned.recover?.(decodePlanned(withoutQuiet(results, planned.quiet), planned.outputs)) ?? []
|
|
169
|
+
},
|
|
170
|
+
finish: (results) => finish(withoutQuiet(results, planned.quiet), planned, table)
|
|
125
171
|
};
|
|
126
172
|
} catch (error) {
|
|
127
173
|
throw scrubWrite(host.schema, table, input, error);
|
|
128
174
|
}
|
|
129
175
|
}
|
|
130
|
-
async function
|
|
176
|
+
async function explainWrite2(schema, op, table, input, options, mods, generators, scope) {
|
|
131
177
|
try {
|
|
132
178
|
const planned = await planWrite(schema, generators, op, table, input, options, mods, scope);
|
|
133
179
|
return { statements: planned.statements };
|
|
@@ -139,10 +185,10 @@ async function planWrite(schema, generators, op, tableName, input, options, mods
|
|
|
139
185
|
await requireValidationImport(schema, op, tableName, options);
|
|
140
186
|
const table = indexes(schema).get(tableName);
|
|
141
187
|
if (table === undefined) {
|
|
142
|
-
|
|
188
|
+
fail2("OKM1120", `Table ${tableName} is not in the schema.`);
|
|
143
189
|
}
|
|
144
190
|
if (mods.uses !== undefined && op !== "insert") {
|
|
145
|
-
const { resolve } = await import("./
|
|
191
|
+
const { resolve } = await import("./m7n9h2a5.js");
|
|
146
192
|
mods = { ...mods, presets: resolve(schema, tableName, mods.uses).wheres };
|
|
147
193
|
}
|
|
148
194
|
const record = isRecord(options) ? options : {};
|
|
@@ -151,6 +197,14 @@ async function planWrite(schema, generators, op, tableName, input, options, mods
|
|
|
151
197
|
return planInsert(table, input, record, mods, generators, scope, schema.tenancy);
|
|
152
198
|
}
|
|
153
199
|
if (op === "update") {
|
|
200
|
+
if (traitNamed(table, "versioned")) {
|
|
201
|
+
const { planVersioned } = await import("./y6njgyda.js");
|
|
202
|
+
return withRowFilters(scope, mods.archive, () => {
|
|
203
|
+
noteArchive(table, mods.archive);
|
|
204
|
+
const planned = planVersioned(schema, table, input, record, mods);
|
|
205
|
+
return { ...planned, keys: undefined, keyColumns: undefined };
|
|
206
|
+
});
|
|
207
|
+
}
|
|
154
208
|
return withRowFilters(scope, mods.archive, () => {
|
|
155
209
|
noteArchive(table, mods.archive);
|
|
156
210
|
return planUpdate(schema, table, input, record, mods, scope);
|
|
@@ -162,8 +216,8 @@ async function planWrite(schema, generators, op, tableName, input, options, mods
|
|
|
162
216
|
});
|
|
163
217
|
}
|
|
164
218
|
async function planInsert(table, input, options, mods, generators, scope, tenancy) {
|
|
165
|
-
const allow =
|
|
166
|
-
|
|
219
|
+
const allow = allowSet2(table, options.allow);
|
|
220
|
+
noteGuarded2(table, allow, "insert");
|
|
167
221
|
const many = Array.isArray(input);
|
|
168
222
|
const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
|
|
169
223
|
tenancy?.stamp(table.model.name, rows, scope);
|
|
@@ -172,13 +226,14 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
|
|
|
172
226
|
const returning = selectedColumns(table, options.returning);
|
|
173
227
|
const outputs = outputsOf(returning);
|
|
174
228
|
const clause = returningClause(returning, undefined);
|
|
175
|
-
const expect =
|
|
229
|
+
const expect = expectOf2(options, mods);
|
|
176
230
|
const shape = many ? "rows" : "row";
|
|
177
231
|
const path = tenancy?.isPath?.(table.model.name) === true;
|
|
178
232
|
if (path && options.onConflict !== undefined && options.onConflict !== "error") {
|
|
179
|
-
|
|
233
|
+
fail2("OKM1120", `onConflict on ${table.model.name} is not available. The insert checks the parent.`);
|
|
180
234
|
}
|
|
181
|
-
|
|
235
|
+
const sorting = traitNamed(table, "sortable");
|
|
236
|
+
if (columns.length === 0 && !sorting) {
|
|
182
237
|
const text = `insert into ${quote(table.model.sql)} default values${clause}`;
|
|
183
238
|
return {
|
|
184
239
|
statements: rows.map(() => ({ text })),
|
|
@@ -186,51 +241,74 @@ async function planInsert(table, input, options, mods, generators, scope, tenanc
|
|
|
186
241
|
shape,
|
|
187
242
|
expect,
|
|
188
243
|
keys: undefined,
|
|
189
|
-
|
|
244
|
+
keyColumns: undefined
|
|
190
245
|
};
|
|
191
246
|
}
|
|
192
|
-
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./
|
|
247
|
+
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./bg3rym3g.js");
|
|
193
248
|
const conflict = conflictMod?.readConflict(table, options.onConflict, allow, tenantKeyList(tenancy));
|
|
249
|
+
if (conflict?.kind === "update" && traitNamed(table, "versioned")) {
|
|
250
|
+
fail2("OKM1120", `onConflict update on ${table.model.name} does not check the version you read.`, "Read the row and update. ignore and return do not change the existing row.");
|
|
251
|
+
}
|
|
252
|
+
const sortMod = sorting ? await import("./9y3mmmh1.js") : undefined;
|
|
194
253
|
const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
|
|
195
254
|
const statements = [];
|
|
255
|
+
const quiet = [];
|
|
196
256
|
const keys = [];
|
|
197
257
|
let offset = 0;
|
|
198
258
|
for (const chunk of chunks) {
|
|
199
259
|
const slice = rows.slice(offset, offset + chunk.length);
|
|
200
260
|
offset += chunk.length;
|
|
201
|
-
const
|
|
261
|
+
const head = path || sortMod === undefined ? undefined : sortMod.insertAtEnd(table, columns, slice, traitGroup(table), tenantKeyList(tenancy));
|
|
262
|
+
const built = head ?? (path ? pathInsert(table, columns, chunk, scope, tenancy) : insertHead(table, columns, chunk));
|
|
263
|
+
if (head !== undefined) {
|
|
264
|
+
quiet.push(statements.length);
|
|
265
|
+
statements.push(head.lock);
|
|
266
|
+
}
|
|
202
267
|
if (conflict === undefined || conflictMod === undefined) {
|
|
203
268
|
statements.push(withParams(`${built.text}${clause}`, built.params));
|
|
204
269
|
continue;
|
|
205
270
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
271
|
+
if (conflict.kind === "return") {
|
|
272
|
+
for (const row of slice)
|
|
273
|
+
keys.push(keyWire(conflict.columns, row));
|
|
274
|
+
}
|
|
275
|
+
statements.push(conflictMod.finishInsert(built.text, clause, built.params ?? [], conflict, table));
|
|
210
276
|
}
|
|
277
|
+
const conflictColumns = conflict?.kind === "return" ? conflict.columns : undefined;
|
|
211
278
|
return {
|
|
212
279
|
statements,
|
|
213
280
|
outputs,
|
|
214
281
|
shape,
|
|
215
282
|
expect,
|
|
216
|
-
keys:
|
|
217
|
-
|
|
218
|
-
...path ? { parent: rows.length } : {}
|
|
283
|
+
keys: conflictColumns === undefined ? undefined : keys,
|
|
284
|
+
keyColumns: conflictColumns,
|
|
285
|
+
...path ? { parent: rows.length } : {},
|
|
286
|
+
...quiet.length > 0 ? { quiet } : {},
|
|
287
|
+
...conflictColumns === undefined || conflictMod === undefined ? {} : {
|
|
288
|
+
recover: (found) => lookupStatements(conflictMod, table, conflictColumns, returning, missingKeys(found, conflictColumns, keys))
|
|
289
|
+
}
|
|
219
290
|
};
|
|
220
291
|
}
|
|
221
292
|
function planUpdate(schema, table, input, options, mods, scope) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
293
|
+
if (mentionsVersion(input)) {
|
|
294
|
+
fail2("OKM1120", `version on ${table.model.name} needs versioned().`, "Add versioned() from okmodel/traits, or remove version.");
|
|
295
|
+
}
|
|
296
|
+
const allow = allowSet2(table, options.allow);
|
|
297
|
+
noteGuarded2(table, allow, "update");
|
|
298
|
+
const returning = returningOption2(table, options.returning);
|
|
225
299
|
const outputs = outputsOf(returning);
|
|
226
|
-
const expect =
|
|
300
|
+
const expect = expectOf2(options, mods);
|
|
227
301
|
if (Array.isArray(input)) {
|
|
228
302
|
const rows = input.map((item) => {
|
|
229
303
|
const row = updateItem(table, item, allow, schema.tenancy);
|
|
230
|
-
|
|
304
|
+
requireFilter2(table, row.where, mods, "update");
|
|
231
305
|
pathAssignment(schema.tenancy, table.model.name, row.set, scope);
|
|
232
|
-
return { ...row, where:
|
|
306
|
+
return { ...row, where: stack2(row.where, mods.presets) };
|
|
233
307
|
});
|
|
308
|
+
rejectDuplicateTargets2(table, rows);
|
|
309
|
+
rejectMixedSets2(rows);
|
|
310
|
+
for (const row of rows)
|
|
311
|
+
refuseTreeCycle(table, row.where, row.set, schema.tenancy?.key);
|
|
234
312
|
const moves = rows.some((row) => pathField(schema.tenancy, table.model.name, row.set) !== undefined);
|
|
235
313
|
if (moves) {
|
|
236
314
|
return {
|
|
@@ -239,7 +317,7 @@ function planUpdate(schema, table, input, options, mods, scope) {
|
|
|
239
317
|
shape: returning === undefined ? "count" : "rows",
|
|
240
318
|
expect,
|
|
241
319
|
keys: undefined,
|
|
242
|
-
|
|
320
|
+
keyColumns: undefined,
|
|
243
321
|
parentGuard: true
|
|
244
322
|
};
|
|
245
323
|
}
|
|
@@ -249,33 +327,49 @@ function planUpdate(schema, table, input, options, mods, scope) {
|
|
|
249
327
|
shape: returning === undefined ? "count" : "rows",
|
|
250
328
|
expect,
|
|
251
329
|
keys: undefined,
|
|
252
|
-
|
|
330
|
+
keyColumns: undefined
|
|
253
331
|
};
|
|
254
332
|
}
|
|
255
333
|
if (!isRecord(input))
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
334
|
+
fail2("OKM1121", "update expects { where, set } or a list of rows.");
|
|
335
|
+
const claim = readClaim2(input);
|
|
336
|
+
rejectUpdateKeys2(input, false);
|
|
337
|
+
const set = writeSet2(table, input.set, allow, schema.tenancy);
|
|
338
|
+
requireFilter2(table, input.where, mods, "update");
|
|
260
339
|
if (Object.keys(set).length === 0)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
340
|
+
fail2("OKM1120", "update set is empty.");
|
|
341
|
+
refuseTreeCycle(table, input.where, set, schema.tenancy?.key);
|
|
342
|
+
const where = stack2(input.where, mods.presets);
|
|
343
|
+
const moved = pathAssignment(schema.tenancy, table.model.name, set, scope);
|
|
344
|
+
if (moved !== undefined && claim !== undefined) {
|
|
345
|
+
fail2("OKM1120", `lock: "skip" on ${table.model.name} cannot change ${moved} in the same claim.`, "Claim the row first. Point it at a parent in a later update.");
|
|
346
|
+
}
|
|
347
|
+
if (moved !== undefined) {
|
|
264
348
|
return {
|
|
265
349
|
statements: [pathSetUpdate(schema, table, where, set, scope, returning)],
|
|
266
350
|
outputs: parentOutputs(returning),
|
|
267
351
|
shape: returning === undefined ? "count" : "rows",
|
|
268
352
|
expect,
|
|
269
353
|
keys: undefined,
|
|
270
|
-
|
|
354
|
+
keyColumns: undefined,
|
|
271
355
|
parentGuard: true
|
|
272
356
|
};
|
|
273
357
|
}
|
|
274
|
-
|
|
358
|
+
if (claim !== undefined) {
|
|
359
|
+
return {
|
|
360
|
+
statements: [emitClaim(schema, table, where, set, claim, returning)],
|
|
361
|
+
outputs,
|
|
362
|
+
shape: returning === undefined ? "count" : "rows",
|
|
363
|
+
expect,
|
|
364
|
+
keys: undefined,
|
|
365
|
+
keyColumns: undefined
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const sql = new Sql2;
|
|
275
369
|
sql.text("update ");
|
|
276
370
|
sql.text(quote(table.model.sql));
|
|
277
371
|
sql.text(" as t set ");
|
|
278
|
-
|
|
372
|
+
emitSet2(sql, table, set, "t");
|
|
279
373
|
emitFilter(schema, table, where, sql, "t");
|
|
280
374
|
schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
|
|
281
375
|
sql.text(returningClause(returning, "t"));
|
|
@@ -285,31 +379,36 @@ function planUpdate(schema, table, input, options, mods, scope) {
|
|
|
285
379
|
shape: returning === undefined ? "count" : "rows",
|
|
286
380
|
expect,
|
|
287
381
|
keys: undefined,
|
|
288
|
-
|
|
382
|
+
keyColumns: undefined
|
|
289
383
|
};
|
|
290
384
|
}
|
|
291
385
|
function planDelete(schema, table, input, options, mods) {
|
|
292
386
|
if (!isRecord(input))
|
|
293
|
-
|
|
387
|
+
fail2("OKM1121", "delete expects { where }.");
|
|
294
388
|
rejectKeys(input, ["where"], "delete");
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
389
|
+
requireFilter2(table, input.where, mods, "delete");
|
|
390
|
+
if (options.returning === true) {
|
|
391
|
+
fail2("OKM1120", "delete does not accept returning: true.", "Pass the field names in returning, or omit returning for a count.");
|
|
392
|
+
}
|
|
393
|
+
const returning = returningOption2(table, options.returning);
|
|
394
|
+
const sql = new Sql2;
|
|
298
395
|
sql.text("delete from ");
|
|
299
396
|
sql.text(quote(table.model.sql));
|
|
300
397
|
sql.text(" as t");
|
|
301
|
-
emitFilter(schema, table,
|
|
398
|
+
emitFilter(schema, table, stack2(input.where, mods.presets), sql, "t");
|
|
302
399
|
sql.text(returningClause(returning, "t"));
|
|
303
400
|
return {
|
|
304
401
|
statements: [sql.statement()],
|
|
305
402
|
outputs: outputsOf(returning),
|
|
306
403
|
shape: returning === undefined ? "count" : "rows",
|
|
307
|
-
expect:
|
|
404
|
+
expect: expectOf2(options, mods),
|
|
308
405
|
keys: undefined,
|
|
309
|
-
|
|
406
|
+
keyColumns: undefined
|
|
310
407
|
};
|
|
311
408
|
}
|
|
312
409
|
function finish(results, planned, table) {
|
|
410
|
+
if (planned.versioned !== undefined)
|
|
411
|
+
return finishVersioned(results, planned, table);
|
|
313
412
|
if (planned.parentGuard === true)
|
|
314
413
|
return finishParent(results, planned, table);
|
|
315
414
|
if (planned.shape === "count") {
|
|
@@ -319,12 +418,8 @@ function finish(results, planned, table) {
|
|
|
319
418
|
checkExpect(count, planned.expect, table, "write");
|
|
320
419
|
return { count };
|
|
321
420
|
}
|
|
322
|
-
const decoded =
|
|
323
|
-
|
|
324
|
-
for (const row of result.rows)
|
|
325
|
-
decoded.push(decodeRow(planned.outputs, row));
|
|
326
|
-
}
|
|
327
|
-
const rows = planned.keys === undefined || planned.keyFields === undefined ? decoded : orderByKeys(decoded, planned.keyFields, planned.keys);
|
|
421
|
+
const decoded = decodePlanned(results, planned.outputs);
|
|
422
|
+
const rows = planned.keys === undefined || planned.keyColumns === undefined ? decoded : orderByKeys(decoded, planned.keyColumns, planned.keys);
|
|
328
423
|
checkExpect(rows.length, planned.expect, table, "insert");
|
|
329
424
|
if (planned.parent !== undefined && rows.length < planned.parent) {
|
|
330
425
|
throw new OkmError2("OKM1705", `Insert on ${table} did not find a parent in this tenant.`, {
|
|
@@ -360,6 +455,36 @@ function finishParent(results, planned, table) {
|
|
|
360
455
|
return decoded[0] ?? null;
|
|
361
456
|
return decoded;
|
|
362
457
|
}
|
|
458
|
+
function finishVersioned(results, planned, table) {
|
|
459
|
+
const versioned = planned.versioned;
|
|
460
|
+
const first = results[0]?.rows[0];
|
|
461
|
+
if (versioned !== undefined && first !== undefined && countCell(first[0]) > 0) {
|
|
462
|
+
const fields = versioned.fields.length === 0 ? "the row" : versioned.fields.join(", ");
|
|
463
|
+
const sent = versioned.sent.join(", ");
|
|
464
|
+
throw new OkmError2("conflict", `update on ${table} tried to write ${fields} at version ${sent}, and the row changed. Reread the row and retry with the version you read.`, {
|
|
465
|
+
kind: "conflict",
|
|
466
|
+
columns: ["version"],
|
|
467
|
+
fix: { summary: "Reread the row and retry with the version you read." }
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (planned.shape === "count") {
|
|
471
|
+
const updated = first === undefined ? 0 : countCell(first[1]);
|
|
472
|
+
checkExpect(updated, planned.expect, table, "write");
|
|
473
|
+
return { count: updated };
|
|
474
|
+
}
|
|
475
|
+
const decoded = [];
|
|
476
|
+
for (const result of results) {
|
|
477
|
+
for (const item of result.rows) {
|
|
478
|
+
if (countCell(item[1]) === 0)
|
|
479
|
+
continue;
|
|
480
|
+
decoded.push(decodeRow(planned.outputs, item));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
checkExpect(decoded.length, planned.expect, table, "write");
|
|
484
|
+
if (planned.shape === "row")
|
|
485
|
+
return decoded[0] ?? null;
|
|
486
|
+
return decoded;
|
|
487
|
+
}
|
|
363
488
|
function countCell(value) {
|
|
364
489
|
if (typeof value === "number")
|
|
365
490
|
return value;
|
|
@@ -401,23 +526,30 @@ function parentOutputs(returning) {
|
|
|
401
526
|
includes: outputs.includes
|
|
402
527
|
};
|
|
403
528
|
}
|
|
529
|
+
function rowId(alias) {
|
|
530
|
+
return `${alias}.tableoid, ${alias}.ctid`;
|
|
531
|
+
}
|
|
404
532
|
function pathSetUpdate(schema, table, where, set, scope, returning) {
|
|
405
|
-
const sql = new
|
|
406
|
-
sql.text("with matched as (select
|
|
533
|
+
const sql = new Sql2;
|
|
534
|
+
sql.text("with matched as (select ");
|
|
535
|
+
sql.text(rowId("t"));
|
|
536
|
+
sql.text(" from ");
|
|
407
537
|
sql.text(quote(table.model.sql));
|
|
408
538
|
sql.text(" as t");
|
|
409
539
|
emitFilter(schema, table, where, sql, "t");
|
|
410
540
|
sql.text("), updated as (update ");
|
|
411
541
|
sql.text(quote(table.model.sql));
|
|
412
542
|
sql.text(" as t set ");
|
|
413
|
-
|
|
414
|
-
sql.text(" where
|
|
543
|
+
emitSet2(sql, table, set, "t");
|
|
544
|
+
sql.text(" where (");
|
|
545
|
+
sql.text(rowId("t"));
|
|
546
|
+
sql.text(") in (select tableoid, ctid from matched)");
|
|
415
547
|
schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
|
|
416
548
|
endParent(sql, returning);
|
|
417
549
|
return sql.statement();
|
|
418
550
|
}
|
|
419
551
|
function pathListUpdate(schema, table, rows, scope, returning) {
|
|
420
|
-
const sql = new
|
|
552
|
+
const sql = new Sql2;
|
|
421
553
|
sql.text("with matched as (");
|
|
422
554
|
for (let index = 0;index < rows.length; index += 1) {
|
|
423
555
|
const row = rows[index];
|
|
@@ -453,7 +585,7 @@ function pathListUpdate(schema, table, rows, scope, returning) {
|
|
|
453
585
|
if (!rejected)
|
|
454
586
|
sql.text("select 0 as ord where false");
|
|
455
587
|
sql.text("), updated as (");
|
|
456
|
-
|
|
588
|
+
updateSliceBody2(schema, table, rows, sql, true);
|
|
457
589
|
sql.text(" and not exists (select 1 from rejected)");
|
|
458
590
|
endParent(sql, returning);
|
|
459
591
|
return sql.statement();
|
|
@@ -483,7 +615,7 @@ function endParent(sql, returning) {
|
|
|
483
615
|
sql.text(returningClause(returning, "t"));
|
|
484
616
|
sql.text(") select c.matched, c.updated, u.* from (select (select count(*)::int from matched) as matched, (select count(*)::int from updated) as updated) c left join updated u on true");
|
|
485
617
|
}
|
|
486
|
-
function
|
|
618
|
+
function updateSliceBody2(schema, table, rows, sql, wrapWhere = false, hooks) {
|
|
487
619
|
const fields = [];
|
|
488
620
|
const seen = new Set;
|
|
489
621
|
for (const row of rows) {
|
|
@@ -497,7 +629,7 @@ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
|
|
|
497
629
|
}
|
|
498
630
|
}
|
|
499
631
|
if (fields.length === 0)
|
|
500
|
-
|
|
632
|
+
fail2("OKM1120", "update set is empty.");
|
|
501
633
|
sql.text("update ");
|
|
502
634
|
sql.text(quote(table.model.sql));
|
|
503
635
|
sql.text(" as t set ");
|
|
@@ -509,9 +641,13 @@ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
|
|
|
509
641
|
sql.text(", ");
|
|
510
642
|
sql.text(quote(column.sql));
|
|
511
643
|
sql.text(" = case");
|
|
512
|
-
for (
|
|
644
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
645
|
+
const row = rows[index];
|
|
646
|
+
if (row === undefined)
|
|
647
|
+
continue;
|
|
513
648
|
sql.text(" when ");
|
|
514
649
|
emitBare(schema, table, row.where, sql, "t");
|
|
650
|
+
hooks?.afterWhere?.(sql, index);
|
|
515
651
|
sql.text(" then ");
|
|
516
652
|
const value = row.set[column.field];
|
|
517
653
|
if (value === undefined)
|
|
@@ -524,6 +660,7 @@ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
|
|
|
524
660
|
sql.text(" end");
|
|
525
661
|
}
|
|
526
662
|
emitTouch(sql, table, fields.length > 0);
|
|
663
|
+
hooks?.afterSet?.(sql);
|
|
527
664
|
sql.text(wrapWhere ? " where (" : " where ");
|
|
528
665
|
for (let index = 0;index < rows.length; index += 1) {
|
|
529
666
|
const row = rows[index];
|
|
@@ -533,15 +670,132 @@ function updateSliceBody(schema, table, rows, sql, wrapWhere = false) {
|
|
|
533
670
|
sql.text(" or ");
|
|
534
671
|
sql.text("(");
|
|
535
672
|
emitBare(schema, table, row.where, sql, "t");
|
|
673
|
+
hooks?.afterWhere?.(sql, index);
|
|
536
674
|
sql.text(")");
|
|
537
675
|
}
|
|
538
676
|
if (wrapWhere)
|
|
539
677
|
sql.text(")");
|
|
540
678
|
}
|
|
541
|
-
function
|
|
679
|
+
function decodePlanned(results, outputs) {
|
|
680
|
+
const decoded = [];
|
|
681
|
+
for (const result of results) {
|
|
682
|
+
for (const row of result.rows)
|
|
683
|
+
decoded.push(decodeRow(outputs, row));
|
|
684
|
+
}
|
|
685
|
+
return decoded;
|
|
686
|
+
}
|
|
687
|
+
function withoutQuiet(results, quiet) {
|
|
688
|
+
if (quiet === undefined || quiet.length === 0)
|
|
689
|
+
return results;
|
|
690
|
+
const hidden = new Set(quiet);
|
|
691
|
+
const kept = [];
|
|
692
|
+
for (let index = 0;index < results.length; index += 1) {
|
|
693
|
+
if (hidden.has(index))
|
|
694
|
+
continue;
|
|
695
|
+
const result = results[index];
|
|
696
|
+
if (result !== undefined)
|
|
697
|
+
kept.push(result);
|
|
698
|
+
}
|
|
699
|
+
return kept;
|
|
700
|
+
}
|
|
701
|
+
function lookupStatements(mod, table, columns, returning, keyRows) {
|
|
702
|
+
if (keyRows.length === 0 || returning.length === 0)
|
|
703
|
+
return [];
|
|
704
|
+
const width = Math.max(1, columns.length);
|
|
705
|
+
const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET2 / width));
|
|
706
|
+
const statements = [];
|
|
707
|
+
for (let index = 0;index < keyRows.length; index += size) {
|
|
708
|
+
statements.push(mod.lookupExisting(table, columns, returning, keyRows.slice(index, index + size)));
|
|
709
|
+
}
|
|
710
|
+
return statements;
|
|
711
|
+
}
|
|
712
|
+
function missingKeys(rows, columns, keys) {
|
|
713
|
+
const have = new Set(rows.map((row) => rowKey(columns, row)));
|
|
714
|
+
const missing = [];
|
|
715
|
+
const seen = new Set;
|
|
716
|
+
for (const key of keys) {
|
|
717
|
+
const id = key.map((value) => value ?? "").join("\x00");
|
|
718
|
+
if (have.has(id) || seen.has(id))
|
|
719
|
+
continue;
|
|
720
|
+
seen.add(id);
|
|
721
|
+
missing.push(key);
|
|
722
|
+
}
|
|
723
|
+
return missing;
|
|
724
|
+
}
|
|
725
|
+
function rejectDuplicateTargets2(table, rows) {
|
|
726
|
+
if (rows.length < 2)
|
|
727
|
+
return;
|
|
728
|
+
const seen = new Set;
|
|
729
|
+
for (const row of rows) {
|
|
730
|
+
const key = canonical(row.where);
|
|
731
|
+
if (seen.has(key)) {
|
|
732
|
+
fail2("OKM1120", `update on ${table.model.name} lists the same target twice.`, "List each row once.");
|
|
733
|
+
}
|
|
734
|
+
seen.add(key);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
var unresolved = 0;
|
|
738
|
+
function canonical(value) {
|
|
739
|
+
if (isOperator(value))
|
|
740
|
+
return `op:${operatorName(value)}:${canonical(operatorValue(value))}`;
|
|
741
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
742
|
+
return JSON.stringify(value);
|
|
743
|
+
}
|
|
744
|
+
if (value === null)
|
|
745
|
+
return "null";
|
|
746
|
+
if (value === undefined)
|
|
747
|
+
return "undefined";
|
|
748
|
+
if (typeof value === "bigint")
|
|
749
|
+
return JSON.stringify(value.toString());
|
|
750
|
+
if (Array.isArray(value))
|
|
751
|
+
return `[${value.map((item) => canonical(item)).join(",")}]`;
|
|
752
|
+
if (isRecord(value)) {
|
|
753
|
+
if (plainRecord(value)) {
|
|
754
|
+
const keys = Object.keys(value).sort();
|
|
755
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
756
|
+
}
|
|
757
|
+
return boxed(value);
|
|
758
|
+
}
|
|
759
|
+
return fresh();
|
|
760
|
+
}
|
|
761
|
+
function plainRecord(value) {
|
|
762
|
+
const proto = Object.getPrototypeOf(value);
|
|
763
|
+
return proto === Object.prototype || proto === null;
|
|
764
|
+
}
|
|
765
|
+
function boxed(value) {
|
|
766
|
+
if (value instanceof Uint8Array)
|
|
767
|
+
return `bytes:${hexBytes(value)}`;
|
|
768
|
+
if (value instanceof Date) {
|
|
769
|
+
const time = value.getTime();
|
|
770
|
+
if (Number.isNaN(time))
|
|
771
|
+
return fresh();
|
|
772
|
+
return `date:${value.toISOString()}`;
|
|
773
|
+
}
|
|
774
|
+
const read = value.toString;
|
|
775
|
+
if (read === Object.prototype.toString)
|
|
776
|
+
return fresh();
|
|
777
|
+
const text = read.call(value);
|
|
778
|
+
if (text.length > 0 && text !== "[object Object]")
|
|
779
|
+
return `text:${text}`;
|
|
780
|
+
return fresh();
|
|
781
|
+
}
|
|
782
|
+
function hexBytes(value) {
|
|
783
|
+
let hex = "";
|
|
784
|
+
for (const byte of value)
|
|
785
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
786
|
+
return hex;
|
|
787
|
+
}
|
|
788
|
+
function fresh() {
|
|
789
|
+
unresolved += 1;
|
|
790
|
+
return `~${String(unresolved)}`;
|
|
791
|
+
}
|
|
792
|
+
function orderByKeys(rows, columns, keys) {
|
|
542
793
|
const found = new Map;
|
|
543
|
-
for (const row of rows)
|
|
544
|
-
|
|
794
|
+
for (const row of rows) {
|
|
795
|
+
const id = rowKey(columns, row);
|
|
796
|
+
if (!found.has(id))
|
|
797
|
+
found.set(id, row);
|
|
798
|
+
}
|
|
545
799
|
const ordered = [];
|
|
546
800
|
for (const key of keys) {
|
|
547
801
|
const row = found.get(key.map((value) => value ?? "").join("\x00"));
|
|
@@ -550,13 +804,30 @@ function orderByKeys(rows, fields, keys) {
|
|
|
550
804
|
}
|
|
551
805
|
return ordered;
|
|
552
806
|
}
|
|
807
|
+
function rowKey(columns, row) {
|
|
808
|
+
let id = "";
|
|
809
|
+
for (let index = 0;index < columns.length; index += 1) {
|
|
810
|
+
const column = columns[index];
|
|
811
|
+
if (column === undefined)
|
|
812
|
+
continue;
|
|
813
|
+
if (index > 0)
|
|
814
|
+
id += "\x00";
|
|
815
|
+
id += valueKey(column, row[column.field]);
|
|
816
|
+
}
|
|
817
|
+
return id;
|
|
818
|
+
}
|
|
819
|
+
function valueKey(column, value) {
|
|
820
|
+
if (value === undefined || value === null)
|
|
821
|
+
return "";
|
|
822
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
823
|
+
return wireKey(value);
|
|
824
|
+
}
|
|
825
|
+
return column.encode(value);
|
|
826
|
+
}
|
|
553
827
|
function wireKey(value) {
|
|
554
828
|
if (typeof value === "string")
|
|
555
829
|
return value;
|
|
556
|
-
|
|
557
|
-
return String(value);
|
|
558
|
-
}
|
|
559
|
-
return "";
|
|
830
|
+
return String(value);
|
|
560
831
|
}
|
|
561
832
|
function insertHead(table, columns, rows) {
|
|
562
833
|
const params = [];
|
|
@@ -583,7 +854,7 @@ function updateList(schema, table, rows, returning) {
|
|
|
583
854
|
if (rows.length === 0)
|
|
584
855
|
return [];
|
|
585
856
|
const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
|
|
586
|
-
const size = Math.max(1, Math.floor(
|
|
857
|
+
const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET2 / (width * 2)));
|
|
587
858
|
const statements = [];
|
|
588
859
|
for (let index = 0;index < rows.length; index += size) {
|
|
589
860
|
statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
|
|
@@ -591,12 +862,32 @@ function updateList(schema, table, rows, returning) {
|
|
|
591
862
|
return statements;
|
|
592
863
|
}
|
|
593
864
|
function updateSlice(schema, table, rows, returning) {
|
|
594
|
-
const sql = new
|
|
595
|
-
|
|
865
|
+
const sql = new Sql2;
|
|
866
|
+
updateSliceBody2(schema, table, rows, sql);
|
|
596
867
|
sql.text(returningClause(returning, "t"));
|
|
597
868
|
return sql.statement();
|
|
598
869
|
}
|
|
599
|
-
function
|
|
870
|
+
function emitClaim(schema, table, where, set, limit, returning) {
|
|
871
|
+
const sql = new Sql2;
|
|
872
|
+
sql.text("update ");
|
|
873
|
+
sql.text(quote(table.model.sql));
|
|
874
|
+
sql.text(" as t set ");
|
|
875
|
+
emitSet2(sql, table, set, "t");
|
|
876
|
+
sql.text(" where (");
|
|
877
|
+
sql.text(rowId("t"));
|
|
878
|
+
sql.text(") in (select ");
|
|
879
|
+
sql.text(rowId("s"));
|
|
880
|
+
sql.text(" from ");
|
|
881
|
+
sql.text(quote(table.model.sql));
|
|
882
|
+
sql.text(" as s");
|
|
883
|
+
emitFilter(schema, table, where, sql, "s");
|
|
884
|
+
sql.text(" for update of s skip locked limit ");
|
|
885
|
+
sql.param(String(limit), "integer");
|
|
886
|
+
sql.text(")");
|
|
887
|
+
sql.text(returningClause(returning, "t"));
|
|
888
|
+
return sql.statement();
|
|
889
|
+
}
|
|
890
|
+
function emitSet2(sql, table, set, alias) {
|
|
600
891
|
const keys = Object.keys(set);
|
|
601
892
|
let wrote = false;
|
|
602
893
|
for (const key of keys) {
|
|
@@ -632,12 +923,7 @@ function emitValue(sql, column, value, alias) {
|
|
|
632
923
|
if (isOperator(value)) {
|
|
633
924
|
const name = operatorName(value);
|
|
634
925
|
if (name === "inc") {
|
|
635
|
-
|
|
636
|
-
if (typeof amount === "object" && amount !== null) {
|
|
637
|
-
fail("OKM1121", `inc on ${column.field} needs a value.`);
|
|
638
|
-
}
|
|
639
|
-
sql.text(`${alias}.${quote(column.sql)} + `);
|
|
640
|
-
sql.param(encodeColumn(column, amount), column.dataType);
|
|
926
|
+
emitIncrement(sql, column, value, alias);
|
|
641
927
|
return;
|
|
642
928
|
}
|
|
643
929
|
if (name === "json.set") {
|
|
@@ -648,7 +934,7 @@ function emitValue(sql, column, value, alias) {
|
|
|
648
934
|
emitArrayWrite(sql, column, name, value, alias);
|
|
649
935
|
return;
|
|
650
936
|
}
|
|
651
|
-
|
|
937
|
+
fail2("OKM1121", `Field ${column.field} received an operator. set accepts a value, inc, json.set, arr.append, or arr.remove.`);
|
|
652
938
|
}
|
|
653
939
|
if (value === null) {
|
|
654
940
|
sql.text(`null::${column.dataType}`);
|
|
@@ -658,17 +944,31 @@ function emitValue(sql, column, value, alias) {
|
|
|
658
944
|
takeObject(column, value);
|
|
659
945
|
sql.param(encodeColumn(column, value), column.dataType);
|
|
660
946
|
}
|
|
947
|
+
var NUMBER_TYPE = /^(?:smallint|integer|bigint|real|double precision|numeric|decimal)(?:\(\d+(?:,\d+)?\))?$/;
|
|
948
|
+
function emitIncrement(sql, column, value, alias) {
|
|
949
|
+
if (!NUMBER_TYPE.test(column.dataType)) {
|
|
950
|
+
fail2("OKM1124", `inc adds to a number. ${column.field} is a ${column.dataType} column.`, "Use inc on an integer, bigint, numeric, or float column.");
|
|
951
|
+
}
|
|
952
|
+
if (!isOperator(value))
|
|
953
|
+
fail2("OKM1121", `inc on ${column.field} needs a number.`);
|
|
954
|
+
const amount = operatorValue(value);
|
|
955
|
+
if (typeof amount !== "number" && typeof amount !== "bigint" && typeof amount !== "string") {
|
|
956
|
+
fail2("OKM1121", `inc on ${column.field} needs a number.`, "Pass the amount to add. null stores null; it is not an amount.");
|
|
957
|
+
}
|
|
958
|
+
sql.text(`${alias}.${quote(column.sql)} + `);
|
|
959
|
+
sql.param(encodeColumn(column, amount), column.dataType);
|
|
960
|
+
}
|
|
661
961
|
function emitJsonSet(sql, column, value, alias) {
|
|
662
962
|
assertOperatorFits(column, "json.set");
|
|
663
963
|
if (!isOperator(value))
|
|
664
|
-
|
|
964
|
+
fail2("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
665
965
|
const stored = operatorValue(value);
|
|
666
966
|
if (!isRecord(stored) || !Array.isArray(stored.path)) {
|
|
667
|
-
|
|
967
|
+
fail2("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
668
968
|
}
|
|
669
969
|
const path = stored.path;
|
|
670
970
|
if (path.length === 0 || path.some((item) => typeof item !== "string")) {
|
|
671
|
-
|
|
971
|
+
fail2("OKM1121", `json.set on ${column.field} needs a list of segments.`);
|
|
672
972
|
}
|
|
673
973
|
const encoded = encodeJson(stored.value);
|
|
674
974
|
const ref = `${alias}.${quote(column.sql)}`;
|
|
@@ -685,10 +985,10 @@ function emitArrayWrite(sql, column, name, value, alias) {
|
|
|
685
985
|
assertOperatorFits(column, name);
|
|
686
986
|
const encode = column.elementEncode;
|
|
687
987
|
if (encode === undefined) {
|
|
688
|
-
|
|
988
|
+
fail2("OKM1124", `Operator ${name} does not apply to ${column.dataType} column ${column.field}. Accepted operators: eq, not, lt, lte, gt, gte, between, inList, notIn, inc.`);
|
|
689
989
|
}
|
|
690
990
|
if (!isOperator(value)) {
|
|
691
|
-
|
|
991
|
+
fail2("OKM1121", `${name} on ${column.field} needs an element.`);
|
|
692
992
|
}
|
|
693
993
|
const fn = name === "arr.append" ? "array_append" : "array_remove";
|
|
694
994
|
sql.text(`${fn}(${alias}.${quote(column.sql)}, `);
|
|
@@ -716,11 +1016,42 @@ function emitBare(schema, table, where, sql, alias) {
|
|
|
716
1016
|
};
|
|
717
1017
|
emitWhere(schema, table, where, sink, alias, 0);
|
|
718
1018
|
if (!dropped) {
|
|
719
|
-
|
|
1019
|
+
fail2("OKM1102", `update on ${table.model.name} needs a where for each row.`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function traitNamed(table, name) {
|
|
1023
|
+
const traits = table.model.traits;
|
|
1024
|
+
if (traits === undefined)
|
|
1025
|
+
return false;
|
|
1026
|
+
for (const trait of traits)
|
|
1027
|
+
if (trait.name === name)
|
|
1028
|
+
return true;
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
function traitGroup(table) {
|
|
1032
|
+
const traits = table.model.traits;
|
|
1033
|
+
if (traits === undefined)
|
|
1034
|
+
return;
|
|
1035
|
+
for (const trait of traits) {
|
|
1036
|
+
if (trait.name !== "sortable")
|
|
1037
|
+
continue;
|
|
1038
|
+
const group = trait.group;
|
|
1039
|
+
return typeof group === "string" ? group : undefined;
|
|
720
1040
|
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
function mentionsVersion(input) {
|
|
1044
|
+
if (Array.isArray(input)) {
|
|
1045
|
+
for (const item of input) {
|
|
1046
|
+
if (isRecord(item) && Object.hasOwn(item, "version"))
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
return isRecord(input) && Object.hasOwn(input, "version");
|
|
721
1052
|
}
|
|
722
1053
|
|
|
723
|
-
class
|
|
1054
|
+
class Sql2 {
|
|
724
1055
|
parts = [];
|
|
725
1056
|
params = [];
|
|
726
1057
|
text(value) {
|
|
@@ -748,7 +1079,7 @@ class Sql {
|
|
|
748
1079
|
}
|
|
749
1080
|
function insertRow(table, value, allow, tenancy) {
|
|
750
1081
|
if (!isRecord(value))
|
|
751
|
-
|
|
1082
|
+
fail2("OKM1121", "insert expects an object or a list of objects.");
|
|
752
1083
|
const row = {};
|
|
753
1084
|
for (const key of Object.keys(value)) {
|
|
754
1085
|
const column = table.columns.get(key);
|
|
@@ -760,6 +1091,7 @@ function insertRow(table, value, allow, tenancy) {
|
|
|
760
1091
|
if (value[key] !== undefined)
|
|
761
1092
|
row[key] = value[key];
|
|
762
1093
|
}
|
|
1094
|
+
refuseHalfMorph(table, row);
|
|
763
1095
|
return row;
|
|
764
1096
|
}
|
|
765
1097
|
function cellsFor(columns, row) {
|
|
@@ -824,7 +1156,7 @@ function tenantKeyList(tenancy) {
|
|
|
824
1156
|
return [tenancy.key];
|
|
825
1157
|
}
|
|
826
1158
|
function pathInsert(table, columns, rows, scope, tenancy) {
|
|
827
|
-
const sql = new
|
|
1159
|
+
const sql = new Sql2;
|
|
828
1160
|
const names = columns.map((column) => quote(column.sql)).join(", ");
|
|
829
1161
|
const selected = columns.map((column) => `v.${quote(column.sql)}`).join(", ");
|
|
830
1162
|
sql.text(`insert into ${quote(table.model.sql)} (${names}) select ${selected} from (values `);
|
|
@@ -859,9 +1191,13 @@ function pathInsert(table, columns, rows, scope, tenancy) {
|
|
|
859
1191
|
});
|
|
860
1192
|
return sql.statement();
|
|
861
1193
|
}
|
|
862
|
-
function
|
|
1194
|
+
function writeSet2(table, value, allow, tenancy) {
|
|
863
1195
|
if (!isRecord(value))
|
|
864
|
-
|
|
1196
|
+
fail2("OKM1121", "set must be an object of fields.");
|
|
1197
|
+
const group = traitGroup(table);
|
|
1198
|
+
if (group !== undefined && Object.hasOwn(value, group) && value[group] !== undefined) {
|
|
1199
|
+
fail2("OKM1120", `update on ${table.model.name} cannot change ${group}. It groups position.`, "Move stays in one group.");
|
|
1200
|
+
}
|
|
865
1201
|
const set = {};
|
|
866
1202
|
for (const key of Object.keys(value)) {
|
|
867
1203
|
if (value[key] === undefined)
|
|
@@ -874,20 +1210,116 @@ function writeSet(table, value, allow, tenancy) {
|
|
|
874
1210
|
refuse(table, column);
|
|
875
1211
|
set[key] = value[key];
|
|
876
1212
|
}
|
|
1213
|
+
refuseHalfMorph(table, set);
|
|
877
1214
|
return set;
|
|
878
1215
|
}
|
|
1216
|
+
function refuseTreeCycle(table, where, set, scopeKey) {
|
|
1217
|
+
const own = keyedId(table, where, scopeKey);
|
|
1218
|
+
if (own === undefined)
|
|
1219
|
+
return;
|
|
1220
|
+
for (const relation of table.model.relations) {
|
|
1221
|
+
if (relation.kind !== "many" || relation.through !== undefined || relation.morph !== undefined) {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (relation.table !== table.model.name)
|
|
1225
|
+
continue;
|
|
1226
|
+
for (const sql of relation.remote) {
|
|
1227
|
+
const column = columnBySql(table, sql);
|
|
1228
|
+
if (column === undefined)
|
|
1229
|
+
continue;
|
|
1230
|
+
const value = set[column.field];
|
|
1231
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
if ((typeof value === "string" ? value : value.toString()) !== own)
|
|
1235
|
+
continue;
|
|
1236
|
+
fail2("OKM1120", `update on ${table.model.name}.${column.field} cannot point a tree row at itself.`, "Set the parent to another row. A filter that is not the primary key is not checked.");
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function keyedId(table, where, scopeKey) {
|
|
1241
|
+
if (!isRecord(where))
|
|
1242
|
+
return;
|
|
1243
|
+
const primary = table.model.primary;
|
|
1244
|
+
const required = primary.filter((field) => field !== scopeKey);
|
|
1245
|
+
const field = required.length === 1 ? required[0] : undefined;
|
|
1246
|
+
if (field === undefined || !Object.hasOwn(where, field))
|
|
1247
|
+
return;
|
|
1248
|
+
for (const key of Object.keys(where)) {
|
|
1249
|
+
if (!primary.includes(key))
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const value = where[field];
|
|
1253
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "bigint") {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
return String(value);
|
|
1257
|
+
}
|
|
1258
|
+
function columnBySql(table, sql) {
|
|
1259
|
+
for (const column of table.model.columns) {
|
|
1260
|
+
if (column.sql === sql)
|
|
1261
|
+
return column;
|
|
1262
|
+
}
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
879
1265
|
function updateItem(table, value, allow, tenancy) {
|
|
880
1266
|
if (!isRecord(value))
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const set =
|
|
1267
|
+
fail2("OKM1121", "update expects { where, set } or a list of rows.");
|
|
1268
|
+
rejectUpdateKeys2(value, true);
|
|
1269
|
+
const set = writeSet2(table, value.set, allow, tenancy);
|
|
1270
|
+
if (Object.keys(set).length === 0)
|
|
1271
|
+
fail2("OKM1120", "update set is empty.");
|
|
884
1272
|
if (value.where !== undefined)
|
|
885
1273
|
return { where: value.where, set };
|
|
886
1274
|
if (value.id !== undefined)
|
|
887
1275
|
return { where: { id: value.id }, set };
|
|
888
|
-
|
|
1276
|
+
fail2("OKM1102", `update on ${table.model.name} needs id or where for each row.`);
|
|
889
1277
|
}
|
|
890
|
-
function
|
|
1278
|
+
function rejectMixedSets2(rows) {
|
|
1279
|
+
const kind = new Map;
|
|
1280
|
+
for (const row of rows) {
|
|
1281
|
+
for (const key of Object.keys(row.set)) {
|
|
1282
|
+
const value = row.set[key];
|
|
1283
|
+
if (value === undefined)
|
|
1284
|
+
continue;
|
|
1285
|
+
const next = isOperator(value) ? operatorName(value) : "value";
|
|
1286
|
+
const previous = kind.get(key);
|
|
1287
|
+
if (previous === undefined) {
|
|
1288
|
+
kind.set(key, next);
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (previous === next)
|
|
1292
|
+
continue;
|
|
1293
|
+
const left = previous === "value" ? "a value" : previous;
|
|
1294
|
+
const right = next === "value" ? "a value" : next;
|
|
1295
|
+
fail2("OKM1120", `${key} is both ${left} and ${right} in this update.`, `Use one value or one operator for ${key} in every row.`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
function readClaim2(input) {
|
|
1300
|
+
if (Object.hasOwn(input, "wait")) {
|
|
1301
|
+
fail2("OKM1120", 'wait belongs on a read. An update claims rows with lock: "skip".', 'Use lock: "skip" and limit. A read uses wait: "nowait" or "skip" inside tx().');
|
|
1302
|
+
}
|
|
1303
|
+
const lock = input.lock;
|
|
1304
|
+
const limit = input.limit;
|
|
1305
|
+
if (lock === undefined && limit === undefined)
|
|
1306
|
+
return;
|
|
1307
|
+
if (lock === undefined) {
|
|
1308
|
+
fail2("OKM1120", 'limit on an update needs lock: "skip".', 'Pass lock: "skip" to claim up to limit rows, or remove limit.');
|
|
1309
|
+
}
|
|
1310
|
+
if (lock !== "skip") {
|
|
1311
|
+
const shown = typeof lock === "string" ? lock : JSON.stringify(lock) ?? "undefined";
|
|
1312
|
+
fail2("OKM1120", `lock on an update must be "skip". ${shown} is not a claim.`, 'Pass lock: "skip". A read uses lock: "update" or "share", and wait: "nowait" or "skip", inside tx().');
|
|
1313
|
+
}
|
|
1314
|
+
if (limit === undefined) {
|
|
1315
|
+
fail2("OKM1120", 'lock: "skip" needs a limit.', "Pass limit so the claim takes a fixed number of rows.");
|
|
1316
|
+
}
|
|
1317
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 2147483647) {
|
|
1318
|
+
fail2("OKM1121", "limit must be a whole number from 1 to 2147483647.");
|
|
1319
|
+
}
|
|
1320
|
+
return limit;
|
|
1321
|
+
}
|
|
1322
|
+
function rejectUpdateKeys2(input, row) {
|
|
891
1323
|
const accepted = row ? UPDATE_ROW_KEYS : UPDATE_KEYS;
|
|
892
1324
|
for (const key of Object.keys(input)) {
|
|
893
1325
|
if (accepted.includes(key))
|
|
@@ -895,15 +1327,15 @@ function rejectUpdateKeys(input, row) {
|
|
|
895
1327
|
throwNamed2("OKM1120", key, [...accepted, ...FILTER_OPTIONS], `Option ${key} is not accepted by update. Accepted options: ${accepted.join(", ")}. timeout and signal go in the second argument.`);
|
|
896
1328
|
}
|
|
897
1329
|
}
|
|
898
|
-
function
|
|
1330
|
+
function requireFilter2(table, where, mods, op) {
|
|
899
1331
|
if (mods.all !== undefined) {
|
|
900
1332
|
if (mods.all.trim().length === 0) {
|
|
901
|
-
|
|
1333
|
+
fail2("OKM1102", `${op} on ${table.model.name} needs a where. .all needs a reason.`);
|
|
902
1334
|
}
|
|
903
1335
|
return;
|
|
904
1336
|
}
|
|
905
1337
|
if (!effectivePredicate(where)) {
|
|
906
|
-
|
|
1338
|
+
fail2("OKM1102", `${op} on ${table.model.name} needs a where. Pass a filter, or .all(reason).`);
|
|
907
1339
|
}
|
|
908
1340
|
}
|
|
909
1341
|
function chunkRows(rows) {
|
|
@@ -915,7 +1347,7 @@ function chunkRows(rows) {
|
|
|
915
1347
|
for (const cell of row)
|
|
916
1348
|
if (cell.kind !== "default")
|
|
917
1349
|
cost += 1;
|
|
918
|
-
if (current.length > 0 && count + cost >
|
|
1350
|
+
if (current.length > 0 && count + cost > WRITE_PARAM_BUDGET2) {
|
|
919
1351
|
chunks.push(current);
|
|
920
1352
|
current = [];
|
|
921
1353
|
count = 0;
|
|
@@ -940,9 +1372,11 @@ function keyWire(columns, row) {
|
|
|
940
1372
|
return encodeColumn(column, value);
|
|
941
1373
|
});
|
|
942
1374
|
}
|
|
943
|
-
function
|
|
1375
|
+
function returningOption2(table, value) {
|
|
944
1376
|
if (value === undefined)
|
|
945
1377
|
return;
|
|
1378
|
+
if (value === true)
|
|
1379
|
+
return selectedColumns(table, undefined);
|
|
946
1380
|
return selectedColumns(table, value);
|
|
947
1381
|
}
|
|
948
1382
|
function returningClause(columns, alias) {
|
|
@@ -965,7 +1399,7 @@ function outputsOf(columns) {
|
|
|
965
1399
|
includes: []
|
|
966
1400
|
};
|
|
967
1401
|
}
|
|
968
|
-
function
|
|
1402
|
+
function expectOf2(options, mods) {
|
|
969
1403
|
const value = mods.expect ?? options.expect;
|
|
970
1404
|
if (value === undefined)
|
|
971
1405
|
return;
|
|
@@ -997,17 +1431,49 @@ function callOptions(options) {
|
|
|
997
1431
|
...hasTimeout ? { timeout } : {}
|
|
998
1432
|
};
|
|
999
1433
|
}
|
|
1434
|
+
function refuseHalfMorph(table, row) {
|
|
1435
|
+
const relations = table.model.relations;
|
|
1436
|
+
for (let index = 0;index < relations.length; index += 1) {
|
|
1437
|
+
const relation = relations[index];
|
|
1438
|
+
const morph = relation?.morph;
|
|
1439
|
+
if (relation === undefined || morph === undefined || morph.direction !== "to")
|
|
1440
|
+
continue;
|
|
1441
|
+
const typeSet = Object.hasOwn(row, morph.typeField);
|
|
1442
|
+
const idSet = Object.hasOwn(row, morph.idField);
|
|
1443
|
+
const type = typeSet ? row[morph.typeField] : undefined;
|
|
1444
|
+
const id = idSet ? row[morph.idField] : undefined;
|
|
1445
|
+
const typeClear = type === null;
|
|
1446
|
+
const idClear = id === null;
|
|
1447
|
+
if (!typeSet && !idSet)
|
|
1448
|
+
continue;
|
|
1449
|
+
if (typeSet !== idSet || typeClear !== idClear) {
|
|
1450
|
+
fail2("OKM1121", `Set ${table.model.name}.${morph.typeField} and ${morph.idField} together, or clear both. Use morphTo("${morph.name}", type, id).`);
|
|
1451
|
+
}
|
|
1452
|
+
if (typeClear)
|
|
1453
|
+
continue;
|
|
1454
|
+
let accepted = false;
|
|
1455
|
+
const names = [];
|
|
1456
|
+
for (const target of morph.targets) {
|
|
1457
|
+
names.push(target.type);
|
|
1458
|
+
if (target.type === type)
|
|
1459
|
+
accepted = true;
|
|
1460
|
+
}
|
|
1461
|
+
if (!accepted) {
|
|
1462
|
+
fail2("OKM1120", `Relation ${table.model.name}.${relation.name} type ${morph.typeField} is ${typeof type === "string" ? type : typeof type}. Accepted tables: ${names.join(", ")}.`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1000
1466
|
function refuse(table, column) {
|
|
1001
1467
|
if (fieldSealed(table.model, column.field)) {
|
|
1002
|
-
|
|
1468
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is set by a trait. Input cannot set it.`);
|
|
1003
1469
|
}
|
|
1004
1470
|
if (column.guardUpdate) {
|
|
1005
|
-
|
|
1471
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is a primary key. Input cannot change it.`);
|
|
1006
1472
|
}
|
|
1007
1473
|
if (column.guarded) {
|
|
1008
|
-
|
|
1474
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is guarded. Input cannot set it.`);
|
|
1009
1475
|
}
|
|
1010
|
-
|
|
1476
|
+
fail2("OKM1120", `Field ${table.model.name}.${column.field} cannot be written.`);
|
|
1011
1477
|
}
|
|
1012
1478
|
function writable(table, column, allow) {
|
|
1013
1479
|
if (fieldSealed(table.model, column.field))
|
|
@@ -1016,15 +1482,15 @@ function writable(table, column, allow) {
|
|
|
1016
1482
|
return true;
|
|
1017
1483
|
return column.guarded && allow.has(column.field);
|
|
1018
1484
|
}
|
|
1019
|
-
function
|
|
1485
|
+
function allowSet2(table, value) {
|
|
1020
1486
|
if (value === undefined)
|
|
1021
1487
|
return NO_ALLOW;
|
|
1022
1488
|
if (!Array.isArray(value))
|
|
1023
|
-
|
|
1489
|
+
fail2("OKM1120", "allow must be a list of field names.");
|
|
1024
1490
|
const set = new Set;
|
|
1025
1491
|
for (const name of value) {
|
|
1026
1492
|
if (typeof name !== "string" || !table.columns.has(name)) {
|
|
1027
|
-
|
|
1493
|
+
fail2("OKM1120", "allow must name fields on the table.");
|
|
1028
1494
|
}
|
|
1029
1495
|
set.add(name);
|
|
1030
1496
|
}
|
|
@@ -1038,7 +1504,7 @@ function noteArchive(table, view) {
|
|
|
1038
1504
|
return;
|
|
1039
1505
|
runSafety(rules, undefined);
|
|
1040
1506
|
}
|
|
1041
|
-
function
|
|
1507
|
+
function noteGuarded2(table, allow, op) {
|
|
1042
1508
|
if (!safetyInstalled())
|
|
1043
1509
|
return;
|
|
1044
1510
|
const contributions = [];
|
|
@@ -1141,8 +1607,5 @@ function collectSecrets(value, names, found, key) {
|
|
|
1141
1607
|
for (const child of Object.keys(value))
|
|
1142
1608
|
collectSecrets(value[child], names, found, child);
|
|
1143
1609
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
executeWrite,
|
|
1147
|
-
explainWrite
|
|
1148
|
-
};
|
|
1610
|
+
|
|
1611
|
+
export { touchFields, fieldSealed, WRITE_PARAM_BUDGET2, executeWrite2, explainWrite2, updateSliceBody2, rejectDuplicateTargets2, emitSet2, Sql2, writeSet2, rejectMixedSets2, readClaim2, rejectUpdateKeys2, requireFilter2, returningOption2, expectOf2, allowSet2, noteGuarded2 };
|