uql-orm 0.63.0 → 0.65.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/bunSql/bunSqlQuerier.d.ts +3 -10
- package/dist/bunSql/bunSqlQuerier.js +2 -16
- package/dist/bunSql/bunSqlQuerierPool.d.ts +0 -2
- package/dist/bunSql/bunSqlQuerierPool.js +14 -16
- package/dist/d1/d1Querier.d.ts +13 -34
- package/dist/d1/d1Querier.js +1 -1
- package/dist/d1/d1QuerierPool.d.ts +3 -3
- package/dist/dialect/abstractDialect.d.ts +4 -9
- package/dist/dialect/abstractDialect.js +4 -5
- package/dist/dialect/abstractSqlDialect.d.ts +2 -2
- package/dist/dialect/index.d.ts +0 -1
- package/dist/dialect/index.js +2 -3
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +9 -1
- package/dist/dialect/pgLikeSqlDialect.js +8 -5
- package/dist/dialect/queryContext.d.ts +3 -3
- package/dist/entity/metadata/definition.d.ts +6 -0
- package/dist/entity/metadata/definition.js +109 -76
- package/dist/migrate/builder/expressions.js +1 -15
- package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
- package/dist/migrate/builder/migrationBuilder.js +9 -83
- package/dist/migrate/builder/types.d.ts +14 -24
- package/dist/migrate/cli.d.ts +2 -7
- package/dist/migrate/cli.js +22 -43
- package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
- package/dist/migrate/codegen/entityCodeGenerator.js +29 -7
- package/dist/migrate/codegen/indexDecoratorSource.js +1 -5
- package/dist/migrate/codegen/migrationFile.d.ts +9 -1
- package/dist/migrate/codegen/migrationFile.js +2 -1
- package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
- package/dist/migrate/codegen/sourceLiteral.js +4 -0
- package/dist/migrate/ddl/indexDdl.d.ts +4 -2
- package/dist/migrate/ddl/indexDdl.js +16 -15
- package/dist/migrate/generator/mongoCommand.d.ts +9 -9
- package/dist/migrate/generator/mongoCommand.js +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
- package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
- package/dist/migrate/index.d.ts +2 -2
- package/dist/migrate/index.js +2 -2
- package/dist/migrate/indexPredicate.d.ts +8 -0
- package/dist/migrate/indexPredicate.js +52 -0
- package/dist/migrate/introspection/registry.d.ts +2 -2
- package/dist/migrate/introspection/registry.js +6 -11
- package/dist/migrate/migrationTarget.d.ts +23 -0
- package/dist/migrate/migrationTarget.js +50 -0
- package/dist/migrate/migrator.d.ts +18 -50
- package/dist/migrate/migrator.js +79 -199
- package/dist/migrate/schemaGenerator.d.ts +11 -14
- package/dist/migrate/schemaGenerator.js +42 -12
- package/dist/mongo/mongoDialect.d.ts +7 -2
- package/dist/mongo/mongoDialect.js +1 -4
- package/dist/mssql/mssqlDialect.js +0 -3
- package/dist/pglite/pgliteQuerier.d.ts +2 -6
- package/dist/pglite/pgliteQuerier.js +2 -9
- package/dist/postgres/index.d.ts +0 -1
- package/dist/postgres/index.js +0 -1
- package/dist/querier/abstractSharedHandleQuerierPool.d.ts +2 -5
- package/dist/querier/abstractSharedHandleQuerierPool.js +2 -5
- package/dist/querier/abstractSqlQuerier.d.ts +2 -3
- package/dist/querier/abstractSqlQuerier.js +8 -4
- package/dist/querier/cursorStream.d.ts +13 -0
- package/dist/{postgres/pgCursorStream.js → querier/cursorStream.js} +4 -13
- package/dist/schema/canonicalType.js +1 -1
- package/dist/schema/schemaASTBuilder.d.ts +2 -0
- package/dist/schema/schemaASTBuilder.js +43 -62
- package/dist/sqlite/abstractSqliteQuerier.d.ts +7 -3
- package/dist/sqlite/abstractSqliteQuerier.js +18 -4
- package/dist/sqlite/hranaQuerier.d.ts +1 -1
- package/dist/sqlite/localSqliteQuerierPool.d.ts +7 -0
- package/dist/sqlite/localSqliteQuerierPool.js +19 -0
- package/dist/sqlite/nodeSqliteQuerierPool.js +2 -3
- package/dist/sqlite/sqliteDialect.js +0 -3
- package/dist/sqlite/sqliteQuerier.d.ts +8 -5
- package/dist/sqlite/sqliteQuerier.js +4 -13
- package/dist/sqlite/sqliteQuerierPool.js +4 -4
- package/dist/turso/tursoSessionQuerier.d.ts +2 -2
- package/dist/turso/tursoSessionQuerier.js +16 -18
- package/dist/type/dialect.d.ts +25 -35
- package/dist/type/entity.d.ts +26 -28
- package/dist/type/migration.d.ts +11 -40
- package/dist/type/migratorDialect.d.ts +0 -9
- package/dist/type/migratorDialect.js +1 -16
- package/dist/type/querierPool.d.ts +0 -4
- package/dist/type/queryRaw.d.ts +2 -2
- package/dist/type/universalQuerier.d.ts +2 -2
- package/dist/util/ddlExpression.util.d.ts +3 -1
- package/dist/util/ddlExpression.util.js +14 -0
- package/dist/util/sqlLiteral.js +5 -3
- package/package.json +1 -3
- package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
- package/dist/migrate/schemaGeneratorAsync.js +0 -12
- package/dist/postgres/pgCursorStream.d.ts +0 -20
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +0 -21
- package/dist/postgres/postgresWireDriverCapabilities.js +0 -21
- package/dist/sqlite/bunSqliteAdapter.bun.d.ts +0 -27
- package/dist/sqlite/bunSqliteAdapter.bun.js +0 -25
- package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -33
- package/dist/sqlite/nodeSqliteAdapter.js +0 -25
package/dist/migrate/migrator.js
CHANGED
|
@@ -3,16 +3,10 @@ import { basename, extname, join } from 'node:path';
|
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { getEntities, getMeta } from '../entity/index.js';
|
|
5
5
|
import { introspectSchema, SchemaAST } from '../schema/index.js';
|
|
6
|
-
import { isKnownMigratorDialect, isMongoQuerier, isSqlQuerier } from '../type/index.js';
|
|
7
6
|
import { LoggerWrapper } from '../util/index.js';
|
|
8
|
-
import {
|
|
9
|
-
import { MigrationBuilder } from './builder/migrationBuilder.js';
|
|
10
|
-
import { buildMigrationModule, migrationSource, } from './codegen/migrationFile.js';
|
|
11
|
-
import { runMongoCommand } from './generator/mongoCommand.js';
|
|
7
|
+
import { buildMigrationModule } from './codegen/migrationFile.js';
|
|
12
8
|
import { introspectorFor } from './introspection/registry.js';
|
|
13
|
-
import {
|
|
14
|
-
import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
15
|
-
import { MongoMigrationStorage } from './storage/mongoStorage.js';
|
|
9
|
+
import { migrationBuilderFor, migrationTargetFor } from './migrationTarget.js';
|
|
16
10
|
/**
|
|
17
11
|
* Main class for managing database migrations
|
|
18
12
|
*/
|
|
@@ -31,55 +25,24 @@ export class Migrator {
|
|
|
31
25
|
get entities() {
|
|
32
26
|
return this._entities ?? getEntities();
|
|
33
27
|
}
|
|
34
|
-
|
|
28
|
+
/** The generator given, or this dialect's once {@link getSchemaGenerator} has loaded it. */
|
|
35
29
|
schemaGenerator;
|
|
36
30
|
schemaIntrospector;
|
|
37
|
-
|
|
38
|
-
_mongoSchemaLoadPromise;
|
|
31
|
+
target;
|
|
39
32
|
constructor(pool, options = {}) {
|
|
40
33
|
this.pool = pool;
|
|
41
|
-
this.
|
|
42
|
-
this.
|
|
43
|
-
this.storage =
|
|
44
|
-
options.storage ??
|
|
45
|
-
(this.dialectName === 'mongodb'
|
|
46
|
-
? new MongoMigrationStorage(pool, { tableName: options.tableName })
|
|
47
|
-
: new DatabaseMigrationStorage(pool, { tableName: options.tableName }));
|
|
34
|
+
this.target = migrationTargetFor(pool, options.defaultForeignKeyAction);
|
|
35
|
+
this.storage = options.storage ?? this.target.storage(options.tableName);
|
|
48
36
|
this.migrationsPath = options.migrationsPath ?? './migrations';
|
|
49
37
|
this._logger = new LoggerWrapper(options.logger, { logValues: options.logValues, slowQuery: options.slowQuery });
|
|
50
38
|
this._entities = options.entities;
|
|
51
|
-
this.schemaIntrospector =
|
|
52
|
-
this.schemaGenerator =
|
|
53
|
-
options.schemaGenerator ?? (this.dialectName === 'mongodb' ? undefined : this.createGenerator());
|
|
39
|
+
this.schemaIntrospector = introspectorFor(pool);
|
|
40
|
+
this.schemaGenerator = options.schemaGenerator;
|
|
54
41
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
async ensureSchemaGenerator() {
|
|
60
|
-
if (this.schemaGenerator || this.dialectName !== 'mongodb') {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
this._mongoSchemaLoadPromise ??= import('./generator/mongoSchemaGenerator.js').then(({ MongoSchemaGenerator }) => {
|
|
64
|
-
this.schemaGenerator = new MongoSchemaGenerator(this.pool.dialect.namingStrategy, this._defaultForeignKeyAction);
|
|
65
|
-
});
|
|
66
|
-
await this._mongoSchemaLoadPromise;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Set the schema generator for DDL operations
|
|
70
|
-
*/
|
|
71
|
-
setSchemaGenerator(generator) {
|
|
72
|
-
this.schemaGenerator = generator;
|
|
73
|
-
}
|
|
74
|
-
/** `schema` reads one namespace instead of the connection's own; see {@link BaseSqlIntrospector.schema}. */
|
|
75
|
-
createIntrospector(schema) {
|
|
76
|
-
return introspectorFor(this.dialectName, this.pool, schema);
|
|
77
|
-
}
|
|
78
|
-
createGenerator() {
|
|
79
|
-
if (!isKnownMigratorDialect(this.dialectName)) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
return createSchemaGenerator(this.pool.dialect, this._defaultForeignKeyAction);
|
|
42
|
+
/** The schema generator, loaded on first use: MongoDB's needs its optional peer. */
|
|
43
|
+
async getSchemaGenerator() {
|
|
44
|
+
this.schemaGenerator ??= await this.target.generator();
|
|
45
|
+
return this.schemaGenerator;
|
|
83
46
|
}
|
|
84
47
|
/**
|
|
85
48
|
* Get all discovered migrations from the migrations directory
|
|
@@ -159,10 +122,10 @@ export class Migrator {
|
|
|
159
122
|
*/
|
|
160
123
|
async runMigration(migration, direction) {
|
|
161
124
|
const startTime = Date.now();
|
|
162
|
-
return this.
|
|
125
|
+
return this.target.withSession(async ({ querier, transaction }) => {
|
|
163
126
|
try {
|
|
164
127
|
this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
|
|
165
|
-
await
|
|
128
|
+
await transaction(async () => {
|
|
166
129
|
if (direction === 'up') {
|
|
167
130
|
await migration.up(querier);
|
|
168
131
|
await this.storage.logWithQuerier(querier, migration.name);
|
|
@@ -194,25 +157,11 @@ export class Migrator {
|
|
|
194
157
|
}
|
|
195
158
|
});
|
|
196
159
|
}
|
|
197
|
-
/**
|
|
198
|
-
* A migration querier, and how to run work in one transaction on it. MongoDB gets none: it creates
|
|
199
|
-
* collections and indexes outside any transaction. SQL asserts its querier before opening one, so a
|
|
200
|
-
* wrong querier reports which one the dialect needs rather than a missing `transaction`.
|
|
201
|
-
*/
|
|
202
|
-
withMigrationQuerier(task) {
|
|
203
|
-
return this.dialectName === 'mongodb'
|
|
204
|
-
? withMongoQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => work()))
|
|
205
|
-
: withSqlQuerierForMigrations(this.pool, 'Migrator', (querier) => task(querier, (work) => querier.transaction(work)));
|
|
206
|
-
}
|
|
207
|
-
/** What this dialect's migration files are written against. */
|
|
208
|
-
get migrationQuerier() {
|
|
209
|
-
return this.dialectName === 'mongodb' ? 'MongoQuerier' : 'SqlQuerier';
|
|
210
|
-
}
|
|
211
160
|
/**
|
|
212
161
|
* Generate a new migration file
|
|
213
162
|
*/
|
|
214
163
|
async generate(name) {
|
|
215
|
-
const { emptyUp, emptyDown } =
|
|
164
|
+
const { emptyUp, emptyDown } = this.target.source;
|
|
216
165
|
const filePath = await this.writeMigration(name, { upInner: emptyUp, downInner: emptyDown });
|
|
217
166
|
this.logger.logInfo(`Created migration: ${filePath}`);
|
|
218
167
|
return filePath;
|
|
@@ -223,7 +172,7 @@ export class Migrator {
|
|
|
223
172
|
const content = buildMigrationModule({
|
|
224
173
|
migrationName: name,
|
|
225
174
|
createdAt: new Date(),
|
|
226
|
-
querier: this.
|
|
175
|
+
querier: this.target.source.querier,
|
|
227
176
|
...body,
|
|
228
177
|
});
|
|
229
178
|
await mkdir(this.migrationsPath, { recursive: true });
|
|
@@ -234,31 +183,25 @@ export class Migrator {
|
|
|
234
183
|
* Generate a migration based on entity schema differences
|
|
235
184
|
*/
|
|
236
185
|
async generateFromEntities(name) {
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
downStatements.push(this.generator.generateDropTable(diff.tableName, { ifExists: true }));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else if (diff.type === 'alter') {
|
|
248
|
-
altering.push(...this.generator.generateAlterTable(diff));
|
|
249
|
-
downStatements.push(...this.generator.generateAlterTableDown(diff));
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const upStatements = [...this.createSchema(creating), ...altering];
|
|
253
|
-
if (upStatements.length === 0) {
|
|
186
|
+
const generator = await this.getSchemaGenerator();
|
|
187
|
+
const { created, altered } = await this.pendingChanges();
|
|
188
|
+
const up = [
|
|
189
|
+
...this.createSchema(generator, created),
|
|
190
|
+
...altered.flatMap((diff) => generator.generateAlterTable(diff)),
|
|
191
|
+
];
|
|
192
|
+
if (up.length === 0) {
|
|
254
193
|
this.logger.logInfo('No schema changes detected.');
|
|
255
194
|
return '';
|
|
256
195
|
}
|
|
257
|
-
const
|
|
196
|
+
const down = [
|
|
197
|
+
...created.map((tableName) => generator.generateDropTable(tableName, { ifExists: true })),
|
|
198
|
+
...altered.flatMap((diff) => generator.generateAlterTableDown(diff)),
|
|
199
|
+
];
|
|
200
|
+
const { emit } = this.target.source;
|
|
258
201
|
const filePath = await this.writeMigration(name, {
|
|
259
202
|
docExtraLines: ['Generated from entity definitions'],
|
|
260
|
-
upInner: emit(
|
|
261
|
-
downInner: emit(
|
|
203
|
+
upInner: emit(up),
|
|
204
|
+
downInner: emit(down.reverse()),
|
|
262
205
|
});
|
|
263
206
|
this.logger.logInfo(`Created migration from entities: ${filePath}`);
|
|
264
207
|
return filePath;
|
|
@@ -267,26 +210,17 @@ export class Migrator {
|
|
|
267
210
|
* Get all schema differences between entities and database
|
|
268
211
|
*/
|
|
269
212
|
async getDiffs() {
|
|
270
|
-
await this.
|
|
271
|
-
if (!this.schemaGenerator || !this.schemaIntrospector) {
|
|
272
|
-
throw new TypeError('Schema generator and introspector must be set');
|
|
273
|
-
}
|
|
213
|
+
const generator = await this.getSchemaGenerator();
|
|
274
214
|
const ast = await this.introspectClaimedSchemas();
|
|
275
215
|
// Both sides built once: the database's above, the entities' here. Left to `diffSchema`, each
|
|
276
216
|
// entity would rebuild the whole AST, which is quadratic in the number of entities. Absent on a
|
|
277
217
|
// generator that compares no schema of its own - MongoDB, which reads only indexes.
|
|
278
|
-
const desiredAst =
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const diff = this.schemaGenerator.diffSchema(entity, currentTable, desiredAst);
|
|
285
|
-
if (diff) {
|
|
286
|
-
diffs.push(diff);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return diffs;
|
|
218
|
+
const desiredAst = generator.buildAST?.(this.entities);
|
|
219
|
+
return this.entities.flatMap((entity) => {
|
|
220
|
+
const table = ast.getTable(generator.resolveTableName(getMeta(entity)));
|
|
221
|
+
const diff = generator.diffSchema(entity, table, desiredAst);
|
|
222
|
+
return diff ? [diff] : [];
|
|
223
|
+
});
|
|
290
224
|
}
|
|
291
225
|
/**
|
|
292
226
|
* One AST spanning every schema the entities claim, each table stamped with the schema it was read
|
|
@@ -299,27 +233,12 @@ export class Migrator {
|
|
|
299
233
|
const claimed = new Set(this.entities.map((entity) => this.pool.dialect.resolveSchema(getMeta(entity))));
|
|
300
234
|
const merged = new SchemaAST();
|
|
301
235
|
for (const schema of claimed) {
|
|
302
|
-
const
|
|
303
|
-
if (!introspector) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
for (const table of (await introspectSchema(introspector)).getTables()) {
|
|
236
|
+
for (const table of (await introspectSchema(this.schemaIntrospectorFor(schema))).getTables()) {
|
|
307
237
|
merged.addTable(table);
|
|
308
238
|
}
|
|
309
239
|
}
|
|
310
240
|
return merged;
|
|
311
241
|
}
|
|
312
|
-
async findEntityForTable(tableName) {
|
|
313
|
-
await this.ensureSchemaGenerator();
|
|
314
|
-
for (const entity of this.entities) {
|
|
315
|
-
const meta = getMeta(entity);
|
|
316
|
-
const name = this.generator.resolveTableName(meta);
|
|
317
|
-
if (name === tableName) {
|
|
318
|
-
return entity;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
242
|
/**
|
|
324
243
|
* Applies the entity schema to the database: every registered entity, or the one `entity` names.
|
|
325
244
|
*
|
|
@@ -344,10 +263,10 @@ export class Migrator {
|
|
|
344
263
|
* with no referential integrity; and the old drop loop went in reverse *declaration* order, which
|
|
345
264
|
* says nothing about the relation graph and is rejected as soon as the constraints are really there.
|
|
346
265
|
*/
|
|
347
|
-
forceStatements() {
|
|
266
|
+
forceStatements(generator) {
|
|
348
267
|
return [
|
|
349
|
-
...
|
|
350
|
-
...
|
|
268
|
+
...generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
|
|
269
|
+
...generator.generateCreateSchema(this.entities),
|
|
351
270
|
];
|
|
352
271
|
}
|
|
353
272
|
/**
|
|
@@ -355,32 +274,23 @@ export class Migrator {
|
|
|
355
274
|
* existence check and is created with `IF NOT EXISTS`, so instances racing the same admin save
|
|
356
275
|
* settle instead of colliding; an existing one still pays for introspection, as a diff needs columns.
|
|
357
276
|
*/
|
|
358
|
-
async planEntity(entity, options) {
|
|
277
|
+
async planEntity(generator, entity, options) {
|
|
359
278
|
const meta = getMeta(entity);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const introspector = this.introspectorFor(this.pool.dialect.resolveSchema(meta));
|
|
363
|
-
if (!introspector) {
|
|
364
|
-
throw new TypeError(`No introspector for '${meta.entity.name}' on '${this.dialectName}'`);
|
|
365
|
-
}
|
|
366
|
-
const tableName = this.generator.resolveTableName(meta);
|
|
279
|
+
const introspector = this.schemaIntrospectorFor(this.pool.dialect.resolveSchema(meta));
|
|
280
|
+
const tableName = generator.resolveTableName(meta);
|
|
367
281
|
return (await introspector.tableExists(tableName))
|
|
368
|
-
? this.alterFromEntity(entity, (await introspectSchema(introspector)).getTable(tableName), options)
|
|
282
|
+
? this.alterFromEntity(generator, entity, (await introspectSchema(introspector)).getTable(tableName), options)
|
|
369
283
|
: // Spanning the whole set, so a foreign key resolves against the tables it points at, and
|
|
370
284
|
// always including this entity: pinned to an explicit `entities` list, a sync of one outside
|
|
371
285
|
// it emitted nothing at all. `only` is what keeps the statements to this table.
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
/** An alter diff as statements, narrowed to what the caller allows. */
|
|
375
|
-
alterFromDiff(diff, options) {
|
|
376
|
-
return this.generator.generateAlterTable(this.filterDiff(diff, options));
|
|
286
|
+
generator.generateCreateSchema(this.entitiesWith(entity), { only: [tableName], ifNotExists: true });
|
|
377
287
|
}
|
|
378
288
|
/** The same for one entity against the table it already has, and nothing where the two agree. */
|
|
379
|
-
alterFromEntity(entity, table, options) {
|
|
289
|
+
alterFromEntity(generator, entity, table, options) {
|
|
380
290
|
// Spanning the set for the reason `planEntity` spells out: a foreign key needs the table it
|
|
381
291
|
// points at, which a sync of one entity outside the configured list would not otherwise have.
|
|
382
|
-
const diff =
|
|
383
|
-
return diff?.type === 'alter' ? this.
|
|
292
|
+
const diff = generator.diffSchema(entity, table, generator.buildAST?.(this.entitiesWith(entity)));
|
|
293
|
+
return diff?.type === 'alter' ? generator.generateAlterTable(this.filterDiff(diff, options)) : [];
|
|
384
294
|
}
|
|
385
295
|
/** The configured entities, with `entity` among them however the migrator was built. */
|
|
386
296
|
entitiesWith(entity) {
|
|
@@ -388,33 +298,26 @@ export class Migrator {
|
|
|
388
298
|
return entities.includes(entity) ? entities : [...entities, entity];
|
|
389
299
|
}
|
|
390
300
|
/** The introspector for a claimed schema, which is the connection's own where none is claimed. */
|
|
391
|
-
|
|
392
|
-
return schema === undefined ? this.schemaIntrospector : this.
|
|
301
|
+
schemaIntrospectorFor(schema) {
|
|
302
|
+
return schema === undefined ? this.schemaIntrospector : introspectorFor(this.pool, schema);
|
|
393
303
|
}
|
|
394
304
|
/**
|
|
395
305
|
* The DDL {@link sync} would run, without running it. Separate so `--dry-run` shows the real
|
|
396
306
|
* statements rather than a summary of a second, differently-computed diff.
|
|
397
307
|
*/
|
|
398
308
|
async planSync(options = {}) {
|
|
399
|
-
await this.
|
|
309
|
+
const generator = await this.getSchemaGenerator();
|
|
400
310
|
if (options.force) {
|
|
401
|
-
return this.forceStatements();
|
|
311
|
+
return this.forceStatements(generator);
|
|
402
312
|
}
|
|
403
313
|
if (options.entity) {
|
|
404
|
-
return this.planEntity(options.entity, options);
|
|
405
|
-
}
|
|
406
|
-
const creating = [];
|
|
407
|
-
const altering = [];
|
|
408
|
-
for (const { diff, entity } of await this.pendingDiffs()) {
|
|
409
|
-
if (diff.type === 'create') {
|
|
410
|
-
if (entity)
|
|
411
|
-
creating.push(diff.tableName);
|
|
412
|
-
}
|
|
413
|
-
else if (diff.type === 'alter') {
|
|
414
|
-
altering.push(...this.alterFromDiff(diff, options));
|
|
415
|
-
}
|
|
314
|
+
return this.planEntity(generator, options.entity, options);
|
|
416
315
|
}
|
|
417
|
-
|
|
316
|
+
const { created, altered } = await this.pendingChanges();
|
|
317
|
+
return [
|
|
318
|
+
...this.createSchema(generator, created),
|
|
319
|
+
...altered.flatMap((diff) => generator.generateAlterTable(this.filterDiff(diff, options))),
|
|
320
|
+
];
|
|
418
321
|
}
|
|
419
322
|
/**
|
|
420
323
|
* New tables are emitted together, never one at a time: a single-entity AST has no other table for a
|
|
@@ -424,31 +327,20 @@ export class Migrator {
|
|
|
424
327
|
*
|
|
425
328
|
* Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
|
|
426
329
|
*/
|
|
427
|
-
createSchema(tableNames) {
|
|
428
|
-
return tableNames.length ?
|
|
330
|
+
createSchema(generator, tableNames) {
|
|
331
|
+
return tableNames.length ? generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
|
|
429
332
|
}
|
|
430
333
|
/**
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* `ALTER COLUMN`), so computing it eagerly for everyone would throw there.
|
|
334
|
+
* The pending diffs a sync or a generated migration acts on: the tables to create and the tables to
|
|
335
|
+
* alter. What to emit for each stays with the caller: a sync narrows an alter to what it allows and
|
|
336
|
+
* never asks for the rollback, which on SQLite cannot even be expressed (no `ALTER COLUMN`).
|
|
435
337
|
*/
|
|
436
|
-
async
|
|
338
|
+
async pendingChanges() {
|
|
437
339
|
const diffs = await this.getDiffs();
|
|
438
|
-
return
|
|
439
|
-
diff,
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* The schema generator. A getter because MongoDB's loads lazily (see {@link ensureSchemaGenerator}),
|
|
445
|
-
* so every caller had to repeat the same assertion after awaiting it.
|
|
446
|
-
*/
|
|
447
|
-
get generator() {
|
|
448
|
-
if (!this.schemaGenerator) {
|
|
449
|
-
throw new TypeError('Schema generator not set. Call setSchemaGenerator() first.');
|
|
450
|
-
}
|
|
451
|
-
return this.schemaGenerator;
|
|
340
|
+
return {
|
|
341
|
+
created: diffs.filter((diff) => diff.type === 'create').map((diff) => diff.tableName),
|
|
342
|
+
altered: diffs.filter((diff) => diff.type === 'alter'),
|
|
343
|
+
};
|
|
452
344
|
}
|
|
453
345
|
filterDiff(diff, options) {
|
|
454
346
|
const filteredDiff = { ...diff };
|
|
@@ -484,30 +376,18 @@ export class Migrator {
|
|
|
484
376
|
}
|
|
485
377
|
return filteredDiff;
|
|
486
378
|
}
|
|
379
|
+
/** Runs the statements a generator wrote, in one transaction where the engine takes DDL in one. */
|
|
487
380
|
async executeSyncStatements(statements, options) {
|
|
488
|
-
await this.
|
|
489
|
-
|
|
490
|
-
|
|
381
|
+
await this.target.withSession(({ run, transaction }) => transaction(async () => {
|
|
382
|
+
for (const statement of statements) {
|
|
383
|
+
if (options.logging)
|
|
384
|
+
this.logger.logSchema(`Executing: ${statement}`);
|
|
385
|
+
await run(statement);
|
|
386
|
+
}
|
|
387
|
+
}));
|
|
491
388
|
if (options.logging)
|
|
492
389
|
this.logger.logSchema('Schema synchronization completed');
|
|
493
390
|
}
|
|
494
|
-
async executeMongoSyncStatements(statements, options, querier) {
|
|
495
|
-
for (const statement of statements) {
|
|
496
|
-
if (options.logging)
|
|
497
|
-
this.logger.logSchema(`Executing MongoDB: ${statement}`);
|
|
498
|
-
await runMongoCommand(querier.db, statement);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
async executeSqlSyncStatements(statements, options, querier) {
|
|
502
|
-
if (!isSqlQuerier(querier)) {
|
|
503
|
-
throw new TypeError('Migrator requires a SQL-based querier for this dialect');
|
|
504
|
-
}
|
|
505
|
-
for (const sql of statements) {
|
|
506
|
-
if (options.logging)
|
|
507
|
-
this.logger.logSchema(`Executing: ${sql}`);
|
|
508
|
-
await querier.run(sql);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
391
|
/**
|
|
512
392
|
* Get migration status
|
|
513
393
|
*/
|
|
@@ -625,7 +505,7 @@ export function defineMigration(migration) {
|
|
|
625
505
|
export function defineBuilderMigration(migration) {
|
|
626
506
|
return {
|
|
627
507
|
...migration,
|
|
628
|
-
up: (querier) => migration.up(
|
|
629
|
-
down: (querier) => migration.down(
|
|
508
|
+
up: async (querier) => migration.up(await migrationBuilderFor(querier), querier),
|
|
509
|
+
down: async (querier) => migration.down(await migrationBuilderFor(querier), querier),
|
|
630
510
|
};
|
|
631
511
|
}
|
|
@@ -2,14 +2,14 @@ import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
|
2
2
|
import type { SchemaAST } from '../schema/schemaAST.js';
|
|
3
3
|
import { type DiffOptions } from '../schema/schemaASTDiffer.js';
|
|
4
4
|
import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
|
|
5
|
-
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema,
|
|
6
|
-
import type { FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
|
|
5
|
+
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, EntityWhereMeta, FieldMeta, FieldOptions, ForeignKeySchema, IndexSchema, NamingStrategy, SchemaDiff, SchemaGenerator, Type } from '../type/index.js';
|
|
6
|
+
import type { AnyMigrationOperation, FullColumnDefinition, IndexDefinition, TableDefinition } from './builder/types.js';
|
|
7
7
|
import { type IndexDdl, type TableDdl } from './ddl/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Unified SQL schema generator.
|
|
10
10
|
* Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
|
|
11
11
|
*/
|
|
12
|
-
export declare class SqlSchemaGenerator implements
|
|
12
|
+
export declare class SqlSchemaGenerator implements SchemaGenerator {
|
|
13
13
|
protected readonly dialect: AbstractSqlDialect;
|
|
14
14
|
protected readonly defaultForeignKeyAction: ForeignKeyAction;
|
|
15
15
|
/** `CREATE INDEX` for this dialect: the migrator's, so a runtime import carries none of it. */
|
|
@@ -24,6 +24,7 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
24
24
|
resolveSchema<E>(meta: EntityMeta<E>): string | undefined;
|
|
25
25
|
resolveColumnName(key: string, field: FieldOptions): string;
|
|
26
26
|
compileDdl(sql: EntityWhereMeta<object>, entity: Type<object>): string;
|
|
27
|
+
compileIndexPredicate(where: EntityWhereMeta<object>, entity: Type<object>, indexName: string): string;
|
|
27
28
|
/** Escape an identifier (table name, column name, etc.) */
|
|
28
29
|
protected escapeId(identifier: string): string;
|
|
29
30
|
/**
|
|
@@ -168,17 +169,18 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
168
169
|
}): string[];
|
|
169
170
|
generateCreateIndexFromDefinition(tableName: string, index: IndexDefinition): string;
|
|
170
171
|
generateRenameTableSql(oldName: string, newName: string): string;
|
|
172
|
+
/** `raw` is split, being the one SQL no generator wrote. */
|
|
173
|
+
generateOperation(operation: AnyMigrationOperation): string[];
|
|
171
174
|
/**
|
|
172
175
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
173
176
|
*
|
|
174
177
|
* `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
|
|
175
178
|
* no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
|
|
176
|
-
* dropped both without a word.
|
|
177
|
-
* already returns, and `execute` splits them.
|
|
179
|
+
* dropped both without a word.
|
|
178
180
|
*/
|
|
179
|
-
generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
|
|
180
|
-
generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
|
|
181
|
-
generateDropColumnSql(tableName: string, columnName: string): string;
|
|
181
|
+
generateAddColumnSql(tableName: string, column: FullColumnDefinition): string[];
|
|
182
|
+
generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string[];
|
|
183
|
+
generateDropColumnSql(tableName: string, columnName: string): string[];
|
|
182
184
|
generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
|
|
183
185
|
/**
|
|
184
186
|
* `CONSTRAINT <name> FOREIGN KEY (...) REFERENCES ... ON DELETE ... ON UPDATE ...`.
|
|
@@ -219,9 +221,4 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
219
221
|
* other way and the table is created under one name and compared under another, which reports every
|
|
220
222
|
* table of a project using a naming strategy as both missing and unexpected.
|
|
221
223
|
*/
|
|
222
|
-
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
223
|
-
/**
|
|
224
|
-
* Synchronous factory for SQL schema generators only.
|
|
225
|
-
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
226
|
-
*/
|
|
227
|
-
export declare function createSchemaGenerator(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): SqlSchemaGenerator | undefined;
|
|
224
|
+
export declare function buildEntityAST(generator: Pick<SchemaGenerator, 'resolveTableAlias' | 'resolveSchema' | 'resolveColumnName' | 'compileDdl' | 'compileIndexPredicate'>, entities: readonly Type<object>[], defaultForeignKeyAction?: ForeignKeyAction): SchemaAST;
|
|
@@ -6,10 +6,12 @@ import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
|
|
|
6
6
|
import { isAutoIncrement, qualifyName } from '../util/index.js';
|
|
7
7
|
import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
|
|
8
8
|
import { formatDefaultValue, SqlExpression } from './builder/expressions.js';
|
|
9
|
+
import { splitSqlStatements } from './builder/splitSqlStatements.js';
|
|
9
10
|
import { indexDdlFor, tableDdlFor } from './ddl/index.js';
|
|
10
11
|
import { sizedType } from './ddl/tableDdl.js';
|
|
11
12
|
import { columnForeignKey, columnIndex, fullColumnDefinitionToNode, renderIndexDefinition, tableDefinitionToNode, } from './generator/definitionToNode.js';
|
|
12
13
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
14
|
+
import { assertIndexPredicate } from './indexPredicate.js';
|
|
13
15
|
/**
|
|
14
16
|
* Unified SQL schema generator.
|
|
15
17
|
* Parameterized by dialect to handle Postgres, MySQL, MariaDB, and SQLite.
|
|
@@ -48,6 +50,10 @@ export class SqlSchemaGenerator {
|
|
|
48
50
|
compileDdl(sql, entity) {
|
|
49
51
|
return this.dialect.compileDdl(sql, entity);
|
|
50
52
|
}
|
|
53
|
+
compileIndexPredicate(where, entity, indexName) {
|
|
54
|
+
assertIndexPredicate(where, this.dialect.dialectName, indexName);
|
|
55
|
+
return this.dialect.compileDdl(where, entity);
|
|
56
|
+
}
|
|
51
57
|
/** Escape an identifier (table name, column name, etc.) */
|
|
52
58
|
escapeId(identifier) {
|
|
53
59
|
return this.dialect.escapeId(identifier);
|
|
@@ -577,13 +583,43 @@ export class SqlSchemaGenerator {
|
|
|
577
583
|
generateRenameTableSql(oldName, newName) {
|
|
578
584
|
return this.tableDdl.renameTable(oldName, newName);
|
|
579
585
|
}
|
|
586
|
+
/** `raw` is split, being the one SQL no generator wrote. */
|
|
587
|
+
generateOperation(operation) {
|
|
588
|
+
switch (operation.type) {
|
|
589
|
+
case 'createTable':
|
|
590
|
+
return this.generateCreateTableFromDefinition(operation.table);
|
|
591
|
+
case 'dropTable':
|
|
592
|
+
return [
|
|
593
|
+
this.generateDropTable(operation.tableName, { ifExists: operation.ifExists, cascade: operation.cascade }),
|
|
594
|
+
];
|
|
595
|
+
case 'renameTable':
|
|
596
|
+
return [this.generateRenameTableSql(operation.oldName, operation.newName)];
|
|
597
|
+
case 'addColumn':
|
|
598
|
+
return this.generateAddColumnSql(operation.tableName, operation.column);
|
|
599
|
+
case 'dropColumn':
|
|
600
|
+
return this.generateDropColumnSql(operation.tableName, operation.columnName);
|
|
601
|
+
case 'renameColumn':
|
|
602
|
+
return [this.generateRenameColumnSql(operation.tableName, operation.oldName, operation.newName)];
|
|
603
|
+
case 'alterColumn':
|
|
604
|
+
return this.generateAlterColumnSql(operation.tableName, operation.columnName, operation.changes);
|
|
605
|
+
case 'createIndex':
|
|
606
|
+
return [this.generateCreateIndexFromDefinition(operation.tableName, operation.index)];
|
|
607
|
+
case 'dropIndex':
|
|
608
|
+
return [this.generateDropIndex(operation.tableName, operation.indexName)];
|
|
609
|
+
case 'addForeignKey':
|
|
610
|
+
return [this.generateAddForeignKeySql(operation.tableName, operation.foreignKey)];
|
|
611
|
+
case 'dropForeignKey':
|
|
612
|
+
return [this.generateDropForeignKeySql(operation.tableName, operation.constraintName)];
|
|
613
|
+
case 'raw':
|
|
614
|
+
return splitSqlStatements(operation.sql);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
580
617
|
/**
|
|
581
618
|
* `ADD COLUMN`, plus the constraint and index the column declares.
|
|
582
619
|
*
|
|
583
620
|
* `CREATE TABLE` lifts a column's `references` and `index` onto the table it is building; this had
|
|
584
621
|
* no lift, so a hand-written `addColumn(...).references(...).index()` emitted the column alone and
|
|
585
|
-
* dropped both without a word.
|
|
586
|
-
* already returns, and `execute` splits them.
|
|
622
|
+
* dropped both without a word.
|
|
587
623
|
*/
|
|
588
624
|
generateAddColumnSql(tableName, column) {
|
|
589
625
|
this.assertColumnAddable(tableName, column);
|
|
@@ -598,14 +634,14 @@ export class SqlSchemaGenerator {
|
|
|
598
634
|
statements.push(this.generateCreateIndex(tableName, index));
|
|
599
635
|
}
|
|
600
636
|
statements.push(...this.generateColumnCommentStatement(tableName, column));
|
|
601
|
-
return statements
|
|
637
|
+
return statements;
|
|
602
638
|
}
|
|
603
639
|
generateAlterColumnSql(tableName, columnName, column) {
|
|
604
640
|
const node = fullColumnDefinitionToNode(column, tableName);
|
|
605
|
-
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node))
|
|
641
|
+
return this.generateAlterColumnStatements(tableName, { ...this.columnNodeToSchema(node), name: columnName }, this.generateColumnFromNode(node));
|
|
606
642
|
}
|
|
607
643
|
generateDropColumnSql(tableName, columnName) {
|
|
608
|
-
return this.tableDdl.dropColumn(tableName, columnName)
|
|
644
|
+
return this.tableDdl.dropColumn(tableName, columnName);
|
|
609
645
|
}
|
|
610
646
|
generateRenameColumnSql(tableName, oldName, newName) {
|
|
611
647
|
return this.tableDdl.renameColumn(tableName, oldName, newName);
|
|
@@ -726,13 +762,7 @@ export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
|
726
762
|
resolveSchema: (meta) => generator.resolveSchema(meta),
|
|
727
763
|
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
728
764
|
compileDdl: (sql, entity) => generator.compileDdl(sql, entity),
|
|
765
|
+
compileIndexPredicate: (where, entity, indexName) => generator.compileIndexPredicate(where, entity, indexName),
|
|
729
766
|
defaultForeignKeyAction,
|
|
730
767
|
});
|
|
731
768
|
}
|
|
732
|
-
/**
|
|
733
|
-
* Synchronous factory for SQL schema generators only.
|
|
734
|
-
* For MongoDB, use `createSchemaGeneratorAsync` from `./schemaGeneratorAsync.js` so the optional `mongodb` peer is not loaded at import time.
|
|
735
|
-
*/
|
|
736
|
-
export function createSchemaGenerator(dialect, defaultForeignKeyAction) {
|
|
737
|
-
return dialect.dialectName === 'mongodb' ? undefined : new SqlSchemaGenerator(dialect, defaultForeignKeyAction);
|
|
738
|
-
}
|
|
@@ -10,7 +10,12 @@ type MongoReadStages = {
|
|
|
10
10
|
/** Keys merged into the query's projection, when it has one: a vector search's score. */
|
|
11
11
|
readonly project?: Record<string, 1>;
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
13
|
+
/** Accumulator threaded through `$where` rendering: the relation lookups it needs, and their temp fields. */
|
|
14
|
+
type RelationLookups = {
|
|
15
|
+
readonly stages: MongoAggregationPipelineEntry<Document>[];
|
|
16
|
+
readonly temps: string[];
|
|
17
|
+
};
|
|
18
|
+
/** Default {@link DialectFeatures} for MongoDB. */
|
|
14
19
|
export declare const mongoDialectFeatures: DialectFeatures;
|
|
15
20
|
export declare class MongoDialect extends AbstractDialect {
|
|
16
21
|
#private;
|
|
@@ -46,7 +51,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
46
51
|
* recursion). Relation keys need `$lookup` stages, so they are only accepted when `lookups` is
|
|
47
52
|
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
48
53
|
*/
|
|
49
|
-
|
|
54
|
+
protected renderFilter<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, lookups?: RelationLookups): Filter<E>;
|
|
50
55
|
/**
|
|
51
56
|
* Renders `$and`/`$or`/`$not`/`$nor` into `filter`. MongoDB has no root-level `$not`, so both
|
|
52
57
|
* negating operators become its `$nor`, which is exactly `NOT (a OR b)` - and by De Morgan that
|
|
@@ -6,11 +6,8 @@ import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/
|
|
|
6
6
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
7
7
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
8
8
|
import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorMap, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, someKey, targetKeyColumns, } from '../util/index.js';
|
|
9
|
-
/** Default {@link DialectFeatures} for MongoDB
|
|
9
|
+
/** Default {@link DialectFeatures} for MongoDB. */
|
|
10
10
|
export const mongoDialectFeatures = {
|
|
11
|
-
explicitJsonCast: false,
|
|
12
|
-
nativeArrays: false,
|
|
13
|
-
supportsJsonb: false,
|
|
14
11
|
ifNotExists: false,
|
|
15
12
|
indexIfNotExists: false,
|
|
16
13
|
schemas: false, // the connection picks the database, and a collection name takes no dot
|
|
@@ -20,9 +20,6 @@ import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
|
20
20
|
*/
|
|
21
21
|
export class MsSqlDialect extends MergeSqlDialect {
|
|
22
22
|
featureDefaults = {
|
|
23
|
-
explicitJsonCast: false,
|
|
24
|
-
nativeArrays: false,
|
|
25
|
-
supportsJsonb: false,
|
|
26
23
|
// Neither object takes an `IF NOT EXISTS`; both need a `sys` catalogue lookup around them, which
|
|
27
24
|
// the generator does not emit.
|
|
28
25
|
ifNotExists: false,
|