uql-orm 0.81.0 → 0.83.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 +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/cockroachdb/crdbQuerierPool.js +2 -2
- package/dist/dialect/abstractSqlDialect.d.ts +16 -6
- package/dist/dialect/abstractSqlDialect.js +110 -41
- package/dist/dialect/hydrateColumn.js +2 -12
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -0
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
- package/dist/dialect/pgLikeSqlDialect.js +13 -4
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/maria/mariadbQuerierPool.js +4 -2
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +20 -10
- package/dist/migrate/builder/tableBuilder.js +1 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
- package/dist/migrate/ddl/pgIndexDdl.js +2 -1
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.js +2 -1
- package/dist/migrate/generator/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +3 -2
- package/dist/migrate/introspection/mssqlIntrospector.js +13 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +6 -2
- package/dist/migrate/introspection/postgresIntrospector.js +1 -1
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.js +2 -1
- package/dist/migrate/schemaGenerator.js +5 -4
- package/dist/migrate/storage/databaseStorage.js +1 -1
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +7 -10
- package/dist/mssql/mssqlQuerier.d.ts +2 -0
- package/dist/mssql/mssqlQuerier.js +8 -5
- package/dist/mysql/mysql2QuerierPool.d.ts +1 -0
- package/dist/mysql/mysql2QuerierPool.js +20 -2
- package/dist/neon/neonQuerierPool.js +2 -2
- package/dist/pglite/pgliteQuerierPool.js +10 -4
- package/dist/postgres/pgQuerierPool.js +2 -2
- package/dist/postgres/{pgNumericTypes.d.ts → pgWireTypes.d.ts} +4 -3
- package/dist/postgres/{pgNumericTypes.js → pgWireTypes.js} +7 -3
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- package/dist/schema/canonicalType.d.ts +3 -0
- package/dist/schema/canonicalType.js +31 -9
- package/dist/schema/schemaASTBuilder.js +2 -1
- package/dist/schema/schemaASTDiffer.js +4 -2
- package/dist/sqlite/sqliteDialect.d.ts +1 -3
- package/dist/sqlite/sqliteDialect.js +3 -6
- package/dist/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +16 -12
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/date.d.ts +11 -0
- package/dist/util/date.js +19 -0
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/fieldOption.util.d.ts +5 -3
- package/dist/util/fieldOption.util.js +6 -5
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.d.ts +8 -1
- package/dist/util/sqlLiteral.js +14 -9
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +4 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
2
2
|
import { COUNT_RESULT_KEY, parseQueryLock, QueryRaw, RAW_ALIAS, } from '../type/index.js';
|
|
3
3
|
import { isInlinedExpression } from '../util/field.util.js';
|
|
4
|
-
import { asSelectMap, assertNonNegativeInteger, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, columnFamily, countedRelations, fieldUpdateOf, fulltextIndexOver, fulltextWeights, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorKey, isVectorSearch, normalizeScalarFieldSelection, parentJoins, rankedTextSearch, targetKeyColumns, textSearchFields, textSortOf, textWeightSteps, parseGroupMap, parseRelationAtKey, parseRelationSize, populatesRelations, aggregateOf, raw, refs, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
|
+
import { asSelectMap, assertNonNegativeInteger, assertWhere, definedEntries, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, columnFamily, countedRelations, fieldUpdateOf, fulltextIndexOver, fulltextWeights, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorKey, isVectorSearch, normalizeScalarFieldSelection, parentJoins, rankedTextSearch, targetKeyColumns, textSearchFields, textSortOf, textWeightSteps, parseGroupMap, parseRelationAtKey, parseRelationSize, populatesRelations, aggregateOf, raw, refs, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
5
|
import { escapeAnsiSqlLiteral } from '../util/sqlLiteral.js';
|
|
6
6
|
import { kindOf, UqlUsageError } from '../util/uqlError.js';
|
|
7
7
|
import { AGGREGATE_PAGE_ALIAS, AGGREGATE_VALUE_ALIAS, ROWS_ALIAS, JSON_ELEM_ALIAS, JSON_PULL_ALIAS, relationSortColumn, } from './aliases.js';
|
|
8
8
|
import { holdsOperator, isJsonScalar, jsonCompareMode, jsonElemExists, jsonPath, } from './jsonSql.js';
|
|
9
|
-
import { betweenBounds, COMPARE_OPS, EQUALITY_OPS, GROUP_OPS, groupClauses, inOperands, isGroupOp, isOrderedOp, isVectorQuery, LIKE_OPS, ORDERED_OPS, VECTOR_QUERY_KEY_SET, whereOperators, } from './operators.js';
|
|
9
|
+
import { betweenBounds, COMPARE_OPS, EQUALITY_OPS, GROUP_OPS, groupClauses, inOperands, isGroupOp, isOrderedOp, isVectorQuery, LIKE_OPS, namesRows, ORDERED_OPS, VECTOR_QUERY_KEY_SET, whereOperators, } from './operators.js';
|
|
10
10
|
import { SqlQueryContext } from './queryContext.js';
|
|
11
11
|
import { groupPathField, NO_JOINS, aggregateColumnField, resolveGroupJoins, resolveQueryJoins, relationSortTerms, resolveSortableJoin, } from './queryJoins.js';
|
|
12
12
|
import { resolveVectorCast } from './vectorCast.js';
|
|
@@ -34,6 +34,13 @@ function orRefuse(value, refusal) {
|
|
|
34
34
|
}
|
|
35
35
|
return value;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether an assignment builds on what each row before it left: `$inc`, `$mul`, `$push`. A set-based
|
|
39
|
+
* `UPDATE` writes a target once however many rows of its set match it, so it would apply one of them.
|
|
40
|
+
*/
|
|
41
|
+
function accumulates(value) {
|
|
42
|
+
return isFieldUpdateOp(value) || (isJsonUpdateOp(value) && value.$push !== undefined);
|
|
43
|
+
}
|
|
37
44
|
/** Conditions joined by `AND`, parenthesized where there is more than one. */
|
|
38
45
|
function conjunction(parts) {
|
|
39
46
|
return parts.length > 1 ? `(${parts.join(' AND ')})` : parts.join('');
|
|
@@ -178,7 +185,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
178
185
|
returningIdExpression(meta) {
|
|
179
186
|
const [idKey] = meta.ids;
|
|
180
187
|
return meta.ids.length === 1
|
|
181
|
-
? `${this.returnedRowPrefix}${this.
|
|
188
|
+
? `${this.returnedRowPrefix}${this.escapedColumnName(meta, idKey)} ${this.escapeId('id')}`
|
|
182
189
|
: '';
|
|
183
190
|
}
|
|
184
191
|
search(ctx, entity, q = {}, opts = {}, joins = NO_JOINS, order) {
|
|
@@ -423,7 +430,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
423
430
|
join.relation.references.forEach((reference, index) => {
|
|
424
431
|
if (index > 0)
|
|
425
432
|
ctx.append(' AND ');
|
|
426
|
-
const foreign = this.
|
|
433
|
+
const foreign = this.escapedColumnName(join.meta, reference.foreign);
|
|
427
434
|
// Two calls rather than one over a union: the parent is either another join's entity or the
|
|
428
435
|
// queried one, and their metadata types have nothing in common.
|
|
429
436
|
const local = this.escapeId(join.parent ? this.columnOf(join.parent.meta, reference.local) : this.columnOf(meta, reference.local));
|
|
@@ -1254,30 +1261,99 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1254
1261
|
const meta = getMeta(entity);
|
|
1255
1262
|
const [filledPayload] = fillOnFields(meta, payload, 'onUpdate');
|
|
1256
1263
|
const keys = filterFieldKeys(meta, filledPayload, 'onUpdate');
|
|
1257
|
-
|
|
1258
|
-
ctx.append(`UPDATE ${tableName} SET `);
|
|
1264
|
+
ctx.append(`UPDATE ${this.escapedTableName(meta)} SET `);
|
|
1259
1265
|
for (let i = 0; i < keys.length; i++) {
|
|
1260
1266
|
if (i > 0) {
|
|
1261
1267
|
ctx.append(', ');
|
|
1262
1268
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1269
|
+
this.appendAssignment(ctx, meta, keys[i], filledPayload[keys[i]]);
|
|
1270
|
+
}
|
|
1271
|
+
this.search(ctx, entity, q, opts);
|
|
1272
|
+
}
|
|
1273
|
+
/** `col = value`, a JSON operator or a `$inc`/`$mul` spelled as the engine's. */
|
|
1274
|
+
appendAssignment(ctx, meta, key, value) {
|
|
1275
|
+
const field = meta.fields[key];
|
|
1276
|
+
const escapedCol = this.escapedColumnName(meta, key);
|
|
1277
|
+
if (isJsonUpdateOp(value)) {
|
|
1278
|
+
this.formatJsonUpdate(ctx, escapedCol, value, field);
|
|
1279
|
+
}
|
|
1280
|
+
else if (isFieldUpdateOp(value)) {
|
|
1281
|
+
const [op, operand] = fieldUpdateOf(key, value);
|
|
1282
|
+
ctx.append(`${escapedCol} = COALESCE(${escapedCol}, 0) ${SQL_ARITHMETIC[op]} `);
|
|
1283
|
+
ctx.addValue(operand);
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
ctx.append(`${escapedCol} = `);
|
|
1287
|
+
this.formatPersistableValue(ctx, field, value);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* A write in a trigger's body. Only the columns it names, none filled in JavaScript - that would bake
|
|
1292
|
+
* one value into the trigger - no id read back, and no entity filter, which a request resolves and a
|
|
1293
|
+
* trigger has none of. `rows` is the `FROM` a set-based engine's body reads its rows through.
|
|
1294
|
+
*/
|
|
1295
|
+
triggerWrite(ctx, write, rows) {
|
|
1296
|
+
const meta = getMeta(write.entity);
|
|
1297
|
+
const table = this.escapedTableName(meta);
|
|
1298
|
+
if (write.kind === 'insert') {
|
|
1299
|
+
const unfilled = definedEntries(meta.fields).find(([key, field]) => field.onInsert !== undefined && field.defaultValue === undefined && write.row[key] === undefined);
|
|
1300
|
+
if (unfilled) {
|
|
1301
|
+
throw new UqlUsageError(`'${meta.name}.${unfilled[0]}' is filled on insert by uql, which a trigger does not run: ` +
|
|
1302
|
+
'name it, or give it a defaultValue');
|
|
1274
1303
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1304
|
+
const entries = this.writtenEntries(meta, write.row);
|
|
1305
|
+
const columns = entries.map(([key]) => this.escapedColumnName(meta, key)).join(', ');
|
|
1306
|
+
const [open, close] = rows ? ['SELECT ', ` ${rows};`] : ['VALUES (', ');'];
|
|
1307
|
+
ctx.append(`INSERT INTO ${table} (${columns}) ${open}`);
|
|
1308
|
+
entries.forEach(([key, value], i) => {
|
|
1309
|
+
if (i > 0) {
|
|
1310
|
+
ctx.append(', ');
|
|
1311
|
+
}
|
|
1312
|
+
this.formatPersistableValue(ctx, meta.fields[key], value);
|
|
1313
|
+
});
|
|
1314
|
+
ctx.append(close);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
assertWhere(meta, write.where);
|
|
1318
|
+
if (!namesRows(write.where)) {
|
|
1319
|
+
throw new UqlUsageError(`a trigger's ${write.kind} over '${meta.name}' names no rows, so it would address every one`);
|
|
1320
|
+
}
|
|
1321
|
+
if (write.kind === 'update') {
|
|
1322
|
+
const entries = this.writtenEntries(meta, write.set);
|
|
1323
|
+
const accumulating = rows && entries.find(([, value]) => accumulates(value));
|
|
1324
|
+
if (accumulating) {
|
|
1325
|
+
throw new UqlUsageError(`'${meta.name}.${accumulating[0]}' cannot accumulate per row in a trigger fired once per statement, ` +
|
|
1326
|
+
'which applies $inc, $mul and $push once');
|
|
1278
1327
|
}
|
|
1328
|
+
ctx.append(`UPDATE ${table} SET `);
|
|
1329
|
+
entries.forEach(([key, value], i) => {
|
|
1330
|
+
if (i > 0) {
|
|
1331
|
+
ctx.append(', ');
|
|
1332
|
+
}
|
|
1333
|
+
this.appendAssignment(ctx, meta, key, value);
|
|
1334
|
+
});
|
|
1279
1335
|
}
|
|
1280
|
-
|
|
1336
|
+
else {
|
|
1337
|
+
ctx.append(`DELETE FROM ${table}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (rows) {
|
|
1340
|
+
ctx.append(` ${rows}`);
|
|
1341
|
+
}
|
|
1342
|
+
// Qualified by the table: on a set-based engine `inserted` holds the same column names.
|
|
1343
|
+
this.renderWhere(ctx, write.entity, write.where, { escapedPrefix: `${table}.` });
|
|
1344
|
+
ctx.append(';');
|
|
1345
|
+
}
|
|
1346
|
+
/** The values a write names, refusing one the entity has no column for. */
|
|
1347
|
+
writtenEntries(meta, values) {
|
|
1348
|
+
const entries = Object.entries(values).filter(([, value]) => value !== undefined);
|
|
1349
|
+
const unknown = entries.find(([key]) => !meta.fields[key]);
|
|
1350
|
+
if (unknown) {
|
|
1351
|
+
throw new UqlUsageError(`'${meta.name}' has no field '${unknown[0]}' for a trigger to write`);
|
|
1352
|
+
}
|
|
1353
|
+
if (!entries.length) {
|
|
1354
|
+
throw new UqlUsageError(`a trigger's write to '${meta.name}' names no field`);
|
|
1355
|
+
}
|
|
1356
|
+
return entries;
|
|
1281
1357
|
}
|
|
1282
1358
|
/**
|
|
1283
1359
|
* `INSERT ... ON CONFLICT ... DO UPDATE/NOTHING RETURNING`. The assignments are built before the insert
|
|
@@ -1312,23 +1388,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1312
1388
|
return fields
|
|
1313
1389
|
.filter((col) => !conflictPaths[col])
|
|
1314
1390
|
.map((col) => {
|
|
1315
|
-
const
|
|
1316
|
-
const columnName = this.resolveColumnName(col, field);
|
|
1391
|
+
const column = this.escapedColumnName(meta, col);
|
|
1317
1392
|
if (Object.hasOwn(sample, col)) {
|
|
1318
|
-
return `${
|
|
1393
|
+
return `${column} = ${callback(column)}`;
|
|
1319
1394
|
}
|
|
1320
|
-
const text = this.buildFragment(ctx, (fragmentCtx) => this.formatPersistableValue(fragmentCtx,
|
|
1321
|
-
return `${
|
|
1395
|
+
const text = this.buildFragment(ctx, (fragmentCtx) => this.formatPersistableValue(fragmentCtx, meta.fields[col], filledPayload[col]));
|
|
1396
|
+
return `${column} = ${text}`;
|
|
1322
1397
|
})
|
|
1323
1398
|
.join(', ');
|
|
1324
1399
|
}
|
|
1325
1400
|
getUpsertConflictPathsStr(meta, conflictPaths) {
|
|
1326
1401
|
return getKeys(conflictPaths)
|
|
1327
|
-
.map((key) =>
|
|
1328
|
-
const field = meta.fields[key];
|
|
1329
|
-
const columnName = this.resolveColumnName(key, field);
|
|
1330
|
-
return this.escapeId(columnName);
|
|
1331
|
-
})
|
|
1402
|
+
.map((key) => this.escapedColumnName(meta, key))
|
|
1332
1403
|
.join(', ');
|
|
1333
1404
|
}
|
|
1334
1405
|
delete(ctx, entity, q, opts = {}) {
|
|
@@ -1337,10 +1408,8 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1337
1408
|
// Soft-delete (stamp only live rows) unless `hardDelete` is requested or the entity has no
|
|
1338
1409
|
// soft-delete field (e.g. a cascade onto a non-soft-deletable child).
|
|
1339
1410
|
if (!opts.hardDelete && meta.softDelete) {
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
ctx.append(`UPDATE ${tableName} SET ${this.escapeId(columnName)} = `);
|
|
1343
|
-
this.formatPersistableValue(ctx, field, getSoftDeleteValue(field));
|
|
1411
|
+
ctx.append(`UPDATE ${tableName} SET `);
|
|
1412
|
+
this.appendAssignment(ctx, meta, meta.softDelete, getSoftDeleteValue(fieldOf(meta, meta.softDelete)));
|
|
1344
1413
|
this.search(ctx, entity, q, opts);
|
|
1345
1414
|
return;
|
|
1346
1415
|
}
|
|
@@ -1537,19 +1606,19 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1537
1606
|
jsonDiffers(elem, operand) {
|
|
1538
1607
|
return `${elem} <> ${operand}`;
|
|
1539
1608
|
}
|
|
1540
|
-
compileDdl(sql, entity) {
|
|
1609
|
+
compileDdl(sql, entity, { rows, escapedPrefix, operand } = {}) {
|
|
1541
1610
|
const ctx = this.createContext({ inlineValues: true });
|
|
1542
1611
|
if (sql instanceof QueryRaw) {
|
|
1543
|
-
sql.render({ ctx, dialect: this, prefix: '', escapedPrefix: '', entity });
|
|
1612
|
+
sql.render({ ctx, dialect: this, prefix: '', escapedPrefix: escapedPrefix ?? '', entity, rows });
|
|
1544
1613
|
}
|
|
1545
1614
|
else if (entity) {
|
|
1546
|
-
this.renderWhere(ctx, entity, sql, { clause: false });
|
|
1615
|
+
this.renderWhere(ctx, entity, sql, { clause: false, escapedPrefix, operand });
|
|
1547
1616
|
}
|
|
1548
1617
|
else {
|
|
1549
|
-
throw new
|
|
1618
|
+
throw new UqlUsageError('a predicate compiles against the entity it is written for, and none was given');
|
|
1550
1619
|
}
|
|
1551
1620
|
if (ctx.values.length) {
|
|
1552
|
-
throw new
|
|
1621
|
+
throw new UqlUsageError(`DDL has no placeholder to bind a value into, and this SQL left one bound: ${ctx.sql}`);
|
|
1553
1622
|
}
|
|
1554
1623
|
return ctx.sql;
|
|
1555
1624
|
}
|
|
@@ -1884,7 +1953,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1884
1953
|
const distance = (fragmentCtx) => this.appendVectorDistance(fragmentCtx, meta, key, near, prefix);
|
|
1885
1954
|
return this.boundConditions(ctx, distance, bounds, (operand, op, val) => (isOrderedOp(op) ? this.operatorCondition(ctx, operand, op, val) : undefined), 'unsupported $near bound');
|
|
1886
1955
|
}
|
|
1887
|
-
/** ANSI-style single-quote escaping.
|
|
1956
|
+
/** ANSI-style single-quote escaping. */
|
|
1888
1957
|
escape(value) {
|
|
1889
1958
|
return escapeAnsiSqlLiteral(value);
|
|
1890
1959
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeDate } from '../util/date.js';
|
|
1
2
|
import { decodeWideNumber } from '../util/wideNumber.js';
|
|
2
3
|
import { decodeFloat32s, parseVectorLiteral } from './vectorCast.js';
|
|
3
4
|
/**
|
|
@@ -31,7 +32,7 @@ const float32Decoder = (value) => {
|
|
|
31
32
|
const DECODERS = {
|
|
32
33
|
// 0/1 from SQLite's INTEGER or MySQL's TINYINT(1). Already a boolean on Postgres.
|
|
33
34
|
boolean: (value) => (typeof value === 'boolean' ? value : Boolean(value)),
|
|
34
|
-
date: (value) => (typeof value === 'string' ? (
|
|
35
|
+
date: (value) => (typeof value === 'string' ? decodeDate(value) : value),
|
|
35
36
|
// Only a string can be bytes that crossed JSON: bytes a driver already decoded stay as they are.
|
|
36
37
|
bytes: (value) => typeof value === 'string' && value.startsWith(BYTES_PREFIX) ? hexBytes(value.slice(BYTES_PREFIX.length)) : value,
|
|
37
38
|
// A number too, not just text: `type: BigInt` is BIGINT, which the pg pools decode at the wire.
|
|
@@ -61,17 +62,6 @@ const DECODERS = {
|
|
|
61
62
|
halfvec: vectorDecoder('halfvec'),
|
|
62
63
|
sparsevec: vectorDecoder('sparsevec'),
|
|
63
64
|
};
|
|
64
|
-
/**
|
|
65
|
-
* An ISO 8601 timestamp as a `Date`, its fraction cut to the milliseconds one holds, and a bare date at
|
|
66
|
-
* local midnight, which is how `pg` reads a `date`. `undefined` for text that is neither.
|
|
67
|
-
*/
|
|
68
|
-
function parseDate(text) {
|
|
69
|
-
const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
70
|
-
const date = day
|
|
71
|
-
? new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]))
|
|
72
|
-
: new Date(text.replace(/(\.\d{3})\d+/, '$1'));
|
|
73
|
-
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
74
|
-
}
|
|
75
65
|
/**
|
|
76
66
|
* What bytes crossing JSON start with, before two hex digits per byte: Postgres's own text for `bytea`,
|
|
77
67
|
* which every dialect spells, so a string a driver reads from a column on its own is never mistaken.
|
|
@@ -78,6 +78,8 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
78
78
|
/** Bytes as the hex text `decodeColumn` reads back, whole. */
|
|
79
79
|
protected bytesAsText(expr: string): string;
|
|
80
80
|
escape(value: unknown): string;
|
|
81
|
+
/** A date as UTC text, which a `DATETIME` stores as is, where a driver would convert it to its own zone. */
|
|
82
|
+
normalizeValue(value: unknown): unknown;
|
|
81
83
|
/**
|
|
82
84
|
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
83
85
|
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
|
+
import { utcTimestamp } from '../util/date.js';
|
|
2
3
|
import { textSearchFields } from '../util/index.js';
|
|
3
4
|
import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
4
5
|
import { AbstractSqlDialect, } from './abstractSqlDialect.js';
|
|
@@ -187,6 +188,10 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
187
188
|
escape(value) {
|
|
188
189
|
return escapeMysqlSqlLiteral(value);
|
|
189
190
|
}
|
|
191
|
+
/** A date as UTC text, which a `DATETIME` stores as is, where a driver would convert it to its own zone. */
|
|
192
|
+
normalizeValue(value) {
|
|
193
|
+
return value instanceof Date ? utcTimestamp(value) : super.normalizeValue(value);
|
|
194
|
+
}
|
|
190
195
|
/**
|
|
191
196
|
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
192
197
|
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type QueryGroupOp, type QueryOrderedOp, type QueryVectorQuery, type QueryWhereArray, type QueryWhereFieldOp } from '../type/index.js';
|
|
1
|
+
import { type QueryGroupOp, type QueryOrderedOp, type QueryVectorQuery, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOp } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* How each grouping operator renders: the operator joining its clauses, and whether the group is
|
|
4
4
|
* negated (`$not` is `NOT (a AND b)`). Total over {@link QueryGroupOp}.
|
|
@@ -29,6 +29,12 @@ export declare function isGroupOp(key: string): key is QueryGroupOp;
|
|
|
29
29
|
* refuse the same payload rather than one throwing and the other failing further in.
|
|
30
30
|
*/
|
|
31
31
|
export declare function groupClauses<E>(key: QueryGroupOp, val: QueryWhereArray<E> | undefined): QueryWhereArray<E>;
|
|
32
|
+
/**
|
|
33
|
+
* Whether a `$where` names any rows, as the WHERE it renders would: an `undefined` value, an empty
|
|
34
|
+
* operator map and a group none of whose clauses names one all render nothing, which a write reads as
|
|
35
|
+
* the whole table.
|
|
36
|
+
*/
|
|
37
|
+
export declare function namesRows<E>(where: QueryWhere<E> | undefined): boolean;
|
|
32
38
|
/**
|
|
33
39
|
* A WHERE value as the operators it applies: an array is `$in`, an operator map its own entries, and
|
|
34
40
|
* anything else `$eq`. Each key is checked here, once, refused as `refusal` where it is no operator.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { VECTOR_QUERY_KEYS, } from '../type/index.js';
|
|
1
|
+
import { QueryRaw, VECTOR_QUERY_KEYS, } from '../type/index.js';
|
|
2
2
|
import { isOperatorMap } from '../util/dialect.util.js';
|
|
3
|
+
import { hasKeys, isWhereMap, someKey } from '../util/object.util.js';
|
|
3
4
|
import { kindOf, UqlUsageError } from '../util/uqlError.js';
|
|
4
5
|
/**
|
|
5
6
|
* How each grouping operator renders: the operator joining its clauses, and whether the group is
|
|
@@ -26,6 +27,17 @@ export function groupClauses(key, val) {
|
|
|
26
27
|
}
|
|
27
28
|
return val ?? [];
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Whether a `$where` names any rows, as the WHERE it renders would: an `undefined` value, an empty
|
|
32
|
+
* operator map and a group none of whose clauses names one all render nothing, which a write reads as
|
|
33
|
+
* the whole table.
|
|
34
|
+
*/
|
|
35
|
+
export function namesRows(where) {
|
|
36
|
+
return (where !== undefined &&
|
|
37
|
+
someKey(where, (key) => isGroupOp(key)
|
|
38
|
+
? groupClauses(key, where[key]).some((clause) => clause instanceof QueryRaw || namesRows(clause))
|
|
39
|
+
: where[key] !== undefined && !(isWhereMap(where[key]) && !hasKeys(where[key]))));
|
|
40
|
+
}
|
|
29
41
|
/**
|
|
30
42
|
* A WHERE value as the operators it applies: an array is `$in`, an operator map its own entries, and
|
|
31
43
|
* anything else `$eq`. Each key is checked here, once, refused as `refusal` where it is no operator.
|
|
@@ -105,6 +105,7 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
105
105
|
* array takes its type from `operand`, so it needs none of the casts `bind` would give each value.
|
|
106
106
|
*/
|
|
107
107
|
protected formatIn(ctx: QueryContext, operand: string, values: unknown[], negate: boolean, bind: (value: unknown) => string): string;
|
|
108
|
+
escape(value: unknown): string;
|
|
108
109
|
protected numericCast(expr: string): string;
|
|
109
110
|
protected appendJsonValue(ctx: QueryContext, value: unknown, type: JsonColumnType): void;
|
|
110
111
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { QueryRaw, } from '../type/index.js';
|
|
2
|
+
import { utcTimestamp } from '../util/date.js';
|
|
2
3
|
import { fulltextConfig, fulltextIndexOver, hasVectorNear, textSearchFields } from '../util/dialect.util.js';
|
|
3
|
-
import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
4
|
+
import { escapePgSqlLiteral, escapeSingleQuotes, PG_UTC } from '../util/sqlLiteral.js';
|
|
4
5
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
5
6
|
import { JSON_PULL_ALIAS, RELATION_ROW_ALIAS } from './aliases.js';
|
|
6
7
|
import { BYTES_PREFIX } from './hydrateColumn.js';
|
|
@@ -128,10 +129,11 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
128
129
|
return statements;
|
|
129
130
|
}
|
|
130
131
|
normalizeValue(value) {
|
|
131
|
-
if (
|
|
132
|
-
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
const values = value.map(utcDate);
|
|
134
|
+
return this.driverCapabilities.nativeArrays ? values : toPgArray(values);
|
|
133
135
|
}
|
|
134
|
-
return super.normalizeValue(value);
|
|
136
|
+
return super.normalizeValue(utcDate(value));
|
|
135
137
|
}
|
|
136
138
|
placeholder(index) {
|
|
137
139
|
return `$${index}`;
|
|
@@ -240,6 +242,9 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
240
242
|
const ph = this.addValue(ctx, values);
|
|
241
243
|
return negate ? `${operand} <> ALL(${ph})` : `${operand} = ANY(${ph})`;
|
|
242
244
|
}
|
|
245
|
+
escape(value) {
|
|
246
|
+
return escapePgSqlLiteral(value);
|
|
247
|
+
}
|
|
243
248
|
numericCast(expr) {
|
|
244
249
|
return `(${expr})::numeric`;
|
|
245
250
|
}
|
|
@@ -306,6 +311,10 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
306
311
|
return this.driverCapabilities.explicitJsonCast ? `(${ph}::text)::${type}` : `${ph}::${type}`;
|
|
307
312
|
}
|
|
308
313
|
}
|
|
314
|
+
/** A date as UTC text, which a zoneless `TIMESTAMP` stores as is, where each driver would pick its own zone. */
|
|
315
|
+
function utcDate(value) {
|
|
316
|
+
return value instanceof Date ? utcTimestamp(value, PG_UTC) : value;
|
|
317
|
+
}
|
|
309
318
|
/**
|
|
310
319
|
* Converts a JS array to a Postgres array literal string: `{"val1","val2"}`.
|
|
311
320
|
* Safely handles nesting and escaping of special characters.
|
|
@@ -45,7 +45,7 @@ export declare function fieldOf<E>(meta: EntityMeta<E>, key: string): FieldMeta;
|
|
|
45
45
|
/** The relation `key` names, for a caller that took `key` from the metadata itself. */
|
|
46
46
|
export declare function relationOf<E>(meta: EntityMeta<E>, key: RelationKey<E>): RelationMeta;
|
|
47
47
|
/** A row of `E` as far as reading its key goes: a record or a write, whatever its values. */
|
|
48
|
-
type KeyedRow<E> = {
|
|
48
|
+
export type KeyedRow<E> = {
|
|
49
49
|
readonly [K in keyof E]?: unknown;
|
|
50
50
|
};
|
|
51
51
|
/** Whether the row names every column of its primary key, `0` and `''` included. */
|
|
@@ -67,4 +67,3 @@ export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
|
|
|
67
67
|
* schema build constrains and a junction joins by, settling the relations holding them first.
|
|
68
68
|
*/
|
|
69
69
|
export declare function foreignKeysOf<E>(meta: EntityMeta<E>): RelationMeta[];
|
|
70
|
-
export {};
|