prisma-ltree 0.0.1 → 0.1.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 +208 -3
- package/dist/codec-types-mZn-cIh9.d.mts +73 -0
- package/dist/codec-types-mZn-cIh9.d.mts.map +1 -0
- package/dist/codec-types.d.mts +2 -0
- package/dist/codec-types.mjs +1 -0
- package/dist/column-types.d.mts +8 -0
- package/dist/column-types.d.mts.map +1 -0
- package/dist/column-types.mjs +18 -0
- package/dist/column-types.mjs.map +1 -0
- package/dist/contract-space-constants-Da9EC668.mjs +15 -0
- package/dist/contract-space-constants-Da9EC668.mjs.map +1 -0
- package/dist/control.d.mts +7 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +139 -0
- package/dist/control.mjs.map +1 -0
- package/dist/descriptor-meta-B84muAZG.mjs +345 -0
- package/dist/descriptor-meta-B84muAZG.mjs.map +1 -0
- package/dist/operation-types-CP6jqHAZ.d.mts +184 -0
- package/dist/operation-types-CP6jqHAZ.d.mts.map +1 -0
- package/dist/operation-types.d.mts +2 -0
- package/dist/operation-types.mjs +1 -0
- package/dist/pack.d.mts +73 -0
- package/dist/pack.d.mts.map +1 -0
- package/dist/pack.mjs +2 -0
- package/dist/runtime.d.mts +11 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +22 -0
- package/dist/runtime.mjs.map +1 -0
- package/package.json +80 -10
- package/src/contract.d.ts +115 -0
- package/src/contract.json +61 -0
- package/src/contract.ts +27 -0
- package/src/core/authoring.ts +22 -0
- package/src/core/codecs.ts +178 -0
- package/src/core/constants.ts +7 -0
- package/src/core/contract-space-constants.ts +14 -0
- package/src/core/descriptor-meta.ts +346 -0
- package/src/core/registry.ts +6 -0
- package/src/exports/codec-types.ts +1 -0
- package/src/exports/column-types.ts +17 -0
- package/src/exports/control.ts +55 -0
- package/src/exports/operation-types.ts +1 -0
- package/src/exports/pack.ts +1 -0
- package/src/exports/runtime.ts +27 -0
- package/src/types/codec-types.ts +7 -0
- package/src/types/operation-types.ts +156 -0
- package/index.js +0 -3
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type { AnyExpression } from "@prisma-next/sql-relational-core/ast";
|
|
2
|
+
import {
|
|
3
|
+
buildOperation,
|
|
4
|
+
type CodecExpression,
|
|
5
|
+
codecOf,
|
|
6
|
+
type Expression,
|
|
7
|
+
toExpr,
|
|
8
|
+
} from "@prisma-next/sql-relational-core/expression";
|
|
9
|
+
import type { CodecTypes } from "../types/codec-types";
|
|
10
|
+
import type { QueryOperationTypes } from "../types/operation-types";
|
|
11
|
+
import { ltreeAuthoringTypes } from "./authoring";
|
|
12
|
+
import { LTREE_ARRAY_CODEC_ID, LTREE_CODEC_ID } from "./constants";
|
|
13
|
+
import { LTREE_ARRAY_NATIVE_TYPE, LTREE_NATIVE_TYPE } from "./contract-space-constants";
|
|
14
|
+
import { ltreeCodecRegistry } from "./registry";
|
|
15
|
+
|
|
16
|
+
type CodecTypesBase = Record<string, { readonly input: unknown; readonly output: unknown }>;
|
|
17
|
+
|
|
18
|
+
type BoolReturn = Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
19
|
+
type LtreeReturn = Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
20
|
+
type IntReturn = Expression<{ readonly codecId: "pg/int4@1"; readonly nullable: false }>;
|
|
21
|
+
type TextReturn = Expression<{ readonly codecId: "pg/text@1"; readonly nullable: false }>;
|
|
22
|
+
|
|
23
|
+
const BOOL_RETURN = { codecId: "pg/bool@1", nullable: false } as const;
|
|
24
|
+
const LTREE_RETURN = { codecId: "pg/ltree@1", nullable: false } as const;
|
|
25
|
+
const INT_RETURN = { codecId: "pg/int4@1", nullable: false } as const;
|
|
26
|
+
const TEXT_RETURN = { codecId: "pg/text@1", nullable: false } as const;
|
|
27
|
+
const TEXT_CODEC_ID = "pg/text@1" as const;
|
|
28
|
+
const TEXT_ARRAY_CODEC_ID = "pg/text-array@1" as const;
|
|
29
|
+
const INT_CODEC_ID = "pg/int4@1" as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build a function-style operation whose SQL is `fnName(self, arg0, arg1, ...)`.
|
|
33
|
+
* The template's placeholder count is derived from the actual argument list, so
|
|
34
|
+
* one helper covers fixed-arity functions (`nlevel`, `subltree`), optional-arg
|
|
35
|
+
* overloads (`subpath`, `index`), and the variadic `lca` (see ADR-001). `args`
|
|
36
|
+
* already carries each value through `toExpr` with its codec; `args[0]` is the
|
|
37
|
+
* `self` receiver.
|
|
38
|
+
*/
|
|
39
|
+
function funcOp<C extends string>(
|
|
40
|
+
method: string,
|
|
41
|
+
fnName: string,
|
|
42
|
+
returns: { readonly codecId: C; readonly nullable: false },
|
|
43
|
+
args: readonly [AnyExpression, ...AnyExpression[]],
|
|
44
|
+
): Expression<{ readonly codecId: C; readonly nullable: false }> {
|
|
45
|
+
const placeholders = args.map((_, i) => (i === 0 ? "{{self}}" : `{{arg${i - 1}}}`)).join(", ");
|
|
46
|
+
return buildOperation({
|
|
47
|
+
method,
|
|
48
|
+
args,
|
|
49
|
+
returns,
|
|
50
|
+
lowering: {
|
|
51
|
+
targetFamily: "sql",
|
|
52
|
+
strategy: "function",
|
|
53
|
+
template: `${fnName}(${placeholders})`,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function ltreeQueryOperations<CT extends CodecTypesBase>(): QueryOperationTypes<CT> {
|
|
59
|
+
// Hierarchy operators compare two `ltree` values; the user argument shares the
|
|
60
|
+
// column's `pg/ltree@1` codec. Pattern-match operators take a query/pattern
|
|
61
|
+
// string (or `string[]`) that is NOT an ltree — it is bound as text and cast
|
|
62
|
+
// in-template to `lquery` / `lquery[]` / `ltxtquery` (the validated-string-param
|
|
63
|
+
// approach; verified executable under PGlite).
|
|
64
|
+
const hierarchyOp = (
|
|
65
|
+
method: string,
|
|
66
|
+
operator: string,
|
|
67
|
+
self: CodecExpression<typeof LTREE_CODEC_ID, boolean, CT>,
|
|
68
|
+
other: CodecExpression<typeof LTREE_CODEC_ID, boolean, CT>,
|
|
69
|
+
): BoolReturn => {
|
|
70
|
+
const selfCodec = codecOf(self);
|
|
71
|
+
return buildOperation({
|
|
72
|
+
method,
|
|
73
|
+
args: [toExpr(self, selfCodec), toExpr(other, selfCodec)],
|
|
74
|
+
returns: BOOL_RETURN,
|
|
75
|
+
lowering: {
|
|
76
|
+
targetFamily: "sql",
|
|
77
|
+
strategy: "function",
|
|
78
|
+
template: `{{self}} ${operator} {{arg0}}`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const patternOp = (
|
|
84
|
+
method: string,
|
|
85
|
+
operator: string,
|
|
86
|
+
castType: string,
|
|
87
|
+
argCodecId: typeof TEXT_CODEC_ID | typeof TEXT_ARRAY_CODEC_ID,
|
|
88
|
+
self: CodecExpression<typeof LTREE_CODEC_ID, boolean, CT>,
|
|
89
|
+
arg: unknown,
|
|
90
|
+
): BoolReturn => {
|
|
91
|
+
const selfCodec = codecOf(self);
|
|
92
|
+
return buildOperation({
|
|
93
|
+
method,
|
|
94
|
+
args: [toExpr(self, selfCodec), toExpr(arg, { codecId: argCodecId })],
|
|
95
|
+
returns: BOOL_RETURN,
|
|
96
|
+
lowering: {
|
|
97
|
+
targetFamily: "sql",
|
|
98
|
+
strategy: "function",
|
|
99
|
+
template: `{{self}} ${operator} ({{arg0}})::${castType}`,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Concatenation operators produce a new ltree. `concat` joins two ltree paths
|
|
105
|
+
// (`{{self}} || {{arg0}}`); `concatText`/`prependText` splice a text label onto
|
|
106
|
+
// the right/left. The text operand binds as `pg/text@1` and is cast in-template
|
|
107
|
+
// (PG resolves the `||` overload from the cast). `prependText` places `{{self}}`
|
|
108
|
+
// second because the ltree receiver is the RIGHT operand of `text || ltree`;
|
|
109
|
+
// `args[0]` is still `self` (the renderer binds by placeholder name, not order).
|
|
110
|
+
const concatOp = (
|
|
111
|
+
method: string,
|
|
112
|
+
template: string,
|
|
113
|
+
args: readonly [AnyExpression, ...AnyExpression[]],
|
|
114
|
+
): LtreeReturn =>
|
|
115
|
+
buildOperation({
|
|
116
|
+
method,
|
|
117
|
+
args,
|
|
118
|
+
returns: LTREE_RETURN,
|
|
119
|
+
lowering: { targetFamily: "sql", strategy: "function", template },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Tier 3 — first-match operators on an `ltree[]` receiver (ADR-003).
|
|
123
|
+
const firstMatchOp = (
|
|
124
|
+
method: string,
|
|
125
|
+
operator: string,
|
|
126
|
+
self: CodecExpression<typeof LTREE_ARRAY_CODEC_ID, boolean, CT>,
|
|
127
|
+
arg: unknown,
|
|
128
|
+
argCodecId: typeof LTREE_CODEC_ID | typeof TEXT_CODEC_ID,
|
|
129
|
+
castType?: string,
|
|
130
|
+
): LtreeReturn => {
|
|
131
|
+
const selfCodec = codecOf(self);
|
|
132
|
+
const template = castType
|
|
133
|
+
? `{{self}} ${operator} ({{arg0}})::${castType}`
|
|
134
|
+
: `{{self}} ${operator} {{arg0}}`;
|
|
135
|
+
return buildOperation({
|
|
136
|
+
method,
|
|
137
|
+
args: [toExpr(self, selfCodec), toExpr(arg, { codecId: argCodecId })],
|
|
138
|
+
returns: LTREE_RETURN,
|
|
139
|
+
lowering: { targetFamily: "sql", strategy: "function", template },
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
isAncestorOf: {
|
|
145
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
146
|
+
impl: (self, other) => hierarchyOp("isAncestorOf", "@>", self, other),
|
|
147
|
+
},
|
|
148
|
+
isDescendantOf: {
|
|
149
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
150
|
+
impl: (self, other) => hierarchyOp("isDescendantOf", "<@", self, other),
|
|
151
|
+
},
|
|
152
|
+
matchesLquery: {
|
|
153
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
154
|
+
impl: (self, pattern) =>
|
|
155
|
+
patternOp("matchesLquery", "~", "lquery", TEXT_CODEC_ID, self, pattern),
|
|
156
|
+
},
|
|
157
|
+
matchesLqueryArray: {
|
|
158
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
159
|
+
impl: (self, patterns) =>
|
|
160
|
+
patternOp("matchesLqueryArray", "?", "lquery[]", TEXT_ARRAY_CODEC_ID, self, patterns),
|
|
161
|
+
},
|
|
162
|
+
matchesLtxtquery: {
|
|
163
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
164
|
+
impl: (self, query) =>
|
|
165
|
+
patternOp("matchesLtxtquery", "@", "ltxtquery", TEXT_CODEC_ID, self, query),
|
|
166
|
+
},
|
|
167
|
+
nlevel: {
|
|
168
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
169
|
+
impl: (self): IntReturn =>
|
|
170
|
+
funcOp("nlevel", "nlevel", INT_RETURN, [toExpr(self, codecOf(self))]),
|
|
171
|
+
},
|
|
172
|
+
subltree: {
|
|
173
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
174
|
+
impl: (self, start, end): LtreeReturn =>
|
|
175
|
+
funcOp("subltree", "subltree", LTREE_RETURN, [
|
|
176
|
+
toExpr(self, codecOf(self)),
|
|
177
|
+
toExpr(start, { codecId: INT_CODEC_ID }),
|
|
178
|
+
toExpr(end, { codecId: INT_CODEC_ID }),
|
|
179
|
+
]),
|
|
180
|
+
},
|
|
181
|
+
// `subpath(self, offset, len?)` — `len` omitted lowers to the 2-arg overload.
|
|
182
|
+
subpath: {
|
|
183
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
184
|
+
impl: (self, offset, len): LtreeReturn => {
|
|
185
|
+
const head: readonly [AnyExpression, AnyExpression] = [
|
|
186
|
+
toExpr(self, codecOf(self)),
|
|
187
|
+
toExpr(offset, { codecId: INT_CODEC_ID }),
|
|
188
|
+
];
|
|
189
|
+
return funcOp(
|
|
190
|
+
"subpath",
|
|
191
|
+
"subpath",
|
|
192
|
+
LTREE_RETURN,
|
|
193
|
+
len === undefined ? head : [...head, toExpr(len, { codecId: INT_CODEC_ID })],
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
// `index(self, other, offset?)` — `offset` omitted lowers to the 2-arg overload.
|
|
198
|
+
indexOf: {
|
|
199
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
200
|
+
impl: (self, other, offset): IntReturn => {
|
|
201
|
+
const selfCodec = codecOf(self);
|
|
202
|
+
const head: readonly [AnyExpression, AnyExpression] = [
|
|
203
|
+
toExpr(self, selfCodec),
|
|
204
|
+
toExpr(other, selfCodec),
|
|
205
|
+
];
|
|
206
|
+
return funcOp(
|
|
207
|
+
"indexOf",
|
|
208
|
+
"index",
|
|
209
|
+
INT_RETURN,
|
|
210
|
+
offset === undefined ? head : [...head, toExpr(offset, { codecId: INT_CODEC_ID })],
|
|
211
|
+
);
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
// `lca(self, other, ...rest)` — variadic longest-common-ancestor (ADR-001).
|
|
215
|
+
// PostgreSQL has no single-arg `lca`, so `other` is required (2–8 paths).
|
|
216
|
+
lca: {
|
|
217
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
218
|
+
impl: (self, other, ...rest): LtreeReturn => {
|
|
219
|
+
const selfCodec = codecOf(self);
|
|
220
|
+
return funcOp("lca", "lca", LTREE_RETURN, [
|
|
221
|
+
toExpr(self, selfCodec),
|
|
222
|
+
toExpr(other, selfCodec),
|
|
223
|
+
...rest.map((p) => toExpr(p, selfCodec)),
|
|
224
|
+
]);
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
// Tier 2 — concatenation (`||`).
|
|
228
|
+
concat: {
|
|
229
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
230
|
+
impl: (self, other): LtreeReturn => {
|
|
231
|
+
const selfCodec = codecOf(self);
|
|
232
|
+
return concatOp("concat", "{{self}} || {{arg0}}", [
|
|
233
|
+
toExpr(self, selfCodec),
|
|
234
|
+
toExpr(other, selfCodec),
|
|
235
|
+
]);
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
concatText: {
|
|
239
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
240
|
+
impl: (self, label): LtreeReturn =>
|
|
241
|
+
concatOp("concatText", "{{self}} || ({{arg0}})::text", [
|
|
242
|
+
toExpr(self, codecOf(self)),
|
|
243
|
+
toExpr(label, { codecId: TEXT_CODEC_ID }),
|
|
244
|
+
]),
|
|
245
|
+
},
|
|
246
|
+
// `text || ltree` — the ltree receiver is the right operand (ADR-002).
|
|
247
|
+
prependText: {
|
|
248
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
249
|
+
impl: (self, prefix): LtreeReturn =>
|
|
250
|
+
concatOp("prependText", "({{arg0}})::text || {{self}}", [
|
|
251
|
+
toExpr(self, codecOf(self)),
|
|
252
|
+
toExpr(prefix, { codecId: TEXT_CODEC_ID }),
|
|
253
|
+
]),
|
|
254
|
+
},
|
|
255
|
+
// Tier 2 — conversion.
|
|
256
|
+
toText: {
|
|
257
|
+
self: { codecId: LTREE_CODEC_ID },
|
|
258
|
+
impl: (self): TextReturn =>
|
|
259
|
+
funcOp("toText", "ltree2text", TEXT_RETURN, [toExpr(self, codecOf(self))]),
|
|
260
|
+
},
|
|
261
|
+
// `text2ltree(text)` — rooted on `pg/text@1` (the only reachable form of the
|
|
262
|
+
// free-function `text2ltree`; see ADR-002). Surfaces as `.toLtree()` on text.
|
|
263
|
+
toLtree: {
|
|
264
|
+
self: { codecId: TEXT_CODEC_ID },
|
|
265
|
+
impl: (self): LtreeReturn =>
|
|
266
|
+
funcOp("toLtree", "text2ltree", LTREE_RETURN, [toExpr(self, codecOf(self))]),
|
|
267
|
+
},
|
|
268
|
+
// Tier 3 — array first-match operators (ADR-003).
|
|
269
|
+
firstAncestorOf: {
|
|
270
|
+
self: { codecId: LTREE_ARRAY_CODEC_ID },
|
|
271
|
+
impl: (self, other) => firstMatchOp("firstAncestorOf", "?@>", self, other, LTREE_CODEC_ID),
|
|
272
|
+
},
|
|
273
|
+
firstDescendantOf: {
|
|
274
|
+
self: { codecId: LTREE_ARRAY_CODEC_ID },
|
|
275
|
+
impl: (self, other) => firstMatchOp("firstDescendantOf", "?<@", self, other, LTREE_CODEC_ID),
|
|
276
|
+
},
|
|
277
|
+
firstMatchLquery: {
|
|
278
|
+
self: { codecId: LTREE_ARRAY_CODEC_ID },
|
|
279
|
+
impl: (self, pattern) =>
|
|
280
|
+
firstMatchOp("firstMatchLquery", "?~", self, pattern, TEXT_CODEC_ID, "lquery"),
|
|
281
|
+
},
|
|
282
|
+
firstMatchLtxtquery: {
|
|
283
|
+
self: { codecId: LTREE_ARRAY_CODEC_ID },
|
|
284
|
+
impl: (self, query) =>
|
|
285
|
+
firstMatchOp("firstMatchLtxtquery", "?@", self, query, TEXT_CODEC_ID, "ltxtquery"),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const ltreePackMetaBase = {
|
|
291
|
+
kind: "extension",
|
|
292
|
+
id: "ltree",
|
|
293
|
+
familyId: "sql",
|
|
294
|
+
targetId: "postgres",
|
|
295
|
+
version: "0.1.0",
|
|
296
|
+
capabilities: {
|
|
297
|
+
postgres: {
|
|
298
|
+
"ltree.path": true,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
authoring: {
|
|
302
|
+
type: ltreeAuthoringTypes,
|
|
303
|
+
},
|
|
304
|
+
types: {
|
|
305
|
+
codecTypes: {
|
|
306
|
+
codecDescriptors: Array.from(ltreeCodecRegistry.values()),
|
|
307
|
+
import: {
|
|
308
|
+
package: "prisma-ltree/codec-types",
|
|
309
|
+
named: "CodecTypes",
|
|
310
|
+
alias: "LtreeTypes",
|
|
311
|
+
},
|
|
312
|
+
typeImports: [
|
|
313
|
+
{
|
|
314
|
+
package: "prisma-ltree/codec-types",
|
|
315
|
+
named: "Ltree",
|
|
316
|
+
alias: "Ltree",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
queryOperationTypes: {
|
|
321
|
+
import: {
|
|
322
|
+
package: "prisma-ltree/operation-types",
|
|
323
|
+
named: "QueryOperationTypes",
|
|
324
|
+
alias: "LtreeQueryOperationTypes",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
storage: [
|
|
328
|
+
{
|
|
329
|
+
typeId: LTREE_CODEC_ID,
|
|
330
|
+
familyId: "sql",
|
|
331
|
+
targetId: "postgres",
|
|
332
|
+
nativeType: LTREE_NATIVE_TYPE,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
typeId: LTREE_ARRAY_CODEC_ID,
|
|
336
|
+
familyId: "sql",
|
|
337
|
+
targetId: "postgres",
|
|
338
|
+
nativeType: LTREE_ARRAY_NATIVE_TYPE,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
} as const;
|
|
343
|
+
|
|
344
|
+
export const ltreePackMeta: typeof ltreePackMetaBase & {
|
|
345
|
+
readonly __codecTypes?: CodecTypes;
|
|
346
|
+
} = ltreePackMetaBase;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { buildCodecDescriptorRegistry } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
|
|
2
|
+
import type { CodecDescriptorRegistry } from "@prisma-next/sql-relational-core/query-lane-context";
|
|
3
|
+
import { codecDescriptors } from "./codecs";
|
|
4
|
+
|
|
5
|
+
export const ltreeCodecRegistry: CodecDescriptorRegistry =
|
|
6
|
+
buildCodecDescriptorRegistry(codecDescriptors);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { CodecTypes, Ltree, LtreeArray } from "../types/codec-types";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ColumnTypeDescriptor } from "@prisma-next/framework-components/codec";
|
|
2
|
+
import { LTREE_ARRAY_CODEC_ID, LTREE_CODEC_ID } from "../core/constants";
|
|
3
|
+
import { LTREE_ARRAY_NATIVE_TYPE, LTREE_NATIVE_TYPE } from "../core/contract-space-constants";
|
|
4
|
+
|
|
5
|
+
export function ltree(): ColumnTypeDescriptor {
|
|
6
|
+
return {
|
|
7
|
+
codecId: LTREE_CODEC_ID,
|
|
8
|
+
nativeType: LTREE_NATIVE_TYPE,
|
|
9
|
+
} as const;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ltreeArray(): ColumnTypeDescriptor {
|
|
13
|
+
return {
|
|
14
|
+
codecId: LTREE_ARRAY_CODEC_ID,
|
|
15
|
+
nativeType: LTREE_ARRAY_NATIVE_TYPE,
|
|
16
|
+
} as const;
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Contract } from "@prisma-next/contract/types";
|
|
2
|
+
import type {
|
|
3
|
+
CodecControlHooks,
|
|
4
|
+
SqlControlExtensionDescriptor,
|
|
5
|
+
} from "@prisma-next/family-sql/control";
|
|
6
|
+
import { contractSpaceFromJson } from "@prisma-next/migration-tools/spaces";
|
|
7
|
+
import type { SqlStorage } from "@prisma-next/sql-contract/types";
|
|
8
|
+
import baselineMetadata from "../../migrations/app/20260619T2142_install_ltree/migration.json" with { type: "json" };
|
|
9
|
+
import baselineOps from "../../migrations/app/20260619T2142_install_ltree/ops.json" with { type: "json" };
|
|
10
|
+
import headRef from "../../migrations/app/refs/head.json" with { type: "json" };
|
|
11
|
+
import contractJson from "../contract.json" with { type: "json" };
|
|
12
|
+
import { LTREE_ARRAY_CODEC_ID, LTREE_CODEC_ID } from "../core/constants";
|
|
13
|
+
import { LTREE_SPACE_ID, LTREE_BASELINE_MIGRATION_NAME } from "../core/contract-space-constants";
|
|
14
|
+
import { ltreePackMeta, ltreeQueryOperations } from "../core/descriptor-meta";
|
|
15
|
+
|
|
16
|
+
const ltreeControlPlaneHooks: CodecControlHooks = {
|
|
17
|
+
expandNativeType: ({ nativeType }) => nativeType,
|
|
18
|
+
resolveIdentityValue: () => null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ltreeContractSpace = contractSpaceFromJson<Contract<SqlStorage>>({
|
|
22
|
+
contractJson,
|
|
23
|
+
migrations: [
|
|
24
|
+
{
|
|
25
|
+
dirName: LTREE_BASELINE_MIGRATION_NAME,
|
|
26
|
+
metadata: baselineMetadata,
|
|
27
|
+
ops: baselineOps,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
headRef,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const ltreeExtensionDescriptor: SqlControlExtensionDescriptor<"postgres"> = {
|
|
34
|
+
...ltreePackMeta,
|
|
35
|
+
id: LTREE_SPACE_ID,
|
|
36
|
+
contractSpace: ltreeContractSpace,
|
|
37
|
+
types: {
|
|
38
|
+
...ltreePackMeta.types,
|
|
39
|
+
codecTypes: {
|
|
40
|
+
...ltreePackMeta.types.codecTypes,
|
|
41
|
+
controlPlaneHooks: {
|
|
42
|
+
[LTREE_CODEC_ID]: ltreeControlPlaneHooks,
|
|
43
|
+
[LTREE_ARRAY_CODEC_ID]: ltreeControlPlaneHooks,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
queryOperations: () => ltreeQueryOperations(),
|
|
48
|
+
create: () => ({
|
|
49
|
+
familyId: "sql" as const,
|
|
50
|
+
targetId: "postgres" as const,
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { ltreeExtensionDescriptor };
|
|
55
|
+
export default ltreeExtensionDescriptor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { QueryOperationTypes } from "../types/operation-types";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ltreePackMeta as default } from "../core/descriptor-meta";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SqlRuntimeExtensionDescriptor } from "@prisma-next/sql-runtime";
|
|
2
|
+
import { ltreePackMeta, ltreeQueryOperations } from "../core/descriptor-meta";
|
|
3
|
+
import { ltreeCodecRegistry } from "../core/registry";
|
|
4
|
+
|
|
5
|
+
const ltreeRuntimeDescriptor: SqlRuntimeExtensionDescriptor<"postgres"> = {
|
|
6
|
+
kind: "extension" as const,
|
|
7
|
+
id: ltreePackMeta.id,
|
|
8
|
+
version: ltreePackMeta.version,
|
|
9
|
+
familyId: "sql" as const,
|
|
10
|
+
targetId: "postgres" as const,
|
|
11
|
+
types: {
|
|
12
|
+
codecTypes: {
|
|
13
|
+
codecDescriptors: Array.from(ltreeCodecRegistry.values()),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
codecs: () => Array.from(ltreeCodecRegistry.values()),
|
|
17
|
+
queryOperations: () => ltreeQueryOperations(),
|
|
18
|
+
create() {
|
|
19
|
+
return {
|
|
20
|
+
familyId: "sql" as const,
|
|
21
|
+
targetId: "postgres" as const,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { ltreeCodecRegistry };
|
|
27
|
+
export default ltreeRuntimeDescriptor;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { SqlQueryOperationTypes } from "@prisma-next/sql-contract/types";
|
|
2
|
+
import type { CodecExpression, Expression } from "@prisma-next/sql-relational-core/expression";
|
|
3
|
+
|
|
4
|
+
type CodecTypesBase = Record<string, { readonly input: unknown; readonly output: unknown }>;
|
|
5
|
+
|
|
6
|
+
export type QueryOperationTypes<CT extends CodecTypesBase> = SqlQueryOperationTypes<
|
|
7
|
+
CT,
|
|
8
|
+
{
|
|
9
|
+
readonly isAncestorOf: {
|
|
10
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
11
|
+
readonly impl: (
|
|
12
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
13
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
14
|
+
) => Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
15
|
+
};
|
|
16
|
+
readonly isDescendantOf: {
|
|
17
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
18
|
+
readonly impl: (
|
|
19
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
20
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
21
|
+
) => Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
22
|
+
};
|
|
23
|
+
// Pattern-match operators: the argument is a query/pattern string (or
|
|
24
|
+
// `string[]`), bound as text and cast in-template to lquery/ltxtquery — not
|
|
25
|
+
// an ltree value.
|
|
26
|
+
readonly matchesLquery: {
|
|
27
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
28
|
+
readonly impl: (
|
|
29
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
30
|
+
pattern: CodecExpression<"pg/text@1", boolean, CT>,
|
|
31
|
+
) => Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
32
|
+
};
|
|
33
|
+
readonly matchesLqueryArray: {
|
|
34
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
35
|
+
readonly impl: (
|
|
36
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
37
|
+
patterns: CodecExpression<"pg/text-array@1", boolean, CT>,
|
|
38
|
+
) => Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
39
|
+
};
|
|
40
|
+
readonly matchesLtxtquery: {
|
|
41
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
42
|
+
readonly impl: (
|
|
43
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
44
|
+
query: CodecExpression<"pg/text@1", boolean, CT>,
|
|
45
|
+
) => Expression<{ readonly codecId: "pg/bool@1"; readonly nullable: false }>;
|
|
46
|
+
};
|
|
47
|
+
// Scalar functions.
|
|
48
|
+
readonly nlevel: {
|
|
49
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
50
|
+
readonly impl: (
|
|
51
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
52
|
+
) => Expression<{ readonly codecId: "pg/int4@1"; readonly nullable: false }>;
|
|
53
|
+
};
|
|
54
|
+
readonly subltree: {
|
|
55
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
56
|
+
readonly impl: (
|
|
57
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
58
|
+
start: CodecExpression<"pg/int4@1", boolean, CT>,
|
|
59
|
+
end: CodecExpression<"pg/int4@1", boolean, CT>,
|
|
60
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
61
|
+
};
|
|
62
|
+
readonly subpath: {
|
|
63
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
64
|
+
readonly impl: (
|
|
65
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
66
|
+
offset: CodecExpression<"pg/int4@1", boolean, CT>,
|
|
67
|
+
len?: CodecExpression<"pg/int4@1", boolean, CT>,
|
|
68
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
69
|
+
};
|
|
70
|
+
readonly indexOf: {
|
|
71
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
72
|
+
readonly impl: (
|
|
73
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
74
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
75
|
+
offset?: CodecExpression<"pg/int4@1", boolean, CT>,
|
|
76
|
+
) => Expression<{ readonly codecId: "pg/int4@1"; readonly nullable: false }>;
|
|
77
|
+
};
|
|
78
|
+
// Variadic longest-common-ancestor on the first path (ADR-001). PostgreSQL's
|
|
79
|
+
// positional `lca` has no single-argument form, so at least one `other` path
|
|
80
|
+
// is required (total 2–8 paths).
|
|
81
|
+
readonly lca: {
|
|
82
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
83
|
+
readonly impl: (
|
|
84
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
85
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
86
|
+
...rest: ReadonlyArray<CodecExpression<"pg/ltree@1", boolean, CT>>
|
|
87
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
88
|
+
};
|
|
89
|
+
// Tier 2 — concatenation. The text operand is bound as text and cast
|
|
90
|
+
// in-template; `prependText` splices the label on the left (`text || ltree`).
|
|
91
|
+
readonly concat: {
|
|
92
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
93
|
+
readonly impl: (
|
|
94
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
95
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
96
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
97
|
+
};
|
|
98
|
+
readonly concatText: {
|
|
99
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
100
|
+
readonly impl: (
|
|
101
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
102
|
+
label: CodecExpression<"pg/text@1", boolean, CT>,
|
|
103
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
104
|
+
};
|
|
105
|
+
readonly prependText: {
|
|
106
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
107
|
+
readonly impl: (
|
|
108
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
109
|
+
prefix: CodecExpression<"pg/text@1", boolean, CT>,
|
|
110
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
111
|
+
};
|
|
112
|
+
// Tier 2 — conversion. `toLtree` is rooted on `pg/text@1` (ADR-002): it is the
|
|
113
|
+
// reachable form of `text2ltree`, surfacing on text columns rather than ltree.
|
|
114
|
+
readonly toText: {
|
|
115
|
+
readonly self: { readonly codecId: "pg/ltree@1" };
|
|
116
|
+
readonly impl: (
|
|
117
|
+
self: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
118
|
+
) => Expression<{ readonly codecId: "pg/text@1"; readonly nullable: false }>;
|
|
119
|
+
};
|
|
120
|
+
readonly toLtree: {
|
|
121
|
+
readonly self: { readonly codecId: "pg/text@1" };
|
|
122
|
+
readonly impl: (
|
|
123
|
+
self: CodecExpression<"pg/text@1", boolean, CT>,
|
|
124
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
125
|
+
};
|
|
126
|
+
// Tier 3 — array first-match operators on `pg/ltree-array@1` (ADR-003).
|
|
127
|
+
readonly firstAncestorOf: {
|
|
128
|
+
readonly self: { readonly codecId: "pg/ltree-array@1" };
|
|
129
|
+
readonly impl: (
|
|
130
|
+
self: CodecExpression<"pg/ltree-array@1", boolean, CT>,
|
|
131
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
132
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
133
|
+
};
|
|
134
|
+
readonly firstDescendantOf: {
|
|
135
|
+
readonly self: { readonly codecId: "pg/ltree-array@1" };
|
|
136
|
+
readonly impl: (
|
|
137
|
+
self: CodecExpression<"pg/ltree-array@1", boolean, CT>,
|
|
138
|
+
other: CodecExpression<"pg/ltree@1", boolean, CT>,
|
|
139
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
140
|
+
};
|
|
141
|
+
readonly firstMatchLquery: {
|
|
142
|
+
readonly self: { readonly codecId: "pg/ltree-array@1" };
|
|
143
|
+
readonly impl: (
|
|
144
|
+
self: CodecExpression<"pg/ltree-array@1", boolean, CT>,
|
|
145
|
+
pattern: CodecExpression<"pg/text@1", boolean, CT>,
|
|
146
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
147
|
+
};
|
|
148
|
+
readonly firstMatchLtxtquery: {
|
|
149
|
+
readonly self: { readonly codecId: "pg/ltree-array@1" };
|
|
150
|
+
readonly impl: (
|
|
151
|
+
self: CodecExpression<"pg/ltree-array@1", boolean, CT>,
|
|
152
|
+
query: CodecExpression<"pg/text@1", boolean, CT>,
|
|
153
|
+
) => Expression<{ readonly codecId: "pg/ltree@1"; readonly nullable: false }>;
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
>;
|
package/index.js
DELETED