uql-orm 0.33.0 → 0.34.1
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/bunSqlQuerierPool.js +2 -1
- package/dist/cockroachdb/crdbQuerierPool.js +2 -1
- package/dist/d1/d1QuerierPool.js +2 -1
- package/dist/dialect/abstractDialect.d.ts +27 -3
- package/dist/dialect/abstractDialect.js +34 -4
- package/dist/dialect/abstractSqlDialect.d.ts +27 -1
- package/dist/dialect/abstractSqlDialect.js +74 -36
- package/dist/dialect/mysqlLikeSqlDialect.js +1 -0
- package/dist/dialect/pgLikeSqlDialect.js +1 -0
- package/dist/entity/metadata/definition.js +9 -0
- package/dist/http/handler.js +16 -1
- package/dist/libsql/libsqlQuerierPool.js +2 -1
- package/dist/maria/mariadbQuerierPool.js +2 -1
- package/dist/migrate/builder/migrationBuilder.js +3 -1
- package/dist/migrate/builder/tableBuilder.js +2 -1
- package/dist/migrate/generator/definitionToNode.js +3 -3
- package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +12 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +16 -2
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +7 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +11 -12
- package/dist/migrate/introspection/mongoIntrospector.js +0 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +8 -6
- package/dist/migrate/introspection/postgresIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/postgresIntrospector.js +6 -5
- package/dist/migrate/migrator.d.ts +10 -1
- package/dist/migrate/migrator.js +30 -8
- package/dist/migrate/schemaGenerator.d.ts +16 -6
- package/dist/migrate/schemaGenerator.js +53 -24
- package/dist/mongo/mongoDialect.js +5 -4
- package/dist/mongo/mongodbQuerierPool.js +2 -1
- package/dist/mysql/mysql2QuerierPool.js +2 -1
- package/dist/neon/neonQuerierPool.js +2 -1
- package/dist/pglite/pgliteQuerierPool.js +2 -1
- package/dist/postgres/pgQuerierPool.js +2 -1
- package/dist/schema/schemaAST.d.ts +8 -1
- package/dist/schema/schemaAST.js +22 -13
- package/dist/schema/schemaASTBuilder.d.ts +5 -3
- package/dist/schema/schemaASTBuilder.js +24 -31
- package/dist/schema/types.d.ts +10 -3
- package/dist/sqlite/localSqliteQuerierPool.js +2 -1
- package/dist/sqlite/sqliteDialect.js +2 -1
- package/dist/turso/tursoLocalQuerierPool.js +2 -1
- package/dist/turso/tursoQuerierPool.js +2 -1
- package/dist/type/dialect.d.ts +6 -0
- package/dist/type/entity.d.ts +7 -0
- package/dist/type/migration.d.ts +16 -2
- package/dist/type/querier.d.ts +5 -0
- package/dist/util/sql.util.d.ts +17 -0
- package/dist/util/sql.util.js +23 -0
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { AbstractSqlDialect } from '../dialect/index.js';
|
|
|
2
2
|
import { getMeta } from '../entity/index.js';
|
|
3
3
|
import { areTypesEqual, canonicalToSql, fieldOptionsToCanonical, isVectorCategory, sqlToCanonical, } from '../schema/canonicalType.js';
|
|
4
4
|
import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getKeys, isAutoIncrement, qualifyName } from '../util/index.js';
|
|
6
|
+
import { derivedForeignKeyName } from '../util/sql.util.js';
|
|
6
7
|
import { formatDefaultValue } from './builder/expressions.js';
|
|
7
8
|
import { fullColumnDefinitionToNode, tableDefinitionToNode } from './generator/definitionToNode.js';
|
|
8
9
|
import { indexNodeToSchema } from './generator/indexNodeToSchema.js';
|
|
@@ -23,17 +24,21 @@ export class SqlSchemaGenerator {
|
|
|
23
24
|
get features() {
|
|
24
25
|
return this.dialect.features;
|
|
25
26
|
}
|
|
26
|
-
resolveTableName(
|
|
27
|
-
return this.dialect.resolveTableName(
|
|
27
|
+
resolveTableName(meta) {
|
|
28
|
+
return this.dialect.resolveTableName(meta);
|
|
29
|
+
}
|
|
30
|
+
resolveTableAlias(meta) {
|
|
31
|
+
return this.dialect.resolveTableAlias(meta);
|
|
32
|
+
}
|
|
33
|
+
resolveSchema(meta) {
|
|
34
|
+
return this.dialect.resolveSchema(meta);
|
|
28
35
|
}
|
|
29
36
|
resolveColumnName(key, field) {
|
|
30
37
|
return this.dialect.resolveColumnName(key, field);
|
|
31
38
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Escape an identifier (table name, column name, etc.)
|
|
34
|
-
*/
|
|
39
|
+
/** Escape an identifier (table name, column name, etc.) */
|
|
35
40
|
escapeId(identifier) {
|
|
36
|
-
return
|
|
41
|
+
return this.dialect.escapeId(identifier);
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
44
|
* Primary key type for auto-increment integer IDs
|
|
@@ -65,14 +70,17 @@ export class SqlSchemaGenerator {
|
|
|
65
70
|
// Inline only where a constraint cannot be added afterwards, which is what makes the cyclic case
|
|
66
71
|
// work everywhere else.
|
|
67
72
|
const inline = withForeignKeys && !this.features.foreignKeyAlter;
|
|
68
|
-
|
|
73
|
+
// Namespaces first: a qualified `CREATE TABLE` fails against a schema nobody created, and the
|
|
74
|
+
// schema is the one part of the layout a migration cannot infer from the table it is making.
|
|
75
|
+
const statements = this.generateCreateSchemas(tables);
|
|
76
|
+
statements.push(...tables.flatMap((table) => this.generateCreateTableFromNode(inline ? table : { ...table, outgoingRelations: [] }, options)));
|
|
69
77
|
if (withForeignKeys && !inline) {
|
|
70
78
|
for (const table of tables) {
|
|
71
79
|
for (const rel of table.outgoingRelations) {
|
|
72
|
-
statements.push(this.generateAddForeignKeySql(table.name, {
|
|
80
|
+
statements.push(this.generateAddForeignKeySql(qualifyName(table.name, table.schema), {
|
|
73
81
|
name: rel.name,
|
|
74
82
|
columns: rel.from.columns.map((c) => c.name),
|
|
75
|
-
referencesTable: rel.to.table.name,
|
|
83
|
+
referencesTable: qualifyName(rel.to.table.name, rel.to.table.schema),
|
|
76
84
|
referencesColumns: rel.to.columns.map((c) => c.name),
|
|
77
85
|
onDelete: rel.onDelete ?? this.defaultForeignKeyAction,
|
|
78
86
|
onUpdate: rel.onUpdate ?? this.defaultForeignKeyAction,
|
|
@@ -82,8 +90,17 @@ export class SqlSchemaGenerator {
|
|
|
82
90
|
}
|
|
83
91
|
return statements;
|
|
84
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* One statement per distinct schema the tables being created live in, in first-seen order. Only
|
|
95
|
+
* the tables actually being created, so a narrowed `only` does not declare namespaces it is not
|
|
96
|
+
* about to fill. Empty on an engine without schemas, whose tables are never qualified.
|
|
97
|
+
*/
|
|
98
|
+
generateCreateSchemas(tables) {
|
|
99
|
+
const named = tables.map((table) => table.schema).filter((it) => it !== undefined);
|
|
100
|
+
return [...new Set(named)].map((schema) => this.dialect.createSchemaSql(schema));
|
|
101
|
+
}
|
|
85
102
|
generateDropSchema(entities, options = {}) {
|
|
86
|
-
return this.orderedTables(entities, 'drop').map((table) => this.generateDropTable(table.name, options));
|
|
103
|
+
return this.orderedTables(entities, 'drop').map((table) => this.generateDropTable(qualifyName(table.name, table.schema), options));
|
|
87
104
|
}
|
|
88
105
|
/**
|
|
89
106
|
* The tables of `entities` in dependency order, optionally narrowed to `only`. The AST always spans
|
|
@@ -97,7 +114,7 @@ export class SqlSchemaGenerator {
|
|
|
97
114
|
return tables;
|
|
98
115
|
}
|
|
99
116
|
const wanted = new Set(only);
|
|
100
|
-
return tables.filter((table) => wanted.has(table.name));
|
|
117
|
+
return tables.filter((table) => wanted.has(qualifyName(table.name, table.schema)));
|
|
101
118
|
}
|
|
102
119
|
generateDropTable(tableName, options = {}) {
|
|
103
120
|
const ifExists = options.ifExists ? 'IF EXISTS ' : '';
|
|
@@ -137,7 +154,7 @@ export class SqlSchemaGenerator {
|
|
|
137
154
|
// Drop indexes
|
|
138
155
|
if (diff.indexesToDrop?.length) {
|
|
139
156
|
for (const indexName of diff.indexesToDrop) {
|
|
140
|
-
statements.push(this.generateDropIndex(diff.tableName, indexName));
|
|
157
|
+
statements.push(this.generateDropIndex(diff.tableName, indexName, diff.schema));
|
|
141
158
|
}
|
|
142
159
|
}
|
|
143
160
|
return statements;
|
|
@@ -162,7 +179,7 @@ export class SqlSchemaGenerator {
|
|
|
162
179
|
// Reverse index additions by dropping them
|
|
163
180
|
if (diff.indexesToAdd?.length) {
|
|
164
181
|
for (const index of diff.indexesToAdd) {
|
|
165
|
-
statements.push(this.generateDropIndex(diff.tableName, index.name));
|
|
182
|
+
statements.push(this.generateDropIndex(diff.tableName, index.name, diff.schema));
|
|
166
183
|
}
|
|
167
184
|
}
|
|
168
185
|
if (diff.columnsToDrop?.length || diff.indexesToDrop?.length) {
|
|
@@ -173,11 +190,15 @@ export class SqlSchemaGenerator {
|
|
|
173
190
|
generateCreateIndex(tableName, index, options = {}) {
|
|
174
191
|
return this.dialect.getCreateIndexStatement(tableName, index, options);
|
|
175
192
|
}
|
|
176
|
-
|
|
193
|
+
/**
|
|
194
|
+
* `schema` is the table's, because that is where its indexes live. MySQL takes it from the table
|
|
195
|
+
* operand instead, which is already qualified.
|
|
196
|
+
*/
|
|
197
|
+
generateDropIndex(tableName, indexName, schema) {
|
|
177
198
|
if (this.dialect.dropIndexSyntax === 'on-table') {
|
|
178
199
|
return `DROP INDEX ${this.escapeId(indexName)} ON ${this.escapeId(tableName)};`;
|
|
179
200
|
}
|
|
180
|
-
return `DROP INDEX IF EXISTS ${this.
|
|
201
|
+
return `DROP INDEX IF EXISTS ${this.dialect.escapeQualifiedId(indexName, schema)};`;
|
|
181
202
|
}
|
|
182
203
|
/**
|
|
183
204
|
* Generate a column definition from a {@link ColumnSchema}, whose type is the engine's own spelling
|
|
@@ -302,7 +323,8 @@ export class SqlSchemaGenerator {
|
|
|
302
323
|
const meta = getMeta(entity);
|
|
303
324
|
if (!currentTable) {
|
|
304
325
|
return {
|
|
305
|
-
tableName: this.
|
|
326
|
+
tableName: this.resolveTableName(meta),
|
|
327
|
+
schema: this.resolveSchema(meta),
|
|
306
328
|
type: 'create',
|
|
307
329
|
};
|
|
308
330
|
}
|
|
@@ -340,7 +362,8 @@ export class SqlSchemaGenerator {
|
|
|
340
362
|
return undefined;
|
|
341
363
|
}
|
|
342
364
|
return {
|
|
343
|
-
tableName: this.
|
|
365
|
+
tableName: this.resolveTableName(meta),
|
|
366
|
+
schema: this.resolveSchema(meta),
|
|
344
367
|
type: 'alter',
|
|
345
368
|
columnsToAdd: columnsToAdd.length > 0 ? columnsToAdd : undefined,
|
|
346
369
|
columnsToAlter: columnsToAlter.length > 0 ? columnsToAlter : undefined,
|
|
@@ -357,7 +380,9 @@ export class SqlSchemaGenerator {
|
|
|
357
380
|
* created deliberately outside the ORM, and dropping it is a decision for a reviewed migration.
|
|
358
381
|
*/
|
|
359
382
|
missingIndexes(entity, currentTable) {
|
|
360
|
-
|
|
383
|
+
// Keyed by the qualified name, so a table in a schema finds itself rather than reporting that
|
|
384
|
+
// the entity declares no indexes at all.
|
|
385
|
+
const desired = buildEntityAST(this, [entity]).getTable(qualifyName(currentTable.name, currentTable.schema))?.indexes ?? [];
|
|
361
386
|
const present = new Set(currentTable.indexes.map((index) => index.name));
|
|
362
387
|
return desired
|
|
363
388
|
.filter((index) => !present.has(index.name) && !this.isInlineVectorIndex(index))
|
|
@@ -477,12 +502,12 @@ export class SqlSchemaGenerator {
|
|
|
477
502
|
const fromCols = rel.from.columns.map((c) => this.escapeId(c.name)).join(', ');
|
|
478
503
|
const toCols = rel.to.columns.map((c) => this.escapeId(c.name)).join(', ');
|
|
479
504
|
const constraintName = rel.name ? `CONSTRAINT ${this.escapeId(rel.name)} ` : '';
|
|
480
|
-
constraints.push(`${constraintName}FOREIGN KEY (${fromCols}) REFERENCES ${this.
|
|
505
|
+
constraints.push(`${constraintName}FOREIGN KEY (${fromCols}) REFERENCES ${this.dialect.escapeQualifiedId(rel.to.table.name, rel.to.table.schema)} (${toCols})` +
|
|
481
506
|
` ON DELETE ${rel.onDelete ?? this.defaultForeignKeyAction} ON UPDATE ${rel.onUpdate ?? this.defaultForeignKeyAction}`);
|
|
482
507
|
}
|
|
483
508
|
}
|
|
484
509
|
const ifNotExists = options.ifNotExists && this.features.ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
485
|
-
let createSql = `CREATE TABLE ${ifNotExists}${this.
|
|
510
|
+
let createSql = `CREATE TABLE ${ifNotExists}${this.dialect.escapeQualifiedId(table.name, table.schema)} (\n`;
|
|
486
511
|
createSql += columns.map((col) => ` ${col}`).join(',\n');
|
|
487
512
|
if (constraints.length > 0) {
|
|
488
513
|
createSql += ',\n';
|
|
@@ -528,7 +553,8 @@ export class SqlSchemaGenerator {
|
|
|
528
553
|
* Delegates to `generateCreateIndex` for unified SQL assembly.
|
|
529
554
|
*/
|
|
530
555
|
generateCreateIndexFromNode(index, options = { ifNotExists: false }) {
|
|
531
|
-
|
|
556
|
+
// The index's own name stays unqualified: it is created in the schema of the table it is on.
|
|
557
|
+
return this.generateCreateIndex(qualifyName(index.table.name, index.table.schema), indexNodeToSchema(index), options);
|
|
532
558
|
}
|
|
533
559
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
534
560
|
const tableNode = tableDefinitionToNode(table);
|
|
@@ -559,7 +585,7 @@ export class SqlSchemaGenerator {
|
|
|
559
585
|
const refCols = foreignKey.referencesColumns.map((c) => this.escapeId(c)).join(', ');
|
|
560
586
|
const constraintName = foreignKey.name
|
|
561
587
|
? this.escapeId(foreignKey.name)
|
|
562
|
-
: this.escapeId(
|
|
588
|
+
: this.escapeId(derivedForeignKeyName(tableName, foreignKey.columns));
|
|
563
589
|
if (!this.features.foreignKeyAlter) {
|
|
564
590
|
throw new TypeError(`Dialect ${this.dialect} does not support adding foreign keys to existing tables`);
|
|
565
591
|
}
|
|
@@ -581,7 +607,10 @@ export class SqlSchemaGenerator {
|
|
|
581
607
|
*/
|
|
582
608
|
export function buildEntityAST(generator, entities, defaultForeignKeyAction) {
|
|
583
609
|
return buildSchemaAST(entities, {
|
|
584
|
-
|
|
610
|
+
// The alias, not `resolveTableName`: a node holds its schema separately, so that a name derived
|
|
611
|
+
// from it stays a single identifier.
|
|
612
|
+
resolveTableName: (meta) => generator.resolveTableAlias(meta),
|
|
613
|
+
resolveSchema: (meta) => generator.resolveSchema(meta),
|
|
585
614
|
resolveColumnName: (key, field) => generator.resolveColumnName(key, field),
|
|
586
615
|
defaultForeignKeyAction,
|
|
587
616
|
});
|
|
@@ -11,6 +11,7 @@ export const mongoDialectFeatures = {
|
|
|
11
11
|
supportsJsonb: false,
|
|
12
12
|
ifNotExists: false,
|
|
13
13
|
indexIfNotExists: false,
|
|
14
|
+
schemas: false, // the connection picks the database, and a collection name takes no dot
|
|
14
15
|
dropTableCascade: false,
|
|
15
16
|
renameColumn: false,
|
|
16
17
|
foreignKeyAlter: false,
|
|
@@ -156,7 +157,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
156
157
|
? this.junctionLookup(relOpts, relMeta, relEntity, targetScope, temp, tail, opts)
|
|
157
158
|
: {
|
|
158
159
|
$lookup: {
|
|
159
|
-
from: this.resolveTableName(
|
|
160
|
+
from: this.resolveTableName(relMeta),
|
|
160
161
|
...this.joinKeys(meta, relMeta, relOpts),
|
|
161
162
|
pipeline: [...(hasKeys(targetScope) ? [{ $match: targetScope }] : []), ...tail],
|
|
162
163
|
as: temp,
|
|
@@ -177,14 +178,14 @@ export class MongoDialect extends AbstractDialect {
|
|
|
177
178
|
const nested = MongoDialect.REL_NESTED_KEY;
|
|
178
179
|
return {
|
|
179
180
|
$lookup: {
|
|
180
|
-
from: this.resolveTableName(
|
|
181
|
+
from: this.resolveTableName(throughMeta),
|
|
181
182
|
localField: MongoDialect.ID_KEY,
|
|
182
183
|
foreignField: this.columnOf(throughMeta, relOpts.references[0].local),
|
|
183
184
|
pipeline: [
|
|
184
185
|
...(hasKeys(junctionScope) ? [{ $match: junctionScope }] : []),
|
|
185
186
|
{
|
|
186
187
|
$lookup: {
|
|
187
|
-
from: this.resolveTableName(
|
|
188
|
+
from: this.resolveTableName(relMeta),
|
|
188
189
|
localField: this.columnOf(throughMeta, relOpts.references[1].local),
|
|
189
190
|
foreignField: MongoDialect.ID_KEY,
|
|
190
191
|
pipeline: [...(hasKeys(targetScope) ? [{ $match: targetScope }] : []), { $limit: 1 }],
|
|
@@ -575,7 +576,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
575
576
|
];
|
|
576
577
|
pipeline.push({
|
|
577
578
|
$lookup: {
|
|
578
|
-
from: this.resolveTableName(join.
|
|
579
|
+
from: this.resolveTableName(join.meta),
|
|
579
580
|
...this.joinKeys(parentMeta, join.meta, join.relation),
|
|
580
581
|
...(lookupPipeline.length ? { pipeline: lookupPipeline } : {}),
|
|
581
582
|
as: join.key,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { MongoClient } from 'mongodb';
|
|
2
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
3
|
import { AbstractQuerierPool } from '../querier/index.js';
|
|
3
4
|
import { MongodbNativeDialect } from './mongodbNativeDialect.js';
|
|
4
5
|
import { MongodbQuerier } from './mongodbQuerier.js';
|
|
5
6
|
export class MongodbQuerierPool extends AbstractQuerierPool {
|
|
6
7
|
client;
|
|
7
8
|
constructor(uri, opts, extra) {
|
|
8
|
-
super(new MongodbNativeDialect(
|
|
9
|
+
super(new MongodbNativeDialect(dialectOptionsFrom(extra)), extra);
|
|
9
10
|
this.client = new MongoClient(uri, opts);
|
|
10
11
|
}
|
|
11
12
|
async getQuerier() {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createPool } from 'mysql2/promise';
|
|
2
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
3
|
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
3
4
|
import { MySql2Dialect } from './mysql2Dialect.js';
|
|
4
5
|
import { MySql2Querier } from './mysql2Querier.js';
|
|
5
6
|
export class MySql2QuerierPool extends AbstractSqlQuerierPool {
|
|
6
7
|
pool;
|
|
7
8
|
constructor(opts, extra) {
|
|
8
|
-
super(new MySql2Dialect(
|
|
9
|
+
super(new MySql2Dialect(dialectOptionsFrom(extra)), extra);
|
|
9
10
|
this.pool = createPool(opts);
|
|
10
11
|
}
|
|
11
12
|
async getQuerier() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Pool, types } from '@neondatabase/serverless';
|
|
2
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
3
|
import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
|
|
3
4
|
import { numericTypes } from '../postgres/pgNumericTypes.js';
|
|
4
5
|
import { NeonDialect } from './neonDialect.js';
|
|
@@ -6,7 +7,7 @@ import { NeonQuerier } from './neonQuerier.js';
|
|
|
6
7
|
export class NeonQuerierPool extends AbstractPgQuerierPool {
|
|
7
8
|
constructor(opts, extra) {
|
|
8
9
|
// Neon's own `types`, not `pg`'s: this entry has to load on an edge runtime where `pg` is absent.
|
|
9
|
-
super(new NeonDialect(
|
|
10
|
+
super(new NeonDialect(dialectOptionsFrom(extra)), new Pool({ types: numericTypes(types), ...opts }), extra);
|
|
10
11
|
}
|
|
11
12
|
buildQuerier(connect) {
|
|
12
13
|
return new NeonQuerier(connect, this.dialect, this.extra);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
1
2
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
3
|
import { PgliteDialect } from './pgliteDialect.js';
|
|
3
4
|
import { PgliteQuerier } from './pgliteQuerier.js';
|
|
@@ -20,7 +21,7 @@ export class PgliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
|
20
21
|
dataDir;
|
|
21
22
|
opts;
|
|
22
23
|
constructor(dataDir = 'memory://', opts, extra) {
|
|
23
|
-
super(new PgliteDialect(
|
|
24
|
+
super(new PgliteDialect(dialectOptionsFrom(extra)), extra);
|
|
24
25
|
this.dataDir = dataDir;
|
|
25
26
|
this.opts = opts;
|
|
26
27
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Pool, types } from 'pg';
|
|
2
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
3
|
import { AbstractPgQuerierPool } from './abstractPgQuerierPool.js';
|
|
3
4
|
import { PgDialect } from './pgDialect.js';
|
|
4
5
|
import { numericTypes } from './pgNumericTypes.js';
|
|
@@ -7,7 +8,7 @@ export class PgQuerierPool extends AbstractPgQuerierPool {
|
|
|
7
8
|
constructor(opts, extra) {
|
|
8
9
|
// keepAlive reduces (but can't eliminate) idle connections being silently
|
|
9
10
|
// dropped by NATs/firewalls on long-lived remote connections.
|
|
10
|
-
super(new PgDialect(
|
|
11
|
+
super(new PgDialect(dialectOptionsFrom(extra)), new Pool({ keepAlive: true, types: numericTypes(types), ...opts }), extra);
|
|
11
12
|
}
|
|
12
13
|
buildQuerier(connect) {
|
|
13
14
|
return new PgQuerier(connect, this.dialect, this.extra);
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* Provides graph operations like navigation, validation, and topological sorting.
|
|
6
6
|
*/
|
|
7
7
|
import type { ColumnNode, IndexNode, SchemaAST as ISchemaAST, RelationshipNode, RelationshipType, TableNode, ValidationError } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* A table node with its collections empty, ready to be filled. Six places build one, and the fields
|
|
10
|
+
* that are pure boilerplate are exactly the ones a new field gets forgotten in: {@link TableNode.schema}
|
|
11
|
+
* was added and `SchemaAST.clone` kept rebuilding nodes without it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createTableNode(name: string, schema?: string, comment?: string): TableNode;
|
|
8
14
|
/**
|
|
9
15
|
* Schema AST - A graph representation of a database schema.
|
|
10
16
|
*
|
|
@@ -20,7 +26,8 @@ export declare class SchemaAST implements ISchemaAST {
|
|
|
20
26
|
readonly relationships: RelationshipNode[];
|
|
21
27
|
readonly indexes: IndexNode[];
|
|
22
28
|
/**
|
|
23
|
-
* Get a table by name
|
|
29
|
+
* Get a table by the name it is keyed under: schema-qualified where it has one, so two tables of
|
|
30
|
+
* the same name in different schemas stay distinct. Build the key with `qualifyName`.
|
|
24
31
|
*/
|
|
25
32
|
getTable(name: string): TableNode | undefined;
|
|
26
33
|
/**
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
* The main class for working with schema graphs.
|
|
5
5
|
* Provides graph operations like navigation, validation, and topological sorting.
|
|
6
6
|
*/
|
|
7
|
+
import { qualifyName } from '../util/sql.util.js';
|
|
8
|
+
/**
|
|
9
|
+
* A table node with its collections empty, ready to be filled. Six places build one, and the fields
|
|
10
|
+
* that are pure boilerplate are exactly the ones a new field gets forgotten in: {@link TableNode.schema}
|
|
11
|
+
* was added and `SchemaAST.clone` kept rebuilding nodes without it.
|
|
12
|
+
*/
|
|
13
|
+
export function createTableNode(name, schema, comment) {
|
|
14
|
+
return {
|
|
15
|
+
name,
|
|
16
|
+
schema,
|
|
17
|
+
comment,
|
|
18
|
+
columns: new Map(),
|
|
19
|
+
primaryKey: [],
|
|
20
|
+
indexes: [],
|
|
21
|
+
incomingRelations: [],
|
|
22
|
+
outgoingRelations: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
7
25
|
/**
|
|
8
26
|
* Schema AST - A graph representation of a database schema.
|
|
9
27
|
*
|
|
@@ -19,7 +37,8 @@ export class SchemaAST {
|
|
|
19
37
|
relationships = [];
|
|
20
38
|
indexes = [];
|
|
21
39
|
/**
|
|
22
|
-
* Get a table by name
|
|
40
|
+
* Get a table by the name it is keyed under: schema-qualified where it has one, so two tables of
|
|
41
|
+
* the same name in different schemas stay distinct. Build the key with `qualifyName`.
|
|
23
42
|
*/
|
|
24
43
|
getTable(name) {
|
|
25
44
|
return this.tables.get(name);
|
|
@@ -28,8 +47,7 @@ export class SchemaAST {
|
|
|
28
47
|
* Add a table to the schema.
|
|
29
48
|
*/
|
|
30
49
|
addTable(table) {
|
|
31
|
-
table.schema
|
|
32
|
-
this.tables.set(table.name, table);
|
|
50
|
+
this.tables.set(qualifyName(table.name, table.schema), table);
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
53
|
* Remove a table from the schema.
|
|
@@ -353,16 +371,7 @@ export class SchemaAST {
|
|
|
353
371
|
// back to it on creation - `columns` and `primaryKey` are readonly properties holding mutable
|
|
354
372
|
// containers, so they are filled in afterwards without reassigning anything.
|
|
355
373
|
for (const [name, table] of this.tables) {
|
|
356
|
-
const clonedTable =
|
|
357
|
-
name,
|
|
358
|
-
columns: new Map(),
|
|
359
|
-
primaryKey: [],
|
|
360
|
-
indexes: [],
|
|
361
|
-
comment: table.comment,
|
|
362
|
-
schema: clone,
|
|
363
|
-
incomingRelations: [],
|
|
364
|
-
outgoingRelations: [],
|
|
365
|
-
};
|
|
374
|
+
const clonedTable = createTableNode(table.name, table.schema, table.comment);
|
|
366
375
|
for (const [colName, col] of table.columns) {
|
|
367
376
|
clonedTable.columns.set(colName, { ...col, table: clonedTable, referencedBy: [], references: undefined });
|
|
368
377
|
}
|
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
import type { EntityMeta, FieldOptions, Type } from '../type/index.js';
|
|
9
9
|
import type { NamingStrategy } from '../type/namingStrategy.js';
|
|
10
10
|
import { SchemaAST } from './schemaAST.js';
|
|
11
|
-
import type
|
|
11
|
+
import { type ForeignKeyAction } from './types.js';
|
|
12
12
|
/**
|
|
13
13
|
* Options for building SchemaAST from entities.
|
|
14
14
|
*/
|
|
15
15
|
export interface BuildSchemaASTOptions {
|
|
16
|
-
/** Custom table name
|
|
17
|
-
resolveTableName?: (
|
|
16
|
+
/** Custom resolver for a table's own name, unqualified. */
|
|
17
|
+
resolveTableName?: (meta: EntityMeta<unknown>) => string;
|
|
18
|
+
/** Custom resolver for the schema a table lives in; `undefined` leaves it unqualified. */
|
|
19
|
+
resolveSchema?: (meta: EntityMeta<unknown>) => string | undefined;
|
|
18
20
|
/** Custom column name resolver */
|
|
19
21
|
resolveColumnName?: (key: string, field: FieldOptions) => string;
|
|
20
22
|
/** Naming strategy to use */
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* - Database introspection results (TableSchema[])
|
|
7
7
|
*/
|
|
8
8
|
import { getMeta } from '../entity/metadata/definition.js';
|
|
9
|
+
import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
|
|
9
10
|
import { fieldOptionsToCanonical } from './canonicalType.js';
|
|
10
|
-
import { SchemaAST } from './schemaAST.js';
|
|
11
|
-
import { DEFAULT_FOREIGN_KEY_ACTION } from './types.js';
|
|
11
|
+
import { createTableNode, SchemaAST } from './schemaAST.js';
|
|
12
|
+
import { DEFAULT_FOREIGN_KEY_ACTION, } from './types.js';
|
|
12
13
|
/**
|
|
13
14
|
* Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
|
|
14
15
|
*
|
|
@@ -19,13 +20,15 @@ export function buildSchemaAST(entities, options = {}) {
|
|
|
19
20
|
const { namingStrategy } = options;
|
|
20
21
|
const ctx = {
|
|
21
22
|
ast: new SchemaAST(),
|
|
22
|
-
resolveTableName: options.resolveTableName ??
|
|
23
|
+
resolveTableName: options.resolveTableName ??
|
|
24
|
+
((m) => namingStrategy?.tableName(m.name ?? m.entity.name) ?? m.name ?? m.entity.name),
|
|
25
|
+
resolveSchema: options.resolveSchema ?? ((m) => m.schema),
|
|
23
26
|
resolveColumnName: options.resolveColumnName ?? ((k, f) => namingStrategy?.columnName(f.name ?? k) ?? f.name ?? k),
|
|
24
27
|
defaultForeignKeyAction: options.defaultForeignKeyAction ?? DEFAULT_FOREIGN_KEY_ACTION,
|
|
25
28
|
};
|
|
26
29
|
for (const pass of [addTableFromEntity, addRelationshipsFromEntity, addIndexesFromEntity]) {
|
|
27
30
|
for (const entity of entities) {
|
|
28
|
-
pass(ctx,
|
|
31
|
+
pass(ctx, getMeta(entity));
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
return ctx.ast;
|
|
@@ -64,20 +67,10 @@ function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
|
64
67
|
/**
|
|
65
68
|
* Add a table from entity metadata.
|
|
66
69
|
*/
|
|
67
|
-
function addTableFromEntity(ctx,
|
|
68
|
-
const tableName = ctx.resolveTableName(
|
|
69
|
-
const
|
|
70
|
-
const primaryKey =
|
|
71
|
-
// Create placeholder table (will be fully initialized below)
|
|
72
|
-
const table = {
|
|
73
|
-
name: tableName,
|
|
74
|
-
columns,
|
|
75
|
-
primaryKey,
|
|
76
|
-
indexes: [],
|
|
77
|
-
schema: ctx.ast,
|
|
78
|
-
incomingRelations: [],
|
|
79
|
-
outgoingRelations: [],
|
|
80
|
-
};
|
|
70
|
+
function addTableFromEntity(ctx, meta) {
|
|
71
|
+
const tableName = ctx.resolveTableName(meta);
|
|
72
|
+
const table = createTableNode(tableName, ctx.resolveSchema(meta));
|
|
73
|
+
const { columns, primaryKey } = table;
|
|
81
74
|
// Add columns from fields
|
|
82
75
|
const fields = meta.fields;
|
|
83
76
|
for (const key of Object.keys(fields)) {
|
|
@@ -112,12 +105,15 @@ function addTableFromEntity(ctx, entity, meta) {
|
|
|
112
105
|
}
|
|
113
106
|
ctx.ast.addTable(table);
|
|
114
107
|
}
|
|
108
|
+
/** The node an entity maps to, found under the key {@link SchemaAST} stores it by. */
|
|
109
|
+
function tableOf(ctx, meta) {
|
|
110
|
+
return ctx.ast.getTable(qualifyName(ctx.resolveTableName(meta), ctx.resolveSchema(meta)));
|
|
111
|
+
}
|
|
115
112
|
/**
|
|
116
113
|
* Add relationships from entity relation decorators.
|
|
117
114
|
*/
|
|
118
|
-
function addRelationshipsFromEntity(ctx,
|
|
119
|
-
const
|
|
120
|
-
const table = ctx.ast.getTable(tableName);
|
|
115
|
+
function addRelationshipsFromEntity(ctx, meta) {
|
|
116
|
+
const table = tableOf(ctx, meta);
|
|
121
117
|
if (!table)
|
|
122
118
|
return;
|
|
123
119
|
const relations = meta.relations;
|
|
@@ -125,10 +121,8 @@ function addRelationshipsFromEntity(ctx, entity, meta) {
|
|
|
125
121
|
const relation = relations[key];
|
|
126
122
|
if (!relation)
|
|
127
123
|
continue;
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const relatedTableName = ctx.resolveTableName(relatedEntity, relatedMeta);
|
|
131
|
-
const relatedTable = ctx.ast.getTable(relatedTableName);
|
|
124
|
+
const relatedMeta = getMeta(relation.entity());
|
|
125
|
+
const relatedTable = tableOf(ctx, relatedMeta);
|
|
132
126
|
if (!relatedTable)
|
|
133
127
|
continue;
|
|
134
128
|
// Only the owning side gets the FK. `mappedBy` marks the inverse side of a one-to-one, whose
|
|
@@ -151,7 +145,7 @@ function addRelationshipsFromEntity(ctx, entity, meta) {
|
|
|
151
145
|
const foreignColumn = relatedTable.columns.get(foreignColName);
|
|
152
146
|
if (localColumn && foreignColumn) {
|
|
153
147
|
const relNode = {
|
|
154
|
-
name:
|
|
148
|
+
name: derivedForeignKeyName(table.name, [localColName]),
|
|
155
149
|
type: relation.cardinality === 'm1' ? 'ManyToOne' : 'OneToOne',
|
|
156
150
|
from: { table, columns: [localColumn] },
|
|
157
151
|
to: { table: relatedTable, columns: [foreignColumn] },
|
|
@@ -171,9 +165,8 @@ function addRelationshipsFromEntity(ctx, entity, meta) {
|
|
|
171
165
|
* Add indexes from field options (`@Field({ index })`) and from `@Index([...])`, which have nothing
|
|
172
166
|
* in common beyond their target table.
|
|
173
167
|
*/
|
|
174
|
-
function addIndexesFromEntity(ctx,
|
|
175
|
-
const
|
|
176
|
-
const table = ctx.ast.getTable(tableName);
|
|
168
|
+
function addIndexesFromEntity(ctx, meta) {
|
|
169
|
+
const table = tableOf(ctx, meta);
|
|
177
170
|
if (!table)
|
|
178
171
|
return;
|
|
179
172
|
for (const key of Object.keys(meta.fields)) {
|
|
@@ -184,7 +177,7 @@ function addIndexesFromEntity(ctx, entity, meta) {
|
|
|
184
177
|
if (!column)
|
|
185
178
|
continue;
|
|
186
179
|
ctx.ast.addIndex({
|
|
187
|
-
name: typeof field.index === 'string' ? field.index :
|
|
180
|
+
name: typeof field.index === 'string' ? field.index : derivedIndexName(table.name, [column.name]),
|
|
188
181
|
table,
|
|
189
182
|
entries: [{ column: column.name }],
|
|
190
183
|
unique: field.unique ?? false,
|
|
@@ -223,7 +216,7 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
|
|
|
223
216
|
// An index over expressions alone has no column names to build a default name from.
|
|
224
217
|
const named = entries.map((entry, at) => (entry.expression ? `expr${at}` : entry.column));
|
|
225
218
|
ctx.ast.addIndex({
|
|
226
|
-
name: idxMeta.name ??
|
|
219
|
+
name: idxMeta.name ?? derivedIndexName(table.name, named),
|
|
227
220
|
table,
|
|
228
221
|
entries,
|
|
229
222
|
include: idxMeta.include?.map((column) => resolveIncludeColumn(ctx, meta, column)),
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -98,8 +98,17 @@ export interface ColumnNode {
|
|
|
98
98
|
* Represents a database table with all its columns, indexes, and relationships.
|
|
99
99
|
*/
|
|
100
100
|
export interface TableNode {
|
|
101
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* The table's own name, never qualified. Everything derived from a table reads this: an index or
|
|
103
|
+
* constraint name is a single identifier, and `idx_sales.Order_total` is a syntax error.
|
|
104
|
+
*/
|
|
102
105
|
readonly name: string;
|
|
106
|
+
/**
|
|
107
|
+
* The namespace the table lives in, absent where nothing named one and it resolves through the
|
|
108
|
+
* connection's own default. Joined onto {@link name} by `qualifyName`, which is the key a
|
|
109
|
+
* `SchemaAST` stores the table under and the operand a statement names it by.
|
|
110
|
+
*/
|
|
111
|
+
readonly schema?: string;
|
|
103
112
|
/** Map of column name to column node */
|
|
104
113
|
readonly columns: Map<string, ColumnNode>;
|
|
105
114
|
/** Primary key columns (supports composite keys) */
|
|
@@ -108,8 +117,6 @@ export interface TableNode {
|
|
|
108
117
|
readonly indexes: IndexNode[];
|
|
109
118
|
/** Optional table comment */
|
|
110
119
|
readonly comment?: string;
|
|
111
|
-
/** Reference to the parent schema */
|
|
112
|
-
schema: SchemaAST;
|
|
113
120
|
/** Relationships pointing TO this table (other tables referencing this one) */
|
|
114
121
|
incomingRelations: RelationshipNode[];
|
|
115
122
|
/** Relationships pointing FROM this table (this table referencing others) */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
1
2
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
3
|
import { SqliteDialect } from './sqliteDialect.js';
|
|
3
4
|
import { SqliteQuerier } from './sqliteQuerier.js';
|
|
@@ -11,7 +12,7 @@ import { SqliteQuerier } from './sqliteQuerier.js';
|
|
|
11
12
|
export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
12
13
|
opts;
|
|
13
14
|
constructor(opts, extra) {
|
|
14
|
-
super(new SqliteDialect(
|
|
15
|
+
super(new SqliteDialect(dialectOptionsFrom(extra)), extra);
|
|
15
16
|
this.opts = opts;
|
|
16
17
|
}
|
|
17
18
|
async openDb() {
|
|
@@ -8,6 +8,7 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
8
8
|
supportsJsonb: false,
|
|
9
9
|
ifNotExists: true,
|
|
10
10
|
indexIfNotExists: true,
|
|
11
|
+
schemas: false, // SQLite's namespaces are attached database files, not declared objects
|
|
11
12
|
dropTableCascade: false,
|
|
12
13
|
renameColumn: true,
|
|
13
14
|
foreignKeyAlter: false, // SQLite does not support adding FKs to existing tables
|
|
@@ -70,7 +71,7 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
70
71
|
*/
|
|
71
72
|
appendTextSearch(ctx, entity, meta, search) {
|
|
72
73
|
const columns = search.$fields.map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
73
|
-
ctx.append(`${this.
|
|
74
|
+
ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
|
|
74
75
|
ctx.addValue(search.$value);
|
|
75
76
|
}
|
|
76
77
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
1
2
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
3
|
import { TursoDialect } from './tursoDialect.js';
|
|
3
4
|
import { TursoLocalQuerier } from './tursoLocalQuerier.js';
|
|
@@ -12,7 +13,7 @@ export class TursoLocalQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
|
12
13
|
filename;
|
|
13
14
|
opts;
|
|
14
15
|
constructor(filename = ':memory:', opts, extra) {
|
|
15
|
-
super(new TursoDialect(
|
|
16
|
+
super(new TursoDialect(dialectOptionsFrom(extra)), extra);
|
|
16
17
|
this.filename = filename;
|
|
17
18
|
this.opts = opts;
|
|
18
19
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
1
2
|
import { AbstractHranaQuerierPool } from '../sqlite/hranaQuerierPool.js';
|
|
2
3
|
import { TursoDialect } from './tursoDialect.js';
|
|
3
4
|
import { TursoQuerier } from './tursoQuerier.js';
|
|
@@ -21,7 +22,7 @@ export class TursoQuerierPool extends AbstractHranaQuerierPool {
|
|
|
21
22
|
* with the same shape: `@libsql/client/web`, `@libsql/client-wasm`, or a test double.
|
|
22
23
|
*/
|
|
23
24
|
constructor(conf, extra) {
|
|
24
|
-
super(new TursoDialect(
|
|
25
|
+
super(new TursoDialect(dialectOptionsFrom(extra)), extra);
|
|
25
26
|
this.conf = conf;
|
|
26
27
|
this.ownsClient = !isClient(conf);
|
|
27
28
|
}
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -76,6 +76,12 @@ export type InsertIdSource = 'returning' | 'firstId' | 'lastId';
|
|
|
76
76
|
export interface EngineFeatures {
|
|
77
77
|
readonly ifNotExists: boolean;
|
|
78
78
|
readonly indexIfNotExists: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Whether the engine has namespaces a table can sit behind. `false` leaves every table
|
|
81
|
+
* unqualified, whatever `schema` an entity or pool named: SQLite attaches database files and
|
|
82
|
+
* MongoDB takes its database from the connection, so neither has one to name.
|
|
83
|
+
*/
|
|
84
|
+
readonly schemas: boolean;
|
|
79
85
|
readonly dropTableCascade: boolean;
|
|
80
86
|
readonly renameColumn: boolean;
|
|
81
87
|
readonly foreignKeyAlter: boolean;
|