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.
Files changed (115) hide show
  1. package/README.md +40 -45
  2. package/dist/decorators/index.cjs +1600 -27
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +6 -2
  5. package/dist/decorators/index.d.ts +6 -2
  6. package/dist/decorators/index.js +1599 -27
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +4608 -3429
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +511 -159
  11. package/dist/index.d.ts +511 -159
  12. package/dist/index.js +4526 -3415
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
  15. package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
  16. package/package.json +1 -1
  17. package/src/codegen/typescript.ts +38 -35
  18. package/src/core/ast/adapters.ts +21 -0
  19. package/src/core/ast/aggregate-functions.ts +13 -13
  20. package/src/core/ast/builders.ts +56 -43
  21. package/src/core/ast/expression-builders.ts +34 -34
  22. package/src/core/ast/expression-nodes.ts +18 -16
  23. package/src/core/ast/expression-visitor.ts +122 -69
  24. package/src/core/ast/expression.ts +6 -4
  25. package/src/core/ast/join-metadata.ts +15 -0
  26. package/src/core/ast/join-node.ts +22 -20
  27. package/src/core/ast/join.ts +5 -5
  28. package/src/core/ast/query.ts +52 -88
  29. package/src/core/ast/types.ts +20 -0
  30. package/src/core/ast/window-functions.ts +55 -55
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
  32. package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
  33. package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
  34. package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
  35. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
  36. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  37. package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
  38. package/src/core/ddl/introspect/context.ts +9 -0
  39. package/src/core/ddl/introspect/functions/postgres.ts +26 -0
  40. package/src/core/ddl/introspect/mssql.ts +149 -149
  41. package/src/core/ddl/introspect/mysql.ts +99 -99
  42. package/src/core/ddl/introspect/postgres.ts +245 -154
  43. package/src/core/ddl/introspect/registry.ts +26 -0
  44. package/src/core/ddl/introspect/run-select.ts +25 -0
  45. package/src/core/ddl/introspect/sqlite.ts +7 -7
  46. package/src/core/ddl/introspect/types.ts +23 -19
  47. package/src/core/ddl/introspect/utils.ts +1 -1
  48. package/src/core/ddl/naming-strategy.ts +10 -0
  49. package/src/core/ddl/schema-dialect.ts +41 -0
  50. package/src/core/ddl/schema-diff.ts +211 -179
  51. package/src/core/ddl/schema-generator.ts +16 -90
  52. package/src/core/ddl/schema-introspect.ts +25 -32
  53. package/src/core/ddl/schema-plan-executor.ts +17 -0
  54. package/src/core/ddl/schema-types.ts +46 -39
  55. package/src/core/ddl/sql-writing.ts +170 -0
  56. package/src/core/dialect/abstract.ts +144 -126
  57. package/src/core/dialect/base/cte-compiler.ts +33 -0
  58. package/src/core/dialect/base/function-table-formatter.ts +132 -0
  59. package/src/core/dialect/base/groupby-compiler.ts +21 -0
  60. package/src/core/dialect/base/join-compiler.ts +26 -0
  61. package/src/core/dialect/base/orderby-compiler.ts +21 -0
  62. package/src/core/dialect/base/pagination-strategy.ts +32 -0
  63. package/src/core/dialect/base/returning-strategy.ts +56 -0
  64. package/src/core/dialect/base/sql-dialect.ts +181 -204
  65. package/src/core/dialect/dialect-factory.ts +91 -0
  66. package/src/core/dialect/mssql/functions.ts +101 -0
  67. package/src/core/dialect/mssql/index.ts +128 -126
  68. package/src/core/dialect/mysql/functions.ts +101 -0
  69. package/src/core/dialect/mysql/index.ts +20 -18
  70. package/src/core/dialect/postgres/functions.ts +95 -0
  71. package/src/core/dialect/postgres/index.ts +30 -28
  72. package/src/core/dialect/sqlite/functions.ts +115 -0
  73. package/src/core/dialect/sqlite/index.ts +30 -28
  74. package/src/core/driver/database-driver.ts +11 -0
  75. package/src/core/driver/mssql-driver.ts +20 -0
  76. package/src/core/driver/mysql-driver.ts +20 -0
  77. package/src/core/driver/postgres-driver.ts +20 -0
  78. package/src/core/driver/sqlite-driver.ts +20 -0
  79. package/src/core/execution/db-executor.ts +63 -0
  80. package/src/core/execution/executors/mssql-executor.ts +39 -0
  81. package/src/core/execution/executors/mysql-executor.ts +47 -0
  82. package/src/core/execution/executors/postgres-executor.ts +32 -0
  83. package/src/core/execution/executors/sqlite-executor.ts +31 -0
  84. package/src/core/functions/datetime.ts +132 -0
  85. package/src/core/functions/numeric.ts +179 -0
  86. package/src/core/functions/standard-strategy.ts +47 -0
  87. package/src/core/functions/text.ts +147 -0
  88. package/src/core/functions/types.ts +18 -0
  89. package/src/core/hydration/types.ts +57 -0
  90. package/src/decorators/bootstrap.ts +10 -0
  91. package/src/decorators/relations.ts +15 -0
  92. package/src/index.ts +30 -19
  93. package/src/orm/entity-metadata.ts +7 -0
  94. package/src/orm/entity.ts +58 -27
  95. package/src/orm/hydration.ts +25 -17
  96. package/src/orm/lazy-batch.ts +46 -2
  97. package/src/orm/orm-context.ts +60 -60
  98. package/src/orm/query-logger.ts +1 -1
  99. package/src/orm/relation-change-processor.ts +43 -2
  100. package/src/orm/relations/has-one.ts +139 -0
  101. package/src/orm/transaction-runner.ts +1 -1
  102. package/src/orm/unit-of-work.ts +60 -60
  103. package/src/query-builder/delete.ts +22 -5
  104. package/src/query-builder/hydration-manager.ts +2 -1
  105. package/src/query-builder/hydration-planner.ts +8 -7
  106. package/src/query-builder/insert.ts +22 -5
  107. package/src/query-builder/relation-conditions.ts +9 -8
  108. package/src/query-builder/relation-service.ts +3 -2
  109. package/src/query-builder/select.ts +66 -61
  110. package/src/query-builder/update.ts +22 -5
  111. package/src/schema/column.ts +246 -246
  112. package/src/schema/relation.ts +35 -1
  113. package/src/schema/table.ts +28 -28
  114. package/src/schema/types.ts +41 -31
  115. 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 '../../orm/db-executor.js';
5
-
6
- export const postgresIntrospector: SchemaIntrospector = {
7
- async introspect(executor: DbExecutor, options: IntrospectOptions): Promise<DatabaseSchema> {
8
- const schema = options.schema || 'public';
9
- const tables: DatabaseTable[] = [];
10
-
11
- const columnRows = await queryRows(
12
- executor,
13
- `
14
- SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default
15
- FROM information_schema.columns
16
- WHERE table_schema = $1
17
- ORDER BY table_name, ordinal_position
18
- `,
19
- [schema]
20
- );
21
-
22
- const pkRows = await queryRows(
23
- executor,
24
- `
25
- SELECT
26
- ns.nspname AS table_schema,
27
- tbl.relname AS table_name,
28
- array_agg(att.attname ORDER BY arr.idx) AS pk_columns
29
- FROM pg_index i
30
- JOIN pg_class tbl ON tbl.oid = i.indrelid
31
- JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
32
- JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS arr(attnum, idx) ON TRUE
33
- LEFT JOIN pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = arr.attnum
34
- WHERE i.indisprimary AND ns.nspname = $1
35
- GROUP BY ns.nspname, tbl.relname
36
- `,
37
- [schema]
38
- );
39
-
40
- const pkMap = new Map<string, string[]>();
41
- pkRows.forEach(r => {
42
- pkMap.set(`${r.table_schema}.${r.table_name}`, r.pk_columns || []);
43
- });
44
-
45
- const fkRows = await queryRows(
46
- executor,
47
- `
48
- SELECT
49
- tc.table_schema,
50
- tc.table_name,
51
- kcu.column_name,
52
- ccu.table_schema AS foreign_table_schema,
53
- ccu.table_name AS foreign_table_name,
54
- ccu.column_name AS foreign_column_name,
55
- rc.update_rule AS on_update,
56
- rc.delete_rule AS on_delete
57
- FROM information_schema.table_constraints AS tc
58
- JOIN information_schema.key_column_usage AS kcu
59
- ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
60
- JOIN information_schema.constraint_column_usage AS ccu
61
- ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
62
- JOIN information_schema.referential_constraints rc
63
- ON rc.constraint_name = tc.constraint_name AND rc.constraint_schema = tc.table_schema
64
- WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = $1
65
- `,
66
- [schema]
67
- );
68
-
69
- const fkMap = new Map<string, any[]>();
70
- fkRows.forEach(r => {
71
- const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
72
- fkMap.set(key, [{
73
- table: `${r.foreign_table_schema}.${r.foreign_table_name}`,
74
- column: r.foreign_column_name,
75
- onDelete: r.on_delete?.toUpperCase(),
76
- onUpdate: r.on_update?.toUpperCase()
77
- }]);
78
- });
79
-
80
- const indexRows = await queryRows(
81
- executor,
82
- `
83
- SELECT
84
- ns.nspname AS table_schema,
85
- tbl.relname AS table_name,
86
- idx.relname AS index_name,
87
- i.indisunique AS is_unique,
88
- pg_get_expr(i.indpred, i.indrelid) AS predicate,
89
- array_agg(att.attname ORDER BY arr.idx) AS column_names
90
- FROM pg_index i
91
- JOIN pg_class tbl ON tbl.oid = i.indrelid
92
- JOIN pg_namespace ns ON ns.oid = tbl.relnamespace
93
- JOIN pg_class idx ON idx.oid = i.indexrelid
94
- JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS arr(attnum, idx) ON TRUE
95
- LEFT JOIN pg_attribute att ON att.attrelid = tbl.oid AND att.attnum = arr.attnum
96
- WHERE ns.nspname = $1 AND NOT i.indisprimary
97
- GROUP BY ns.nspname, tbl.relname, idx.relname, i.indisunique, i.indpred
98
- `,
99
- [schema]
100
- );
101
-
102
- const tablesByKey = new Map<string, DatabaseTable>();
103
-
104
- columnRows.forEach(r => {
105
- const key = `${r.table_schema}.${r.table_name}`;
106
- if (!shouldIncludeTable(r.table_name, options)) {
107
- return;
108
- }
109
- if (!tablesByKey.has(key)) {
110
- tablesByKey.set(key, {
111
- name: r.table_name,
112
- schema: r.table_schema,
113
- columns: [],
114
- primaryKey: pkMap.get(key) || [],
115
- indexes: []
116
- });
117
- }
118
- const cols = tablesByKey.get(key)!;
119
- const fk = fkMap.get(`${r.table_schema}.${r.table_name}.${r.column_name}`)?.[0];
120
- const column: DatabaseColumn = {
121
- name: r.column_name,
122
- type: r.data_type,
123
- notNull: r.is_nullable === 'NO',
124
- default: r.column_default ?? undefined,
125
- references: fk
126
- ? {
127
- table: fk.table,
128
- column: fk.column,
129
- onDelete: fk.onDelete,
130
- onUpdate: fk.onUpdate
131
- }
132
- : undefined
133
- };
134
- cols.columns.push(column);
135
- });
136
-
137
- indexRows.forEach(r => {
138
- const key = `${r.table_schema}.${r.table_name}`;
139
- const table = tablesByKey.get(key);
140
- if (!table) return;
141
- const idx: DatabaseIndex = {
142
- name: r.index_name,
143
- columns: (r.column_names || []).map((c: string) => ({ column: c })),
144
- unique: !!r.is_unique,
145
- where: r.predicate || undefined
146
- };
147
- table.indexes = table.indexes || [];
148
- table.indexes.push(idx);
149
- });
150
-
151
- tables.push(...tablesByKey.values());
152
- return { tables };
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 '../../orm/db-executor.js';
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 '../../../orm/db-executor.js';
2
- import { DatabaseSchema } from '../schema-types.js';
3
-
4
- /**
5
- * Dialect-agnostic options for schema introspection.
6
- */
7
- export interface IntrospectOptions {
8
- /** Dialect-specific schema/catalog. Postgres: schema; MySQL: database; MSSQL: schema. */
9
- schema?: string;
10
- includeTables?: string[];
11
- excludeTables?: string[];
12
- }
13
-
14
- /**
15
- * Strategy interface implemented per dialect to introspect an existing database schema.
16
- */
17
- export interface SchemaIntrospector {
18
- introspect(executor: DbExecutor, options: IntrospectOptions): Promise<DatabaseSchema>;
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 '../../orm/db-executor.js';
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
+ }