metal-orm 1.0.42 → 1.0.44
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 +195 -37
- package/dist/index.cjs +1014 -538
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1267 -371
- package/dist/index.d.ts +1267 -371
- package/dist/index.js +1012 -536
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/scripts/run-eslint.mjs +34 -0
- package/src/codegen/typescript.ts +32 -15
- package/src/core/ast/adapters.ts +8 -2
- package/src/core/ast/builders.ts +105 -76
- package/src/core/ast/expression-builders.ts +430 -392
- package/src/core/ast/expression-nodes.ts +14 -5
- package/src/core/ast/expression-visitor.ts +56 -14
- package/src/core/ast/helpers.ts +23 -0
- package/src/core/ast/join-node.ts +18 -2
- package/src/core/ast/query.ts +6 -6
- package/src/core/ast/window-functions.ts +10 -2
- package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
- package/src/core/ddl/dialects/index.ts +1 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
- package/src/core/ddl/dialects/render-reference.test.ts +69 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
- package/src/core/ddl/introspect/context.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +53 -8
- package/src/core/ddl/introspect/mysql.ts +32 -6
- package/src/core/ddl/introspect/postgres.ts +102 -34
- package/src/core/ddl/introspect/registry.ts +14 -0
- package/src/core/ddl/introspect/run-select.ts +19 -4
- package/src/core/ddl/introspect/sqlite.ts +78 -11
- package/src/core/ddl/introspect/types.ts +0 -1
- package/src/core/ddl/introspect/utils.ts +21 -3
- package/src/core/ddl/naming-strategy.ts +6 -0
- package/src/core/ddl/schema-dialect.ts +20 -6
- package/src/core/ddl/schema-diff.ts +22 -0
- package/src/core/ddl/schema-generator.ts +26 -12
- package/src/core/ddl/schema-plan-executor.ts +6 -0
- package/src/core/ddl/schema-types.ts +6 -0
- package/src/core/ddl/sql-writing.ts +4 -4
- package/src/core/dialect/abstract.ts +19 -7
- package/src/core/dialect/base/function-table-formatter.ts +3 -2
- package/src/core/dialect/base/join-compiler.ts +5 -3
- package/src/core/dialect/base/returning-strategy.ts +1 -0
- package/src/core/dialect/base/sql-dialect.ts +3 -3
- package/src/core/dialect/mssql/functions.ts +24 -25
- package/src/core/dialect/mssql/index.ts +1 -4
- package/src/core/dialect/mysql/functions.ts +0 -1
- package/src/core/dialect/postgres/functions.ts +33 -34
- package/src/core/dialect/postgres/index.ts +1 -0
- package/src/core/dialect/sqlite/functions.ts +18 -19
- package/src/core/dialect/sqlite/index.ts +2 -0
- package/src/core/execution/db-executor.ts +1 -1
- package/src/core/execution/executors/mysql-executor.ts +2 -2
- package/src/core/execution/executors/postgres-executor.ts +1 -1
- package/src/core/execution/pooling/pool.ts +12 -5
- package/src/core/functions/datetime.ts +58 -34
- package/src/core/functions/numeric.ts +96 -31
- package/src/core/functions/standard-strategy.ts +35 -0
- package/src/core/functions/text.ts +84 -23
- package/src/core/functions/types.ts +23 -8
- package/src/decorators/bootstrap.ts +42 -11
- package/src/decorators/column.ts +20 -11
- package/src/decorators/decorator-metadata.ts +30 -9
- package/src/decorators/entity.ts +29 -5
- package/src/decorators/index.ts +3 -0
- package/src/decorators/relations.ts +34 -11
- package/src/orm/als.ts +34 -9
- package/src/orm/entity-context.ts +62 -8
- package/src/orm/entity-meta.ts +8 -8
- package/src/orm/entity-metadata.ts +131 -16
- package/src/orm/entity.ts +28 -29
- package/src/orm/execute.ts +19 -4
- package/src/orm/hydration.ts +42 -39
- package/src/orm/identity-map.ts +1 -1
- package/src/orm/lazy-batch.ts +74 -104
- package/src/orm/orm-session.ts +24 -23
- package/src/orm/orm.ts +2 -5
- package/src/orm/relation-change-processor.ts +12 -11
- package/src/orm/relations/belongs-to.ts +11 -11
- package/src/orm/relations/has-many.ts +54 -10
- package/src/orm/relations/has-one.ts +8 -7
- package/src/orm/relations/many-to-many.ts +13 -13
- package/src/orm/runtime-types.ts +4 -4
- package/src/orm/save-graph.ts +31 -25
- package/src/orm/unit-of-work.ts +17 -17
- package/src/query/index.ts +74 -0
- package/src/query/target.ts +46 -0
- package/src/query-builder/delete-query-state.ts +30 -0
- package/src/query-builder/delete.ts +64 -18
- package/src/query-builder/hydration-manager.ts +52 -5
- package/src/query-builder/insert-query-state.ts +30 -0
- package/src/query-builder/insert.ts +58 -10
- package/src/query-builder/query-ast-service.ts +7 -2
- package/src/query-builder/query-resolution.ts +78 -0
- package/src/query-builder/raw-column-parser.ts +7 -1
- package/src/query-builder/relation-alias.ts +7 -0
- package/src/query-builder/relation-conditions.ts +61 -48
- package/src/query-builder/relation-service.ts +68 -63
- package/src/query-builder/relation-utils.ts +3 -0
- package/src/query-builder/select/cte-facet.ts +40 -0
- package/src/query-builder/select/from-facet.ts +80 -0
- package/src/query-builder/select/join-facet.ts +62 -0
- package/src/query-builder/select/predicate-facet.ts +103 -0
- package/src/query-builder/select/projection-facet.ts +69 -0
- package/src/query-builder/select/relation-facet.ts +81 -0
- package/src/query-builder/select/setop-facet.ts +36 -0
- package/src/query-builder/select-helpers.ts +15 -2
- package/src/query-builder/select-query-builder-deps.ts +19 -1
- package/src/query-builder/select-query-state.ts +2 -1
- package/src/query-builder/select.ts +795 -1163
- package/src/query-builder/update-query-state.ts +52 -0
- package/src/query-builder/update.ts +69 -18
- package/src/schema/column.ts +26 -26
- package/src/schema/table-guards.ts +31 -0
- package/src/schema/table.ts +47 -18
- package/src/schema/types.ts +22 -22
|
@@ -3,13 +3,58 @@ import { queryRows, shouldIncludeTable } from './utils.js';
|
|
|
3
3
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
4
4
|
import { DbExecutor } from '../../execution/db-executor.js';
|
|
5
5
|
|
|
6
|
+
/** Row type for MSSQL column information. */
|
|
7
|
+
type MssqlColumnRow = {
|
|
8
|
+
table_schema: string;
|
|
9
|
+
table_name: string;
|
|
10
|
+
column_name: string;
|
|
11
|
+
data_type: string;
|
|
12
|
+
is_nullable: boolean | number;
|
|
13
|
+
is_identity: boolean | number;
|
|
14
|
+
column_default: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Row type for MSSQL primary key information. */
|
|
18
|
+
type MssqlPrimaryKeyRow = {
|
|
19
|
+
table_schema: string;
|
|
20
|
+
table_name: string;
|
|
21
|
+
column_name: string;
|
|
22
|
+
key_ordinal: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Row type for MSSQL index information. */
|
|
26
|
+
type MssqlIndexRow = {
|
|
27
|
+
table_schema: string;
|
|
28
|
+
table_name: string;
|
|
29
|
+
index_name: string;
|
|
30
|
+
is_unique: boolean | number;
|
|
31
|
+
has_filter: boolean | number;
|
|
32
|
+
filter_definition: string | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Row type for MSSQL index column information. */
|
|
36
|
+
type MssqlIndexColumnRow = {
|
|
37
|
+
table_schema: string;
|
|
38
|
+
table_name: string;
|
|
39
|
+
index_name: string;
|
|
40
|
+
column_name: string;
|
|
41
|
+
key_ordinal: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** MSSQL schema introspector implementation. */
|
|
6
45
|
export const mssqlIntrospector: SchemaIntrospector = {
|
|
46
|
+
/**
|
|
47
|
+
* Introspects the MSSQL database schema.
|
|
48
|
+
* @param ctx - The introspection context containing the database executor.
|
|
49
|
+
* @param options - Options for introspection, such as schema filter.
|
|
50
|
+
* @returns A promise that resolves to the introspected database schema.
|
|
51
|
+
*/
|
|
7
52
|
async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
8
53
|
const schema = options.schema;
|
|
9
54
|
const filterSchema = schema ? 'sch.name = @p1' : '1=1';
|
|
10
55
|
const params = schema ? [schema] : [];
|
|
11
56
|
|
|
12
|
-
const columnRows = await queryRows(
|
|
57
|
+
const columnRows = (await queryRows(
|
|
13
58
|
ctx.executor,
|
|
14
59
|
`
|
|
15
60
|
SELECT
|
|
@@ -27,9 +72,9 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
27
72
|
WHERE t.is_ms_shipped = 0 AND ${filterSchema}
|
|
28
73
|
`,
|
|
29
74
|
params
|
|
30
|
-
);
|
|
75
|
+
)) as MssqlColumnRow[];
|
|
31
76
|
|
|
32
|
-
const pkRows = await queryRows(
|
|
77
|
+
const pkRows = (await queryRows(
|
|
33
78
|
ctx.executor,
|
|
34
79
|
`
|
|
35
80
|
SELECT
|
|
@@ -46,7 +91,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
46
91
|
ORDER BY ic.key_ordinal
|
|
47
92
|
`,
|
|
48
93
|
params
|
|
49
|
-
);
|
|
94
|
+
)) as MssqlPrimaryKeyRow[];
|
|
50
95
|
|
|
51
96
|
const pkMap = new Map<string, string[]>();
|
|
52
97
|
pkRows.forEach(r => {
|
|
@@ -56,7 +101,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
56
101
|
pkMap.set(key, list);
|
|
57
102
|
});
|
|
58
103
|
|
|
59
|
-
const indexRows = await queryRows(
|
|
104
|
+
const indexRows = (await queryRows(
|
|
60
105
|
ctx.executor,
|
|
61
106
|
`
|
|
62
107
|
SELECT
|
|
@@ -72,9 +117,9 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
72
117
|
WHERE i.is_primary_key = 0 AND i.is_hypothetical = 0 AND ${filterSchema}
|
|
73
118
|
`,
|
|
74
119
|
params
|
|
75
|
-
);
|
|
120
|
+
)) as MssqlIndexRow[];
|
|
76
121
|
|
|
77
|
-
const indexColsRows = await queryRows(
|
|
122
|
+
const indexColsRows = (await queryRows(
|
|
78
123
|
ctx.executor,
|
|
79
124
|
`
|
|
80
125
|
SELECT
|
|
@@ -92,7 +137,7 @@ export const mssqlIntrospector: SchemaIntrospector = {
|
|
|
92
137
|
ORDER BY ic.key_ordinal
|
|
93
138
|
`,
|
|
94
139
|
params
|
|
95
|
-
);
|
|
140
|
+
)) as MssqlIndexColumnRow[];
|
|
96
141
|
|
|
97
142
|
const indexColumnsMap = new Map<string, { column: string; order: number }[]>();
|
|
98
143
|
indexColsRows.forEach(r => {
|
|
@@ -3,13 +3,39 @@ import { queryRows, shouldIncludeTable } from './utils.js';
|
|
|
3
3
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
4
4
|
import { DbExecutor } from '../../execution/db-executor.js';
|
|
5
5
|
|
|
6
|
+
/** Row type for MySQL column information. */
|
|
7
|
+
type MysqlColumnRow = {
|
|
8
|
+
table_schema: string;
|
|
9
|
+
table_name: string;
|
|
10
|
+
column_name: string;
|
|
11
|
+
data_type: string;
|
|
12
|
+
is_nullable: string;
|
|
13
|
+
column_default: string | null;
|
|
14
|
+
extra: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type MysqlPrimaryKeyRow = {
|
|
18
|
+
table_schema: string;
|
|
19
|
+
table_name: string;
|
|
20
|
+
column_name: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type MysqlIndexRow = {
|
|
24
|
+
table_schema: string;
|
|
25
|
+
table_name: string;
|
|
26
|
+
index_name: string;
|
|
27
|
+
non_unique: number;
|
|
28
|
+
cols: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** MySQL schema introspector. */
|
|
6
32
|
export const mysqlIntrospector: SchemaIntrospector = {
|
|
7
33
|
async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
8
34
|
const schema = options.schema;
|
|
9
35
|
const filterClause = schema ? 'table_schema = ?' : 'table_schema = database()';
|
|
10
36
|
const params = schema ? [schema] : [];
|
|
11
37
|
|
|
12
|
-
const columnRows = await queryRows(
|
|
38
|
+
const columnRows = (await queryRows(
|
|
13
39
|
ctx.executor,
|
|
14
40
|
`
|
|
15
41
|
SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, extra
|
|
@@ -18,9 +44,9 @@ export const mysqlIntrospector: SchemaIntrospector = {
|
|
|
18
44
|
ORDER BY table_name, ordinal_position
|
|
19
45
|
`,
|
|
20
46
|
params
|
|
21
|
-
);
|
|
47
|
+
)) as MysqlColumnRow[];
|
|
22
48
|
|
|
23
|
-
const pkRows = await queryRows(
|
|
49
|
+
const pkRows = (await queryRows(
|
|
24
50
|
ctx.executor,
|
|
25
51
|
`
|
|
26
52
|
SELECT table_schema, table_name, column_name
|
|
@@ -29,7 +55,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
|
|
|
29
55
|
ORDER BY ordinal_position
|
|
30
56
|
`,
|
|
31
57
|
params
|
|
32
|
-
);
|
|
58
|
+
)) as MysqlPrimaryKeyRow[];
|
|
33
59
|
|
|
34
60
|
const pkMap = new Map<string, string[]>();
|
|
35
61
|
pkRows.forEach(r => {
|
|
@@ -39,7 +65,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
|
|
|
39
65
|
pkMap.set(key, list);
|
|
40
66
|
});
|
|
41
67
|
|
|
42
|
-
const indexRows = await queryRows(
|
|
68
|
+
const indexRows = (await queryRows(
|
|
43
69
|
ctx.executor,
|
|
44
70
|
`
|
|
45
71
|
SELECT
|
|
@@ -53,7 +79,7 @@ export const mysqlIntrospector: SchemaIntrospector = {
|
|
|
53
79
|
GROUP BY table_schema, table_name, index_name, non_unique
|
|
54
80
|
`,
|
|
55
81
|
params
|
|
56
|
-
);
|
|
82
|
+
)) as MysqlIndexRow[];
|
|
57
83
|
|
|
58
84
|
const tablesByKey = new Map<string, DatabaseTable>();
|
|
59
85
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SchemaIntrospector, IntrospectOptions } from './types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { shouldIncludeTable } from './utils.js';
|
|
3
3
|
import { DatabaseSchema, DatabaseTable, DatabaseIndex, DatabaseColumn } from '../schema-types.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ReferentialAction } from '../../../schema/column.js';
|
|
5
5
|
import type { IntrospectContext } from './context.js';
|
|
6
6
|
import { PgInformationSchemaColumns } from './catalogs/postgres.js';
|
|
7
|
-
import { PgKeyColumnUsage, PgTableConstraints, PgConstraintColumnUsage, PgReferentialConstraints
|
|
7
|
+
import { PgKeyColumnUsage, PgTableConstraints, PgConstraintColumnUsage, PgReferentialConstraints } from './catalogs/postgres.js';
|
|
8
8
|
import { SelectQueryBuilder } from '../../../query-builder/select.js';
|
|
9
9
|
import { eq, and } from '../../ast/expression-builders.js';
|
|
10
10
|
import type { SelectQueryNode, TableNode } from '../../ast/query.js';
|
|
@@ -13,13 +13,81 @@ import type { ColumnNode, ExpressionNode } from '../../ast/expression-nodes.js';
|
|
|
13
13
|
import { fnTable } from '../../ast/builders.js';
|
|
14
14
|
import { runSelect, runSelectNode } from './run-select.js';
|
|
15
15
|
|
|
16
|
+
/** Row type for PostgreSQL column introspection from information_schema.columns. */
|
|
17
|
+
type ColumnIntrospectRow = {
|
|
18
|
+
table_schema: string;
|
|
19
|
+
table_name: string;
|
|
20
|
+
column_name: string;
|
|
21
|
+
data_type: string;
|
|
22
|
+
is_nullable: string;
|
|
23
|
+
column_default: string | null;
|
|
24
|
+
ordinal_position: number | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Row type for PostgreSQL primary key introspection from key_column_usage and table_constraints. */
|
|
28
|
+
type PrimaryKeyIntrospectRow = {
|
|
29
|
+
table_schema: string;
|
|
30
|
+
table_name: string;
|
|
31
|
+
column_name: string;
|
|
32
|
+
ordinal_position: number | null;
|
|
33
|
+
constraint_name: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Row type for PostgreSQL foreign key introspection from various constraint tables. */
|
|
37
|
+
type ForeignKeyIntrospectRow = {
|
|
38
|
+
table_schema: string;
|
|
39
|
+
table_name: string;
|
|
40
|
+
column_name: string;
|
|
41
|
+
constraint_name: string;
|
|
42
|
+
foreign_table_schema: string;
|
|
43
|
+
foreign_table_name: string;
|
|
44
|
+
foreign_column_name: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Represents a foreign key reference entry with optional referential actions. */
|
|
48
|
+
type ForeignKeyEntry = {
|
|
49
|
+
table: string;
|
|
50
|
+
column: string;
|
|
51
|
+
onDelete?: ReferentialAction;
|
|
52
|
+
onUpdate?: ReferentialAction;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Row type for PostgreSQL index query results from pg_catalog tables. */
|
|
56
|
+
type IndexQueryRow = {
|
|
57
|
+
table_schema: string;
|
|
58
|
+
table_name: string;
|
|
59
|
+
index_name: string;
|
|
60
|
+
is_unique: boolean;
|
|
61
|
+
predicate: string | null;
|
|
62
|
+
attname: string | null;
|
|
63
|
+
ord: number | null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Grouped index information used for aggregating index columns. */
|
|
67
|
+
type IndexGroup = {
|
|
68
|
+
table_schema: string;
|
|
69
|
+
table_name: string;
|
|
70
|
+
index_name: string;
|
|
71
|
+
is_unique: boolean;
|
|
72
|
+
predicate: string | null;
|
|
73
|
+
cols: { ord: number; att: string | null }[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** PostgreSQL schema introspector. */
|
|
16
77
|
export const postgresIntrospector: SchemaIntrospector = {
|
|
78
|
+
/**
|
|
79
|
+
* Introspects the PostgreSQL database schema by querying information_schema and pg_catalog.
|
|
80
|
+
* Builds tables with columns, primary keys, foreign keys, and indexes.
|
|
81
|
+
* @param ctx - The introspection context with database executor.
|
|
82
|
+
* @param options - Options for schema selection and table filtering.
|
|
83
|
+
* @returns A promise resolving to the complete database schema.
|
|
84
|
+
*/
|
|
17
85
|
async introspect(ctx: IntrospectContext, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
18
86
|
const schema = options.schema || 'public';
|
|
19
87
|
const tables: DatabaseTable[] = [];
|
|
20
88
|
|
|
21
89
|
// Columns query
|
|
22
|
-
const qbColumns = new SelectQueryBuilder(PgInformationSchemaColumns
|
|
90
|
+
const qbColumns = new SelectQueryBuilder(PgInformationSchemaColumns)
|
|
23
91
|
.select({
|
|
24
92
|
table_schema: PgInformationSchemaColumns.columns.table_schema,
|
|
25
93
|
table_name: PgInformationSchemaColumns.columns.table_name,
|
|
@@ -33,10 +101,10 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
33
101
|
.orderBy(PgInformationSchemaColumns.columns.table_name)
|
|
34
102
|
.orderBy(PgInformationSchemaColumns.columns.ordinal_position);
|
|
35
103
|
|
|
36
|
-
const columnRows = await runSelect(qbColumns, ctx);
|
|
104
|
+
const columnRows = await runSelect<ColumnIntrospectRow>(qbColumns, ctx);
|
|
37
105
|
|
|
38
106
|
// Primary key columns query
|
|
39
|
-
const qbPk = new SelectQueryBuilder(PgKeyColumnUsage
|
|
107
|
+
const qbPk = new SelectQueryBuilder(PgKeyColumnUsage)
|
|
40
108
|
.select({
|
|
41
109
|
table_schema: PgKeyColumnUsage.columns.table_schema,
|
|
42
110
|
table_name: PgKeyColumnUsage.columns.table_name,
|
|
@@ -44,13 +112,13 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
44
112
|
ordinal_position: PgKeyColumnUsage.columns.ordinal_position,
|
|
45
113
|
constraint_name: PgKeyColumnUsage.columns.constraint_name
|
|
46
114
|
})
|
|
47
|
-
.innerJoin(PgTableConstraints
|
|
115
|
+
.innerJoin(PgTableConstraints, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
|
|
48
116
|
.where(eq(PgTableConstraints.columns.constraint_type, 'PRIMARY KEY'))
|
|
49
117
|
.where(eq(PgKeyColumnUsage.columns.table_schema, schema))
|
|
50
118
|
.orderBy(PgKeyColumnUsage.columns.table_name)
|
|
51
119
|
.orderBy(PgKeyColumnUsage.columns.ordinal_position);
|
|
52
120
|
|
|
53
|
-
const pkRows = await runSelect(qbPk, ctx);
|
|
121
|
+
const pkRows = await runSelect<PrimaryKeyIntrospectRow>(qbPk, ctx);
|
|
54
122
|
|
|
55
123
|
// Build primary key map (grouped by table, ordered by ordinal_position)
|
|
56
124
|
const pkMap = new Map<string, string[]>();
|
|
@@ -67,7 +135,7 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
67
135
|
}
|
|
68
136
|
|
|
69
137
|
// Foreign key columns query
|
|
70
|
-
const qbFk = new SelectQueryBuilder(PgKeyColumnUsage
|
|
138
|
+
const qbFk = new SelectQueryBuilder(PgKeyColumnUsage)
|
|
71
139
|
.select({
|
|
72
140
|
table_schema: PgKeyColumnUsage.columns.table_schema,
|
|
73
141
|
table_name: PgKeyColumnUsage.columns.table_name,
|
|
@@ -77,16 +145,16 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
77
145
|
foreign_table_name: PgConstraintColumnUsage.columns.table_name,
|
|
78
146
|
foreign_column_name: PgConstraintColumnUsage.columns.column_name
|
|
79
147
|
})
|
|
80
|
-
.innerJoin(PgTableConstraints
|
|
81
|
-
.innerJoin(PgConstraintColumnUsage
|
|
82
|
-
.innerJoin(PgReferentialConstraints
|
|
148
|
+
.innerJoin(PgTableConstraints, eq(PgTableConstraints.columns.constraint_name, PgKeyColumnUsage.columns.constraint_name))
|
|
149
|
+
.innerJoin(PgConstraintColumnUsage, eq(PgConstraintColumnUsage.columns.constraint_name, PgTableConstraints.columns.constraint_name))
|
|
150
|
+
.innerJoin(PgReferentialConstraints, eq(PgReferentialConstraints.columns.constraint_name, PgTableConstraints.columns.constraint_name))
|
|
83
151
|
.where(eq(PgTableConstraints.columns.constraint_type, 'FOREIGN KEY'))
|
|
84
152
|
.where(eq(PgKeyColumnUsage.columns.table_schema, schema));
|
|
85
153
|
|
|
86
|
-
const fkRows = await runSelect(qbFk, ctx);
|
|
154
|
+
const fkRows = await runSelect<ForeignKeyIntrospectRow>(qbFk, ctx);
|
|
87
155
|
|
|
88
156
|
// Build foreign key map
|
|
89
|
-
const fkMap = new Map<string,
|
|
157
|
+
const fkMap = new Map<string, ForeignKeyEntry[]>();
|
|
90
158
|
for (const r of fkRows) {
|
|
91
159
|
const key = `${r.table_schema}.${r.table_name}.${r.column_name}`;
|
|
92
160
|
const existing = fkMap.get(key) ?? [];
|
|
@@ -138,10 +206,10 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
138
206
|
{
|
|
139
207
|
type: 'Join',
|
|
140
208
|
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']
|
|
209
|
+
table: fnTable('unnest', [{ type: 'Column', table: 'i', name: 'indkey' } as ColumnNode], 'arr', {
|
|
210
|
+
lateral: true,
|
|
211
|
+
withOrdinality: true,
|
|
212
|
+
columnAliases: ['attnum', 'idx']
|
|
145
213
|
}),
|
|
146
214
|
condition: { type: 'BinaryExpression', left: { type: 'Literal', value: 1 }, operator: '=', right: { type: 'Literal', value: 1 } } as unknown as ExpressionNode
|
|
147
215
|
} as JoinNode,
|
|
@@ -158,23 +226,23 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
158
226
|
],
|
|
159
227
|
where: and(
|
|
160
228
|
eq({ table: 'ns', name: 'nspname' }, schema) as ExpressionNode,
|
|
161
|
-
eq({ table: 'i', name: 'indisprimary' },
|
|
229
|
+
eq({ table: 'i', name: 'indisprimary' }, false) as ExpressionNode
|
|
162
230
|
) as ExpressionNode
|
|
163
231
|
};
|
|
164
232
|
|
|
165
|
-
const indexQueryRows = await runSelectNode(indexQuery, ctx);
|
|
166
|
-
|
|
233
|
+
const indexQueryRows = await runSelectNode<IndexQueryRow>(indexQuery, ctx);
|
|
234
|
+
|
|
167
235
|
// Aggregate index rows by table/index to build final index list
|
|
168
|
-
const indexGrouped = new Map<string,
|
|
236
|
+
const indexGrouped = new Map<string, IndexGroup>();
|
|
169
237
|
for (const r of indexQueryRows) {
|
|
170
238
|
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: []
|
|
239
|
+
const entry = indexGrouped.get(key) ?? {
|
|
240
|
+
table_schema: r.table_schema,
|
|
241
|
+
table_name: r.table_name,
|
|
242
|
+
index_name: r.index_name,
|
|
243
|
+
is_unique: r.is_unique,
|
|
244
|
+
predicate: r.predicate,
|
|
245
|
+
cols: []
|
|
178
246
|
};
|
|
179
247
|
entry.cols.push({ ord: r.ord ?? 0, att: r.attname ?? null });
|
|
180
248
|
indexGrouped.set(key, entry);
|
|
@@ -215,11 +283,11 @@ export const postgresIntrospector: SchemaIntrospector = {
|
|
|
215
283
|
default: r.column_default ?? undefined,
|
|
216
284
|
references: fk
|
|
217
285
|
? {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
286
|
+
table: fk.table,
|
|
287
|
+
column: fk.column,
|
|
288
|
+
onDelete: fk.onDelete,
|
|
289
|
+
onUpdate: fk.onUpdate
|
|
290
|
+
}
|
|
223
291
|
: undefined
|
|
224
292
|
};
|
|
225
293
|
cols.columns.push(column);
|
|
@@ -5,8 +5,12 @@ import { mysqlIntrospector } from './mysql.js';
|
|
|
5
5
|
import { sqliteIntrospector } from './sqlite.js';
|
|
6
6
|
import { mssqlIntrospector } from './mssql.js';
|
|
7
7
|
|
|
8
|
+
/** Registry mapping dialect names to their corresponding schema introspectors. */
|
|
8
9
|
const registry = new Map<DialectName, SchemaIntrospector>();
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Registers the built-in schema introspectors for all supported database dialects.
|
|
13
|
+
*/
|
|
10
14
|
const registerBuiltInIntrospectors = () => {
|
|
11
15
|
registry.set('postgres', postgresIntrospector);
|
|
12
16
|
registry.set('mysql', mysqlIntrospector);
|
|
@@ -16,10 +20,20 @@ const registerBuiltInIntrospectors = () => {
|
|
|
16
20
|
|
|
17
21
|
registerBuiltInIntrospectors();
|
|
18
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Registers a schema introspector for a dialect.
|
|
25
|
+
* @param dialect - The dialect name.
|
|
26
|
+
* @param introspector - The schema introspector.
|
|
27
|
+
*/
|
|
19
28
|
export const registerSchemaIntrospector = (dialect: DialectName, introspector: SchemaIntrospector): void => {
|
|
20
29
|
registry.set(dialect, introspector);
|
|
21
30
|
};
|
|
22
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Gets the schema introspector for a dialect.
|
|
34
|
+
* @param dialect - The dialect name.
|
|
35
|
+
* @returns The schema introspector or undefined if not found.
|
|
36
|
+
*/
|
|
23
37
|
export const getSchemaIntrospector = (dialect: DialectName): SchemaIntrospector | undefined => {
|
|
24
38
|
return registry.get(dialect);
|
|
25
39
|
};
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import type { SelectQueryBuilder } from '../../../query-builder/select.js';
|
|
2
1
|
import type { IntrospectContext } from './context.js';
|
|
2
|
+
import type { SelectQueryNode } from '../../ast/query.js';
|
|
3
3
|
|
|
4
4
|
import { toRows } from './utils.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/** A source that can provide a select query AST. */
|
|
7
|
+
type SelectQuerySource = { getAST(): SelectQueryNode };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runs a select query from a query builder and returns the results.
|
|
11
|
+
* @param qb - The query builder.
|
|
12
|
+
* @param ctx - The introspection context.
|
|
13
|
+
* @returns The query results.
|
|
14
|
+
*/
|
|
15
|
+
export async function runSelect<T = Record<string, unknown>>(
|
|
16
|
+
qb: SelectQuerySource,
|
|
8
17
|
ctx: IntrospectContext
|
|
9
18
|
): Promise<T[]> {
|
|
10
19
|
const ast = qb.getAST();
|
|
@@ -17,7 +26,13 @@ export async function runSelect<T = Record<string, any>>(
|
|
|
17
26
|
|
|
18
27
|
export default runSelect;
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Runs a select query from an AST node and returns the results.
|
|
31
|
+
* @param ast - The select query AST.
|
|
32
|
+
* @param ctx - The introspection context.
|
|
33
|
+
* @returns The query results.
|
|
34
|
+
*/
|
|
35
|
+
export async function runSelectNode<T = Record<string, unknown>>(ast: SelectQueryNode, ctx: IntrospectContext): Promise<T[]> {
|
|
21
36
|
const compiled = ctx.dialect.compileSelect(ast);
|
|
22
37
|
const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
23
38
|
const [first] = results;
|
|
@@ -1,24 +1,91 @@
|
|
|
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 { ReferentialAction } from '../../../schema/column.js';
|
|
4
5
|
import { DbExecutor } from '../../execution/db-executor.js';
|
|
5
6
|
|
|
7
|
+
/** Row type for SQLite table list from sqlite_master. */
|
|
8
|
+
type SqliteTableRow = {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Row type for SQLite table column information from PRAGMA table_info. */
|
|
13
|
+
type SqliteTableInfoRow = {
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
notnull: number;
|
|
17
|
+
dflt_value: string | null;
|
|
18
|
+
pk: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Row type for SQLite foreign key information from PRAGMA foreign_key_list. */
|
|
22
|
+
type SqliteForeignKeyRow = {
|
|
23
|
+
table: string;
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
on_delete: string | null;
|
|
27
|
+
on_update: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Row type for SQLite index list from PRAGMA index_list. */
|
|
31
|
+
type SqliteIndexListRow = {
|
|
32
|
+
name: string;
|
|
33
|
+
unique: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Row type for SQLite index column information from PRAGMA index_info. */
|
|
37
|
+
type SqliteIndexInfoRow = {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts a SQLite referential action string to a ReferentialAction enum value.
|
|
43
|
+
* @param value - The string value from SQLite pragma (e.g., 'CASCADE', 'SET NULL').
|
|
44
|
+
* @returns The corresponding ReferentialAction enum value, or undefined if the value is invalid or null.
|
|
45
|
+
*/
|
|
46
|
+
const toReferentialAction = (value: string | null | undefined): ReferentialAction | undefined => {
|
|
47
|
+
if (!value) return undefined;
|
|
48
|
+
const normalized = value.toUpperCase();
|
|
49
|
+
if (
|
|
50
|
+
normalized === 'NO ACTION' ||
|
|
51
|
+
normalized === 'RESTRICT' ||
|
|
52
|
+
normalized === 'CASCADE' ||
|
|
53
|
+
normalized === 'SET NULL' ||
|
|
54
|
+
normalized === 'SET DEFAULT'
|
|
55
|
+
) {
|
|
56
|
+
return normalized as ReferentialAction;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Escapes single quotes in a string for safe inclusion in SQL queries.
|
|
63
|
+
* @param name - The string to escape.
|
|
64
|
+
* @returns The escaped string with single quotes doubled.
|
|
65
|
+
*/
|
|
6
66
|
const escapeSingleQuotes = (name: string) => name.replace(/'/g, "''");
|
|
7
67
|
|
|
68
|
+
/** SQLite schema introspector. */
|
|
8
69
|
export const sqliteIntrospector: SchemaIntrospector = {
|
|
70
|
+
/**
|
|
71
|
+
* Introspects the SQLite database schema by querying sqlite_master and various PRAGMAs.
|
|
72
|
+
* @param ctx - The database execution context containing the DbExecutor.
|
|
73
|
+
* @param options - Options controlling which tables and schemas to include.
|
|
74
|
+
* @returns A promise that resolves to the introspected DatabaseSchema.
|
|
75
|
+
*/
|
|
9
76
|
async introspect(ctx: { executor: DbExecutor }, options: IntrospectOptions): Promise<DatabaseSchema> {
|
|
10
77
|
const tables: DatabaseTable[] = [];
|
|
11
|
-
const tableRows = await queryRows(
|
|
78
|
+
const tableRows = (await queryRows(
|
|
12
79
|
ctx.executor,
|
|
13
80
|
`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';`
|
|
14
|
-
);
|
|
81
|
+
)) as SqliteTableRow[];
|
|
15
82
|
|
|
16
83
|
for (const row of tableRows) {
|
|
17
|
-
const name = row.name
|
|
84
|
+
const name = row.name;
|
|
18
85
|
if (!shouldIncludeTable(name, options)) continue;
|
|
19
86
|
const table: DatabaseTable = { name, columns: [], primaryKey: [], indexes: [] };
|
|
20
87
|
|
|
21
|
-
const cols = await queryRows(ctx.executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`);
|
|
88
|
+
const cols = (await queryRows(ctx.executor, `PRAGMA table_info('${escapeSingleQuotes(name)}');`)) as SqliteTableInfoRow[];
|
|
22
89
|
cols.forEach(c => {
|
|
23
90
|
table.columns.push({
|
|
24
91
|
name: c.name,
|
|
@@ -33,26 +100,26 @@ export const sqliteIntrospector: SchemaIntrospector = {
|
|
|
33
100
|
}
|
|
34
101
|
});
|
|
35
102
|
|
|
36
|
-
const fkRows = await queryRows(ctx.executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`);
|
|
103
|
+
const fkRows = (await queryRows(ctx.executor, `PRAGMA foreign_key_list('${escapeSingleQuotes(name)}');`)) as SqliteForeignKeyRow[];
|
|
37
104
|
fkRows.forEach(fk => {
|
|
38
105
|
const col = table.columns.find(c => c.name === fk.from);
|
|
39
106
|
if (col) {
|
|
40
107
|
col.references = {
|
|
41
108
|
table: fk.table,
|
|
42
109
|
column: fk.to,
|
|
43
|
-
onDelete: fk.on_delete
|
|
44
|
-
onUpdate: fk.on_update
|
|
110
|
+
onDelete: toReferentialAction(fk.on_delete),
|
|
111
|
+
onUpdate: toReferentialAction(fk.on_update)
|
|
45
112
|
};
|
|
46
113
|
}
|
|
47
114
|
});
|
|
48
115
|
|
|
49
|
-
const idxList = await queryRows(ctx.executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`);
|
|
116
|
+
const idxList = (await queryRows(ctx.executor, `PRAGMA index_list('${escapeSingleQuotes(name)}');`)) as SqliteIndexListRow[];
|
|
50
117
|
for (const idx of idxList) {
|
|
51
|
-
const idxName = idx.name
|
|
52
|
-
const columnsInfo = await queryRows(ctx.executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`);
|
|
118
|
+
const idxName = idx.name;
|
|
119
|
+
const columnsInfo = (await queryRows(ctx.executor, `PRAGMA index_info('${escapeSingleQuotes(idxName)}');`)) as SqliteIndexInfoRow[];
|
|
53
120
|
const idxEntry: DatabaseIndex = {
|
|
54
121
|
name: idxName,
|
|
55
|
-
columns: columnsInfo.map(ci => ({ column: ci.name
|
|
122
|
+
columns: columnsInfo.map(ci => ({ column: ci.name })),
|
|
56
123
|
unique: idx.unique === 1
|
|
57
124
|
};
|
|
58
125
|
table.indexes!.push(idxEntry);
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
2
|
import { IntrospectOptions } from './types.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Converts a query result to an array of row objects.
|
|
6
|
+
* @param result - The query result.
|
|
7
|
+
* @returns The array of rows.
|
|
8
|
+
*/
|
|
9
|
+
export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
|
|
5
10
|
if (!result) return [];
|
|
6
11
|
return result.values.map(row =>
|
|
7
|
-
result.columns.reduce<Record<string,
|
|
12
|
+
result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
|
|
8
13
|
acc[col] = row[idx];
|
|
9
14
|
return acc;
|
|
10
15
|
}, {})
|
|
11
16
|
);
|
|
12
17
|
};
|
|
13
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Executes a SQL query and returns the rows.
|
|
21
|
+
* @param executor - The database executor.
|
|
22
|
+
* @param sql - The SQL query.
|
|
23
|
+
* @param params - The query parameters.
|
|
24
|
+
* @returns The array of rows.
|
|
25
|
+
*/
|
|
14
26
|
export const queryRows = async (
|
|
15
27
|
executor: DbExecutor,
|
|
16
28
|
sql: string,
|
|
17
29
|
params: unknown[] = []
|
|
18
|
-
): Promise<Record<string,
|
|
30
|
+
): Promise<Record<string, unknown>[]> => {
|
|
19
31
|
const [first] = await executor.executeSql(sql, params);
|
|
20
32
|
return toRows(first);
|
|
21
33
|
};
|
|
22
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a table should be included in introspection based on options.
|
|
37
|
+
* @param name - The table name.
|
|
38
|
+
* @param options - The introspection options.
|
|
39
|
+
* @returns True if the table should be included.
|
|
40
|
+
*/
|
|
23
41
|
export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
|
|
24
42
|
if (options.includeTables && !options.includeTables.includes(name)) return false;
|
|
25
43
|
if (options.excludeTables && options.excludeTables.includes(name)) return false;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { TableDef, IndexDef } from '../../schema/table.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Derives the name for an index based on the table and index definition.
|
|
5
|
+
* @param table - The table definition.
|
|
6
|
+
* @param index - The index definition.
|
|
7
|
+
* @returns The derived index name.
|
|
8
|
+
*/
|
|
3
9
|
export const deriveIndexName = (table: TableDef, index: IndexDef): string => {
|
|
4
10
|
const base = (index.columns ?? [])
|
|
5
11
|
.map(col => (typeof col === 'string' ? col : col.column))
|