okmodel 0.5.1 → 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 -1116
- 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 +20 -0
- 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 +41 -3
- package/dist/adapters/pg/reserved.d.ts +42 -0
- package/dist/adapters/pg/session.d.ts +0 -18
- package/dist/contracts/catalog/document.d.ts +19 -5
- package/dist/contracts/catalog/object.d.ts +2 -1
- package/dist/contracts/catalog/policy.d.ts +48 -0
- package/dist/contracts/catalog/types.d.ts +25 -1
- package/dist/contracts/catalog/view.d.ts +2 -0
- package/dist/contracts/connection.d.ts +7 -6
- package/dist/contracts/internal.js +1 -1
- package/dist/contracts/location.d.ts +10 -0
- package/dist/dialects/pg/column.d.ts +13 -2
- package/dist/dialects/pg/ddl.d.ts +37 -1
- package/dist/dialects/pg/emit.d.ts +19 -1
- package/dist/dialects/pg/errors.d.ts +32 -1
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/filter-parse.d.ts +5 -4
- package/dist/dialects/pg/floor.d.ts +68 -0
- package/dist/dialects/pg/fn/index.js +2 -2
- package/dist/dialects/pg/index.d.ts +8 -2
- package/dist/dialects/pg/index.js +67 -171
- package/dist/dialects/pg/introspect.d.ts +7 -0
- package/dist/dialects/pg/keys.d.ts +11 -8
- package/dist/dialects/pg/model.d.ts +47 -2
- 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/owner.d.ts +42 -0
- package/dist/dialects/pg/path-endpoint.d.ts +20 -0
- package/dist/dialects/pg/preset.d.ts +1 -1
- package/dist/dialects/pg/relations.d.ts +100 -3
- package/dist/dialects/pg/role/check.d.ts +3 -0
- package/dist/dialects/pg/role/index.d.ts +71 -6
- package/dist/dialects/pg/role/sql.d.ts +11 -0
- package/dist/dialects/pg/schema.d.ts +33 -3
- package/dist/dialects/pg/table.d.ts +25 -3
- package/dist/dialects/pg/tenancy.d.ts +45 -3
- package/dist/dialects/pg/view/client.d.ts +19 -1
- package/dist/dialects/pg/view/index.d.ts +4 -0
- package/dist/dialects/pg/view/index.js +21 -178
- package/dist/dialects/pg/view/refresh.d.ts +24 -0
- package/dist/runtime/aggregate.d.ts +8 -2
- package/dist/runtime/batch.d.ts +1 -1
- package/dist/runtime/bucket-group.d.ts +70 -0
- package/dist/runtime/conflict.d.ts +24 -6
- package/dist/runtime/drift.d.ts +9 -0
- package/dist/runtime/include.d.ts +0 -8
- package/dist/runtime/inflight.d.ts +14 -0
- package/dist/runtime/page.d.ts +4 -1
- package/dist/runtime/pg/bun.js +12 -5
- package/dist/runtime/pg/pg.js +5 -5
- package/dist/runtime/pg/pglite.d.ts +1 -0
- package/dist/runtime/pg/pglite.js +9 -8
- package/dist/runtime/pg/postgresjs.js +5 -5
- package/dist/runtime/plan.d.ts +113 -15
- package/dist/runtime/predicate.d.ts +19 -0
- package/dist/runtime/read-stream.d.ts +13 -16
- package/dist/runtime/result-name.d.ts +17 -0
- package/dist/runtime/safety/index.js +1 -1
- 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/composite.d.ts +26 -0
- package/dist/runtime/tenancy/context.d.ts +16 -0
- package/dist/runtime/tenancy/index.d.ts +9 -5
- package/dist/runtime/tenancy/index.js +1511 -38
- package/dist/runtime/tenancy/path.d.ts +87 -0
- package/dist/runtime/tenancy/rls.d.ts +67 -0
- package/dist/runtime/tenancy/via.d.ts +22 -0
- package/dist/runtime/text-search.d.ts +20 -0
- package/dist/runtime/traits/index.d.ts +80 -0
- package/dist/runtime/traits/index.js +187 -17
- package/dist/runtime/tree.d.ts +38 -0
- package/dist/runtime/types.d.ts +231 -23
- package/dist/runtime/validate/index.js +4 -4
- package/dist/runtime/version.d.ts +39 -0
- package/dist/runtime/window.d.ts +21 -0
- package/dist/runtime/write.d.ts +55 -3
- package/dist/shared/{krxx77g0.js → 14vx95q4.js} +12 -18
- package/dist/shared/1est4ezg.js +313 -0
- package/dist/shared/{4az561fn.js → 1qa0qajz.js} +8 -23
- package/dist/shared/1w508jbr.js +1611 -0
- package/dist/shared/2c13q96h.js +18 -0
- package/dist/shared/2d3xq4ps.js +9 -0
- package/dist/shared/2shdbn8q.js +23 -0
- package/dist/shared/{t4tbsy9h.js → 358zsv4r.js} +1 -1
- package/dist/shared/3v7t1yrv.js +208 -0
- package/dist/shared/5e3tf63j.js +220 -0
- package/dist/shared/5fe3kbsh.js +10 -0
- package/dist/shared/5v3xf80k.js +37 -0
- package/dist/shared/{51mvgj8w.js → 63qtkrca.js} +130 -3
- package/dist/shared/{gbtcc2d7.js → 67txcrcw.js} +62 -66
- package/dist/shared/79dt02vc.js +12 -0
- package/dist/shared/{v36c5g6p.js → 7k9grvf6.js} +407 -14
- package/dist/shared/7kms6fvj.js +20 -0
- package/dist/shared/7mnme5tn.js +8 -0
- package/dist/shared/7n7ddwt4.js +6 -0
- package/dist/shared/{87jvvhnt.js → 81ebdx9p.js} +48 -68
- package/dist/shared/{szm0cksa.js → 8skf0cam.js} +34 -6
- package/dist/shared/{gjthds9j.js → 9628a9xt.js} +1 -1
- package/dist/shared/9a4ae4p8.js +31 -0
- package/dist/shared/{8xsbagad.js → 9s0ypp87.js} +88 -7
- package/dist/shared/9y3mmmh1.js +436 -0
- package/dist/shared/ab9gmbwy.js +213 -0
- package/dist/shared/{sycnx8gt.js → ax7qv55k.js} +18 -8
- package/dist/shared/{f5cw5q5r.js → aytqh5y2.js} +6 -4
- package/dist/shared/{fancm5pt.js → b5pftnn2.js} +138 -15
- package/dist/shared/{efmy2gcg.js → b76b8d95.js} +2 -2
- package/dist/shared/bdk4sty6.js +58 -0
- package/dist/shared/{32zqc1dw.js → bg3rym3g.js} +63 -32
- package/dist/shared/{0t22wwh1.js → bm22fvzr.js} +16 -14
- package/dist/shared/bz84tkzm.js +342 -0
- package/dist/shared/{31vm914h.js → cm3w0ywp.js} +15 -16
- package/dist/shared/cn957f8d.js +179 -0
- package/dist/shared/{pf0d8f77.js → cnkky55j.js} +25 -14
- package/dist/shared/{5fqx9yh4.js → dk8v79p3.js} +3 -1
- package/dist/shared/{nkaxvt9q.js → dkxgzaqd.js} +224 -19
- package/dist/shared/dzj09cye.js +10 -0
- package/dist/shared/{4m9fn52z.js → ed0b8k4s.js} +1 -1
- package/dist/shared/{z2rnngj4.js → ek2wt2dz.js} +8 -5
- package/dist/shared/exn3sd90.js +359 -0
- package/dist/shared/g6evj1j3.js +66 -0
- package/dist/shared/ga9j92qj.js +25 -0
- package/dist/shared/{qv2ydck9.js → gs74dbj5.js} +651 -76
- package/dist/shared/gzaveazc.js +285 -0
- package/dist/shared/{8rsy3hxz.js → h15jx9bc.js} +1 -1
- package/dist/shared/{dyj86cmj.js → hyh73qk6.js} +3 -3
- package/dist/shared/je4axnm8.js +22 -0
- package/dist/shared/{ycatkxmq.js → jgqj11w4.js} +1 -1
- package/dist/shared/{6qd27mx0.js → jvd0mqag.js} +5 -5
- package/dist/shared/jznfax0y.js +155 -0
- package/dist/shared/{cgbamx3e.js → kb2sdhfb.js} +7 -4
- package/dist/shared/{ps7a6h14.js → kr0jzbw2.js} +12 -8
- package/dist/shared/{janx09m5.js → ktz42yez.js} +20 -8
- package/dist/shared/{gde73es6.js → m6p12kbr.js} +56 -55
- package/dist/shared/{r6nwkxnt.js → m7n9h2a5.js} +4 -4
- package/dist/shared/{672t5vym.js → mss6ctgv.js} +2 -2
- package/dist/shared/{nxdp2ze3.js → mtaszv5a.js} +144 -12
- package/dist/shared/n816peh1.js +45 -0
- package/dist/shared/nbw1z4yd.js +18 -0
- package/dist/shared/{cctw17hj.js → p6qc5gcp.js} +38 -34
- package/dist/shared/{6rk2gen5.js → pbp1ss85.js} +20 -17
- package/dist/shared/pt94kz7f.js +6 -0
- package/dist/shared/{wq9n6pwr.js → pz6rj8ay.js} +82 -9
- package/dist/shared/q5c6ngn3.js +13 -0
- package/dist/shared/{8bwbwy8h.js → qq3899ht.js} +128 -18
- package/dist/shared/{8ktwd3rh.js → r3j8s5y0.js} +1 -1
- package/dist/shared/rhs3xe4k.js +7 -0
- package/dist/shared/{ntneex2a.js → rpycarcr.js} +6 -6
- package/dist/shared/{sdc7r2ww.js → rs73fca9.js} +8 -8
- package/dist/shared/{tm5ab8we.js → sfz1ksn6.js} +7 -4
- package/dist/shared/{6mnp99hx.js → svncdeh5.js} +16 -12
- package/dist/shared/{hf22e0wt.js → tbrvgw6z.js} +62 -42
- package/dist/shared/te5as7z7.js +622 -0
- package/dist/shared/{ga3sfy93.js → tsvww22q.js} +535 -200
- package/dist/shared/{47x8x9ze.js → ttsqgp4p.js} +20 -13
- package/dist/shared/{x72jb7ad.js → v8g1vgv8.js} +1 -1
- package/dist/shared/vb6qfyve.js +152 -0
- package/dist/shared/{r2sp8hrc.js → vczs8gd4.js} +22 -13
- package/dist/shared/{1g0drakv.js → vqrjpcqn.js} +145 -3
- package/dist/shared/{2pqx26h8.js → w72kgg4q.js} +31 -22
- package/dist/shared/{96qcj21c.js → w79hsckn.js} +16 -51
- package/dist/shared/wa07eas9.js +730 -0
- package/dist/shared/{k8v5v5wj.js → x933k9a3.js} +75 -34
- package/dist/shared/{cr74e96e.js → xhfsfkgk.js} +62 -28
- package/dist/shared/xjk7hnn5.js +272 -0
- package/dist/shared/{5enne4ma.js → xnxsed62.js} +66 -113
- package/dist/shared/xqve9598.js +53 -0
- package/dist/shared/{9x25fxhc.js → y34a8714.js} +1 -1
- package/dist/shared/{k7dwkhvq.js → y6jdds7g.js} +4 -1
- package/dist/shared/y6njgyda.js +249 -0
- package/dist/shared/{c9z5g0fm.js → ywr8hmey.js} +5 -2
- package/dist/shared/{g7ez901n.js → ywspezzx.js} +1 -1
- package/dist/shared/z7s6fpxv.js +62 -0
- package/dist/shared/{n7mcc1gk.js → zchaghkf.js} +4 -3
- package/dist/shared/{9m80mkac.js → ze4hxsmy.js} +1 -1
- 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/shared/{y005b761.js → zy8r7qvz.js} +6 -2
- package/dist/tooling/cli.js +41 -15
- package/dist/tooling/migrate/apply.d.ts +14 -0
- package/dist/tooling/migrate/check.d.ts +3 -4
- package/dist/tooling/migrate/classify.d.ts +4 -0
- package/dist/tooling/migrate/config.d.ts +3 -2
- package/dist/tooling/migrate/engine.d.ts +3 -2
- package/dist/tooling/migrate/files.d.ts +11 -1
- package/dist/tooling/migrate/index.js +14 -14
- package/dist/tooling/migrate/lint.d.ts +1 -1
- package/dist/tooling/migrate/lost.d.ts +28 -0
- package/dist/tooling/migrate/paths.d.ts +14 -0
- package/dist/tooling/migrate/plan.d.ts +6 -3
- package/dist/tooling/migrate/runner.d.ts +25 -0
- package/dist/tooling/migrate/sandbox.d.ts +57 -0
- package/dist/tooling/migrate/sql-lex.d.ts +20 -3
- package/dist/tooling/migrate/text.d.ts +42 -0
- package/dist/tooling/node-version.d.ts +21 -0
- package/dist/tooling/testing/facts.d.ts +33 -5
- package/dist/tooling/testing/index.d.ts +1 -1
- package/dist/tooling/testing/index.js +3 -3
- package/dist/tooling/testing/isolation.d.ts +11 -5
- package/package.json +8 -3
- package/dist/shared/7m88x3e9.js +0 -45
- package/dist/shared/7zwwyh0v.js +0 -448
- package/dist/shared/aj2qxyhj.js +0 -90
- package/dist/shared/caw8b9d8.js +0 -204
- package/dist/shared/jwtxdj06.js +0 -76
- package/dist/shared/kfjp0gck.js +0 -212
- package/dist/shared/q58y17hy.js +0 -21
- package/dist/shared/qm7cdtvr.js +0 -912
- package/dist/shared/r8x5qhd7.js +0 -86
- package/dist/shared/zqrnhwsq.js +0 -31
|
@@ -0,0 +1,1611 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stack2
|
|
3
|
+
} from "./mss6ctgv.js";
|
|
4
|
+
import {
|
|
5
|
+
effectivePredicate
|
|
6
|
+
} from "./n816peh1.js";
|
|
7
|
+
import {
|
|
8
|
+
assertOperatorFits,
|
|
9
|
+
textArray,
|
|
10
|
+
arrayElementType
|
|
11
|
+
} from "./zchaghkf.js";
|
|
12
|
+
import {
|
|
13
|
+
runWrite
|
|
14
|
+
} from "./n4jqhpqz.js";
|
|
15
|
+
import {
|
|
16
|
+
writeWouldValidate
|
|
17
|
+
} from "./ze4hxsmy.js";
|
|
18
|
+
import {
|
|
19
|
+
encodeJson
|
|
20
|
+
} from "./9628a9xt.js";
|
|
21
|
+
import {
|
|
22
|
+
withRowFilters,
|
|
23
|
+
archiveRules,
|
|
24
|
+
emitWhere,
|
|
25
|
+
selectedColumns,
|
|
26
|
+
rejectKeys,
|
|
27
|
+
takeObject,
|
|
28
|
+
projectExpr,
|
|
29
|
+
decodeRow,
|
|
30
|
+
indexes,
|
|
31
|
+
quote,
|
|
32
|
+
isRecord,
|
|
33
|
+
registerFailFix,
|
|
34
|
+
fail2
|
|
35
|
+
} from "./67txcrcw.js";
|
|
36
|
+
import {
|
|
37
|
+
safetyInstalled,
|
|
38
|
+
runSafety
|
|
39
|
+
} from "./a5sxz52y.js";
|
|
40
|
+
import {
|
|
41
|
+
isOperator,
|
|
42
|
+
operatorName,
|
|
43
|
+
operatorValue
|
|
44
|
+
} from "./cn0n68ft.js";
|
|
45
|
+
import {
|
|
46
|
+
OkmError2,
|
|
47
|
+
throwNamed2
|
|
48
|
+
} from "./52szzakm.js";
|
|
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
|
+
|
|
94
|
+
// src/runtime/write.ts
|
|
95
|
+
registerFailFix("OKM1102", "Pass where, or call .all(reason) to match every row.");
|
|
96
|
+
registerFailFix("OKM1190", "Remove the guarded field. Input cannot set it.");
|
|
97
|
+
var WRITE_PARAM_BUDGET2 = 2048;
|
|
98
|
+
var REDACTED = "[redacted]";
|
|
99
|
+
function encodeColumn(column, value) {
|
|
100
|
+
if (!column.hidden && !column.sensitive)
|
|
101
|
+
return column.encode(value);
|
|
102
|
+
try {
|
|
103
|
+
return column.encode(value);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof OkmError2) {
|
|
106
|
+
throw new OkmError2(error.code, `Field ${column.field} was rejected.`, {
|
|
107
|
+
kind: error.kind,
|
|
108
|
+
fix: error.fix
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function requireValidationImport(schema, op, tableName, options) {
|
|
115
|
+
if (op === "delete" || validationRegistered(schema))
|
|
116
|
+
return;
|
|
117
|
+
const stored = schema;
|
|
118
|
+
const source = stored.tables?.find((item) => item.name === tableName);
|
|
119
|
+
if (source === undefined || !writeWouldValidate(stored.validation, source, options))
|
|
120
|
+
return;
|
|
121
|
+
const { refuseMissingValidation } = await import("./83eaefwr.js");
|
|
122
|
+
refuseMissingValidation();
|
|
123
|
+
}
|
|
124
|
+
function validationRegistered(schema) {
|
|
125
|
+
const hooks = schema.hooks;
|
|
126
|
+
if (hooks === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
for (const hook of hooks) {
|
|
129
|
+
if (hook["~v"] === 1)
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
var INSERT_OPTIONS = [
|
|
135
|
+
"allow",
|
|
136
|
+
"expect",
|
|
137
|
+
"onConflict",
|
|
138
|
+
"returning",
|
|
139
|
+
"signal",
|
|
140
|
+
"timeout",
|
|
141
|
+
"validate"
|
|
142
|
+
];
|
|
143
|
+
var FILTER_OPTIONS = ["allow", "expect", "returning", "signal", "timeout", "validate"];
|
|
144
|
+
var UPDATE_KEYS = ["set", "where", "limit", "lock", "version"];
|
|
145
|
+
var UPDATE_ROW_KEYS = ["id", "set", "where", "version"];
|
|
146
|
+
var NO_ALLOW = new Set;
|
|
147
|
+
async function executeWrite2(host, op, table, input, options, mods, scope, plan) {
|
|
148
|
+
const prepared = await prepareWrite(host, op, table, input, options, mods, scope);
|
|
149
|
+
if (plan !== undefined)
|
|
150
|
+
return prepared;
|
|
151
|
+
if (prepared.statements.length === 0)
|
|
152
|
+
return prepared.finish([]);
|
|
153
|
+
const results = await runWrite(host, prepared.statements, prepared.options);
|
|
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));
|
|
159
|
+
}
|
|
160
|
+
async function prepareWrite(host, op, table, input, options, mods, scope) {
|
|
161
|
+
try {
|
|
162
|
+
const planned = await planWrite(host.schema, host.generators, op, table, input, options, mods, scope);
|
|
163
|
+
return {
|
|
164
|
+
statements: planned.statements,
|
|
165
|
+
options: callOptions(options),
|
|
166
|
+
...planned.parentGuard === true ? { checked: "parent" } : planned.expect !== undefined ? { checked: "expect" } : {},
|
|
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)
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw scrubWrite(host.schema, table, input, error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function explainWrite2(schema, op, table, input, options, mods, generators, scope) {
|
|
177
|
+
try {
|
|
178
|
+
const planned = await planWrite(schema, generators, op, table, input, options, mods, scope);
|
|
179
|
+
return { statements: planned.statements };
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw scrubWrite(schema, table, input, error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function planWrite(schema, generators, op, tableName, input, options, mods, scope) {
|
|
185
|
+
await requireValidationImport(schema, op, tableName, options);
|
|
186
|
+
const table = indexes(schema).get(tableName);
|
|
187
|
+
if (table === undefined) {
|
|
188
|
+
fail2("OKM1120", `Table ${tableName} is not in the schema.`);
|
|
189
|
+
}
|
|
190
|
+
if (mods.uses !== undefined && op !== "insert") {
|
|
191
|
+
const { resolve } = await import("./m7n9h2a5.js");
|
|
192
|
+
mods = { ...mods, presets: resolve(schema, tableName, mods.uses).wheres };
|
|
193
|
+
}
|
|
194
|
+
const record = isRecord(options) ? options : {};
|
|
195
|
+
rejectKeys(record, op === "insert" ? INSERT_OPTIONS : FILTER_OPTIONS, op);
|
|
196
|
+
if (op === "insert") {
|
|
197
|
+
return planInsert(table, input, record, mods, generators, scope, schema.tenancy);
|
|
198
|
+
}
|
|
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
|
+
}
|
|
208
|
+
return withRowFilters(scope, mods.archive, () => {
|
|
209
|
+
noteArchive(table, mods.archive);
|
|
210
|
+
return planUpdate(schema, table, input, record, mods, scope);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return withRowFilters(scope, mods.archive, () => {
|
|
214
|
+
noteArchive(table, mods.archive);
|
|
215
|
+
return planDelete(schema, table, input, record, mods);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function planInsert(table, input, options, mods, generators, scope, tenancy) {
|
|
219
|
+
const allow = allowSet2(table, options.allow);
|
|
220
|
+
noteGuarded2(table, allow, "insert");
|
|
221
|
+
const many = Array.isArray(input);
|
|
222
|
+
const rows = (many ? input : [input]).map((row) => insertRow(table, row, allow, tenancy));
|
|
223
|
+
tenancy?.stamp(table.model.name, rows, scope);
|
|
224
|
+
fillInsert(table, rows, generators);
|
|
225
|
+
const columns = writtenColumns(table, rows, allow, tenantKeyList(tenancy));
|
|
226
|
+
const returning = selectedColumns(table, options.returning);
|
|
227
|
+
const outputs = outputsOf(returning);
|
|
228
|
+
const clause = returningClause(returning, undefined);
|
|
229
|
+
const expect = expectOf2(options, mods);
|
|
230
|
+
const shape = many ? "rows" : "row";
|
|
231
|
+
const path = tenancy?.isPath?.(table.model.name) === true;
|
|
232
|
+
if (path && options.onConflict !== undefined && options.onConflict !== "error") {
|
|
233
|
+
fail2("OKM1120", `onConflict on ${table.model.name} is not available. The insert checks the parent.`);
|
|
234
|
+
}
|
|
235
|
+
const sorting = traitNamed(table, "sortable");
|
|
236
|
+
if (columns.length === 0 && !sorting) {
|
|
237
|
+
const text = `insert into ${quote(table.model.sql)} default values${clause}`;
|
|
238
|
+
return {
|
|
239
|
+
statements: rows.map(() => ({ text })),
|
|
240
|
+
outputs,
|
|
241
|
+
shape,
|
|
242
|
+
expect,
|
|
243
|
+
keys: undefined,
|
|
244
|
+
keyColumns: undefined
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const conflictMod = options.onConflict === undefined || options.onConflict === "error" ? undefined : await import("./bg3rym3g.js");
|
|
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;
|
|
253
|
+
const chunks = chunkRows(rows.map((row) => cellsFor(columns, row)));
|
|
254
|
+
const statements = [];
|
|
255
|
+
const quiet = [];
|
|
256
|
+
const keys = [];
|
|
257
|
+
let offset = 0;
|
|
258
|
+
for (const chunk of chunks) {
|
|
259
|
+
const slice = rows.slice(offset, offset + chunk.length);
|
|
260
|
+
offset += chunk.length;
|
|
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
|
+
}
|
|
267
|
+
if (conflict === undefined || conflictMod === undefined) {
|
|
268
|
+
statements.push(withParams(`${built.text}${clause}`, built.params));
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
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));
|
|
276
|
+
}
|
|
277
|
+
const conflictColumns = conflict?.kind === "return" ? conflict.columns : undefined;
|
|
278
|
+
return {
|
|
279
|
+
statements,
|
|
280
|
+
outputs,
|
|
281
|
+
shape,
|
|
282
|
+
expect,
|
|
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
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function planUpdate(schema, table, input, options, mods, scope) {
|
|
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);
|
|
299
|
+
const outputs = outputsOf(returning);
|
|
300
|
+
const expect = expectOf2(options, mods);
|
|
301
|
+
if (Array.isArray(input)) {
|
|
302
|
+
const rows = input.map((item) => {
|
|
303
|
+
const row = updateItem(table, item, allow, schema.tenancy);
|
|
304
|
+
requireFilter2(table, row.where, mods, "update");
|
|
305
|
+
pathAssignment(schema.tenancy, table.model.name, row.set, scope);
|
|
306
|
+
return { ...row, where: stack2(row.where, mods.presets) };
|
|
307
|
+
});
|
|
308
|
+
rejectDuplicateTargets2(table, rows);
|
|
309
|
+
rejectMixedSets2(rows);
|
|
310
|
+
for (const row of rows)
|
|
311
|
+
refuseTreeCycle(table, row.where, row.set, schema.tenancy?.key);
|
|
312
|
+
const moves = rows.some((row) => pathField(schema.tenancy, table.model.name, row.set) !== undefined);
|
|
313
|
+
if (moves) {
|
|
314
|
+
return {
|
|
315
|
+
statements: [pathListUpdate(schema, table, rows, scope, returning)],
|
|
316
|
+
outputs: parentOutputs(returning),
|
|
317
|
+
shape: returning === undefined ? "count" : "rows",
|
|
318
|
+
expect,
|
|
319
|
+
keys: undefined,
|
|
320
|
+
keyColumns: undefined,
|
|
321
|
+
parentGuard: true
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
statements: updateList(schema, table, rows, returning),
|
|
326
|
+
outputs,
|
|
327
|
+
shape: returning === undefined ? "count" : "rows",
|
|
328
|
+
expect,
|
|
329
|
+
keys: undefined,
|
|
330
|
+
keyColumns: undefined
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (!isRecord(input))
|
|
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");
|
|
339
|
+
if (Object.keys(set).length === 0)
|
|
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) {
|
|
348
|
+
return {
|
|
349
|
+
statements: [pathSetUpdate(schema, table, where, set, scope, returning)],
|
|
350
|
+
outputs: parentOutputs(returning),
|
|
351
|
+
shape: returning === undefined ? "count" : "rows",
|
|
352
|
+
expect,
|
|
353
|
+
keys: undefined,
|
|
354
|
+
keyColumns: undefined,
|
|
355
|
+
parentGuard: true
|
|
356
|
+
};
|
|
357
|
+
}
|
|
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;
|
|
369
|
+
sql.text("update ");
|
|
370
|
+
sql.text(quote(table.model.sql));
|
|
371
|
+
sql.text(" as t set ");
|
|
372
|
+
emitSet2(sql, table, set, "t");
|
|
373
|
+
emitFilter(schema, table, where, sql, "t");
|
|
374
|
+
schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
|
|
375
|
+
sql.text(returningClause(returning, "t"));
|
|
376
|
+
return {
|
|
377
|
+
statements: [sql.statement()],
|
|
378
|
+
outputs,
|
|
379
|
+
shape: returning === undefined ? "count" : "rows",
|
|
380
|
+
expect,
|
|
381
|
+
keys: undefined,
|
|
382
|
+
keyColumns: undefined
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function planDelete(schema, table, input, options, mods) {
|
|
386
|
+
if (!isRecord(input))
|
|
387
|
+
fail2("OKM1121", "delete expects { where }.");
|
|
388
|
+
rejectKeys(input, ["where"], "delete");
|
|
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;
|
|
395
|
+
sql.text("delete from ");
|
|
396
|
+
sql.text(quote(table.model.sql));
|
|
397
|
+
sql.text(" as t");
|
|
398
|
+
emitFilter(schema, table, stack2(input.where, mods.presets), sql, "t");
|
|
399
|
+
sql.text(returningClause(returning, "t"));
|
|
400
|
+
return {
|
|
401
|
+
statements: [sql.statement()],
|
|
402
|
+
outputs: outputsOf(returning),
|
|
403
|
+
shape: returning === undefined ? "count" : "rows",
|
|
404
|
+
expect: expectOf2(options, mods),
|
|
405
|
+
keys: undefined,
|
|
406
|
+
keyColumns: undefined
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function finish(results, planned, table) {
|
|
410
|
+
if (planned.versioned !== undefined)
|
|
411
|
+
return finishVersioned(results, planned, table);
|
|
412
|
+
if (planned.parentGuard === true)
|
|
413
|
+
return finishParent(results, planned, table);
|
|
414
|
+
if (planned.shape === "count") {
|
|
415
|
+
let count = 0;
|
|
416
|
+
for (const result of results)
|
|
417
|
+
count += result.count;
|
|
418
|
+
checkExpect(count, planned.expect, table, "write");
|
|
419
|
+
return { count };
|
|
420
|
+
}
|
|
421
|
+
const decoded = decodePlanned(results, planned.outputs);
|
|
422
|
+
const rows = planned.keys === undefined || planned.keyColumns === undefined ? decoded : orderByKeys(decoded, planned.keyColumns, planned.keys);
|
|
423
|
+
checkExpect(rows.length, planned.expect, table, "insert");
|
|
424
|
+
if (planned.parent !== undefined && rows.length < planned.parent) {
|
|
425
|
+
throw new OkmError2("OKM1705", `Insert on ${table} did not find a parent in this tenant.`, {
|
|
426
|
+
fix: { summary: "Insert under a parent that belongs to this tenant." }
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (planned.shape === "row")
|
|
430
|
+
return rows[0] ?? null;
|
|
431
|
+
return rows;
|
|
432
|
+
}
|
|
433
|
+
function finishParent(results, planned, table) {
|
|
434
|
+
const row = results[0]?.rows[0];
|
|
435
|
+
const matched = countCell(row?.[0]);
|
|
436
|
+
const updated = countCell(row?.[1]);
|
|
437
|
+
if (updated < matched) {
|
|
438
|
+
throw new OkmError2("OKM1705", `Update on ${table} did not find a parent in this tenant.`, {
|
|
439
|
+
fix: { summary: "Point the foreign key at a parent in this tenant." }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (planned.shape === "count") {
|
|
443
|
+
checkExpect(updated, planned.expect, table, "write");
|
|
444
|
+
return { count: updated };
|
|
445
|
+
}
|
|
446
|
+
const decoded = [];
|
|
447
|
+
if (updated > 0) {
|
|
448
|
+
for (const result of results) {
|
|
449
|
+
for (const item of result.rows)
|
|
450
|
+
decoded.push(decodeRow(planned.outputs, item));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
checkExpect(decoded.length, planned.expect, table, "write");
|
|
454
|
+
if (planned.shape === "row")
|
|
455
|
+
return decoded[0] ?? null;
|
|
456
|
+
return decoded;
|
|
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
|
+
}
|
|
488
|
+
function countCell(value) {
|
|
489
|
+
if (typeof value === "number")
|
|
490
|
+
return value;
|
|
491
|
+
if (typeof value === "bigint")
|
|
492
|
+
return Number(value);
|
|
493
|
+
if (typeof value === "string" && value.length > 0)
|
|
494
|
+
return Number(value);
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
function pathField(tenancy, table, set) {
|
|
498
|
+
if (tenancy?.isPath?.(table) !== true)
|
|
499
|
+
return;
|
|
500
|
+
const field = tenancy.pathOf?.(table)?.[0]?.localField;
|
|
501
|
+
if (field === undefined || !Object.hasOwn(set, field))
|
|
502
|
+
return;
|
|
503
|
+
return field;
|
|
504
|
+
}
|
|
505
|
+
function pathAssignment(tenancy, table, set, scope) {
|
|
506
|
+
const field = pathField(tenancy, table, set);
|
|
507
|
+
if (field === undefined)
|
|
508
|
+
return;
|
|
509
|
+
const value = set[field];
|
|
510
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
511
|
+
throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field}. Pass the parent id as a string.`, { fix: { summary: "Pass the parent id as a string so the tenant can be checked." } });
|
|
512
|
+
}
|
|
513
|
+
if (scope === undefined || "unscoped" in scope) {
|
|
514
|
+
throw new OkmError2("OKM1705", `Update on ${table} cannot verify ${field} without a tenant.`, {
|
|
515
|
+
fix: { summary: `Call for({ ... }) so ${field} can be checked.` }
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
return field;
|
|
519
|
+
}
|
|
520
|
+
function parentOutputs(returning) {
|
|
521
|
+
const outputs = outputsOf(returning);
|
|
522
|
+
if (returning === undefined)
|
|
523
|
+
return outputs;
|
|
524
|
+
return {
|
|
525
|
+
fields: outputs.fields.map((field) => ({ ...field, at: field.at + 2 })),
|
|
526
|
+
includes: outputs.includes
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function rowId(alias) {
|
|
530
|
+
return `${alias}.tableoid, ${alias}.ctid`;
|
|
531
|
+
}
|
|
532
|
+
function pathSetUpdate(schema, table, where, set, scope, returning) {
|
|
533
|
+
const sql = new Sql2;
|
|
534
|
+
sql.text("with matched as (select ");
|
|
535
|
+
sql.text(rowId("t"));
|
|
536
|
+
sql.text(" from ");
|
|
537
|
+
sql.text(quote(table.model.sql));
|
|
538
|
+
sql.text(" as t");
|
|
539
|
+
emitFilter(schema, table, where, sql, "t");
|
|
540
|
+
sql.text("), updated as (update ");
|
|
541
|
+
sql.text(quote(table.model.sql));
|
|
542
|
+
sql.text(" as t set ");
|
|
543
|
+
emitSet2(sql, table, set, "t");
|
|
544
|
+
sql.text(" where (");
|
|
545
|
+
sql.text(rowId("t"));
|
|
546
|
+
sql.text(") in (select tableoid, ctid from matched)");
|
|
547
|
+
schema.tenancy?.noteParent?.(table.model.name, set, sql.sink(), scope, (field) => table.columns.get(field)?.sql);
|
|
548
|
+
endParent(sql, returning);
|
|
549
|
+
return sql.statement();
|
|
550
|
+
}
|
|
551
|
+
function pathListUpdate(schema, table, rows, scope, returning) {
|
|
552
|
+
const sql = new Sql2;
|
|
553
|
+
sql.text("with matched as (");
|
|
554
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
555
|
+
const row = rows[index];
|
|
556
|
+
if (row === undefined)
|
|
557
|
+
continue;
|
|
558
|
+
if (index > 0)
|
|
559
|
+
sql.text(" union all ");
|
|
560
|
+
sql.text("select t.ctid as tid, ");
|
|
561
|
+
sql.text(String(index));
|
|
562
|
+
sql.text(" as ord from ");
|
|
563
|
+
sql.text(quote(table.model.sql));
|
|
564
|
+
sql.text(" as t where ");
|
|
565
|
+
emitBare(schema, table, row.where, sql, "t");
|
|
566
|
+
}
|
|
567
|
+
sql.text("), rejected as (");
|
|
568
|
+
let rejected = false;
|
|
569
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
570
|
+
const row = rows[index];
|
|
571
|
+
if (row === undefined)
|
|
572
|
+
continue;
|
|
573
|
+
if (pathField(schema.tenancy, table.model.name, row.set) === undefined)
|
|
574
|
+
continue;
|
|
575
|
+
if (rejected)
|
|
576
|
+
sql.text(" union all ");
|
|
577
|
+
rejected = true;
|
|
578
|
+
sql.text("select ");
|
|
579
|
+
sql.text(String(index));
|
|
580
|
+
sql.text(" as ord where exists (select 1 from matched where ord = ");
|
|
581
|
+
sql.text(String(index));
|
|
582
|
+
sql.text(")");
|
|
583
|
+
noteParentNot(schema, table, row.set, scope, sql);
|
|
584
|
+
}
|
|
585
|
+
if (!rejected)
|
|
586
|
+
sql.text("select 0 as ord where false");
|
|
587
|
+
sql.text("), updated as (");
|
|
588
|
+
updateSliceBody2(schema, table, rows, sql, true);
|
|
589
|
+
sql.text(" and not exists (select 1 from rejected)");
|
|
590
|
+
endParent(sql, returning);
|
|
591
|
+
return sql.statement();
|
|
592
|
+
}
|
|
593
|
+
function noteParentNot(schema, table, set, scope, sql) {
|
|
594
|
+
let first = true;
|
|
595
|
+
schema.tenancy?.noteParent?.(table.model.name, set, {
|
|
596
|
+
text(value) {
|
|
597
|
+
if (first) {
|
|
598
|
+
first = false;
|
|
599
|
+
sql.text(" and not ");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
sql.text(value);
|
|
603
|
+
},
|
|
604
|
+
param(encoded) {
|
|
605
|
+
sql.param(encoded, "");
|
|
606
|
+
},
|
|
607
|
+
mark() {}
|
|
608
|
+
}, scope, (field) => table.columns.get(field)?.sql);
|
|
609
|
+
}
|
|
610
|
+
function endParent(sql, returning) {
|
|
611
|
+
if (returning === undefined || returning.length === 0) {
|
|
612
|
+
sql.text(" returning 1) select (select count(*)::int from matched), (select count(*)::int from updated)");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
sql.text(returningClause(returning, "t"));
|
|
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");
|
|
617
|
+
}
|
|
618
|
+
function updateSliceBody2(schema, table, rows, sql, wrapWhere = false, hooks) {
|
|
619
|
+
const fields = [];
|
|
620
|
+
const seen = new Set;
|
|
621
|
+
for (const row of rows) {
|
|
622
|
+
for (const key of Object.keys(row.set)) {
|
|
623
|
+
if (seen.has(key))
|
|
624
|
+
continue;
|
|
625
|
+
seen.add(key);
|
|
626
|
+
const column = table.columns.get(key);
|
|
627
|
+
if (column !== undefined)
|
|
628
|
+
fields.push(column);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (fields.length === 0)
|
|
632
|
+
fail2("OKM1120", "update set is empty.");
|
|
633
|
+
sql.text("update ");
|
|
634
|
+
sql.text(quote(table.model.sql));
|
|
635
|
+
sql.text(" as t set ");
|
|
636
|
+
for (let field = 0;field < fields.length; field += 1) {
|
|
637
|
+
const column = fields[field];
|
|
638
|
+
if (column === undefined)
|
|
639
|
+
continue;
|
|
640
|
+
if (field > 0)
|
|
641
|
+
sql.text(", ");
|
|
642
|
+
sql.text(quote(column.sql));
|
|
643
|
+
sql.text(" = case");
|
|
644
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
645
|
+
const row = rows[index];
|
|
646
|
+
if (row === undefined)
|
|
647
|
+
continue;
|
|
648
|
+
sql.text(" when ");
|
|
649
|
+
emitBare(schema, table, row.where, sql, "t");
|
|
650
|
+
hooks?.afterWhere?.(sql, index);
|
|
651
|
+
sql.text(" then ");
|
|
652
|
+
const value = row.set[column.field];
|
|
653
|
+
if (value === undefined)
|
|
654
|
+
sql.text(`t.${quote(column.sql)}`);
|
|
655
|
+
else
|
|
656
|
+
emitValue(sql, column, value, "t");
|
|
657
|
+
}
|
|
658
|
+
sql.text(" else t.");
|
|
659
|
+
sql.text(quote(column.sql));
|
|
660
|
+
sql.text(" end");
|
|
661
|
+
}
|
|
662
|
+
emitTouch(sql, table, fields.length > 0);
|
|
663
|
+
hooks?.afterSet?.(sql);
|
|
664
|
+
sql.text(wrapWhere ? " where (" : " where ");
|
|
665
|
+
for (let index = 0;index < rows.length; index += 1) {
|
|
666
|
+
const row = rows[index];
|
|
667
|
+
if (row === undefined)
|
|
668
|
+
continue;
|
|
669
|
+
if (index > 0)
|
|
670
|
+
sql.text(" or ");
|
|
671
|
+
sql.text("(");
|
|
672
|
+
emitBare(schema, table, row.where, sql, "t");
|
|
673
|
+
hooks?.afterWhere?.(sql, index);
|
|
674
|
+
sql.text(")");
|
|
675
|
+
}
|
|
676
|
+
if (wrapWhere)
|
|
677
|
+
sql.text(")");
|
|
678
|
+
}
|
|
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) {
|
|
793
|
+
const found = new Map;
|
|
794
|
+
for (const row of rows) {
|
|
795
|
+
const id = rowKey(columns, row);
|
|
796
|
+
if (!found.has(id))
|
|
797
|
+
found.set(id, row);
|
|
798
|
+
}
|
|
799
|
+
const ordered = [];
|
|
800
|
+
for (const key of keys) {
|
|
801
|
+
const row = found.get(key.map((value) => value ?? "").join("\x00"));
|
|
802
|
+
if (row !== undefined)
|
|
803
|
+
ordered.push(row);
|
|
804
|
+
}
|
|
805
|
+
return ordered;
|
|
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
|
+
}
|
|
827
|
+
function wireKey(value) {
|
|
828
|
+
if (typeof value === "string")
|
|
829
|
+
return value;
|
|
830
|
+
return String(value);
|
|
831
|
+
}
|
|
832
|
+
function insertHead(table, columns, rows) {
|
|
833
|
+
const params = [];
|
|
834
|
+
const lists = [];
|
|
835
|
+
for (const row of rows) {
|
|
836
|
+
const slots = [];
|
|
837
|
+
for (const cell of row) {
|
|
838
|
+
if (cell.kind === "default") {
|
|
839
|
+
slots.push("default");
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
params.push(cell.kind === "null" ? null : cell.wire);
|
|
843
|
+
slots.push(`$${String(params.length)}::${cell.dataType}`);
|
|
844
|
+
}
|
|
845
|
+
lists.push(`(${slots.join(", ")})`);
|
|
846
|
+
}
|
|
847
|
+
const names = columns.map((column) => quote(column.sql)).join(", ");
|
|
848
|
+
return {
|
|
849
|
+
text: `insert into ${quote(table.model.sql)} (${names}) values ${lists.join(", ")}`,
|
|
850
|
+
params
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function updateList(schema, table, rows, returning) {
|
|
854
|
+
if (rows.length === 0)
|
|
855
|
+
return [];
|
|
856
|
+
const width = Math.max(1, Object.keys(rows[0]?.set ?? {}).length + 2);
|
|
857
|
+
const size = Math.max(1, Math.floor(WRITE_PARAM_BUDGET2 / (width * 2)));
|
|
858
|
+
const statements = [];
|
|
859
|
+
for (let index = 0;index < rows.length; index += size) {
|
|
860
|
+
statements.push(updateSlice(schema, table, rows.slice(index, index + size), returning));
|
|
861
|
+
}
|
|
862
|
+
return statements;
|
|
863
|
+
}
|
|
864
|
+
function updateSlice(schema, table, rows, returning) {
|
|
865
|
+
const sql = new Sql2;
|
|
866
|
+
updateSliceBody2(schema, table, rows, sql);
|
|
867
|
+
sql.text(returningClause(returning, "t"));
|
|
868
|
+
return sql.statement();
|
|
869
|
+
}
|
|
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) {
|
|
891
|
+
const keys = Object.keys(set);
|
|
892
|
+
let wrote = false;
|
|
893
|
+
for (const key of keys) {
|
|
894
|
+
const column = table.columns.get(key);
|
|
895
|
+
if (column === undefined)
|
|
896
|
+
continue;
|
|
897
|
+
if (wrote)
|
|
898
|
+
sql.text(", ");
|
|
899
|
+
wrote = true;
|
|
900
|
+
sql.text(quote(column.sql));
|
|
901
|
+
sql.text(" = ");
|
|
902
|
+
emitValue(sql, column, set[key], alias);
|
|
903
|
+
}
|
|
904
|
+
wrote = emitTouch(sql, table, wrote);
|
|
905
|
+
}
|
|
906
|
+
function emitTouch(sql, table, wrote) {
|
|
907
|
+
const touch = touchFields(table.model);
|
|
908
|
+
if (touch === undefined)
|
|
909
|
+
return wrote;
|
|
910
|
+
for (const field of touch) {
|
|
911
|
+
const column = table.columns.get(field);
|
|
912
|
+
if (column === undefined)
|
|
913
|
+
continue;
|
|
914
|
+
if (wrote)
|
|
915
|
+
sql.text(", ");
|
|
916
|
+
wrote = true;
|
|
917
|
+
sql.text(quote(column.sql));
|
|
918
|
+
sql.text(" = now()");
|
|
919
|
+
}
|
|
920
|
+
return wrote;
|
|
921
|
+
}
|
|
922
|
+
function emitValue(sql, column, value, alias) {
|
|
923
|
+
if (isOperator(value)) {
|
|
924
|
+
const name = operatorName(value);
|
|
925
|
+
if (name === "inc") {
|
|
926
|
+
emitIncrement(sql, column, value, alias);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
if (name === "json.set") {
|
|
930
|
+
emitJsonSet(sql, column, value, alias);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
if (name === "arr.append" || name === "arr.remove") {
|
|
934
|
+
emitArrayWrite(sql, column, name, value, alias);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
fail2("OKM1121", `Field ${column.field} received an operator. set accepts a value, inc, json.set, arr.append, or arr.remove.`);
|
|
938
|
+
}
|
|
939
|
+
if (value === null) {
|
|
940
|
+
sql.text(`null::${column.dataType}`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (typeof value === "object")
|
|
944
|
+
takeObject(column, value);
|
|
945
|
+
sql.param(encodeColumn(column, value), column.dataType);
|
|
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
|
+
}
|
|
961
|
+
function emitJsonSet(sql, column, value, alias) {
|
|
962
|
+
assertOperatorFits(column, "json.set");
|
|
963
|
+
if (!isOperator(value))
|
|
964
|
+
fail2("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
965
|
+
const stored = operatorValue(value);
|
|
966
|
+
if (!isRecord(stored) || !Array.isArray(stored.path)) {
|
|
967
|
+
fail2("OKM1121", `json.set on ${column.field} needs a path and a value.`);
|
|
968
|
+
}
|
|
969
|
+
const path = stored.path;
|
|
970
|
+
if (path.length === 0 || path.some((item) => typeof item !== "string")) {
|
|
971
|
+
fail2("OKM1121", `json.set on ${column.field} needs a list of segments.`);
|
|
972
|
+
}
|
|
973
|
+
const encoded = encodeJson(stored.value);
|
|
974
|
+
const ref = `${alias}.${quote(column.sql)}`;
|
|
975
|
+
const target = column.dataType === "json" ? `${ref}::jsonb` : ref;
|
|
976
|
+
sql.text(`jsonb_set(${target}, `);
|
|
977
|
+
sql.param(textArray(path, `path on ${column.field}`), "text[]");
|
|
978
|
+
sql.text(", ");
|
|
979
|
+
sql.param(encoded, "jsonb");
|
|
980
|
+
sql.text(")");
|
|
981
|
+
if (column.dataType === "json")
|
|
982
|
+
sql.text("::json");
|
|
983
|
+
}
|
|
984
|
+
function emitArrayWrite(sql, column, name, value, alias) {
|
|
985
|
+
assertOperatorFits(column, name);
|
|
986
|
+
const encode = column.elementEncode;
|
|
987
|
+
if (encode === undefined) {
|
|
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.`);
|
|
989
|
+
}
|
|
990
|
+
if (!isOperator(value)) {
|
|
991
|
+
fail2("OKM1121", `${name} on ${column.field} needs an element.`);
|
|
992
|
+
}
|
|
993
|
+
const fn = name === "arr.append" ? "array_append" : "array_remove";
|
|
994
|
+
sql.text(`${fn}(${alias}.${quote(column.sql)}, `);
|
|
995
|
+
sql.param(encode(operatorValue(value)), arrayElementType(column.dataType, column));
|
|
996
|
+
sql.text(")");
|
|
997
|
+
}
|
|
998
|
+
function emitFilter(schema, table, where, sql, alias) {
|
|
999
|
+
emitWhere(schema, table, where, sql.sink(), alias, 0);
|
|
1000
|
+
}
|
|
1001
|
+
function emitBare(schema, table, where, sql, alias) {
|
|
1002
|
+
let dropped = false;
|
|
1003
|
+
const sink = {
|
|
1004
|
+
text(value) {
|
|
1005
|
+
if (!dropped) {
|
|
1006
|
+
dropped = true;
|
|
1007
|
+
if (value === " where " || value === " and ")
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
sql.text(value);
|
|
1011
|
+
},
|
|
1012
|
+
param(encoded) {
|
|
1013
|
+
sql.param(encoded, "");
|
|
1014
|
+
},
|
|
1015
|
+
mark() {}
|
|
1016
|
+
};
|
|
1017
|
+
emitWhere(schema, table, where, sink, alias, 0);
|
|
1018
|
+
if (!dropped) {
|
|
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;
|
|
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");
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
class Sql2 {
|
|
1055
|
+
parts = [];
|
|
1056
|
+
params = [];
|
|
1057
|
+
text(value) {
|
|
1058
|
+
this.parts.push(value);
|
|
1059
|
+
}
|
|
1060
|
+
param(wire, dataType) {
|
|
1061
|
+
this.params.push(wire);
|
|
1062
|
+
const slot = `$${String(this.params.length)}`;
|
|
1063
|
+
this.parts.push(dataType.length === 0 ? slot : `${slot}::${dataType}`);
|
|
1064
|
+
}
|
|
1065
|
+
sink() {
|
|
1066
|
+
return {
|
|
1067
|
+
text: (value) => {
|
|
1068
|
+
this.text(value);
|
|
1069
|
+
},
|
|
1070
|
+
param: (encoded) => {
|
|
1071
|
+
this.param(encoded, "");
|
|
1072
|
+
},
|
|
1073
|
+
mark() {}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
statement() {
|
|
1077
|
+
return this.params.length === 0 ? { text: this.parts.join("") } : { text: this.parts.join(""), params: this.params };
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
function insertRow(table, value, allow, tenancy) {
|
|
1081
|
+
if (!isRecord(value))
|
|
1082
|
+
fail2("OKM1121", "insert expects an object or a list of objects.");
|
|
1083
|
+
const row = {};
|
|
1084
|
+
for (const key of Object.keys(value)) {
|
|
1085
|
+
const column = table.columns.get(key);
|
|
1086
|
+
if (column === undefined)
|
|
1087
|
+
continue;
|
|
1088
|
+
tenancy?.guard(table.model.name, key, "insert");
|
|
1089
|
+
if (!writable(table, column, allow))
|
|
1090
|
+
refuse(table, column);
|
|
1091
|
+
if (value[key] !== undefined)
|
|
1092
|
+
row[key] = value[key];
|
|
1093
|
+
}
|
|
1094
|
+
refuseHalfMorph(table, row);
|
|
1095
|
+
return row;
|
|
1096
|
+
}
|
|
1097
|
+
function cellsFor(columns, row) {
|
|
1098
|
+
return columns.map((column) => {
|
|
1099
|
+
if (!Object.hasOwn(row, column.field))
|
|
1100
|
+
return { kind: "default" };
|
|
1101
|
+
const value = row[column.field];
|
|
1102
|
+
if (value === null)
|
|
1103
|
+
return { kind: "null", dataType: column.dataType };
|
|
1104
|
+
if (typeof value === "object")
|
|
1105
|
+
takeObject(column, value);
|
|
1106
|
+
return { kind: "value", wire: encodeColumn(column, value), dataType: column.dataType };
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
function fillInsert(table, rows, generators) {
|
|
1110
|
+
const fills = [];
|
|
1111
|
+
for (const column of table.model.columns) {
|
|
1112
|
+
if (column.fill !== undefined)
|
|
1113
|
+
fills.push(column);
|
|
1114
|
+
}
|
|
1115
|
+
if (fills.length === 0)
|
|
1116
|
+
return;
|
|
1117
|
+
for (const row of rows) {
|
|
1118
|
+
for (const column of fills) {
|
|
1119
|
+
if (Object.hasOwn(row, column.field))
|
|
1120
|
+
continue;
|
|
1121
|
+
const fill = column.fill;
|
|
1122
|
+
if (fill === undefined)
|
|
1123
|
+
continue;
|
|
1124
|
+
row[column.field] = nextFill(fill, generators);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
function nextFill(fill, generators) {
|
|
1129
|
+
if (fill.name === "uuidv4" && generators?.uuidv4 !== undefined)
|
|
1130
|
+
return generators.uuidv4();
|
|
1131
|
+
if (fill.name === "uuidv7" && generators?.uuidv7 !== undefined)
|
|
1132
|
+
return generators.uuidv7();
|
|
1133
|
+
if (fill.name === "okid" && generators?.okid !== undefined)
|
|
1134
|
+
return generators.okid();
|
|
1135
|
+
return fill.call();
|
|
1136
|
+
}
|
|
1137
|
+
function writtenColumns(table, rows, allow, tenantKeys) {
|
|
1138
|
+
const present = new Set;
|
|
1139
|
+
for (const row of rows) {
|
|
1140
|
+
for (const key of Object.keys(row))
|
|
1141
|
+
present.add(key);
|
|
1142
|
+
}
|
|
1143
|
+
const columns = [];
|
|
1144
|
+
for (const column of table.model.columns) {
|
|
1145
|
+
if (present.has(column.field) && (writable(table, column, allow) || column.fill !== undefined || tenantKeys?.includes(column.field) === true)) {
|
|
1146
|
+
columns.push(column);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return columns;
|
|
1150
|
+
}
|
|
1151
|
+
function tenantKeyList(tenancy) {
|
|
1152
|
+
if (tenancy === undefined)
|
|
1153
|
+
return;
|
|
1154
|
+
if (tenancy.keys !== undefined && tenancy.keys.length > 0)
|
|
1155
|
+
return tenancy.keys;
|
|
1156
|
+
return [tenancy.key];
|
|
1157
|
+
}
|
|
1158
|
+
function pathInsert(table, columns, rows, scope, tenancy) {
|
|
1159
|
+
const sql = new Sql2;
|
|
1160
|
+
const names = columns.map((column) => quote(column.sql)).join(", ");
|
|
1161
|
+
const selected = columns.map((column) => `v.${quote(column.sql)}`).join(", ");
|
|
1162
|
+
sql.text(`insert into ${quote(table.model.sql)} (${names}) select ${selected} from (values `);
|
|
1163
|
+
for (let row = 0;row < rows.length; row += 1) {
|
|
1164
|
+
const cells = rows[row];
|
|
1165
|
+
if (cells === undefined)
|
|
1166
|
+
continue;
|
|
1167
|
+
if (row > 0)
|
|
1168
|
+
sql.text(", ");
|
|
1169
|
+
sql.text("(");
|
|
1170
|
+
for (let index = 0;index < cells.length; index += 1) {
|
|
1171
|
+
const cell = cells[index];
|
|
1172
|
+
if (index > 0)
|
|
1173
|
+
sql.text(", ");
|
|
1174
|
+
if (cell === undefined || cell.kind === "default") {
|
|
1175
|
+
sql.text("default");
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
sql.param(cell.kind === "null" ? null : cell.wire, cell.dataType);
|
|
1179
|
+
}
|
|
1180
|
+
sql.text(")");
|
|
1181
|
+
}
|
|
1182
|
+
sql.text(`) as v(${names})`);
|
|
1183
|
+
tenancy?.lockPredicate?.({
|
|
1184
|
+
table: table.model.name,
|
|
1185
|
+
fieldSql: (field) => table.columns.get(field)?.sql,
|
|
1186
|
+
encode: table.columns.get(tenancy.key)?.encode,
|
|
1187
|
+
alias: "v",
|
|
1188
|
+
appended: false,
|
|
1189
|
+
scope,
|
|
1190
|
+
sink: sql.sink()
|
|
1191
|
+
});
|
|
1192
|
+
return sql.statement();
|
|
1193
|
+
}
|
|
1194
|
+
function writeSet2(table, value, allow, tenancy) {
|
|
1195
|
+
if (!isRecord(value))
|
|
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
|
+
}
|
|
1201
|
+
const set = {};
|
|
1202
|
+
for (const key of Object.keys(value)) {
|
|
1203
|
+
if (value[key] === undefined)
|
|
1204
|
+
continue;
|
|
1205
|
+
const column = table.columns.get(key);
|
|
1206
|
+
if (column === undefined)
|
|
1207
|
+
continue;
|
|
1208
|
+
tenancy?.guard(table.model.name, key, "update");
|
|
1209
|
+
if (column.guardUpdate || !writable(table, column, allow))
|
|
1210
|
+
refuse(table, column);
|
|
1211
|
+
set[key] = value[key];
|
|
1212
|
+
}
|
|
1213
|
+
refuseHalfMorph(table, set);
|
|
1214
|
+
return set;
|
|
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
|
+
}
|
|
1265
|
+
function updateItem(table, value, allow, tenancy) {
|
|
1266
|
+
if (!isRecord(value))
|
|
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.");
|
|
1272
|
+
if (value.where !== undefined)
|
|
1273
|
+
return { where: value.where, set };
|
|
1274
|
+
if (value.id !== undefined)
|
|
1275
|
+
return { where: { id: value.id }, set };
|
|
1276
|
+
fail2("OKM1102", `update on ${table.model.name} needs id or where for each row.`);
|
|
1277
|
+
}
|
|
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) {
|
|
1323
|
+
const accepted = row ? UPDATE_ROW_KEYS : UPDATE_KEYS;
|
|
1324
|
+
for (const key of Object.keys(input)) {
|
|
1325
|
+
if (accepted.includes(key))
|
|
1326
|
+
continue;
|
|
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.`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function requireFilter2(table, where, mods, op) {
|
|
1331
|
+
if (mods.all !== undefined) {
|
|
1332
|
+
if (mods.all.trim().length === 0) {
|
|
1333
|
+
fail2("OKM1102", `${op} on ${table.model.name} needs a where. .all needs a reason.`);
|
|
1334
|
+
}
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (!effectivePredicate(where)) {
|
|
1338
|
+
fail2("OKM1102", `${op} on ${table.model.name} needs a where. Pass a filter, or .all(reason).`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function chunkRows(rows) {
|
|
1342
|
+
const chunks = [];
|
|
1343
|
+
let current = [];
|
|
1344
|
+
let count = 0;
|
|
1345
|
+
for (const row of rows) {
|
|
1346
|
+
let cost = 0;
|
|
1347
|
+
for (const cell of row)
|
|
1348
|
+
if (cell.kind !== "default")
|
|
1349
|
+
cost += 1;
|
|
1350
|
+
if (current.length > 0 && count + cost > WRITE_PARAM_BUDGET2) {
|
|
1351
|
+
chunks.push(current);
|
|
1352
|
+
current = [];
|
|
1353
|
+
count = 0;
|
|
1354
|
+
}
|
|
1355
|
+
current.push(row);
|
|
1356
|
+
count += cost;
|
|
1357
|
+
}
|
|
1358
|
+
if (current.length > 0)
|
|
1359
|
+
chunks.push(current);
|
|
1360
|
+
return chunks;
|
|
1361
|
+
}
|
|
1362
|
+
function withParams(text, params) {
|
|
1363
|
+
if (params === undefined || params.length === 0)
|
|
1364
|
+
return { text };
|
|
1365
|
+
return { text, params };
|
|
1366
|
+
}
|
|
1367
|
+
function keyWire(columns, row) {
|
|
1368
|
+
return columns.map((column) => {
|
|
1369
|
+
const value = row[column.field];
|
|
1370
|
+
if (value === undefined || value === null)
|
|
1371
|
+
return null;
|
|
1372
|
+
return encodeColumn(column, value);
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
function returningOption2(table, value) {
|
|
1376
|
+
if (value === undefined)
|
|
1377
|
+
return;
|
|
1378
|
+
if (value === true)
|
|
1379
|
+
return selectedColumns(table, undefined);
|
|
1380
|
+
return selectedColumns(table, value);
|
|
1381
|
+
}
|
|
1382
|
+
function returningClause(columns, alias) {
|
|
1383
|
+
if (columns === undefined || columns.length === 0)
|
|
1384
|
+
return "";
|
|
1385
|
+
const parts = columns.map((column) => {
|
|
1386
|
+
const sql = alias === undefined ? quote(column.sql) : `${alias}.${quote(column.sql)}`;
|
|
1387
|
+
return projectExpr(sql, column.dataType);
|
|
1388
|
+
});
|
|
1389
|
+
return ` returning ${parts.join(", ")}`;
|
|
1390
|
+
}
|
|
1391
|
+
function outputsOf(columns) {
|
|
1392
|
+
const list = columns ?? [];
|
|
1393
|
+
return {
|
|
1394
|
+
fields: list.map((column, index) => ({
|
|
1395
|
+
key: column.field,
|
|
1396
|
+
at: index,
|
|
1397
|
+
decode: column.decode
|
|
1398
|
+
})),
|
|
1399
|
+
includes: []
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function expectOf2(options, mods) {
|
|
1403
|
+
const value = mods.expect ?? options.expect;
|
|
1404
|
+
if (value === undefined)
|
|
1405
|
+
return;
|
|
1406
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
1407
|
+
throw new OkmError2("invalid", "expect must be an integer from 0 up.", { kind: "invalid" });
|
|
1408
|
+
}
|
|
1409
|
+
return value;
|
|
1410
|
+
}
|
|
1411
|
+
function checkExpect(count, expected, table, op) {
|
|
1412
|
+
if (expected === undefined || count === expected)
|
|
1413
|
+
return;
|
|
1414
|
+
throw new OkmError2("not_found", `${op} on ${table} changed ${String(count)} rows. expect was ${String(expected)}.`, {
|
|
1415
|
+
kind: "not_found",
|
|
1416
|
+
table,
|
|
1417
|
+
fix: { summary: "Change the filter, or pass the count this write produced." }
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
function callOptions(options) {
|
|
1421
|
+
if (!isRecord(options))
|
|
1422
|
+
return;
|
|
1423
|
+
const signal = options.signal;
|
|
1424
|
+
const timeout = options.timeout;
|
|
1425
|
+
const hasSignal = typeof signal === "object" && signal !== null && "aborted" in signal;
|
|
1426
|
+
const hasTimeout = typeof timeout === "number";
|
|
1427
|
+
if (!hasSignal && !hasTimeout)
|
|
1428
|
+
return;
|
|
1429
|
+
return {
|
|
1430
|
+
...hasSignal ? { signal } : {},
|
|
1431
|
+
...hasTimeout ? { timeout } : {}
|
|
1432
|
+
};
|
|
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
|
+
}
|
|
1466
|
+
function refuse(table, column) {
|
|
1467
|
+
if (fieldSealed(table.model, column.field)) {
|
|
1468
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is set by a trait. Input cannot set it.`);
|
|
1469
|
+
}
|
|
1470
|
+
if (column.guardUpdate) {
|
|
1471
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is a primary key. Input cannot change it.`);
|
|
1472
|
+
}
|
|
1473
|
+
if (column.guarded) {
|
|
1474
|
+
fail2("OKM1190", `Field ${table.model.name}.${column.field} is guarded. Input cannot set it.`);
|
|
1475
|
+
}
|
|
1476
|
+
fail2("OKM1120", `Field ${table.model.name}.${column.field} cannot be written.`);
|
|
1477
|
+
}
|
|
1478
|
+
function writable(table, column, allow) {
|
|
1479
|
+
if (fieldSealed(table.model, column.field))
|
|
1480
|
+
return false;
|
|
1481
|
+
if (column.writable)
|
|
1482
|
+
return true;
|
|
1483
|
+
return column.guarded && allow.has(column.field);
|
|
1484
|
+
}
|
|
1485
|
+
function allowSet2(table, value) {
|
|
1486
|
+
if (value === undefined)
|
|
1487
|
+
return NO_ALLOW;
|
|
1488
|
+
if (!Array.isArray(value))
|
|
1489
|
+
fail2("OKM1120", "allow must be a list of field names.");
|
|
1490
|
+
const set = new Set;
|
|
1491
|
+
for (const name of value) {
|
|
1492
|
+
if (typeof name !== "string" || !table.columns.has(name)) {
|
|
1493
|
+
fail2("OKM1120", "allow must name fields on the table.");
|
|
1494
|
+
}
|
|
1495
|
+
set.add(name);
|
|
1496
|
+
}
|
|
1497
|
+
return set;
|
|
1498
|
+
}
|
|
1499
|
+
function noteArchive(table, view) {
|
|
1500
|
+
if (!safetyInstalled())
|
|
1501
|
+
return;
|
|
1502
|
+
const rules = archiveRules(table.model, view);
|
|
1503
|
+
if (rules === undefined)
|
|
1504
|
+
return;
|
|
1505
|
+
runSafety(rules, undefined);
|
|
1506
|
+
}
|
|
1507
|
+
function noteGuarded2(table, allow, op) {
|
|
1508
|
+
if (!safetyInstalled())
|
|
1509
|
+
return;
|
|
1510
|
+
const contributions = [];
|
|
1511
|
+
const hatches = [];
|
|
1512
|
+
const touch = touchFields(table.model);
|
|
1513
|
+
const source = table.model.source;
|
|
1514
|
+
for (const column of table.model.columns) {
|
|
1515
|
+
const traitName = sealingTrait(table.model, column.field);
|
|
1516
|
+
if (traitName !== undefined) {
|
|
1517
|
+
const sets = op === "insert" || touch?.includes(column.field) === true;
|
|
1518
|
+
contributions.push({
|
|
1519
|
+
rule: traitName,
|
|
1520
|
+
contribution: sets ? `${traitName} ${table.model.name}.${column.field} set by ${traitName}` : `${traitName} ${table.model.name}.${column.field} kept`,
|
|
1521
|
+
provenance: "planner",
|
|
1522
|
+
...source !== undefined ? { source } : {}
|
|
1523
|
+
});
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
if (!column.guarded)
|
|
1527
|
+
continue;
|
|
1528
|
+
const name = `${table.model.name}.${column.field}`;
|
|
1529
|
+
if (allow.has(column.field)) {
|
|
1530
|
+
contributions.push({
|
|
1531
|
+
rule: "guarded",
|
|
1532
|
+
contribution: `guarded ${name} allowed`,
|
|
1533
|
+
provenance: "allow"
|
|
1534
|
+
});
|
|
1535
|
+
hatches.push({ name: "allow", reason: name });
|
|
1536
|
+
} else {
|
|
1537
|
+
contributions.push({
|
|
1538
|
+
rule: "guarded",
|
|
1539
|
+
contribution: `guarded ${name} absent`,
|
|
1540
|
+
provenance: "planner"
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (contributions.length === 0)
|
|
1545
|
+
return;
|
|
1546
|
+
runSafety(contributions, hatches.length === 0 ? undefined : hatches);
|
|
1547
|
+
}
|
|
1548
|
+
function scrubWrite(schema, tableName, input, error) {
|
|
1549
|
+
if (!(error instanceof OkmError2))
|
|
1550
|
+
return error;
|
|
1551
|
+
const table = indexes(schema).get(tableName);
|
|
1552
|
+
if (table === undefined || table.model.conceal !== true)
|
|
1553
|
+
return error;
|
|
1554
|
+
const secrets = [];
|
|
1555
|
+
const names = new Set;
|
|
1556
|
+
for (const column of table.model.columns) {
|
|
1557
|
+
if (column.hidden || column.sensitive)
|
|
1558
|
+
names.add(column.field);
|
|
1559
|
+
}
|
|
1560
|
+
collectSecrets(input, names, secrets);
|
|
1561
|
+
if (secrets.length === 0)
|
|
1562
|
+
return error;
|
|
1563
|
+
let message = error.message;
|
|
1564
|
+
for (const secret of secrets) {
|
|
1565
|
+
if (secret.length < 4 || !message.includes(secret))
|
|
1566
|
+
continue;
|
|
1567
|
+
message = message.replaceAll(secret, REDACTED);
|
|
1568
|
+
}
|
|
1569
|
+
const issues = error.issues === undefined ? undefined : redactIssues(error.issues, secrets);
|
|
1570
|
+
if (message === error.message && issues === undefined)
|
|
1571
|
+
return error;
|
|
1572
|
+
return new OkmError2(error.code, message, {
|
|
1573
|
+
kind: error.kind,
|
|
1574
|
+
...error.table !== undefined ? { table: error.table } : {},
|
|
1575
|
+
...error.columns.length > 0 ? { columns: error.columns } : {},
|
|
1576
|
+
...error.issues !== undefined ? { issues: issues ?? error.issues } : {},
|
|
1577
|
+
fix: error.fix
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
function redactIssues(issues, secrets) {
|
|
1581
|
+
let changed = false;
|
|
1582
|
+
const next = issues.map((issue) => {
|
|
1583
|
+
let message = issue.message;
|
|
1584
|
+
for (const secret of secrets) {
|
|
1585
|
+
if (secret.length < 4 || !message.includes(secret))
|
|
1586
|
+
continue;
|
|
1587
|
+
message = message.replaceAll(secret, REDACTED);
|
|
1588
|
+
changed = true;
|
|
1589
|
+
}
|
|
1590
|
+
return message === issue.message ? issue : { path: issue.path, message };
|
|
1591
|
+
});
|
|
1592
|
+
return changed ? next : undefined;
|
|
1593
|
+
}
|
|
1594
|
+
function collectSecrets(value, names, found, key) {
|
|
1595
|
+
if (typeof value === "string") {
|
|
1596
|
+
if (key !== undefined && names.has(key))
|
|
1597
|
+
found.push(value);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (Array.isArray(value)) {
|
|
1601
|
+
for (const item of value)
|
|
1602
|
+
collectSecrets(item, names, found);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (!isRecord(value))
|
|
1606
|
+
return;
|
|
1607
|
+
for (const child of Object.keys(value))
|
|
1608
|
+
collectSecrets(value[child], names, found, child);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
export { touchFields, fieldSealed, WRITE_PARAM_BUDGET2, executeWrite2, explainWrite2, updateSliceBody2, rejectDuplicateTargets2, emitSet2, Sql2, writeSet2, rejectMixedSets2, readClaim2, rejectUpdateKeys2, requireFilter2, returningOption2, expectOf2, allowSet2, noteGuarded2 };
|