uql-orm 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/dialect/abstractSqlDialect.d.ts +15 -5
- package/dist/dialect/abstractSqlDialect.js +109 -40
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/vectorSqlDialect.d.ts +2 -0
- package/dist/dialect/vectorSqlDialect.js +4 -0
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +26 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +2 -2
- package/dist/migrate/codegen/indexDecoratorSource.d.ts +3 -2
- package/dist/migrate/codegen/indexDecoratorSource.js +5 -23
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +4 -4
- package/dist/migrate/ddl/mssqlTableDdl.js +20 -14
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +2 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +12 -10
- package/dist/migrate/ddl/pgIndexDdl.d.ts +2 -1
- package/dist/migrate/ddl/pgIndexDdl.js +10 -7
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.d.ts +3 -2
- package/dist/migrate/ddl/tableDdl.js +11 -8
- 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/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +17 -24
- package/dist/migrate/index.d.ts +2 -1
- package/dist/migrate/index.js +1 -0
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -7
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +13 -19
- package/dist/migrate/introspection/baseSqlIntrospector.js +7 -18
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +5 -4
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +15 -5
- package/dist/migrate/introspection/mysqlIntrospector.js +32 -4
- package/dist/migrate/introspection/postgresIntrospector.d.ts +29 -21
- package/dist/migrate/introspection/postgresIntrospector.js +63 -46
- package/dist/migrate/introspection/sqliteIntrospector.js +11 -9
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.d.ts +5 -0
- package/dist/migrate/migrator.js +35 -45
- package/dist/migrate/schemaChange.d.ts +18 -0
- package/dist/migrate/schemaChange.js +37 -0
- package/dist/migrate/schemaGenerator.d.ts +13 -14
- package/dist/migrate/schemaGenerator.js +88 -181
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +3 -7
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- package/dist/schema/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 +2 -0
- package/dist/schema/schemaASTBuilder.js +17 -11
- 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/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +15 -11
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/migration.d.ts +32 -50
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/ddlExpression.util.d.ts +5 -1
- package/dist/util/ddlExpression.util.js +6 -2
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.js +3 -2
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { withDeleted } from './util/filters.util.js';
|
|
|
8
8
|
export type { HookContext } from './util/hook.util.js';
|
|
9
9
|
export { DefaultLogger } from './util/logger.js';
|
|
10
10
|
export { raw, refs } from './util/raw.js';
|
|
11
|
+
export { deleteFrom, insertInto, updateTable } from './util/triggerWrite.js';
|
|
11
12
|
export * from './util/uqlError.js';
|
package/dist/index.js
CHANGED
|
@@ -7,4 +7,5 @@ export * from './type/index.js';
|
|
|
7
7
|
export { withDeleted } from './util/filters.util.js';
|
|
8
8
|
export { DefaultLogger } from './util/logger.js';
|
|
9
9
|
export { raw, refs } from './util/raw.js';
|
|
10
|
+
export { deleteFrom, insertInto, updateTable } from './util/triggerWrite.js';
|
|
10
11
|
export * from './util/uqlError.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isMongoQuerier, isSqlQuerier, } from '../type/index.js';
|
|
2
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
2
3
|
/**
|
|
3
4
|
* Querier used for schema migrations and the migration journal.
|
|
4
5
|
*
|
|
@@ -34,7 +35,7 @@ export function withMongoQuerierForMigrations(pool, requiredBy, task) {
|
|
|
34
35
|
function withQuerierOfKind(pool, isKind, error, task) {
|
|
35
36
|
return withQuerierForMigrations(pool, (querier) => {
|
|
36
37
|
if (!isKind(querier)) {
|
|
37
|
-
throw new
|
|
38
|
+
throw new UqlUsageError(error);
|
|
38
39
|
}
|
|
39
40
|
return task(querier);
|
|
40
41
|
});
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
1
2
|
/**
|
|
2
3
|
* Validates shape required for the migrations CLI (real `QuerierPool`, not config stubs).
|
|
3
4
|
*/
|
|
4
5
|
export function assertCliConfig(config) {
|
|
5
6
|
if (config === null || typeof config !== 'object') {
|
|
6
|
-
throw new
|
|
7
|
+
throw new UqlUsageError('Config must be a non-null object');
|
|
7
8
|
}
|
|
8
9
|
const c = config;
|
|
9
10
|
const pool = c['pool'];
|
|
10
11
|
if (pool === null || typeof pool !== 'object') {
|
|
11
|
-
throw new
|
|
12
|
+
throw new UqlUsageError('Config.pool is required and must be an object');
|
|
12
13
|
}
|
|
13
14
|
const p = pool;
|
|
14
15
|
for (const key of ['getQuerier', 'transaction', 'withQuerier']) {
|
|
15
16
|
if (typeof p[key] !== 'function') {
|
|
16
|
-
throw new
|
|
17
|
+
throw new UqlUsageError(`Config.pool.${key} must be a function`);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
if (p['end'] !== undefined && typeof p['end'] !== 'function') {
|
|
20
|
-
throw new
|
|
21
|
+
throw new UqlUsageError('Config.pool.end must be a function when provided');
|
|
21
22
|
}
|
|
22
23
|
const dialect = p['dialect'];
|
|
23
24
|
if (dialect === null || typeof dialect !== 'object') {
|
|
24
|
-
throw new
|
|
25
|
+
throw new UqlUsageError('Config.pool.dialect is required and must be an object');
|
|
25
26
|
}
|
|
26
27
|
const dialectName = dialect['dialectName'];
|
|
27
28
|
if (typeof dialectName !== 'string') {
|
|
28
|
-
throw new
|
|
29
|
+
throw new UqlUsageError('Config.pool.dialect.dialectName must be a string');
|
|
29
30
|
}
|
|
30
31
|
}
|
package/dist/migrate/bin.js
CHANGED
|
File without changes
|
|
@@ -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;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
1
2
|
const ANSI = {
|
|
2
3
|
now: 'CURRENT_TIMESTAMP',
|
|
3
4
|
currentDate: 'CURRENT_DATE',
|
|
@@ -83,6 +84,30 @@ export function formatDefaultValue(value, dialect, columnType) {
|
|
|
83
84
|
const { wrapTypes } = DIALECT_DEFAULTS[dialect.dialectName];
|
|
84
85
|
return columnType !== undefined && wrapTypes?.test(columnType) ? `(${sql})` : sql;
|
|
85
86
|
}
|
|
87
|
+
/** Whether a stored default is the declared one, as the engine reprints it: `'a'::character varying` is `'a'`. */
|
|
88
|
+
export function sameDefault(desired, current, dialect) {
|
|
89
|
+
if (current === desired)
|
|
90
|
+
return true;
|
|
91
|
+
// Both spellings of "no default" are the same fact, and engines disagree on which they report:
|
|
92
|
+
// MariaDB says `null` where MySQL says nothing at all. Reading them as different values asked to
|
|
93
|
+
// `MODIFY` every nullable column, on every sync, forever.
|
|
94
|
+
if (current == null || desired == null)
|
|
95
|
+
return current == null && desired == null;
|
|
96
|
+
const normalize = (value) => {
|
|
97
|
+
// Render first: the desired side may be a symbolic expression, the current side is always the
|
|
98
|
+
// engine's own text, and `{"kind":"now"}` matches no spelling of `CURRENT_TIMESTAMP`.
|
|
99
|
+
const val = SqlExpression.isExpression(value) ? formatDefaultValue(value, dialect) : value;
|
|
100
|
+
if (typeof val === 'string') {
|
|
101
|
+
let s = val.replace(/::[a-z_]+(\s+[a-z_]+)*(\[\])?$/i, '');
|
|
102
|
+
s = s.replace(/^'(.*)'$/, '$1');
|
|
103
|
+
if (s.toLowerCase() === 'null')
|
|
104
|
+
return 'null';
|
|
105
|
+
return s;
|
|
106
|
+
}
|
|
107
|
+
return typeof val === 'object' ? JSON.stringify(val) : String(val);
|
|
108
|
+
};
|
|
109
|
+
return normalize(current) === normalize(desired);
|
|
110
|
+
}
|
|
86
111
|
/**
|
|
87
112
|
* Quoting is the dialect's `escape`, so a backslash in a default is escaped the way the engine reads
|
|
88
113
|
* it - MySQL takes `'a\b'` as a backspace where Postgres takes it literally. Only the cases `escape`
|
|
@@ -105,7 +130,7 @@ function expressionSql(expression, dialect) {
|
|
|
105
130
|
const { expressions } = DIALECT_DEFAULTS[dialect.dialectName];
|
|
106
131
|
const sql = expression.kind === 'raw' ? expression.sql : expressions[expression.kind];
|
|
107
132
|
if (sql == null) {
|
|
108
|
-
throw new
|
|
133
|
+
throw new UqlUsageError(`${dialect.dialectName} has no '${expression.kind}' default; pass expr.raw(...) with SQL this engine accepts`);
|
|
109
134
|
}
|
|
110
135
|
return sql;
|
|
111
136
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
4
5
|
/**
|
|
5
6
|
* Loads the config with a plain `import()`, leaving TypeScript to the runtime: uql bundles no transpiler,
|
|
6
7
|
* since only the project knows its decorator spec. Node's type stripping handles no decorators.
|
|
7
8
|
*/
|
|
8
9
|
async function importConfig(path) {
|
|
9
10
|
const mod = (await import(pathToFileURL(path).href).catch((cause) => {
|
|
10
|
-
throw new
|
|
11
|
+
throw new UqlUsageError(`Could not import ${path}: ${cause?.message}\n` +
|
|
11
12
|
'If it reaches entity classes, their decorators need a runtime that transforms TypeScript, not ' +
|
|
12
13
|
'just one that strips its types. Run the CLI with `bun`, or with `node --import tsx` ' +
|
|
13
14
|
'(`npm i -D tsx`). A JavaScript config, or passing the config inline, needs neither.', { cause });
|
|
@@ -21,14 +22,14 @@ export async function loadConfig(customPath) {
|
|
|
21
22
|
.then(() => true)
|
|
22
23
|
.catch(() => false);
|
|
23
24
|
if (!exists) {
|
|
24
|
-
throw new
|
|
25
|
+
throw new UqlUsageError(`Could not find uql configuration file at ${customPath}`);
|
|
25
26
|
}
|
|
26
27
|
try {
|
|
27
28
|
const config = await importConfig(fullPath);
|
|
28
29
|
return config;
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
31
|
-
throw new
|
|
32
|
+
throw new UqlUsageError(`Could not load configuration file at ${customPath}: ${error.message}`);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
const configPaths = ['uql.config.ts', 'uql.config.js', 'uql.config.mjs', '.uqlrc.ts', '.uqlrc.js'];
|
|
@@ -42,5 +43,5 @@ export async function loadConfig(customPath) {
|
|
|
42
43
|
return config;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
throw new
|
|
46
|
+
throw new UqlUsageError('Could not find uql configuration file. Create a uql.config.ts or uql.config.js file in your project root.');
|
|
46
47
|
}
|
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,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}'`);
|
|
@@ -2,6 +2,7 @@ import { jsonTypeMode } from '../../dialect/jsonSql.js';
|
|
|
2
2
|
import { INDEX_TYPES } from '../../schema/types.js';
|
|
3
3
|
import { INDEX_FEATURE_LABELS, } from '../../type/index.js';
|
|
4
4
|
import { fulltextConfig, getKeys } from '../../util/index.js';
|
|
5
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
5
6
|
/**
|
|
6
7
|
* What in an index asks for each feature. A `Record` over the feature union rather than a list, so a
|
|
7
8
|
* feature added to {@link INDEX_FEATURE_LABELS} cannot reach a dialect without the test that decides
|
|
@@ -20,14 +21,14 @@ const INDEX_FEATURE_PROBES = {
|
|
|
20
21
|
/** Refuses an index of a type `types` lacks, `hints` naming what to declare instead. */
|
|
21
22
|
export function assertIndexType(index, types, dialectName, hints = new Map()) {
|
|
22
23
|
if (index.type && !types.has(index.type)) {
|
|
23
|
-
throw new
|
|
24
|
+
throw new UqlUsageError(`${dialectName} has no ${index.type} index (index "${index.name}")` + (hints.get(index.type) ?? ''));
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
/** Refuses an index asking for a feature `features` lacks. */
|
|
27
28
|
export function assertIndexFeatures(index, features, dialectName) {
|
|
28
29
|
for (const feature of getKeys(INDEX_FEATURE_PROBES)) {
|
|
29
30
|
if (INDEX_FEATURE_PROBES[feature](index) && !features.has(feature)) {
|
|
30
|
-
throw new
|
|
31
|
+
throw new UqlUsageError(`${dialectName} does not support ${INDEX_FEATURE_LABELS[feature]} (index "${index.name}")`);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -122,7 +123,7 @@ export class IndexDdl {
|
|
|
122
123
|
* every other dialect refuses `jsonArray` in {@link assertIndexFeatures} and never reaches this.
|
|
123
124
|
*/
|
|
124
125
|
jsonArrayIndexExpr(_escapedColumn, _json) {
|
|
125
|
-
throw new
|
|
126
|
+
throw new UqlUsageError(`${this.dialect.dialectName} has no multi-valued index`);
|
|
126
127
|
}
|
|
127
128
|
/** Postgres-wire dialects put a vector or user-declared operator class here. */
|
|
128
129
|
indexColumnOpsClass(_entry, _index) {
|
|
@@ -2,15 +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
9
|
/** T-SQL has no `IF NOT EXISTS` on a table, so the create is guarded by a lookup in the same statement. */
|
|
10
10
|
createTable(target: string, ifNotExists: boolean): string;
|
|
11
11
|
/** T-SQL rejects the optional `COLUMN` keyword after `ADD`. */
|
|
12
12
|
addColumn(table: string, definition: string): string;
|
|
13
|
-
/** 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. */
|
|
14
14
|
dropColumn(table: string, column: string): string[];
|
|
15
15
|
/**
|
|
16
16
|
* `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
|
|
@@ -21,5 +21,5 @@ export declare class MsSqlTableDdl extends TableDdl {
|
|
|
21
21
|
renameTable(oldName: string, newName: string): string;
|
|
22
22
|
storedGeneratedColumn(_type: string, expression: string): string;
|
|
23
23
|
/** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
|
|
24
|
-
private
|
|
24
|
+
private dropPinning;
|
|
25
25
|
}
|
|
@@ -1,19 +1,23 @@
|
|
|
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 {
|
|
19
23
|
/** T-SQL has no `IF NOT EXISTS` on a table, so the create is guarded by a lookup in the same statement. */
|
|
@@ -25,9 +29,9 @@ export class MsSqlTableDdl extends TableDdl {
|
|
|
25
29
|
addColumn(table, definition) {
|
|
26
30
|
return /*sql*/ `ALTER TABLE ${this.dialect.escapeId(table)} ADD ${definition};`;
|
|
27
31
|
}
|
|
28
|
-
/** 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. */
|
|
29
33
|
dropColumn(table, column) {
|
|
30
|
-
return [this.
|
|
34
|
+
return [this.dropPinning(table, column, Object.values(PINNED_BY)), ...super.dropColumn(table, column)];
|
|
31
35
|
}
|
|
32
36
|
/**
|
|
33
37
|
* `ALTER COLUMN` takes the type and nullability alone, so the default is dropped and added back as a
|
|
@@ -37,7 +41,7 @@ export class MsSqlTableDdl extends TableDdl {
|
|
|
37
41
|
const target = this.dialect.escapeId(table);
|
|
38
42
|
const name = this.dialect.escapeId(column.name);
|
|
39
43
|
const statements = [
|
|
40
|
-
this.
|
|
44
|
+
this.dropPinning(table, column.name, [PINNED_BY.default]),
|
|
41
45
|
/*sql*/ `ALTER TABLE ${target} ALTER COLUMN ${name} ${sizedType(column)} ${column.nullable ? 'NULL' : 'NOT NULL'};`,
|
|
42
46
|
];
|
|
43
47
|
if (column.defaultValue !== undefined) {
|
|
@@ -56,10 +60,12 @@ export class MsSqlTableDdl extends TableDdl {
|
|
|
56
60
|
return /*sql*/ `AS (${expression}) PERSISTED`;
|
|
57
61
|
}
|
|
58
62
|
/** One statement, so a split on `;` cannot part the lookup from the `EXEC` it feeds. */
|
|
59
|
-
|
|
60
|
-
const target = this.dialect.escapeId(table);
|
|
61
|
-
return (`DECLARE @drop nvarchar(max) = (SELECT STRING_AGG(
|
|
62
|
-
|
|
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 ` +
|
|
63
69
|
`WHERE c.object_id = OBJECT_ID(${this.dialect.escape(target)}) AND c.name = ${this.dialect.escape(column)}) ` +
|
|
64
70
|
'EXEC (@drop);');
|
|
65
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,6 @@
|
|
|
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
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
3
4
|
import { IndexDdl } from './indexDdl.js';
|
|
4
5
|
/**
|
|
5
6
|
* A full-text index is its own keyword here (`CREATE FULLTEXT INDEX ... (cols)`); `USING fulltext` is
|
|
@@ -38,14 +39,14 @@ export class MySqlIndexDdl extends MysqlLikeIndexDdl {
|
|
|
38
39
|
jsonPathIndexExpr(escapedColumn, json) {
|
|
39
40
|
const mode = jsonTypeMode(json.type);
|
|
40
41
|
if (mode === 'json') {
|
|
41
|
-
throw new
|
|
42
|
+
throw new UqlUsageError(`mysql cannot index the boolean JSON path '${json.path}', which compares as JSON`);
|
|
42
43
|
}
|
|
43
44
|
const expr = super.jsonPathIndexExpr(escapedColumn, json);
|
|
44
45
|
if (mode === 'numeric') {
|
|
45
46
|
return expr;
|
|
46
47
|
}
|
|
47
48
|
if (!json.length) {
|
|
48
|
-
throw new
|
|
49
|
+
throw new UqlUsageError(`a MySQL index over the string JSON path '${json.path}' needs a length`);
|
|
49
50
|
}
|
|
50
51
|
return `CAST(${expr} AS CHAR(${json.length}) CHARACTER SET utf8mb4) COLLATE utf8mb4_bin`;
|
|
51
52
|
}
|
|
@@ -84,15 +85,16 @@ export class MariaIndexDdl extends MysqlLikeIndexDdl {
|
|
|
84
85
|
]);
|
|
85
86
|
/**
|
|
86
87
|
* `M=n DISTANCE=metric`, trailing its `CREATE VECTOR INDEX`. The metric names are MariaDB's own
|
|
87
|
-
* (`euclidean`, not `l2`),
|
|
88
|
-
*
|
|
88
|
+
* (`euclidean`, not `l2`), stated even for the default distance, cosine, since MariaDB's own is
|
|
89
|
+
* euclidean; and an unsupported one throws rather than silently building on euclidean.
|
|
89
90
|
*/
|
|
90
91
|
indexTuning(index) {
|
|
91
92
|
let tuning = super.indexTuning(index) + (index.m === undefined ? '' : ` M=${index.m}`);
|
|
92
|
-
if (index.
|
|
93
|
-
const
|
|
93
|
+
if (isVectorIndexType(index.type)) {
|
|
94
|
+
const distance = indexDistance(index);
|
|
95
|
+
const metric = this.dialect.vectorMetrics.get(distance)?.index;
|
|
94
96
|
if (!metric) {
|
|
95
|
-
throw unsupportedVectorMetric(this.dialect.dialectName,
|
|
97
|
+
throw unsupportedVectorMetric(this.dialect.dialectName, distance, index.name);
|
|
96
98
|
}
|
|
97
99
|
tuning += ` DISTANCE=${metric}`;
|
|
98
100
|
}
|
|
@@ -131,13 +133,13 @@ function arrayCastType(json) {
|
|
|
131
133
|
const type = json.type;
|
|
132
134
|
const cast = ARRAY_CASTS.get(typeof type === 'string' ? type.toLowerCase() : type);
|
|
133
135
|
if (!cast) {
|
|
134
|
-
throw new
|
|
136
|
+
throw new UqlUsageError(`mysql has no array cast for ${typeof type === 'string' ? type : type.name} elements`);
|
|
135
137
|
}
|
|
136
138
|
if (cast !== 'CHAR' && cast !== 'BINARY') {
|
|
137
139
|
return cast;
|
|
138
140
|
}
|
|
139
141
|
if (!json.length) {
|
|
140
|
-
throw new
|
|
142
|
+
throw new UqlUsageError(`a multi-valued index over ${cast === 'CHAR' ? 'string' : 'binary'} elements needs a length`);
|
|
141
143
|
}
|
|
142
144
|
return `${cast}(${json.length})`;
|
|
143
145
|
}
|
|
@@ -10,7 +10,8 @@ export declare class PgIndexDdl extends IndexDdl {
|
|
|
10
10
|
protected indexAccessMethod(index: IndexSchema): string;
|
|
11
11
|
/**
|
|
12
12
|
* A vector index's operator class, `{type}_{metric}_ops` (`halfvec_cosine_ops`), refusing a metric it
|
|
13
|
-
* lacks rather than build with the default; any other entry takes the class it declares.
|
|
13
|
+
* lacks rather than build with the default; any other entry takes the class it declares. Stated even
|
|
14
|
+
* for the default distance: pgvector's own default class is L2, which a cosine search never uses.
|
|
14
15
|
*/
|
|
15
16
|
protected indexColumnOpsClass(entry: IndexColumnSchema, index: IndexSchema): string;
|
|
16
17
|
protected indexInclude(index: IndexSchema): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { unsupportedVectorMetric } from '../../type/vector.js';
|
|
1
|
+
import { indexDistance, unsupportedVectorMetric } from '../../type/vector.js';
|
|
2
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
2
3
|
import { IndexDdl } from './indexDdl.js';
|
|
3
4
|
/** `CREATE INDEX ... USING hnsw ("embedding" vector_cosine_ops) WITH (m = ...)`, pgvector's form. */
|
|
4
5
|
export class PgIndexDdl extends IndexDdl {
|
|
@@ -33,21 +34,23 @@ export class PgIndexDdl extends IndexDdl {
|
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* A vector index's operator class, `{type}_{metric}_ops` (`halfvec_cosine_ops`), refusing a metric it
|
|
36
|
-
* lacks rather than build with the default; any other entry takes the class it declares.
|
|
37
|
+
* lacks rather than build with the default; any other entry takes the class it declares. Stated even
|
|
38
|
+
* for the default distance: pgvector's own default class is L2, which a cosine search never uses.
|
|
37
39
|
*/
|
|
38
40
|
indexColumnOpsClass(entry, index) {
|
|
39
|
-
if (!this.isVectorIndex(index)
|
|
41
|
+
if (!this.isVectorIndex(index)) {
|
|
40
42
|
return entry.opsClass ? ` ${entry.opsClass}` : '';
|
|
41
43
|
}
|
|
42
|
-
const
|
|
44
|
+
const distance = indexDistance(index);
|
|
45
|
+
const metric = this.dialect.vectorMetrics.get(distance)?.index;
|
|
43
46
|
if (!metric) {
|
|
44
|
-
throw unsupportedVectorMetric(this.dialect.dialectName,
|
|
47
|
+
throw unsupportedVectorMetric(this.dialect.dialectName, distance, index.name);
|
|
45
48
|
}
|
|
46
49
|
const vectorType = this.dialect.supportedVectorType(index.vectorType ?? 'vector');
|
|
47
50
|
const opsClass = `${vectorType}_${metric}_ops`;
|
|
48
51
|
// IVFFlat has neither a sparsevec nor an L1 operator class; HNSW has all of them (pgvector 0.8.2).
|
|
49
|
-
if (index.type === 'ivfflat' && (vectorType === 'sparsevec' ||
|
|
50
|
-
throw new
|
|
52
|
+
if (index.type === 'ivfflat' && (vectorType === 'sparsevec' || distance === 'l1')) {
|
|
53
|
+
throw new UqlUsageError(`ivfflat has no ${opsClass} operator class (index "${index.name}"); use hnsw`);
|
|
51
54
|
}
|
|
52
55
|
return ` ${opsClass}`;
|
|
53
56
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { indexDistance, isVectorIndexType, unsupportedVectorMetric } from '../../type/vector.js';
|
|
2
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
2
3
|
import { IndexDdl } from './indexDdl.js';
|
|
3
4
|
/**
|
|
4
5
|
* SQLite's `CREATE INDEX`, which names no index type. A vector index is libSQL's DiskANN where the dialect
|
|
@@ -31,7 +32,7 @@ export class SqliteIndexDdl extends IndexDdl {
|
|
|
31
32
|
}
|
|
32
33
|
// Its tables are named after the index, unquoted: any other name fails with "unable to initialize diskann".
|
|
33
34
|
if (!/^\w+$/.test(index.name) || index.entries.length !== 1) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new UqlUsageError(`libSQL names a vector index only by letters, digits and underscores, over one column (index "${index.name}")`);
|
|
35
36
|
}
|
|
36
37
|
return metric;
|
|
37
38
|
}
|
|
@@ -18,9 +18,10 @@ export declare class TableDdl {
|
|
|
18
18
|
dropColumn(table: string, column: string): string[];
|
|
19
19
|
/**
|
|
20
20
|
* What changes `column` to what it now declares. `definition` is the whole column, which MySQL's
|
|
21
|
-
* `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own
|
|
21
|
+
* `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own, so given what the
|
|
22
|
+
* column was (`from`), only the clauses that changed.
|
|
22
23
|
*/
|
|
23
|
-
alterColumn(table: string, column: ColumnSchema, definition: string): string[];
|
|
24
|
+
alterColumn(table: string, column: ColumnSchema, definition: string, from?: ColumnSchema): string[];
|
|
24
25
|
renameColumn(table: string, oldName: string, newName: string): string;
|
|
25
26
|
renameTable(oldName: string, newName: string): string;
|
|
26
27
|
/** A stored generated column's type, with the clause computing it. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
2
|
+
import { formatDefaultValue, sameDefault } from '../builder/expressions.js';
|
|
2
3
|
/**
|
|
3
4
|
* A column's type with the size it was read back with, unless its spelling already carries one:
|
|
4
5
|
* introspection reports `VARCHAR` and `255` apart, where a type from an entity is already whole.
|
|
@@ -34,11 +35,12 @@ export class TableDdl {
|
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* What changes `column` to what it now declares. `definition` is the whole column, which MySQL's
|
|
37
|
-
* `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own
|
|
38
|
+
* `MODIFY COLUMN` restates; Postgres takes each change as a clause of its own, so given what the
|
|
39
|
+
* column was (`from`), only the clauses that changed.
|
|
38
40
|
*/
|
|
39
|
-
alterColumn(table, column, definition) {
|
|
41
|
+
alterColumn(table, column, definition, from) {
|
|
40
42
|
if (this.dialect.alterColumnSyntax === 'none') {
|
|
41
|
-
throw new
|
|
43
|
+
throw new UqlUsageError(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
|
|
42
44
|
`This database does not support ALTER COLUMN.`);
|
|
43
45
|
}
|
|
44
46
|
const target = this.dialect.escapeId(table);
|
|
@@ -47,10 +49,11 @@ export class TableDdl {
|
|
|
47
49
|
}
|
|
48
50
|
const alter = `ALTER TABLE ${target} ALTER COLUMN ${this.dialect.escapeId(column.name)}`;
|
|
49
51
|
return [
|
|
50
|
-
`${alter} TYPE ${column.type};`,
|
|
51
|
-
`${alter} ${column.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'};`,
|
|
52
|
-
column.defaultValue
|
|
53
|
-
|
|
52
|
+
(!from || from.type !== column.type) && `${alter} TYPE ${column.type};`,
|
|
53
|
+
(!from || from.nullable !== column.nullable) && `${alter} ${column.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'};`,
|
|
54
|
+
(!from || !sameDefault(column.defaultValue, from.defaultValue, this.dialect)) &&
|
|
55
|
+
(column.defaultValue === undefined ? `${alter} DROP DEFAULT;` : `${alter} SET${this.defaultClause(column)};`),
|
|
56
|
+
].filter((statement) => statement !== false);
|
|
54
57
|
}
|
|
55
58
|
renameColumn(table, oldName, newName) {
|
|
56
59
|
const [target, from, to] = [table, oldName, newName].map((name) => this.dialect.escapeId(name));
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* actual database schema.
|
|
6
6
|
*/
|
|
7
7
|
import type { AbstractDialect } from '../../dialect/abstractDialect.js';
|
|
8
|
-
import type { IndexFacet } from '../../schema/indexDifferences.js';
|
|
9
8
|
import type { SchemaAST } from '../../schema/schemaAST.js';
|
|
10
9
|
import type { DriftReport } from '../../schema/types.js';
|
|
11
10
|
/**
|
|
@@ -18,15 +17,15 @@ export interface DriftDetectorOptions {
|
|
|
18
17
|
checkNullable?: boolean;
|
|
19
18
|
/** Include index differences */
|
|
20
19
|
checkIndexes?: boolean;
|
|
21
|
-
/** `indexFacets` of the introspector that produced the actual schema; anything else goes uncompared. */
|
|
22
|
-
indexFacets?: ReadonlySet<IndexFacet>;
|
|
23
20
|
/** Include foreign key differences */
|
|
24
21
|
checkForeignKeys?: boolean;
|
|
25
22
|
/**
|
|
26
|
-
* Include default value differences. Off
|
|
27
|
-
* (`now()`, `
|
|
23
|
+
* Include default value differences. Off unless {@link defaultsEqual} is given: without it defaults
|
|
24
|
+
* compare as written, and an engine reports one as it stored it (`now()`, `'active'::text`).
|
|
28
25
|
*/
|
|
29
26
|
checkDefaults?: boolean;
|
|
27
|
+
/** How the engine's generator compares a default, so drift reports the ones a migration would change. */
|
|
28
|
+
defaultsEqual?: (expected: unknown, actual: unknown) => boolean;
|
|
30
29
|
/**
|
|
31
30
|
* Tables to leave out of the comparison. The migrations bookkeeping table belongs here - it exists in
|
|
32
31
|
* the database by design and has no entity, so reporting it as unexpected told every project to
|