uql-orm 0.79.0 → 0.81.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.map +2 -2
- package/dist/cockroachdb/cockroachDialect.js +5 -1
- package/dist/dialect/abstractDialect.d.ts +1 -31
- package/dist/dialect/abstractDialect.js +3 -27
- package/dist/dialect/abstractSqlDialect.d.ts +25 -56
- package/dist/dialect/abstractSqlDialect.js +78 -145
- package/dist/dialect/aliases.d.ts +5 -0
- package/dist/dialect/aliases.js +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +4 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +14 -1
- package/dist/dialect/operators.d.ts +66 -0
- package/dist/dialect/operators.js +129 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +4 -1
- package/dist/dialect/pgLikeSqlDialect.js +16 -3
- package/dist/dialect/vectorSqlDialect.d.ts +2 -0
- package/dist/dialect/vectorSqlDialect.js +4 -0
- package/dist/entity/decorator/entity.d.ts +6 -1
- package/dist/entity/decorator/entity.js +12 -1
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +6 -1
- package/dist/entity/metadata/definition.js +19 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +24 -0
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
- package/dist/migrate/codegen/entityTypes.js +1 -2
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
- package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +6 -4
- package/dist/migrate/ddl/mssqlTableDdl.js +25 -14
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +7 -6
- package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
- package/dist/migrate/ddl/pgIndexDdl.js +8 -6
- package/dist/migrate/ddl/tableDdl.d.ts +5 -2
- package/dist/migrate/ddl/tableDdl.js +13 -7
- package/dist/migrate/drift/driftDetector.d.ts +4 -5
- package/dist/migrate/drift/driftDetector.js +21 -21
- package/dist/migrate/generator/definitionToNode.d.ts +1 -1
- package/dist/migrate/generator/definitionToNode.js +9 -20
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +5 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +20 -18
- package/dist/migrate/index.d.ts +2 -1
- package/dist/migrate/index.js +1 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +16 -6
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +18 -4
- package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/mongoIntrospector.js +7 -3
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +9 -0
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +16 -5
- package/dist/migrate/introspection/mysqlIntrospector.js +39 -2
- package/dist/migrate/introspection/postgresIntrospector.d.ts +30 -21
- package/dist/migrate/introspection/postgresIntrospector.js +70 -43
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +13 -8
- package/dist/migrate/migrator.d.ts +33 -1
- package/dist/migrate/migrator.js +111 -43
- package/dist/migrate/schemaChange.d.ts +18 -0
- package/dist/migrate/schemaChange.js +37 -0
- package/dist/migrate/schemaGenerator.d.ts +25 -15
- package/dist/migrate/schemaGenerator.js +130 -182
- package/dist/migrate/storage/databaseStorage.d.ts +4 -0
- package/dist/migrate/storage/databaseStorage.js +14 -8
- package/dist/migrate/triggerSql.d.ts +24 -0
- package/dist/migrate/triggerSql.js +229 -0
- package/dist/mongo/mongoDialect.d.ts +0 -21
- package/dist/mongo/mongoDialect.js +105 -100
- package/dist/mongo/mongodbQuerier.js +17 -1
- package/dist/mssql/mssqlDialect.d.ts +18 -7
- package/dist/mssql/mssqlDialect.js +77 -33
- package/dist/mssql/mssqlQuerier.js +2 -2
- package/dist/schema/canonicalType.d.ts +6 -1
- package/dist/schema/canonicalType.js +14 -0
- package/dist/schema/indexDifferences.d.ts +22 -6
- package/dist/schema/indexDifferences.js +23 -8
- package/dist/schema/matchByKey.d.ts +10 -0
- package/dist/schema/matchByKey.js +18 -0
- package/dist/schema/schemaAST.d.ts +6 -2
- package/dist/schema/schemaAST.js +7 -3
- package/dist/schema/schemaASTBuilder.d.ts +4 -8
- package/dist/schema/schemaASTBuilder.js +20 -29
- package/dist/schema/schemaASTDiffer.d.ts +2 -3
- package/dist/schema/schemaASTDiffer.js +15 -36
- package/dist/schema/types.d.ts +14 -15
- package/dist/sqlite/sqliteDialect.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +12 -3
- package/dist/type/dialect.d.ts +69 -9
- package/dist/type/entity.d.ts +96 -3
- package/dist/type/migration.d.ts +43 -44
- package/dist/type/query.d.ts +13 -4
- package/dist/type/queryWhere.d.ts +4 -2
- package/dist/util/ddlExpression.util.d.ts +5 -1
- package/dist/util/ddlExpression.util.js +6 -2
- package/dist/util/field.util.d.ts +9 -1
- package/dist/util/field.util.js +14 -2
- package/dist/util/fieldOption.util.d.ts +2 -2
- package/dist/util/fieldOption.util.js +2 -2
- package/dist/util/raw.d.ts +9 -1
- package/dist/util/raw.js +36 -11
- package/dist/util/sql.util.d.ts +12 -0
- package/dist/util/sql.util.js +24 -3
- package/dist/util/uqlError.d.ts +2 -0
- package/dist/util/uqlError.js +4 -0
- package/package.json +4 -4
- package/skills/uql-orm/SKILL.md +11 -6
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { VECTOR_QUERY_KEYS, } from '../type/index.js';
|
|
2
|
+
import { isOperatorMap } from '../util/dialect.util.js';
|
|
3
|
+
import { kindOf, UqlUsageError } from '../util/uqlError.js';
|
|
4
|
+
/**
|
|
5
|
+
* How each grouping operator renders: the operator joining its clauses, and whether the group is
|
|
6
|
+
* negated (`$not` is `NOT (a AND b)`). Total over {@link QueryGroupOp}.
|
|
7
|
+
*/
|
|
8
|
+
export const GROUP_OPS = {
|
|
9
|
+
$and: { join: '$and', negate: false },
|
|
10
|
+
$or: { join: '$or', negate: false },
|
|
11
|
+
$not: { join: '$and', negate: true },
|
|
12
|
+
$nor: { join: '$or', negate: true },
|
|
13
|
+
};
|
|
14
|
+
/** Whether a `$where` key groups clauses, narrowing it for the renderers that read {@link GROUP_OPS}. */
|
|
15
|
+
export function isGroupOp(key) {
|
|
16
|
+
return Object.hasOwn(GROUP_OPS, key);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A group operator's clauses, rejecting what the types do not cover: `/http` casts client JSON
|
|
20
|
+
* straight to `Query`, so a scalar can arrive where an array belongs. Shared so both backends
|
|
21
|
+
* refuse the same payload rather than one throwing and the other failing further in.
|
|
22
|
+
*/
|
|
23
|
+
export function groupClauses(key, val) {
|
|
24
|
+
if (val !== undefined && !Array.isArray(val)) {
|
|
25
|
+
throw new UqlUsageError(`${key} expects an array, got ${kindOf(val)}`);
|
|
26
|
+
}
|
|
27
|
+
return val ?? [];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A WHERE value as the operators it applies: an array is `$in`, an operator map its own entries, and
|
|
31
|
+
* anything else `$eq`. Each key is checked here, once, refused as `refusal` where it is no operator.
|
|
32
|
+
*/
|
|
33
|
+
export function whereOperators(val, refusal) {
|
|
34
|
+
const ops = Array.isArray(val) ? { $in: val } : isOperatorMap(val) ? val : { $eq: val };
|
|
35
|
+
return Object.entries(ops).map(([op, value]) => {
|
|
36
|
+
if (!isFieldOp(op)) {
|
|
37
|
+
throw new UqlUsageError(`${refusal}: ${op}`);
|
|
38
|
+
}
|
|
39
|
+
return [op, value];
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** An `$in`/`$nin` operand, which the types require to be an array but `/http` hands over untyped. */
|
|
43
|
+
export function inOperands(op, value) {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
throw new UqlUsageError(`${op} expects an array, got ${kindOf(value)}`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
/** A `$between`'s two bounds, which `/http` hands over untyped too; one missing would bind `undefined`. */
|
|
50
|
+
export function betweenBounds(value) {
|
|
51
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
52
|
+
const got = Array.isArray(value) ? `${value.length} values` : kindOf(value);
|
|
53
|
+
throw new UqlUsageError(`$between expects [min, max], got ${got}`);
|
|
54
|
+
}
|
|
55
|
+
const [min, max] = value;
|
|
56
|
+
return [min, max];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every field operator as data, which is what checks a key `/http` hands over; `satisfies` fails the
|
|
60
|
+
* build on one {@link QueryWhereFieldOp} gains and this misses.
|
|
61
|
+
*/
|
|
62
|
+
const FIELD_OPS = {
|
|
63
|
+
$eq: true,
|
|
64
|
+
$ne: true,
|
|
65
|
+
$not: true,
|
|
66
|
+
$lt: true,
|
|
67
|
+
$lte: true,
|
|
68
|
+
$gt: true,
|
|
69
|
+
$gte: true,
|
|
70
|
+
$between: true,
|
|
71
|
+
$startsWith: true,
|
|
72
|
+
$istartsWith: true,
|
|
73
|
+
$endsWith: true,
|
|
74
|
+
$iendsWith: true,
|
|
75
|
+
$includes: true,
|
|
76
|
+
$iincludes: true,
|
|
77
|
+
$like: true,
|
|
78
|
+
$ilike: true,
|
|
79
|
+
$regex: true,
|
|
80
|
+
$in: true,
|
|
81
|
+
$nin: true,
|
|
82
|
+
$isNull: true,
|
|
83
|
+
$isNotNull: true,
|
|
84
|
+
$all: true,
|
|
85
|
+
$size: true,
|
|
86
|
+
$elemMatch: true,
|
|
87
|
+
$near: true,
|
|
88
|
+
};
|
|
89
|
+
function isFieldOp(op) {
|
|
90
|
+
return Object.hasOwn(FIELD_OPS, op);
|
|
91
|
+
}
|
|
92
|
+
/** Whether a `$near` names the vector it measures from, which its type requires and `/http` does not. */
|
|
93
|
+
export function isVectorQuery(near) {
|
|
94
|
+
return Boolean(near['$vector']);
|
|
95
|
+
}
|
|
96
|
+
export const COMPARE_OPS = new Map([
|
|
97
|
+
['$gt', ' > '],
|
|
98
|
+
['$gte', ' >= '],
|
|
99
|
+
['$lt', ' < '],
|
|
100
|
+
['$lte', ' <= '],
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* The ordered comparisons, `QueryOrderedOp` at runtime, derived from {@link COMPARE_OPS} rather than spelled
|
|
104
|
+
* again: {@link QueryVectorNear}'s bounds, so `$near` never accepts one the renderer has no operator
|
|
105
|
+
* for, and the operators that read a JSON path as a number.
|
|
106
|
+
*/
|
|
107
|
+
export const ORDERED_OPS = new Set([...COMPARE_OPS.keys(), '$between']);
|
|
108
|
+
export function isOrderedOp(op) {
|
|
109
|
+
return ORDERED_OPS.has(op);
|
|
110
|
+
}
|
|
111
|
+
/** The operators an equality compares by value, which a JSON path reads the way that value compares. */
|
|
112
|
+
export const EQUALITY_OPS = new Set(['$eq', '$ne', '$in', '$nin']);
|
|
113
|
+
/**
|
|
114
|
+
* Every `$like`-family operator: the pattern it wraps its value in, and whether it ignores case.
|
|
115
|
+
* Each case-sensitive operator is paired here with the `$i` twin that shares its pattern, so the
|
|
116
|
+
* two can never drift apart - and neither one decides case folding, which is
|
|
117
|
+
* `AbstractSqlDialect.caseInsensitiveMatch`'s single call.
|
|
118
|
+
*/
|
|
119
|
+
export const LIKE_OPS = new Map([
|
|
120
|
+
['$like', '$ilike', (v) => v],
|
|
121
|
+
['$startsWith', '$istartsWith', (v) => `${v}%`],
|
|
122
|
+
['$endsWith', '$iendsWith', (v) => `%${v}`],
|
|
123
|
+
['$includes', '$iincludes', (v) => `%${v}%`],
|
|
124
|
+
].flatMap(([sensitive, insensitive, pattern]) => [
|
|
125
|
+
[sensitive, { pattern, insensitive: false }],
|
|
126
|
+
[insensitive, { pattern, insensitive: true }],
|
|
127
|
+
]));
|
|
128
|
+
/** What a `$near` says about the search itself; everything else in it is a bound. */
|
|
129
|
+
export const VECTOR_QUERY_KEY_SET = new Set(VECTOR_QUERY_KEYS);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IndexType } from '../schema/types.js';
|
|
2
|
+
import type { SqlDialectName } from '../type/index.js';
|
|
2
3
|
import { type DriverCapabilities, type EntityMeta, type FieldOptions, type JsonColumnType, type Query, type QueryContext, type QueryTextSearchOptions, type SqlDialectFeatures, type VectorDistance, type VectorMetric } from '../type/index.js';
|
|
3
4
|
import type { DialectOptions } from './abstractDialect.js';
|
|
4
5
|
import { AbstractSqlDialect, type RelationRows } from './abstractSqlDialect.js';
|
|
@@ -13,6 +14,8 @@ export declare const PG_VECTOR_METRICS: ReadonlyMap<VectorDistance, VectorMetric
|
|
|
13
14
|
export declare const PG_FEATURES: SqlDialectFeatures;
|
|
14
15
|
/** What Postgres and CockroachDB share: JSONB, full-text search, pgvector's operators, and the upsert. */
|
|
15
16
|
export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
17
|
+
/** Every member of this family runs the same SQL, so a body written once serves them all. */
|
|
18
|
+
get dialectFamily(): SqlDialectName;
|
|
16
19
|
/** How the driver binds a parameter: node-`pg`'s, unless the pool states its own. */
|
|
17
20
|
readonly driverCapabilities: DriverCapabilities;
|
|
18
21
|
constructor(options?: PgLikeDialectOptions);
|
|
@@ -96,7 +99,7 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
96
99
|
jsonPathExpr(escapedColumn: string, path: string, mode: JsonAccessMode): string;
|
|
97
100
|
protected get regexpOp(): string;
|
|
98
101
|
protected readonly caseInsensitiveMatch = "ilike";
|
|
99
|
-
|
|
102
|
+
neExpr(field: string, ph: string): string;
|
|
100
103
|
/**
|
|
101
104
|
* One array parameter, which a context that inlines values has none of: it lists them instead. The
|
|
102
105
|
* array takes its type from `operand`, so it needs none of the casts `bind` would give each value.
|
|
@@ -17,7 +17,6 @@ export const PG_VECTOR_METRICS = new Map([
|
|
|
17
17
|
const HNSW_EF_SEARCH = 'hnsw.ef_search';
|
|
18
18
|
/** What the Postgres-wire engines have. */
|
|
19
19
|
export const PG_FEATURES = {
|
|
20
|
-
ifNotExists: true,
|
|
21
20
|
indexIfNotExists: true,
|
|
22
21
|
schemas: true,
|
|
23
22
|
dropTableCascade: true,
|
|
@@ -41,9 +40,23 @@ export const PG_FEATURES = {
|
|
|
41
40
|
narrowVectorTypes: false,
|
|
42
41
|
vectorTuningNeedsTransaction: true,
|
|
43
42
|
serialDeclaresPrimaryKey: false,
|
|
43
|
+
triggers: {
|
|
44
|
+
preamble: '',
|
|
45
|
+
assignsRow: true,
|
|
46
|
+
body: 'function',
|
|
47
|
+
guards: 'clause',
|
|
48
|
+
layout: 'timingFirst',
|
|
49
|
+
rows: 'row',
|
|
50
|
+
scope: 'table',
|
|
51
|
+
before: true,
|
|
52
|
+
},
|
|
44
53
|
};
|
|
45
54
|
/** What Postgres and CockroachDB share: JSONB, full-text search, pgvector's operators, and the upsert. */
|
|
46
55
|
export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
56
|
+
/** Every member of this family runs the same SQL, so a body written once serves them all. */
|
|
57
|
+
get dialectFamily() {
|
|
58
|
+
return 'postgres';
|
|
59
|
+
}
|
|
47
60
|
/** How the driver binds a parameter: node-`pg`'s, unless the pool states its own. */
|
|
48
61
|
driverCapabilities;
|
|
49
62
|
constructor(options = {}) {
|
|
@@ -213,8 +226,8 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
213
226
|
return '~';
|
|
214
227
|
}
|
|
215
228
|
caseInsensitiveMatch = 'ilike';
|
|
216
|
-
|
|
217
|
-
return
|
|
229
|
+
neExpr(field, ph) {
|
|
230
|
+
return `${field} IS DISTINCT FROM ${ph}`;
|
|
218
231
|
}
|
|
219
232
|
/**
|
|
220
233
|
* One array parameter, which a context that inlines values has none of: it lists them instead. The
|
|
@@ -30,6 +30,8 @@ export declare abstract class VectorSqlDialect extends AbstractDialect {
|
|
|
30
30
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
31
31
|
/** Whether this engine has a vector index: the one a metric's `index` names. */
|
|
32
32
|
hasVectorIndex(): boolean;
|
|
33
|
+
/** The distance a vector index built for `metric`, as {@link vectorMetrics} names it for an index, measures. */
|
|
34
|
+
indexedDistance(metric: string | undefined): VectorDistance | undefined;
|
|
33
35
|
/** Quotes an identifier; supplied by the SQL dialect built on top of this layer. */
|
|
34
36
|
abstract escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
|
|
35
37
|
/** What a distance expression reads, for a `$sort` and a `$near` alike. */
|
|
@@ -45,6 +45,10 @@ export class VectorSqlDialect extends AbstractDialect {
|
|
|
45
45
|
hasVectorIndex() {
|
|
46
46
|
return [...this.vectorMetrics.values()].some((metric) => metric.index);
|
|
47
47
|
}
|
|
48
|
+
/** The distance a vector index built for `metric`, as {@link vectorMetrics} names it for an index, measures. */
|
|
49
|
+
indexedDistance(metric) {
|
|
50
|
+
return [...this.vectorMetrics].find(([, { index }]) => index === metric)?.[0];
|
|
51
|
+
}
|
|
48
52
|
/** What a distance expression reads, for a `$sort` and a `$near` alike. */
|
|
49
53
|
resolveVectorDistance(meta, key, search) {
|
|
50
54
|
const field = meta.fields[key];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityIndexColumnInput, EntityIndexOptions, EntityOptions, FilterName, FilterOptions, RefMap, Type } from '../../type/index.js';
|
|
1
|
+
import type { EntityIndexColumnInput, EntityIndexOptions, EntityOptions, FilterName, FilterOptions, RefMap, TriggerOptions, Type } from '../../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Marks a class as an entity and finalizes its metadata, draining `context.metadata`: the class gets
|
|
4
4
|
* `Symbol.metadata` only after its decorators return.
|
|
@@ -16,3 +16,8 @@ export declare function Filter<E, N extends string>(name: FilterName<N>, opts: F
|
|
|
16
16
|
* @example `@Index((user) => [user.lastName, raw`lower(${user.email})`], { unique: true })`
|
|
17
17
|
*/
|
|
18
18
|
export declare function Index<E>(columns: (refs: RefMap<E>) => readonly EntityIndexColumnInput<E>[], options?: EntityIndexOptions<E>): (entity: Type<E>) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Declares triggers the database runs, in the order written. Stacks, so several may sit above one class.
|
|
21
|
+
* @example ``@Trigger({ on: 'beforeUpdate', of: (post) => [post.body], run: (newRow) => raw`...` })``
|
|
22
|
+
*/
|
|
23
|
+
export declare function Trigger<E>(...triggers: readonly TriggerOptions<E>[]): (entity: Type<E>) => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyMembers, defineEntity, defineFilter, defineIndex } from '../metadata/definition.js';
|
|
1
|
+
import { applyMembers, defineEntity, defineFilter, defineIndex, defineTrigger } from '../metadata/definition.js';
|
|
2
2
|
import { drainRegistrations } from './bag.js';
|
|
3
3
|
// The class-level decorators. Unlike the member ones they receive the class, so each is a direct call
|
|
4
4
|
// into the registry with no bag in between.
|
|
@@ -32,3 +32,14 @@ export function Index(columns, options = {}) {
|
|
|
32
32
|
defineIndex(entity, { ...options, columns });
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Declares triggers the database runs, in the order written. Stacks, so several may sit above one class.
|
|
37
|
+
* @example ``@Trigger({ on: 'beforeUpdate', of: (post) => [post.body], run: (newRow) => raw`...` })``
|
|
38
|
+
*/
|
|
39
|
+
export function Trigger(...triggers) {
|
|
40
|
+
return (entity) => {
|
|
41
|
+
for (const trigger of triggers) {
|
|
42
|
+
defineTrigger(entity, trigger);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/entity/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './decorator/entity.js';
|
|
2
2
|
export * from './decorator/members.js';
|
|
3
|
-
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, relationOf, soleIdOf, } from './metadata/definition.js';
|
|
3
|
+
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, defineTrigger, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, relationOf, soleIdOf, } from './metadata/definition.js';
|
package/dist/entity/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './decorator/entity.js';
|
|
2
2
|
export * from './decorator/members.js';
|
|
3
|
-
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, relationOf, soleIdOf, } from './metadata/definition.js';
|
|
3
|
+
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, defineTrigger, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, relationOf, soleIdOf, } from './metadata/definition.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldMeta, FieldOptions, FilterName, FilterOptions, HookEvent, IdKey, RelationKey, RelationMeta, RelationOptions, RelationRegistration, Type, WrittenId } from '../../type/index.js';
|
|
1
|
+
import type { EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldMeta, FieldOptions, FilterName, FilterOptions, HookEvent, IdKey, RelationKey, RelationMeta, RelationOptions, RelationRegistration, TriggerOptions, Type, WrittenId } from '../../type/index.js';
|
|
2
2
|
export declare function defineField<E>(entity: Type<E>, key: string, opts?: FieldOptions): EntityMeta<E>;
|
|
3
3
|
export declare function defineId<E>(entity: Type<E>, key: string, opts: FieldOptions): EntityMeta<E>;
|
|
4
4
|
/** `T` is the relation's target, independent of the owner `E`. */
|
|
@@ -15,6 +15,11 @@ export declare function defineHook<E>(entity: Type<E>, methodName: string, event
|
|
|
15
15
|
* sugar are normalized here, which is what lets the dialects render one shape instead of re-parsing it.
|
|
16
16
|
*/
|
|
17
17
|
export declare function defineIndex<E>(entity: Type<E>, index: EntityIndexInput<E>): EntityMeta<E>;
|
|
18
|
+
/**
|
|
19
|
+
* Registers a trigger, its watched columns read off the entity's refs so a rename reaches them and a
|
|
20
|
+
* column the entity has not got does not compile. Appended, since triggers fire in the order written.
|
|
21
|
+
*/
|
|
22
|
+
export declare function defineTrigger<E>(entity: Type<E>, trigger: TriggerOptions<E>): EntityMeta<E>;
|
|
18
23
|
export declare function defineFilter<E, N extends string>(entity: Type<E>, name: FilterName<N>, opts: FilterOptions<E>): EntityMeta<E>;
|
|
19
24
|
/**
|
|
20
25
|
* Feeds fields, relations and hooks into the `define*` primitives, so the decorators and the imperative
|
|
@@ -112,6 +112,22 @@ export function defineIndex(entity, index) {
|
|
|
112
112
|
});
|
|
113
113
|
return meta;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Registers a trigger, its watched columns read off the entity's refs so a rename reaches them and a
|
|
117
|
+
* column the entity has not got does not compile. Appended, since triggers fire in the order written.
|
|
118
|
+
*/
|
|
119
|
+
export function defineTrigger(entity, trigger) {
|
|
120
|
+
const meta = ensureWritableMeta(entity);
|
|
121
|
+
// The type already refuses an empty map; this is the same answer for plain JavaScript.
|
|
122
|
+
if (typeof trigger.run !== 'function' && !hasKeys(trigger.run)) {
|
|
123
|
+
throw new TypeError(`'${entity.name}' has a trigger whose body names at least one engine to run on`);
|
|
124
|
+
}
|
|
125
|
+
if (trigger.name && meta.triggers?.some((it) => it.name === trigger.name)) {
|
|
126
|
+
throw new TypeError(`'${entity.name}' already has a trigger named '${trigger.name}'`);
|
|
127
|
+
}
|
|
128
|
+
(meta.triggers ??= []).push({ ...trigger, of: trigger.of?.(memberRefs()).map((ref) => ref.key) });
|
|
129
|
+
return meta;
|
|
130
|
+
}
|
|
115
131
|
export function defineFilter(entity, name, opts) {
|
|
116
132
|
const meta = ensureWritableMeta(entity);
|
|
117
133
|
if (name === SOFT_DELETE_FILTER) {
|
|
@@ -178,6 +194,9 @@ export function defineEntity(entity, opts = {}) {
|
|
|
178
194
|
for (const index of opts.indexes ?? []) {
|
|
179
195
|
defineIndex(entity, index);
|
|
180
196
|
}
|
|
197
|
+
for (const trigger of opts.triggers ?? []) {
|
|
198
|
+
defineTrigger(entity, trigger);
|
|
199
|
+
}
|
|
181
200
|
for (const [name, filter] of definedEntries(opts.filters ?? {})) {
|
|
182
201
|
defineFilter(entity, name, filter);
|
|
183
202
|
}
|
|
@@ -63,3 +63,5 @@ export declare const expr: {
|
|
|
63
63
|
* the result needs wrapping, which MySQL demands on its large types whatever the value.
|
|
64
64
|
*/
|
|
65
65
|
export declare function formatDefaultValue(value: unknown, dialect: AbstractSqlDialect, columnType?: string): string;
|
|
66
|
+
/** Whether a stored default is the declared one, as the engine reprints it: `'a'::character varying` is `'a'`. */
|
|
67
|
+
export declare function sameDefault(desired: unknown, current: unknown, dialect: AbstractSqlDialect): boolean;
|
|
@@ -83,6 +83,30 @@ export function formatDefaultValue(value, dialect, columnType) {
|
|
|
83
83
|
const { wrapTypes } = DIALECT_DEFAULTS[dialect.dialectName];
|
|
84
84
|
return columnType !== undefined && wrapTypes?.test(columnType) ? `(${sql})` : sql;
|
|
85
85
|
}
|
|
86
|
+
/** Whether a stored default is the declared one, as the engine reprints it: `'a'::character varying` is `'a'`. */
|
|
87
|
+
export function sameDefault(desired, current, dialect) {
|
|
88
|
+
if (current === desired)
|
|
89
|
+
return true;
|
|
90
|
+
// Both spellings of "no default" are the same fact, and engines disagree on which they report:
|
|
91
|
+
// MariaDB says `null` where MySQL says nothing at all. Reading them as different values asked to
|
|
92
|
+
// `MODIFY` every nullable column, on every sync, forever.
|
|
93
|
+
if (current == null || desired == null)
|
|
94
|
+
return current == null && desired == null;
|
|
95
|
+
const normalize = (value) => {
|
|
96
|
+
// Render first: the desired side may be a symbolic expression, the current side is always the
|
|
97
|
+
// engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
|
|
98
|
+
const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, dialect) : value;
|
|
99
|
+
if (typeof val === 'string') {
|
|
100
|
+
let s = val.replace(/::[a-z_]+(\s+[a-z_]+)*(\[\])?$/i, '');
|
|
101
|
+
s = s.replace(/^'(.*)'$/, '$1');
|
|
102
|
+
if (s.toLowerCase() === 'null')
|
|
103
|
+
return 'null';
|
|
104
|
+
return s;
|
|
105
|
+
}
|
|
106
|
+
return typeof val === 'object' ? JSON.stringify(val) : String(val);
|
|
107
|
+
};
|
|
108
|
+
return normalize(current) === normalize(desired);
|
|
109
|
+
}
|
|
86
110
|
/**
|
|
87
111
|
* Quoting is the dialect's `escape`, so a backslash in a default is escaped the way the engine reads
|
|
88
112
|
* it - MySQL takes `'a\b'` as a backspace where Postgres takes it literally. Only the cases `escape`
|
package/dist/migrate/cli.js
CHANGED
|
@@ -247,7 +247,7 @@ export async function runDriftCheck(migrator, config) {
|
|
|
247
247
|
// `unknown` and type drift compares equal, silently reporting a mismatched column as in sync.
|
|
248
248
|
const report = detectDrift(expectedAST, actualAST, {
|
|
249
249
|
dialect: config.pool?.dialect,
|
|
250
|
-
|
|
250
|
+
defaultsEqual: generator.defaultsEqual,
|
|
251
251
|
excludeTables: [config.tableName ?? DEFAULT_MIGRATIONS_TABLE],
|
|
252
252
|
});
|
|
253
253
|
printDriftReport(report);
|
|
@@ -252,8 +252,8 @@ export class EntityCodeGenerator {
|
|
|
252
252
|
}
|
|
253
253
|
const member = (param, column) => memberSource(param, this.options.propertyNameTransformer(column.name));
|
|
254
254
|
const own = lowerFirst(this.options.classNameTransformer(rel.from.table.name));
|
|
255
|
-
const key = rel.to.table.primaryKey;
|
|
256
|
-
if (rel.from.columns.length === 1 && key.length === 1 && rel.to.columns[0].name === key[0]
|
|
255
|
+
const key = rel.to.table.primaryKey?.columns ?? [];
|
|
256
|
+
if (rel.from.columns.length === 1 && key.length === 1 && rel.to.columns[0].name === key[0]) {
|
|
257
257
|
return `(${own}) => ${member(own, rel.from.columns[0])}`;
|
|
258
258
|
}
|
|
259
259
|
const target = lowerFirst(relatedClassName);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getMeta } from '../../entity/index.js';
|
|
2
|
-
import { canonicalToTypeScript } from '../../schema/canonicalType.js';
|
|
3
|
-
import { resolveColumnCanonicalType } from '../../schema/schemaASTBuilder.js';
|
|
2
|
+
import { canonicalToTypeScript, resolveColumnCanonicalType } from '../../schema/canonicalType.js';
|
|
4
3
|
import { isToManyRelation, upperFirst } from '../../util/index.js';
|
|
5
4
|
import { isIdentifierName } from './sourceLiteral.js';
|
|
6
5
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { IndexNode } from '../../schema/types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Whether `@Field({ index })` can carry the whole index. It says only "this column is indexed under
|
|
4
|
-
* this name", so anything else the index declares - an
|
|
5
|
-
* method, stored columns, a stored order - has to be written out as
|
|
4
|
+
* this name", and `unique` beside it that the index is unique, so anything else the index declares - an
|
|
5
|
+
* expression, a predicate, an access method, stored columns, a stored order - has to be written out as
|
|
6
|
+
* an `@Index` instead.
|
|
6
7
|
*/
|
|
7
8
|
export declare function isPlainFieldIndex(index: IndexNode): boolean;
|
|
8
9
|
/**
|
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
import { isVectorIndexType } from '../../type/index.js';
|
|
2
2
|
import { memberSource, rawTag } from './sourceLiteral.js';
|
|
3
|
-
/**
|
|
4
|
-
* A vector index carries its metric in the operator class pgvector names after it
|
|
5
|
-
* (`vector_cosine_ops`), which is the only place introspection can recover it from. `@Index` requires
|
|
6
|
-
* a `distance` beside a vector `type`, so emitting the type without one would not compile.
|
|
7
|
-
*/
|
|
8
|
-
const DISTANCE_BY_OPS_SUFFIX = new Map([
|
|
9
|
-
['cosine', 'cosine'],
|
|
10
|
-
['l2', 'l2'],
|
|
11
|
-
['ip', 'inner'],
|
|
12
|
-
['l1', 'l1'],
|
|
13
|
-
]);
|
|
14
|
-
function vectorDistance(index) {
|
|
15
|
-
if (index.distance) {
|
|
16
|
-
return index.distance;
|
|
17
|
-
}
|
|
18
|
-
const opsClass = index.entries.map((entry) => entry.opsClass).find(Boolean);
|
|
19
|
-
const suffix = opsClass?.match(/_(\w+)_ops$/)?.[1];
|
|
20
|
-
return suffix === undefined ? undefined : DISTANCE_BY_OPS_SUFFIX.get(suffix);
|
|
21
|
-
}
|
|
22
3
|
/**
|
|
23
4
|
* The per-entry modifiers worth writing into an entity, which is not everything introspection reports.
|
|
24
5
|
* Postgres states an entry in full - a plain column comes back `order: 'asc', nulls: 'last'` - and
|
|
@@ -37,8 +18,9 @@ function significantModifiers(entry) {
|
|
|
37
18
|
}
|
|
38
19
|
/**
|
|
39
20
|
* Whether `@Field({ index })` can carry the whole index. It says only "this column is indexed under
|
|
40
|
-
* this name", so anything else the index declares - an
|
|
41
|
-
* method, stored columns, a stored order - has to be written out as
|
|
21
|
+
* this name", and `unique` beside it that the index is unique, so anything else the index declares - an
|
|
22
|
+
* expression, a predicate, an access method, stored columns, a stored order - has to be written out as
|
|
23
|
+
* an `@Index` instead.
|
|
42
24
|
*/
|
|
43
25
|
export function isPlainFieldIndex(index) {
|
|
44
26
|
const entries = index.entries;
|
|
@@ -46,7 +28,6 @@ export function isPlainFieldIndex(index) {
|
|
|
46
28
|
return (entries.length === 1 &&
|
|
47
29
|
entry !== undefined &&
|
|
48
30
|
!entry.expression &&
|
|
49
|
-
!index.unique &&
|
|
50
31
|
index.where === undefined &&
|
|
51
32
|
// Postgres names an access method on every index, so the default one still counts as plain.
|
|
52
33
|
(index.type === undefined || index.type === 'btree') &&
|
|
@@ -60,8 +41,9 @@ export function isPlainFieldIndex(index) {
|
|
|
60
41
|
*/
|
|
61
42
|
export function buildIndexDecoratorSource(index, propertyName, param) {
|
|
62
43
|
const entries = index.entries.map((entry) => indexEntrySource(entry, propertyName, param)).join(', ');
|
|
44
|
+
// `@Index` requires a `distance` beside a vector `type`, as the introspector reads it back.
|
|
63
45
|
const isVector = isVectorIndexType(index.type);
|
|
64
|
-
const distance = isVector ?
|
|
46
|
+
const distance = isVector ? index.distance : undefined;
|
|
65
47
|
const options = [];
|
|
66
48
|
if (index.name)
|
|
67
49
|
options.push(`name: '${index.name}'`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IndexSchema } from '../../type/index.js';
|
|
1
2
|
import { IndexDdl } from './indexDdl.js';
|
|
2
3
|
/**
|
|
3
4
|
* SQL Server's `CREATE INDEX` is the portable form minus what 2025 rejects: an expression (Msg 16216),
|
|
@@ -7,4 +8,8 @@ import { IndexDdl } from './indexDdl.js';
|
|
|
7
8
|
export declare class MsSqlIndexDdl extends IndexDdl {
|
|
8
9
|
protected readonly indexFeatures: Set<"expression" | "include" | "jsonArray" | "jsonPath" | "nullsOrder" | "opsClass" | "partial" | "prefixLength">;
|
|
9
10
|
protected readonly indexTypes: Set<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch">;
|
|
11
|
+
/** T-SQL has no `IF NOT EXISTS` on an index, so the create is guarded by a lookup in the same statement. */
|
|
12
|
+
getCreateIndexStatement(tableName: string, index: IndexSchema, opts?: {
|
|
13
|
+
ifNotExists?: boolean;
|
|
14
|
+
}): string;
|
|
10
15
|
}
|
|
@@ -7,4 +7,14 @@ import { IndexDdl } from './indexDdl.js';
|
|
|
7
7
|
export class MsSqlIndexDdl extends IndexDdl {
|
|
8
8
|
indexFeatures = new Set(['partial']);
|
|
9
9
|
indexTypes = new Set(['btree']);
|
|
10
|
+
/** T-SQL has no `IF NOT EXISTS` on an index, so the create is guarded by a lookup in the same statement. */
|
|
11
|
+
getCreateIndexStatement(tableName, index, opts = {}) {
|
|
12
|
+
const create = super.getCreateIndexStatement(tableName, index, { ifNotExists: false });
|
|
13
|
+
if (!(opts.ifNotExists ?? this.dialect.features.indexIfNotExists)) {
|
|
14
|
+
return create;
|
|
15
|
+
}
|
|
16
|
+
const table = this.dialect.escape(this.dialect.escapeId(tableName));
|
|
17
|
+
const name = this.dialect.escape(index.name);
|
|
18
|
+
return `IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${name} AND object_id = OBJECT_ID(${table})) ${create}`;
|
|
19
|
+
}
|
|
10
20
|
}
|
|
@@ -2,13 +2,15 @@ import type { ColumnSchema } from '../../type/index.js';
|
|
|
2
2
|
import { TableDdl } from './tableDdl.js';
|
|
3
3
|
/**
|
|
4
4
|
* SQL Server keeps a column's `DEFAULT`, `CHECK` and `UNIQUE` as constraints under names it picks, and
|
|
5
|
-
* refuses to drop or retype the column past one, so they go first - looked up
|
|
6
|
-
* databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
|
|
5
|
+
* refuses to drop or retype the column past one or past an index over it, so they go first - looked up
|
|
6
|
+
* by column, as no two databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
|
|
7
7
|
*/
|
|
8
8
|
export declare class MsSqlTableDdl extends TableDdl {
|
|
9
|
+
/** T-SQL has no `IF NOT EXISTS` on a table, so the create is guarded by a lookup in the same statement. */
|
|
10
|
+
createTable(target: string, ifNotExists: boolean): string;
|
|
9
11
|
/** T-SQL rejects the optional `COLUMN` keyword after `ADD`. */
|
|
10
12
|
addColumn(table: string, definition: string): string;
|
|
11
|
-
/** Its constraints go with the column, as they do on every other engine. */
|
|
13
|
+
/** Its constraints and indexes go with the column, as they do on every other engine. */
|
|
12
14
|
dropColumn(table: string, column: string): string[];
|
|
13
15
|
/**
|
|
14
16
|
* `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
|
|
@@ -19,5 +21,5 @@ export declare class MsSqlTableDdl extends TableDdl {
|
|
|
19
21
|
renameTable(oldName: string, newName: string): string;
|
|
20
22
|
storedGeneratedColumn(_type: string, expression: string): string;
|
|
21
23
|
/** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
|
|
22
|
-
private
|
|
24
|
+
private dropPinning;
|
|
23
25
|
}
|
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
import { escapeSingleQuotes } from '../../util/sqlLiteral.js';
|
|
2
2
|
import { sizedType, TableDdl } from './tableDdl.js';
|
|
3
|
-
/**
|
|
4
|
-
const
|
|
5
|
-
default: /*sql*/ `SELECT d.name FROM sys.default_constraints d
|
|
3
|
+
/** What pins column `c`, the `sys.columns` row {@link MsSqlTableDdl} reads: each constraint, then each index, by kind. */
|
|
4
|
+
const PINNED_BY = {
|
|
5
|
+
default: /*sql*/ `SELECT d.name, 0 AS is_index FROM sys.default_constraints d
|
|
6
6
|
WHERE d.parent_object_id = c.object_id AND d.parent_column_id = c.column_id`,
|
|
7
|
-
check: /*sql*/ `SELECT k.name FROM sys.check_constraints k
|
|
7
|
+
check: /*sql*/ `SELECT k.name, 0 AS is_index FROM sys.check_constraints k
|
|
8
8
|
WHERE k.parent_object_id = c.object_id AND k.parent_column_id = c.column_id`,
|
|
9
|
-
unique: /*sql*/ `SELECT u.name FROM sys.key_constraints u
|
|
9
|
+
unique: /*sql*/ `SELECT u.name, 0 AS is_index FROM sys.key_constraints u
|
|
10
10
|
JOIN sys.index_columns ic ON ic.object_id = u.parent_object_id AND ic.index_id = u.unique_index_id
|
|
11
11
|
WHERE u.parent_object_id = c.object_id AND u.type = 'UQ' AND ic.column_id = c.column_id`,
|
|
12
|
+
index: /*sql*/ `SELECT DISTINCT i.name, 1 AS is_index FROM sys.indexes i
|
|
13
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
14
|
+
WHERE i.object_id = c.object_id AND ic.column_id = c.column_id
|
|
15
|
+
AND i.is_primary_key = 0 AND i.is_unique_constraint = 0`,
|
|
12
16
|
};
|
|
13
17
|
/**
|
|
14
18
|
* SQL Server keeps a column's `DEFAULT`, `CHECK` and `UNIQUE` as constraints under names it picks, and
|
|
15
|
-
* refuses to drop or retype the column past one, so they go first - looked up
|
|
16
|
-
* databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
|
|
19
|
+
* refuses to drop or retype the column past one or past an index over it, so they go first - looked up
|
|
20
|
+
* by column, as no two databases name them alike. Renames are `sp_rename`, T-SQL having no `RENAME` clause.
|
|
17
21
|
*/
|
|
18
22
|
export class MsSqlTableDdl extends TableDdl {
|
|
23
|
+
/** T-SQL has no `IF NOT EXISTS` on a table, so the create is guarded by a lookup in the same statement. */
|
|
24
|
+
createTable(target, ifNotExists) {
|
|
25
|
+
const guard = ifNotExists ? `IF OBJECT_ID(${this.dialect.escape(target)}, N'U') IS NULL ` : '';
|
|
26
|
+
return `${guard}${super.createTable(target, false)}`;
|
|
27
|
+
}
|
|
19
28
|
/** T-SQL rejects the optional `COLUMN` keyword after `ADD`. */
|
|
20
29
|
addColumn(table, definition) {
|
|
21
30
|
return /*sql*/ `ALTER TABLE ${this.dialect.escapeId(table)} ADD ${definition};`;
|
|
22
31
|
}
|
|
23
|
-
/** Its constraints go with the column, as they do on every other engine. */
|
|
32
|
+
/** Its constraints and indexes go with the column, as they do on every other engine. */
|
|
24
33
|
dropColumn(table, column) {
|
|
25
|
-
return [this.
|
|
34
|
+
return [this.dropPinning(table, column, Object.values(PINNED_BY)), ...super.dropColumn(table, column)];
|
|
26
35
|
}
|
|
27
36
|
/**
|
|
28
37
|
* `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
|
|
@@ -32,7 +41,7 @@ export class MsSqlTableDdl extends TableDdl {
|
|
|
32
41
|
const target = this.dialect.escapeId(table);
|
|
33
42
|
const name = this.dialect.escapeId(column.name);
|
|
34
43
|
const statements = [
|
|
35
|
-
this.
|
|
44
|
+
this.dropPinning(table, column.name, [PINNED_BY.default]),
|
|
36
45
|
/*sql*/ `ALTER TABLE ${target} ALTER COLUMN ${name} ${sizedType(column)} ${column.nullable ? 'NULL' : 'NOT NULL'};`,
|
|
37
46
|
];
|
|
38
47
|
if (column.defaultValue !== undefined) {
|
|
@@ -51,10 +60,12 @@ export class MsSqlTableDdl extends TableDdl {
|
|
|
51
60
|
return /*sql*/ `AS (${expression}) PERSISTED`;
|
|
52
61
|
}
|
|
53
62
|
/** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
|
|
54
|
-
|
|
55
|
-
const target = this.dialect.escapeId(table);
|
|
56
|
-
return (`DECLARE @drop nvarchar(max) = (SELECT STRING_AGG(
|
|
57
|
-
|
|
63
|
+
dropPinning(table, column, kinds) {
|
|
64
|
+
const target = escapeSingleQuotes(this.dialect.escapeId(table));
|
|
65
|
+
return (`DECLARE @drop nvarchar(max) = (SELECT STRING_AGG(CASE pinned.is_index ` +
|
|
66
|
+
`WHEN 1 THEN N'DROP INDEX ' + QUOTENAME(pinned.name) + N' ON ${target}' ` +
|
|
67
|
+
`ELSE N'ALTER TABLE ${target} DROP CONSTRAINT ' + QUOTENAME(pinned.name) END, N'; ') ` +
|
|
68
|
+
`FROM sys.columns c CROSS APPLY (${kinds.join(' UNION ALL ')}) pinned ` +
|
|
58
69
|
`WHERE c.object_id = OBJECT_ID(${this.dialect.escape(target)}) AND c.name = ${this.dialect.escape(column)}) ` +
|
|
59
70
|
'EXEC (@drop);');
|
|
60
71
|
}
|
|
@@ -48,8 +48,8 @@ export declare class MariaIndexDdl extends MysqlLikeIndexDdl {
|
|
|
48
48
|
protected readonly indexTypeHints: Map<"brin" | "btree" | "fulltext" | "gin" | "gist" | "hash" | "hnsw" | "ivfflat" | "vector" | "vectorSearch", string>;
|
|
49
49
|
/**
|
|
50
50
|
* `M=n DISTANCE=metric`, trailing its `CREATE VECTOR INDEX`. The metric names are MariaDB's own
|
|
51
|
-
* (`euclidean`, not `l2`),
|
|
52
|
-
*
|
|
51
|
+
* (`euclidean`, not `l2`), stated even for the default distance, cosine, since MariaDB's own is
|
|
52
|
+
* euclidean; and an unsupported one throws rather than silently building on euclidean.
|
|
53
53
|
*/
|
|
54
54
|
protected indexTuning(index: IndexSchema): string;
|
|
55
55
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsonTypeMode } from '../../dialect/jsonSql.js';
|
|
2
|
-
import { unsupportedVectorMetric, VECTOR_INDEX_TYPES } from '../../type/vector.js';
|
|
2
|
+
import { indexDistance, isVectorIndexType, unsupportedVectorMetric, VECTOR_INDEX_TYPES } from '../../type/vector.js';
|
|
3
3
|
import { IndexDdl } from './indexDdl.js';
|
|
4
4
|
/**
|
|
5
5
|
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext` is
|
|
@@ -84,15 +84,16 @@ export class MariaIndexDdl extends MysqlLikeIndexDdl {
|
|
|
84
84
|
]);
|
|
85
85
|
/**
|
|
86
86
|
* `M=n DISTANCE=metric`, trailing its `CREATE VECTOR INDEX`. The metric names are MariaDB's own
|
|
87
|
-
* (`euclidean`, not `l2`),
|
|
88
|
-
*
|
|
87
|
+
* (`euclidean`, not `l2`), stated even for the default distance, cosine, since MariaDB's own is
|
|
88
|
+
* euclidean; and an unsupported one throws rather than silently building on euclidean.
|
|
89
89
|
*/
|
|
90
90
|
indexTuning(index) {
|
|
91
91
|
let tuning = super.indexTuning(index) + (index.m === undefined ? '' : ` M=${index.m}`);
|
|
92
|
-
if (index.
|
|
93
|
-
const
|
|
92
|
+
if (isVectorIndexType(index.type)) {
|
|
93
|
+
const distance = indexDistance(index);
|
|
94
|
+
const metric = this.dialect.vectorMetrics.get(distance)?.index;
|
|
94
95
|
if (!metric) {
|
|
95
|
-
throw unsupportedVectorMetric(this.dialect.dialectName,
|
|
96
|
+
throw unsupportedVectorMetric(this.dialect.dialectName, distance, index.name);
|
|
96
97
|
}
|
|
97
98
|
tuning += ` DISTANCE=${metric}`;
|
|
98
99
|
}
|