uql-orm 0.56.0 → 0.58.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 +7 -9
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/cockroachdb/cockroachDialect.d.ts +5 -2
- package/dist/cockroachdb/cockroachDialect.js +2 -10
- package/dist/d1/d1SqliteDialect.d.ts +1 -0
- package/dist/d1/d1SqliteDialect.js +2 -0
- package/dist/dialect/abstractSqlDialect.d.ts +196 -33
- package/dist/dialect/abstractSqlDialect.js +410 -203
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +12 -7
- package/dist/dialect/hydrateColumn.d.ts +8 -2
- package/dist/dialect/hydrateColumn.js +33 -1
- package/dist/dialect/jsonSql.d.ts +13 -5
- package/dist/dialect/jsonSql.js +24 -7
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +31 -3
- package/dist/dialect/mysqlLikeSqlDialect.js +57 -5
- package/dist/dialect/pgLikeSqlDialect.d.ts +20 -20
- package/dist/dialect/pgLikeSqlDialect.js +23 -48
- package/dist/dialect/pgVectorMetrics.d.ts +13 -0
- package/dist/dialect/pgVectorMetrics.js +17 -0
- package/dist/dialect/queryContext.d.ts +3 -7
- package/dist/dialect/queryContext.js +13 -8
- package/dist/dialect/queryJoins.d.ts +8 -4
- package/dist/dialect/queryJoins.js +26 -11
- package/dist/dialect/vectorSqlDialect.d.ts +2 -2
- package/dist/dialect/vectorSqlDialect.js +2 -3
- package/dist/entity/decorator/bag.d.ts +2 -2
- package/dist/entity/decorator/entity.d.ts +8 -9
- package/dist/entity/decorator/entity.js +6 -7
- package/dist/entity/decorator/members.d.ts +7 -6
- package/dist/entity/decorator/members.js +2 -1
- package/dist/entity/metadata/definition.d.ts +16 -11
- package/dist/entity/metadata/definition.js +54 -42
- package/dist/http/handler.d.ts +2 -2
- package/dist/http/handler.js +0 -1
- package/dist/maria/mariaDialect.d.ts +13 -6
- package/dist/maria/mariaDialect.js +29 -9
- package/dist/migrate/cli.d.ts +2 -3
- package/dist/migrate/cli.js +2 -2
- package/dist/migrate/codegen/entityCodeGenerator.js +6 -4
- package/dist/migrate/codegen/entityTypes.d.ts +1 -1
- package/dist/migrate/codegen/entityTypes.js +4 -3
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +5 -4
- package/dist/migrate/codegen/indexDecoratorSource.js +17 -13
- package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
- package/dist/migrate/codegen/sourceLiteral.js +4 -0
- package/dist/migrate/ddl/index.d.ts +1 -5
- package/dist/migrate/ddl/index.js +14 -25
- package/dist/migrate/ddl/indexDdl.d.ts +11 -2
- package/dist/migrate/ddl/indexDdl.js +17 -1
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +10 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +10 -17
- package/dist/migrate/ddl/mysqlIndexDdl.js +16 -27
- package/dist/migrate/ddl/pgIndexDdl.d.ts +18 -8
- package/dist/migrate/ddl/pgIndexDdl.js +29 -12
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +3 -3
- package/dist/migrate/migrator.d.ts +4 -4
- package/dist/migrate/schemaGenerator.d.ts +8 -8
- package/dist/migrate/schemaGenerator.js +5 -7
- package/dist/migrate/schemaGeneratorAsync.d.ts +2 -3
- package/dist/mongo/mongoDialect.d.ts +31 -18
- package/dist/mongo/mongoDialect.js +146 -104
- package/dist/mongo/mongodbQuerier.d.ts +10 -17
- package/dist/mongo/mongodbQuerier.js +31 -106
- package/dist/mssql/mssqlDialect.d.ts +16 -0
- package/dist/mssql/mssqlDialect.js +26 -4
- package/dist/mysql/mysqlDialect.d.ts +2 -0
- package/dist/mysql/mysqlDialect.js +4 -0
- package/dist/querier/abstractQuerier.d.ts +20 -36
- package/dist/querier/abstractQuerier.js +35 -129
- package/dist/querier/abstractQuerierPool.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.d.ts +4 -17
- package/dist/querier/abstractSqlQuerier.js +40 -50
- package/dist/schema/canonicalType.js +4 -4
- package/dist/schema/indexDifferences.js +4 -4
- package/dist/schema/schemaASTBuilder.d.ts +3 -3
- package/dist/schema/schemaASTBuilder.js +32 -3
- package/dist/schema/schemaASTDiffer.js +5 -5
- package/dist/sqlite/sqliteDialect.d.ts +20 -1
- package/dist/sqlite/sqliteDialect.js +38 -7
- package/dist/turso/tursoDialect.d.ts +2 -0
- package/dist/turso/tursoDialect.js +2 -0
- package/dist/type/config.d.ts +3 -3
- package/dist/type/dialect.d.ts +4 -5
- package/dist/type/entity.d.ts +110 -69
- package/dist/type/migration.d.ts +7 -7
- package/dist/type/migratorDialect.d.ts +4 -0
- package/dist/type/querier.d.ts +6 -6
- package/dist/type/querierPool.d.ts +2 -2
- package/dist/type/query.d.ts +41 -72
- package/dist/type/query.js +10 -5
- package/dist/type/queryAggregate.d.ts +43 -34
- package/dist/type/queryAggregate.js +1 -1
- package/dist/type/queryWhere.d.ts +12 -9
- package/dist/type/universalQuerier.d.ts +4 -4
- package/dist/util/dialect.util.d.ts +4 -4
- package/dist/util/dialect.util.js +24 -15
- package/dist/util/field.util.d.ts +5 -0
- package/dist/util/field.util.js +19 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/relationQuery.util.d.ts +12 -65
- package/dist/util/relationQuery.util.js +27 -81
- package/dist/util/rowKey.util.d.ts +1 -11
- package/dist/util/rowKey.util.js +1 -13
- package/package.json +1 -1
- package/dist/querier/relationCount.d.ts +0 -16
- package/dist/querier/relationCount.js +0 -121
|
@@ -14,13 +14,11 @@ export declare const COUNT_ALIAS = "_uql_count";
|
|
|
14
14
|
export declare const TOTAL_ALIAS = "_uql_total";
|
|
15
15
|
/** The derived table a `$distinct` count wraps its deduplicated set in. MySQL requires the alias. */
|
|
16
16
|
export declare const DISTINCT_DERIVED_ALIAS = "_uql_distinct";
|
|
17
|
-
/**
|
|
18
|
-
export declare const
|
|
19
|
-
/** The
|
|
20
|
-
export declare const
|
|
21
|
-
/**
|
|
22
|
-
export declare const JSON_ELEM_ALIAS_PREFIX = "_uql_elem";
|
|
23
|
-
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS_PREFIX}. */
|
|
17
|
+
/** The row a Postgres relation aggregates whole: a LATERAL projection of the columns it answers under. */
|
|
18
|
+
export declare const RELATION_ROW_ALIAS = "_uql_row";
|
|
19
|
+
/** The alias an exploded JSON array element is read through, `_uql_elem_2` and on where one nests in another. */
|
|
20
|
+
export declare const JSON_ELEM_ALIAS = "_uql_elem";
|
|
21
|
+
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS}. */
|
|
24
22
|
export declare const JSON_PULL_ALIAS = "_uql_pull";
|
|
25
23
|
/** Prefix for the field a MongoDB relation lookup parks its result on, one per condition. */
|
|
26
24
|
export declare const REL_TEMP_PREFIX = "_uql_rel_";
|
|
@@ -40,3 +38,8 @@ export declare const UPSERT_SOURCE_ALIAS = "_uql_src";
|
|
|
40
38
|
* ranks a field that is not there as all-equal rather than failing, so a drift would go unnoticed.
|
|
41
39
|
*/
|
|
42
40
|
export declare function sortCountField(relKey: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* The column a relation's rows carry one sort term out in, beside the columns they answer under, for
|
|
43
|
+
* the aggregate reading them to order by: `_uql_sort_createdAt`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function relationSortColumn(path: string): string;
|
package/dist/dialect/aliases.js
CHANGED
|
@@ -14,13 +14,11 @@ export const COUNT_ALIAS = '_uql_count';
|
|
|
14
14
|
export const TOTAL_ALIAS = '_uql_total';
|
|
15
15
|
/** The derived table a `$distinct` count wraps its deduplicated set in. MySQL requires the alias. */
|
|
16
16
|
export const DISTINCT_DERIVED_ALIAS = '_uql_distinct';
|
|
17
|
-
/**
|
|
18
|
-
export const
|
|
19
|
-
/** The
|
|
20
|
-
export const
|
|
21
|
-
/**
|
|
22
|
-
export const JSON_ELEM_ALIAS_PREFIX = '_uql_elem';
|
|
23
|
-
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS_PREFIX}. */
|
|
17
|
+
/** The row a Postgres relation aggregates whole: a LATERAL projection of the columns it answers under. */
|
|
18
|
+
export const RELATION_ROW_ALIAS = '_uql_row';
|
|
19
|
+
/** The alias an exploded JSON array element is read through, `_uql_elem_2` and on where one nests in another. */
|
|
20
|
+
export const JSON_ELEM_ALIAS = '_uql_elem';
|
|
21
|
+
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS}. */
|
|
24
22
|
export const JSON_PULL_ALIAS = '_uql_pull';
|
|
25
23
|
/** Prefix for the field a MongoDB relation lookup parks its result on, one per condition. */
|
|
26
24
|
export const REL_TEMP_PREFIX = '_uql_rel_';
|
|
@@ -42,3 +40,10 @@ export const UPSERT_SOURCE_ALIAS = '_uql_src';
|
|
|
42
40
|
export function sortCountField(relKey) {
|
|
43
41
|
return `_uql_sort_count_${relKey}`;
|
|
44
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The column a relation's rows carry one sort term out in, beside the columns they answer under, for
|
|
45
|
+
* the aggregate reading them to order by: `_uql_sort_createdAt`.
|
|
46
|
+
*/
|
|
47
|
+
export function relationSortColumn(path) {
|
|
48
|
+
return `_uql_sort_${path}`;
|
|
49
|
+
}
|
|
@@ -2,9 +2,10 @@ import { type VectorCast } from './vectorCast.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* How a stored column is decoded on read: the inverse of `AbstractSqlDialect.persistKind`. `json`
|
|
4
4
|
* parses; a {@link VectorCast} says which literal; `boolean` undoes an engine with no boolean type,
|
|
5
|
-
* `number` and `bigint` a driver that hands a wide integer or a decimal back as text
|
|
5
|
+
* `number` and `bigint` a driver that hands a wide integer or a decimal back as text, and `date` and
|
|
6
|
+
* `bytes` a row that crossed JSON inside its parent's statement, which spells both as text.
|
|
6
7
|
*/
|
|
7
|
-
export type HydrateKind = 'json' | 'boolean' | 'number' | 'bigint' | VectorCast;
|
|
8
|
+
export type HydrateKind = 'json' | 'boolean' | 'number' | 'bigint' | 'date' | 'bytes' | VectorCast;
|
|
8
9
|
/**
|
|
9
10
|
* Decode one non-null cell. Kept beside {@link HydrateKind} rather than inlined into the querier's
|
|
10
11
|
* row walk, so classifying a column and decoding it stay one subject in one file.
|
|
@@ -14,3 +15,8 @@ export type HydrateKind = 'json' | 'boolean' | 'number' | 'bigint' | VectorCast;
|
|
|
14
15
|
* that does not match its column's format is returned untouched rather than replaced by a guess.
|
|
15
16
|
*/
|
|
16
17
|
export declare function decodeColumn(value: unknown, kind: HydrateKind): unknown;
|
|
18
|
+
/**
|
|
19
|
+
* What bytes crossing JSON start with, before two hex digits per byte: Postgres's own text for `bytea`,
|
|
20
|
+
* which every dialect spells, so a string a driver reads from a column on its own is never mistaken.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BYTES_PREFIX = "\\x";
|
|
@@ -13,6 +13,14 @@ export function decodeColumn(value, kind) {
|
|
|
13
13
|
// 0/1 from SQLite's INTEGER or MySQL's TINYINT(1). Already a boolean on Postgres.
|
|
14
14
|
return typeof value === 'boolean' ? value : Boolean(value);
|
|
15
15
|
}
|
|
16
|
+
if (kind === 'date') {
|
|
17
|
+
return typeof value === 'string' ? (parseDate(value) ?? value) : value;
|
|
18
|
+
}
|
|
19
|
+
if (kind === 'bytes') {
|
|
20
|
+
return typeof value === 'string' && value.startsWith(BYTES_PREFIX)
|
|
21
|
+
? hexBytes(value.slice(BYTES_PREFIX.length))
|
|
22
|
+
: value;
|
|
23
|
+
}
|
|
16
24
|
const text = asText(value);
|
|
17
25
|
if (kind === 'bigint') {
|
|
18
26
|
if (typeof value === 'bigint') {
|
|
@@ -32,7 +40,7 @@ export function decodeColumn(value, kind) {
|
|
|
32
40
|
return value;
|
|
33
41
|
}
|
|
34
42
|
if (kind === 'number') {
|
|
35
|
-
return
|
|
43
|
+
return decodeWideNumber(text);
|
|
36
44
|
}
|
|
37
45
|
if (kind === 'json') {
|
|
38
46
|
try {
|
|
@@ -45,6 +53,30 @@ export function decodeColumn(value, kind) {
|
|
|
45
53
|
}
|
|
46
54
|
return parseVectorLiteral(text, kind) ?? value;
|
|
47
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* An ISO 8601 timestamp as a `Date`, its fraction cut to the milliseconds one holds, and a bare date at
|
|
58
|
+
* local midnight, which is how `pg` reads a `date`. `undefined` for text that is neither.
|
|
59
|
+
*/
|
|
60
|
+
function parseDate(text) {
|
|
61
|
+
const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
62
|
+
const date = day
|
|
63
|
+
? new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]))
|
|
64
|
+
: new Date(text.replace(/(\.\d{3})\d+/, '$1'));
|
|
65
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* What bytes crossing JSON start with, before two hex digits per byte: Postgres's own text for `bytea`,
|
|
69
|
+
* which every dialect spells, so a string a driver reads from a column on its own is never mistaken.
|
|
70
|
+
*/
|
|
71
|
+
export const BYTES_PREFIX = '\\x';
|
|
72
|
+
/** Two hex digits per byte, back to bytes. */
|
|
73
|
+
function hexBytes(hex) {
|
|
74
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
75
|
+
for (let at = 0; at < bytes.length; at++) {
|
|
76
|
+
bytes[at] = Number.parseInt(hex.slice(at * 2, at * 2 + 2), 16);
|
|
77
|
+
}
|
|
78
|
+
return bytes;
|
|
79
|
+
}
|
|
48
80
|
/** Lazy so a consumer that never reads an encoded column never constructs one. */
|
|
49
81
|
let decoder;
|
|
50
82
|
/**
|
|
@@ -5,20 +5,28 @@ import type { FieldOptions, FieldType } from '../type/index.js';
|
|
|
5
5
|
* differently from an ANSI string literal.
|
|
6
6
|
*/
|
|
7
7
|
export declare function jsonPath(path: string, suffix?: string): string;
|
|
8
|
+
/** How many argument groups of `size` fit in one call beside its target, under `maxArgs`. */
|
|
9
|
+
export declare function groupsPerCall(maxArgs: number, size: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* `fn(target, ...groups)`, nested wherever one call would take more than `maxArgs` arguments: each call
|
|
12
|
+
* applies its share to what the call inside it returned, as `JSON_SET`, `JSON_REMOVE` and `json_insert`
|
|
13
|
+
* do. A group, such as a path and its value, stays in one call.
|
|
14
|
+
*/
|
|
15
|
+
export declare function chainedCall(fn: string, target: string, groups: readonly string[], size: number, maxArgs: number): string;
|
|
8
16
|
/**
|
|
9
17
|
* `FN(target, path, value, ...)` - the multi-pair JSON assignment shape shared by MySQL's
|
|
10
|
-
* `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`. `pathSuffix` appends an
|
|
11
|
-
* (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
|
|
18
|
+
* `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`, nested past `maxArgs`. `pathSuffix` appends an
|
|
19
|
+
* accessor per key (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
|
|
12
20
|
* `jsonScalarParam` bound to its `QueryContext`.
|
|
13
21
|
*/
|
|
14
|
-
export declare function jsonAssignCall(bindValue: (value: unknown) => string, fn: string, target: string, entries: Record<string, unknown>, pathSuffix?: string): string;
|
|
22
|
+
export declare function jsonAssignCall(bindValue: (value: unknown) => string, fn: string, target: string, entries: Record<string, unknown>, maxArgs: number, pathSuffix?: string): string;
|
|
15
23
|
/** The `$set` target: a nullable column needs a `COALESCE` fallback to build on. */
|
|
16
24
|
export declare function jsonSetTarget(expr: string, field: FieldOptions | undefined, empty: string): string;
|
|
17
25
|
/**
|
|
18
26
|
* `FN(expr, path, ...)` - the multi-path JSON removal shape shared by MySQL's `JSON_REMOVE` and
|
|
19
|
-
* SQLite's `json_remove`,
|
|
27
|
+
* SQLite's `json_remove`, nested past `maxArgs`.
|
|
20
28
|
*/
|
|
21
|
-
export declare function jsonRemoveCall(fn: string, expr: string, keys: readonly string[]): string;
|
|
29
|
+
export declare function jsonRemoveCall(fn: string, expr: string, keys: readonly string[], maxArgs: number): string;
|
|
22
30
|
/** `WHERE` is omitted for an empty `$elemMatch`, which asks only that the array has an element. */
|
|
23
31
|
export declare function jsonElemExists(from: string, conditions: readonly string[]): string;
|
|
24
32
|
/**
|
package/dist/dialect/jsonSql.js
CHANGED
|
@@ -9,15 +9,32 @@ export function jsonPath(path, suffix = '') {
|
|
|
9
9
|
const segments = path.split('.').map(escapeSingleQuotes).join('.');
|
|
10
10
|
return `'$.${segments}${suffix}'`;
|
|
11
11
|
}
|
|
12
|
+
/** How many argument groups of `size` fit in one call beside its target, under `maxArgs`. */
|
|
13
|
+
export function groupsPerCall(maxArgs, size) {
|
|
14
|
+
return Math.max(1, Math.floor((maxArgs - 1) / size));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `fn(target, ...groups)`, nested wherever one call would take more than `maxArgs` arguments: each call
|
|
18
|
+
* applies its share to what the call inside it returned, as `JSON_SET`, `JSON_REMOVE` and `json_insert`
|
|
19
|
+
* do. A group, such as a path and its value, stays in one call.
|
|
20
|
+
*/
|
|
21
|
+
export function chainedCall(fn, target, groups, size, maxArgs) {
|
|
22
|
+
const perCall = groupsPerCall(maxArgs, size);
|
|
23
|
+
let call = target;
|
|
24
|
+
for (let at = 0; at < groups.length; at += perCall) {
|
|
25
|
+
call = `${fn}(${call}, ${groups.slice(at, at + perCall).join(', ')})`;
|
|
26
|
+
}
|
|
27
|
+
return call;
|
|
28
|
+
}
|
|
12
29
|
/**
|
|
13
30
|
* `FN(target, path, value, ...)` - the multi-pair JSON assignment shape shared by MySQL's
|
|
14
|
-
* `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`. `pathSuffix` appends an
|
|
15
|
-
* (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
|
|
31
|
+
* `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`, nested past `maxArgs`. `pathSuffix` appends an
|
|
32
|
+
* accessor per key (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
|
|
16
33
|
* `jsonScalarParam` bound to its `QueryContext`.
|
|
17
34
|
*/
|
|
18
|
-
export function jsonAssignCall(bindValue, fn, target, entries, pathSuffix = '') {
|
|
35
|
+
export function jsonAssignCall(bindValue, fn, target, entries, maxArgs, pathSuffix = '') {
|
|
19
36
|
const pairs = Object.entries(entries).map(([key, value]) => `${jsonPath(key, pathSuffix)}, ${bindValue(value)}`);
|
|
20
|
-
return
|
|
37
|
+
return chainedCall(fn, target, pairs, 2, maxArgs);
|
|
21
38
|
}
|
|
22
39
|
/** The `$set` target: a nullable column needs a `COALESCE` fallback to build on. */
|
|
23
40
|
export function jsonSetTarget(expr, field, empty) {
|
|
@@ -25,10 +42,10 @@ export function jsonSetTarget(expr, field, empty) {
|
|
|
25
42
|
}
|
|
26
43
|
/**
|
|
27
44
|
* `FN(expr, path, ...)` - the multi-path JSON removal shape shared by MySQL's `JSON_REMOVE` and
|
|
28
|
-
* SQLite's `json_remove`,
|
|
45
|
+
* SQLite's `json_remove`, nested past `maxArgs`.
|
|
29
46
|
*/
|
|
30
|
-
export function jsonRemoveCall(fn, expr, keys) {
|
|
31
|
-
return
|
|
47
|
+
export function jsonRemoveCall(fn, expr, keys, maxArgs) {
|
|
48
|
+
return chainedCall(fn, expr, keys.map((key) => jsonPath(key)), 1, maxArgs);
|
|
32
49
|
}
|
|
33
50
|
/** `WHERE` is omitted for an empty `$elemMatch`, which asks only that the array has an element. */
|
|
34
51
|
export function jsonElemExists(from, conditions) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DialectFeatures, EntityMeta, FieldOptions, InsertIdSource, QueryConflictPaths, QueryContext, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
|
|
2
|
-
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
1
|
+
import type { DialectFeatures, EntityMeta, FieldOptions, InsertIdSource, Query, QueryConflictPaths, QueryContext, QueryOptions, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
|
|
2
|
+
import { AbstractSqlDialect, type DerivedRelation, type RelationRows } from './abstractSqlDialect.js';
|
|
3
3
|
/**
|
|
4
4
|
* Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
|
|
5
5
|
*
|
|
@@ -64,11 +64,39 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
64
64
|
*/
|
|
65
65
|
protected readonly upsertNewRowAlias: string | undefined;
|
|
66
66
|
readonly maxBindValues: number;
|
|
67
|
+
/**
|
|
68
|
+
* An ordered `GROUP_CONCAT` of each row's object, which reads as a JSON array: MySQL's `JSON_ARRAYAGG`
|
|
69
|
+
* takes no `ORDER BY`, and as a window over the rows it rebuilds the array for every one of them.
|
|
70
|
+
*/
|
|
71
|
+
protected appendRelationArray(ctx: QueryContext, rows: RelationRows): void;
|
|
72
|
+
/** A read's statement, with the settings it needs applied to it alone. */
|
|
73
|
+
find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions, totalAlias?: string): void;
|
|
74
|
+
/** A `$distinct` read's count, which reads the relations the read does. */
|
|
75
|
+
countDistinct<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?: QueryOptions): void;
|
|
76
|
+
private settled;
|
|
77
|
+
/**
|
|
78
|
+
* `name=value` for each variable a read's statement sets for itself alone. `GROUP_CONCAT`, and
|
|
79
|
+
* MariaDB's `JSON_ARRAYAGG` built on it, cut a relation's array at `group_concat_max_len`.
|
|
80
|
+
*/
|
|
81
|
+
protected statementSettings<E>(entity: Type<E>, q: Query<E>): string[];
|
|
82
|
+
/** `sql` with `settings` scoped to it, in this engine's spelling. */
|
|
83
|
+
protected abstract applySettings(sql: string, settings: readonly string[]): string;
|
|
84
|
+
/** `JSON_OBJECT` of each key and its value. */
|
|
85
|
+
protected jsonObject(pairs: DerivedRelation['pairs']): string;
|
|
86
|
+
/**
|
|
87
|
+
* A number and bytes cross JSON as text, where JSON would round the one and spell the other as base64,
|
|
88
|
+
* and a vector as the engine reads one back: text on MariaDB, which stores it packed.
|
|
89
|
+
*/
|
|
90
|
+
protected readonly carriedFields: {
|
|
91
|
+
numeric: (expr: string) => string;
|
|
92
|
+
blob: (expr: string) => string;
|
|
93
|
+
vector: (expr: string, field: FieldOptions) => string;
|
|
94
|
+
};
|
|
67
95
|
escape(value: unknown): string;
|
|
68
96
|
/**
|
|
69
97
|
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
70
98
|
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
71
|
-
* `@Index([...], { type: 'fulltext' })`.
|
|
99
|
+
* `@Index((post) => [...], { type: 'fulltext' })`.
|
|
72
100
|
*/
|
|
73
101
|
protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
74
102
|
protected numericCast(expr: string): string;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
2
|
import { textSearchFields } from '../util/index.js';
|
|
3
3
|
import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
4
|
-
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
4
|
+
import { AbstractSqlDialect, } from './abstractSqlDialect.js';
|
|
5
5
|
import { COUNT_ALIAS, JSON_PULL_ALIAS } from './aliases.js';
|
|
6
|
+
import { BYTES_PREFIX } from './hydrateColumn.js';
|
|
6
7
|
import { jsonAssignCall, jsonPath, jsonRemoveCall, jsonSetTarget } from './jsonSql.js';
|
|
7
|
-
|
|
8
|
+
import { aggregatesRelations } from './queryJoins.js';
|
|
9
|
+
/**
|
|
10
|
+
* The largest `BIGINT UNSIGNED`: the row count MySQL's manual gives for "all rows from the offset on",
|
|
11
|
+
* and the largest `group_concat_max_len` either engine takes.
|
|
12
|
+
*/
|
|
8
13
|
const MAX_LIMIT = BigInt.asUintN(64, -1n);
|
|
9
14
|
/**
|
|
10
15
|
* Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
|
|
@@ -126,13 +131,60 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
126
131
|
*/
|
|
127
132
|
upsertNewRowAlias = undefined;
|
|
128
133
|
maxBindValues = 65535;
|
|
134
|
+
/**
|
|
135
|
+
* An ordered `GROUP_CONCAT` of each row's object, which reads as a JSON array: MySQL's `JSON_ARRAYAGG`
|
|
136
|
+
* takes no `ORDER BY`, and as a window over the rows it rebuilds the array for every one of them.
|
|
137
|
+
*/
|
|
138
|
+
appendRelationArray(ctx, rows) {
|
|
139
|
+
const { from, pairs, order } = this.derivedRelation(ctx, rows);
|
|
140
|
+
const objects = `${this.jsonObject(pairs)}${order ? ` ORDER BY ${order}` : ''} SEPARATOR ','`;
|
|
141
|
+
ctx.append(`(SELECT COALESCE(CONCAT('[', GROUP_CONCAT(${objects}), ']'), '[]') FROM ${from})`);
|
|
142
|
+
}
|
|
143
|
+
/** A read's statement, with the settings it needs applied to it alone. */
|
|
144
|
+
find(ctx, entity, q = {}, opts, totalAlias) {
|
|
145
|
+
this.settled(ctx, entity, q, (statement) => super.find(statement, entity, q, opts, totalAlias));
|
|
146
|
+
}
|
|
147
|
+
/** A `$distinct` read's count, which reads the relations the read does. */
|
|
148
|
+
countDistinct(ctx, entity, q, opts) {
|
|
149
|
+
this.settled(ctx, entity, q, (statement) => super.countDistinct(statement, entity, q, opts));
|
|
150
|
+
}
|
|
151
|
+
settled(ctx, entity, q, build) {
|
|
152
|
+
const settings = this.statementSettings(entity, q);
|
|
153
|
+
if (!settings.length) {
|
|
154
|
+
build(ctx);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const statement = ctx.createFragment();
|
|
158
|
+
build(statement);
|
|
159
|
+
ctx.append(this.applySettings(statement.sql, settings));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `name=value` for each variable a read's statement sets for itself alone. `GROUP_CONCAT`, and
|
|
163
|
+
* MariaDB's `JSON_ARRAYAGG` built on it, cut a relation's array at `group_concat_max_len`.
|
|
164
|
+
*/
|
|
165
|
+
statementSettings(entity, q) {
|
|
166
|
+
return aggregatesRelations(getMeta(entity), q) ? [`group_concat_max_len=${MAX_LIMIT}`] : [];
|
|
167
|
+
}
|
|
168
|
+
/** `JSON_OBJECT` of each key and its value. */
|
|
169
|
+
jsonObject(pairs) {
|
|
170
|
+
return `JSON_OBJECT(${this.jsonObjectArgs(pairs)})`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A number and bytes cross JSON as text, where JSON would round the one and spell the other as base64,
|
|
174
|
+
* and a vector as the engine reads one back: text on MariaDB, which stores it packed.
|
|
175
|
+
*/
|
|
176
|
+
carriedFields = {
|
|
177
|
+
numeric: (expr) => `CAST(${expr} AS CHAR)`,
|
|
178
|
+
blob: (expr) => `CONCAT(${this.escape(BYTES_PREFIX)}, HEX(${expr}))`,
|
|
179
|
+
vector: (expr, field) => this.selectFieldExpr(expr, field),
|
|
180
|
+
};
|
|
129
181
|
escape(value) {
|
|
130
182
|
return escapeMysqlSqlLiteral(value);
|
|
131
183
|
}
|
|
132
184
|
/**
|
|
133
185
|
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
134
186
|
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
135
|
-
* `@Index([...], { type: 'fulltext' })`.
|
|
187
|
+
* `@Index((post) => [...], { type: 'fulltext' })`.
|
|
136
188
|
*/
|
|
137
189
|
appendTextSearch(ctx, _entity, meta, search) {
|
|
138
190
|
const columns = textSearchFields(meta, search).map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
@@ -169,7 +221,7 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
169
221
|
* applicable: it requires the target column as the direct `JSON_SET` input.
|
|
170
222
|
*/
|
|
171
223
|
jsonSet(ctx, expr, set, field) {
|
|
172
|
-
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set);
|
|
224
|
+
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set, this.maxFunctionArgs);
|
|
173
225
|
}
|
|
174
226
|
/**
|
|
175
227
|
* `JSON_MERGE_PRESERVE` concatenates arrays and creates absent keys, so every pushed key is
|
|
@@ -181,7 +233,7 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
181
233
|
return `JSON_MERGE_PRESERVE(${expr}, JSON_OBJECT(${entries.join(', ')}))`;
|
|
182
234
|
}
|
|
183
235
|
jsonUnset(_ctx, expr, unset) {
|
|
184
|
-
return jsonRemoveCall('JSON_REMOVE', expr, unset);
|
|
236
|
+
return jsonRemoveCall('JSON_REMOVE', expr, unset, this.maxFunctionArgs);
|
|
185
237
|
}
|
|
186
238
|
/**
|
|
187
239
|
* MySQL's `->`/`->>` take a full JSON path (`'$.a.b'`, never a bare key) and only apply to a
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type DialectFeatures, type EntityMeta, type FieldOptions, type JsonColumnType, type Query, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type Type
|
|
2
|
-
import { type
|
|
3
|
-
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
1
|
+
import { type DialectFeatures, type EntityMeta, type FieldOptions, type JsonColumnType, type Query, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type Type } from '../type/index.js';
|
|
2
|
+
import { AbstractSqlDialect, type RelationRows } from './abstractSqlDialect.js';
|
|
4
3
|
/**
|
|
5
4
|
* Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
|
|
6
5
|
* CockroachDB (wire- and SQL-compatible for everything below, including `TO_TSVECTOR`/`TO_TSQUERY`
|
|
@@ -23,28 +22,29 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
23
22
|
readonly rollbackTransactionCommand = "ROLLBACK";
|
|
24
23
|
readonly alterColumnStrategy = "separate-clauses";
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `
|
|
30
|
-
* prepared statement serves every page.
|
|
31
|
-
*
|
|
32
|
-
* Postgres, CockroachDB, PGlite, Neon and bun-sql inherit it together. **MySQL has `LATERAL` and must
|
|
33
|
-
* not use it** - it does not plan this as a correlated index loop and measured slower than both its
|
|
34
|
-
* own `UNION ALL` and a query per parent, which is why this is an override rather than a capability
|
|
35
|
-
* flag. [The design](../../../../architecture/populate-limits.md).
|
|
25
|
+
* `JSON_AGG` of each row whole, read through a LATERAL projection of the columns it answers under:
|
|
26
|
+
* the sort terms carried out beside them order it without joining the object, and no function call
|
|
27
|
+
* builds it, so a wide row meets no argument limit. `json` has no equality, so under a parent's
|
|
28
|
+
* `DISTINCT` the array is compared as `jsonb`.
|
|
36
29
|
*/
|
|
37
|
-
protected
|
|
30
|
+
protected appendRelationArray(ctx: QueryContext, rows: RelationRows): void;
|
|
31
|
+
/**
|
|
32
|
+
* What JSON would round or cannot spell crosses it as text, for the field's own decode to read back:
|
|
33
|
+
* a column declared numeric, whatever it decodes as, a vector, and bytes as hex.
|
|
34
|
+
*/
|
|
35
|
+
protected readonly carriedFields: {
|
|
36
|
+
numeric: (expr: string) => string;
|
|
37
|
+
vector: (expr: string) => string;
|
|
38
|
+
blob: (expr: string) => string;
|
|
39
|
+
};
|
|
38
40
|
/** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
|
|
39
41
|
protected readonly upsertUpdateBindsInPlace = true;
|
|
40
42
|
readonly insertIdSource = "returning";
|
|
41
43
|
readonly maxBindValues: number;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*/
|
|
47
|
-
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorOperatorMetric>;
|
|
44
|
+
readonly vectorMetrics: ReadonlyMap<import("../type/vector.js").VectorDistance, {
|
|
45
|
+
readonly op: string;
|
|
46
|
+
readonly opsSuffix: string;
|
|
47
|
+
}>;
|
|
48
48
|
/** `SET LOCAL` applies to the enclosing transaction and to nothing at all without one. */
|
|
49
49
|
readonly vectorTuningNeedsTransaction = true;
|
|
50
50
|
/**
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { canonicalToSql, fieldOptionsToCanonical } from '../schema/canonicalType.js';
|
|
2
1
|
import { QueryRaw, } from '../type/index.js';
|
|
3
2
|
import { hasVectorNear, textSearchFields } from '../util/dialect.util.js';
|
|
4
|
-
import { raw } from '../util/raw.js';
|
|
5
|
-
import { queryNarrowedTo } from '../util/relationQuery.util.js';
|
|
6
3
|
import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
7
4
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
8
|
-
import { JSON_PULL_ALIAS,
|
|
5
|
+
import { JSON_PULL_ALIAS, RELATION_ROW_ALIAS } from './aliases.js';
|
|
6
|
+
import { BYTES_PREFIX } from './hydrateColumn.js';
|
|
9
7
|
import { jsonSetTarget } from './jsonSql.js';
|
|
8
|
+
import { PG_VECTOR_METRICS } from './pgVectorMetrics.js';
|
|
10
9
|
import { resolveVectorCast, toSparsevecLiteral } from './vectorCast.js';
|
|
11
10
|
/**
|
|
12
11
|
* Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
|
|
@@ -52,56 +51,32 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
52
51
|
rollbackTransactionCommand = 'ROLLBACK';
|
|
53
52
|
alterColumnStrategy = 'separate-clauses';
|
|
54
53
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
59
|
-
* prepared statement serves every page.
|
|
60
|
-
*
|
|
61
|
-
* Postgres, CockroachDB, PGlite, Neon and bun-sql inherit it together. **MySQL has `LATERAL` and must
|
|
62
|
-
* not use it** - it does not plan this as a correlated index loop and measured slower than both its
|
|
63
|
-
* own `UNION ALL` and a query per parent, which is why this is an override rather than a capability
|
|
64
|
-
* flag. [The design](../../../../architecture/populate-limits.md).
|
|
54
|
+
* `JSON_AGG` of each row whole, read through a LATERAL projection of the columns it answers under:
|
|
55
|
+
* the sort terms carried out beside them order it without joining the object, and no function call
|
|
56
|
+
* builds it, so a wide row meets no argument limit. `json` has no equality, so under a parent's
|
|
57
|
+
* `DISTINCT` the array is compared as `jsonb`.
|
|
65
58
|
*/
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// declares one, rather than the child's foreign key, which would have to be resolved through the
|
|
73
|
-
// reference it takes its own type from.
|
|
74
|
-
const sources = joins.map(({ parent }) => {
|
|
75
|
-
const field = parentFields[parent];
|
|
76
|
-
if (!field) {
|
|
77
|
-
throw new TypeError(`cannot page a relation per parent: '${parent}' is not a field of the parent entity`);
|
|
78
|
-
}
|
|
79
|
-
const values = parents.map((it) => it[parent]);
|
|
80
|
-
return `${this.addValue(ctx.values, values)}::${canonicalToSql(fieldOptionsToCanonical(field), this)}[]`;
|
|
81
|
-
});
|
|
82
|
-
const rowSource = `UNNEST(${sources.join(', ')}) AS ${keys}(${joins.map((_, index) => `k${index}`).join(', ')})`;
|
|
83
|
-
// The keys come from the row source rather than as values, which is the whole point of correlating:
|
|
84
|
-
// one branch, planned once, instead of one per parent.
|
|
85
|
-
const correlated = Object.fromEntries(joins.map(({ joined }, index) => [joined, raw(({ ctx: inner }) => inner.append(column(index)))]));
|
|
86
|
-
ctx.append(`SELECT ${branch}.* FROM ${rowSource} JOIN LATERAL (`);
|
|
87
|
-
this.find(ctx, entity, queryNarrowedTo(q, correlated));
|
|
88
|
-
ctx.append(`) ${branch} ON TRUE`);
|
|
59
|
+
appendRelationArray(ctx, rows) {
|
|
60
|
+
const { from, pairs, order } = this.derivedRelation(ctx, rows);
|
|
61
|
+
const row = this.escapeId(RELATION_ROW_ALIAS, true);
|
|
62
|
+
const columns = pairs.map(([, column]) => column).join(', ');
|
|
63
|
+
const array = /*sql*/ `(SELECT COALESCE(JSON_AGG(${row}${order ? ` ORDER BY ${order}` : ''}), '[]'::json) FROM ${from} CROSS JOIN LATERAL (SELECT ${columns}) ${row})`;
|
|
64
|
+
ctx.append(rows.distinct ? `${array}::jsonb` : array);
|
|
89
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* What JSON would round or cannot spell crosses it as text, for the field's own decode to read back:
|
|
68
|
+
* a column declared numeric, whatever it decodes as, a vector, and bytes as hex.
|
|
69
|
+
*/
|
|
70
|
+
carriedFields = {
|
|
71
|
+
numeric: (expr) => `${expr}::text`,
|
|
72
|
+
vector: (expr) => `${expr}::text`,
|
|
73
|
+
blob: (expr) => `${this.escape(BYTES_PREFIX)} || ENCODE(${expr}, 'hex')`,
|
|
74
|
+
};
|
|
90
75
|
/** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
|
|
91
76
|
upsertUpdateBindsInPlace = true;
|
|
92
77
|
insertIdSource = 'returning';
|
|
93
78
|
maxBindValues = 65535;
|
|
94
|
-
|
|
95
|
-
* Each metric's pgvector distance operator and the operator-class suffix its index takes, in one
|
|
96
|
-
* place so a dialect cannot end up with the operator but not the opclass. The key set is the single
|
|
97
|
-
* source of truth for which metrics the dialect supports at all: CockroachDB narrows it to three.
|
|
98
|
-
*/
|
|
99
|
-
vectorMetrics = new Map([
|
|
100
|
-
['cosine', { op: '<=>', opsSuffix: 'cosine' }],
|
|
101
|
-
['l2', { op: '<->', opsSuffix: 'l2' }],
|
|
102
|
-
['inner', { op: '<#>', opsSuffix: 'ip' }],
|
|
103
|
-
['l1', { op: '<+>', opsSuffix: 'l1' }],
|
|
104
|
-
]);
|
|
79
|
+
vectorMetrics = PG_VECTOR_METRICS;
|
|
105
80
|
/** `SET LOCAL` applies to the enclosing transaction and to nothing at all without one. */
|
|
106
81
|
vectorTuningNeedsTransaction = true;
|
|
107
82
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { VectorDistance, VectorOperatorMetric } from '../type/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Each metric's pgvector distance operator and the operator-class suffix its index takes, in one list
|
|
4
|
+
* so a dialect cannot have the operator but not the opclass. Its own module because the two ends live
|
|
5
|
+
* apart: the operator on the dialect, the opclass in the migrator's index DDL.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PG_VECTOR_METRICS: ReadonlyMap<VectorDistance, VectorOperatorMetric>;
|
|
8
|
+
/**
|
|
9
|
+
* CockroachDB's three: `<+>` and `vector_l1_ops` answer "unimplemented: operator class ... is not
|
|
10
|
+
* supported" (verified live on v26.2), tracked at https://github.com/cockroachdb/cockroach/issues/147839.
|
|
11
|
+
* Re-check that issue before adding `l1`; it is omitted on purpose.
|
|
12
|
+
*/
|
|
13
|
+
export declare const COCKROACH_VECTOR_METRICS: ReadonlyMap<VectorDistance, VectorOperatorMetric>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each metric's pgvector distance operator and the operator-class suffix its index takes, in one list
|
|
3
|
+
* so a dialect cannot have the operator but not the opclass. Its own module because the two ends live
|
|
4
|
+
* apart: the operator on the dialect, the opclass in the migrator's index DDL.
|
|
5
|
+
*/
|
|
6
|
+
export const PG_VECTOR_METRICS = new Map([
|
|
7
|
+
['cosine', { op: '<=>', opsSuffix: 'cosine' }],
|
|
8
|
+
['l2', { op: '<->', opsSuffix: 'l2' }],
|
|
9
|
+
['inner', { op: '<#>', opsSuffix: 'ip' }],
|
|
10
|
+
['l1', { op: '<+>', opsSuffix: 'l1' }],
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* CockroachDB's three: `<+>` and `vector_l1_ops` answer "unimplemented: operator class ... is not
|
|
14
|
+
* supported" (verified live on v26.2), tracked at https://github.com/cockroachdb/cockroach/issues/147839.
|
|
15
|
+
* Re-check that issue before adding `l1`; it is omitted on purpose.
|
|
16
|
+
*/
|
|
17
|
+
export const COCKROACH_VECTOR_METRICS = new Map([...PG_VECTOR_METRICS].filter(([metric]) => metric !== 'l1'));
|
|
@@ -11,14 +11,14 @@ export declare class SqlQueryContext implements QueryContext {
|
|
|
11
11
|
private readonly statement?;
|
|
12
12
|
private readonly sqlChunks;
|
|
13
13
|
private readonly params;
|
|
14
|
-
private
|
|
14
|
+
private readonly tableAliases;
|
|
15
15
|
/**
|
|
16
16
|
* @param dialect The SQL dialect used to determine how values should be formatted as placeholders.
|
|
17
17
|
* @param params An existing values array to bind into instead of a fresh one - shared by a
|
|
18
18
|
* fragment context built via {@link AbstractSqlDialect.buildFragment}, so a bound value's
|
|
19
19
|
* placeholder is numbered correctly against the real query from the moment it's added, rather
|
|
20
20
|
* than needing to be reconciled after the fact.
|
|
21
|
-
* @param statement The context this one renders a fragment of, which owns the
|
|
21
|
+
* @param statement The context this one renders a fragment of, which owns the claimed aliases: a
|
|
22
22
|
* fragment is part of one statement, so its aliases have to be unique across the whole of it.
|
|
23
23
|
*/
|
|
24
24
|
constructor(dialect: QueryDialect, params?: unknown[], statement?: SqlQueryContext | undefined);
|
|
@@ -46,11 +46,7 @@ export declare class SqlQueryContext implements QueryContext {
|
|
|
46
46
|
* @returns The current context instance for method chaining.
|
|
47
47
|
*/
|
|
48
48
|
pushValue(...values: unknown[]): this;
|
|
49
|
-
|
|
50
|
-
* A fresh alias unique within the statement being built, e.g. `nextAlias('_uql_elem')` ->
|
|
51
|
-
* `'_uql_elem_1'`, `'_uql_elem_2'`, ...
|
|
52
|
-
*/
|
|
53
|
-
nextAlias(prefix: string): string;
|
|
49
|
+
claimAlias(name: string, parent?: string): string;
|
|
54
50
|
/**
|
|
55
51
|
* Returns the complete SQL query string by joining all accumulated chunks.
|
|
56
52
|
*/
|
|
@@ -10,14 +10,14 @@ export class SqlQueryContext {
|
|
|
10
10
|
statement;
|
|
11
11
|
sqlChunks = [];
|
|
12
12
|
params;
|
|
13
|
-
|
|
13
|
+
tableAliases = new Set();
|
|
14
14
|
/**
|
|
15
15
|
* @param dialect The SQL dialect used to determine how values should be formatted as placeholders.
|
|
16
16
|
* @param params An existing values array to bind into instead of a fresh one - shared by a
|
|
17
17
|
* fragment context built via {@link AbstractSqlDialect.buildFragment}, so a bound value's
|
|
18
18
|
* placeholder is numbered correctly against the real query from the moment it's added, rather
|
|
19
19
|
* than needing to be reconciled after the fact.
|
|
20
|
-
* @param statement The context this one renders a fragment of, which owns the
|
|
20
|
+
* @param statement The context this one renders a fragment of, which owns the claimed aliases: a
|
|
21
21
|
* fragment is part of one statement, so its aliases have to be unique across the whole of it.
|
|
22
22
|
*/
|
|
23
23
|
constructor(dialect, params = [], statement) {
|
|
@@ -62,12 +62,17 @@ export class SqlQueryContext {
|
|
|
62
62
|
this.params.push(...values.map((v) => this.dialect.normalizeValue(v)));
|
|
63
63
|
return this;
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
claimAlias(name, parent) {
|
|
66
|
+
if (this.statement) {
|
|
67
|
+
return this.statement.claimAlias(name, parent);
|
|
68
|
+
}
|
|
69
|
+
const reserved = parent?.toLowerCase();
|
|
70
|
+
let alias = name;
|
|
71
|
+
for (let n = 2; this.tableAliases.has(alias.toLowerCase()) || alias.toLowerCase() === reserved; n++) {
|
|
72
|
+
alias = `${name}_${n}`;
|
|
73
|
+
}
|
|
74
|
+
this.tableAliases.add(alias.toLowerCase());
|
|
75
|
+
return alias;
|
|
71
76
|
}
|
|
72
77
|
/**
|
|
73
78
|
* Returns the complete SQL query string by joining all accumulated chunks.
|