metal-orm 1.0.14 → 1.0.15
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 +40 -45
- package/dist/decorators/index.cjs +1600 -27
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -2
- package/dist/decorators/index.d.ts +6 -2
- package/dist/decorators/index.js +1599 -27
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +4608 -3429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +511 -159
- package/dist/index.d.ts +511 -159
- package/dist/index.js +4526 -3415
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
- package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
- package/package.json +1 -1
- package/src/codegen/typescript.ts +38 -35
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +16 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +144 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +30 -19
- package/src/orm/entity-metadata.ts +7 -0
- package/src/orm/entity.ts +58 -27
- package/src/orm/hydration.ts +25 -17
- package/src/orm/lazy-batch.ts +46 -2
- package/src/orm/orm-context.ts +60 -60
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +43 -2
- package/src/orm/relations/has-one.ts +139 -0
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +60 -60
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +66 -61
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
|
@@ -1,154 +1,245 @@
|
|
|
1
|
-
import { SchemaIntrospector, IntrospectOptions } from './types.js';
|
|
2
|
-
import { queryRows, shouldIncludeTable } from './utils.js';
|
|
3
|
-
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
4
|
-
import { DbExecutor } from '../../
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
1
|
+
import type { SchemaIntrospector, IntrospectOptions } from './types.js';
|
|
2
|
+
import { queryRows, shouldIncludeTable } from './utils.js';
|
|
3
|
+
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
4
|
+
import type { DbExecutor } from '../../execution/db-executor.js';
|
|
5
|
+
import type { IntrospectContext } from './context.js';
|
|
6
|
+
import { PgInformationSchemaColumns } from './catalogs/postgres.js';
|
|
7
|
+
import { PgKeyColumnUsage, PgTableConstraints, PgConstraintColumnUsage, PgReferentialConstraints, PgIndex, PgClass, PgNamespace, PgAttribute } from './catalogs/postgres.js';
|
|
8
|
+
import { SelectQueryBuilder } from '../../../query-builder/select.js';
|
|
9
|
+
import { eq, and } from '../../ast/expression-builders.js';
|
|
10
|
+
import type { SelectQueryNode, TableNode } from '../../ast/query.js';
|
|
11
|
+
import type { JoinNode } from '../../ast/join.js';
|
|
12
|
+
import type { ColumnNode, ExpressionNode } from '../../ast/expression-nodes.js';
|
|
13
|
+
import { fnTable } from '../../ast/builders.js';
|
|
14
|
+
import { runSelect, runSelectNode } from './run-select.js';
|
|
15
|
+
|
|
16
|
+
export const postgresIntrospector: SchemaIntrospector = {
|
|
17
|
+
async introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
18
|
+
const schema = options.schema || 'public';
|
|
19
|
+
const tables: DatabaseTable[] = [];
|
|
20
|
+
|
|
21
|
+
// Columns query
|
|
22
|
+
const qbColumns = new SelectQueryBuilder(PgInformationSchemaColumns as any)
|
|
23
|
+
.select({
|
|
24
|
+
table_schema: PgInformationSchemaColumns.columns.table_schema,
|
|
25
|
+
table_name: PgInformationSchemaColumns.columns.table_name,
|
|
26
|
+
column_name: PgInformationSchemaColumns.columns.column_name,
|
|
27
|
+
data_type: PgInformationSchemaColumns.columns.data_type,
|
|
28
|
+
is_nullable: PgInformationSchemaColumns.columns.is_nullable,
|
|
29
|
+
column_default: PgInformationSchemaColumns.columns.column_default,
|
|
30
|
+
ordinal_position: PgInformationSchemaColumns.columns.ordinal_position
|
|
31
|
+
})
|
|
32
|
+
.where(eq(PgInformationSchemaColumns.columns.table_schema, schema))
|
|
33
|
+
.orderBy(PgInformationSchemaColumns.columns.table_name)
|
|
34
|
+
.orderBy(PgInformationSchemaColumns.columns.ordinal_position);
|
|
35
|
+
|
|
36
|
+
const columnRows = await runSelect(qbColumns, ctx);
|
|
37
|
+
|
|
38
|
+
// Primary key columns query
|
|
39
|
+
const qbPk = new SelectQueryBuilder(PgKeyColumnUsage as any)
|
|
40
|
+
.select({
|
|
41
|
+
table_schema: PgKeyColumnUsage.columns.table_schema,
|
|
42
|
+
table_name: PgKeyColumnUsage.columns.table_name,
|
|
43
|
+
column_name: PgKeyColumnUsage.columns.column_name,
|
|
44
|
+
ordinal_position: PgKeyColumnUsage.columns.ordinal_position,
|
|
45
|
+
constraint_name: PgKeyColumnUsage.columns.constraint_name
|
|
46
|
+
})
|
|
47
|
+
.innerJoin(PgTableConstraints as any, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
|
|
48
|
+
.where(eq(PgTableConstraints.columns.constraint_type, 'PRIMARY KEY'))
|
|
49
|
+
.where(eq(PgKeyColumnUsage.columns.table_schema, schema))
|
|
50
|
+
.orderBy(PgKeyColumnUsage.columns.table_name)
|
|
51
|
+
.orderBy(PgKeyColumnUsage.columns.ordinal_position);
|
|
52
|
+
|
|
53
|
+
const pkRows = await runSelect(qbPk, ctx);
|
|
54
|
+
|
|
55
|
+
// Build primary key map (grouped by table, ordered by ordinal_position)
|
|
56
|
+
const pkMap = new Map<string, string[]>();
|
|
57
|
+
const pkGrouped = new Map<string, { pos: number; col: string }[]>();
|
|
58
|
+
for (const r of pkRows) {
|
|
59
|
+
const key = `${r.table_schema}.${r.table_name}`;
|
|
60
|
+
const arr = pkGrouped.get(key) ?? [];
|
|
61
|
+
arr.push({ pos: r.ordinal_position ?? 0, col: r.column_name });
|
|
62
|
+
pkGrouped.set(key, arr);
|
|
63
|
+
}
|
|
64
|
+
for (const [k, vals] of pkGrouped.entries()) {
|
|
65
|
+
vals.sort((a, b) => (a.pos || 0) - (b.pos || 0));
|
|
66
|
+
pkMap.set(k, vals.map(v => v.col));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Foreign key columns query
|
|
70
|
+
const qbFk = new SelectQueryBuilder(PgKeyColumnUsage as any)
|
|
71
|
+
.select({
|
|
72
|
+
table_schema: PgKeyColumnUsage.columns.table_schema,
|
|
73
|
+
table_name: PgKeyColumnUsage.columns.table_name,
|
|
74
|
+
column_name: PgKeyColumnUsage.columns.column_name,
|
|
75
|
+
constraint_name: PgKeyColumnUsage.columns.constraint_name,
|
|
76
|
+
foreign_table_schema: PgConstraintColumnUsage.columns.table_schema,
|
|
77
|
+
foreign_table_name: PgConstraintColumnUsage.columns.table_name,
|
|
78
|
+
foreign_column_name: PgConstraintColumnUsage.columns.column_name
|
|
79
|
+
})
|
|
80
|
+
.innerJoin(PgTableConstraints as any, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
|
|
81
|
+
.innerJoin(PgConstraintColumnUsage as any, eq(PgConstraintColumnUsage.columns.constraint_name, PgTableConstraints.columns.constraint_name))
|
|
82
|
+
.innerJoin(PgReferentialConstraints as any, eq(PgReferentialConstraints.columns.constraint_name, PgTableConstraints.columns.constraint_name))
|
|
83
|
+
.where(eq(PgTableConstraints.columns.constraint_type, 'FOREIGN KEY'))
|
|
84
|
+
.where(eq(PgKeyColumnUsage.columns.table_schema, schema));
|
|
85
|
+
|
|
86
|
+
const fkRows = await runSelect(qbFk, ctx);
|
|
87
|
+
|
|
88
|
+
// Build foreign key map
|
|
89
|
+
const fkMap = new Map<string, any[]>();
|
|
90
|
+
for (const r of fkRows) {
|
|
91
|
+
const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
|
|
92
|
+
const existing = fkMap.get(key) ?? [];
|
|
93
|
+
existing.push({
|
|
94
|
+
table: `${r.foreign_table_schema}.${r.foreign_table_name}`,
|
|
95
|
+
column: r.foreign_column_name,
|
|
96
|
+
onDelete: undefined,
|
|
97
|
+
onUpdate: undefined
|
|
98
|
+
});
|
|
99
|
+
fkMap.set(key, existing);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Index columns query using AST with FunctionTable for unnest
|
|
103
|
+
const indexQuery: SelectQueryNode = {
|
|
104
|
+
type: 'SelectQuery',
|
|
105
|
+
from: { type: 'Table', name: 'pg_index', schema: 'pg_catalog', alias: 'i' } as TableNode,
|
|
106
|
+
columns: [
|
|
107
|
+
{ type: 'Column', table: 'ns', name: 'nspname', alias: 'table_schema' } as ColumnNode,
|
|
108
|
+
{ type: 'Column', table: 'tbl', name: 'relname', alias: 'table_name' } as ColumnNode,
|
|
109
|
+
{ type: 'Column', table: 'idx', name: 'relname', alias: 'index_name' } as ColumnNode,
|
|
110
|
+
{ type: 'Column', table: 'i', name: 'indisunique', alias: 'is_unique' } as ColumnNode,
|
|
111
|
+
{ type: 'Column', table: 'i', name: 'indpred', alias: 'predicate' } as ColumnNode,
|
|
112
|
+
{ type: 'Column', table: 'att', name: 'attname', alias: 'attname' } as ColumnNode,
|
|
113
|
+
{ type: 'Column', table: 'arr', name: 'idx', alias: 'ord' } as ColumnNode
|
|
114
|
+
],
|
|
115
|
+
joins: [
|
|
116
|
+
// JOIN pg_class AS tbl ON tbl.oid = i.indrelid
|
|
117
|
+
{
|
|
118
|
+
type: 'Join',
|
|
119
|
+
kind: 'INNER',
|
|
120
|
+
table: { type: 'Table', name: 'pg_class', schema: 'pg_catalog', alias: 'tbl' } as TableNode,
|
|
121
|
+
condition: eq({ table: 'tbl', name: 'oid' }, { table: 'i', name: 'indrelid' }) as ExpressionNode
|
|
122
|
+
} as JoinNode,
|
|
123
|
+
// JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace
|
|
124
|
+
{
|
|
125
|
+
type: 'Join',
|
|
126
|
+
kind: 'INNER',
|
|
127
|
+
table: { type: 'Table', name: 'pg_namespace', schema: 'pg_catalog', alias: 'ns' } as TableNode,
|
|
128
|
+
condition: eq({ table: 'ns', name: 'oid' }, { table: 'tbl', name: 'relnamespace' }) as ExpressionNode
|
|
129
|
+
} as JoinNode,
|
|
130
|
+
// JOIN pg_class AS idx ON idx.oid = i.indexrelid
|
|
131
|
+
{
|
|
132
|
+
type: 'Join',
|
|
133
|
+
kind: 'INNER',
|
|
134
|
+
table: { type: 'Table', name: 'pg_class', schema: 'pg_catalog', alias: 'idx' } as TableNode,
|
|
135
|
+
condition: eq({ table: 'idx', name: 'oid' }, { table: 'i', name: 'indexrelid' }) as ExpressionNode
|
|
136
|
+
} as JoinNode,
|
|
137
|
+
// LATERAL JOIN UNNEST(i.indkey) WITH ORDINALITY AS arr(attnum, idx)
|
|
138
|
+
{
|
|
139
|
+
type: 'Join',
|
|
140
|
+
kind: 'INNER',
|
|
141
|
+
table: fnTable('unnest', [{ type: 'Column', table: 'i', name: 'indkey' } as ColumnNode], 'arr', {
|
|
142
|
+
lateral: true,
|
|
143
|
+
withOrdinality: true,
|
|
144
|
+
columnAliases: ['attnum', 'idx']
|
|
145
|
+
}),
|
|
146
|
+
condition: { type: 'BinaryExpression', left: { type: 'Literal', value: 1 }, operator: '=', right: { type: 'Literal', value: 1 } } as unknown as ExpressionNode
|
|
147
|
+
} as JoinNode,
|
|
148
|
+
// LEFT JOIN pg_attribute AS att ON att.attrelid = tbl.oid AND att.attnum = arr.attnum
|
|
149
|
+
{
|
|
150
|
+
type: 'Join',
|
|
151
|
+
kind: 'LEFT',
|
|
152
|
+
table: { type: 'Table', name: 'pg_attribute', schema: 'pg_catalog', alias: 'att' } as TableNode,
|
|
153
|
+
condition: and(
|
|
154
|
+
eq({ table: 'att', name: 'attrelid' }, { table: 'tbl', name: 'oid' }),
|
|
155
|
+
eq({ table: 'att', name: 'attnum' }, { table: 'arr', name: 'attnum' })
|
|
156
|
+
) as ExpressionNode
|
|
157
|
+
} as JoinNode
|
|
158
|
+
],
|
|
159
|
+
where: and(
|
|
160
|
+
eq({ table: 'ns', name: 'nspname' }, schema) as ExpressionNode,
|
|
161
|
+
eq({ table: 'i', name: 'indisprimary' }, { type: 'Literal', value: false } as any) as ExpressionNode
|
|
162
|
+
) as ExpressionNode
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const indexQueryRows = await runSelectNode(indexQuery, ctx);
|
|
166
|
+
|
|
167
|
+
// Aggregate index rows by table/index to build final index list
|
|
168
|
+
const indexGrouped = new Map<string, { table_schema: string; table_name: string; index_name: string; is_unique: any; predicate: any; cols: { ord: number; att: string | null }[] }>();
|
|
169
|
+
for (const r of indexQueryRows) {
|
|
170
|
+
const key = `${r.table_schema}.${r.table_name}.${r.index_name}`;
|
|
171
|
+
const entry = indexGrouped.get(key) ?? {
|
|
172
|
+
table_schema: r.table_schema,
|
|
173
|
+
table_name: r.table_name,
|
|
174
|
+
index_name: r.index_name,
|
|
175
|
+
is_unique: r.is_unique,
|
|
176
|
+
predicate: r.predicate,
|
|
177
|
+
cols: []
|
|
178
|
+
};
|
|
179
|
+
entry.cols.push({ ord: r.ord ?? 0, att: r.attname ?? null });
|
|
180
|
+
indexGrouped.set(key, entry);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const indexRows = Array.from(indexGrouped.values()).map(v => ({
|
|
184
|
+
table_schema: v.table_schema,
|
|
185
|
+
table_name: v.table_name,
|
|
186
|
+
index_name: v.index_name,
|
|
187
|
+
is_unique: v.is_unique,
|
|
188
|
+
predicate: v.predicate,
|
|
189
|
+
column_names: v.cols.sort((a, b) => (a.ord || 0) - (b.ord || 0)).map(c => c.att).filter(Boolean)
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
// Build final schema
|
|
193
|
+
const tablesByKey = new Map<string, DatabaseTable>();
|
|
194
|
+
|
|
195
|
+
columnRows.forEach(r => {
|
|
196
|
+
const key = `${r.table_schema}.${r.table_name}`;
|
|
197
|
+
if (!shouldIncludeTable(r.table_name, options)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!tablesByKey.has(key)) {
|
|
201
|
+
tablesByKey.set(key, {
|
|
202
|
+
name: r.table_name,
|
|
203
|
+
schema: r.table_schema,
|
|
204
|
+
columns: [],
|
|
205
|
+
primaryKey: pkMap.get(key) || [],
|
|
206
|
+
indexes: []
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const cols = tablesByKey.get(key)!;
|
|
210
|
+
const fk = fkMap.get(`${r.table_schema}.${r.table_name}.${r.column_name}`)?.[0];
|
|
211
|
+
const column: DatabaseColumn = {
|
|
212
|
+
name: r.column_name,
|
|
213
|
+
type: r.data_type,
|
|
214
|
+
notNull: r.is_nullable === 'NO',
|
|
215
|
+
default: r.column_default ?? undefined,
|
|
216
|
+
references: fk
|
|
217
|
+
? {
|
|
218
|
+
table: fk.table,
|
|
219
|
+
column: fk.column,
|
|
220
|
+
onDelete: fk.onDelete,
|
|
221
|
+
onUpdate: fk.onUpdate
|
|
222
|
+
}
|
|
223
|
+
: undefined
|
|
224
|
+
};
|
|
225
|
+
cols.columns.push(column);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
indexRows.forEach(r => {
|
|
229
|
+
const key = `${r.table_schema}.${r.table_name}`;
|
|
230
|
+
const table = tablesByKey.get(key);
|
|
231
|
+
if (!table) return;
|
|
232
|
+
const idx: DatabaseIndex = {
|
|
233
|
+
name: r.index_name,
|
|
234
|
+
columns: (r.column_names || []).map((c: string) => ({ column: c })),
|
|
235
|
+
unique: !!r.is_unique,
|
|
236
|
+
where: r.predicate || undefined
|
|
237
|
+
};
|
|
238
|
+
table.indexes = table.indexes || [];
|
|
239
|
+
table.indexes.push(idx);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
tables.push(...tablesByKey.values());
|
|
243
|
+
return { tables };
|
|
244
|
+
}
|
|
245
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DialectName } from '../schema-generator.js';
|
|
2
|
+
import type { SchemaIntrospector } from './types.js';
|
|
3
|
+
import { postgresIntrospector } from './postgres.js';
|
|
4
|
+
import { mysqlIntrospector } from './mysql.js';
|
|
5
|
+
import { sqliteIntrospector } from './sqlite.js';
|
|
6
|
+
import { mssqlIntrospector } from './mssql.js';
|
|
7
|
+
|
|
8
|
+
const registry = new Map<DialectName, SchemaIntrospector>();
|
|
9
|
+
|
|
10
|
+
const registerBuiltInIntrospectors = () => {
|
|
11
|
+
registry.set('postgres', postgresIntrospector);
|
|
12
|
+
registry.set('mysql', mysqlIntrospector);
|
|
13
|
+
registry.set('sqlite', sqliteIntrospector);
|
|
14
|
+
registry.set('mssql', mssqlIntrospector);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
registerBuiltInIntrospectors();
|
|
18
|
+
|
|
19
|
+
export const registerSchemaIntrospector = (dialect: DialectName, introspector: SchemaIntrospector): void => {
|
|
20
|
+
registry.set(dialect, introspector);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getSchemaIntrospector = (dialect: DialectName): SchemaIntrospector | undefined => {
|
|
24
|
+
return registry.get(dialect);
|
|
25
|
+
};
|
|
26
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SelectQueryBuilder } from '../../../query-builder/select.js';
|
|
2
|
+
import type { IntrospectContext } from './context.js';
|
|
3
|
+
|
|
4
|
+
import { toRows } from './utils.js';
|
|
5
|
+
|
|
6
|
+
export async function runSelect<T = Record<string, any>>(
|
|
7
|
+
qb: SelectQueryBuilder<any, any>,
|
|
8
|
+
ctx: IntrospectContext
|
|
9
|
+
): Promise<T[]> {
|
|
10
|
+
const ast = qb.getAST();
|
|
11
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
12
|
+
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
13
|
+
// executor returns QueryResult[]; take the first result set and map to rows
|
|
14
|
+
const [first] = results;
|
|
15
|
+
return toRows(first) as T[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default runSelect;
|
|
19
|
+
|
|
20
|
+
export async function runSelectNode<T = Record<string, any>>(ast: any, ctx: IntrospectContext): Promise<T[]> {
|
|
21
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
22
|
+
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
23
|
+
const [first] = results;
|
|
24
|
+
return toRows(first) as T[];
|
|
25
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { SchemaIntrospector, IntrospectOptions } from './types.js';
|
|
2
2
|
import { queryRows, shouldIncludeTable } from './utils.js';
|
|
3
3
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex } from '../schema-types.js';
|
|
4
|
-
import { DbExecutor } from '../../
|
|
4
|
+
import { DbExecutor } from '../../execution/db-executor.js';
|
|
5
5
|
|
|
6
6
|
const escapeSingleQuotes = (name: string) => name.replace(/'/g, "''");
|
|
7
7
|
|
|
8
8
|
export const sqliteIntrospector: SchemaIntrospector = {
|
|
9
|
-
async introspect(executor: DbExecutor, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
9
|
+
async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
10
10
|
const tables: DatabaseTable[] = [];
|
|
11
11
|
const tableRows = await queryRows(
|
|
12
|
-
executor,
|
|
12
|
+
ctx.executor,
|
|
13
13
|
`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';`
|
|
14
14
|
);
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
18
18
|
if (!shouldIncludeTable(name, options)) continue;
|
|
19
19
|
const table: DatabaseTable = { name, columns: [], primaryKey: [], indexes: [] };
|
|
20
20
|
|
|
21
|
-
const cols = await queryRows(executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`);
|
|
21
|
+
const cols = await queryRows(ctx.executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`);
|
|
22
22
|
cols.forEach(c => {
|
|
23
23
|
table.columns.push({
|
|
24
24
|
name: c.name,
|
|
@@ -33,7 +33,7 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
const fkRows = await queryRows(executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`);
|
|
36
|
+
const fkRows = await queryRows(ctx.executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`);
|
|
37
37
|
fkRows.forEach(fk => {
|
|
38
38
|
const col = table.columns.find(c => c.name === fk.from);
|
|
39
39
|
if (col) {
|
|
@@ -46,10 +46,10 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
const idxList = await queryRows(executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`);
|
|
49
|
+
const idxList = await queryRows(ctx.executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`);
|
|
50
50
|
for (const idx of idxList) {
|
|
51
51
|
const idxName = idx.name as string;
|
|
52
|
-
const columnsInfo = await queryRows(executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`);
|
|
52
|
+
const columnsInfo = await queryRows(ctx.executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`);
|
|
53
53
|
const idxEntry: DatabaseIndex = {
|
|
54
54
|
name: idxName,
|
|
55
55
|
columns: columnsInfo.map(ci => ({ column: ci.name as string })),
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import type { DbExecutor } from '
|
|
2
|
-
import { DatabaseSchema } from '../schema-types.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import type { DbExecutor } from '../../execution/db-executor.js';
|
|
2
|
+
import { DatabaseSchema } from '../schema-types.js';
|
|
3
|
+
import type { IntrospectContext } from './context.js';
|
|
4
|
+
|
|
5
|
+
export type { IntrospectContext };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Dialect-agnostic options for schema introspection.
|
|
9
|
+
*/
|
|
10
|
+
export interface IntrospectOptions {
|
|
11
|
+
/** Dialect-specific schema/catalog. Postgres: schema; MySQL: database; MSSQL: schema. */
|
|
12
|
+
schema?: string;
|
|
13
|
+
includeTables?: string[];
|
|
14
|
+
excludeTables?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Strategy interface implemented per dialect to introspect an existing database schema.
|
|
19
|
+
*/
|
|
20
|
+
export interface SchemaIntrospector {
|
|
21
|
+
// Requires IntrospectContext with both dialect and executor
|
|
22
|
+
introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema>;
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DbExecutor, QueryResult } from '../../
|
|
1
|
+
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
2
|
import { IntrospectOptions } from './types.js';
|
|
3
3
|
|
|
4
4
|
export const toRows = (result: QueryResult | undefined): Record<string, any>[] => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TableDef, IndexDef } from '../../schema/table.js';
|
|
2
|
+
|
|
3
|
+
export const deriveIndexName = (table: TableDef, index: IndexDef): string => {
|
|
4
|
+
const base = (index.columns ?? [])
|
|
5
|
+
.map(col => (typeof col === 'string' ? col : col.column))
|
|
6
|
+
.join('_');
|
|
7
|
+
|
|
8
|
+
const suffix = index.unique ? 'uniq' : 'idx';
|
|
9
|
+
return `${table.name}_${base}_${suffix}`;
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TableDef, IndexDef } from '../../schema/table.js';
|
|
2
|
+
import type { ColumnDef, ForeignKeyReference } from '../../schema/column.js';
|
|
3
|
+
import type { DatabaseTable, DatabaseColumn, ColumnDiff } from './schema-types.js';
|
|
4
|
+
|
|
5
|
+
export type DialectName =
|
|
6
|
+
| 'postgres'
|
|
7
|
+
| 'mysql'
|
|
8
|
+
| 'sqlite'
|
|
9
|
+
| 'mssql'
|
|
10
|
+
| (string & {});
|
|
11
|
+
|
|
12
|
+
export interface SchemaDialect {
|
|
13
|
+
readonly name: DialectName;
|
|
14
|
+
|
|
15
|
+
// Minimal quoting surface; reusable for helpers
|
|
16
|
+
quoteIdentifier(id: string): string;
|
|
17
|
+
|
|
18
|
+
// Table naming
|
|
19
|
+
formatTableName(table: TableDef | DatabaseTable): string;
|
|
20
|
+
|
|
21
|
+
// Column rendering
|
|
22
|
+
renderColumnType(column: ColumnDef): string;
|
|
23
|
+
renderDefault(value: unknown, column: ColumnDef): string;
|
|
24
|
+
renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined;
|
|
25
|
+
|
|
26
|
+
// Constraints & indexes
|
|
27
|
+
renderReference(ref: ForeignKeyReference, table: TableDef): string;
|
|
28
|
+
renderIndex(table: TableDef, index: IndexDef): string;
|
|
29
|
+
renderTableOptions(table: TableDef): string | undefined;
|
|
30
|
+
|
|
31
|
+
// Capability flags
|
|
32
|
+
supportsPartialIndexes(): boolean;
|
|
33
|
+
|
|
34
|
+
// DDL operations
|
|
35
|
+
dropColumnSql?(table: DatabaseTable, column: string): string[];
|
|
36
|
+
dropIndexSql?(table: DatabaseTable, index: string): string[];
|
|
37
|
+
dropTableSql?(table: DatabaseTable): string[];
|
|
38
|
+
warnDropColumn?(table: DatabaseTable, column: string): string | undefined;
|
|
39
|
+
alterColumnSql?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[];
|
|
40
|
+
warnAlterColumn?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string | undefined;
|
|
41
|
+
}
|