uql-orm 0.24.6 → 0.25.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 +9 -8
- package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -1
- package/dist/cockroachdb/crdbQuerierPool.js +5 -4
- package/dist/dialect/abstractSqlDialect.d.ts +78 -27
- package/dist/dialect/abstractSqlDialect.js +194 -130
- package/dist/dialect/hydrateColumn.d.ts +16 -0
- package/dist/dialect/hydrateColumn.js +66 -0
- package/dist/dialect/jsonSql.d.ts +24 -0
- package/dist/dialect/jsonSql.js +39 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +10 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -7
- package/dist/dialect/pgLikeSqlDialect.js +2 -14
- package/dist/dialect/vectorCast.d.ts +15 -0
- package/dist/dialect/vectorCast.js +58 -0
- package/dist/entity/metadata/definition.d.ts +0 -1
- package/dist/entity/metadata/definition.js +1 -1
- package/dist/maria/mariaDialect.d.ts +3 -2
- package/dist/maria/mariaDialect.js +3 -18
- package/dist/maria/mariadbQuerierPool.js +6 -1
- package/dist/migrate/builder/migrationBuilder.d.ts +12 -16
- package/dist/migrate/builder/migrationBuilder.js +24 -59
- package/dist/migrate/builder/tableBuilder.js +0 -12
- package/dist/migrate/cli.d.ts +0 -1
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +0 -3
- package/dist/migrate/drift/driftDetector.js +17 -15
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +18 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +8 -2
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +10 -9
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/mysqlIntrospector.js +0 -9
- package/dist/migrate/introspection/postgresIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/postgresIntrospector.js +0 -12
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +1 -9
- package/dist/migrate/migrator.d.ts +17 -0
- package/dist/migrate/migrator.js +51 -50
- package/dist/migrate/schemaGenerator.d.ts +19 -8
- package/dist/migrate/schemaGenerator.js +48 -17
- package/dist/neon/neonQuerierPool.d.ts +1 -1
- package/dist/neon/neonQuerierPool.js +6 -4
- package/dist/postgres/abstractPgQuerierPool.d.ts +6 -0
- package/dist/postgres/abstractPgQuerierPool.js +3 -0
- package/dist/postgres/pgNumericTypes.d.ts +41 -0
- package/dist/postgres/pgNumericTypes.js +35 -0
- package/dist/postgres/pgQuerierPool.d.ts +1 -1
- package/dist/postgres/pgQuerierPool.js +5 -4
- package/dist/querier/abstractSqlQuerier.d.ts +9 -2
- package/dist/querier/abstractSqlQuerier.js +34 -25
- package/dist/schema/canonicalType.js +2 -12
- package/dist/schema/schemaAST.js +0 -24
- package/dist/schema/schemaASTBuilder.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.js +4 -5
- package/dist/sqlite/nodeSqliteQuerierPool.js +1 -0
- package/dist/sqlite/sqliteQuerierPool.d.ts +5 -0
- package/dist/sqlite/sqliteQuerierPool.js +7 -0
- package/dist/turso/tursoLocalQuerierPool.js +1 -0
- package/dist/type/entity.d.ts +16 -2
- package/dist/type/migration.d.ts +30 -9
- package/dist/type/queryAggregate.d.ts +3 -0
- package/dist/util/field.util.d.ts +4 -0
- package/dist/util/field.util.js +12 -0
- package/dist/util/sqlLiteral.js +18 -15
- package/package.json +6 -6
|
@@ -4,9 +4,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
4
4
|
* Works with both MySQL and MariaDB as they share the same information_schema structure.
|
|
5
5
|
*/
|
|
6
6
|
export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
7
|
-
// ============================================================================
|
|
8
|
-
// SQL Queries (dialect-specific)
|
|
9
|
-
// ============================================================================
|
|
10
7
|
getTableNamesQuery() {
|
|
11
8
|
return /*sql*/ `
|
|
12
9
|
SELECT TABLE_NAME as table_name
|
|
@@ -95,12 +92,6 @@ export class MysqlSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
95
92
|
ORDER BY ORDINAL_POSITION
|
|
96
93
|
`;
|
|
97
94
|
}
|
|
98
|
-
// ============================================================================
|
|
99
|
-
// Internal Types
|
|
100
|
-
// ============================================================================
|
|
101
|
-
mapTableNameRow(row) {
|
|
102
|
-
return row.table_name;
|
|
103
|
-
}
|
|
104
95
|
async mapColumnsResult(_read, _tableName, results) {
|
|
105
96
|
return results.map((row) => ({
|
|
106
97
|
name: row.column_name,
|
|
@@ -11,9 +11,6 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
11
11
|
protected getIndexesQuery(_tableName: string): string;
|
|
12
12
|
protected getForeignKeysQuery(_tableName: string): string;
|
|
13
13
|
protected getPrimaryKeyQuery(_tableName: string): string;
|
|
14
|
-
protected mapTableNameRow(row: {
|
|
15
|
-
table_name: string;
|
|
16
|
-
}): string;
|
|
17
14
|
protected mapColumnsResult(_read: TableRowReader, _tableName: string, results: PostgresColumnRow[]): Promise<ColumnSchema[]>;
|
|
18
15
|
protected mapIndexesResult(_read: TableRowReader, _tableName: string, results: {
|
|
19
16
|
index_name: string;
|
|
@@ -3,9 +3,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
3
3
|
* PostgreSQL schema introspector
|
|
4
4
|
*/
|
|
5
5
|
export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// SQL Queries (dialect-specific)
|
|
8
|
-
// ============================================================================
|
|
9
6
|
getTableNamesQuery() {
|
|
10
7
|
return /*sql*/ `
|
|
11
8
|
SELECT table_name
|
|
@@ -128,12 +125,6 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
128
125
|
ORDER BY kcu.ordinal_position
|
|
129
126
|
`;
|
|
130
127
|
}
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// Internal Types
|
|
133
|
-
// ============================================================================
|
|
134
|
-
mapTableNameRow(row) {
|
|
135
|
-
return row.table_name;
|
|
136
|
-
}
|
|
137
128
|
async mapColumnsResult(_read, _tableName, results) {
|
|
138
129
|
return results.map((row) => ({
|
|
139
130
|
name: row.column_name,
|
|
@@ -166,9 +157,6 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
166
157
|
onUpdate: this.normalizeReferentialAction(row.update_rule),
|
|
167
158
|
}));
|
|
168
159
|
}
|
|
169
|
-
// ============================================================================
|
|
170
|
-
// PostgreSQL-specific helpers
|
|
171
|
-
// ============================================================================
|
|
172
160
|
normalizeType(dataType, udtName) {
|
|
173
161
|
// Handle user-defined types and arrays
|
|
174
162
|
if (dataType === 'USER-DEFINED') {
|
|
@@ -15,6 +15,7 @@ export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospec
|
|
|
15
15
|
protected getIndexesParams(_tableName: string): unknown[];
|
|
16
16
|
protected getForeignKeysParams(_tableName: string): unknown[];
|
|
17
17
|
protected getPrimaryKeyParams(_tableName: string): unknown[];
|
|
18
|
+
/** `sqlite_master`, not `information_schema`, so the column is `name`. */
|
|
18
19
|
protected mapTableNameRow(row: {
|
|
19
20
|
name: string;
|
|
20
21
|
}): string;
|
|
@@ -3,9 +3,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
3
3
|
* SQLite schema introspector
|
|
4
4
|
*/
|
|
5
5
|
export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// SQL Queries (dialect-specific)
|
|
8
|
-
// ============================================================================
|
|
9
6
|
getTableNamesQuery() {
|
|
10
7
|
return /*sql*/ `
|
|
11
8
|
SELECT name
|
|
@@ -55,9 +52,7 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
55
52
|
getPrimaryKeyParams(_tableName) {
|
|
56
53
|
return [];
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
// Row Mapping (dialect-specific)
|
|
60
|
-
// ============================================================================
|
|
55
|
+
/** `sqlite_master`, not `information_schema`, so the column is `name`. */
|
|
61
56
|
mapTableNameRow(row) {
|
|
62
57
|
return row.name;
|
|
63
58
|
}
|
|
@@ -128,9 +123,6 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
128
123
|
}
|
|
129
124
|
return pkColumns.map((r) => r.name);
|
|
130
125
|
}
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// SQLite-specific helpers
|
|
133
|
-
// ============================================================================
|
|
134
126
|
async getUniqueColumns(read, tableName) {
|
|
135
127
|
const indexes = await read(this.getIndexesQuery(tableName));
|
|
136
128
|
const uniqueColumns = new Set();
|
|
@@ -53,6 +53,14 @@ export declare class Migrator {
|
|
|
53
53
|
to?: string;
|
|
54
54
|
step?: number;
|
|
55
55
|
}): Promise<MigrationResult[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
|
|
58
|
+
*
|
|
59
|
+
* Both directions do exactly this and differ only in the list they start from: `up` takes the
|
|
60
|
+
* pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
|
|
61
|
+
* makes `--to` and `--step` mean the same thing whichever way you are going.
|
|
62
|
+
*/
|
|
63
|
+
private runInOrder;
|
|
56
64
|
/**
|
|
57
65
|
* Run a single migration within a transaction
|
|
58
66
|
*/
|
|
@@ -96,6 +104,15 @@ export declare class Migrator {
|
|
|
96
104
|
safe?: boolean;
|
|
97
105
|
drop?: boolean;
|
|
98
106
|
}): Promise<string[]>;
|
|
107
|
+
/**
|
|
108
|
+
* New tables are emitted together, never one at a time: a single-entity AST has no other table for a
|
|
109
|
+
* relation to resolve against, so every cross-entity foreign key was dropped and generated schemas
|
|
110
|
+
* carried none. Spanning the graph is also what lets a cyclic relation (any `createdBy`
|
|
111
|
+
* back-reference) be created at all.
|
|
112
|
+
*
|
|
113
|
+
* Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
|
|
114
|
+
*/
|
|
115
|
+
private createSchema;
|
|
99
116
|
/**
|
|
100
117
|
* Each pending diff with the entity it came from, since resolving that is async and every caller
|
|
101
118
|
* needs it. What to emit stays with the caller: a sync narrows the forward direction to what the
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -130,27 +130,7 @@ export class Migrator {
|
|
|
130
130
|
* Run all pending migrations
|
|
131
131
|
*/
|
|
132
132
|
async up(options = {}) {
|
|
133
|
-
|
|
134
|
-
const results = [];
|
|
135
|
-
let migrationsToRun = pendingMigrations;
|
|
136
|
-
if (options.to) {
|
|
137
|
-
const toIndex = migrationsToRun.findIndex((m) => m.name === options.to);
|
|
138
|
-
if (toIndex === -1) {
|
|
139
|
-
throw new Error(`Migration '${options.to}' not found`);
|
|
140
|
-
}
|
|
141
|
-
migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
|
|
142
|
-
}
|
|
143
|
-
if (options.step !== undefined) {
|
|
144
|
-
migrationsToRun = migrationsToRun.slice(0, options.step);
|
|
145
|
-
}
|
|
146
|
-
for (const migration of migrationsToRun) {
|
|
147
|
-
const result = await this.runMigration(migration, 'up');
|
|
148
|
-
results.push(result);
|
|
149
|
-
if (!result.success) {
|
|
150
|
-
break; // Stop on first failure
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return results;
|
|
133
|
+
return this.runInOrder(await this.pending(), 'up', options);
|
|
154
134
|
}
|
|
155
135
|
/**
|
|
156
136
|
* Rollback migrations
|
|
@@ -159,23 +139,33 @@ export class Migrator {
|
|
|
159
139
|
const [migrations, executed] = await Promise.all([this.getMigrations(), this.storage.executed()]);
|
|
160
140
|
const executedSet = new Set(executed);
|
|
161
141
|
const executedMigrations = migrations.filter((m) => executedSet.has(m.name)).reverse(); // Rollback in reverse order
|
|
162
|
-
|
|
163
|
-
|
|
142
|
+
return this.runInOrder(executedMigrations, 'down', options);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
|
|
146
|
+
*
|
|
147
|
+
* Both directions do exactly this and differ only in the list they start from: `up` takes the
|
|
148
|
+
* pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
|
|
149
|
+
* makes `--to` and `--step` mean the same thing whichever way you are going.
|
|
150
|
+
*/
|
|
151
|
+
async runInOrder(migrations, direction, options) {
|
|
152
|
+
let selected = migrations;
|
|
164
153
|
if (options.to) {
|
|
165
|
-
const toIndex =
|
|
154
|
+
const toIndex = selected.findIndex((m) => m.name === options.to);
|
|
166
155
|
if (toIndex === -1) {
|
|
167
156
|
throw new Error(`Migration '${options.to}' not found`);
|
|
168
157
|
}
|
|
169
|
-
|
|
158
|
+
selected = selected.slice(0, toIndex + 1);
|
|
170
159
|
}
|
|
171
160
|
if (options.step !== undefined) {
|
|
172
|
-
|
|
161
|
+
selected = selected.slice(0, options.step);
|
|
173
162
|
}
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
const results = [];
|
|
164
|
+
for (const migration of selected) {
|
|
165
|
+
const result = await this.runMigration(migration, direction);
|
|
176
166
|
results.push(result);
|
|
177
167
|
if (!result.success) {
|
|
178
|
-
break;
|
|
168
|
+
break;
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
171
|
return results;
|
|
@@ -244,20 +234,22 @@ export class Migrator {
|
|
|
244
234
|
* Generate a migration based on entity schema differences
|
|
245
235
|
*/
|
|
246
236
|
async generateFromEntities(name) {
|
|
247
|
-
const
|
|
237
|
+
const creating = [];
|
|
238
|
+
const altering = [];
|
|
248
239
|
const downStatements = [];
|
|
249
240
|
for (const { diff, entity } of await this.pendingDiffs()) {
|
|
250
241
|
if (diff.type === 'create') {
|
|
251
242
|
if (entity) {
|
|
252
|
-
|
|
243
|
+
creating.push(diff.tableName);
|
|
253
244
|
downStatements.push(this.generator.generateDropTable(diff.tableName, { ifExists: true }));
|
|
254
245
|
}
|
|
255
246
|
}
|
|
256
247
|
else if (diff.type === 'alter') {
|
|
257
|
-
|
|
248
|
+
altering.push(...this.generator.generateAlterTable(diff));
|
|
258
249
|
downStatements.push(...this.generator.generateAlterTableDown(diff));
|
|
259
250
|
}
|
|
260
251
|
}
|
|
252
|
+
const upStatements = [...this.createSchema(creating), ...altering];
|
|
261
253
|
if (upStatements.length === 0) {
|
|
262
254
|
this.logger.logInfo('No schema changes detected.');
|
|
263
255
|
return '';
|
|
@@ -327,21 +319,18 @@ export class Migrator {
|
|
|
327
319
|
*/
|
|
328
320
|
async syncForce() {
|
|
329
321
|
await this.ensureSchemaGenerator();
|
|
322
|
+
// Both directions span the whole entity set rather than looping an entity at a time. A per-entity
|
|
323
|
+
// AST cannot resolve a cross-entity foreign key, so the old create loop silently produced a schema
|
|
324
|
+
// with no referential integrity; and the old drop loop went in reverse *declaration* order, which
|
|
325
|
+
// says nothing about the relation graph and is rejected as soon as the constraints are really there.
|
|
326
|
+
const statements = [
|
|
327
|
+
...this.generator.generateDropSchema(this.entities, { ifExists: true, cascade: true }),
|
|
328
|
+
...this.generator.generateCreateSchema(this.entities),
|
|
329
|
+
];
|
|
330
330
|
await withSqlQuerierForMigrations(this.pool, 'Migrator', (querier) => querier.transaction(async () => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const dropSql = this.generator.generateDropTable(tableName, { ifExists: true });
|
|
335
|
-
this.logger.logSchema(`Executing: ${dropSql}`);
|
|
336
|
-
await querier.run(dropSql);
|
|
337
|
-
}
|
|
338
|
-
// Create all tables
|
|
339
|
-
for (const entity of this.entities) {
|
|
340
|
-
const createStmts = this.generator.generateCreateTable(entity);
|
|
341
|
-
for (const createSql of createStmts) {
|
|
342
|
-
this.logger.logSchema(`Executing: ${createSql}`);
|
|
343
|
-
await querier.run(createSql);
|
|
344
|
-
}
|
|
331
|
+
for (const sql of statements) {
|
|
332
|
+
this.logger.logSchema(`Executing: ${sql}`);
|
|
333
|
+
await querier.run(sql);
|
|
345
334
|
}
|
|
346
335
|
}));
|
|
347
336
|
this.logger.logSchema('Schema sync (force) completed');
|
|
@@ -363,17 +352,29 @@ export class Migrator {
|
|
|
363
352
|
* statements rather than a summary of a second, differently-computed diff.
|
|
364
353
|
*/
|
|
365
354
|
async planSync(options = {}) {
|
|
366
|
-
const
|
|
355
|
+
const creating = [];
|
|
356
|
+
const altering = [];
|
|
367
357
|
for (const { diff, entity } of await this.pendingDiffs()) {
|
|
368
358
|
if (diff.type === 'create') {
|
|
369
359
|
if (entity)
|
|
370
|
-
|
|
360
|
+
creating.push(diff.tableName);
|
|
371
361
|
}
|
|
372
362
|
else if (diff.type === 'alter') {
|
|
373
|
-
|
|
363
|
+
altering.push(...this.generator.generateAlterTable(this.filterDiff(diff, options)));
|
|
374
364
|
}
|
|
375
365
|
}
|
|
376
|
-
return
|
|
366
|
+
return [...this.createSchema(creating), ...altering];
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* New tables are emitted together, never one at a time: a single-entity AST has no other table for a
|
|
370
|
+
* relation to resolve against, so every cross-entity foreign key was dropped and generated schemas
|
|
371
|
+
* carried none. Spanning the graph is also what lets a cyclic relation (any `createdBy`
|
|
372
|
+
* back-reference) be created at all.
|
|
373
|
+
*
|
|
374
|
+
* Empty in, empty out, so a diff with no new tables does not build an AST for the whole graph.
|
|
375
|
+
*/
|
|
376
|
+
createSchema(tableNames) {
|
|
377
|
+
return tableNames.length ? this.generator.generateCreateSchema(this.entities, { only: tableNames }) : [];
|
|
377
378
|
}
|
|
378
379
|
/**
|
|
379
380
|
* Each pending diff with the entity it came from, since resolving that is async and every caller
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AbstractDialect, AbstractSqlDialect } from '../dialect/index.js';
|
|
2
2
|
import type { CanonicalType, ColumnNode, ForeignKeyAction, IndexNode, TableNode } from '../schema/types.js';
|
|
3
|
-
import type { ColumnSchema, DialectFeatures, EntityMeta, FieldOptions, IndexSchema, NamingStrategy, SchemaDiff, SqlDdlGenerator, Type } from '../type/index.js';
|
|
3
|
+
import type { ColumnSchema, CreateSchemaOptions, DialectFeatures, DropSchemaOptions, EntityMeta, FieldOptions, IndexSchema, NamingStrategy, SchemaDiff, SqlDdlGenerator, Type } from '../type/index.js';
|
|
4
4
|
import type { FullColumnDefinition, TableDefinition, TableForeignKeyDefinition } from './builder/types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Unified SQL schema generator.
|
|
@@ -27,13 +27,24 @@ export declare class SqlSchemaGenerator implements SqlDdlGenerator {
|
|
|
27
27
|
*/
|
|
28
28
|
protected getCanonicalType(field: FieldOptions, fieldType?: unknown): CanonicalType;
|
|
29
29
|
protected canonicalTypeToSql(type: CanonicalType): string;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Every `CREATE TABLE` for `entities`, then their foreign keys.
|
|
32
|
+
*
|
|
33
|
+
* Two phases rather than inline constraints, because a relation graph is routinely cyclic: any
|
|
34
|
+
* `createdBy`-style back-reference makes `A` reference `B` while `B` references `A`, and no create
|
|
35
|
+
* order satisfies that. TypeORM's schema builder splits for the same reason (`createNewTables()`
|
|
36
|
+
* then `createForeignKeys()`). SQLite is the exception and keeps them inline: it cannot `ALTER` a
|
|
37
|
+
* foreign key in, but it resolves targets lazily, so a forward reference is fine there.
|
|
38
|
+
*/
|
|
39
|
+
generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
|
|
40
|
+
generateDropSchema(entities: readonly Type<unknown>[], options?: DropSchemaOptions): string[];
|
|
41
|
+
/**
|
|
42
|
+
* The tables of `entities` in dependency order, optionally narrowed to `only`. The AST always spans
|
|
43
|
+
* every entity even when narrowed, so a relation pointing at a table outside the subset still
|
|
44
|
+
* resolves instead of being silently dropped.
|
|
45
|
+
*/
|
|
46
|
+
private orderedTables;
|
|
47
|
+
generateDropTable(tableName: string, options?: DropSchemaOptions): string;
|
|
37
48
|
generateAlterTable(diff: SchemaDiff): string[];
|
|
38
49
|
generateAlterTableDown(diff: SchemaDiff): string[];
|
|
39
50
|
generateCreateIndex(tableName: string, index: IndexSchema, options?: {
|
|
@@ -40,9 +40,6 @@ export class SqlSchemaGenerator {
|
|
|
40
40
|
get serialPrimaryKeyType() {
|
|
41
41
|
return this.dialect.serialPrimaryKey;
|
|
42
42
|
}
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// CanonicalType Integration (Unified Type System)
|
|
45
|
-
// ============================================================================
|
|
46
43
|
/**
|
|
47
44
|
* Convert FieldOptions to CanonicalType using the unified type system.
|
|
48
45
|
*/
|
|
@@ -52,14 +49,54 @@ export class SqlSchemaGenerator {
|
|
|
52
49
|
canonicalTypeToSql(type) {
|
|
53
50
|
return canonicalToSql(type, this.dialect);
|
|
54
51
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Every `CREATE TABLE` for `entities`, then their foreign keys.
|
|
54
|
+
*
|
|
55
|
+
* Two phases rather than inline constraints, because a relation graph is routinely cyclic: any
|
|
56
|
+
* `createdBy`-style back-reference makes `A` reference `B` while `B` references `A`, and no create
|
|
57
|
+
* order satisfies that. TypeORM's schema builder splits for the same reason (`createNewTables()`
|
|
58
|
+
* then `createForeignKeys()`). SQLite is the exception and keeps them inline: it cannot `ALTER` a
|
|
59
|
+
* foreign key in, but it resolves targets lazily, so a forward reference is fine there.
|
|
60
|
+
*/
|
|
61
|
+
generateCreateSchema(entities, options = {}) {
|
|
62
|
+
const tables = this.orderedTables(entities, 'create', options.only);
|
|
63
|
+
const withForeignKeys = options.foreignKeys ?? true;
|
|
64
|
+
// Inline only where a constraint cannot be added afterwards, which is what makes the cyclic case
|
|
65
|
+
// work everywhere else.
|
|
66
|
+
const inline = withForeignKeys && !this.features.foreignKeyAlter;
|
|
67
|
+
const statements = tables.flatMap((table) => this.generateCreateTableFromNode(inline ? table : { ...table, outgoingRelations: [] }, options));
|
|
68
|
+
if (withForeignKeys && !inline) {
|
|
69
|
+
for (const table of tables) {
|
|
70
|
+
for (const rel of table.outgoingRelations) {
|
|
71
|
+
statements.push(this.generateAddForeignKeySql(table.name, {
|
|
72
|
+
name: rel.name,
|
|
73
|
+
columns: rel.from.columns.map((c) => c.name),
|
|
74
|
+
referencesTable: rel.to.table.name,
|
|
75
|
+
referencesColumns: rel.to.columns.map((c) => c.name),
|
|
76
|
+
onDelete: rel.onDelete ?? this.defaultForeignKeyAction,
|
|
77
|
+
onUpdate: rel.onUpdate ?? this.defaultForeignKeyAction,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return statements;
|
|
83
|
+
}
|
|
84
|
+
generateDropSchema(entities, options = {}) {
|
|
85
|
+
return this.orderedTables(entities, 'drop').map((table) => this.generateDropTable(table.name, options));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The tables of `entities` in dependency order, optionally narrowed to `only`. The AST always spans
|
|
89
|
+
* every entity even when narrowed, so a relation pointing at a table outside the subset still
|
|
90
|
+
* resolves instead of being silently dropped.
|
|
91
|
+
*/
|
|
92
|
+
orderedTables(entities, direction, only) {
|
|
93
|
+
const ast = new SchemaASTBuilder(this.dialect.namingStrategy).fromEntities(entities);
|
|
94
|
+
const tables = direction === 'create' ? ast.getCreateOrder() : ast.getDropOrder();
|
|
95
|
+
if (!only) {
|
|
96
|
+
return tables;
|
|
97
|
+
}
|
|
98
|
+
const wanted = new Set(only);
|
|
99
|
+
return tables.filter((table) => wanted.has(table.name));
|
|
63
100
|
}
|
|
64
101
|
generateDropTable(tableName, options = {}) {
|
|
65
102
|
const ifExists = options.ifExists ? 'IF EXISTS ' : '';
|
|
@@ -390,9 +427,6 @@ export class SqlSchemaGenerator {
|
|
|
390
427
|
};
|
|
391
428
|
return normalize(current) === normalize(desired);
|
|
392
429
|
}
|
|
393
|
-
// ============================================================================
|
|
394
|
-
// SchemaAST Support Methods
|
|
395
|
-
// ============================================================================
|
|
396
430
|
generateCreateTableFromNode(table, options = {}) {
|
|
397
431
|
const columns = [];
|
|
398
432
|
const constraints = [];
|
|
@@ -468,9 +502,6 @@ export class SqlSchemaGenerator {
|
|
|
468
502
|
generateCreateIndexFromNode(index, options = { ifNotExists: false }) {
|
|
469
503
|
return this.generateCreateIndex(index.table.name, indexNodeToSchema(index), options);
|
|
470
504
|
}
|
|
471
|
-
// ============================================================================
|
|
472
|
-
// Phase 3: Builder Operation Methods (Moved forward for unification)
|
|
473
|
-
// ============================================================================
|
|
474
505
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
475
506
|
const tableNode = this.tableDefinitionToNode(table);
|
|
476
507
|
return this.generateCreateTableFromNode(tableNode, options);
|
|
@@ -6,5 +6,5 @@ import { NeonQuerier } from './neonQuerier.js';
|
|
|
6
6
|
export declare class NeonQuerierPool extends AbstractPgQuerierPool<PoolClient, NeonQuerier, NeonDialect> {
|
|
7
7
|
readonly pool: Pool;
|
|
8
8
|
constructor(opts: PoolConfig, extra?: ExtraOptions);
|
|
9
|
-
|
|
9
|
+
protected buildQuerier(connect: () => Promise<PoolClient>): NeonQuerier;
|
|
10
10
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { Pool } from '@neondatabase/serverless';
|
|
1
|
+
import { Pool, types } from '@neondatabase/serverless';
|
|
2
2
|
import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
|
|
3
|
+
import { numericTypes } from '../postgres/pgNumericTypes.js';
|
|
3
4
|
import { NeonDialect } from './neonDialect.js';
|
|
4
5
|
import { NeonQuerier } from './neonQuerier.js';
|
|
5
6
|
export class NeonQuerierPool extends AbstractPgQuerierPool {
|
|
6
7
|
constructor(opts, extra) {
|
|
7
|
-
|
|
8
|
+
// Neon's own `types`, not `pg`'s: this entry has to load on an edge runtime where `pg` is absent.
|
|
9
|
+
super(new NeonDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ types: numericTypes(types), ...opts }), extra);
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
return new NeonQuerier(
|
|
11
|
+
buildQuerier(connect) {
|
|
12
|
+
return new NeonQuerier(connect, this.dialect, this.extra);
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -17,5 +17,11 @@ export interface PgAnyPool<C extends PgAnyClient> extends ErrorEmittingPool {
|
|
|
17
17
|
export declare abstract class AbstractPgQuerierPool<C extends PgAnyClient, Q extends AbstractPgQuerier<C, D>, D extends AbstractSqlDialect> extends AbstractSqlQuerierPool<Q, D> {
|
|
18
18
|
readonly pool: PgAnyPool<C>;
|
|
19
19
|
constructor(dialect: D, pool: PgAnyPool<C>, extra?: ExtraOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Every pg-compatible pool acquires a client the same way, so only the querier class varies.
|
|
22
|
+
* Subclasses name that instead of restating the lazy `connect` the querier expects.
|
|
23
|
+
*/
|
|
24
|
+
protected abstract buildQuerier(connect: () => Promise<C>): Q;
|
|
25
|
+
getQuerier(): Promise<Q>;
|
|
20
26
|
end(): Promise<void>;
|
|
21
27
|
}
|
|
@@ -14,6 +14,9 @@ export class AbstractPgQuerierPool extends AbstractSqlQuerierPool {
|
|
|
14
14
|
this.pool = pool;
|
|
15
15
|
attachPoolErrorHandler(pool, 'Idle Postgres pool client encountered an error');
|
|
16
16
|
}
|
|
17
|
+
async getQuerier() {
|
|
18
|
+
return this.buildQuerier(() => this.pool.connect());
|
|
19
|
+
}
|
|
17
20
|
async end() {
|
|
18
21
|
await this.pool.end();
|
|
19
22
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CustomTypesConfig } from 'pg';
|
|
2
|
+
/**
|
|
3
|
+
* The shape every pg-family driver exposes as `types`: `pg`'s own, and `@neondatabase/serverless`'s
|
|
4
|
+
* reimplementation of it. Taken as a parameter rather than imported, because `uql-orm/neon` must not
|
|
5
|
+
* pull `pg` into an edge bundle that has no such peer installed - the same reason
|
|
6
|
+
* `abstractPgQuerierPool.ts` keeps its `pg` imports type-only.
|
|
7
|
+
*/
|
|
8
|
+
type PgTypes = {
|
|
9
|
+
readonly builtins: Readonly<Record<string, number>>;
|
|
10
|
+
getTypeParser(oid: number, format?: 'text' | 'binary'): (value: string) => unknown;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Decode `INT8` and `FLOAT8` as JS numbers, leaving every other type to the driver.
|
|
14
|
+
*
|
|
15
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
16
|
+
* `schema/canonicalType.ts`), so without it a field declared `number` read back as `'9'` - including
|
|
17
|
+
* every auto-increment primary key, on every entity. `FLOAT8` is a float64, which is exactly what a
|
|
18
|
+
* JS number is, so decoding it loses nothing at all.
|
|
19
|
+
*
|
|
20
|
+
* At the driver because everything crosses the wire decoder exactly once - entity reads, `RETURNING
|
|
21
|
+
* id`, raw SQL, counts, aggregates - while the ORM's hydration only ever sees entity reads. Which
|
|
22
|
+
* types belong here and which need the entity's declaration is settled in `hydratableFields`.
|
|
23
|
+
*
|
|
24
|
+
* `NUMERIC` is deliberately absent, and decoded in hydration instead: `type: BigInt` also maps to
|
|
25
|
+
* BIGINT, so a blanket decode here is already as far as a driver can go without the declaration. That
|
|
26
|
+
* split also covers mysql2, which returns DECIMAL as text and has no equivalent hook.
|
|
27
|
+
*
|
|
28
|
+
* Per pool, never global, which is the whole reason this takes `types` as an argument. TypeORM does
|
|
29
|
+
* the same job by assigning `postgres.defaults.parseInt8`, a module-wide flag every pool in the
|
|
30
|
+
* process then shares; MikroORM passes a per-pool `TypeOverrides`, as here. Two globals of exactly
|
|
31
|
+
* that shape have already been deleted from this repo - `test/pgTypeParsers.util.ts` and the
|
|
32
|
+
* `types.setTypeParser` calls in `neon/neonQuerier.test.ts` - and both made the suite pass on
|
|
33
|
+
* behaviour the library never shipped. Do not reintroduce one.
|
|
34
|
+
*
|
|
35
|
+
* Exact to 2^53, which covers any auto-increment id. A caller who needs more passes their own
|
|
36
|
+
* `types` in the pool options: it is spread after this one and therefore wins. For a decimal, the
|
|
37
|
+
* lighter escape hatch is the declaration itself: `@Field({ type: String, columnType: 'decimal' })`
|
|
38
|
+
* keeps the column DECIMAL while leaving the value as the exact text the driver returned.
|
|
39
|
+
*/
|
|
40
|
+
export declare function numericTypes(types: PgTypes): CustomTypesConfig;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode `INT8` and `FLOAT8` as JS numbers, leaving every other type to the driver.
|
|
3
|
+
*
|
|
4
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
5
|
+
* `schema/canonicalType.ts`), so without it a field declared `number` read back as `'9'` - including
|
|
6
|
+
* every auto-increment primary key, on every entity. `FLOAT8` is a float64, which is exactly what a
|
|
7
|
+
* JS number is, so decoding it loses nothing at all.
|
|
8
|
+
*
|
|
9
|
+
* At the driver because everything crosses the wire decoder exactly once - entity reads, `RETURNING
|
|
10
|
+
* id`, raw SQL, counts, aggregates - while the ORM's hydration only ever sees entity reads. Which
|
|
11
|
+
* types belong here and which need the entity's declaration is settled in `hydratableFields`.
|
|
12
|
+
*
|
|
13
|
+
* `NUMERIC` is deliberately absent, and decoded in hydration instead: `type: BigInt` also maps to
|
|
14
|
+
* BIGINT, so a blanket decode here is already as far as a driver can go without the declaration. That
|
|
15
|
+
* split also covers mysql2, which returns DECIMAL as text and has no equivalent hook.
|
|
16
|
+
*
|
|
17
|
+
* Per pool, never global, which is the whole reason this takes `types` as an argument. TypeORM does
|
|
18
|
+
* the same job by assigning `postgres.defaults.parseInt8`, a module-wide flag every pool in the
|
|
19
|
+
* process then shares; MikroORM passes a per-pool `TypeOverrides`, as here. Two globals of exactly
|
|
20
|
+
* that shape have already been deleted from this repo - `test/pgTypeParsers.util.ts` and the
|
|
21
|
+
* `types.setTypeParser` calls in `neon/neonQuerier.test.ts` - and both made the suite pass on
|
|
22
|
+
* behaviour the library never shipped. Do not reintroduce one.
|
|
23
|
+
*
|
|
24
|
+
* Exact to 2^53, which covers any auto-increment id. A caller who needs more passes their own
|
|
25
|
+
* `types` in the pool options: it is spread after this one and therefore wins. For a decimal, the
|
|
26
|
+
* lighter escape hatch is the declaration itself: `@Field({ type: String, columnType: 'decimal' })`
|
|
27
|
+
* keeps the column DECIMAL while leaving the value as the exact text the driver returned.
|
|
28
|
+
*/
|
|
29
|
+
export function numericTypes(types) {
|
|
30
|
+
// Text only: in binary mode an INT8 arrives as an 8-byte Buffer, and `Number(buffer)` is `NaN`.
|
|
31
|
+
const textNumeric = new Set([types.builtins['INT8'], types.builtins['FLOAT8']]);
|
|
32
|
+
return {
|
|
33
|
+
getTypeParser: (oid, format) => format === 'text' && textNumeric.has(oid) ? Number : types.getTypeParser(oid, format),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -6,5 +6,5 @@ import { PgQuerier } from './pgQuerier.js';
|
|
|
6
6
|
export declare class PgQuerierPool extends AbstractPgQuerierPool<PoolClient, PgQuerier, PgDialect> {
|
|
7
7
|
readonly pool: Pool;
|
|
8
8
|
constructor(opts: PoolConfig, extra?: ExtraOptions);
|
|
9
|
-
|
|
9
|
+
protected buildQuerier(connect: () => Promise<PoolClient>): PgQuerier;
|
|
10
10
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { Pool } from 'pg';
|
|
1
|
+
import { Pool, types } from 'pg';
|
|
2
2
|
import { AbstractPgQuerierPool } from './abstractPgQuerierPool.js';
|
|
3
3
|
import { PgDialect } from './pgDialect.js';
|
|
4
|
+
import { numericTypes } from './pgNumericTypes.js';
|
|
4
5
|
import { PgQuerier } from './pgQuerier.js';
|
|
5
6
|
export class PgQuerierPool extends AbstractPgQuerierPool {
|
|
6
7
|
constructor(opts, extra) {
|
|
7
8
|
// keepAlive reduces (but can't eliminate) idle connections being silently
|
|
8
9
|
// dropped by NATs/firewalls on long-lived remote connections.
|
|
9
|
-
super(new PgDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ keepAlive: true, ...opts }), extra);
|
|
10
|
+
super(new PgDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ keepAlive: true, types: numericTypes(types), ...opts }), extra);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
return new PgQuerier(
|
|
12
|
+
buildQuerier(connect) {
|
|
13
|
+
return new PgQuerier(connect, this.dialect, this.extra);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
@@ -41,8 +41,15 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
41
41
|
* Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
|
|
42
42
|
*/
|
|
43
43
|
protected internalStream<T>(query: string, values?: unknown[]): AsyncIterable<T>;
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Turn what a driver returned back into the types the entity declares, for the row and everything
|
|
46
|
+
* populated under it. Which columns, and as what, is `hydratableFields`; the per-cell decode is
|
|
47
|
+
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is only sparse on Postgres.
|
|
48
|
+
*
|
|
49
|
+
* `visited` guards a populated graph that points back at itself; it defaults rather than living in
|
|
50
|
+
* a separate entry-point wrapper, because the wrapper's whole body was seeding it.
|
|
51
|
+
*/
|
|
52
|
+
private hydrateFields;
|
|
46
53
|
protected internalCount<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
47
54
|
protected internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
48
55
|
internalInsertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|