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,21 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./
|
|
2
|
+
DriverError2
|
|
3
|
+
} from "./cm3w0ywp.js";
|
|
4
|
+
import {
|
|
5
|
+
noTimeout
|
|
6
|
+
} from "./79dt02vc.js";
|
|
4
7
|
import {
|
|
5
8
|
noticeBuffer,
|
|
6
|
-
WireSession
|
|
7
|
-
|
|
8
|
-
} from "./4az561fn.js";
|
|
9
|
+
WireSession
|
|
10
|
+
} from "./1qa0qajz.js";
|
|
9
11
|
import {
|
|
10
12
|
BUNSQL_CAPABILITIES,
|
|
11
13
|
driverErrors,
|
|
12
14
|
rejectClosed,
|
|
13
15
|
mapFailure,
|
|
14
16
|
runCall
|
|
15
|
-
} from "./
|
|
17
|
+
} from "./kb2sdhfb.js";
|
|
16
18
|
|
|
17
19
|
// src/adapters/pg/bunsql.ts
|
|
18
20
|
import { SQL } from "bun";
|
|
21
|
+
var clientPools = new WeakSet;
|
|
22
|
+
function markClientPool(pool) {
|
|
23
|
+
clientPools.add(pool);
|
|
24
|
+
}
|
|
19
25
|
function open5(config) {
|
|
20
26
|
const max = config.max ?? 10;
|
|
21
27
|
const acquireMs = config.timeouts?.acquire;
|
|
@@ -25,6 +31,8 @@ function open5(config) {
|
|
|
25
31
|
const isClosed = () => closed;
|
|
26
32
|
const named = config.prepared === "named";
|
|
27
33
|
const parked = [];
|
|
34
|
+
let dropped = 0;
|
|
35
|
+
let closures = 0;
|
|
28
36
|
const sql = new SQL(config.url, {
|
|
29
37
|
max,
|
|
30
38
|
idleTimeout: 0,
|
|
@@ -36,26 +44,53 @@ function open5(config) {
|
|
|
36
44
|
application_name: "okmodel",
|
|
37
45
|
...config.searchPath !== undefined ? { search_path: config.searchPath } : {}
|
|
38
46
|
},
|
|
39
|
-
...config.ssl !== undefined ? { tls: config.ssl } : {}
|
|
47
|
+
...config.ssl !== undefined ? { tls: config.ssl } : {},
|
|
48
|
+
onclose() {
|
|
49
|
+
dropped += 1;
|
|
50
|
+
closures += 1;
|
|
51
|
+
}
|
|
40
52
|
});
|
|
41
53
|
const root = new WireSession((text, params) => send2(sql, text, params), notices, isClosed, false, () => {
|
|
42
54
|
counters.busy += 1;
|
|
43
55
|
}, () => {
|
|
44
56
|
counters.busy = Math.max(0, counters.busy - 1);
|
|
45
57
|
});
|
|
46
|
-
const held = {
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
const held = {
|
|
59
|
+
sql,
|
|
60
|
+
parked,
|
|
61
|
+
acquireMs,
|
|
62
|
+
counters,
|
|
63
|
+
notices,
|
|
64
|
+
isClosed,
|
|
65
|
+
closures: () => closures
|
|
66
|
+
};
|
|
67
|
+
const run = (text, params, watch) => {
|
|
68
|
+
const sent = dropped;
|
|
69
|
+
return root.query(text, params, watch).catch((error) => import("./ab9gmbwy.js").then((mod) => mod.recover(sql, error, text, root.depth(), clientPools.has(pool), dropped > sent, () => root.query(text, params, watch))));
|
|
70
|
+
};
|
|
71
|
+
const afterDrop = (text, params, watch) => {
|
|
72
|
+
if (dropped === 0)
|
|
73
|
+
return run(text, params, watch);
|
|
74
|
+
const seen = dropped;
|
|
75
|
+
return import("./ab9gmbwy.js").then((mod) => mod.drainClosed(sql).then(() => {
|
|
76
|
+
if (dropped === seen)
|
|
77
|
+
dropped = 0;
|
|
78
|
+
return run(text, params, watch);
|
|
79
|
+
}));
|
|
80
|
+
};
|
|
81
|
+
const checkout = () => closed ? rejectClosed() : import("./ab9gmbwy.js").then((mod) => mod.checkout(held));
|
|
82
|
+
const pool = {
|
|
49
83
|
capabilities: BUNSQL_CAPABILITIES,
|
|
50
84
|
execute(text, params, options) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
85
|
+
const refused = noTimeout(options);
|
|
86
|
+
if (refused !== undefined)
|
|
87
|
+
return refused;
|
|
88
|
+
return runCall(closed, options, (watch) => releaseParked(parked).then(() => afterDrop(text, params, watch)));
|
|
56
89
|
},
|
|
57
90
|
async batch(statements, options) {
|
|
58
|
-
|
|
91
|
+
const refused = noTimeout(options);
|
|
92
|
+
if (refused !== undefined)
|
|
93
|
+
return refused;
|
|
59
94
|
const connection = await checkout();
|
|
60
95
|
try {
|
|
61
96
|
return await connection.batch(statements, options);
|
|
@@ -65,10 +100,13 @@ function open5(config) {
|
|
|
65
100
|
},
|
|
66
101
|
reserve: () => checkout(),
|
|
67
102
|
stream(text, params) {
|
|
68
|
-
return
|
|
103
|
+
return async function* () {
|
|
104
|
+
const { streamRows } = await import("./ab9gmbwy.js");
|
|
105
|
+
yield* streamRows(checkout, text, params);
|
|
106
|
+
}();
|
|
69
107
|
},
|
|
70
108
|
listen(channel, onNotify) {
|
|
71
|
-
return listen(channel, onNotify);
|
|
109
|
+
return import("./ab9gmbwy.js").then((mod) => mod.listen(sql, parked, closed, channel, onNotify));
|
|
72
110
|
},
|
|
73
111
|
stats: () => statsOf(counters, max),
|
|
74
112
|
async close() {
|
|
@@ -77,31 +115,29 @@ function open5(config) {
|
|
|
77
115
|
await sql.close();
|
|
78
116
|
}
|
|
79
117
|
};
|
|
80
|
-
|
|
81
|
-
return async function* () {
|
|
82
|
-
const connection = await checkout();
|
|
83
|
-
try {
|
|
84
|
-
yield* cursorStream((statement, values) => connection.execute(statement, values), text, params);
|
|
85
|
-
} finally {
|
|
86
|
-
await connection.release();
|
|
87
|
-
}
|
|
88
|
-
}();
|
|
89
|
-
}
|
|
90
|
-
async function listen(channel, onNotify) {
|
|
91
|
-
if (closed)
|
|
92
|
-
return rejectClosed();
|
|
93
|
-
await releaseParked(parked);
|
|
94
|
-
const subscription = await sql.listen(channel, onNotify);
|
|
95
|
-
return async () => {
|
|
96
|
-
await subscription.unlisten();
|
|
97
|
-
};
|
|
98
|
-
}
|
|
118
|
+
return pool;
|
|
99
119
|
}
|
|
100
120
|
function send2(sql, text, params) {
|
|
121
|
+
const refused = refuseInt8(params);
|
|
122
|
+
if (refused !== undefined)
|
|
123
|
+
return refused;
|
|
101
124
|
if (params === undefined || params.length === 0)
|
|
102
125
|
return sql.unsafe(text).raw();
|
|
103
126
|
return sql.unsafe(text, [...params]).raw();
|
|
104
127
|
}
|
|
128
|
+
function refuseInt8(params) {
|
|
129
|
+
if (params === undefined)
|
|
130
|
+
return;
|
|
131
|
+
for (const value of params) {
|
|
132
|
+
if (value === null || value.length < 19 || !/^-?(?:0|[1-9]\d*)$/.test(value))
|
|
133
|
+
continue;
|
|
134
|
+
const parsed = BigInt(value);
|
|
135
|
+
if (parsed > 9223372036854775807n || parsed < -9223372036854775808n) {
|
|
136
|
+
return Promise.reject(new DriverError2("bigint out of range", { sqlstate: "22003" }));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
105
141
|
async function reserve(sql, acquireMs, counters) {
|
|
106
142
|
counters.waiting += 1;
|
|
107
143
|
try {
|
|
@@ -151,4 +187,4 @@ function statsOf(counters, size) {
|
|
|
151
187
|
};
|
|
152
188
|
}
|
|
153
189
|
|
|
154
|
-
export { open5, send2, reserve, park };
|
|
190
|
+
export { markClientPool, open5, send2, reserve, park };
|
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
assertBoundText,
|
|
6
6
|
assertRegconfig,
|
|
7
7
|
textArray
|
|
8
|
-
} from "./
|
|
8
|
+
} from "./zchaghkf.js";
|
|
9
9
|
import {
|
|
10
10
|
installOperatorSql,
|
|
11
11
|
quote,
|
|
12
12
|
isRecord,
|
|
13
|
-
|
|
14
|
-
} from "./
|
|
13
|
+
fail2
|
|
14
|
+
} from "./67txcrcw.js";
|
|
15
15
|
import {
|
|
16
16
|
isOperator,
|
|
17
17
|
operatorName,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./cn0n68ft.js";
|
|
20
20
|
|
|
21
21
|
// src/runtime/operator-sql.ts
|
|
22
|
-
function emitStructured(column, ref, name, value, sink) {
|
|
22
|
+
function emitStructured(column, ref, name, value, sink, order) {
|
|
23
23
|
if (name === "contains" || name === "containedBy" || name === "overlaps") {
|
|
24
24
|
emitContainment(column, ref, name, value, sink);
|
|
25
25
|
return;
|
|
@@ -36,12 +36,20 @@ function emitStructured(column, ref, name, value, sink) {
|
|
|
36
36
|
emitMatches(column, ref, value, sink);
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
+
if (name === "relevance") {
|
|
40
|
+
if (order !== true)
|
|
41
|
+
fail2("OKM1121", `relevance on ${column.field} belongs in orderBy.`);
|
|
42
|
+
emitRelevance(column, ref, value, sink);
|
|
43
|
+
sink.text(" desc");
|
|
44
|
+
sink.mark(`o:${column.field}:relevance`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
39
47
|
if (name === "similar" || name === "wordSimilar") {
|
|
40
48
|
emitTrigram(column, ref, name, value, sink);
|
|
41
49
|
return;
|
|
42
50
|
}
|
|
43
51
|
assertOperatorFits(column, name);
|
|
44
|
-
|
|
52
|
+
fail2("OKM1121", `${name} is not a value operator on ${column.field}. Use has, none, or every on a relation.`);
|
|
45
53
|
}
|
|
46
54
|
function emitContainment(column, ref, name, value, sink) {
|
|
47
55
|
assertOperatorFits(column, name);
|
|
@@ -56,7 +64,7 @@ function emitJsonKey(column, ref, name, value, sink) {
|
|
|
56
64
|
assertOperatorFits(column, name);
|
|
57
65
|
if (name === "hasKey") {
|
|
58
66
|
if (typeof value !== "string")
|
|
59
|
-
|
|
67
|
+
fail2("OKM1121", `hasKey on ${column.field} needs a key.`);
|
|
60
68
|
assertBoundText(value, `key on ${column.field}`);
|
|
61
69
|
sink.text("jsonb_exists(");
|
|
62
70
|
sink.text(ref);
|
|
@@ -66,7 +74,7 @@ function emitJsonKey(column, ref, name, value, sink) {
|
|
|
66
74
|
return;
|
|
67
75
|
}
|
|
68
76
|
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
69
|
-
|
|
77
|
+
fail2("OKM1121", `hasAnyKey on ${column.field} needs a list of keys.`);
|
|
70
78
|
}
|
|
71
79
|
sink.text("jsonb_exists_any(");
|
|
72
80
|
sink.text(ref);
|
|
@@ -77,15 +85,15 @@ function emitJsonKey(column, ref, name, value, sink) {
|
|
|
77
85
|
function emitPath(column, ref, value, sink) {
|
|
78
86
|
assertOperatorFits(column, "path");
|
|
79
87
|
if (!isRecord(value) || !Array.isArray(value.segments) || !isOperator(value.op)) {
|
|
80
|
-
|
|
88
|
+
fail2("OKM1121", `path on ${column.field} needs segments and a comparison.`);
|
|
81
89
|
}
|
|
82
90
|
const segments = value.segments;
|
|
83
91
|
if (segments.length === 0 || segments.some((item) => typeof item !== "string")) {
|
|
84
|
-
|
|
92
|
+
fail2("OKM1121", `path on ${column.field} needs a list of segments.`);
|
|
85
93
|
}
|
|
86
94
|
const opName = operatorName(value.op);
|
|
87
95
|
if (opName !== "eq" && opName !== "lt" && opName !== "lte" && opName !== "gt" && opName !== "gte") {
|
|
88
|
-
|
|
96
|
+
fail2("OKM1121", `path on ${column.field} compares with eq, lt, lte, gt, or gte.`);
|
|
89
97
|
}
|
|
90
98
|
const operand = operatorValue(value.op);
|
|
91
99
|
const cast = pathCast(column, operand);
|
|
@@ -106,7 +114,7 @@ function pathCast(column, operand) {
|
|
|
106
114
|
return "boolean";
|
|
107
115
|
if (typeof operand === "string")
|
|
108
116
|
return "text";
|
|
109
|
-
|
|
117
|
+
fail2("OKM1121", `path on ${column.field} compares a string, number, or boolean.`);
|
|
110
118
|
}
|
|
111
119
|
function pathParam(operand) {
|
|
112
120
|
if (typeof operand === "number")
|
|
@@ -117,35 +125,61 @@ function pathParam(operand) {
|
|
|
117
125
|
return operand;
|
|
118
126
|
return "";
|
|
119
127
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
var MATCH_FN = {
|
|
129
|
+
natural: "websearch_to_tsquery",
|
|
130
|
+
"all-words": "plainto_tsquery",
|
|
131
|
+
phrase: "phraseto_tsquery",
|
|
132
|
+
advanced: "to_tsquery"
|
|
133
|
+
};
|
|
134
|
+
function readMatch(column, value, label) {
|
|
135
|
+
if (!isRecord(value) || typeof value.query !== "string" || value.query.length === 0) {
|
|
136
|
+
fail2("OKM1121", `${label} on ${column.field} needs text to look for.`);
|
|
124
137
|
}
|
|
125
|
-
assertNoControl(value.query,
|
|
126
|
-
const
|
|
127
|
-
const fn =
|
|
138
|
+
assertNoControl(value.query, `${label} on ${column.field}`);
|
|
139
|
+
const match = value.match ?? "natural";
|
|
140
|
+
const fn = typeof match === "string" ? MATCH_FN[match] : undefined;
|
|
128
141
|
if (fn === undefined) {
|
|
129
|
-
|
|
142
|
+
fail2("OKM1121", `${label} on ${column.field} match must be natural, all-words, phrase, or advanced.`);
|
|
130
143
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
if (value.language === undefined)
|
|
145
|
+
return { fn, language: undefined, query: value.query };
|
|
146
|
+
if (typeof value.language !== "string") {
|
|
147
|
+
fail2("OKM1121", `${label} on ${column.field} language must be a name such as english.`);
|
|
148
|
+
}
|
|
149
|
+
assertRegconfig(value.language);
|
|
150
|
+
return { fn, language: value.language, query: value.query };
|
|
151
|
+
}
|
|
152
|
+
function emitTsquery(sink, query) {
|
|
153
|
+
sink.mark(query.language === undefined ? `:${query.fn}` : `:${query.fn}:lang`);
|
|
154
|
+
sink.text(query.fn);
|
|
134
155
|
sink.text("(");
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
sink.param(value.config);
|
|
156
|
+
if (query.language !== undefined) {
|
|
157
|
+
sink.param(query.language);
|
|
138
158
|
sink.text("::regconfig, ");
|
|
139
159
|
}
|
|
140
|
-
sink.param(
|
|
160
|
+
sink.param(query.query);
|
|
161
|
+
sink.text(")");
|
|
162
|
+
}
|
|
163
|
+
function emitMatches(column, ref, value, sink) {
|
|
164
|
+
assertOperatorFits(column, "matches");
|
|
165
|
+
sink.text(ref);
|
|
166
|
+
sink.text(" @@ ");
|
|
167
|
+
emitTsquery(sink, readMatch(column, value, "matches"));
|
|
168
|
+
}
|
|
169
|
+
function emitRelevance(column, ref, value, sink) {
|
|
170
|
+
assertOperatorFits(column, "relevance");
|
|
171
|
+
sink.text("ts_rank(");
|
|
172
|
+
sink.text(ref);
|
|
173
|
+
sink.text(", ");
|
|
174
|
+
emitTsquery(sink, readMatch(column, value, "relevance"));
|
|
141
175
|
sink.text(")");
|
|
142
176
|
}
|
|
143
177
|
function emitTrigram(column, ref, name, value, sink) {
|
|
144
178
|
if (!isRecord(value) || typeof value.query !== "string" || typeof value.schema !== "string") {
|
|
145
|
-
|
|
179
|
+
fail2("OKM1121", `${name} on ${column.field} needs query text.`);
|
|
146
180
|
}
|
|
147
181
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value.schema)) {
|
|
148
|
-
|
|
182
|
+
fail2("OKM1121", `${name} on ${column.field} needs an extension schema name.`);
|
|
149
183
|
}
|
|
150
184
|
sink.text(ref);
|
|
151
185
|
sink.text(" operator(");
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readHandle,
|
|
3
|
+
readCall,
|
|
4
|
+
attachHttp
|
|
5
|
+
} from "./tbrvgw6z.js";
|
|
6
|
+
import {
|
|
7
|
+
decodeResult,
|
|
8
|
+
emitWhere,
|
|
9
|
+
emitOrder,
|
|
10
|
+
selectedColumns,
|
|
11
|
+
rejectKeys,
|
|
12
|
+
projectExpr,
|
|
13
|
+
indexes,
|
|
14
|
+
quote,
|
|
15
|
+
list,
|
|
16
|
+
isRecord,
|
|
17
|
+
fail2
|
|
18
|
+
} from "./67txcrcw.js";
|
|
19
|
+
import {
|
|
20
|
+
OkmError2,
|
|
21
|
+
throwNamed2
|
|
22
|
+
} from "./52szzakm.js";
|
|
23
|
+
|
|
24
|
+
// src/runtime/tree.ts
|
|
25
|
+
var OPTIONS = [
|
|
26
|
+
"where",
|
|
27
|
+
"select",
|
|
28
|
+
"orderBy",
|
|
29
|
+
"depth",
|
|
30
|
+
"self",
|
|
31
|
+
"relation",
|
|
32
|
+
"route",
|
|
33
|
+
"signal",
|
|
34
|
+
"timeout"
|
|
35
|
+
];
|
|
36
|
+
function build2(session, table, options, mods, view) {
|
|
37
|
+
try {
|
|
38
|
+
return open(session, table, options, mods, view);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw error instanceof OkmError2 ? attachHttp(session.http, error) : error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function open(session, table, options, mods, view) {
|
|
44
|
+
const packed = readPacked(options);
|
|
45
|
+
const user = packed.user;
|
|
46
|
+
rejectKeys(user, OPTIONS, packed.walk);
|
|
47
|
+
const indexed = indexes(session.schema).get(table);
|
|
48
|
+
if (indexed === undefined)
|
|
49
|
+
fail2("OKM1120", `Table ${table} is not in the schema.`);
|
|
50
|
+
const edge = pickEdge(indexed, user.relation);
|
|
51
|
+
const identity = identityColumn2(session, indexed);
|
|
52
|
+
const depth = readDepth(user.depth);
|
|
53
|
+
const self = readSelf(user.self);
|
|
54
|
+
const id = userId(identity, packed.id);
|
|
55
|
+
const shared = {
|
|
56
|
+
...user.where !== undefined ? { where: user.where } : {},
|
|
57
|
+
...user.select !== undefined ? { select: user.select } : {},
|
|
58
|
+
...user.orderBy !== undefined ? { orderBy: user.orderBy } : {},
|
|
59
|
+
...user.signal !== undefined ? { signal: user.signal } : {},
|
|
60
|
+
...user.timeout !== undefined ? { timeout: user.timeout } : {},
|
|
61
|
+
...user.route !== undefined ? { route: user.route } : {}
|
|
62
|
+
};
|
|
63
|
+
const base = readCall("find", table, shared, mods.all, session.scope, session.schema.model[table], session.schema, view);
|
|
64
|
+
const spec = { direction: packed.walk, edge, identity, depth, self, id };
|
|
65
|
+
const call = {
|
|
66
|
+
...base,
|
|
67
|
+
op: packed.walk,
|
|
68
|
+
build: (schema, planned, sink, outputs, source) => {
|
|
69
|
+
emitTree(schema, planned, sink, outputs, source, spec);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return readHandle(session, table, call, mods, (compiled, rows) => decodeResult(compiled, rows, table));
|
|
73
|
+
}
|
|
74
|
+
function emitTree(schema, table, sink, outputs, call, spec) {
|
|
75
|
+
const { direction, edge, identity, depth, self, id } = spec;
|
|
76
|
+
sink.mark(`walk:${direction}|rel:${edge.name}|self:${self ? "1" : "0"}|depth:${depth === undefined ? "all" : String(depth)}|`);
|
|
77
|
+
const selected = selectedColumns(table, call.select);
|
|
78
|
+
sink.text("with recursive walk as (select ");
|
|
79
|
+
emitKeyList(sink, "s", edge.parent, "k");
|
|
80
|
+
emitKeyList(sink, "s", edge.child, "f");
|
|
81
|
+
sink.text(", 0 as depth, array[s.");
|
|
82
|
+
sink.text(quote(identity.sql));
|
|
83
|
+
sink.text("] as seen from ");
|
|
84
|
+
sink.text(quote(table.model.sql));
|
|
85
|
+
sink.text(" s where s.");
|
|
86
|
+
sink.text(quote(identity.sql));
|
|
87
|
+
sink.text(" = ");
|
|
88
|
+
sink.param(identity.encode(id));
|
|
89
|
+
emitWhere(schema, table, undefined, sink, "s", 0, true);
|
|
90
|
+
sink.text(" union all select ");
|
|
91
|
+
let started = emitBare(sink, "c", edge.parent, false);
|
|
92
|
+
emitBare(sink, "c", edge.child, started);
|
|
93
|
+
sink.text(", w.depth + 1, w.seen || c.");
|
|
94
|
+
sink.text(quote(identity.sql));
|
|
95
|
+
sink.text(" from ");
|
|
96
|
+
sink.text(quote(table.model.sql));
|
|
97
|
+
sink.text(" c join walk w on ");
|
|
98
|
+
if (direction === "descendants")
|
|
99
|
+
emitStep(sink, "c", edge.child, edge.parent, "k");
|
|
100
|
+
else
|
|
101
|
+
emitStep(sink, "c", edge.parent, edge.child, "f");
|
|
102
|
+
sink.text(" where c.");
|
|
103
|
+
sink.text(quote(identity.sql));
|
|
104
|
+
sink.text(" <> all(w.seen)");
|
|
105
|
+
if (depth !== undefined) {
|
|
106
|
+
sink.text(" and w.depth < ");
|
|
107
|
+
sink.text(String(depth));
|
|
108
|
+
}
|
|
109
|
+
emitWhere(schema, table, undefined, sink, "c", 0, true);
|
|
110
|
+
sink.text(") select ");
|
|
111
|
+
let column = 0;
|
|
112
|
+
for (let index = 0;index < selected.length; index += 1) {
|
|
113
|
+
const item = selected[index];
|
|
114
|
+
if (item === undefined)
|
|
115
|
+
continue;
|
|
116
|
+
if (column > 0)
|
|
117
|
+
sink.text(", ");
|
|
118
|
+
sink.text(projectExpr(`t.${quote(item.sql)}`, item.dataType));
|
|
119
|
+
sink.mark(`s:${item.field}`);
|
|
120
|
+
outputs?.fields.push({ key: item.field, at: column, decode: item.decode });
|
|
121
|
+
column += 1;
|
|
122
|
+
}
|
|
123
|
+
if (column === 0)
|
|
124
|
+
sink.text("1");
|
|
125
|
+
sink.text(" from ");
|
|
126
|
+
sink.text(quote(table.model.sql));
|
|
127
|
+
sink.text(" t join walk w on ");
|
|
128
|
+
emitStep(sink, "t", edge.parent, edge.parent, "k");
|
|
129
|
+
if (self)
|
|
130
|
+
emitWhere(schema, table, call.where, sink, "t", 0, false);
|
|
131
|
+
else {
|
|
132
|
+
sink.text(" where w.depth > 0");
|
|
133
|
+
emitWhere(schema, table, call.where, sink, "t", 0, true);
|
|
134
|
+
}
|
|
135
|
+
if (call.orderBy !== undefined) {
|
|
136
|
+
emitOrder(table, call.orderBy, sink, "t");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
sink.text(" order by w.depth asc");
|
|
140
|
+
sink.mark("order:depth");
|
|
141
|
+
for (const field of table.model.primary) {
|
|
142
|
+
const item = table.columns.get(field);
|
|
143
|
+
if (item === undefined)
|
|
144
|
+
continue;
|
|
145
|
+
sink.text(", t.");
|
|
146
|
+
sink.text(quote(item.sql));
|
|
147
|
+
sink.text(" asc");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function emitKeyList(sink, alias, columns, prefix) {
|
|
151
|
+
for (let index = 0;index < columns.length; index += 1) {
|
|
152
|
+
const name = columns[index];
|
|
153
|
+
if (name === undefined)
|
|
154
|
+
continue;
|
|
155
|
+
if (index > 0 || prefix === "f")
|
|
156
|
+
sink.text(", ");
|
|
157
|
+
sink.text(alias);
|
|
158
|
+
sink.text(".");
|
|
159
|
+
sink.text(quote(name));
|
|
160
|
+
sink.text(" as ");
|
|
161
|
+
sink.text(prefix);
|
|
162
|
+
sink.text(String(index));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function emitBare(sink, alias, columns, lead) {
|
|
166
|
+
let started = lead;
|
|
167
|
+
for (const name of columns) {
|
|
168
|
+
if (started)
|
|
169
|
+
sink.text(", ");
|
|
170
|
+
started = true;
|
|
171
|
+
sink.text(alias);
|
|
172
|
+
sink.text(".");
|
|
173
|
+
sink.text(quote(name));
|
|
174
|
+
}
|
|
175
|
+
return started;
|
|
176
|
+
}
|
|
177
|
+
function emitStep(sink, alias, columns, against, prefix) {
|
|
178
|
+
for (let index = 0;index < columns.length; index += 1) {
|
|
179
|
+
const name = columns[index];
|
|
180
|
+
if (name === undefined || against[index] === undefined)
|
|
181
|
+
continue;
|
|
182
|
+
if (index > 0)
|
|
183
|
+
sink.text(" and ");
|
|
184
|
+
sink.text(alias);
|
|
185
|
+
sink.text(".");
|
|
186
|
+
sink.text(quote(name));
|
|
187
|
+
sink.text(" = w.");
|
|
188
|
+
sink.text(prefix);
|
|
189
|
+
sink.text(String(index));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function readPacked(options) {
|
|
193
|
+
if (!isRecord(options) || options.walk !== "descendants" && options.walk !== "ancestors") {
|
|
194
|
+
fail2("OKM1120", "descendants and ancestors are table methods.");
|
|
195
|
+
}
|
|
196
|
+
const user = options.options;
|
|
197
|
+
if (user === undefined)
|
|
198
|
+
return { id: options.id, walk: options.walk, user: {} };
|
|
199
|
+
if (!isRecord(user))
|
|
200
|
+
fail2("OKM1120", `${options.walk} options must be an object.`);
|
|
201
|
+
return { id: options.id, walk: options.walk, user };
|
|
202
|
+
}
|
|
203
|
+
function pickEdge(table, relation) {
|
|
204
|
+
const edges = selfEdges(table);
|
|
205
|
+
if (edges.length === 0) {
|
|
206
|
+
fail2("OKM1120", `${table.model.name} has no relation to itself.`, `Declare children: many("${table.model.name}") or parent: one("${table.model.name}").`);
|
|
207
|
+
}
|
|
208
|
+
const groups = new Set(edges.map((edge) => edge.child.join("\x00")));
|
|
209
|
+
if (relation === undefined) {
|
|
210
|
+
if (groups.size !== 1) {
|
|
211
|
+
fail2("OKM1120", `${table.model.name} has more than one relation to itself. Pass relation.`, `Accepted names: ${edges.map((edge) => edge.name).join(", ")}.`);
|
|
212
|
+
}
|
|
213
|
+
const only = edges[0];
|
|
214
|
+
if (only === undefined)
|
|
215
|
+
fail2("OKM1120", `${table.model.name} has no relation to itself.`);
|
|
216
|
+
return only;
|
|
217
|
+
}
|
|
218
|
+
if (typeof relation !== "string" || relation.length === 0) {
|
|
219
|
+
fail2("OKM1120", "relation must be a name.");
|
|
220
|
+
}
|
|
221
|
+
const found = edges.find((edge) => edge.name === relation);
|
|
222
|
+
if (found === undefined) {
|
|
223
|
+
throwNamed2("OKM1120", relation, edges.map((edge) => edge.name), `Relation ${relation} is not a self-relation of ${table.model.name}. Accepted names: ${list(edges.map((edge) => edge.name))}.`);
|
|
224
|
+
}
|
|
225
|
+
return found;
|
|
226
|
+
}
|
|
227
|
+
function selfEdges(table) {
|
|
228
|
+
const edges = [];
|
|
229
|
+
for (const relation of table.model.relations) {
|
|
230
|
+
if (relation.table !== table.model.name || relation.through !== undefined)
|
|
231
|
+
continue;
|
|
232
|
+
edges.push(relation.kind === "many" ? { name: relation.name, parent: relation.local, child: relation.remote } : { name: relation.name, parent: relation.remote, child: relation.local });
|
|
233
|
+
}
|
|
234
|
+
return edges;
|
|
235
|
+
}
|
|
236
|
+
function identityColumn2(session, table) {
|
|
237
|
+
const tenancy = session.schema.tenancy;
|
|
238
|
+
const keys = tenancy === undefined ? [] : tenancy.keys ?? [tenancy.key];
|
|
239
|
+
const fields = table.model.primary.filter((field) => !keys.includes(field));
|
|
240
|
+
if (fields.length !== 1) {
|
|
241
|
+
fail2("OKM1120", `${table.model.name} needs one primary key column besides the tenant key.`, "Use a single-column primary key.");
|
|
242
|
+
}
|
|
243
|
+
const field = fields[0];
|
|
244
|
+
const column = field === undefined ? undefined : table.columns.get(field);
|
|
245
|
+
if (column === undefined) {
|
|
246
|
+
fail2("OKM1120", `${table.model.name} needs one primary key column besides the tenant key.`);
|
|
247
|
+
}
|
|
248
|
+
return column;
|
|
249
|
+
}
|
|
250
|
+
function readDepth(value) {
|
|
251
|
+
if (value === undefined)
|
|
252
|
+
return;
|
|
253
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
254
|
+
fail2("OKM1120", "depth must be an integer from 0 up.");
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function readSelf(value) {
|
|
259
|
+
if (value === undefined)
|
|
260
|
+
return false;
|
|
261
|
+
if (typeof value !== "boolean")
|
|
262
|
+
fail2("OKM1120", "self must be true or false.");
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
function userId(column, id) {
|
|
266
|
+
if (id === undefined || id === null) {
|
|
267
|
+
fail2("OKM1121", `${column.field} needs a value.`);
|
|
268
|
+
}
|
|
269
|
+
return id;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { build2, identityColumn2 };
|