turbine-orm 0.29.0 → 0.31.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 +1 -1
- package/dist/cjs/cli/index.js +5 -0
- package/dist/cjs/cli/mcp.js +22 -92
- package/dist/cjs/client.js +47 -6
- package/dist/cjs/generate.js +71 -25
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/introspect.js +350 -120
- package/dist/cjs/mssql.js +42 -136
- package/dist/cjs/mysql.js +16 -129
- package/dist/cjs/optional-peer-import.cjs +122 -0
- package/dist/cjs/powdb.js +579 -89
- package/dist/cjs/powql.js +56 -26
- package/dist/cjs/query/builder.js +601 -86
- package/dist/cjs/query/filters.js +80 -2
- package/dist/cjs/schema-metadata.js +316 -0
- package/dist/cjs/sqlite.js +8 -89
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/mcp.d.ts +18 -0
- package/dist/cli/mcp.js +22 -93
- package/dist/client.d.ts +19 -2
- package/dist/client.js +47 -6
- package/dist/generate.d.ts +16 -4
- package/dist/generate.js +71 -25
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/dist/introspect.d.ts +94 -1
- package/dist/introspect.js +345 -120
- package/dist/mssql.js +40 -104
- package/dist/mysql.js +14 -97
- package/dist/optional-peer-import.cjs +89 -0
- package/dist/optional-peer-import.d.cts +53 -0
- package/dist/powdb.d.ts +118 -23
- package/dist/powdb.js +574 -88
- package/dist/powql.d.ts +6 -0
- package/dist/powql.js +58 -28
- package/dist/query/builder.d.ts +145 -8
- package/dist/query/builder.js +602 -87
- package/dist/query/deferred.d.ts +7 -2
- package/dist/query/filters.d.ts +46 -1
- package/dist/query/filters.js +76 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +85 -11
- package/dist/schema-metadata.d.ts +77 -0
- package/dist/schema-metadata.js +313 -0
- package/dist/schema.d.ts +10 -0
- package/dist/sqlite.js +9 -90
- package/package.json +3 -3
package/dist/powql.d.ts
CHANGED
|
@@ -156,6 +156,12 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
156
156
|
* it and the trailing `returning` reads it back — as is any non-string PK.
|
|
157
157
|
*/
|
|
158
158
|
private applyPkDefault;
|
|
159
|
+
/**
|
|
160
|
+
* The table name as a PowQL type reference — backtick-quoted when it is a
|
|
161
|
+
* reserved word (e.g. a table named `order`). Used in every emitted
|
|
162
|
+
* statement; plain `this.table` stays in error messages.
|
|
163
|
+
*/
|
|
164
|
+
private get qt();
|
|
159
165
|
create(args: CreateArgs<T>): Promise<T>;
|
|
160
166
|
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
161
167
|
update(args: UpdateArgs<T>): Promise<T>;
|
package/dist/powql.js
CHANGED
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
import { randomUUID } from 'node:crypto';
|
|
38
38
|
import { NotFoundError, TimeoutError, UnsupportedFeatureError, ValidationError } from './errors.js';
|
|
39
39
|
import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './nested-write.js';
|
|
40
|
-
import { PowdbFloatParam, powqlColumnType, rowToEntity } from './powdb.js';
|
|
40
|
+
import { PowdbFloatParam, powqlColumnType, quotePowqlIdent, rowToEntity } from './powdb.js';
|
|
41
41
|
import { escapeLike } from './query/utils.js';
|
|
42
|
-
import { normalizeKeyColumns, } from './schema.js';
|
|
42
|
+
import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
|
|
43
43
|
/**
|
|
44
44
|
* Max parent keys per relation-loader `in (…)` query. A `with` over a large
|
|
45
45
|
* parent set is split into batches of this size so a single query never exceeds
|
|
@@ -106,7 +106,14 @@ export class PowqlInterface {
|
|
|
106
106
|
}
|
|
107
107
|
this.meta = meta;
|
|
108
108
|
this.defaultLimit = options.defaultLimit;
|
|
109
|
-
|
|
109
|
+
// Same per-table resolution as QueryInterface: an object map accepts BOTH
|
|
110
|
+
// the snake_case table name and the camelCase accessor as keys (snake_case
|
|
111
|
+
// wins on conflict); unlisted tables keep the default (warn on).
|
|
112
|
+
const warnOpt = options.warnOnUnlimited;
|
|
113
|
+
this.warnOnUnlimited =
|
|
114
|
+
typeof warnOpt === 'object' && warnOpt !== null
|
|
115
|
+
? (warnOpt[table] ?? warnOpt[snakeToCamel(table)]) !== false
|
|
116
|
+
: warnOpt !== false;
|
|
110
117
|
this.onQuery = options._onQuery;
|
|
111
118
|
}
|
|
112
119
|
// -------------------------------------------------------------------------
|
|
@@ -411,7 +418,7 @@ export class PowqlInterface {
|
|
|
411
418
|
const chunk = targetPks.slice(i, i + MAX_RELATION_KEYS);
|
|
412
419
|
const params = [];
|
|
413
420
|
const ph = chunk.map((v) => this.param(v, params)).join(', ');
|
|
414
|
-
const { rows } = await this.exec(`${through.table} filter .${targetJCol} in (${ph}) { .${sourceJCol} }`, params, timeout);
|
|
421
|
+
const { rows } = await this.exec(`${quotePowqlIdent(through.table)} filter .${targetJCol} in (${ph}) { .${sourceJCol} }`, params, timeout);
|
|
415
422
|
for (const r of rows) {
|
|
416
423
|
const v = r[sourceJCol];
|
|
417
424
|
if (v != null)
|
|
@@ -563,7 +570,7 @@ export class PowqlInterface {
|
|
|
563
570
|
}
|
|
564
571
|
const limitClause = limit !== undefined ? ` limit ${this.param(limit, params)}` : '';
|
|
565
572
|
const offsetClause = args.offset ? ` offset ${this.param(args.offset, params)}` : '';
|
|
566
|
-
const powql = `${this.
|
|
573
|
+
const powql = `${this.qt}${distinct}${filter}${order}${limitClause}${offsetClause} ${this.projection(cols)}`;
|
|
567
574
|
const { rows } = await this.exec(powql, params, args.timeout);
|
|
568
575
|
return rows;
|
|
569
576
|
}
|
|
@@ -712,7 +719,7 @@ export class PowqlInterface {
|
|
|
712
719
|
const chunk = parentKeys.slice(i, i + MAX_RELATION_KEYS);
|
|
713
720
|
const params = [];
|
|
714
721
|
const placeholders = chunk.map((v) => this.param(v, params)).join(', ');
|
|
715
|
-
const powql = `${through.table} filter .${sourceJCol} in (${placeholders}) { .${sourceJCol}, .${targetJCol} }`;
|
|
722
|
+
const powql = `${quotePowqlIdent(through.table)} filter .${sourceJCol} in (${placeholders}) { .${sourceJCol}, .${targetJCol} }`;
|
|
716
723
|
const { rows } = await this.exec(powql, params, timeout);
|
|
717
724
|
for (const row of rows) {
|
|
718
725
|
const sv = String(row[sourceJCol]);
|
|
@@ -794,6 +801,14 @@ export class PowqlInterface {
|
|
|
794
801
|
}
|
|
795
802
|
return out;
|
|
796
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* The table name as a PowQL type reference — backtick-quoted when it is a
|
|
806
|
+
* reserved word (e.g. a table named `order`). Used in every emitted
|
|
807
|
+
* statement; plain `this.table` stays in error messages.
|
|
808
|
+
*/
|
|
809
|
+
get qt() {
|
|
810
|
+
return quotePowqlIdent(this.table);
|
|
811
|
+
}
|
|
797
812
|
async create(args) {
|
|
798
813
|
return this.withMiddleware('create', args, async () => {
|
|
799
814
|
if (hasRelationFields(args.data, this.meta)) {
|
|
@@ -802,9 +817,11 @@ export class PowqlInterface {
|
|
|
802
817
|
const data = this.applyPkDefault(args.data);
|
|
803
818
|
const assigns = this.scalarData(data);
|
|
804
819
|
const params = [];
|
|
805
|
-
const body = assigns
|
|
820
|
+
const body = assigns
|
|
821
|
+
.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
|
|
822
|
+
.join(', ');
|
|
806
823
|
// `returning` surfaces the inserted row (all columns, schema order) in one round-trip.
|
|
807
|
-
const { rows } = await this.exec(`insert ${this.
|
|
824
|
+
const { rows } = await this.exec(`insert ${this.qt} { ${body} } returning`, params, args.timeout);
|
|
808
825
|
const row = rows.length ? this.shape(rows)[0] : null;
|
|
809
826
|
if (!row)
|
|
810
827
|
throw new NotFoundError({ table: this.table, where: data });
|
|
@@ -819,10 +836,10 @@ export class PowqlInterface {
|
|
|
819
836
|
const params = [];
|
|
820
837
|
const tuples = inputs.map((d) => {
|
|
821
838
|
const assigns = this.scalarData(d);
|
|
822
|
-
return `{ ${assigns.map((a) => `${a.col.name} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
|
|
839
|
+
return `{ ${assigns.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
|
|
823
840
|
});
|
|
824
841
|
// Multi-row insert with `returning` hands back every inserted row in one round-trip.
|
|
825
|
-
const { rows } = await this.exec(`insert ${this.
|
|
842
|
+
const { rows } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout);
|
|
826
843
|
return this.shape(rows);
|
|
827
844
|
});
|
|
828
845
|
}
|
|
@@ -837,7 +854,7 @@ export class PowqlInterface {
|
|
|
837
854
|
this.assertCompiledWhere(where, false, 'update');
|
|
838
855
|
const setClause = this.buildUpdateAssignments(args.data, params);
|
|
839
856
|
// `returning` hands back the post-update row(s); take the first (single-row contract).
|
|
840
|
-
const { rows } = await this.exec(`${this.
|
|
857
|
+
const { rows } = await this.exec(`${this.qt} filter ${where} update { ${setClause} } returning`, params, args.timeout);
|
|
841
858
|
const row = rows.length ? this.shape(rows)[0] : null;
|
|
842
859
|
if (!row)
|
|
843
860
|
throw new NotFoundError({ table: this.table, where: args.where });
|
|
@@ -852,7 +869,7 @@ export class PowqlInterface {
|
|
|
852
869
|
this.assertCompiledWhere(where, args.allowFullTableScan, 'updateMany');
|
|
853
870
|
const setClause = this.buildUpdateAssignments(args.data, params);
|
|
854
871
|
const filter = where ? ` filter ${where}` : '';
|
|
855
|
-
const { rowCount } = await this.exec(`${this.
|
|
872
|
+
const { rowCount } = await this.exec(`${this.qt}${filter} update { ${setClause} }`, params, args.timeout);
|
|
856
873
|
return { count: rowCount };
|
|
857
874
|
});
|
|
858
875
|
}
|
|
@@ -867,7 +884,7 @@ export class PowqlInterface {
|
|
|
867
884
|
}
|
|
868
885
|
const colMeta = this.column(field);
|
|
869
886
|
const ref = this.ref(field);
|
|
870
|
-
const col = colMeta.name;
|
|
887
|
+
const col = quotePowqlIdent(colMeta.name);
|
|
871
888
|
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
|
|
872
889
|
const opObj = value;
|
|
873
890
|
if ('set' in opObj)
|
|
@@ -925,21 +942,34 @@ export class PowqlInterface {
|
|
|
925
942
|
// drifts from `powdbDialect`; falls back to the literal lowercase keywords.
|
|
926
943
|
const d = this.options.dialect;
|
|
927
944
|
const client = await this.pool.connect();
|
|
945
|
+
let began = false;
|
|
928
946
|
try {
|
|
929
947
|
await client.query(d?.beginStatement?.() ?? 'begin');
|
|
948
|
+
began = true;
|
|
930
949
|
const { TransactionClient } = await import('./client.js');
|
|
931
950
|
const tx = new TransactionClient(client, this.schema, this.middlewares, this.options);
|
|
932
951
|
const ctx = { schema: this.schema, tx: tx };
|
|
933
|
-
|
|
952
|
+
// Plant the single-writer re-entrancy marker for the implicit tx's
|
|
953
|
+
// subtree (same seam TurbineClient.$transaction uses) — user code that
|
|
954
|
+
// fires db.$transaction from inside (e.g. $use middleware around a
|
|
955
|
+
// nested-write child op) must fast-fail E017, not queue into deadlock.
|
|
956
|
+
const wrap = client
|
|
957
|
+
.wrapTransactionCallback;
|
|
958
|
+
const result = await (wrap ? wrap(() => fn(ctx)) : fn(ctx));
|
|
934
959
|
await client.query(d?.commitStatement?.() ?? 'commit');
|
|
935
960
|
return result;
|
|
936
961
|
}
|
|
937
962
|
catch (err) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
963
|
+
// Only roll back a transaction we actually opened — a failed BEGIN
|
|
964
|
+
// (queue timeout, re-entrancy E017) must not emit a stray ROLLBACK that
|
|
965
|
+
// could land inside another transaction on a shared engine handle.
|
|
966
|
+
if (began) {
|
|
967
|
+
try {
|
|
968
|
+
await client.query(d?.rollbackStatement?.() ?? 'rollback');
|
|
969
|
+
}
|
|
970
|
+
catch {
|
|
971
|
+
/* best-effort — the connection may be gone */
|
|
972
|
+
}
|
|
943
973
|
}
|
|
944
974
|
throw err;
|
|
945
975
|
}
|
|
@@ -962,7 +992,7 @@ export class PowqlInterface {
|
|
|
962
992
|
const where = this.buildWhere(resolvedWhere, params);
|
|
963
993
|
this.assertCompiledWhere(where, false, 'delete');
|
|
964
994
|
// `returning` hands back the deleted row(s) — no separate pre-image reselect needed.
|
|
965
|
-
const { rows } = await this.exec(`${this.
|
|
995
|
+
const { rows } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout);
|
|
966
996
|
const row = rows.length ? this.shape(rows)[0] : null;
|
|
967
997
|
if (!row)
|
|
968
998
|
throw new NotFoundError({ table: this.table, where: args.where });
|
|
@@ -976,7 +1006,7 @@ export class PowqlInterface {
|
|
|
976
1006
|
const where = this.buildWhere(resolvedWhere, params);
|
|
977
1007
|
this.assertCompiledWhere(where, args.allowFullTableScan, 'deleteMany');
|
|
978
1008
|
const filter = where ? ` filter ${where}` : '';
|
|
979
|
-
const { rowCount } = await this.exec(`${this.
|
|
1009
|
+
const { rowCount } = await this.exec(`${this.qt}${filter} delete`, params, args.timeout);
|
|
980
1010
|
return { count: rowCount };
|
|
981
1011
|
});
|
|
982
1012
|
}
|
|
@@ -991,14 +1021,14 @@ export class PowqlInterface {
|
|
|
991
1021
|
}
|
|
992
1022
|
const params = [];
|
|
993
1023
|
const createBody = this.scalarData(createData)
|
|
994
|
-
.map((a) => `${a.col.name} := ${this.writeRef(a.value, a.col, params)}`)
|
|
1024
|
+
.map((a) => `${quotePowqlIdent(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
|
|
995
1025
|
.join(', ');
|
|
996
1026
|
const updateBody = this.buildUpdateAssignments(args.update, params);
|
|
997
1027
|
// PowDB 0.7.0's `upsert` statement does NOT accept a trailing `returning`
|
|
998
1028
|
// (verified: "unexpected trailing token … 'returning'"), because it is one
|
|
999
1029
|
// atomic insert-or-update, not two branches. So upsert alone keeps the
|
|
1000
1030
|
// reselect-by-PK fetch; create/update/delete all use `returning`.
|
|
1001
|
-
await this.exec(`upsert ${this.
|
|
1031
|
+
await this.exec(`upsert ${this.qt} on .${pkCol} { ${createBody} } on conflict { ${updateBody} }`, params, args.timeout);
|
|
1002
1032
|
const pkField = this.meta.reverseColumnMap[pkCol] ?? pkCol;
|
|
1003
1033
|
const row = await this.reselectByPk(createData[pkField], args.timeout);
|
|
1004
1034
|
if (!row)
|
|
@@ -1043,7 +1073,7 @@ export class PowqlInterface {
|
|
|
1043
1073
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
1044
1074
|
const where = this.buildWhere(resolvedWhere, params);
|
|
1045
1075
|
const filter = where ? ` filter ${where}` : '';
|
|
1046
|
-
const { rows } = await this.exec(`count(${this.
|
|
1076
|
+
const { rows } = await this.exec(`count(${this.qt}${filter})`, params, args.timeout);
|
|
1047
1077
|
return Number((rows[0]?.value ?? rows[0]?.count ?? 0));
|
|
1048
1078
|
});
|
|
1049
1079
|
}
|
|
@@ -1063,12 +1093,12 @@ export class PowqlInterface {
|
|
|
1063
1093
|
};
|
|
1064
1094
|
if (args._count) {
|
|
1065
1095
|
if (args._count === true) {
|
|
1066
|
-
result._count = (await scalar(`count(${this.
|
|
1096
|
+
result._count = (await scalar(`count(${this.qt}${filter})`)) ?? 0;
|
|
1067
1097
|
}
|
|
1068
1098
|
else {
|
|
1069
1099
|
const counts = {};
|
|
1070
1100
|
for (const field of Object.keys(args._count).filter((f) => args._count[f])) {
|
|
1071
|
-
counts[field] = (await scalar(`count(${this.
|
|
1101
|
+
counts[field] = (await scalar(`count(${this.qt}${filter} { ${this.ref(field)} })`)) ?? 0;
|
|
1072
1102
|
}
|
|
1073
1103
|
result._count = counts;
|
|
1074
1104
|
}
|
|
@@ -1080,7 +1110,7 @@ export class PowqlInterface {
|
|
|
1080
1110
|
const acc = {};
|
|
1081
1111
|
for (const field of Object.keys(spec).filter((f) => spec[f])) {
|
|
1082
1112
|
const powfn = fn.slice(1); // sum/avg/min/max
|
|
1083
|
-
acc[field] = await scalar(`${powfn}(${this.
|
|
1113
|
+
acc[field] = await scalar(`${powfn}(${this.qt}${filter} { ${this.ref(field)} })`);
|
|
1084
1114
|
}
|
|
1085
1115
|
result[fn] = acc;
|
|
1086
1116
|
}
|
|
@@ -1114,7 +1144,7 @@ export class PowqlInterface {
|
|
|
1114
1144
|
}
|
|
1115
1145
|
const having = this.buildHaving(args.having, params);
|
|
1116
1146
|
const order = this.buildOrder(args.orderBy);
|
|
1117
|
-
const powql = `${this.
|
|
1147
|
+
const powql = `${this.qt}${filter} group ${groupKeys.join(', ')}${having}${order} { ${proj.join(', ')} }`;
|
|
1118
1148
|
const { rows } = await this.exec(powql, params, args.timeout);
|
|
1119
1149
|
// Reshape: group keys → camel fields + coerced; aggregates → nested {_sum:{field}}.
|
|
1120
1150
|
return rows.map((raw) => {
|
package/dist/query/builder.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
55
55
|
/** Pre-computed column type lookups (avoids linear scans per query) */
|
|
56
56
|
private readonly columnPgTypeMap;
|
|
57
57
|
private readonly columnArrayTypeMap;
|
|
58
|
+
/**
|
|
59
|
+
* Columns whose type lives in a DIFFERENT schema than the introspected one
|
|
60
|
+
* (ColumnMetadata.pgTypeSchema is recorded only in that case) — such columns
|
|
61
|
+
* must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
|
|
62
|
+
*/
|
|
63
|
+
private readonly crossSchemaTypeColumns;
|
|
58
64
|
/** Tracks tables that have already triggered a deep-with warning (one-time) */
|
|
59
65
|
private readonly deepWithWarned;
|
|
60
66
|
/**
|
|
@@ -249,7 +255,10 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
249
255
|
* loop calling `db.users.findMany()` thousands of times only logs once.
|
|
250
256
|
* Suppressed when `defaultLimit` is configured (the caller has already
|
|
251
257
|
* opted in to a bounded query) and when the user passed an explicit
|
|
252
|
-
* `limit`, `take`, or `cursor`.
|
|
258
|
+
* `limit`, `take`, or `cursor`. A per-call `warnOnUnlimited` overrides the
|
|
259
|
+
* config-level setting in either direction (`false` silences a call that
|
|
260
|
+
* intentionally reads the full set; `true` forces the warning even when
|
|
261
|
+
* disabled in config).
|
|
253
262
|
*/
|
|
254
263
|
private maybeWarnUnlimited;
|
|
255
264
|
/**
|
|
@@ -427,17 +436,27 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
427
436
|
*/
|
|
428
437
|
private collectRelationFilterParams;
|
|
429
438
|
private collectRelFilterParams;
|
|
430
|
-
/**
|
|
439
|
+
/**
|
|
440
|
+
* Collect params from operator clauses. Mirrors buildOperatorClauses:
|
|
441
|
+
* {@link ColumnRef} values compile into the SQL text, so they push NOTHING -
|
|
442
|
+
* but they re-run the same validation (unknown ref / insensitive mode) so a
|
|
443
|
+
* warmed cache can never skip a check the build path enforces.
|
|
444
|
+
*/
|
|
431
445
|
private collectOperatorParams;
|
|
432
|
-
/**
|
|
446
|
+
/**
|
|
447
|
+
* Collect params from JSON filter. Mirrors buildJsonFilterClauses exactly:
|
|
448
|
+
* the `path` is bound at most once (its placeholder is shared by every
|
|
449
|
+
* extraction clause), then equals/contains/hasKey values, then the range
|
|
450
|
+
* comparison values in {@link JSON_RANGE_OPERATORS} order.
|
|
451
|
+
*/
|
|
433
452
|
private collectJsonFilterParams;
|
|
434
453
|
/** Collect params from array filter. Mirrors buildArrayFilterClauses. */
|
|
435
454
|
private collectArrayFilterParams;
|
|
436
455
|
/**
|
|
437
|
-
* Collect params for an orderBy clause.
|
|
438
|
-
*
|
|
439
|
-
* parameterless. Mirrors buildOrderBy's push
|
|
440
|
-
* param re-collection stays in lockstep.
|
|
456
|
+
* Collect params for an orderBy clause. Vector KNN ordering pushes the
|
|
457
|
+
* `$n::vector` query vector and JSON-path ordering pushes its text[] path;
|
|
458
|
+
* plain direction ordering is parameterless. Mirrors buildOrderBy's push
|
|
459
|
+
* order exactly so the cached-SQL param re-collection stays in lockstep.
|
|
441
460
|
*/
|
|
442
461
|
private collectOrderByParams;
|
|
443
462
|
/**
|
|
@@ -545,6 +564,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
545
564
|
* (relation targets, not just `this.table`).
|
|
546
565
|
*/
|
|
547
566
|
private pgTypeForColumn;
|
|
567
|
+
/**
|
|
568
|
+
* The Postgres enum type name for a column, when the schema knows one.
|
|
569
|
+
*
|
|
570
|
+
* Introspection stores each column's `udt_name` in `pgTypes` and every
|
|
571
|
+
* database enum in `schema.enums` (typname → labels); a column whose type
|
|
572
|
+
* matches an enum key needs an explicit `::"EnumName"` cast on its write
|
|
573
|
+
* binds — bulk-insert forms like `UNNEST($1::text[])` otherwise type the
|
|
574
|
+
* value as text and Postgres refuses the implicit text→enum coercion
|
|
575
|
+
* ("column X is of type Y but expression is of type text").
|
|
576
|
+
*
|
|
577
|
+
* Postgres-only by construction: gated on the active dialect being
|
|
578
|
+
* `postgresql` AND on `schema.enums` having entries (only PG introspection
|
|
579
|
+
* produces them — `defineSchema` and the other engines leave it empty), so
|
|
580
|
+
* SQLite/MySQL/MSSQL/PowDB output is byte-identical.
|
|
581
|
+
*/
|
|
582
|
+
private enumTypeForColumn;
|
|
583
|
+
/**
|
|
584
|
+
* `::"EnumName"` cast suffix for a write-bind placeholder on an enum
|
|
585
|
+
* column; `''` for every other column, so non-enum SQL stays byte-identical.
|
|
586
|
+
* The type name is an introspected identifier and is quoted via the dialect.
|
|
587
|
+
*/
|
|
588
|
+
private enumCastSuffix;
|
|
548
589
|
/**
|
|
549
590
|
* Equality-fallthrough guard shared by every SQL-build path AND every
|
|
550
591
|
* cache-hit param-collect path. A plain object literal that matched no known
|
|
@@ -575,9 +616,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
575
616
|
* differently-shaped wheres would share one cached SQL string.
|
|
576
617
|
*/
|
|
577
618
|
private fingerprintAliasWhere;
|
|
619
|
+
/**
|
|
620
|
+
* Validate a `{ col }` column reference against its table and return the
|
|
621
|
+
* resolved snake_case column name. Shared by the SQL-build path
|
|
622
|
+
* ({@link buildOperatorClauses}) and the cache-hit param-collect path
|
|
623
|
+
* (`collectOperatorParams`) so both always throw identically: a warmed
|
|
624
|
+
* cache can never skip the check.
|
|
625
|
+
*/
|
|
626
|
+
private resolveColumnRef;
|
|
627
|
+
/**
|
|
628
|
+
* Compile a `{ col }` reference to its quoted, prefix-matched SQL identifier.
|
|
629
|
+
* NO param is bound: the referenced column is part of the SQL text (and of
|
|
630
|
+
* the where fingerprint, see {@link fingerprintOperatorShape}).
|
|
631
|
+
*/
|
|
632
|
+
private columnRefSql;
|
|
578
633
|
/**
|
|
579
634
|
* Build SQL clauses for a single operator object on a column.
|
|
580
635
|
* Each operator key becomes its own clause, all ANDed together.
|
|
636
|
+
*
|
|
637
|
+
* `equals`/`not`/`gt`/`gte`/`lt`/`lte` also accept a {@link ColumnRef}
|
|
638
|
+
* (`{ col: 'otherField' }`) which compiles to a column-to-column comparison
|
|
639
|
+
* against `refCtx`: no param bound, so `collectOperatorParams` mirrors by
|
|
640
|
+
* pushing nothing and the referenced name lives in the fingerprint.
|
|
581
641
|
*/
|
|
582
642
|
private buildOperatorClauses;
|
|
583
643
|
/**
|
|
@@ -611,6 +671,33 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
611
671
|
* {@link UnsupportedFeatureError} (E017) instead of broken SQL.
|
|
612
672
|
*/
|
|
613
673
|
private nullsSuffix;
|
|
674
|
+
/**
|
|
675
|
+
* Resolve an orderBy key to its snake_case column via the table's columnMap
|
|
676
|
+
* (camelToSnake fallback), throwing the SAME unknown-field E003 the top-level
|
|
677
|
+
* where path uses. Shared by top-level JSON-path ordering and every nested
|
|
678
|
+
* relation orderBy path so nested orderBy accepts exactly what top-level
|
|
679
|
+
* accepts (the 0.30.x bug: nested orderBy skipped the columnMap and rejected
|
|
680
|
+
* camelCase-named DB columns like "sortOrder").
|
|
681
|
+
*/
|
|
682
|
+
private resolveOrderByColumn;
|
|
683
|
+
/**
|
|
684
|
+
* Validate a {@link JsonPathOrderBy} entry: column must exist AND be
|
|
685
|
+
* json/jsonb, path must be a non-empty array of keys/indexes: and return
|
|
686
|
+
* the resolved column. Shared by the SQL-build path
|
|
687
|
+
* ({@link buildJsonPathOrderEntry}) and the cache-hit param-collect mirrors
|
|
688
|
+
* so both always throw identically.
|
|
689
|
+
*/
|
|
690
|
+
private validateJsonPathOrderBy;
|
|
691
|
+
/**
|
|
692
|
+
* Compile one {@link JsonPathOrderBy} entry:
|
|
693
|
+
* `("col" #>> $n::text[])::numeric ASC`: the numeric cast only with
|
|
694
|
+
* `type: 'numeric'` (default is text comparison), the extraction routed
|
|
695
|
+
* through the dialect's JSON hook exactly like the JSON where-filters, the
|
|
696
|
+
* path bound as ONE text[] param (mirrored by the order-param collectors).
|
|
697
|
+
* `prefix` scopes the column (`''` top-level, `t0.` inside a relation
|
|
698
|
+
* subquery).
|
|
699
|
+
*/
|
|
700
|
+
private buildJsonPathOrderEntry;
|
|
614
701
|
/**
|
|
615
702
|
* Compile a relation ordering term. For a to-many relation the only allowed
|
|
616
703
|
* key is `_count`, which becomes a correlated `COUNT(*)` subquery. For a
|
|
@@ -619,8 +706,37 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
619
706
|
*
|
|
620
707
|
* Validation: relation must exist (E005); to-many only allows `_count`, and
|
|
621
708
|
* to-one only allows real target columns (E003).
|
|
709
|
+
*
|
|
710
|
+
* `ctx` generalizes the term beyond the root table: inside a relation
|
|
711
|
+
* subquery's orderBy the relations live on the TARGET table's metadata and
|
|
712
|
+
* the correlation parent is the relation's alias, not `this.table`.
|
|
622
713
|
*/
|
|
623
714
|
private buildRelationOrderBy;
|
|
715
|
+
/**
|
|
716
|
+
* Compile the ORDER BY terms of a relation `with` clause against the
|
|
717
|
+
* relation's table alias. One unified path for every relation shape
|
|
718
|
+
* (hasMany / manyToMany / belongsTo / hasOne) supporting exactly what the
|
|
719
|
+
* top-level orderBy accepts at this level:
|
|
720
|
+
*
|
|
721
|
+
* - scalar columns via columnMap resolution (camelToSnake fallback) with
|
|
722
|
+
* {@link OrderBySpec} nulls placement,
|
|
723
|
+
* - {@link JsonPathOrderBy} entries (path bound as one text[] param),
|
|
724
|
+
* - relation ordering on the TARGET's relations (`_count` for to-many, a
|
|
725
|
+
* target column for to-one), correlated to the relation alias,
|
|
726
|
+
* - vector KNN ordering stays top-level-only (E003, same as before).
|
|
727
|
+
*
|
|
728
|
+
* Param pushes (JSON paths, relation-order global filters) MUST be mirrored,
|
|
729
|
+
* in the same order, by {@link collectRelationOrderParams}.
|
|
730
|
+
*/
|
|
731
|
+
private buildRelationOrderClause;
|
|
732
|
+
/**
|
|
733
|
+
* Param-collect mirror of {@link buildRelationOrderClause}: JSON-path
|
|
734
|
+
* entries push their path (one text[] param each); relation-order entries
|
|
735
|
+
* mirror {@link collectOrderByParams}' relation branch (count / to-one
|
|
736
|
+
* global-filter params); scalar entries push nothing but re-run the same
|
|
737
|
+
* column validation so a warmed cache can never skip it.
|
|
738
|
+
*/
|
|
739
|
+
private collectRelationOrderParams;
|
|
624
740
|
/**
|
|
625
741
|
* Build a correlated `(SELECT COUNT(*) …)` scalar subquery for a to-many
|
|
626
742
|
* relation, correlated to `parentRef`. hasMany counts child rows via the FK;
|
|
@@ -899,11 +1015,32 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
899
1015
|
* E.g. '_text' → 'text', '_int4' → 'integer'
|
|
900
1016
|
*/
|
|
901
1017
|
private getArrayElementType;
|
|
1018
|
+
/**
|
|
1019
|
+
* Validate and enumerate the range comparisons (`gt`/`gte`/`lt`/`lte`) on a
|
|
1020
|
+
* JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
|
|
1021
|
+
* the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
|
|
1022
|
+
* param-collect path ({@link collectJsonFilterParams}) so both always agree
|
|
1023
|
+
* on which params are pushed — and both throw identically for invalid
|
|
1024
|
+
* shapes, so a warmed cache can never skip validation.
|
|
1025
|
+
*/
|
|
1026
|
+
private jsonRangeEntries;
|
|
902
1027
|
/**
|
|
903
1028
|
* Build SQL clauses for JSONB filter operators on a column.
|
|
904
|
-
* Supports: path, equals, contains, hasKey.
|
|
1029
|
+
* Supports: path, equals, contains, hasKey, gt, gte, lt, lte.
|
|
1030
|
+
*
|
|
1031
|
+
* The `path` param is bound at most once and its placeholder is shared by
|
|
1032
|
+
* every clause that extracts it (equals + range ops), so the param list
|
|
1033
|
+
* stays byte-identical to {@link collectJsonFilterParams}.
|
|
905
1034
|
*/
|
|
906
1035
|
private buildJsonFilterClauses;
|
|
1036
|
+
/**
|
|
1037
|
+
* Cast an extracted JSON path text value to a numeric type for range
|
|
1038
|
+
* comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
|
|
1039
|
+
* compare JSON numbers, and `::float` would lose precision on big ints);
|
|
1040
|
+
* other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
|
|
1041
|
+
* SQL Server have no `::` operator) as a float cast.
|
|
1042
|
+
*/
|
|
1043
|
+
private castJsonNumeric;
|
|
907
1044
|
/**
|
|
908
1045
|
* Build SQL clauses for Array filter operators on a column.
|
|
909
1046
|
* Supports: has, hasEvery, hasSome, isEmpty.
|