turbine-orm 0.76.0 → 0.77.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/dist/cjs/cli/index.js +2 -2
- package/dist/cjs/cli/migrate.js +8 -8
- package/dist/cjs/cli/studio.js +6 -3
- package/dist/cjs/client.js +10 -10
- package/dist/cjs/dialect.js +1 -1
- package/dist/cjs/errors.d.ts +2 -2
- package/dist/cjs/errors.js +21 -25
- package/dist/cjs/generate.js +1 -1
- package/dist/cjs/introspect.js +5 -5
- package/dist/cjs/mssql.js +12 -12
- package/dist/cjs/mysql.js +5 -5
- package/dist/cjs/nested-write.js +31 -31
- package/dist/cjs/observe.js +1 -1
- package/dist/cjs/pipeline-submittable.js +5 -1
- package/dist/cjs/pipeline.js +8 -0
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +4 -4
- package/dist/cjs/powdb-shared.d.ts +348 -0
- package/dist/cjs/powdb-shared.js +587 -0
- package/dist/cjs/powdb.d.ts +12 -299
- package/dist/cjs/powdb.js +106 -567
- package/dist/cjs/powql.d.ts +1 -1
- package/dist/cjs/powql.js +89 -89
- package/dist/cjs/prisma-compat.js +16 -16
- package/dist/cjs/query/aggregates.js +19 -20
- package/dist/cjs/query/batched-loader.js +8 -8
- package/dist/cjs/query/builder.js +8 -8
- package/dist/cjs/query/compound-unique.js +2 -2
- package/dist/cjs/query/filters.js +1 -1
- package/dist/cjs/query/relations.js +60 -48
- package/dist/cjs/query/types.js +4 -4
- package/dist/cjs/query/utils.js +5 -5
- package/dist/cjs/query/warn-registry.d.ts +7 -0
- package/dist/cjs/query/warn-registry.js +7 -0
- package/dist/cjs/query/where-compile.js +1 -1
- package/dist/cjs/query/where.js +30 -31
- package/dist/cjs/query/writes.js +7 -7
- package/dist/cjs/realtime.js +4 -4
- package/dist/cjs/schema-metadata.js +1 -1
- package/dist/cjs/schema-sql.js +3 -3
- package/dist/cjs/seed.js +1 -1
- package/dist/cjs/sqlite.js +7 -7
- package/dist/cjs/typed-sql.js +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/migrate.js +8 -8
- package/dist/cli/studio.js +6 -3
- package/dist/client.js +10 -10
- package/dist/dialect.js +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +21 -25
- package/dist/generate.js +1 -1
- package/dist/introspect.js +5 -5
- package/dist/mssql.js +12 -12
- package/dist/mysql.js +5 -5
- package/dist/nested-write.js +31 -31
- package/dist/observe.js +1 -1
- package/dist/pipeline-submittable.js +6 -2
- package/dist/pipeline.js +9 -1
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +2 -2
- package/dist/powdb-shared.d.ts +348 -0
- package/dist/powdb-shared.js +570 -0
- package/dist/powdb.d.ts +12 -299
- package/dist/powdb.js +71 -534
- package/dist/powql.d.ts +1 -1
- package/dist/powql.js +43 -43
- package/dist/prisma-compat.js +16 -16
- package/dist/query/aggregates.js +19 -20
- package/dist/query/batched-loader.js +8 -8
- package/dist/query/builder.js +8 -8
- package/dist/query/compound-unique.js +2 -2
- package/dist/query/filters.js +1 -1
- package/dist/query/relations.js +60 -48
- package/dist/query/types.js +4 -4
- package/dist/query/utils.js +5 -5
- package/dist/query/warn-registry.d.ts +7 -0
- package/dist/query/warn-registry.js +7 -0
- package/dist/query/where-compile.js +1 -1
- package/dist/query/where.js +30 -31
- package/dist/query/writes.js +7 -7
- package/dist/realtime.js +4 -4
- package/dist/schema-metadata.js +1 -1
- package/dist/schema-sql.js +3 -3
- package/dist/seed.js +1 -1
- package/dist/sqlite.js +7 -7
- package/dist/typed-sql.js +1 -1
- package/package.json +15 -7
package/dist/cjs/powql.js
CHANGED
|
@@ -73,7 +73,7 @@ exports.PowqlInterface = exports.MAX_POWQL_DATETIME_TERMS = void 0;
|
|
|
73
73
|
const node_crypto_1 = require("node:crypto");
|
|
74
74
|
const errors_js_1 = require("./errors.js");
|
|
75
75
|
const nested_write_js_1 = require("./nested-write.js");
|
|
76
|
-
const
|
|
76
|
+
const powdb_shared_js_1 = require("./powdb-shared.js");
|
|
77
77
|
const aggregates_js_1 = require("./query/aggregates.js");
|
|
78
78
|
const compound_unique_js_1 = require("./query/compound-unique.js");
|
|
79
79
|
const filters_js_1 = require("./query/filters.js");
|
|
@@ -225,7 +225,7 @@ class PowqlInterface {
|
|
|
225
225
|
this.options = options;
|
|
226
226
|
const meta = schema.tables[table];
|
|
227
227
|
if (!meta) {
|
|
228
|
-
throw new errors_js_1.ValidationError(`
|
|
228
|
+
throw new errors_js_1.ValidationError(`Unknown table "${table}". ${(0, utils_js_1.availableClause)(Object.keys(schema.tables), 'The schema has no tables.')}`);
|
|
229
229
|
}
|
|
230
230
|
this.meta = meta;
|
|
231
231
|
this.defaultLimit = options.defaultLimit;
|
|
@@ -258,7 +258,7 @@ class PowqlInterface {
|
|
|
258
258
|
const snake = resolved ?? (0, utils_js_1.ownLookup)(this.meta.columnMap, field) ?? field;
|
|
259
259
|
const col = this.meta.columns.find((c) => c.name === snake || c.field === field);
|
|
260
260
|
if (!col) {
|
|
261
|
-
throw new errors_js_1.ValidationError(`
|
|
261
|
+
throw new errors_js_1.ValidationError(`Unknown column "${field}" on table "${this.table}". Known: ${this.meta.columns
|
|
262
262
|
.map((c) => c.field)
|
|
263
263
|
.join(', ')}`);
|
|
264
264
|
}
|
|
@@ -325,7 +325,7 @@ class PowqlInterface {
|
|
|
325
325
|
* included, so no existing schema sees a different statement.
|
|
326
326
|
*/
|
|
327
327
|
colRefName(name, alias) {
|
|
328
|
-
return alias ? `${alias}.${(0,
|
|
328
|
+
return alias ? `${alias}.${(0, powdb_shared_js_1.quotePowqlIdent)(name)}` : `.${(0, powdb_shared_js_1.quotePowqlDotted)(name)}`;
|
|
329
329
|
}
|
|
330
330
|
/**
|
|
331
331
|
* Push a value into the param array and return its `$N` placeholder. When the
|
|
@@ -339,13 +339,13 @@ class PowqlInterface {
|
|
|
339
339
|
let tagged = value;
|
|
340
340
|
if (col && typeof value === 'number' && this.isFloatCol(col)) {
|
|
341
341
|
// Float column: force a float-form literal even for an integer value.
|
|
342
|
-
tagged = new
|
|
342
|
+
tagged = new powdb_shared_js_1.PowdbFloatParam(value);
|
|
343
343
|
}
|
|
344
|
-
else if (col && value !== null && typeof value === 'object' && !(value instanceof Date) && (0,
|
|
344
|
+
else if (col && value !== null && typeof value === 'object' && !(value instanceof Date) && (0, powdb_shared_js_1.isJsonColumn)(col)) {
|
|
345
345
|
// json document column: a JS object/array is serialized to canonical JSON
|
|
346
346
|
// text and stored as a json document (a JS string passes through raw, same
|
|
347
347
|
// contract as pg jsonb; `null` stays `null`).
|
|
348
|
-
tagged = new
|
|
348
|
+
tagged = new powdb_shared_js_1.PowdbJsonParam(value, col.name);
|
|
349
349
|
}
|
|
350
350
|
params.push(tagged);
|
|
351
351
|
return `$${params.length}`;
|
|
@@ -362,13 +362,13 @@ class PowqlInterface {
|
|
|
362
362
|
*/
|
|
363
363
|
writeRef(value, col, params) {
|
|
364
364
|
if (typeof value === 'number' && !Number.isFinite(value) && this.isFloatCol(col)) {
|
|
365
|
-
throw new errors_js_1.ValidationError(`
|
|
365
|
+
throw new errors_js_1.ValidationError(`Non-finite value for float column "${col.name}" on "${this.table}".`);
|
|
366
366
|
}
|
|
367
367
|
return this.param(value, params, col);
|
|
368
368
|
}
|
|
369
369
|
isFloatCol(col) {
|
|
370
370
|
try {
|
|
371
|
-
return (0,
|
|
371
|
+
return (0, powdb_shared_js_1.powqlColumnType)(col) === 'float';
|
|
372
372
|
}
|
|
373
373
|
catch {
|
|
374
374
|
return false;
|
|
@@ -381,7 +381,7 @@ class PowqlInterface {
|
|
|
381
381
|
* default so a hand-built test pool never crashes the version gates.
|
|
382
382
|
*/
|
|
383
383
|
get capabilities() {
|
|
384
|
-
return this.pool.capabilities ??
|
|
384
|
+
return this.pool.capabilities ?? powdb_shared_js_1.ALL_POWDB_CAPABILITIES;
|
|
385
385
|
}
|
|
386
386
|
/**
|
|
387
387
|
* The `limit` a query actually emits: the explicit `limit`, then the
|
|
@@ -488,7 +488,7 @@ class PowqlInterface {
|
|
|
488
488
|
['offset', offset],
|
|
489
489
|
]) {
|
|
490
490
|
if (value !== undefined && value !== null && value < 0) {
|
|
491
|
-
throw new errors_js_1.ValidationError(
|
|
491
|
+
throw new errors_js_1.ValidationError(`${context} on "${this.table}": \`${name}\` must not be negative (got ${value}).`);
|
|
492
492
|
}
|
|
493
493
|
}
|
|
494
494
|
}
|
|
@@ -538,7 +538,7 @@ class PowqlInterface {
|
|
|
538
538
|
// Relation filters are pre-resolved to scalar in/notIn by
|
|
539
539
|
// resolveRelationFilters() before buildWhere runs, reaching here means
|
|
540
540
|
// a caller skipped that step (an internal bug, not user error).
|
|
541
|
-
throw new errors_js_1.ValidationError(`
|
|
541
|
+
throw new errors_js_1.ValidationError(`internal: relation filter "${key}" reached buildWhere unresolved (missing resolveRelationFilters()).`);
|
|
542
542
|
}
|
|
543
543
|
else {
|
|
544
544
|
// A JsonFilter (or a bare `{ path }`) can compile to zero clauses; skip
|
|
@@ -581,9 +581,9 @@ class PowqlInterface {
|
|
|
581
581
|
* read through `introspectPowdbDatabase`, or hand-written metadata.
|
|
582
582
|
*/
|
|
583
583
|
assertDatetimePredicateSupported(col) {
|
|
584
|
-
if (!(0,
|
|
584
|
+
if (!(0, powdb_shared_js_1.isPowdbDatetimeColumn)(col))
|
|
585
585
|
return;
|
|
586
|
-
(0,
|
|
586
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'datetimeCompare', `comparisons on the PowDB datetime column "${col.name}"`, 'Reading that column is unaffected, and so are ordering, grouping and `is null` checks; only comparing it ' +
|
|
587
587
|
'against a bound timestamp needs the fix. A nested `with` (the default nested-projection / join paths) ' +
|
|
588
588
|
'correlates column to column, so it loads the relation without any such comparison.');
|
|
589
589
|
}
|
|
@@ -602,8 +602,8 @@ class PowqlInterface {
|
|
|
602
602
|
// path filters (≥ 0.12). `isJsonFilter` matches `path`/`equals`/`contains`/
|
|
603
603
|
// `hasKey`; on a NON-json column those fall through to the scalar operator
|
|
604
604
|
// path below (e.g. `equals` stays a plain equality), exactly like SQL.
|
|
605
|
-
if ((0,
|
|
606
|
-
(0,
|
|
605
|
+
if ((0, powdb_shared_js_1.isJsonColumn)(colMeta) && (0, filters_js_1.isJsonFilter)(value)) {
|
|
606
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'jsonDocs', 'JSON path filters');
|
|
607
607
|
return this.buildJsonPathCondition(colMeta, value, params, alias);
|
|
608
608
|
}
|
|
609
609
|
rejectUnsupportedFilter(op, field);
|
|
@@ -668,7 +668,7 @@ class PowqlInterface {
|
|
|
668
668
|
conds.push(`${lhs} like ${this.bindLike(`%${(0, utils_js_1.escapeLike)(String(opVal))}`, params, insensitive)}`);
|
|
669
669
|
break;
|
|
670
670
|
default:
|
|
671
|
-
throw new errors_js_1.ValidationError(`
|
|
671
|
+
throw new errors_js_1.ValidationError(`Unsupported operator "${opName}" on PowDB.`);
|
|
672
672
|
}
|
|
673
673
|
}
|
|
674
674
|
return conds.length > 1 ? `(${conds.join(' and ')})` : (conds[0] ?? this.alwaysFalse());
|
|
@@ -756,14 +756,14 @@ class PowqlInterface {
|
|
|
756
756
|
if (v === undefined)
|
|
757
757
|
continue;
|
|
758
758
|
if (filter.path === undefined) {
|
|
759
|
-
throw new errors_js_1.ValidationError(`
|
|
759
|
+
throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${col.name} requires a \`path\` ` +
|
|
760
760
|
`(e.g. { path: ['meta', 'score'], ${op}: ${JSON.stringify(v)} }).`);
|
|
761
761
|
}
|
|
762
762
|
if (typeof v !== 'number' && typeof v !== 'string') {
|
|
763
|
-
throw new errors_js_1.ValidationError(`
|
|
763
|
+
throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${col.name} requires a number or string, got ${JSON.stringify(v)}.`);
|
|
764
764
|
}
|
|
765
765
|
if (typeof v === 'number' && !Number.isFinite(v)) {
|
|
766
|
-
throw new errors_js_1.ValidationError(`
|
|
766
|
+
throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${col.name} requires a finite number.`);
|
|
767
767
|
}
|
|
768
768
|
conds.push(`${pathP()} ${powOp} ${this.param(v, params)}`);
|
|
769
769
|
}
|
|
@@ -779,7 +779,7 @@ class PowqlInterface {
|
|
|
779
779
|
// generator bug rather than a user error, and must not silently widen the
|
|
780
780
|
// query the way it used to.
|
|
781
781
|
if (!conds.length) {
|
|
782
|
-
throw new errors_js_1.ValidationError(`
|
|
782
|
+
throw new errors_js_1.ValidationError(`internal: JSON filter on "${col.name}" compiled to no condition. This is a bug in turbine-orm.`);
|
|
783
783
|
}
|
|
784
784
|
return conds.length > 1 ? `(${conds.join(' and ')})` : conds[0];
|
|
785
785
|
}
|
|
@@ -805,7 +805,7 @@ class PowqlInterface {
|
|
|
805
805
|
// missing-value row to match `notIn []`, so NO presence guard is appended here).
|
|
806
806
|
return negate ? '(1 = 1)' : this.alwaysFalse();
|
|
807
807
|
}
|
|
808
|
-
if (col && (0,
|
|
808
|
+
if (col && (0, powdb_shared_js_1.isPowdbDatetimeColumn)(col))
|
|
809
809
|
return this.buildDatetimeInList(lhs, values, params, negate, col);
|
|
810
810
|
const items = values.map((v) => this.bind(v, params, insensitive)).join(', ');
|
|
811
811
|
if (negate) {
|
|
@@ -871,7 +871,7 @@ class PowqlInterface {
|
|
|
871
871
|
* identically however the statement was assembled.
|
|
872
872
|
*/
|
|
873
873
|
inClause(ref, values, params, col) {
|
|
874
|
-
if (col && (0,
|
|
874
|
+
if (col && (0, powdb_shared_js_1.isPowdbDatetimeColumn)(col)) {
|
|
875
875
|
this.assertDatetimePredicateSupported(col);
|
|
876
876
|
return this.buildDatetimeInList(ref, values, params, false, col);
|
|
877
877
|
}
|
|
@@ -885,7 +885,7 @@ class PowqlInterface {
|
|
|
885
885
|
*/
|
|
886
886
|
keyChunkSize(meta, colName) {
|
|
887
887
|
const col = meta?.columns.find((c) => c.name === colName);
|
|
888
|
-
return col && (0,
|
|
888
|
+
return col && (0, powdb_shared_js_1.isPowdbDatetimeColumn)(col) ? exports.MAX_POWQL_DATETIME_TERMS : MAX_RELATION_KEYS;
|
|
889
889
|
}
|
|
890
890
|
/**
|
|
891
891
|
* Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
|
|
@@ -945,7 +945,7 @@ class PowqlInterface {
|
|
|
945
945
|
}
|
|
946
946
|
const targetMeta = this.schema.tables[rel.to];
|
|
947
947
|
if (!targetMeta)
|
|
948
|
-
throw new errors_js_1.ValidationError(`
|
|
948
|
+
throw new errors_js_1.ValidationError(`Relation "${rel.name}" targets unknown table "${rel.to}".`);
|
|
949
949
|
// hasMany/hasOne: localKey = our referenceKey, collect the child's foreignKey.
|
|
950
950
|
// belongsTo: localKey = our foreignKey, collect the target's referenceKey.
|
|
951
951
|
const localCol = rel.type === 'belongsTo' ? fk[0] : rk[0];
|
|
@@ -974,13 +974,13 @@ class PowqlInterface {
|
|
|
974
974
|
async resolveManyToManyCondition(rel, mode, innerWhere, innerEmpty, timeout) {
|
|
975
975
|
const through = rel.through;
|
|
976
976
|
if (!through)
|
|
977
|
-
throw new errors_js_1.ValidationError(`
|
|
977
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${rel.name}" is missing its junction (\`through\`).`);
|
|
978
978
|
const sourceJ = (0, schema_js_1.normalizeKeyColumns)(through.sourceKey);
|
|
979
979
|
const targetJ = (0, schema_js_1.normalizeKeyColumns)(through.targetKey);
|
|
980
980
|
const sourceRef = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
|
|
981
981
|
const targetMeta = this.schema.tables[rel.to];
|
|
982
982
|
if (!targetMeta)
|
|
983
|
-
throw new errors_js_1.ValidationError(`
|
|
983
|
+
throw new errors_js_1.ValidationError(`Relation "${rel.name}" targets unknown table "${rel.to}".`);
|
|
984
984
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
985
985
|
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}", PowQL has no tuple-\`in\` for composite junction/target keys`);
|
|
986
986
|
}
|
|
@@ -1012,7 +1012,7 @@ class PowqlInterface {
|
|
|
1012
1012
|
const chunk = targetPks.slice(i, i + junctionChunk);
|
|
1013
1013
|
const params = [];
|
|
1014
1014
|
const keyClause = this.inClause(this.colRefName(targetJCol), chunk, params, targetJColMeta);
|
|
1015
|
-
const { rows } = await this.exec(`${(0,
|
|
1015
|
+
const { rows } = await this.exec(`${(0, powdb_shared_js_1.quotePowqlIdent)(through.table)} filter ${keyClause} { ${this.colRefName(sourceJCol)} }`, params, timeout, 'findMany');
|
|
1016
1016
|
for (const r of rows) {
|
|
1017
1017
|
const v = r[sourceJCol];
|
|
1018
1018
|
if (v != null)
|
|
@@ -1289,10 +1289,10 @@ class PowqlInterface {
|
|
|
1289
1289
|
/** Compile one {@link JsonPathOrderBy} entry to `order .col->$n asc` (+ optional numeric cast). */
|
|
1290
1290
|
buildJsonPathOrder(field, spec, params, alias) {
|
|
1291
1291
|
const col = this.column(field);
|
|
1292
|
-
if (!(0,
|
|
1292
|
+
if (!(0, powdb_shared_js_1.isJsonColumn)(col)) {
|
|
1293
1293
|
throw new errors_js_1.UnsupportedFeatureError('JSON-path ordering', 'PowDB', `field "${field}" is not a json column`);
|
|
1294
1294
|
}
|
|
1295
|
-
(0,
|
|
1295
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'jsonDocs', 'JSON path ordering');
|
|
1296
1296
|
if (spec.nulls === 'first') {
|
|
1297
1297
|
throw new errors_js_1.UnsupportedFeatureError('NULLS FIRST placement', 'PowDB', `field "${field}": PowDB orders missing / JSON-null keys LAST in both directions`);
|
|
1298
1298
|
}
|
|
@@ -1318,9 +1318,9 @@ class PowqlInterface {
|
|
|
1318
1318
|
}
|
|
1319
1319
|
/** Build the E018 refusal for a write / `begin` on a read-only pool. */
|
|
1320
1320
|
readOnlyError(operation) {
|
|
1321
|
-
// Pass a clean detail: the ReadOnlyError constructor owns
|
|
1322
|
-
//
|
|
1323
|
-
//
|
|
1321
|
+
// Pass a clean detail: the ReadOnlyError constructor owns the "Route writes
|
|
1322
|
+
// to a writable primary." hint, so adding it here would double it. The
|
|
1323
|
+
// `[TURBINE_E018]` tag is added once, later, by formatErrorMessage.
|
|
1324
1324
|
return new errors_js_1.ReadOnlyError(`${operation} on "${this.table}" refused: this PowDB connection is read-only.`);
|
|
1325
1325
|
}
|
|
1326
1326
|
/**
|
|
@@ -1382,7 +1382,7 @@ class PowqlInterface {
|
|
|
1382
1382
|
return false;
|
|
1383
1383
|
if (!POWQL_READ_ACTIONS.has(action))
|
|
1384
1384
|
return false;
|
|
1385
|
-
return (0,
|
|
1385
|
+
return (0, powdb_shared_js_1.isStaleFramePowdbError)(err);
|
|
1386
1386
|
}
|
|
1387
1387
|
emit(action, sql, params, duration, rows, error) {
|
|
1388
1388
|
if (!this.onQuery)
|
|
@@ -1428,7 +1428,7 @@ class PowqlInterface {
|
|
|
1428
1428
|
* coerces its string cells correctly. Defaults to the pool capability for the
|
|
1429
1429
|
* rare caller with no per-result flag (hand-built test pools). */
|
|
1430
1430
|
shape(rows, native = Boolean(this.capabilities.nativeRaw)) {
|
|
1431
|
-
return rows.map((r) => (0,
|
|
1431
|
+
return rows.map((r) => (0, powdb_shared_js_1.rowToEntity)(r, this.meta, native));
|
|
1432
1432
|
}
|
|
1433
1433
|
// -------------------------------------------------------------------------
|
|
1434
1434
|
// Reads
|
|
@@ -1556,7 +1556,7 @@ class PowqlInterface {
|
|
|
1556
1556
|
// Aliased scan: every parent column keys itself (`col: t0.col`) so the
|
|
1557
1557
|
// result columns keep their bare snake names, then the nested blocks.
|
|
1558
1558
|
const aliasCtr = { n: 1 };
|
|
1559
|
-
const parts = cols.map((c) => `${(0,
|
|
1559
|
+
const parts = cols.map((c) => `${(0, powdb_shared_js_1.quotePowqlIdent)(c)}: t0.${(0, powdb_shared_js_1.quotePowqlIdent)(c)}`);
|
|
1560
1560
|
for (const plan of nestedPlans) {
|
|
1561
1561
|
parts.push(await this.buildNestedBlock(plan, 't0', aliasCtr, params, args.timeout));
|
|
1562
1562
|
}
|
|
@@ -1687,7 +1687,7 @@ class PowqlInterface {
|
|
|
1687
1687
|
*/
|
|
1688
1688
|
async loadRelations(parents, withClause, timeout, depth = 0, parent, includePii = false) {
|
|
1689
1689
|
if (depth >= 10) {
|
|
1690
|
-
throw new errors_js_1.ValidationError(`
|
|
1690
|
+
throw new errors_js_1.ValidationError(`Nested 'with' on PowDB exceeded depth 10 (relation cycle?).`);
|
|
1691
1691
|
}
|
|
1692
1692
|
if (!parents.length)
|
|
1693
1693
|
return;
|
|
@@ -1702,7 +1702,7 @@ class PowqlInterface {
|
|
|
1702
1702
|
continue;
|
|
1703
1703
|
const rel = this.meta.relations[relName];
|
|
1704
1704
|
if (!rel)
|
|
1705
|
-
throw new errors_js_1.ValidationError(`
|
|
1705
|
+
throw new errors_js_1.ValidationError(`Unknown relation "${relName}" on "${this.table}".`);
|
|
1706
1706
|
if (strategyIsJoin && parent && this.joinEligible(rel, opt, parent.args, parents.length)) {
|
|
1707
1707
|
if (this.capabilities.serverJoins) {
|
|
1708
1708
|
await this.loadRelationViaJoin(parents, rel, relName, opt, parent, timeout, includePii);
|
|
@@ -1713,7 +1713,7 @@ class PowqlInterface {
|
|
|
1713
1713
|
// E017; a CLIENT-LEVEL default silently falls back to the keyed loaders
|
|
1714
1714
|
// (so pointing an existing app at an older engine keeps working).
|
|
1715
1715
|
if (parent.args.relationLoadStrategy === 'join') {
|
|
1716
|
-
(0,
|
|
1716
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'serverJoins', 'native PowQL relation joins');
|
|
1717
1717
|
}
|
|
1718
1718
|
}
|
|
1719
1719
|
if (rel.type === 'manyToMany') {
|
|
@@ -1829,13 +1829,13 @@ class PowqlInterface {
|
|
|
1829
1829
|
async loadManyToMany(parents, rel, relName, opt, timeout, includePii = false) {
|
|
1830
1830
|
const through = rel.through;
|
|
1831
1831
|
if (!through)
|
|
1832
|
-
throw new errors_js_1.ValidationError(`
|
|
1832
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relName}" is missing its junction (\`through\`).`);
|
|
1833
1833
|
const sourceJ = (0, schema_js_1.normalizeKeyColumns)(through.sourceKey);
|
|
1834
1834
|
const targetJ = (0, schema_js_1.normalizeKeyColumns)(through.targetKey);
|
|
1835
1835
|
const sourceRef = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
|
|
1836
1836
|
const targetMeta = this.schema.tables[rel.to];
|
|
1837
1837
|
if (!targetMeta)
|
|
1838
|
-
throw new errors_js_1.ValidationError(`
|
|
1838
|
+
throw new errors_js_1.ValidationError(`Relation "${relName}" targets unknown table "${rel.to}".`);
|
|
1839
1839
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
|
|
1840
1840
|
throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}", PowQL has no tuple-\`in\`, so composite junction/target keys can't be loaded`);
|
|
1841
1841
|
}
|
|
@@ -1864,7 +1864,7 @@ class PowqlInterface {
|
|
|
1864
1864
|
const chunk = parentKeys.slice(i, i + junctionChunk);
|
|
1865
1865
|
const params = [];
|
|
1866
1866
|
const keyClause = this.inClause(this.colRefName(sourceJCol), chunk, params, sourceJColMeta);
|
|
1867
|
-
const powql = `${(0,
|
|
1867
|
+
const powql = `${(0, powdb_shared_js_1.quotePowqlIdent)(through.table)} filter ${keyClause} ` +
|
|
1868
1868
|
`{ ${this.colRefName(sourceJCol)}, ${this.colRefName(targetJCol)} }`;
|
|
1869
1869
|
const { rows } = await this.exec(powql, params, timeout, 'findMany');
|
|
1870
1870
|
for (const row of rows) {
|
|
@@ -2069,7 +2069,7 @@ class PowqlInterface {
|
|
|
2069
2069
|
const options = (opt === true ? {} : opt);
|
|
2070
2070
|
const targetMeta = this.schema.tables[rel.to];
|
|
2071
2071
|
if (!targetMeta)
|
|
2072
|
-
throw new errors_js_1.ValidationError(`
|
|
2072
|
+
throw new errors_js_1.ValidationError(`Relation "${relName}" targets unknown table "${rel.to}".`);
|
|
2073
2073
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
2074
2074
|
const fk = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
|
|
2075
2075
|
const rk = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
|
|
@@ -2087,9 +2087,9 @@ class PowqlInterface {
|
|
|
2087
2087
|
this.assertPagination(options.limit, options.offset, `relation "${relName}"`);
|
|
2088
2088
|
const limitClause = options.limit !== undefined ? ` limit ${this.param(options.limit, params)}` : '';
|
|
2089
2089
|
const offsetClause = options.offset ? ` offset ${this.param(options.offset, params)}` : '';
|
|
2090
|
-
const proj = this.joinProjection(childCols, `p.${(0,
|
|
2090
|
+
const proj = this.joinProjection(childCols, `p.${(0, powdb_shared_js_1.quotePowqlIdent)(parentKeyCol)}`, 'c');
|
|
2091
2091
|
const powql = `${targetQi.qt} as c join ${this.qt} as p ` +
|
|
2092
|
-
`on c.${(0,
|
|
2092
|
+
`on c.${(0, powdb_shared_js_1.quotePowqlIdent)(childKeyCol)} = p.${(0, powdb_shared_js_1.quotePowqlIdent)(parentKeyCol)}` +
|
|
2093
2093
|
`${filter}${order}${limitClause}${offsetClause} ${proj}`;
|
|
2094
2094
|
// A READ: thread a read-shaped action through the exec seam.
|
|
2095
2095
|
const { rows, native } = await targetQi.exec(powql, params, timeout, 'findMany');
|
|
@@ -2109,11 +2109,11 @@ class PowqlInterface {
|
|
|
2109
2109
|
async loadManyToManyViaJoin(parents, rel, relName, opt, parent, timeout, includePii = false) {
|
|
2110
2110
|
const through = rel.through;
|
|
2111
2111
|
if (!through)
|
|
2112
|
-
throw new errors_js_1.ValidationError(`
|
|
2112
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relName}" is missing its junction (\`through\`).`);
|
|
2113
2113
|
const options = (opt === true ? {} : opt);
|
|
2114
2114
|
const targetMeta = this.schema.tables[rel.to];
|
|
2115
2115
|
if (!targetMeta)
|
|
2116
|
-
throw new errors_js_1.ValidationError(`
|
|
2116
|
+
throw new errors_js_1.ValidationError(`Relation "${relName}" targets unknown table "${rel.to}".`);
|
|
2117
2117
|
const targetQi = new PowqlInterface(this.pool, rel.to, this.schema, [], this.options);
|
|
2118
2118
|
const sourceJCol = (0, schema_js_1.normalizeKeyColumns)(through.sourceKey)[0];
|
|
2119
2119
|
const targetJCol = (0, schema_js_1.normalizeKeyColumns)(through.targetKey)[0];
|
|
@@ -2123,10 +2123,10 @@ class PowqlInterface {
|
|
|
2123
2123
|
const params = [];
|
|
2124
2124
|
const { cols: childCols, forcedPk: childForcedPk } = this.joinChildCols(targetQi, options, includePii);
|
|
2125
2125
|
const filter = await this.joinFilter(targetQi, parent.resolvedWhere, options.where, 't', params, options.timeout ?? timeout);
|
|
2126
|
-
const proj = this.joinProjection(childCols, `j.${(0,
|
|
2126
|
+
const proj = this.joinProjection(childCols, `j.${(0, powdb_shared_js_1.quotePowqlIdent)(sourceJCol)}`, 't');
|
|
2127
2127
|
const powql = `${targetQi.qt} as t ` +
|
|
2128
|
-
`join ${(0,
|
|
2129
|
-
`join ${this.qt} as p on j.${(0,
|
|
2128
|
+
`join ${(0, powdb_shared_js_1.quotePowqlIdent)(through.table)} as j on t.${(0, powdb_shared_js_1.quotePowqlIdent)(targetPkCol)} = j.${(0, powdb_shared_js_1.quotePowqlIdent)(targetJCol)} ` +
|
|
2129
|
+
`join ${this.qt} as p on j.${(0, powdb_shared_js_1.quotePowqlIdent)(sourceJCol)} = p.${(0, powdb_shared_js_1.quotePowqlIdent)(sourceRefCol)}` +
|
|
2130
2130
|
`${filter} ${proj}`;
|
|
2131
2131
|
const { rows, native } = await targetQi.exec(powql, params, timeout, 'findMany');
|
|
2132
2132
|
const byKey = this.bucketByTpk(targetQi, rows, native, childForcedPk);
|
|
@@ -2143,7 +2143,7 @@ class PowqlInterface {
|
|
|
2143
2143
|
joinChildCols(targetQi, options, includePii = false) {
|
|
2144
2144
|
const plan = targetQi.projectionPlan(options.select, options.omit, includePii);
|
|
2145
2145
|
if (plan.cols.includes('__tpk')) {
|
|
2146
|
-
throw new errors_js_1.ValidationError(`
|
|
2146
|
+
throw new errors_js_1.ValidationError(`relation target "${targetQi.table}" has a column named "__tpk", which collides with the reserved ` +
|
|
2147
2147
|
`join correlation alias. Rename the column or load this relation with relationLoadStrategy: 'batched'.`);
|
|
2148
2148
|
}
|
|
2149
2149
|
return plan;
|
|
@@ -2156,7 +2156,7 @@ class PowqlInterface {
|
|
|
2156
2156
|
joinProjection(childCols, tpkExpr, childAlias) {
|
|
2157
2157
|
const parts = [
|
|
2158
2158
|
`__tpk: ${tpkExpr}`,
|
|
2159
|
-
...childCols.map((c) => `${(0,
|
|
2159
|
+
...childCols.map((c) => `${(0, powdb_shared_js_1.quotePowqlIdent)(c)}: ${childAlias}.${(0, powdb_shared_js_1.quotePowqlIdent)(c)}`),
|
|
2160
2160
|
];
|
|
2161
2161
|
return `{ ${parts.join(', ')} }`;
|
|
2162
2162
|
}
|
|
@@ -2278,7 +2278,7 @@ class PowqlInterface {
|
|
|
2278
2278
|
const { cols, forcedPk } = targetQi.projectionPlan(options.select, options.omit, includePii);
|
|
2279
2279
|
const byName = new Map(targetQi.meta.columns.map((c) => [c.name, c]));
|
|
2280
2280
|
for (const c of cols) {
|
|
2281
|
-
const ts = (byName.get(c)?.tsType ?? '')
|
|
2281
|
+
const ts = (0, powdb_shared_js_1.baseTsType)(byName.get(c)?.tsType ?? '');
|
|
2282
2282
|
if (ts === 'bigint' || ts === 'Uint8Array')
|
|
2283
2283
|
return null;
|
|
2284
2284
|
}
|
|
@@ -2326,7 +2326,7 @@ class PowqlInterface {
|
|
|
2326
2326
|
// Exactly one equi-correlation predicate (the 0.18 correlation rule); the
|
|
2327
2327
|
// child where chains behind it as a parenthesized `and` group, which the
|
|
2328
2328
|
// grammar accepts even when the group itself contains `or`.
|
|
2329
|
-
const correlation = `${alias}.${(0,
|
|
2329
|
+
const correlation = `${alias}.${(0, powdb_shared_js_1.quotePowqlIdent)(childKeyCol)} = ${parentAlias}.${(0, powdb_shared_js_1.quotePowqlIdent)(parentKeyCol)}`;
|
|
2330
2330
|
const resolved = await targetQi.resolveRelationFilters(options.where, timeout);
|
|
2331
2331
|
const childWhere = targetQi.buildWhere(resolved, params, alias);
|
|
2332
2332
|
const filter = childWhere ? `${correlation} and (${childWhere})` : correlation;
|
|
@@ -2337,11 +2337,11 @@ class PowqlInterface {
|
|
|
2337
2337
|
? ` limit ${this.param(options.limit, params)}`
|
|
2338
2338
|
: '';
|
|
2339
2339
|
const offsetClause = !single && options.offset ? ` offset ${this.param(options.offset, params)}` : '';
|
|
2340
|
-
const parts = plan.cols.map((c) => `${(0,
|
|
2340
|
+
const parts = plan.cols.map((c) => `${(0, powdb_shared_js_1.quotePowqlIdent)(c)}: ${alias}.${(0, powdb_shared_js_1.quotePowqlIdent)(c)}`);
|
|
2341
2341
|
for (const child of plan.children) {
|
|
2342
2342
|
parts.push(await targetQi.buildNestedBlock(child, alias, aliasCtr, params, timeout));
|
|
2343
2343
|
}
|
|
2344
|
-
return (`${(0,
|
|
2344
|
+
return (`${(0, powdb_shared_js_1.quotePowqlIdent)(relName)}: ${targetQi.qt} as ${alias} ` +
|
|
2345
2345
|
`filter ${filter}${order}${limitClause}${offsetClause} { ${parts.join(', ')} }`);
|
|
2346
2346
|
}
|
|
2347
2347
|
/**
|
|
@@ -2370,7 +2370,7 @@ class PowqlInterface {
|
|
|
2370
2370
|
}
|
|
2371
2371
|
}
|
|
2372
2372
|
const items = Array.isArray(value) ? value : [];
|
|
2373
|
-
const shaped = items.map((o) => (0,
|
|
2373
|
+
const shaped = items.map((o) => (0, powdb_shared_js_1.rowToEntity)(o, plan.targetQi.meta, true));
|
|
2374
2374
|
for (const child of shaped) {
|
|
2375
2375
|
for (const sub of plan.children)
|
|
2376
2376
|
plan.targetQi.attachOneNested(child, sub);
|
|
@@ -2415,7 +2415,7 @@ class PowqlInterface {
|
|
|
2415
2415
|
}
|
|
2416
2416
|
/** True when `name` is a bare PowQL identifier (quoting leaves it unchanged). */
|
|
2417
2417
|
isBareIdent(name) {
|
|
2418
|
-
return (0,
|
|
2418
|
+
return (0, powdb_shared_js_1.quotePowqlIdent)(name) === name;
|
|
2419
2419
|
}
|
|
2420
2420
|
/**
|
|
2421
2421
|
* Find the declared to-one link on THIS table that matches `rel` exactly: same
|
|
@@ -2485,7 +2485,7 @@ class PowqlInterface {
|
|
|
2485
2485
|
// at least one projected child column is bigint/bytes. Otherwise nested
|
|
2486
2486
|
// projections already handle it (and cache), so leave it to them.
|
|
2487
2487
|
const hasCarrierBlockedCol = userCols.some((c) => {
|
|
2488
|
-
const ts = (byName.get(c)?.tsType ?? '')
|
|
2488
|
+
const ts = (0, powdb_shared_js_1.baseTsType)(byName.get(c)?.tsType ?? '');
|
|
2489
2489
|
return ts === 'bigint' || ts === 'Uint8Array';
|
|
2490
2490
|
});
|
|
2491
2491
|
if (!hasCarrierBlockedCol)
|
|
@@ -2530,8 +2530,8 @@ class PowqlInterface {
|
|
|
2530
2530
|
* parse exactly like their bare forms and yield the same result-column names.
|
|
2531
2531
|
*/
|
|
2532
2532
|
linkPathFields(plan, parentAlias) {
|
|
2533
|
-
const link = (0,
|
|
2534
|
-
return plan.cols.map((c) => `${(0,
|
|
2533
|
+
const link = (0, powdb_shared_js_1.quotePowqlDotted)(plan.linkName);
|
|
2534
|
+
return plan.cols.map((c) => `${(0, powdb_shared_js_1.quotePowqlIdent)(`${plan.keyPrefix}${c}`)}: ${parentAlias}.${link}.${(0, powdb_shared_js_1.quotePowqlDotted)(c)}`);
|
|
2535
2535
|
}
|
|
2536
2536
|
/**
|
|
2537
2537
|
* Reconstruct each link-path relation's child entity from its flat hop fields
|
|
@@ -2553,7 +2553,7 @@ class PowqlInterface {
|
|
|
2553
2553
|
raw[c] = row[`${plan.keyPrefix}${c}`];
|
|
2554
2554
|
delete row[`${plan.keyPrefix}${c}`];
|
|
2555
2555
|
}
|
|
2556
|
-
const child = (0,
|
|
2556
|
+
const child = (0, powdb_shared_js_1.rowToEntity)(raw, plan.targetQi.meta, native);
|
|
2557
2557
|
if (child[pkField] == null) {
|
|
2558
2558
|
row[plan.relName] = null;
|
|
2559
2559
|
continue;
|
|
@@ -2610,7 +2610,7 @@ class PowqlInterface {
|
|
|
2610
2610
|
* statement; plain `this.table` stays in error messages.
|
|
2611
2611
|
*/
|
|
2612
2612
|
get qt() {
|
|
2613
|
-
return (0,
|
|
2613
|
+
return (0, powdb_shared_js_1.quotePowqlIdent)(this.table);
|
|
2614
2614
|
}
|
|
2615
2615
|
async create(args) {
|
|
2616
2616
|
return this.withMiddleware('create', args, async () => {
|
|
@@ -2621,7 +2621,7 @@ class PowqlInterface {
|
|
|
2621
2621
|
const assigns = this.scalarData(data);
|
|
2622
2622
|
const params = [];
|
|
2623
2623
|
const body = assigns
|
|
2624
|
-
.map((a) => `${(0,
|
|
2624
|
+
.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
|
|
2625
2625
|
.join(', ');
|
|
2626
2626
|
// `returning` surfaces the inserted row (all columns, schema order) in one round-trip.
|
|
2627
2627
|
const { rows, native } = await this.exec(`insert ${this.qt} { ${body} } returning`, params, args.timeout, 'create');
|
|
@@ -2669,7 +2669,7 @@ class PowqlInterface {
|
|
|
2669
2669
|
parts.push(`does not supply ${quoteList(missing)}`);
|
|
2670
2670
|
if (unexpected.length > 0)
|
|
2671
2671
|
parts.push(`supplies ${quoteList(unexpected)}, which the first row does not`);
|
|
2672
|
-
throw new errors_js_1.ValidationError(`
|
|
2672
|
+
throw new errors_js_1.ValidationError(`createMany on "${this.table}": row ${i} ${parts.join(' and ')}. ` +
|
|
2673
2673
|
'Every row must supply the same fields (a field set to `undefined` counts as omitted, exactly as it does ' +
|
|
2674
2674
|
'in `create`). PowQL itself would insert the ragged rows, but the SQL engines build ONE statement whose ' +
|
|
2675
2675
|
"column list comes from the first row, so there a later row's extra field is dropped and a field it " +
|
|
@@ -2691,7 +2691,7 @@ class PowqlInterface {
|
|
|
2691
2691
|
const params = [];
|
|
2692
2692
|
const tuples = inputs.map((d) => {
|
|
2693
2693
|
const assigns = this.scalarData(d);
|
|
2694
|
-
return `{ ${assigns.map((a) => `${(0,
|
|
2694
|
+
return `{ ${assigns.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`).join(', ')} }`;
|
|
2695
2695
|
});
|
|
2696
2696
|
// Multi-row insert with `returning` hands back every inserted row in one round-trip.
|
|
2697
2697
|
const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
|
|
@@ -2721,7 +2721,7 @@ class PowqlInterface {
|
|
|
2721
2721
|
// by a caller who believed they held the lock.
|
|
2722
2722
|
const lock = args.optimisticLock;
|
|
2723
2723
|
if (lock) {
|
|
2724
|
-
const versionCol = (0,
|
|
2724
|
+
const versionCol = (0, powdb_shared_js_1.quotePowqlIdent)(this.column(lock.field).name);
|
|
2725
2725
|
setClause += `, ${versionCol} := ${this.ref(lock.field)} + 1`;
|
|
2726
2726
|
params.push(lock.expected);
|
|
2727
2727
|
const check = `${this.ref(lock.field)} = $${params.length}`;
|
|
@@ -2767,7 +2767,7 @@ class PowqlInterface {
|
|
|
2767
2767
|
}
|
|
2768
2768
|
const colMeta = this.column(field);
|
|
2769
2769
|
const ref = this.ref(field);
|
|
2770
|
-
const col = (0,
|
|
2770
|
+
const col = (0, powdb_shared_js_1.quotePowqlIdent)(colMeta.name);
|
|
2771
2771
|
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
|
|
2772
2772
|
const opObj = value;
|
|
2773
2773
|
if ('set' in opObj)
|
|
@@ -2781,14 +2781,14 @@ class PowqlInterface {
|
|
|
2781
2781
|
else if ('divide' in opObj)
|
|
2782
2782
|
parts.push(`${col} := ${ref} / ${this.writeRef(opObj.divide, colMeta, params)}`);
|
|
2783
2783
|
else
|
|
2784
|
-
throw new errors_js_1.ValidationError(`
|
|
2784
|
+
throw new errors_js_1.ValidationError(`Unsupported update operator on "${field}".`);
|
|
2785
2785
|
}
|
|
2786
2786
|
else {
|
|
2787
2787
|
parts.push(`${col} := ${this.writeRef(value, colMeta, params)}`);
|
|
2788
2788
|
}
|
|
2789
2789
|
}
|
|
2790
2790
|
if (!parts.length)
|
|
2791
|
-
throw new errors_js_1.ValidationError(`
|
|
2791
|
+
throw new errors_js_1.ValidationError(`update on "${this.table}" has no fields to set.`);
|
|
2792
2792
|
return parts.join(', ');
|
|
2793
2793
|
}
|
|
2794
2794
|
// -------------------------------------------------------------------------
|
|
@@ -2918,7 +2918,7 @@ class PowqlInterface {
|
|
|
2918
2918
|
}
|
|
2919
2919
|
const params = [];
|
|
2920
2920
|
const createBody = this.scalarData(createData)
|
|
2921
|
-
.map((a) => `${(0,
|
|
2921
|
+
.map((a) => `${(0, powdb_shared_js_1.quotePowqlIdent)(a.col.name)} := ${this.writeRef(a.value, a.col, params)}`)
|
|
2922
2922
|
.join(', ');
|
|
2923
2923
|
const updateBody = this.buildUpdateAssignments(args.update, params);
|
|
2924
2924
|
// PowDB 0.7.0's `upsert` statement does NOT accept a trailing `returning`
|
|
@@ -2950,7 +2950,7 @@ class PowqlInterface {
|
|
|
2950
2950
|
return { field, value: createData[field] ?? createData[pk] };
|
|
2951
2951
|
});
|
|
2952
2952
|
if (pkPairs.some((p) => p.value == null)) {
|
|
2953
|
-
throw new errors_js_1.ValidationError(`
|
|
2953
|
+
throw new errors_js_1.ValidationError(`upsert on "${this.table}" needs every composite-PK field in \`create\` (${pkPairs
|
|
2954
2954
|
.map((p) => p.field)
|
|
2955
2955
|
.join(', ')}).`);
|
|
2956
2956
|
}
|
|
@@ -3003,7 +3003,7 @@ class PowqlInterface {
|
|
|
3003
3003
|
const col = this.column(field);
|
|
3004
3004
|
if (!col.nullable)
|
|
3005
3005
|
return;
|
|
3006
|
-
(0,
|
|
3006
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'projectedCountNonNull', `per-field \`_count\` of the nullable column "${col.name}"`, 'Below that version the engine ignored the column projection and returned the ROW count, which differs from ' +
|
|
3007
3007
|
'every SQL engine exactly when the column is nullable. `_count: true` (a row count) and a per-field ' +
|
|
3008
3008
|
'`_count` of a NOT NULL column are correct on every version and are never refused.');
|
|
3009
3009
|
}
|
|
@@ -3068,7 +3068,7 @@ class PowqlInterface {
|
|
|
3068
3068
|
return spec !== undefined && Object.values(spec).some((v) => v != null && typeof v === 'object');
|
|
3069
3069
|
});
|
|
3070
3070
|
if (usesJson) {
|
|
3071
|
-
(0,
|
|
3071
|
+
(0, powdb_shared_js_1.requireCapability)(this.capabilities, 'jsonDocs', 'JSON-path groupBy keys / aggregate targets');
|
|
3072
3072
|
}
|
|
3073
3073
|
// `emitNative` decides whether the query GENERATION needs the legacy-wire
|
|
3074
3074
|
// `json_type` discriminator (pool-level capability). The DECODE side reads
|
|
@@ -3086,7 +3086,7 @@ class PowqlInterface {
|
|
|
3086
3086
|
const usedKeys = new Set();
|
|
3087
3087
|
const claim = (key, what) => {
|
|
3088
3088
|
if (key === '_count' || usedKeys.has(key)) {
|
|
3089
|
-
throw new errors_js_1.ValidationError(`
|
|
3089
|
+
throw new errors_js_1.ValidationError(`groupBy output name "${key}" (${what}) collides with another output column on table ` +
|
|
3090
3090
|
`"${this.table}": set an explicit \`alias\` (or rename the aggregate key) to disambiguate.`);
|
|
3091
3091
|
}
|
|
3092
3092
|
usedKeys.add(key);
|
|
@@ -3116,8 +3116,8 @@ class PowqlInterface {
|
|
|
3116
3116
|
}
|
|
3117
3117
|
else {
|
|
3118
3118
|
const col = this.column(entry.field);
|
|
3119
|
-
if (!(0,
|
|
3120
|
-
throw new errors_js_1.ValidationError(`
|
|
3119
|
+
if (!(0, powdb_shared_js_1.isJsonColumn)(col)) {
|
|
3120
|
+
throw new errors_js_1.ValidationError(`groupBy JSON group key on "${entry.field}" (table "${this.table}") requires a json column.`);
|
|
3121
3121
|
}
|
|
3122
3122
|
(0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, entry.field, col.name, 'groupBy JSON `by` key', (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
|
|
3123
3123
|
this.assertJsonPath('group key', entry.field, entry.path);
|
|
@@ -3222,8 +3222,8 @@ class PowqlInterface {
|
|
|
3222
3222
|
}
|
|
3223
3223
|
else {
|
|
3224
3224
|
const col = this.column(target.field);
|
|
3225
|
-
if (!(0,
|
|
3226
|
-
throw new errors_js_1.ValidationError(`
|
|
3225
|
+
if (!(0, powdb_shared_js_1.isJsonColumn)(col)) {
|
|
3226
|
+
throw new errors_js_1.ValidationError(`groupBy ${fn} target "${key}" on "${target.field}" (table "${this.table}") requires a json column.`);
|
|
3227
3227
|
}
|
|
3228
3228
|
if (fn === '_min' || fn === '_max') {
|
|
3229
3229
|
(0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, target.field, col.name, `groupBy ${fn} JSON target`, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
|
|
@@ -3231,7 +3231,7 @@ class PowqlInterface {
|
|
|
3231
3231
|
this.assertJsonPath(`${fn} target "${key}"`, target.field, target.path);
|
|
3232
3232
|
const alwaysNumeric = fn === '_sum' || fn === '_avg';
|
|
3233
3233
|
if (alwaysNumeric && target.type === 'text') {
|
|
3234
|
-
throw new errors_js_1.ValidationError(`
|
|
3234
|
+
throw new errors_js_1.ValidationError(`groupBy ${fn} target "${key}" on table "${this.table}": ` +
|
|
3235
3235
|
`${fn} over a JSON path is always numeric: remove \`type: 'text'\`.`);
|
|
3236
3236
|
}
|
|
3237
3237
|
const numeric = alwaysNumeric || target.type === 'numeric';
|
|
@@ -3270,7 +3270,7 @@ class PowqlInterface {
|
|
|
3270
3270
|
if (r.kind === 'plain') {
|
|
3271
3271
|
const cell = raw[r.rowKey];
|
|
3272
3272
|
out[r.resultKey] = resultNative
|
|
3273
|
-
? (0,
|
|
3273
|
+
? (0, powdb_shared_js_1.coerceNativeValue)(cell, r.col)
|
|
3274
3274
|
: typeof cell === 'string'
|
|
3275
3275
|
? coerceScalar(cell, r.col.tsType)
|
|
3276
3276
|
: cell;
|
|
@@ -3299,7 +3299,7 @@ class PowqlInterface {
|
|
|
3299
3299
|
if (!Array.isArray(path) ||
|
|
3300
3300
|
path.length === 0 ||
|
|
3301
3301
|
path.some((el) => typeof el !== 'string' && !(typeof el === 'number' && Number.isFinite(el)))) {
|
|
3302
|
-
throw new errors_js_1.ValidationError(`
|
|
3302
|
+
throw new errors_js_1.ValidationError(`groupBy ${context} on "${field}" (table "${this.table}") requires a non-empty \`path\` ` +
|
|
3303
3303
|
`array of keys/indexes (e.g. { field: '${field}', path: ['category'] }).`);
|
|
3304
3304
|
}
|
|
3305
3305
|
}
|
|
@@ -3351,7 +3351,7 @@ class PowqlInterface {
|
|
|
3351
3351
|
// must come from the fixed allowlist, never from the caller's key.
|
|
3352
3352
|
const fn = POWQL_HAVING_AGG_FNS.get(aggKey);
|
|
3353
3353
|
if (!fn) {
|
|
3354
|
-
throw new errors_js_1.ValidationError(`
|
|
3354
|
+
throw new errors_js_1.ValidationError(`Unknown aggregate "${aggKey}" in having for field "${key}" on table "${this.table}". ` +
|
|
3355
3355
|
`Supported: ${[...POWQL_HAVING_AGG_FNS.keys()].join(', ')}.`);
|
|
3356
3356
|
}
|
|
3357
3357
|
const inner = this.lookupGroupKey(aggInner, key) ?? this.ref(key);
|
|
@@ -3389,14 +3389,14 @@ class PowqlInterface {
|
|
|
3389
3389
|
if (key === '_count') {
|
|
3390
3390
|
const expr = aggOrderExprs.get('_count');
|
|
3391
3391
|
if (!expr) {
|
|
3392
|
-
throw new errors_js_1.ValidationError(`
|
|
3392
|
+
throw new errors_js_1.ValidationError(`Cannot order groupBy by "_count" on table "${this.table}": _count is not selected. ` +
|
|
3393
3393
|
`Orderable keys: ${validKeys()}.`);
|
|
3394
3394
|
}
|
|
3395
3395
|
parts.push(`${expr} ${this.groupOrderDir(value, '_count')}`);
|
|
3396
3396
|
continue;
|
|
3397
3397
|
}
|
|
3398
3398
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
3399
|
-
throw new errors_js_1.ValidationError(`
|
|
3399
|
+
throw new errors_js_1.ValidationError(`Invalid groupBy orderBy for "${key}" on table "${this.table}": ` +
|
|
3400
3400
|
`expected a field map like { ${key}: { amount: 'desc' } }.`);
|
|
3401
3401
|
}
|
|
3402
3402
|
for (const [field, dirSpec] of Object.entries(value)) {
|
|
@@ -3406,7 +3406,7 @@ class PowqlInterface {
|
|
|
3406
3406
|
// the registry is keyed by the canonical one.
|
|
3407
3407
|
const expr = aggOrderExprs.get(`${key}:${field}`) ?? this.aggOrderExpr(aggOrderExprs, key, field);
|
|
3408
3408
|
if (!expr) {
|
|
3409
|
-
throw new errors_js_1.ValidationError(`
|
|
3409
|
+
throw new errors_js_1.ValidationError(`Cannot order groupBy by "${key}.${field}" on table "${this.table}": ` +
|
|
3410
3410
|
`that aggregate is not requested in this call. Orderable keys: ${validKeys()}.`);
|
|
3411
3411
|
}
|
|
3412
3412
|
parts.push(`${expr} ${this.groupOrderDir(dirSpec, `${key}.${field}`)}`);
|
|
@@ -3415,7 +3415,7 @@ class PowqlInterface {
|
|
|
3415
3415
|
}
|
|
3416
3416
|
const expr = this.lookupGroupKey(byOrderExprs, key);
|
|
3417
3417
|
if (!expr) {
|
|
3418
|
-
throw new errors_js_1.ValidationError(`
|
|
3418
|
+
throw new errors_js_1.ValidationError(`Unknown field "${key}" in groupBy orderBy on table "${this.table}". Orderable keys: ${validKeys()}.`);
|
|
3419
3419
|
}
|
|
3420
3420
|
parts.push(`${expr} ${this.groupOrderDir(value, key)}`);
|
|
3421
3421
|
}
|
|
@@ -3505,7 +3505,7 @@ class PowqlInterface {
|
|
|
3505
3505
|
return;
|
|
3506
3506
|
if (compiledWhere.length > 0)
|
|
3507
3507
|
return;
|
|
3508
|
-
throw new errors_js_1.ValidationError(
|
|
3508
|
+
throw new errors_js_1.ValidationError(`${action} on "${this.table}" refused: the \`where\` clause is empty. ` +
|
|
3509
3509
|
"Pass `allowFullTableScan: UNSAFE` to opt in (import { UNSAFE } from 'turbine-orm'), " +
|
|
3510
3510
|
'or check that your filter values are defined.');
|
|
3511
3511
|
}
|
|
@@ -3513,7 +3513,7 @@ class PowqlInterface {
|
|
|
3513
3513
|
exports.PowqlInterface = PowqlInterface;
|
|
3514
3514
|
/** Coerce a group-key scalar string by the column's TS type (numbers/bools). */
|
|
3515
3515
|
function coerceScalar(raw, tsType) {
|
|
3516
|
-
const ts =
|
|
3516
|
+
const ts = (0, powdb_shared_js_1.baseTsType)(tsType);
|
|
3517
3517
|
if (raw === 'null')
|
|
3518
3518
|
return null;
|
|
3519
3519
|
if (ts === 'number' || ts === 'bigint') {
|