uql-orm 0.37.1 → 0.39.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/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +5 -24
- package/dist/cockroachdb/cockroachDialect.js +3 -29
- package/dist/dialect/abstractSqlDialect.d.ts +42 -12
- package/dist/dialect/abstractSqlDialect.js +117 -48
- package/dist/dialect/aliases.d.ts +6 -0
- package/dist/dialect/aliases.js +6 -0
- package/dist/dialect/index.d.ts +0 -5
- package/dist/dialect/index.js +2 -5
- package/dist/dialect/jsonSql.d.ts +17 -2
- package/dist/dialect/jsonSql.js +15 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +13 -14
- package/dist/dialect/mysqlLikeSqlDialect.js +16 -20
- package/dist/dialect/pgLikeSqlDialect.d.ts +17 -20
- package/dist/dialect/pgLikeSqlDialect.js +30 -62
- package/dist/dialect/vectorCast.d.ts +0 -9
- package/dist/dialect/vectorCast.js +0 -8
- package/dist/dialect/vectorSqlDialect.d.ts +47 -17
- package/dist/dialect/vectorSqlDialect.js +78 -30
- package/dist/entity/decorator/entity.d.ts +1 -1
- package/dist/entity/metadata/definition.d.ts +1 -1
- package/dist/entity/metadata/definition.js +4 -3
- package/dist/http/query.js +3 -2
- package/dist/libsql/libsqlDialect.d.ts +2 -2
- package/dist/libsql/libsqlDialect.js +3 -3
- package/dist/maria/mariaDialect.d.ts +12 -17
- package/dist/maria/mariaDialect.js +21 -36
- package/dist/maria/mariaVectorMetrics.d.ts +8 -0
- package/dist/maria/mariaVectorMetrics.js +10 -0
- package/dist/maria/mariadbQuerier.d.ts +5 -0
- package/dist/maria/mariadbQuerier.js +5 -0
- package/dist/migrate/builder/migrationBuilder.d.ts +8 -17
- package/dist/migrate/builder/migrationBuilder.js +48 -136
- package/dist/migrate/builder/types.d.ts +0 -2
- package/dist/migrate/ddl/index.d.ts +11 -0
- package/dist/migrate/ddl/index.js +34 -0
- package/dist/{dialect/indexSqlDialect.d.ts → migrate/ddl/indexDdl.d.ts} +23 -19
- package/dist/migrate/ddl/indexDdl.js +126 -0
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +52 -0
- package/dist/migrate/ddl/mysqlIndexDdl.js +125 -0
- package/dist/migrate/ddl/pgIndexDdl.d.ts +36 -0
- package/dist/migrate/ddl/pgIndexDdl.js +87 -0
- package/dist/migrate/drift/driftDetector.js +6 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
- package/dist/migrate/index.d.ts +1 -0
- package/dist/migrate/index.js +2 -0
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +9 -3
- package/dist/migrate/introspection/mysqlIntrospector.js +27 -5
- package/dist/migrate/migrator.js +3 -2
- package/dist/migrate/schemaGenerator.d.ts +6 -6
- package/dist/migrate/schemaGenerator.js +13 -22
- package/dist/mongo/mongoDialect.d.ts +1 -2
- package/dist/mongo/mongoDialect.js +29 -22
- package/dist/mongo/mongodbQuerier.js +1 -1
- package/dist/mysql/mysqlDialect.d.ts +3 -6
- package/dist/mysql/mysqlDialect.js +4 -11
- package/dist/postgres/postgresDialect.d.ts +2 -0
- package/dist/postgres/postgresDialect.js +2 -0
- package/dist/querier/abstractSqlQuerier.d.ts +11 -0
- package/dist/querier/abstractSqlQuerier.js +35 -0
- package/dist/schema/canonicalType.js +35 -36
- package/dist/schema/indexDifferences.d.ts +2 -1
- package/dist/schema/indexDifferences.js +3 -2
- package/dist/sqlite/sqliteDialect.d.ts +2 -2
- package/dist/sqlite/sqliteDialect.js +5 -5
- package/dist/turso/tursoDialect.d.ts +2 -2
- package/dist/turso/tursoDialect.js +4 -4
- package/dist/type/dialect.d.ts +21 -7
- package/dist/type/dialect.js +17 -0
- package/dist/type/entity.d.ts +117 -8
- package/dist/type/entity.js +7 -0
- package/dist/type/query.d.ts +18 -0
- package/dist/type/query.js +6 -0
- package/dist/type/queryWhere.d.ts +46 -2
- package/dist/type/vector.d.ts +45 -7
- package/dist/type/vector.js +16 -1
- package/dist/util/dialect.util.d.ts +20 -1
- package/dist/util/dialect.util.js +49 -4
- package/dist/util/object.util.d.ts +7 -1
- package/dist/util/object.util.js +8 -0
- package/dist/util/relationQuery.util.d.ts +3 -1
- package/dist/util/relationQuery.util.js +8 -3
- package/package.json +1 -1
- package/dist/dialect/indexSqlDialect.js +0 -103
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
|
-
import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
|
|
2
|
+
import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, VECTOR_QUERY_KEYS, } from '../type/index.js';
|
|
3
3
|
import { asSelectMap, assertNonNegativeInteger, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, isBooleanType, isJsonType, isJsonUpdateOp, isNumericType, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
4
|
import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
5
5
|
import { COUNT_ALIAS, DISTINCT_DERIVED_ALIAS, JSON_ELEM_ALIAS_PREFIX } from './aliases.js';
|
|
6
|
-
import { IndexSqlDialect } from './indexSqlDialect.js';
|
|
7
6
|
import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
|
|
8
7
|
import { isJsonbOp, jsonCompareMode, jsonElemExists } from './jsonSql.js';
|
|
9
8
|
import { SqlQueryContext } from './queryContext.js';
|
|
10
9
|
import { NO_JOINS, resolveQueryJoins, resolveSortableJoin, } from './queryJoins.js';
|
|
11
10
|
import { isVectorFieldType, resolveVectorCast } from './vectorCast.js';
|
|
12
|
-
|
|
11
|
+
import { VectorSqlDialect } from './vectorSqlDialect.js';
|
|
12
|
+
export class AbstractSqlDialect extends VectorSqlDialect {
|
|
13
13
|
/**
|
|
14
14
|
* How this engine declares a namespace, so a generated migration creates the schemas its tables
|
|
15
15
|
* need before creating them. Only reached where {@link DialectFeatures.schemas} is on. MySQL and
|
|
@@ -394,6 +394,17 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
394
394
|
['$lt', ' < '],
|
|
395
395
|
['$lte', ' <= '],
|
|
396
396
|
]);
|
|
397
|
+
/** What a `$near` says about the search itself; everything else in it is a bound. */
|
|
398
|
+
static VECTOR_QUERY_KEYS = new Set(VECTOR_QUERY_KEYS);
|
|
399
|
+
/**
|
|
400
|
+
* The runtime half of {@link QueryVectorNear}'s bounds, derived from the map above rather than
|
|
401
|
+
* spelled again: both are `QueryOrderedOp`, so `$near` can never accept a comparison the renderer
|
|
402
|
+
* below has no operator for.
|
|
403
|
+
*/
|
|
404
|
+
static NEAR_BOUND_OPS = new Set([
|
|
405
|
+
...AbstractSqlDialect.COMPARE_OP_MAP.keys(),
|
|
406
|
+
'$between',
|
|
407
|
+
]);
|
|
397
408
|
/**
|
|
398
409
|
* Every `$like`-family operator: the pattern it wraps its value in, and whether it ignores case.
|
|
399
410
|
* Each case-sensitive operator is paired here with the `$i` twin that shares its pattern, so the
|
|
@@ -479,6 +490,9 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
479
490
|
case '$elemMatch':
|
|
480
491
|
ctx.append(this.jsonElemMatch(ctx, field, val));
|
|
481
492
|
break;
|
|
493
|
+
case '$near':
|
|
494
|
+
this.compareVectorNear(ctx, getMeta(entity), key, val);
|
|
495
|
+
break;
|
|
482
496
|
default:
|
|
483
497
|
throw TypeError(`unknown operator: ${op}`);
|
|
484
498
|
}
|
|
@@ -547,34 +561,35 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
547
561
|
* All dialect-specific behavior comes from overridable methods on `this`.
|
|
548
562
|
*/
|
|
549
563
|
buildJsonFieldCondition(ctx, fieldAccessor, jsonPath, op, value, asJson = false) {
|
|
550
|
-
const jsonField = fieldAccessor(jsonPath);
|
|
564
|
+
const jsonField = fieldAccessor(jsonPath, asJson ? 'json' : 'text');
|
|
565
|
+
// The left side of a comparison reads the path the way its operand is compared: a numeric one
|
|
566
|
+
// cast to a number, a boolean as the JSON value.
|
|
567
|
+
const comparand = (val) => fieldAccessor(jsonPath, jsonCompareMode(val));
|
|
551
568
|
// The `$like` family reads a JSON path exactly as it reads a column, case folding included.
|
|
552
569
|
const like = this.likeCondition(ctx, jsonField, op, value);
|
|
553
570
|
if (like) {
|
|
554
571
|
return like;
|
|
555
572
|
}
|
|
573
|
+
// The ordered comparisons read the path as a number whatever the operand is, and spell their
|
|
574
|
+
// operator out of the same table a comparison against a column does.
|
|
575
|
+
const compareOp = AbstractSqlDialect.COMPARE_OP_MAP.get(op);
|
|
576
|
+
if (compareOp) {
|
|
577
|
+
return `${fieldAccessor(jsonPath, 'numeric')}${compareOp}${this.addValue(ctx.values, value)}`;
|
|
578
|
+
}
|
|
556
579
|
switch (op) {
|
|
557
580
|
case '$eq':
|
|
558
581
|
if (value === null)
|
|
559
582
|
return `${jsonField} IS NULL`;
|
|
560
|
-
return `${
|
|
583
|
+
return `${comparand(value)} = ${this.jsonOperand(ctx, value, asJson)}`;
|
|
561
584
|
case '$ne':
|
|
562
585
|
if (value === null)
|
|
563
586
|
return `${jsonField} IS NOT NULL`;
|
|
564
|
-
return this.neExpr(
|
|
565
|
-
case '$gt':
|
|
566
|
-
return `${this.numericCast(jsonField)} > ${this.addValue(ctx.values, value)}`;
|
|
567
|
-
case '$gte':
|
|
568
|
-
return `${this.numericCast(jsonField)} >= ${this.addValue(ctx.values, value)}`;
|
|
569
|
-
case '$lt':
|
|
570
|
-
return `${this.numericCast(jsonField)} < ${this.addValue(ctx.values, value)}`;
|
|
571
|
-
case '$lte':
|
|
572
|
-
return `${this.numericCast(jsonField)} <= ${this.addValue(ctx.values, value)}`;
|
|
587
|
+
return this.neExpr(comparand(value), this.jsonOperand(ctx, value, asJson));
|
|
573
588
|
case '$regex':
|
|
574
589
|
return `${jsonField} ${this.regexpOp} ${this.addValue(ctx.values, value)}`;
|
|
575
590
|
case '$in':
|
|
576
591
|
case '$nin':
|
|
577
|
-
return this.jsonInNin(ctx, jsonField, op, value, asJson);
|
|
592
|
+
return this.jsonInNin(ctx, jsonField, comparand, op, value, asJson);
|
|
578
593
|
case '$all':
|
|
579
594
|
return this.jsonAll(ctx, jsonField, value);
|
|
580
595
|
case '$size':
|
|
@@ -585,11 +600,11 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
585
600
|
throw TypeError(`unknown operator: ${op}`);
|
|
586
601
|
}
|
|
587
602
|
}
|
|
588
|
-
jsonInNin(ctx, jsonField, op, value, asJson) {
|
|
603
|
+
jsonInNin(ctx, jsonField, comparand, op, value, asJson) {
|
|
589
604
|
const values = Array.isArray(value) ? value : [];
|
|
590
605
|
const negate = op === '$nin';
|
|
591
606
|
if (!asJson) {
|
|
592
|
-
return `${
|
|
607
|
+
return `${comparand(values)}${this.formatIn(ctx, values, negate)}`;
|
|
593
608
|
}
|
|
594
609
|
// JSON values have no portable array literal, so the set expands into explicit comparisons.
|
|
595
610
|
const comparisons = values.map((val) => `${jsonField} ${negate ? '<>' : '='} ${this.jsonScalarParam(ctx, val)}`);
|
|
@@ -599,13 +614,6 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
599
614
|
jsonOperand(ctx, value, asJson) {
|
|
600
615
|
return asJson ? this.jsonScalarParam(ctx, value) : this.addValue(ctx.values, value);
|
|
601
616
|
}
|
|
602
|
-
/**
|
|
603
|
-
* The left side of a comparison against a JSON scalar, cast when the operand is numeric - see
|
|
604
|
-
* {@link jsonCompareMode} for why each mode exists.
|
|
605
|
-
*/
|
|
606
|
-
jsonComparand(jsonField, value) {
|
|
607
|
-
return jsonCompareMode(value) === 'numeric' ? this.numericCast(jsonField) : jsonField;
|
|
608
|
-
}
|
|
609
617
|
/**
|
|
610
618
|
* Whether the dialect's array containment ({@link jsonAll}) matches an object element that merely
|
|
611
619
|
* *includes* the given keys, as PostgreSQL's `@>` and MySQL's `JSON_CONTAINS` do. SQLite compares
|
|
@@ -634,7 +642,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
634
642
|
const entries = Object.entries(match);
|
|
635
643
|
const asJson = !this.jsonScalarElemKeepsType && entries.every(([op, val]) => isJsonbOp(op, val));
|
|
636
644
|
const alias = ctx.nextAlias(JSON_ELEM_ALIAS_PREFIX);
|
|
637
|
-
const conditions = entries.map(([op, val]) => this.buildJsonFieldCondition(ctx,
|
|
645
|
+
const conditions = entries.map(([op, val]) => this.buildJsonFieldCondition(ctx, this.elemAccessor(alias, asJson), '', op, val, asJson));
|
|
638
646
|
return jsonElemExists(this.jsonElemFrom(jsonField, [], alias, asJson), conditions);
|
|
639
647
|
}
|
|
640
648
|
if (isOperatorObject(match)) {
|
|
@@ -648,10 +656,21 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
648
656
|
const alias = ctx.nextAlias(JSON_ELEM_ALIAS_PREFIX);
|
|
649
657
|
const conditions = buildElemMatchConditions(match, (field, op, opVal) => {
|
|
650
658
|
const asJson = isJsonbOp(op, opVal);
|
|
651
|
-
return this.buildJsonFieldCondition(ctx,
|
|
659
|
+
return this.buildJsonFieldCondition(ctx, this.elemAccessor(alias, asJson), field, op, opVal, asJson);
|
|
652
660
|
});
|
|
653
661
|
return jsonElemExists(this.jsonElemFrom(jsonField, Object.keys(match), alias), conditions);
|
|
654
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* How a `$elemMatch` reads one exploded element: {@link jsonPathExpr}'s counterpart, over a column
|
|
665
|
+
* of the derived table rather than a path of the document. An empty field names the element itself,
|
|
666
|
+
* which is what the operator-only form matches on.
|
|
667
|
+
*/
|
|
668
|
+
elemAccessor(alias, asJson) {
|
|
669
|
+
return (field, mode) => {
|
|
670
|
+
const ref = this.jsonElemRef(alias, field || undefined, asJson);
|
|
671
|
+
return mode === 'numeric' ? this.numericCast(ref) : ref;
|
|
672
|
+
};
|
|
673
|
+
}
|
|
655
674
|
/**
|
|
656
675
|
* A JSON-encoded bound parameter, cast to the dialect's JSON type. Only the positional-placeholder
|
|
657
676
|
* dialects use this - PostgreSQL binds JSON through {@link PgLikeSqlDialect.jsonScalarParam} instead.
|
|
@@ -735,7 +754,8 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
735
754
|
if (field) {
|
|
736
755
|
return field.virtual ? this.escapeId(key) : this.columnWithPrefix(key, field, prefix);
|
|
737
756
|
}
|
|
738
|
-
|
|
757
|
+
const json = this.resolveJsonDotPath(meta, key, prefix);
|
|
758
|
+
return json ? this.jsonPathExpr(json.column, json.jsonPath, 'text') : this.escapeId(key);
|
|
739
759
|
}
|
|
740
760
|
pager(ctx, opts) {
|
|
741
761
|
// `!== undefined`, not truthiness: `$limit: 0` asks for no rows, where "unset" means every row.
|
|
@@ -1339,20 +1359,29 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
1339
1359
|
if (!field || !isJsonType(field.type)) {
|
|
1340
1360
|
return undefined;
|
|
1341
1361
|
}
|
|
1342
|
-
const jsonPath = key.slice(dotIndex + 1);
|
|
1343
1362
|
const colName = this.resolveColumnName(root, field);
|
|
1344
|
-
const
|
|
1345
|
-
return {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1363
|
+
const prefixed = (prefix ? this.escapeId(prefix, true, true) : '') + this.escapeId(colName);
|
|
1364
|
+
return { jsonPath: key.slice(dotIndex + 1), column: prefixed };
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* One JSON path, read the way `mode` asks for: the one place the three readings are chosen between,
|
|
1368
|
+
* so `$where`, `$sort` and every operator reach a path the same way. Public because a JSON index
|
|
1369
|
+
* is matched back by its own text, so the migrator's `CREATE INDEX` has to spell it from here too.
|
|
1370
|
+
*/
|
|
1371
|
+
jsonPathExpr(escapedColumn, jsonPath, mode) {
|
|
1372
|
+
if (mode === 'json') {
|
|
1373
|
+
return this.getJsonPathJsonbExpr(escapedColumn, jsonPath);
|
|
1374
|
+
}
|
|
1375
|
+
const scalar = this.getJsonPathScalarExpr(escapedColumn, jsonPath);
|
|
1376
|
+
return mode === 'numeric' ? this.numericCast(scalar) : scalar;
|
|
1349
1377
|
}
|
|
1350
1378
|
/**
|
|
1351
1379
|
* Compare a JSONB dot-notation path, e.g. `'settings.isArchived': { $ne: true }`.
|
|
1352
1380
|
* Receives a pre-resolved `resolveJsonDotPath` result to avoid redundant computation.
|
|
1353
1381
|
*/
|
|
1354
1382
|
compareJsonPath(ctx, resolved, val) {
|
|
1355
|
-
const { jsonPath,
|
|
1383
|
+
const { jsonPath, column } = resolved;
|
|
1384
|
+
const accessor = (path, mode) => this.jsonPathExpr(column, path, mode);
|
|
1356
1385
|
const value = this.normalizeWhereValue(val);
|
|
1357
1386
|
const operators = getKeys(value);
|
|
1358
1387
|
if (operators.length > 1) {
|
|
@@ -1362,7 +1391,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
1362
1391
|
if (index > 0)
|
|
1363
1392
|
ctx.append(' AND ');
|
|
1364
1393
|
const asJson = isJsonbOp(op, value[op]);
|
|
1365
|
-
const sql = this.buildJsonFieldCondition(ctx,
|
|
1394
|
+
const sql = this.buildJsonFieldCondition(ctx, accessor, jsonPath, op, value[op], asJson);
|
|
1366
1395
|
if (sql) {
|
|
1367
1396
|
ctx.append(sql);
|
|
1368
1397
|
}
|
|
@@ -1485,18 +1514,13 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
1485
1514
|
this.buildSizeComparison(ctx, () => this.appendRelationSubquery(ctx, getMeta(entity), rel, opts, 'COUNT(*)', {}), sizeVal);
|
|
1486
1515
|
}
|
|
1487
1516
|
/**
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1517
|
+
* `<expr> <op> <value>` for each operator, AND-joined and parenthesized when there is more than
|
|
1518
|
+
* one. `exprFn` is re-run per operator because what it appends is an expression, not a column:
|
|
1519
|
+
* a `WHERE` has no output alias to refer back to, so the only way to compare it twice is to spell
|
|
1520
|
+
* it twice. Shared by `$size`, which counts, and `$near`, which measures a distance.
|
|
1491
1521
|
*/
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
sizeExprFn();
|
|
1495
|
-
ctx.append(' = ');
|
|
1496
|
-
ctx.addValue(sizeVal);
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
const entries = Object.entries(sizeVal).filter(([, v]) => v !== undefined);
|
|
1522
|
+
buildExprComparison(ctx, exprFn, ops, appendOp) {
|
|
1523
|
+
const entries = Object.entries(ops).filter(([, v]) => v !== undefined);
|
|
1500
1524
|
if (entries.length > 1) {
|
|
1501
1525
|
ctx.append('(');
|
|
1502
1526
|
}
|
|
@@ -1504,13 +1528,58 @@ export class AbstractSqlDialect extends IndexSqlDialect {
|
|
|
1504
1528
|
if (index > 0) {
|
|
1505
1529
|
ctx.append(' AND ');
|
|
1506
1530
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1531
|
+
exprFn();
|
|
1532
|
+
appendOp(op, val);
|
|
1509
1533
|
});
|
|
1510
1534
|
if (entries.length > 1) {
|
|
1511
1535
|
ctx.append(')');
|
|
1512
1536
|
}
|
|
1513
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Build a complete `$size` comparison expression.
|
|
1540
|
+
* @param sizeExprFn - function that appends the size expression to ctx (e.g. `jsonb_array_length("col")`)
|
|
1541
|
+
*/
|
|
1542
|
+
buildSizeComparison(ctx, sizeExprFn, sizeVal) {
|
|
1543
|
+
if (typeof sizeVal === 'number') {
|
|
1544
|
+
sizeExprFn();
|
|
1545
|
+
ctx.append(' = ');
|
|
1546
|
+
ctx.addValue(sizeVal);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
this.buildExprComparison(ctx, sizeExprFn, sizeVal, (op, val) => this.appendSizeOp(ctx, op, val));
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* `<distance expr> <op> ?` - the `$where` half of vector search, where `$sort` is the ranking half.
|
|
1553
|
+
*
|
|
1554
|
+
* The bounds are validated here rather than left to the shared renderer, which also knows `$like`
|
|
1555
|
+
* and `$in`; and `$eq`/`$ne` are absent on purpose, since a distance is a float. `/http` casts
|
|
1556
|
+
* client JSON straight to `Query`, so an unknown key has to be refused rather than ignored.
|
|
1557
|
+
*/
|
|
1558
|
+
compareVectorNear(ctx, meta, key, near) {
|
|
1559
|
+
const bounds = {};
|
|
1560
|
+
for (const [op, val] of Object.entries(near)) {
|
|
1561
|
+
if (AbstractSqlDialect.VECTOR_QUERY_KEYS.has(op) || val === undefined) {
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (!AbstractSqlDialect.NEAR_BOUND_OPS.has(op)) {
|
|
1565
|
+
throw TypeError(`unsupported $near bound: ${op}`);
|
|
1566
|
+
}
|
|
1567
|
+
bounds[op] = val;
|
|
1568
|
+
}
|
|
1569
|
+
if (!hasKeys(bounds)) {
|
|
1570
|
+
const boundOps = [...AbstractSqlDialect.NEAR_BOUND_OPS].join(', ');
|
|
1571
|
+
throw TypeError(`$near on '${key}' needs a bound (${boundOps}); without one it filters nothing`);
|
|
1572
|
+
}
|
|
1573
|
+
// Required by the type, so this only fires for a query that never met it: `/http` casts client
|
|
1574
|
+
// JSON straight to `Query`. A `$near` never borrows the `$sort`'s vector, which is what keeps the
|
|
1575
|
+
// predicate meaning the same thing in a `count`, or in an entity filter merged into a `$where`.
|
|
1576
|
+
const { $vector, $distance } = near;
|
|
1577
|
+
if (!$vector) {
|
|
1578
|
+
throw TypeError(`$near on '${key}' needs its own $vector`);
|
|
1579
|
+
}
|
|
1580
|
+
const search = { $vector, $distance };
|
|
1581
|
+
this.buildExprComparison(ctx, () => this.appendVectorSort(ctx, meta, key, search), bounds, (op, val) => this.appendOperatorCondition(ctx, '', op, val));
|
|
1582
|
+
}
|
|
1514
1583
|
/** The runtime half of {@link QuerySizeComparisonOps}: what a count can sensibly be compared with. */
|
|
1515
1584
|
static SIZE_COMPARE_OPS = new Set([
|
|
1516
1585
|
'$eq',
|
|
@@ -22,6 +22,12 @@ export declare const JSON_PULL_ALIAS = "_uql_pull";
|
|
|
22
22
|
export declare const REL_TEMP_PREFIX = "_uql_rel_";
|
|
23
23
|
/** The field a ManyToMany lookup nests its target match under, inside the junction's own pipeline. */
|
|
24
24
|
export declare const REL_NESTED_KEY = "_uql_target";
|
|
25
|
+
/**
|
|
26
|
+
* The alias MySQL's upsert gives the row being inserted, so its `ON DUPLICATE KEY UPDATE`
|
|
27
|
+
* assignments read `_uql_new.col` instead of the deprecated `VALUES(col)`. MariaDB has no such
|
|
28
|
+
* syntax and keeps `VALUES(col)`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const UPSERT_NEW_ROW_ALIAS = "_uql_new";
|
|
25
31
|
/**
|
|
26
32
|
* Where a `$sort` by a relation's size parks its tally until the ordering has run. A function, so the
|
|
27
33
|
* `$sort` that names the field and the stage that produces it cannot spell it differently - MongoDB
|
package/dist/dialect/aliases.js
CHANGED
|
@@ -22,6 +22,12 @@ export const JSON_PULL_ALIAS = '_uql_pull';
|
|
|
22
22
|
export const REL_TEMP_PREFIX = '_uql_rel_';
|
|
23
23
|
/** The field a ManyToMany lookup nests its target match under, inside the junction's own pipeline. */
|
|
24
24
|
export const REL_NESTED_KEY = '_uql_target';
|
|
25
|
+
/**
|
|
26
|
+
* The alias MySQL's upsert gives the row being inserted, so its `ON DUPLICATE KEY UPDATE`
|
|
27
|
+
* assignments read `_uql_new.col` instead of the deprecated `VALUES(col)`. MariaDB has no such
|
|
28
|
+
* syntax and keeps `VALUES(col)`.
|
|
29
|
+
*/
|
|
30
|
+
export const UPSERT_NEW_ROW_ALIAS = '_uql_new';
|
|
25
31
|
/**
|
|
26
32
|
* Where a `$sort` by a relation's size parks its tally until the ordering has run. A function, so the
|
|
27
33
|
* `$sort` that names the field and the stage that produces it cannot spell it differently - MongoDB
|
package/dist/dialect/index.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
export { MariaDialect } from '../maria/mariaDialect.js';
|
|
2
|
-
export { MySqlDialect } from '../mysql/mysqlDialect.js';
|
|
3
|
-
export { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
4
|
-
export { SqliteDialect } from '../sqlite/sqliteDialect.js';
|
|
5
1
|
export * from './abstractDialect.js';
|
|
6
2
|
export * from './abstractSqlDialect.js';
|
|
7
|
-
export * from './indexSqlDialect.js';
|
|
8
3
|
export * from './mysqlLikeSqlDialect.js';
|
|
9
4
|
export * from './queryContext.js';
|
package/dist/dialect/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
4
|
-
export { SqliteDialect } from '../sqlite/sqliteDialect.js';
|
|
1
|
+
// The concrete dialects are behind their own entries (`uql-orm/postgres`, `/mysql`, `/maria`,
|
|
2
|
+
// `/sqlite`, `/cockroachdb`): importing the root should not carry four engines' worth of SQL.
|
|
5
3
|
export * from './abstractDialect.js';
|
|
6
4
|
export * from './abstractSqlDialect.js';
|
|
7
|
-
export * from './indexSqlDialect.js';
|
|
8
5
|
export * from './mysqlLikeSqlDialect.js';
|
|
9
6
|
export * from './queryContext.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldOptions } from '../type/index.js';
|
|
1
|
+
import type { FieldOptions, FieldType } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* A `'$.a.b'` JSON path literal, each dot-separated segment escaped. `suffix` appends an accessor
|
|
4
4
|
* such as `[#]` or `[*]`. Shared across dialects unchanged: no dialect escapes a JSON path key
|
|
@@ -21,6 +21,12 @@ export declare function jsonSetTarget(expr: string, field: FieldOptions | undefi
|
|
|
21
21
|
export declare function jsonRemoveCall(fn: string, expr: string, keys: readonly string[]): string;
|
|
22
22
|
/** `WHERE` is omitted for an empty `$elemMatch`, which asks only that the array has an element. */
|
|
23
23
|
export declare function jsonElemExists(from: string, conditions: readonly string[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* How a JSON value is read out of its document: as the JSON value itself, as a number to compare
|
|
26
|
+
* against, or as the text a path yields. One vocabulary for every operand of a comparison, so the
|
|
27
|
+
* left side is read the way the right side is compared - see {@link jsonCompareMode}.
|
|
28
|
+
*/
|
|
29
|
+
export type JsonAccessMode = 'json' | 'numeric' | 'text';
|
|
24
30
|
/**
|
|
25
31
|
* How a JSON scalar has to be compared against `value` (or, for `$in`/`$nin`, against every element
|
|
26
32
|
* of it). Extracting a JSON value yields *text*, which loses the type, so each operand type is
|
|
@@ -33,7 +39,16 @@ export declare function jsonElemExists(from: string, conditions: readonly string
|
|
|
33
39
|
*
|
|
34
40
|
* Mixed operand types fall back to `text`, since one comparison cannot be two shapes at once.
|
|
35
41
|
*/
|
|
36
|
-
export declare function jsonCompareMode(value: unknown):
|
|
42
|
+
export declare function jsonCompareMode(value: unknown): JsonAccessMode;
|
|
43
|
+
/**
|
|
44
|
+
* The mode a *declared* type asks for: {@link jsonCompareMode}'s twin, reading the type instead of an
|
|
45
|
+
* operand. An index over a JSON path is only reachable by a comparison that extracts it the same way,
|
|
46
|
+
* so the two have to answer alike - which is why they are one pair over one vocabulary.
|
|
47
|
+
*
|
|
48
|
+
* Reads the type through `util/field.util`'s predicates, which the dialects already carry: resolving
|
|
49
|
+
* it through `schema/canonicalType` instead pulls that whole module into every consumer bundle.
|
|
50
|
+
*/
|
|
51
|
+
export declare function jsonTypeMode(type: FieldType): JsonAccessMode;
|
|
37
52
|
/**
|
|
38
53
|
* Whether the operator reads the JSON *value* instead of its text form. The array operators always
|
|
39
54
|
* do. Equality joins them for boolean operands, because extracting JSON as text loses the type in
|
package/dist/dialect/jsonSql.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBooleanType, isNumericType } from '../util/field.util.js';
|
|
1
2
|
import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
2
3
|
/**
|
|
3
4
|
* A `'$.a.b'` JSON path literal, each dot-separated segment escaped. `suffix` appends an accessor
|
|
@@ -56,6 +57,20 @@ export function jsonCompareMode(value) {
|
|
|
56
57
|
}
|
|
57
58
|
return operands.every((operand) => typeof operand === 'number') ? 'numeric' : 'text';
|
|
58
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The mode a *declared* type asks for: {@link jsonCompareMode}'s twin, reading the type instead of an
|
|
62
|
+
* operand. An index over a JSON path is only reachable by a comparison that extracts it the same way,
|
|
63
|
+
* so the two have to answer alike - which is why they are one pair over one vocabulary.
|
|
64
|
+
*
|
|
65
|
+
* Reads the type through `util/field.util`'s predicates, which the dialects already carry: resolving
|
|
66
|
+
* it through `schema/canonicalType` instead pulls that whole module into every consumer bundle.
|
|
67
|
+
*/
|
|
68
|
+
export function jsonTypeMode(type) {
|
|
69
|
+
if (isNumericType(type)) {
|
|
70
|
+
return 'numeric';
|
|
71
|
+
}
|
|
72
|
+
return isBooleanType(type) ? 'json' : 'text';
|
|
73
|
+
}
|
|
59
74
|
/**
|
|
60
75
|
* Whether the operator reads the JSON *value* instead of its text form. The array operators always
|
|
61
76
|
* do. Equality joins them for boolean operands, because extracting JSON as text loses the type in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DialectFeatures, EntityMeta, FieldOptions,
|
|
1
|
+
import type { DialectFeatures, EntityMeta, FieldOptions, InsertIdSource, QueryConflictPaths, QueryContext, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
|
|
2
2
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
3
3
|
/**
|
|
4
4
|
* Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
|
|
@@ -9,6 +9,9 @@ import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
|
9
9
|
* - `$elemMatch` (JSON_TABLE, or fast JSON_CONTAINS for the simple case)
|
|
10
10
|
* - the update operators `$set` (JSON_SET), `$unset` (JSON_REMOVE), `$push` (JSON_MERGE_PRESERVE)
|
|
11
11
|
* and `$pull` (JSON_REPLACE over JSON_TABLE)
|
|
12
|
+
*
|
|
13
|
+
* Neither has `FOR NO KEY UPDATE`/`FOR KEY SHARE`, PostgreSQL's weaker pair, so asking for one is
|
|
14
|
+
* rejected rather than served a stronger lock.
|
|
12
15
|
*/
|
|
13
16
|
export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
14
17
|
/** Default {@link DialectFeatures} for MySQL-compatible SQL dialects. */
|
|
@@ -40,8 +43,8 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
40
43
|
* unique index for you.
|
|
41
44
|
*
|
|
42
45
|
* The update assignments are built into their own context and pushed afterwards, since they read
|
|
43
|
-
*
|
|
44
|
-
* payload) has to land after the insert's for a `?`-placeholder driver.
|
|
46
|
+
* the inserted row rather than binding, and any value they *do* bind (an `onUpdate` field absent from
|
|
47
|
+
* the payload) has to land after the insert's for a `?`-placeholder driver.
|
|
45
48
|
*/
|
|
46
49
|
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
|
|
47
50
|
/**
|
|
@@ -49,6 +52,13 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
49
52
|
* 10.5+ has it, and used to restate this whole method just to add it.
|
|
50
53
|
*/
|
|
51
54
|
protected upsertReturning<E>(_entity: Type<E>): string;
|
|
55
|
+
/**
|
|
56
|
+
* The alias the inserted row is given after the values list, and read back by the assignments.
|
|
57
|
+
* Undefined where the dialect has no such syntax - MariaDB, which reads that row through
|
|
58
|
+
* `VALUE(col)` instead: it renamed `VALUES()` in 10.3.3, the old name clashing with the standard
|
|
59
|
+
* table value constructors, where MySQL deprecated the function outright in favour of the alias.
|
|
60
|
+
*/
|
|
61
|
+
protected readonly upsertNewRowAlias: string | undefined;
|
|
52
62
|
readonly maxBindValues: number;
|
|
53
63
|
escape(value: unknown): string;
|
|
54
64
|
/**
|
|
@@ -57,17 +67,6 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
57
67
|
* `@Index([...], { type: 'fulltext' })`.
|
|
58
68
|
*/
|
|
59
69
|
protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
60
|
-
/**
|
|
61
|
-
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
|
|
62
|
-
* is a syntax error, so it is the keyword that changes rather than the access method.
|
|
63
|
-
*/
|
|
64
|
-
protected indexKeyword(index: IndexSchema): string;
|
|
65
|
-
protected readonly indexFeatures: Set<IndexFeature>;
|
|
66
|
-
/**
|
|
67
|
-
* No `FOR NO KEY UPDATE`/`FOR KEY SHARE`: those are PostgreSQL's weaker pair and the family has
|
|
68
|
-
* no equivalent, so asking for one is rejected rather than served a stronger lock.
|
|
69
|
-
*/
|
|
70
|
-
protected indexAccessMethod(index: IndexSchema): string;
|
|
71
70
|
protected numericCast(expr: string): string;
|
|
72
71
|
protected neExpr(field: string, ph: string): string;
|
|
73
72
|
/** How a surviving element is fed back into the array a `$pull` rebuilds. */
|
|
@@ -15,6 +15,9 @@ const MAX_LIMIT = BigInt.asUintN(64, -1n);
|
|
|
15
15
|
* - `$elemMatch` (JSON_TABLE, or fast JSON_CONTAINS for the simple case)
|
|
16
16
|
* - the update operators `$set` (JSON_SET), `$unset` (JSON_REMOVE), `$push` (JSON_MERGE_PRESERVE)
|
|
17
17
|
* and `$pull` (JSON_REPLACE over JSON_TABLE)
|
|
18
|
+
*
|
|
19
|
+
* Neither has `FOR NO KEY UPDATE`/`FOR KEY SHARE`, PostgreSQL's weaker pair, so asking for one is
|
|
20
|
+
* rejected rather than served a stronger lock.
|
|
18
21
|
*/
|
|
19
22
|
export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
20
23
|
/** Default {@link DialectFeatures} for MySQL-compatible SQL dialects. */
|
|
@@ -29,7 +32,7 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
29
32
|
renameColumn: true,
|
|
30
33
|
foreignKeyAlter: true,
|
|
31
34
|
columnComment: true,
|
|
32
|
-
|
|
35
|
+
vectorIndexRequiresNotNull: false,
|
|
33
36
|
vectorSupportsLength: false,
|
|
34
37
|
supportsTimestamptz: false,
|
|
35
38
|
defaultStringAsText: false,
|
|
@@ -80,17 +83,18 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
80
83
|
* unique index for you.
|
|
81
84
|
*
|
|
82
85
|
* The update assignments are built into their own context and pushed afterwards, since they read
|
|
83
|
-
*
|
|
84
|
-
* payload) has to land after the insert's for a `?`-placeholder driver.
|
|
86
|
+
* the inserted row rather than binding, and any value they *do* bind (an `onUpdate` field absent from
|
|
87
|
+
* the payload) has to land after the insert's for a `?`-placeholder driver.
|
|
85
88
|
*/
|
|
86
89
|
upsert(ctx, entity, conflictPaths, payload) {
|
|
87
90
|
const meta = getMeta(entity);
|
|
91
|
+
const alias = this.upsertNewRowAlias && this.escapeId(this.upsertNewRowAlias, true);
|
|
88
92
|
const updateCtx = this.createContext();
|
|
89
|
-
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `
|
|
93
|
+
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => alias ? `${alias}.${name}` : `VALUE(${name})`);
|
|
90
94
|
const returning = this.upsertReturning(entity);
|
|
91
95
|
if (update) {
|
|
92
96
|
this.appendInsertValues(ctx, entity, payload);
|
|
93
|
-
ctx.append(` ON DUPLICATE KEY UPDATE ${update}${returning}`);
|
|
97
|
+
ctx.append(`${alias ? ` AS ${alias}` : ''} ON DUPLICATE KEY UPDATE ${update}${returning}`);
|
|
94
98
|
ctx.pushValue(...updateCtx.values);
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
@@ -107,6 +111,13 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
107
111
|
upsertReturning(_entity) {
|
|
108
112
|
return '';
|
|
109
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* The alias the inserted row is given after the values list, and read back by the assignments.
|
|
116
|
+
* Undefined where the dialect has no such syntax - MariaDB, which reads that row through
|
|
117
|
+
* `VALUE(col)` instead: it renamed `VALUES()` in 10.3.3, the old name clashing with the standard
|
|
118
|
+
* table value constructors, where MySQL deprecated the function outright in favour of the alias.
|
|
119
|
+
*/
|
|
120
|
+
upsertNewRowAlias = undefined;
|
|
110
121
|
maxBindValues = 65535;
|
|
111
122
|
escape(value) {
|
|
112
123
|
return escapeMysqlSqlLiteral(value);
|
|
@@ -123,21 +134,6 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
123
134
|
ctx.addValue(search.$value);
|
|
124
135
|
ctx.append(')');
|
|
125
136
|
}
|
|
126
|
-
/**
|
|
127
|
-
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
|
|
128
|
-
* is a syntax error, so it is the keyword that changes rather than the access method.
|
|
129
|
-
*/
|
|
130
|
-
indexKeyword(index) {
|
|
131
|
-
return index.type === 'fulltext' ? 'FULLTEXT INDEX' : super.indexKeyword(index);
|
|
132
|
-
}
|
|
133
|
-
indexFeatures = new Set(['expression', 'prefixLength']);
|
|
134
|
-
/**
|
|
135
|
-
* No `FOR NO KEY UPDATE`/`FOR KEY SHARE`: those are PostgreSQL's weaker pair and the family has
|
|
136
|
-
* no equivalent, so asking for one is rejected rather than served a stronger lock.
|
|
137
|
-
*/
|
|
138
|
-
indexAccessMethod(index) {
|
|
139
|
-
return index.type && index.type !== 'fulltext' ? ` USING ${index.type}` : '';
|
|
140
|
-
}
|
|
141
137
|
numericCast(expr) {
|
|
142
138
|
return `CAST(${expr} AS DECIMAL)`;
|
|
143
139
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DialectFeatures, type EntityMeta, type FieldOptions, type
|
|
1
|
+
import { type DialectFeatures, type EntityMeta, type FieldOptions, type JsonColumnType, type Query, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type Type, type VectorDistance, type VectorOperatorMetric } from '../type/index.js';
|
|
2
2
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
3
3
|
/**
|
|
4
4
|
* Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
|
|
@@ -30,25 +30,24 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
30
30
|
* place so a dialect cannot end up with the operator but not the opclass. The key set is the single
|
|
31
31
|
* source of truth for which metrics the dialect supports at all: CockroachDB narrows it to three.
|
|
32
32
|
*/
|
|
33
|
-
readonly vectorMetrics: ReadonlyMap<VectorDistance,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorOperatorMetric>;
|
|
34
|
+
/** `SET LOCAL` applies to the enclosing transaction and to nothing at all without one. */
|
|
35
|
+
readonly vectorTuningNeedsTransaction = true;
|
|
36
|
+
/**
|
|
37
|
+
* The GUC each pgvector index type reads for "how much of the index to explore". They are not the
|
|
38
|
+
* same quantity - `ef_search` is a candidate-list size, `probes` a count of lists - which is why
|
|
39
|
+
* `$candidates` is documented in the index's own units rather than as a portable number.
|
|
40
|
+
*/
|
|
41
|
+
private static readonly ANN_SETTINGS;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
43
|
+
* `SET LOCAL hnsw.ef_search = N`, plus `hnsw.iterative_scan` when the query also filters by
|
|
44
|
+
* distance. Without iterative scan, HNSW returns its candidate list and the predicate then removes
|
|
45
|
+
* from it, so a `$near` can hand back fewer rows than qualify - the recall bug `$candidates`
|
|
46
|
+
* exists to answer. `strict_order`, never `relaxed_order`: the latter returns rows out of distance
|
|
47
|
+
* order, which would quietly contradict the `ORDER BY` the caller asked for.
|
|
48
48
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
protected indexTuning(index: IndexSchema): string;
|
|
49
|
+
vectorTuningStatements<E>(meta: EntityMeta<E>, q: Query<E>): readonly string[];
|
|
50
|
+
normalizeValue(value: unknown): unknown;
|
|
52
51
|
placeholder(index: number): string;
|
|
53
52
|
/**
|
|
54
53
|
* `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
|
|
@@ -90,6 +89,4 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
90
89
|
* Helper to add a JSON value to context with appropriate stringification and cast.
|
|
91
90
|
*/
|
|
92
91
|
private jsonVal;
|
|
93
|
-
/** Emit a pgvector-style distance expression: `"col" <op> $N::<vectorType>`. */
|
|
94
|
-
protected appendVectorSort<E>(ctx: QueryContext, meta: EntityMeta<E>, key: string, search: QueryVectorSearch): void;
|
|
95
92
|
}
|