uql-orm 0.81.0 → 0.83.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/cockroachdb/crdbQuerierPool.js +2 -2
- package/dist/dialect/abstractSqlDialect.d.ts +16 -6
- package/dist/dialect/abstractSqlDialect.js +110 -41
- package/dist/dialect/hydrateColumn.js +2 -12
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -0
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
- package/dist/dialect/pgLikeSqlDialect.js +13 -4
- 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/maria/mariadbQuerierPool.js +4 -2
- 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 +20 -10
- package/dist/migrate/builder/tableBuilder.js +1 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
- package/dist/migrate/ddl/pgIndexDdl.js +2 -1
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.js +2 -1
- package/dist/migrate/generator/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +3 -2
- package/dist/migrate/introspection/mssqlIntrospector.js +13 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +6 -2
- package/dist/migrate/introspection/postgresIntrospector.js +1 -1
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.js +2 -1
- package/dist/migrate/schemaGenerator.js +5 -4
- package/dist/migrate/storage/databaseStorage.js +1 -1
- 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 +7 -10
- package/dist/mssql/mssqlQuerier.d.ts +2 -0
- package/dist/mssql/mssqlQuerier.js +8 -5
- package/dist/mysql/mysql2QuerierPool.d.ts +1 -0
- package/dist/mysql/mysql2QuerierPool.js +20 -2
- package/dist/neon/neonQuerierPool.js +2 -2
- package/dist/pglite/pgliteQuerierPool.js +10 -4
- package/dist/postgres/pgQuerierPool.js +2 -2
- package/dist/postgres/{pgNumericTypes.d.ts → pgWireTypes.d.ts} +4 -3
- package/dist/postgres/{pgNumericTypes.js → pgWireTypes.js} +7 -3
- 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/canonicalType.d.ts +3 -0
- package/dist/schema/canonicalType.js +31 -9
- package/dist/schema/schemaASTBuilder.js +2 -1
- package/dist/schema/schemaASTDiffer.js +4 -2
- package/dist/sqlite/sqliteDialect.d.ts +1 -3
- package/dist/sqlite/sqliteDialect.js +3 -6
- package/dist/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +16 -12
- package/dist/type/logger.d.ts +2 -2
- 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/date.d.ts +11 -0
- package/dist/util/date.js +19 -0
- 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/fieldOption.util.d.ts +5 -3
- package/dist/util/fieldOption.util.js +6 -5
- 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.d.ts +8 -1
- package/dist/util/sqlLiteral.js +14 -9
- 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 +4 -4
|
@@ -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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
1
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:
|
|
@@ -39,7 +40,7 @@ export class TableDdl {
|
|
|
39
40
|
*/
|
|
40
41
|
alterColumn(table, column, definition, from) {
|
|
41
42
|
if (this.dialect.alterColumnSyntax === 'none') {
|
|
42
|
-
throw new
|
|
43
|
+
throw new UqlUsageError(`${this.dialect}: Cannot alter column "${column.name}" - you must recreate the table. ` +
|
|
43
44
|
`This database does not support ALTER COLUMN.`);
|
|
44
45
|
}
|
|
45
46
|
const target = this.dialect.escapeId(table);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
1
2
|
export function serializeMongoCommand(command) {
|
|
2
3
|
return JSON.stringify(command);
|
|
3
4
|
}
|
|
@@ -9,7 +10,7 @@ function parseMongoCommand(statement) {
|
|
|
9
10
|
return JSON.parse(statement);
|
|
10
11
|
}
|
|
11
12
|
function unsupportedMongoCommand(statement) {
|
|
12
|
-
return new
|
|
13
|
+
return new UqlUsageError(`unsupported MongoDB migration command: ${statement}`);
|
|
13
14
|
}
|
|
14
15
|
/** Execute one emitted command. */
|
|
15
16
|
export function runMongoCommand(db, statement) {
|
|
@@ -33,7 +33,7 @@ export declare class MongoSchemaGenerator extends MongoDialect implements Schema
|
|
|
33
33
|
generateDropTable(tableName: string): string;
|
|
34
34
|
/** A collection's indexes: each dropped, then each created, an alter as both. */
|
|
35
35
|
generateAlterTable(diff: SchemaDiff): string[];
|
|
36
|
-
/** MongoDB
|
|
36
|
+
/** MongoDB runs no trigger within a write, and a write to an entity declaring one is refused, so there is none to reconcile. */
|
|
37
37
|
generateTriggers(): string[];
|
|
38
38
|
generateTriggersDown(): string[];
|
|
39
39
|
generateTriggerDrops(): string[];
|
|
@@ -6,6 +6,7 @@ import { QueryRaw, } from '../../type/index.js';
|
|
|
6
6
|
import { indexDistance, unsupportedVectorMetric } from '../../type/vector.js';
|
|
7
7
|
import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../../util/ddlExpression.util.js';
|
|
8
8
|
import { fulltextConfig, fulltextWeights } from '../../util/dialect.util.js';
|
|
9
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
9
10
|
import { assertIndexFeatures, assertIndexType } from '../ddl/indexDdl.js';
|
|
10
11
|
import { assertIndexPredicate, refusedIndexPredicate } from '../indexPredicate.js';
|
|
11
12
|
import { sides } from '../schemaChange.js';
|
|
@@ -30,7 +31,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
30
31
|
}
|
|
31
32
|
/** A collection has no SQL to render a check, a computed column or an index expression into. */
|
|
32
33
|
compileDdl() {
|
|
33
|
-
throw new
|
|
34
|
+
throw new UqlUsageError('mongodb has no SQL to render a check, a computed column or an index expression into');
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* A document store has no cross-collection constraint, so unlike the SQL generator there is nothing to
|
|
@@ -84,7 +85,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
84
85
|
*/
|
|
85
86
|
compileIndexPredicate(where, entity, indexName) {
|
|
86
87
|
if (where instanceof QueryRaw) {
|
|
87
|
-
throw new
|
|
88
|
+
throw new UqlUsageError(`mongodb does not support partial indexes from a SQL predicate (index "${indexName}")`);
|
|
88
89
|
}
|
|
89
90
|
assertIndexPredicate(where, this.dialectName, indexName);
|
|
90
91
|
const filter = this.renderFilter(entity, where);
|
|
@@ -114,7 +115,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
114
115
|
...sides(diff.indexes, 'to').map((index) => this.generateCreateIndex(diff.tableName, index)),
|
|
115
116
|
];
|
|
116
117
|
}
|
|
117
|
-
/** MongoDB
|
|
118
|
+
/** MongoDB runs no trigger within a write, and a write to an entity declaring one is refused, so there is none to reconcile. */
|
|
118
119
|
generateTriggers() {
|
|
119
120
|
return [];
|
|
120
121
|
}
|
|
@@ -160,7 +161,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
160
161
|
assertIndexFeatures(index, new Set(), this.dialectName);
|
|
161
162
|
const [vector, ...filters] = index.entries;
|
|
162
163
|
if (!vector || index.dimensions === undefined) {
|
|
163
|
-
throw new
|
|
164
|
+
throw new UqlUsageError(`an Atlas vector search index states its field's dimensions (index "${index.name}")`);
|
|
164
165
|
}
|
|
165
166
|
const distance = indexDistance(index);
|
|
166
167
|
const similarity = ATLAS_SIMILARITY[distance];
|
|
@@ -192,7 +193,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
192
193
|
case 'createTable': {
|
|
193
194
|
const { name, columns, indexes } = operation.table;
|
|
194
195
|
if (columns.length) {
|
|
195
|
-
throw new
|
|
196
|
+
throw new UqlUsageError(`mongodb does not support columns in a migration (collection "${name}")`);
|
|
196
197
|
}
|
|
197
198
|
return [
|
|
198
199
|
serializeMongoCommand({ action: 'createCollection', name }),
|
|
@@ -208,7 +209,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
208
209
|
case 'dropIndex':
|
|
209
210
|
return [this.generateDropIndex(operation.tableName, operation.indexName)];
|
|
210
211
|
default:
|
|
211
|
-
throw new
|
|
212
|
+
throw new UqlUsageError(`mongodb does not support ${operation.type} in a migration`);
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
diffSchema(entity, currentTable) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
2
2
|
import { isOperatorObject } from '../util/object.util.js';
|
|
3
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
3
4
|
/**
|
|
4
5
|
* The `$where` a partial index takes, on the engines where that is less than a query's: SQL Server's
|
|
5
6
|
* `CREATE INDEX` `<filter_predicate>` (comparisons and `IN` on plain columns, joined by `AND`), and
|
|
@@ -30,7 +31,7 @@ export function assertIndexPredicate(where, dialectName, indexName) {
|
|
|
30
31
|
}
|
|
31
32
|
/** What refusing any part of a partial index's predicate reports. */
|
|
32
33
|
export function refusedIndexPredicate(dialectName, part, indexName) {
|
|
33
|
-
return new
|
|
34
|
+
return new UqlUsageError(`${dialectName} does not support ${part} in a partial index predicate (index "${indexName}")`);
|
|
34
35
|
}
|
|
35
36
|
/** The first part of `where` outside `grammar`, depth-first; a `raw` clause is left alone. */
|
|
36
37
|
function refusedOperator(where, grammar) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FOREIGN_KEY_ACTIONS } from '../../schema/types.js';
|
|
2
2
|
import { isSqlQuerier } from '../../type/index.js';
|
|
3
3
|
import { isOwnedName } from '../../util/sql.util.js';
|
|
4
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
4
5
|
import { BaseSqlIntrospector } from './baseSqlIntrospector.js';
|
|
5
6
|
/** A SQL introspector: an engine states its catalogue queries (`get*Query`) and how their rows map (`map*Result`). */
|
|
6
7
|
export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
@@ -73,7 +74,7 @@ export class AbstractSqlSchemaIntrospector extends BaseSqlIntrospector {
|
|
|
73
74
|
withSqlQuerier(task) {
|
|
74
75
|
return this.pool.withQuerier((querier) => {
|
|
75
76
|
if (!isSqlQuerier(querier)) {
|
|
76
|
-
throw new
|
|
77
|
+
throw new UqlUsageError(`${this.constructor.name} requires a SQL-based querier`);
|
|
77
78
|
}
|
|
78
79
|
return task(querier);
|
|
79
80
|
});
|
|
@@ -10,7 +10,7 @@ export declare class MongoSchemaIntrospector implements SchemaIntrospector {
|
|
|
10
10
|
/** `listIndexes` reports keys, uniqueness and text weights; a `partialFilterExpression` is no SQL predicate. */
|
|
11
11
|
readonly indexFacets: ReadonlySet<IndexFacet>;
|
|
12
12
|
constructor(pool: QuerierPool);
|
|
13
|
-
/** MongoDB
|
|
13
|
+
/** MongoDB runs no trigger within a write, so uql installs none. */
|
|
14
14
|
ownedTriggers(): Promise<InstalledTriggers>;
|
|
15
15
|
introspect(tables?: readonly string[]): Promise<SchemaAST>;
|
|
16
16
|
getTableSchema(tableName: string): Promise<TableSchema | undefined>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { textConfigOf } from '../../mongo/textLanguage.js';
|
|
2
2
|
import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
|
|
3
3
|
import { isMongoQuerier, } from '../../type/index.js';
|
|
4
|
+
import { UqlUsageError } from '../../util/uqlError.js';
|
|
4
5
|
/** What a server without Atlas Search answers a search index command with. */
|
|
5
6
|
const SEARCH_NOT_ENABLED = 31082;
|
|
6
7
|
/**
|
|
@@ -14,7 +15,7 @@ export class MongoSchemaIntrospector {
|
|
|
14
15
|
constructor(pool) {
|
|
15
16
|
this.pool = pool;
|
|
16
17
|
}
|
|
17
|
-
/** MongoDB
|
|
18
|
+
/** MongoDB runs no trigger within a write, so uql installs none. */
|
|
18
19
|
async ownedTriggers() {
|
|
19
20
|
return new Map();
|
|
20
21
|
}
|
|
@@ -80,7 +81,7 @@ export class MongoSchemaIntrospector {
|
|
|
80
81
|
withDb(task) {
|
|
81
82
|
return this.pool.withQuerier((querier) => {
|
|
82
83
|
if (!isMongoQuerier(querier)) {
|
|
83
|
-
throw new
|
|
84
|
+
throw new UqlUsageError('MongoSchemaIntrospector requires a MongoDB querier');
|
|
84
85
|
}
|
|
85
86
|
return task(querier.db);
|
|
86
87
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultTimestampPrecision } from '../../schema/canonicalType.js';
|
|
1
2
|
import { AbstractSqlSchemaIntrospector, } from './abstractSqlSchemaIntrospector.js';
|
|
2
3
|
/**
|
|
3
4
|
* SQL Server schema introspector.
|
|
@@ -137,7 +138,7 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
137
138
|
const bytes = this.toNumber(row.max_length);
|
|
138
139
|
return {
|
|
139
140
|
name: row.column_name,
|
|
140
|
-
type:
|
|
141
|
+
type: spelledType(type, bytes, this.toNumber(row.numeric_scale)),
|
|
141
142
|
nullable: Boolean(row.is_nullable),
|
|
142
143
|
defaultValue: this.parseDefaultValue(row.column_default),
|
|
143
144
|
isAutoIncrement: Boolean(row.is_identity),
|
|
@@ -183,6 +184,17 @@ export class MsSqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
183
184
|
/** The types whose `max_length` is a width rather than a fixed storage size. */
|
|
184
185
|
const CHARACTER_TYPES = new Set(['CHAR', 'NCHAR', 'VARCHAR', 'NVARCHAR', 'BINARY', 'VARBINARY']);
|
|
185
186
|
const NUMERIC_TYPES = new Set(['DECIMAL', 'NUMERIC']);
|
|
187
|
+
/** `(MAX)` on an unbounded character type, and a timestamp's fractional digits where not the engine's default. */
|
|
188
|
+
function spelledType(type, bytes, scale) {
|
|
189
|
+
if (bytes === -1 && CHARACTER_TYPES.has(type)) {
|
|
190
|
+
return `${type}(MAX)`;
|
|
191
|
+
}
|
|
192
|
+
return (type === 'DATETIME2' || type === 'DATETIMEOFFSET') &&
|
|
193
|
+
scale !== undefined &&
|
|
194
|
+
scale !== defaultTimestampPrecision('mssql')
|
|
195
|
+
? `${type}(${scale})`
|
|
196
|
+
: type;
|
|
197
|
+
}
|
|
186
198
|
/**
|
|
187
199
|
* A column's declared width from `max_length`, which is bytes: an `N` type holds two a character, a
|
|
188
200
|
* `VECTOR` is an 8-byte header and four a dimension, and `MAX` is `-1`. Read as bytes, every Unicode
|
|
@@ -29,6 +29,7 @@ export declare class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospect
|
|
|
29
29
|
}
|
|
30
30
|
type MysqlColumnRow = {
|
|
31
31
|
column_name: string;
|
|
32
|
+
data_type: string;
|
|
32
33
|
column_type: string;
|
|
33
34
|
is_nullable: string;
|
|
34
35
|
column_default: string | null;
|
|
@@ -36,6 +37,7 @@ type MysqlColumnRow = {
|
|
|
36
37
|
extra: string;
|
|
37
38
|
character_maximum_length: number | bigint | null;
|
|
38
39
|
numeric_precision: number | bigint | null;
|
|
40
|
+
datetime_precision: number | bigint | null;
|
|
39
41
|
numeric_scale: number | null;
|
|
40
42
|
column_comment: string | null;
|
|
41
43
|
generated_as: string | null;
|
|
@@ -47,6 +47,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
47
47
|
CHARACTER_MAXIMUM_LENGTH as character_maximum_length,
|
|
48
48
|
NUMERIC_PRECISION as numeric_precision,
|
|
49
49
|
NUMERIC_SCALE as numeric_scale,
|
|
50
|
+
DATETIME_PRECISION as datetime_precision,
|
|
50
51
|
COLUMN_KEY as column_key,
|
|
51
52
|
EXTRA as extra,
|
|
52
53
|
CASE WHEN EXTRA LIKE '%STORED GENERATED%' THEN GENERATION_EXPRESSION END as generated_as,
|
|
@@ -113,7 +114,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
113
114
|
isUnique: row.column_key === 'UNI',
|
|
114
115
|
// A `VECTOR`'s is its bytes, four a dimension, which `column_type` already states as dimensions.
|
|
115
116
|
length: /^vector/i.test(row.column_type) ? undefined : this.toNumber(row.character_maximum_length),
|
|
116
|
-
|
|
117
|
+
// A timestamp's fractional digits, stated even when 0, which uql's own unstated `DATETIME(3)` is not.
|
|
118
|
+
precision: this.toNumber(TIMESTAMP_TYPES.has(row.data_type) ? row.datetime_precision : row.numeric_precision),
|
|
117
119
|
scale: this.toNumber(row.numeric_scale),
|
|
118
120
|
comment: row.column_comment || undefined,
|
|
119
121
|
generatedAs: row.generated_as ?? undefined,
|
|
@@ -146,7 +148,8 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
146
148
|
if (normalized === 'NULL') {
|
|
147
149
|
return null;
|
|
148
150
|
}
|
|
149
|
-
|
|
151
|
+
// Whatever precision it repeats from its column, which the column's own type already states.
|
|
152
|
+
if (/^CURRENT_TIMESTAMP(?:\(\d?\))?$/.test(normalized)) {
|
|
150
153
|
return 'CURRENT_TIMESTAMP';
|
|
151
154
|
}
|
|
152
155
|
if (/^-?\d+(\.\d+)?$/.test(defaultValue)) {
|
|
@@ -158,6 +161,7 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
158
161
|
return quoted ? unescapeMysqlString(quoted[1]) : literal;
|
|
159
162
|
}
|
|
160
163
|
}
|
|
164
|
+
const TIMESTAMP_TYPES = new Set(['datetime', 'timestamp']);
|
|
161
165
|
/**
|
|
162
166
|
* MariaDB reads out of the same `information_schema` as MySQL, save for one column type it does not
|
|
163
167
|
* have: `JSON` there is an alias for `LONGTEXT` plus a `json_valid()` check constraint named after
|
|
@@ -75,7 +75,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
75
75
|
c.is_identity,
|
|
76
76
|
c.identity_generation,
|
|
77
77
|
CASE WHEN a.attgenerated = 's' THEN c.generation_expression END AS generated_as,
|
|
78
|
-
CASE WHEN c.data_type IN ('USER-DEFINED', 'vector') AND a.atttypmod > -1
|
|
78
|
+
CASE WHEN (c.data_type IN ('USER-DEFINED', 'vector') OR c.data_type LIKE 'timestamp%') AND a.atttypmod > -1
|
|
79
79
|
THEN format_type(a.atttypid, a.atttypmod) END AS formatted_type,
|
|
80
80
|
EXISTS (
|
|
81
81
|
SELECT 1 FROM information_schema.table_constraints tc
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isMongoQuerier, isSqlQuerier, } from '../type/index.js';
|
|
2
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
2
3
|
import { withMongoQuerierForMigrations, withSqlQuerierForMigrations } from './acquireQuerierForMigrations.js';
|
|
3
4
|
import { MigrationBuilder } from './builder/migrationBuilder.js';
|
|
4
5
|
import { migrationSource } from './codegen/migrationFile.js';
|
|
@@ -46,5 +47,5 @@ export async function migrationBuilderFor(querier) {
|
|
|
46
47
|
if (isMongoQuerier(querier)) {
|
|
47
48
|
return new MigrationBuilder(await mongoSchemaGenerator(), mongoSession(querier).run);
|
|
48
49
|
}
|
|
49
|
-
throw new
|
|
50
|
+
throw new UqlUsageError('A migration builder needs a SQL or a MongoDB querier');
|
|
50
51
|
}
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getEntities, getMeta } from '../entity/index.js';
|
|
|
5
5
|
import { SchemaAST } from '../schema/index.js';
|
|
6
6
|
import { hasTriggers } from '../util/field.util.js';
|
|
7
7
|
import { LoggerWrapper } from '../util/index.js';
|
|
8
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
8
9
|
import { buildMigrationModule } from './codegen/migrationFile.js';
|
|
9
10
|
import { introspectorFor } from './introspection/registry.js';
|
|
10
11
|
import { migrationBuilderFor, migrationTargetFor } from './migrationTarget.js';
|
|
@@ -96,7 +97,7 @@ export class Migrator {
|
|
|
96
97
|
if (options.to) {
|
|
97
98
|
const toIndex = selected.findIndex((m) => m.name === options.to);
|
|
98
99
|
if (toIndex === -1) {
|
|
99
|
-
throw new
|
|
100
|
+
throw new UqlUsageError(`Migration '${options.to}' not found`);
|
|
100
101
|
}
|
|
101
102
|
selected = selected.slice(0, toIndex + 1);
|
|
102
103
|
}
|
|
@@ -5,6 +5,7 @@ import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
|
5
5
|
import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
|
|
6
6
|
import { isAutoIncrement, qualifyName } from '../util/index.js';
|
|
7
7
|
import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName, isOwnedName } from '../util/sql.util.js';
|
|
8
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
8
9
|
import { sameDefault } from './builder/expressions.js';
|
|
9
10
|
import { splitSqlStatements } from './builder/splitSqlStatements.js';
|
|
10
11
|
import { indexDdlFor, tableDdlFor } from './ddl/index.js';
|
|
@@ -536,7 +537,7 @@ export class SqlSchemaGenerator {
|
|
|
536
537
|
}
|
|
537
538
|
generateAddForeignKeySql(tableName, foreignKey) {
|
|
538
539
|
if (!this.features.foreignKeyAlter) {
|
|
539
|
-
throw new
|
|
540
|
+
throw new UqlUsageError(`Dialect ${this.dialect} does not support adding foreign keys to existing tables`);
|
|
540
541
|
}
|
|
541
542
|
const constraint = this.foreignKeyConstraint(tableName, foreignKey, this.escapeId(foreignKey.references.table));
|
|
542
543
|
return `ALTER TABLE ${this.escapeId(tableName)} ADD ${constraint};`;
|
|
@@ -566,7 +567,7 @@ export class SqlSchemaGenerator {
|
|
|
566
567
|
return `ALTER TABLE ${table} DROP PRIMARY KEY;`;
|
|
567
568
|
}
|
|
568
569
|
if (!constraintName) {
|
|
569
|
-
throw new
|
|
570
|
+
throw new UqlUsageError(`Cannot drop the primary key of "${tableName}": ${this.dialect} names the constraint, and ` +
|
|
570
571
|
'introspection did not report a name for it.');
|
|
571
572
|
}
|
|
572
573
|
return `ALTER TABLE ${table} DROP CONSTRAINT ${this.escapeId(constraintName)};`;
|
|
@@ -579,7 +580,7 @@ export class SqlSchemaGenerator {
|
|
|
579
580
|
if (!column.generatedAs || this.features.generatedColumnAdd) {
|
|
580
581
|
return;
|
|
581
582
|
}
|
|
582
|
-
throw new
|
|
583
|
+
throw new UqlUsageError(`${this.dialect}: Cannot add the computed column "${column.name}" to the existing table ` +
|
|
583
584
|
`"${tableName}" - this database only accepts one in a CREATE TABLE. Drop \`stored\` to have the ` +
|
|
584
585
|
'expression spliced into each statement instead, or recreate the table in a written migration.');
|
|
585
586
|
}
|
|
@@ -587,7 +588,7 @@ export class SqlSchemaGenerator {
|
|
|
587
588
|
if (this.features.primaryKeyAlter) {
|
|
588
589
|
return;
|
|
589
590
|
}
|
|
590
|
-
throw new
|
|
591
|
+
throw new UqlUsageError(`${this.dialect}: Cannot change the primary key of "${tableName}" - this database has no ALTER ` +
|
|
591
592
|
'for it. Recreate the table in a written migration.');
|
|
592
593
|
}
|
|
593
594
|
}
|
|
@@ -32,7 +32,7 @@ export class DatabaseMigrationStorage {
|
|
|
32
32
|
async createTableIfNotExists(querier) {
|
|
33
33
|
const table = new TableBuilder(this.tableName);
|
|
34
34
|
table.string('name', { length: 255, primaryKey: true });
|
|
35
|
-
table.
|
|
35
|
+
table.timestamptz('executed_at', { defaultValue: expr.now() });
|
|
36
36
|
const generator = new SqlSchemaGenerator(querier.dialect);
|
|
37
37
|
for (const sql of generator.generateCreateTableFromDefinition(table.build(), { ifNotExists: true })) {
|
|
38
38
|
await querier.run(sql);
|
|
@@ -21,4 +21,4 @@ export declare function dropTrigger<E>(dialect: AbstractSqlDialect, meta: Entity
|
|
|
21
21
|
* column. Generated rather than authored, so unlike an authored body it renders on every engine from
|
|
22
22
|
* one declaration - and not at all on the MySQL family, whose columns stamp themselves.
|
|
23
23
|
*/
|
|
24
|
-
export declare function stampTriggers<E>(dialect: AbstractSqlDialect, meta: EntityMeta<E>): EntityTriggerMeta<E>[];
|
|
24
|
+
export declare function stampTriggers<E extends object>(dialect: AbstractSqlDialect, meta: EntityMeta<E>): EntityTriggerMeta<E>[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { stampEvents } from '../util/field.util.js';
|
|
2
2
|
import { definedEntries } from '../util/object.util.js';
|
|
3
|
-
import { raw,
|
|
3
|
+
import { raw, refs, rowRefs } from '../util/raw.js';
|
|
4
4
|
import { ownedName } from '../util/sql.util.js';
|
|
5
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
5
6
|
/**
|
|
6
7
|
* One trigger for `dialect`, named for its table and label and ending in a hash of its own SQL. That hash
|
|
7
8
|
* is the whole of change detection: a trigger is in place exactly when its name is installed, and an
|
|
@@ -20,7 +21,7 @@ export function renderTrigger(dialect, meta, trigger, position) {
|
|
|
20
21
|
*/
|
|
21
22
|
export function dropTrigger(dialect, meta, name) {
|
|
22
23
|
const { scope, body } = dialect.features.triggers;
|
|
23
|
-
const table = dialect.
|
|
24
|
+
const table = dialect.escapedTableName(meta);
|
|
24
25
|
return [
|
|
25
26
|
`DROP TRIGGER IF EXISTS ${triggerId(dialect, meta, name)}${scope === 'table' ? ` ON ${table}` : ''}`,
|
|
26
27
|
...(body === 'function' ? [`DROP FUNCTION IF EXISTS ${schemaObjectId(dialect, meta, name)}()`] : []),
|
|
@@ -45,17 +46,15 @@ function schemaObjectId(dialect, meta, name) {
|
|
|
45
46
|
*/
|
|
46
47
|
function triggerStatements(dialect, meta, trigger, name) {
|
|
47
48
|
const features = dialect.features.triggers;
|
|
48
|
-
|
|
49
|
-
const before =
|
|
50
|
-
const operation = trigger.on.slice(before ? 6 : 5).toUpperCase();
|
|
49
|
+
const [timing, operation] = EVENT_PARTS[trigger.on];
|
|
50
|
+
const before = timing === 'BEFORE';
|
|
51
51
|
const body = triggerBody(dialect, meta, trigger, before);
|
|
52
52
|
const id = triggerId(dialect, meta, name);
|
|
53
|
-
const table = dialect.
|
|
54
|
-
const column = (key) => dialect.escapeId(dialect.columnOf(meta, key));
|
|
53
|
+
const table = dialect.escapedTableName(meta);
|
|
55
54
|
const names = rowNames(dialect);
|
|
56
|
-
const rows = [rowRefs(names.$new), rowRefs(names.$old)];
|
|
57
|
-
const sql = dialect.compileDdl(body(...rows), meta.entity);
|
|
58
|
-
const guard = triggerGuard(dialect, meta, trigger, rows, names
|
|
55
|
+
const rows = [rowRefs(meta.entity, names.$new), rowRefs(meta.entity, names.$old)];
|
|
56
|
+
const sql = dialect.compileDdl(body(...rows), meta.entity, { rows: rowsFrom(dialect, meta, operation) });
|
|
57
|
+
const guard = triggerGuard(dialect, meta, trigger, rows, names);
|
|
59
58
|
const inBody = features.guards !== 'clause';
|
|
60
59
|
const guarded = !guard || !inBody
|
|
61
60
|
? sql
|
|
@@ -66,14 +65,14 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
66
65
|
// which rows are touched, so it runs even when the guard keeps the statements from running.
|
|
67
66
|
const opened = features.preamble ? `${features.preamble}\n${guarded}` : guarded;
|
|
68
67
|
const of = features.guards === 'clause' && operation === 'UPDATE' && trigger.of?.length
|
|
69
|
-
? ` OF ${trigger.of.map(
|
|
68
|
+
? ` OF ${trigger.of.map((key) => dialect.escapedColumnName(meta, key)).join(', ')}`
|
|
70
69
|
: '';
|
|
71
70
|
const each = features.rows === 'set' ? '' : '\nFOR EACH ROW';
|
|
72
71
|
const clause = guard && !inBody ? `\nWHEN (${guard})` : '';
|
|
73
|
-
const
|
|
72
|
+
const when = `${timing} ${operation}${of}`;
|
|
74
73
|
const header = features.layout === 'tableFirst'
|
|
75
|
-
? `CREATE TRIGGER ${id}\nON ${table} ${
|
|
76
|
-
: `CREATE TRIGGER ${id}\n${
|
|
74
|
+
? `CREATE TRIGGER ${id}\nON ${table} ${when}${each}${clause}\nAS`
|
|
75
|
+
: `CREATE TRIGGER ${id}\n${when} ON ${table}${each}${clause}`;
|
|
77
76
|
if (features.body !== 'function') {
|
|
78
77
|
return [`${header}\nBEGIN\n${opened}\nEND`];
|
|
79
78
|
}
|
|
@@ -83,6 +82,15 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
83
82
|
const fn = schemaObjectId(dialect, meta, name);
|
|
84
83
|
return [plpgsqlFunction(fn, `BEGIN\n${opened}\nRETURN ${returned};\nEND`), `${header}\nEXECUTE FUNCTION ${fn}()`];
|
|
85
84
|
}
|
|
85
|
+
/** Each event read apart: `beforeUpdate` is `BEFORE` and `UPDATE`. */
|
|
86
|
+
const EVENT_PARTS = {
|
|
87
|
+
beforeInsert: ['BEFORE', 'INSERT'],
|
|
88
|
+
afterInsert: ['AFTER', 'INSERT'],
|
|
89
|
+
beforeUpdate: ['BEFORE', 'UPDATE'],
|
|
90
|
+
afterUpdate: ['AFTER', 'UPDATE'],
|
|
91
|
+
beforeDelete: ['BEFORE', 'DELETE'],
|
|
92
|
+
afterDelete: ['AFTER', 'DELETE'],
|
|
93
|
+
};
|
|
86
94
|
/**
|
|
87
95
|
* The body for the engine in use, refusing first what the engine cannot render at all. One body serves
|
|
88
96
|
* every engine; in a map, one written for the family serves its forks: CockroachDB runs Postgres's
|
|
@@ -91,25 +99,25 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
91
99
|
function triggerBody(dialect, meta, trigger, before) {
|
|
92
100
|
const features = dialect.features.triggers;
|
|
93
101
|
if (before && !features.before) {
|
|
94
|
-
throw new
|
|
102
|
+
throw new UqlUsageError(`${dialect.dialectName} has no BEFORE trigger, only AFTER and INSTEAD OF, so '${trigger.on}' cannot be ` +
|
|
95
103
|
'rendered there. Use the matching after event, which sees the row already written.');
|
|
96
104
|
}
|
|
97
105
|
if (trigger.where && features.rows === 'set') {
|
|
98
|
-
throw new
|
|
106
|
+
throw new UqlUsageError(`${dialect.dialectName} fires a trigger once per statement, over the rows it touched, so no condition ` +
|
|
99
107
|
`can read one row: '${meta.entity.name}' cannot state a trigger 'where' there. Guard inside the body ` +
|
|
100
108
|
'instead, where `inserted` and `deleted` can be read as tables.');
|
|
101
109
|
}
|
|
102
110
|
const { run } = trigger;
|
|
103
111
|
const body = typeof run === 'function' ? run : (run[dialect.dialectName] ?? run[dialect.dialectFamily]);
|
|
104
112
|
if (!body) {
|
|
105
|
-
throw new
|
|
113
|
+
throw new UqlUsageError(`'${meta.entity.name}' has a trigger with no body for ${dialect.dialectName}, the engine in use. ` +
|
|
106
114
|
'Write one for it, or one body for every engine.');
|
|
107
115
|
}
|
|
108
116
|
return body;
|
|
109
117
|
}
|
|
110
118
|
/** The one condition both guards reduce to: any watched column that moved, and whatever `where` asks. */
|
|
111
|
-
function triggerGuard(dialect, meta, trigger, rows, names
|
|
112
|
-
const moved = movedColumns(dialect, meta, trigger.of ?? [], names
|
|
119
|
+
function triggerGuard(dialect, meta, trigger, rows, names) {
|
|
120
|
+
const moved = movedColumns(dialect, meta, trigger.of ?? [], names);
|
|
113
121
|
return [
|
|
114
122
|
...(moved ? [moved] : []),
|
|
115
123
|
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(moved)) : []),
|
|
@@ -137,16 +145,10 @@ function condition(dialect, meta, where, rows, names, operand) {
|
|
|
137
145
|
return [operand ? `(${sql})` : sql];
|
|
138
146
|
}
|
|
139
147
|
const predicates = definedEntries(where);
|
|
140
|
-
return predicates.map(([row, predicate]) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
clause: false,
|
|
145
|
-
operand: operand || predicates.length > 1,
|
|
146
|
-
escapedPrefix,
|
|
147
|
-
});
|
|
148
|
-
return ctx.sql;
|
|
149
|
-
});
|
|
148
|
+
return predicates.map(([row, predicate]) => dialect.compileDdl(predicate, meta.entity, {
|
|
149
|
+
escapedPrefix: `${names[row]}.`,
|
|
150
|
+
operand: operand || predicates.length > 1,
|
|
151
|
+
}));
|
|
150
152
|
}
|
|
151
153
|
/**
|
|
152
154
|
* The triggers a stamp needs: one per event it names, each assigning the field's expression to its
|
|
@@ -157,8 +159,8 @@ export function stampTriggers(dialect, meta) {
|
|
|
157
159
|
const features = dialect.features.triggers;
|
|
158
160
|
// Restating the row has to wait for it to be there, so those engines stamp after the write.
|
|
159
161
|
const after = !features.assignsRow;
|
|
160
|
-
const
|
|
161
|
-
return definedEntries(
|
|
162
|
+
const fields = meta.fields;
|
|
163
|
+
return definedEntries(fields).flatMap(([key, field]) => {
|
|
162
164
|
const events = stampEvents(field);
|
|
163
165
|
const { computed } = field;
|
|
164
166
|
if (!events?.length || !computed) {
|
|
@@ -169,7 +171,7 @@ export function stampTriggers(dialect, meta) {
|
|
|
169
171
|
// The event is part of the name: a stamp on both writes installs two triggers, and one identifier
|
|
170
172
|
// between them would have the second replace the first rather than sit beside it.
|
|
171
173
|
name: `${key}_${event}`,
|
|
172
|
-
run: () => stampBody(dialect, meta, key, computed,
|
|
174
|
+
run: (newRow) => stampBody(dialect, meta, key, computed, newRow),
|
|
173
175
|
}));
|
|
174
176
|
});
|
|
175
177
|
}
|
|
@@ -178,26 +180,25 @@ const STAMP_EVENTS = {
|
|
|
178
180
|
before: { insert: 'beforeInsert', update: 'beforeUpdate' },
|
|
179
181
|
after: { insert: 'afterInsert', update: 'afterUpdate' },
|
|
180
182
|
};
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
/**
|
|
184
|
+
* The one statement a stamp runs, in whichever of the two shapes the engine leaves open: an assignment
|
|
185
|
+
* to the incoming row, or where it may not be written, the row restated after the write, as any write in
|
|
186
|
+
* a trigger's body is, keyed on its whole key and only where the stamp still differs: that `UPDATE` fires
|
|
187
|
+
* the trigger again, and with recursive triggers on it then finds nothing left to change.
|
|
188
|
+
*/
|
|
189
|
+
function stampBody(dialect, meta, key, value, newRow) {
|
|
190
|
+
const { entity } = meta;
|
|
191
|
+
if (dialect.features.triggers.assignsRow) {
|
|
192
|
+
const target = newRow[key];
|
|
193
|
+
return dialect.features.triggers.body === 'function' ? raw `${target} := ${value};` : raw `SET ${target} = ${value};`;
|
|
188
194
|
}
|
|
189
|
-
const table =
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const from = features.rows === 'set' ? ` FROM ${newName}` : '';
|
|
197
|
-
// Only where the stamp still differs: this `UPDATE` fires the trigger again, and with recursive triggers
|
|
198
|
-
// on, the restatement it runs then finds nothing left to change instead of recursing without end.
|
|
199
|
-
const differs = dialect.neExpr(`${table}.${column}`, dialect.compileDdl(value, meta.entity));
|
|
200
|
-
return raw `UPDATE ${text(table)} SET ${text(column)} = ${value}${text(from)} WHERE ${keyed} AND ${text(differs)};`;
|
|
195
|
+
const table = refs(entity);
|
|
196
|
+
const keyed = meta.ids.map((id) => raw `${table[id]} = ${newRow[id]}`);
|
|
197
|
+
// Read through the write's own qualifier, as `keyed` is: `inserted` holds the same column name.
|
|
198
|
+
const stamped = dialect.compileDdl(value, entity);
|
|
199
|
+
const differs = raw(({ ctx, escapedPrefix }) => ctx.append(dialect.neExpr(`${escapedPrefix}${dialect.escapedColumnName(meta, key)}`, stamped)));
|
|
200
|
+
const where = { $and: [...keyed, differs] };
|
|
201
|
+
return raw(({ ctx, rows }) => dialect.triggerWrite(ctx, { kind: 'update', entity, set: { [key]: value }, where }, rows));
|
|
201
202
|
}
|
|
202
203
|
/** A dollar quote the body does not contain, so no `$$` in it - a literal, a comment - ends the function early. */
|
|
203
204
|
function dollarQuote(body) {
|
|
@@ -207,23 +208,38 @@ function dollarQuote(body) {
|
|
|
207
208
|
}
|
|
208
209
|
return tag;
|
|
209
210
|
}
|
|
210
|
-
/** SQL already written out, for the identifiers a statement splices rather than binds. */
|
|
211
|
-
function text(sql) {
|
|
212
|
-
return raw((opts) => opts.ctx.append(sql));
|
|
213
|
-
}
|
|
214
211
|
/**
|
|
215
212
|
* Whether any watched column moved, null-safely, or `undefined` where none is watched: the two records
|
|
216
213
|
* compared on a row-based engine, and on a set-based one the same question over a join of its two tables.
|
|
217
214
|
*/
|
|
218
|
-
function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }
|
|
215
|
+
function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }) {
|
|
219
216
|
if (!of.length) {
|
|
220
217
|
return undefined;
|
|
221
218
|
}
|
|
222
|
-
const differs = of.map((key) =>
|
|
219
|
+
const differs = of.map((key) => {
|
|
220
|
+
const column = dialect.escapedColumnName(meta, key);
|
|
221
|
+
return dialect.neExpr(`${oldName}.${column}`, `${newName}.${column}`);
|
|
222
|
+
});
|
|
223
223
|
const moved = differs.length > 1 ? `(${differs.join(' OR ')})` : differs.join('');
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const source = rowsFrom(dialect, meta, 'UPDATE');
|
|
225
|
+
return source ? `EXISTS (SELECT 1 ${source} WHERE ${moved})` : moved;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Where a set-based engine's body reads the rows it fires for, as the `FROM` a statement names them in:
|
|
229
|
+
* `inserted` on an insert, `deleted` on a delete, and on an update both, joined on the whole key. None on a
|
|
230
|
+
* row-based engine, whose body reads `NEW` and `OLD` bare.
|
|
231
|
+
*/
|
|
232
|
+
function rowsFrom(dialect, meta, operation) {
|
|
233
|
+
if (dialect.features.triggers.rows !== 'set') {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const { $new, $old } = rowNames(dialect);
|
|
237
|
+
if (operation !== 'UPDATE') {
|
|
238
|
+
return `FROM ${operation === 'INSERT' ? $new : $old}`;
|
|
226
239
|
}
|
|
227
|
-
const keyed = meta.ids.map((id) =>
|
|
228
|
-
|
|
240
|
+
const keyed = meta.ids.map((id) => {
|
|
241
|
+
const column = dialect.escapedColumnName(meta, id);
|
|
242
|
+
return `${$new}.${column} = ${$old}.${column}`;
|
|
243
|
+
});
|
|
244
|
+
return `FROM ${$new} JOIN ${$old} ON ${keyed.join(' AND ')}`;
|
|
229
245
|
}
|
|
@@ -90,9 +90,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
90
90
|
* apply to JSON paths.
|
|
91
91
|
*/
|
|
92
92
|
private assertKnownPathRoot;
|
|
93
|
-
/**
|
|
94
|
-
* Transform UQL operators to MongoDB operators.
|
|
95
|
-
*/
|
|
93
|
+
/** Transform UQL operators to MongoDB operators, refusing as `refusal` a key that is none. */
|
|
96
94
|
private transformOperators;
|
|
97
95
|
/**
|
|
98
96
|
* What a value holding `value` matches, as the SQL engines read it: an operator map tests it, an array
|