uql-orm 0.21.0 → 0.23.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 +5 -4
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
- package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
- package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
- package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
- package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
- package/dist/cockroachdb/cockroachDialect.js +33 -18
- package/dist/d1/d1Querier.d.ts +4 -3
- package/dist/d1/d1Querier.js +0 -3
- package/dist/d1/d1SqliteDialect.d.ts +7 -0
- package/dist/d1/d1SqliteDialect.js +9 -0
- package/dist/dialect/abstractDialect.d.ts +8 -3
- package/dist/dialect/abstractDialect.js +9 -4
- package/dist/dialect/abstractSqlDialect.d.ts +49 -8
- package/dist/dialect/abstractSqlDialect.js +95 -47
- package/dist/dialect/index.d.ts +1 -0
- package/dist/dialect/index.js +1 -0
- package/dist/dialect/indexSqlDialect.d.ts +57 -0
- package/dist/dialect/indexSqlDialect.js +101 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +26 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +57 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +37 -22
- package/dist/dialect/pgLikeSqlDialect.js +97 -68
- package/dist/dialect/vectorCast.d.ts +21 -0
- package/dist/dialect/vectorCast.js +28 -0
- package/dist/dialect/vectorSqlDialect.d.ts +10 -3
- package/dist/dialect/vectorSqlDialect.js +19 -8
- package/dist/entity/decorator/bag.d.ts +35 -0
- package/dist/entity/decorator/bag.js +54 -0
- package/dist/entity/decorator/entity.d.ts +28 -2
- package/dist/entity/decorator/entity.js +40 -2
- package/dist/entity/decorator/members.d.ts +51 -0
- package/dist/entity/decorator/members.js +51 -0
- package/dist/entity/index.d.ts +3 -1
- package/dist/entity/index.js +3 -1
- package/dist/entity/metadata/definition.d.ts +23 -8
- package/dist/entity/metadata/definition.js +56 -100
- package/dist/http/contract.js +1 -2
- package/dist/http/handler.js +5 -26
- package/dist/http/query.js +1 -1
- package/dist/libsql/libsqlDialect.d.ts +12 -2
- package/dist/libsql/libsqlDialect.js +14 -2
- package/dist/libsql/libsqlQuerier.d.ts +9 -22
- package/dist/libsql/libsqlQuerier.js +9 -85
- package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
- package/dist/libsql/libsqlQuerierPool.js +17 -15
- package/dist/maria/mariaDialect.d.ts +26 -4
- package/dist/maria/mariaDialect.js +54 -15
- package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
- package/dist/migrate/acquireQuerierForMigrations.js +28 -0
- package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
- package/dist/migrate/builder/migrationBuilder.js +58 -76
- package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
- package/dist/migrate/builder/splitSqlStatements.js +0 -2
- package/dist/migrate/builder/tableBuilder.d.ts +12 -2
- package/dist/migrate/builder/tableBuilder.js +71 -119
- package/dist/migrate/builder/types.d.ts +30 -41
- package/dist/migrate/cli-config.js +24 -8
- package/dist/migrate/cli.d.ts +3 -2
- package/dist/migrate/cli.js +26 -49
- package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
- package/dist/migrate/codegen/entityCodeGenerator.js +8 -67
- package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
- package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
- package/dist/migrate/codegen/index.d.ts +0 -3
- package/dist/migrate/codegen/index.js +0 -6
- package/dist/migrate/drift/driftDetector.d.ts +10 -1
- package/dist/migrate/drift/driftDetector.js +22 -5
- package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
- package/dist/migrate/generator/indexNodeToSchema.js +14 -0
- package/dist/migrate/generator/mongoCommand.d.ts +57 -0
- package/dist/migrate/generator/mongoCommand.js +26 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
- package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
- package/dist/migrate/index.d.ts +0 -1
- package/dist/migrate/index.js +0 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +30 -11
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +61 -57
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
- package/dist/migrate/introspection/mongoIntrospector.js +9 -15
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
- package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
- package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
- package/dist/migrate/introspection/postgresIntrospector.js +4 -10
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
- package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
- package/dist/migrate/migrator.d.ts +23 -3
- package/dist/migrate/migrator.js +101 -150
- package/dist/migrate/schemaGenerator.d.ts +26 -37
- package/dist/migrate/schemaGenerator.js +69 -174
- package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
- package/dist/migrate/schemaGeneratorAsync.js +3 -3
- package/dist/migrate/storage/databaseStorage.d.ts +2 -0
- package/dist/migrate/storage/databaseStorage.js +12 -27
- package/dist/mongo/mongoDialect.d.ts +2 -0
- package/dist/mongo/mongoDialect.js +14 -3
- package/dist/mongo/mongodbQuerier.js +171 -229
- package/dist/mysql/mysqlDialect.d.ts +8 -0
- package/dist/mysql/mysqlDialect.js +13 -0
- package/dist/nestjs/uqlContextInterceptor.js +58 -22
- package/dist/nestjs/uqlModule.d.ts +2 -11
- package/dist/nestjs/uqlModule.js +93 -44
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +28 -0
- package/dist/querier/abstractQuerier.js +47 -22
- package/dist/querier/abstractQuerierPool.d.ts +3 -0
- package/dist/querier/abstractQuerierPool.js +5 -3
- package/dist/querier/abstractSqlQuerier.d.ts +1 -3
- package/dist/querier/abstractSqlQuerier.js +48 -96
- package/dist/querier/index.d.ts +3 -1
- package/dist/querier/index.js +3 -1
- package/dist/querier/querierContext.browser.d.ts +12 -0
- package/dist/querier/querierContext.browser.js +18 -0
- package/dist/querier/querierContext.d.ts +22 -0
- package/dist/querier/querierContext.js +42 -0
- package/dist/querier/queryError.d.ts +22 -0
- package/dist/querier/queryError.js +20 -0
- package/dist/querier/transactional.d.ts +26 -0
- package/dist/querier/transactional.js +43 -0
- package/dist/schema/canonicalType.d.ts +14 -2
- package/dist/schema/canonicalType.js +41 -31
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.d.ts +9 -2
- package/dist/schema/schemaASTBuilder.js +58 -49
- package/dist/schema/types.d.ts +10 -58
- package/dist/sqlite/abstractSqliteQuerier.d.ts +46 -0
- package/dist/sqlite/abstractSqliteQuerier.js +44 -0
- package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
- package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
- package/dist/sqlite/hranaQuerier.d.ts +54 -0
- package/dist/sqlite/hranaQuerier.js +68 -0
- package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
- package/dist/sqlite/hranaQuerierPool.js +25 -0
- package/dist/sqlite/index.d.ts +3 -1
- package/dist/sqlite/index.js +3 -1
- package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
- package/dist/sqlite/localSqliteQuerierPool.js +34 -0
- package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
- package/dist/sqlite/nodeSqliteAdapter.js +28 -0
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
- package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
- package/dist/sqlite/sqliteDialect.d.ts +18 -5
- package/dist/sqlite/sqliteDialect.js +23 -36
- package/dist/sqlite/sqliteQuerier.d.ts +16 -9
- package/dist/sqlite/sqliteQuerier.js +4 -27
- package/dist/sqlite/sqliteQuerierPool.d.ts +11 -14
- package/dist/sqlite/sqliteQuerierPool.js +16 -24
- package/dist/turso/index.d.ts +3 -0
- package/dist/turso/index.js +3 -0
- package/dist/turso/local.d.ts +3 -0
- package/dist/turso/local.js +3 -0
- package/dist/turso/tursoDialect.d.ts +15 -0
- package/dist/turso/tursoDialect.js +18 -0
- package/dist/turso/tursoLocalQuerier.d.ts +25 -0
- package/dist/turso/tursoLocalQuerier.js +20 -0
- package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
- package/dist/turso/tursoLocalQuerierPool.js +39 -0
- package/dist/turso/tursoQuerier.d.ts +10 -0
- package/dist/turso/tursoQuerier.js +10 -0
- package/dist/turso/tursoQuerierPool.d.ts +39 -0
- package/dist/turso/tursoQuerierPool.js +38 -0
- package/dist/type/config.d.ts +0 -6
- package/dist/type/dialect.d.ts +12 -4
- package/dist/type/dialect.js +7 -1
- package/dist/type/entity.d.ts +154 -26
- package/dist/type/migration.d.ts +32 -23
- package/dist/type/querier.d.ts +6 -0
- package/dist/type/vector.d.ts +5 -2
- package/dist/util/hook.util.js +1 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/indexColumn.util.d.ts +6 -0
- package/dist/util/indexColumn.util.js +26 -0
- package/package.json +21 -16
- package/dist/entity/decorator/field.d.ts +0 -2
- package/dist/entity/decorator/field.js +0 -7
- package/dist/entity/decorator/filter.d.ts +0 -7
- package/dist/entity/decorator/filter.js +0 -11
- package/dist/entity/decorator/hook.d.ts +0 -7
- package/dist/entity/decorator/hook.js +0 -14
- package/dist/entity/decorator/id.d.ts +0 -2
- package/dist/entity/decorator/id.js +0 -7
- package/dist/entity/decorator/index-decorator.d.ts +0 -29
- package/dist/entity/decorator/index-decorator.js +0 -26
- package/dist/entity/decorator/index.d.ts +0 -8
- package/dist/entity/decorator/index.js +0 -8
- package/dist/entity/decorator/relation.d.ts +0 -8
- package/dist/entity/decorator/relation.js +0 -19
- package/dist/migrate/codegen/entityMerger.d.ts +0 -114
- package/dist/migrate/codegen/entityMerger.js +0 -293
- package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
- package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
- package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
- package/dist/migrate/codegen/smartRelationDetector.js +0 -134
- package/dist/migrate/generator/index.d.ts +0 -2
- package/dist/migrate/generator/index.js +0 -2
- package/dist/migrate/sync/index.d.ts +0 -6
- package/dist/migrate/sync/index.js +0 -6
- package/dist/migrate/sync/schemaSync.d.ts +0 -131
- package/dist/migrate/sync/schemaSync.js +0 -259
- package/dist/querier/decorator/index.d.ts +0 -4
- package/dist/querier/decorator/index.js +0 -4
- package/dist/querier/decorator/injectQuerier.d.ts +0 -3
- package/dist/querier/decorator/injectQuerier.js +0 -33
- package/dist/querier/decorator/log.d.ts +0 -32
- package/dist/querier/decorator/log.js +0 -52
- package/dist/querier/decorator/serialized.d.ts +0 -6
- package/dist/querier/decorator/serialized.js +0 -13
- package/dist/querier/decorator/transactional.d.ts +0 -6
- package/dist/querier/decorator/transactional.js +0 -48
- package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
- package/dist/sqlite/betterSqlite3Dialect.js +0 -9
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
2
|
import { QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
|
|
3
|
-
import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject,
|
|
3
|
+
import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, hasMultipleKeys, isJsonType, isJsonUpdateOp, isOperatorObject, isOperatorOnlyObject, isPopulatingRelations, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationAtKey, parseRelationSize, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
4
|
import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
5
|
+
import { IndexSqlDialect } from './indexSqlDialect.js';
|
|
5
6
|
import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
|
|
6
7
|
import { JSON_ELEM_ALIAS_PREFIX, jsonElemExists } from './jsonSql.js';
|
|
7
8
|
import { SqlQueryContext } from './queryContext.js';
|
|
8
|
-
import {
|
|
9
|
-
export class AbstractSqlDialect extends
|
|
9
|
+
import { isVectorFieldType } from './vectorCast.js';
|
|
10
|
+
export class AbstractSqlDialect extends IndexSqlDialect {
|
|
10
11
|
isolationLevelStrategy = 'inline';
|
|
11
12
|
alterColumnStrategy = 'single-statement';
|
|
12
13
|
alterColumnSyntax = 'ALTER COLUMN';
|
|
@@ -135,7 +136,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
135
136
|
const field = meta.fields[key];
|
|
136
137
|
if (!field)
|
|
137
138
|
return;
|
|
138
|
-
const columnName = this.resolveColumnName(key, field);
|
|
139
139
|
if (field.virtual) {
|
|
140
140
|
this.getRawValue(ctx, {
|
|
141
141
|
value: raw(field.virtual[RAW_VALUE], key),
|
|
@@ -143,17 +143,35 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
143
143
|
escapedPrefix,
|
|
144
144
|
autoPrefixAlias: opts.autoPrefixAlias,
|
|
145
145
|
});
|
|
146
|
+
return;
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
const columnName = this.resolveColumnName(key, field);
|
|
149
|
+
const column = escapedPrefix + this.escapeId(columnName);
|
|
150
|
+
const expr = this.selectFieldExpr(column, field);
|
|
151
|
+
ctx.append(expr);
|
|
152
|
+
// An expression needs the alias too, or the row comes back keyed by the expression text.
|
|
153
|
+
if (expr !== column || columnName !== key || opts.autoPrefixAlias) {
|
|
154
|
+
ctx.append(' ' + this.escapeId(prefix + key, true));
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
});
|
|
156
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* The expression a scalar field is read through, the plain column by default. MariaDB reads a
|
|
161
|
+
* vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
|
|
162
|
+
*/
|
|
163
|
+
selectFieldExpr(escapedColumn, _field) {
|
|
164
|
+
return escapedColumn;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
|
|
168
|
+
* (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
|
|
169
|
+
* the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
|
|
170
|
+
* than inheriting another engine's syntax.
|
|
171
|
+
*/
|
|
172
|
+
appendTextSearch(_ctx, _entity, _meta, _search) {
|
|
173
|
+
throw new TypeError(`${this.dialectName} does not support $text full-text search`);
|
|
174
|
+
}
|
|
157
175
|
select(ctx, entity, select, exclude, populate, opts = {}, distinct, sort) {
|
|
158
176
|
const meta = getMeta(entity);
|
|
159
177
|
const tableName = this.resolveTableName(entity, meta);
|
|
@@ -296,16 +314,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
296
314
|
return;
|
|
297
315
|
}
|
|
298
316
|
if (key === '$text') {
|
|
299
|
-
|
|
300
|
-
const searchFields = search.$fields ?? getFieldKeys(meta.fields);
|
|
301
|
-
const fields = searchFields.map((fKey) => {
|
|
302
|
-
const field = meta.fields[fKey];
|
|
303
|
-
const columnName = this.resolveColumnName(fKey, field);
|
|
304
|
-
return this.escapeId(columnName);
|
|
305
|
-
});
|
|
306
|
-
ctx.append(`MATCH(${fields.join(', ')}) AGAINST(`);
|
|
307
|
-
ctx.addValue(search.$value);
|
|
308
|
-
ctx.append(')');
|
|
317
|
+
this.appendTextSearch(ctx, entity, meta, val);
|
|
309
318
|
return;
|
|
310
319
|
}
|
|
311
320
|
if (key === '$and' || key === '$or' || key === '$not' || key === '$nor') {
|
|
@@ -911,6 +920,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
911
920
|
this.search(ctx, entity, q, opts);
|
|
912
921
|
}
|
|
913
922
|
insert(ctx, entity, payload, opts) {
|
|
923
|
+
this.appendInsertValues(ctx, entity, payload, opts);
|
|
924
|
+
// Every engine whose ids come back from the statement itself wants the same clause, so it is
|
|
925
|
+
// appended once here instead of in an identical `insert` override per dialect.
|
|
926
|
+
if (this.insertIdSource === 'returning') {
|
|
927
|
+
ctx.append(` ${this.returningId(entity)}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
|
|
932
|
+
* {@link insert}: their own clause has to come before the `RETURNING`, not after it.
|
|
933
|
+
*/
|
|
934
|
+
appendInsertValues(ctx, entity, payload, opts) {
|
|
914
935
|
const meta = getMeta(entity);
|
|
915
936
|
const payloads = fillOnFields(meta, payload, 'onInsert');
|
|
916
937
|
const keys = getInsertFieldKeys(meta, payloads);
|
|
@@ -919,10 +940,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
919
940
|
const width = keys.length;
|
|
920
941
|
const fields = new Array(width);
|
|
921
942
|
const columns = new Array(width);
|
|
943
|
+
const kinds = new Array(width);
|
|
922
944
|
for (let i = 0; i < width; i++) {
|
|
923
945
|
const key = keys[i];
|
|
924
|
-
|
|
946
|
+
const field = meta.fields[key];
|
|
947
|
+
fields[i] = field;
|
|
925
948
|
columns[i] = this.escapedColumnName(meta, key);
|
|
949
|
+
kinds[i] = this.persistKind(field);
|
|
926
950
|
}
|
|
927
951
|
const tableName = this.resolveTableName(entity, meta);
|
|
928
952
|
ctx.append(`INSERT INTO ${this.escapeId(tableName)} (${columns.join(', ')}) VALUES (`);
|
|
@@ -939,8 +963,12 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
939
963
|
if (value === undefined) {
|
|
940
964
|
this.appendDefaultInsertValue(ctx, fields[i]);
|
|
941
965
|
}
|
|
966
|
+
else if (kinds[i] === 'plain' && !(value instanceof QueryRaw)) {
|
|
967
|
+
// The overwhelmingly common case in a bulk insert, so it binds without a dispatch.
|
|
968
|
+
ctx.addValue(value);
|
|
969
|
+
}
|
|
942
970
|
else {
|
|
943
|
-
this.
|
|
971
|
+
this.writePersistableValue(ctx, kinds[i], fields[i], value);
|
|
944
972
|
}
|
|
945
973
|
}
|
|
946
974
|
}
|
|
@@ -978,22 +1006,29 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
978
1006
|
}
|
|
979
1007
|
this.search(ctx, entity, q, opts);
|
|
980
1008
|
}
|
|
981
|
-
|
|
1009
|
+
/**
|
|
1010
|
+
* `INSERT ... ON CONFLICT (...) DO UPDATE/NOTHING RETURNING ...`, which SQLite adopted from Postgres
|
|
1011
|
+
* and which every dialect here speaks except the MySQL family (see {@link MysqlLikeSqlDialect}).
|
|
1012
|
+
*
|
|
1013
|
+
* Two orderings matter, and they pull in opposite directions. The assignments are computed *before*
|
|
1014
|
+
* the insert, because `appendInsertValues` fills `onInsert` fields into the payload and a column that
|
|
1015
|
+
* exists only there - `createdAt` - must not join the update set. Their bound values are pushed
|
|
1016
|
+
* *after* it, because a `?` placeholder is positional and the clause comes last in the statement.
|
|
1017
|
+
* {@link PgLikeSqlDialect} overrides this: `$N` placeholders make array order irrelevant, so it can
|
|
1018
|
+
* bind into the main context and skip the second one.
|
|
1019
|
+
*/
|
|
1020
|
+
upsert(ctx, entity, conflictPaths, payload, extraReturning = '') {
|
|
982
1021
|
const meta = getMeta(entity);
|
|
983
1022
|
const updateCtx = this.createContext();
|
|
984
|
-
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload,
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
|
|
994
|
-
ctx.pushValue(...insertCtx.values);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
1023
|
+
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, this.upsertExcluded);
|
|
1024
|
+
const keys = this.getUpsertConflictPathsStr(meta, conflictPaths);
|
|
1025
|
+
const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
|
|
1026
|
+
this.appendInsertValues(ctx, entity, payload);
|
|
1027
|
+
ctx.append(` ON CONFLICT (${keys}) ${onConflict} ${this.returningId(entity)}${extraReturning}`);
|
|
1028
|
+
ctx.pushValue(...updateCtx.values);
|
|
1029
|
+
}
|
|
1030
|
+
/** How an `ON CONFLICT` assignment reads the row that was being inserted. */
|
|
1031
|
+
upsertExcluded = (columnName) => `EXCLUDED.${columnName}`;
|
|
997
1032
|
getUpsertUpdateAssignments(ctx, meta, conflictPaths, payload, callback) {
|
|
998
1033
|
const sample = Array.isArray(payload) ? payload[0] : payload;
|
|
999
1034
|
const cloned = { ...sample };
|
|
@@ -1045,22 +1080,38 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1045
1080
|
return escapeSqlId(val, this.escapeIdChar, forbidQualified, addDot);
|
|
1046
1081
|
}
|
|
1047
1082
|
/**
|
|
1048
|
-
*
|
|
1049
|
-
* {@link appendVectorValue} rather than this, so the chain runs once per
|
|
1050
|
-
* and delegating back to `super` ran every check twice, measurably slowing
|
|
1083
|
+
* Bind one persisted value, classifying its column on the spot. Dialects override
|
|
1084
|
+
* {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
|
|
1085
|
+
* value - overriding this and delegating back to `super` ran every check twice, measurably slowing
|
|
1086
|
+
* every INSERT/UPDATE.
|
|
1051
1087
|
*/
|
|
1052
1088
|
formatPersistableValue(ctx, field, value) {
|
|
1089
|
+
this.writePersistableValue(ctx, this.persistKind(field), field, value);
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* How a column's values are written. A function of the column, not of the value, so a bulk insert
|
|
1093
|
+
* classifies each column once instead of re-deciding per row: a 20-row, 6-column insert asked
|
|
1094
|
+
* `isJsonType` and `isVectorFieldType` 120 times to get the same six answers.
|
|
1095
|
+
*/
|
|
1096
|
+
persistKind(field) {
|
|
1097
|
+
const type = field?.type;
|
|
1098
|
+
if (isJsonType(type)) {
|
|
1099
|
+
return 'json';
|
|
1100
|
+
}
|
|
1101
|
+
return isVectorFieldType(type) ? 'vector' : 'plain';
|
|
1102
|
+
}
|
|
1103
|
+
/** The one type dispatch for a persisted value, over a column kind decided by the caller. */
|
|
1104
|
+
writePersistableValue(ctx, kind, field, value) {
|
|
1053
1105
|
if (value instanceof QueryRaw) {
|
|
1054
1106
|
this.getRawValue(ctx, { value });
|
|
1055
1107
|
return;
|
|
1056
1108
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
this.appendJsonValue(ctx, value, type);
|
|
1109
|
+
if (kind === 'json') {
|
|
1110
|
+
this.appendJsonValue(ctx, value, field?.type);
|
|
1060
1111
|
return;
|
|
1061
1112
|
}
|
|
1062
|
-
if (
|
|
1063
|
-
this.appendVectorValue(ctx, value);
|
|
1113
|
+
if (kind === 'vector' && Array.isArray(value)) {
|
|
1114
|
+
this.appendVectorValue(ctx, value, field);
|
|
1064
1115
|
return;
|
|
1065
1116
|
}
|
|
1066
1117
|
ctx.addValue(value);
|
|
@@ -1068,9 +1119,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1068
1119
|
appendJsonValue(ctx, value, _type) {
|
|
1069
1120
|
ctx.addValue(value == null ? null : JSON.stringify(value));
|
|
1070
1121
|
}
|
|
1071
|
-
appendVectorValue(ctx, value) {
|
|
1072
|
-
ctx.addValue(`[${value.join(',')}]`);
|
|
1073
|
-
}
|
|
1074
1122
|
/**
|
|
1075
1123
|
* Reads `operand` as a JSON value. Passing the `?` placeholder yields the cast for a bound
|
|
1076
1124
|
* parameter, and passing an expression re-reads text as JSON - the same SQL either way, which is
|
package/dist/dialect/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
|
4
4
|
export { SqliteDialect } from '../sqlite/sqliteDialect.js';
|
|
5
5
|
export * from './abstractDialect.js';
|
|
6
6
|
export * from './abstractSqlDialect.js';
|
|
7
|
+
export * from './indexSqlDialect.js';
|
|
7
8
|
export * from './mysqlLikeSqlDialect.js';
|
|
8
9
|
export * from './queryContext.js';
|
package/dist/dialect/index.js
CHANGED
|
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
|
4
4
|
export { SqliteDialect } from '../sqlite/sqliteDialect.js';
|
|
5
5
|
export * from './abstractDialect.js';
|
|
6
6
|
export * from './abstractSqlDialect.js';
|
|
7
|
+
export * from './indexSqlDialect.js';
|
|
7
8
|
export * from './mysqlLikeSqlDialect.js';
|
|
8
9
|
export * from './queryContext.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type IndexColumnSchema, type IndexFeature, type IndexSchema } from '../type/index.js';
|
|
2
|
+
import { VectorSqlDialect } from './vectorSqlDialect.js';
|
|
3
|
+
/**
|
|
4
|
+
* `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
|
|
5
|
+
*
|
|
6
|
+
* A layer of its own for the same reason as {@link VectorSqlDialect} below it - it needs only
|
|
7
|
+
* `escapeId` and `features.indexIfNotExists` from the SQL dialect above, so keeping it here spares
|
|
8
|
+
* that 2000-line class thirteen more members. What each engine can express at all is data
|
|
9
|
+
* ({@link indexFeatures}), validated once, rather than a throw per feature per dialect.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class IndexSqlDialect extends VectorSqlDialect {
|
|
12
|
+
/**
|
|
13
|
+
* The `CREATE INDEX` statement for this dialect. The form here is the portable one, which SQLite
|
|
14
|
+
* (and so libSQL, Turso and D1) takes verbatim: no access-method clause, no operator classes, no
|
|
15
|
+
* tuning parameters. Dialects that have those override the fragments below rather than this.
|
|
16
|
+
*/
|
|
17
|
+
getCreateIndexStatement(tableName: string, index: IndexSchema, opts?: {
|
|
18
|
+
ifNotExists?: boolean;
|
|
19
|
+
}): string;
|
|
20
|
+
/**
|
|
21
|
+
* Index features this dialect can express. Everything here is supported by at least one engine and
|
|
22
|
+
* refused by at least one other, so an index asking for a missing one is rejected rather than
|
|
23
|
+
* emitted: each of them is a hard error at the server, not a slower plan.
|
|
24
|
+
*/
|
|
25
|
+
protected readonly indexFeatures: ReadonlySet<IndexFeature>;
|
|
26
|
+
private assertIndexFeatures;
|
|
27
|
+
/**
|
|
28
|
+
* The column-level declaration for a vector index that lives inside `CREATE TABLE` rather than in
|
|
29
|
+
* its own statement, which the `inlineVectorIndex` feature flags. Only MariaDB has one.
|
|
30
|
+
*/
|
|
31
|
+
getInlineVectorIndexDeclaration(index: IndexSchema): string;
|
|
32
|
+
/** Only CockroachDB's native vector index replaces the `INDEX` keyword. */
|
|
33
|
+
protected indexKeyword(_index: IndexSchema): string;
|
|
34
|
+
/** One index entry: what is indexed, its operator class if any, then its stored order. */
|
|
35
|
+
protected indexColumn(entry: IndexColumnSchema, index: IndexSchema): string;
|
|
36
|
+
/**
|
|
37
|
+
* A quoted column, optionally prefix-limited, or an expression in its own parentheses - the form
|
|
38
|
+
* `((lower("email")))` that MySQL requires and Postgres, CockroachDB and SQLite all accept, so one
|
|
39
|
+
* rendering serves every engine that has expression indexes.
|
|
40
|
+
*/
|
|
41
|
+
protected indexColumnTarget(entry: IndexColumnSchema): string;
|
|
42
|
+
/** Postgres-wire dialects put a vector or user-declared operator class here. */
|
|
43
|
+
protected indexColumnOpsClass(_entry: IndexColumnSchema, _index: IndexSchema): string;
|
|
44
|
+
/** `ASC` is every engine's default, so only `DESC` is worth emitting. */
|
|
45
|
+
protected indexColumnOrder(entry: IndexColumnSchema): string;
|
|
46
|
+
/** ` INCLUDE (...)`: non-key columns stored for index-only scans. Postgres-wire only. */
|
|
47
|
+
protected indexInclude(_index: IndexSchema): string;
|
|
48
|
+
/** ` USING <method>`, which SQLite's grammar has no place for at all. */
|
|
49
|
+
protected indexAccessMethod(_index: IndexSchema): string;
|
|
50
|
+
/** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
|
|
51
|
+
protected indexTuning(_index: IndexSchema): string;
|
|
52
|
+
/**
|
|
53
|
+
* The partial-index predicate. Dialects without partial indexes throw instead of dropping it:
|
|
54
|
+
* silently widening a partial unique index changes which rows the database accepts.
|
|
55
|
+
*/
|
|
56
|
+
protected indexPredicate(index: IndexSchema): string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { INDEX_FEATURE_LABELS } from '../type/index.js';
|
|
2
|
+
import { VectorSqlDialect } from './vectorSqlDialect.js';
|
|
3
|
+
/**
|
|
4
|
+
* `CREATE INDEX` for SQL dialects: the statement and the fragments each engine spells differently.
|
|
5
|
+
*
|
|
6
|
+
* A layer of its own for the same reason as {@link VectorSqlDialect} below it - it needs only
|
|
7
|
+
* `escapeId` and `features.indexIfNotExists` from the SQL dialect above, so keeping it here spares
|
|
8
|
+
* that 2000-line class thirteen more members. What each engine can express at all is data
|
|
9
|
+
* ({@link indexFeatures}), validated once, rather than a throw per feature per dialect.
|
|
10
|
+
*/
|
|
11
|
+
export class IndexSqlDialect extends VectorSqlDialect {
|
|
12
|
+
/**
|
|
13
|
+
* The `CREATE INDEX` statement for this dialect. The form here is the portable one, which SQLite
|
|
14
|
+
* (and so libSQL, Turso and D1) takes verbatim: no access-method clause, no operator classes, no
|
|
15
|
+
* tuning parameters. Dialects that have those override the fragments below rather than this.
|
|
16
|
+
*/
|
|
17
|
+
getCreateIndexStatement(tableName, index, opts = {}) {
|
|
18
|
+
this.assertIndexFeatures(index);
|
|
19
|
+
const unique = index.unique ? 'UNIQUE ' : '';
|
|
20
|
+
const ifNotExists = (opts.ifNotExists ?? this.features.indexIfNotExists) ? 'IF NOT EXISTS ' : '';
|
|
21
|
+
const columns = index.columns.map((entry) => this.indexColumn(entry, index)).join(', ');
|
|
22
|
+
return (`CREATE ${unique}${this.indexKeyword(index)} ${ifNotExists}${this.escapeId(index.name)} ` +
|
|
23
|
+
`ON ${this.escapeId(tableName)}${this.indexAccessMethod(index)} (${columns})` +
|
|
24
|
+
`${this.indexInclude(index)}${this.indexTuning(index)}${this.indexPredicate(index)};`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Index features this dialect can express. Everything here is supported by at least one engine and
|
|
28
|
+
* refused by at least one other, so an index asking for a missing one is rejected rather than
|
|
29
|
+
* emitted: each of them is a hard error at the server, not a slower plan.
|
|
30
|
+
*/
|
|
31
|
+
indexFeatures = new Set(['expression']);
|
|
32
|
+
assertIndexFeatures(index) {
|
|
33
|
+
const requested = [
|
|
34
|
+
['expression', index.columns.some((entry) => entry.expression)],
|
|
35
|
+
['prefixLength', index.columns.some((entry) => entry.length !== undefined)],
|
|
36
|
+
['nullsOrder', index.columns.some((entry) => entry.nulls !== undefined)],
|
|
37
|
+
['opsClass', index.columns.some((entry) => entry.opsClass !== undefined)],
|
|
38
|
+
['include', Boolean(index.include?.length)],
|
|
39
|
+
];
|
|
40
|
+
for (const [feature, needed] of requested) {
|
|
41
|
+
if (needed && !this.indexFeatures.has(feature)) {
|
|
42
|
+
throw new TypeError(`${this.dialectName} does not support ${INDEX_FEATURE_LABELS[feature]} (index "${index.name}")`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The column-level declaration for a vector index that lives inside `CREATE TABLE` rather than in
|
|
48
|
+
* its own statement, which the `inlineVectorIndex` feature flags. Only MariaDB has one.
|
|
49
|
+
*/
|
|
50
|
+
getInlineVectorIndexDeclaration(index) {
|
|
51
|
+
throw new TypeError(`${this.dialectName} has no inline vector index (index "${index.name}")`);
|
|
52
|
+
}
|
|
53
|
+
/** Only CockroachDB's native vector index replaces the `INDEX` keyword. */
|
|
54
|
+
indexKeyword(_index) {
|
|
55
|
+
return 'INDEX';
|
|
56
|
+
}
|
|
57
|
+
/** One index entry: what is indexed, its operator class if any, then its stored order. */
|
|
58
|
+
indexColumn(entry, index) {
|
|
59
|
+
return `${this.indexColumnTarget(entry)}${this.indexColumnOpsClass(entry, index)}${this.indexColumnOrder(entry)}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A quoted column, optionally prefix-limited, or an expression in its own parentheses - the form
|
|
63
|
+
* `((lower("email")))` that MySQL requires and Postgres, CockroachDB and SQLite all accept, so one
|
|
64
|
+
* rendering serves every engine that has expression indexes.
|
|
65
|
+
*/
|
|
66
|
+
indexColumnTarget(entry) {
|
|
67
|
+
if (entry.expression) {
|
|
68
|
+
return `(${entry.column})`;
|
|
69
|
+
}
|
|
70
|
+
const column = this.escapeId(entry.column);
|
|
71
|
+
return entry.length === undefined ? column : `${column}(${entry.length})`;
|
|
72
|
+
}
|
|
73
|
+
/** Postgres-wire dialects put a vector or user-declared operator class here. */
|
|
74
|
+
indexColumnOpsClass(_entry, _index) {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
/** `ASC` is every engine's default, so only `DESC` is worth emitting. */
|
|
78
|
+
indexColumnOrder(entry) {
|
|
79
|
+
const order = entry.order === 'desc' ? ' DESC' : '';
|
|
80
|
+
return entry.nulls ? `${order} NULLS ${entry.nulls.toUpperCase()}` : order;
|
|
81
|
+
}
|
|
82
|
+
/** ` INCLUDE (...)`: non-key columns stored for index-only scans. Postgres-wire only. */
|
|
83
|
+
indexInclude(_index) {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
/** ` USING <method>`, which SQLite's grammar has no place for at all. */
|
|
87
|
+
indexAccessMethod(_index) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
/** pgvector's ` WITH (m = ..., ef_construction = ..., lists = ...)`. */
|
|
91
|
+
indexTuning(_index) {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The partial-index predicate. Dialects without partial indexes throw instead of dropping it:
|
|
96
|
+
* silently widening a partial unique index changes which rows the database accepts.
|
|
97
|
+
*/
|
|
98
|
+
indexPredicate(index) {
|
|
99
|
+
return index.where ? ` WHERE ${index.where}` : '';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DialectFeatures, FieldOptions, InsertIdSource, QueryContext, QuerySizeComparisonOps } from '../type/index.js';
|
|
1
|
+
import type { DialectFeatures, EntityMeta, FieldOptions, IndexFeature, IndexSchema, InsertIdSource, QueryConflictPaths, QueryContext, 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.
|
|
@@ -26,8 +26,33 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
26
26
|
readonly alterColumnSyntax = "MODIFY COLUMN";
|
|
27
27
|
readonly booleanLiteral = "integer";
|
|
28
28
|
readonly insertIdSource: InsertIdSource;
|
|
29
|
+
/**
|
|
30
|
+
* `INSERT ... ON DUPLICATE KEY UPDATE`, and `INSERT IGNORE` when every non-conflict column is itself a
|
|
31
|
+
* conflict key so there is nothing to assign. Neither form takes a conflict target: MySQL picks the
|
|
32
|
+
* unique index for you.
|
|
33
|
+
*
|
|
34
|
+
* The update assignments are built into their own context and pushed afterwards, since they read
|
|
35
|
+
* `VALUES(col)` rather than binding, and any value they *do* bind (an `onUpdate` field absent from the
|
|
36
|
+
* payload) has to land after the insert's for a `?`-placeholder driver.
|
|
37
|
+
*/
|
|
38
|
+
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
|
|
29
39
|
readonly maxBindValues: number;
|
|
30
40
|
escape(value: unknown): string;
|
|
41
|
+
/**
|
|
42
|
+
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
43
|
+
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
44
|
+
* `@Index([...], { type: 'fulltext' })`.
|
|
45
|
+
*/
|
|
46
|
+
protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
47
|
+
/**
|
|
48
|
+
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
|
|
49
|
+
* is a syntax error, so it is the keyword that changes rather than the access method.
|
|
50
|
+
*/
|
|
51
|
+
protected indexKeyword(index: IndexSchema): string;
|
|
52
|
+
protected readonly indexFeatures: Set<IndexFeature>;
|
|
53
|
+
protected indexAccessMethod(index: IndexSchema): string;
|
|
54
|
+
/** Neither MySQL nor MariaDB has partial indexes, and quietly widening one changes which rows it rejects. */
|
|
55
|
+
protected indexPredicate(index: IndexSchema): string;
|
|
31
56
|
protected numericCast(expr: string): string;
|
|
32
57
|
protected ilikeExpr(f: string, ph: string): string;
|
|
33
58
|
protected neExpr(field: string, ph: string): string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getMeta } from '../entity/index.js';
|
|
2
|
+
import { getFieldKeys } from '../util/index.js';
|
|
1
3
|
import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
2
4
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
3
5
|
import { JSON_PULL_ALIAS, jsonAssignCall, jsonPath, jsonRemoveCall, jsonSetTarget } from './jsonSql.js';
|
|
@@ -23,7 +25,7 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
23
25
|
renameColumn: true,
|
|
24
26
|
foreignKeyAlter: true,
|
|
25
27
|
columnComment: true,
|
|
26
|
-
|
|
28
|
+
inlineVectorIndex: false,
|
|
27
29
|
vectorSupportsLength: false,
|
|
28
30
|
supportsTimestamptz: false,
|
|
29
31
|
defaultStringAsText: false,
|
|
@@ -43,10 +45,64 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
|
|
|
43
45
|
// No `RETURNING` support, so multi-row insert IDs are inferred from the header - see the
|
|
44
46
|
// `innodb_autoinc_lock_mode` caveat on `buildUpdateResult` in `util/sql.util.ts`.
|
|
45
47
|
insertIdSource = 'firstId';
|
|
48
|
+
/**
|
|
49
|
+
* `INSERT ... ON DUPLICATE KEY UPDATE`, and `INSERT IGNORE` when every non-conflict column is itself a
|
|
50
|
+
* conflict key so there is nothing to assign. Neither form takes a conflict target: MySQL picks the
|
|
51
|
+
* unique index for you.
|
|
52
|
+
*
|
|
53
|
+
* The update assignments are built into their own context and pushed afterwards, since they read
|
|
54
|
+
* `VALUES(col)` rather than binding, and any value they *do* bind (an `onUpdate` field absent from the
|
|
55
|
+
* payload) has to land after the insert's for a `?`-placeholder driver.
|
|
56
|
+
*/
|
|
57
|
+
upsert(ctx, entity, conflictPaths, payload) {
|
|
58
|
+
const meta = getMeta(entity);
|
|
59
|
+
const updateCtx = this.createContext();
|
|
60
|
+
const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
|
|
61
|
+
if (update) {
|
|
62
|
+
this.appendInsertValues(ctx, entity, payload);
|
|
63
|
+
ctx.append(` ON DUPLICATE KEY UPDATE ${update}`);
|
|
64
|
+
ctx.pushValue(...updateCtx.values);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const insertCtx = this.createContext();
|
|
68
|
+
this.appendInsertValues(insertCtx, entity, payload);
|
|
69
|
+
ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
|
|
70
|
+
ctx.pushValue(...insertCtx.values);
|
|
71
|
+
}
|
|
46
72
|
maxBindValues = 65535;
|
|
47
73
|
escape(value) {
|
|
48
74
|
return escapeMysqlSqlLiteral(value);
|
|
49
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
|
|
78
|
+
* the server answers "Can't find FULLTEXT index matching the column list". Declare it with
|
|
79
|
+
* `@Index([...], { type: 'fulltext' })`.
|
|
80
|
+
*/
|
|
81
|
+
appendTextSearch(ctx, _entity, meta, search) {
|
|
82
|
+
const searchFields = search.$fields ?? getFieldKeys(meta.fields);
|
|
83
|
+
const columns = searchFields.map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
84
|
+
ctx.append(`MATCH(${columns.join(', ')}) AGAINST(`);
|
|
85
|
+
ctx.addValue(search.$value);
|
|
86
|
+
ctx.append(')');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext`
|
|
90
|
+
* is a syntax error, so it is the keyword that changes rather than the access method.
|
|
91
|
+
*/
|
|
92
|
+
indexKeyword(index) {
|
|
93
|
+
return index.type === 'fulltext' ? 'FULLTEXT INDEX' : super.indexKeyword(index);
|
|
94
|
+
}
|
|
95
|
+
indexFeatures = new Set(['expression', 'prefixLength']);
|
|
96
|
+
indexAccessMethod(index) {
|
|
97
|
+
return index.type && index.type !== 'fulltext' ? ` USING ${index.type}` : '';
|
|
98
|
+
}
|
|
99
|
+
/** Neither MySQL nor MariaDB has partial indexes, and quietly widening one changes which rows it rejects. */
|
|
100
|
+
indexPredicate(index) {
|
|
101
|
+
if (index.where) {
|
|
102
|
+
throw new TypeError(`${this.dialectName} does not support partial indexes (index "${index.name}" declares a "where" condition)`);
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
50
106
|
numericCast(expr) {
|
|
51
107
|
return `CAST(${expr} AS DECIMAL)`;
|
|
52
108
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DialectFeatures, type EntityMeta, type FieldOptions, type
|
|
1
|
+
import { type DialectFeatures, type EntityMeta, type FieldOptions, type IndexColumnSchema, type IndexFeature, type IndexSchema, type JsonColumnType, type QueryConflictPaths, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type QueryVectorSearch, type Type, type VectorDistance } 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
|
|
@@ -21,25 +21,43 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
21
21
|
readonly alterColumnStrategy = "separate-clauses";
|
|
22
22
|
readonly insertIdSource = "returning";
|
|
23
23
|
readonly maxBindValues: number;
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Each metric's pgvector distance operator and the operator-class suffix its index takes, in one
|
|
26
|
+
* place so a dialect cannot end up with the operator but not the opclass. The key set is the single
|
|
27
|
+
* source of truth for which metrics the dialect supports at all: CockroachDB narrows it to three.
|
|
28
|
+
*/
|
|
29
|
+
readonly vectorMetrics: ReadonlyMap<VectorDistance, {
|
|
30
|
+
op: string;
|
|
31
|
+
opsSuffix: string;
|
|
32
|
+
}>;
|
|
25
33
|
normalizeValue(value: unknown): unknown;
|
|
34
|
+
/** pgvector's own index types; CockroachDB's native one widens this. */
|
|
35
|
+
protected isVectorIndex(index: IndexSchema): boolean;
|
|
36
|
+
protected indexAccessMethod(index: IndexSchema): string;
|
|
37
|
+
protected readonly indexFeatures: Set<IndexFeature>;
|
|
38
|
+
/**
|
|
39
|
+
* A vector index's operator class is named `{type}_{metric}_ops`: an index on a `halfvec` column
|
|
40
|
+
* needs `halfvec_cosine_ops`, and `vector_cosine_ops` there is rejected outright. An unsupported
|
|
41
|
+
* distance throws rather than being omitted, since a bare `USING hnsw ("embedding")` would build
|
|
42
|
+
* with the dialect's default metric instead of the one requested, with nothing signalling it.
|
|
43
|
+
* Everything else takes the operator class the entry declares, e.g. `jsonb_path_ops` for GIN.
|
|
44
|
+
*/
|
|
45
|
+
protected indexColumnOpsClass(entry: IndexColumnSchema, index: IndexSchema): string;
|
|
46
|
+
protected indexInclude(index: IndexSchema): string;
|
|
47
|
+
protected indexTuning(index: IndexSchema): string;
|
|
26
48
|
placeholder(index: number): string;
|
|
27
|
-
insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
|
|
28
|
-
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
|
|
29
49
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* detect insert-vs-update; CockroachDB has no `xmax`/`ctid` system columns, so it uses the default
|
|
34
|
-
* (empty) and `created` stays `undefined` in the result.
|
|
50
|
+
* The same statement as the base, binding the assignments into the main context instead of a second
|
|
51
|
+
* one: `$N` placeholders carry their own index, so the values need not be in statement order, and
|
|
52
|
+
* computing them first is what keeps `appendInsertValues`' `onInsert` fields out of the update set.
|
|
35
53
|
*/
|
|
36
|
-
|
|
54
|
+
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[], extraReturning?: string): void;
|
|
37
55
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
56
|
+
* `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
|
|
57
|
+
* (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
|
|
58
|
+
* rejects anything unparseable - including a plain two-word search.
|
|
41
59
|
*/
|
|
42
|
-
|
|
60
|
+
protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
43
61
|
protected jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string;
|
|
44
62
|
protected jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string;
|
|
45
63
|
/**
|
|
@@ -54,7 +72,11 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
54
72
|
protected formatIn(ctx: QueryContext, values: unknown[], negate: boolean): string;
|
|
55
73
|
protected numericCast(expr: string): string;
|
|
56
74
|
protected appendJsonValue(ctx: QueryContext, value: unknown, type: JsonColumnType): void;
|
|
57
|
-
|
|
75
|
+
/**
|
|
76
|
+
* pgvector needs the cast to pick the right type, and `sparsevec` needs its own literal: the dense
|
|
77
|
+
* `[1,0,2]` every other type takes is rejected as "invalid input syntax for type sparsevec".
|
|
78
|
+
*/
|
|
79
|
+
protected appendVectorValue(ctx: QueryContext, value: readonly unknown[], field?: FieldOptions): void;
|
|
58
80
|
/**
|
|
59
81
|
* `create_if_missing => false` keeps an absent key (and a NULL column) untouched; `WITH
|
|
60
82
|
* ORDINALITY` keeps the surviving elements in their original order.
|
|
@@ -70,13 +92,6 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
70
92
|
* Helper to add a JSON value to context with appropriate stringification and cast.
|
|
71
93
|
*/
|
|
72
94
|
private jsonVal;
|
|
73
|
-
/**
|
|
74
|
-
* pgvector distance operators. Not every dialect supports every metric - see
|
|
75
|
-
* {@link CockroachDialect.vectorOpsClass} for which ones each dialect actually has; the
|
|
76
|
-
* `vectorOpsClass` key set (checked in `appendVectorSort` below via `vectorOpsClass.get`)
|
|
77
|
-
* is the single source of truth for that, not this map.
|
|
78
|
-
*/
|
|
79
|
-
private static readonly VECTOR_OPS;
|
|
80
95
|
/** Emit a pgvector-style distance expression: `"col" <op> $N::<vectorType>`. */
|
|
81
96
|
protected appendVectorSort<E>(ctx: QueryContext, meta: EntityMeta<E>, key: string, search: QueryVectorSearch): void;
|
|
82
97
|
}
|