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
|
@@ -146,11 +146,20 @@ const operandTypes = new Set<OperandNode['type']>([
|
|
|
146
146
|
'WindowFunction'
|
|
147
147
|
]);
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
export const
|
|
153
|
-
|
|
149
|
+
const hasTypeProperty = (value: unknown): value is { type?: string } =>
|
|
150
|
+
typeof value === 'object' && value !== null && 'type' in value;
|
|
151
|
+
|
|
152
|
+
export const isOperandNode = (node: unknown): node is OperandNode => {
|
|
153
|
+
if (!hasTypeProperty(node)) return false;
|
|
154
|
+
return operandTypes.has(node.type as OperandNode['type']);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const isFunctionNode = (node: unknown): node is FunctionNode =>
|
|
158
|
+
isOperandNode(node) && node.type === 'Function';
|
|
159
|
+
export const isCaseExpressionNode = (node: unknown): node is CaseExpressionNode =>
|
|
160
|
+
isOperandNode(node) && node.type === 'CaseExpression';
|
|
161
|
+
export const isWindowFunctionNode = (node: unknown): node is WindowFunctionNode =>
|
|
162
|
+
isOperandNode(node) && node.type === 'WindowFunction';
|
|
154
163
|
export const isExpressionSelectionNode = (
|
|
155
164
|
node: ColumnRef | FunctionNode | CaseExpressionNode | WindowFunctionNode
|
|
156
165
|
): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
|
|
@@ -47,18 +47,52 @@ export interface OperandVisitor<R> {
|
|
|
47
47
|
otherwise?(node: OperandNode): R;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
type ExpressionDispatch = <R>(node:
|
|
51
|
-
type OperandDispatch = <R>(node:
|
|
50
|
+
type ExpressionDispatch = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>) => R;
|
|
51
|
+
type OperandDispatch = <R>(node: OperandNode, visitor: OperandVisitor<R>) => R;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Registry class for managing dispatchers in an immutable way
|
|
55
|
+
*/
|
|
56
|
+
class DispatcherRegistry<T> {
|
|
57
|
+
private readonly dispatchers: ReadonlyMap<string, T>;
|
|
58
|
+
|
|
59
|
+
constructor(dispatchers: Map<string, T> = new Map()) {
|
|
60
|
+
this.dispatchers = dispatchers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Registers a new dispatcher and returns a new registry instance
|
|
65
|
+
*/
|
|
66
|
+
register(type: string, dispatcher: T): DispatcherRegistry<T> {
|
|
67
|
+
const newMap = new Map(this.dispatchers);
|
|
68
|
+
newMap.set(type, dispatcher);
|
|
69
|
+
return new DispatcherRegistry(newMap);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Gets a dispatcher for the given type
|
|
74
|
+
*/
|
|
75
|
+
get(type: string): T | undefined {
|
|
76
|
+
return this.dispatchers.get(type);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns a new empty registry
|
|
81
|
+
*/
|
|
82
|
+
clear(): DispatcherRegistry<T> {
|
|
83
|
+
return new DispatcherRegistry();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let expressionRegistry = new DispatcherRegistry<ExpressionDispatch>();
|
|
88
|
+
let operandRegistry = new DispatcherRegistry<OperandDispatch>();
|
|
55
89
|
|
|
56
90
|
/**
|
|
57
91
|
* Registers a dispatcher for a custom expression node type.
|
|
58
92
|
* Allows new node kinds without modifying the core switch.
|
|
59
93
|
*/
|
|
60
94
|
export const registerExpressionDispatcher = (type: string, dispatcher: ExpressionDispatch): void => {
|
|
61
|
-
|
|
95
|
+
expressionRegistry = expressionRegistry.register(type, dispatcher);
|
|
62
96
|
};
|
|
63
97
|
|
|
64
98
|
/**
|
|
@@ -66,21 +100,29 @@ export const registerExpressionDispatcher = (type: string, dispatcher: Expressio
|
|
|
66
100
|
* Allows new node kinds without modifying the core switch.
|
|
67
101
|
*/
|
|
68
102
|
export const registerOperandDispatcher = (type: string, dispatcher: OperandDispatch): void => {
|
|
69
|
-
|
|
103
|
+
operandRegistry = operandRegistry.register(type, dispatcher);
|
|
70
104
|
};
|
|
71
105
|
|
|
72
106
|
/**
|
|
73
107
|
* Clears all registered dispatchers. Primarily for tests.
|
|
74
108
|
*/
|
|
75
|
-
export const clearExpressionDispatchers = (): void =>
|
|
76
|
-
|
|
109
|
+
export const clearExpressionDispatchers = (): void => {
|
|
110
|
+
expressionRegistry = expressionRegistry.clear();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const clearOperandDispatchers = (): void => {
|
|
114
|
+
operandRegistry = operandRegistry.clear();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getNodeType = (node: { type?: string } | null | undefined): string | undefined =>
|
|
118
|
+
typeof node === 'object' && node !== null && typeof node.type === 'string' ? node.type : undefined;
|
|
77
119
|
|
|
78
120
|
const unsupportedExpression = (node: ExpressionNode): never => {
|
|
79
|
-
throw new Error(`Unsupported expression type "${(node
|
|
121
|
+
throw new Error(`Unsupported expression type "${getNodeType(node) ?? 'unknown'}"`);
|
|
80
122
|
};
|
|
81
123
|
|
|
82
124
|
const unsupportedOperand = (node: OperandNode): never => {
|
|
83
|
-
throw new Error(`Unsupported operand type "${(node
|
|
125
|
+
throw new Error(`Unsupported operand type "${getNodeType(node) ?? 'unknown'}"`);
|
|
84
126
|
};
|
|
85
127
|
/**
|
|
86
128
|
* Dispatches an expression node to the visitor
|
|
@@ -88,8 +130,8 @@ const unsupportedOperand = (node: OperandNode): never => {
|
|
|
88
130
|
* @param visitor - Visitor implementation
|
|
89
131
|
*/
|
|
90
132
|
export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
|
|
91
|
-
const dynamic =
|
|
92
|
-
if (dynamic) return dynamic(node
|
|
133
|
+
const dynamic = expressionRegistry.get(node.type);
|
|
134
|
+
if (dynamic) return dynamic(node, visitor);
|
|
93
135
|
|
|
94
136
|
switch (node.type) {
|
|
95
137
|
case 'BinaryExpression':
|
|
@@ -126,8 +168,8 @@ export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisi
|
|
|
126
168
|
* @param visitor - Visitor implementation
|
|
127
169
|
*/
|
|
128
170
|
export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
|
|
129
|
-
const dynamic =
|
|
130
|
-
if (dynamic) return dynamic(node
|
|
171
|
+
const dynamic = operandRegistry.get(node.type);
|
|
172
|
+
if (dynamic) return dynamic(node, visitor);
|
|
131
173
|
|
|
132
174
|
switch (node.type) {
|
|
133
175
|
case 'Column':
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ColumnNode, LiteralNode } from './expression.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a column node for use in expressions
|
|
5
|
+
* @param table - Table name
|
|
6
|
+
* @param name - Column name
|
|
7
|
+
* @returns ColumnNode with the specified table and name
|
|
8
|
+
*/
|
|
9
|
+
export const createColumn = (table: string, name: string): ColumnNode => ({
|
|
10
|
+
type: 'Column',
|
|
11
|
+
table,
|
|
12
|
+
name
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a literal value node for use in expressions
|
|
17
|
+
* @param val - Literal value (string or number)
|
|
18
|
+
* @returns LiteralNode with the specified value
|
|
19
|
+
*/
|
|
20
|
+
export const createLiteral = (val: string | number): LiteralNode => ({
|
|
21
|
+
type: 'Literal',
|
|
22
|
+
value: val
|
|
23
|
+
});
|
|
@@ -2,7 +2,7 @@ import { JoinNode } from './join.js';
|
|
|
2
2
|
import { ExpressionNode } from './expression.js';
|
|
3
3
|
import { JoinKind } from '../sql/sql.js';
|
|
4
4
|
import { JoinMetadata } from './join-metadata.js';
|
|
5
|
-
import { TableSourceNode, TableNode
|
|
5
|
+
import { TableSourceNode, TableNode } from './query.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Creates a JoinNode ready for AST insertion.
|
|
@@ -17,8 +17,24 @@ export const createJoinNode = (
|
|
|
17
17
|
type: 'Join',
|
|
18
18
|
kind,
|
|
19
19
|
table: typeof tableName === 'string'
|
|
20
|
-
? (
|
|
20
|
+
? (parseQualifiedTableRef(tableName) as TableNode)
|
|
21
21
|
: (tableName as TableSourceNode),
|
|
22
22
|
condition,
|
|
23
23
|
meta: relationName ? ({ relationName } as JoinMetadata) : undefined
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parses a simple qualified reference like `schema.table` into a structured TableNode.
|
|
28
|
+
*
|
|
29
|
+
* Notes:
|
|
30
|
+
* - We intentionally only support a single dot here.
|
|
31
|
+
* - For multi-part qualification (server/db/schema/table), callers should pass a TableNode.
|
|
32
|
+
*/
|
|
33
|
+
const parseQualifiedTableRef = (ref: string): TableNode => {
|
|
34
|
+
const parts = ref.split('.');
|
|
35
|
+
if (parts.length === 2) {
|
|
36
|
+
const [schema, name] = parts;
|
|
37
|
+
return { type: 'Table', schema, name };
|
|
38
|
+
}
|
|
39
|
+
return { type: 'Table', name: ref };
|
|
40
|
+
};
|
package/src/core/ast/query.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AliasRefNode,
|
|
3
|
+
CaseExpressionNode,
|
|
2
4
|
ColumnNode,
|
|
3
|
-
FunctionNode,
|
|
4
5
|
ExpressionNode,
|
|
5
|
-
|
|
6
|
-
CaseExpressionNode,
|
|
7
|
-
WindowFunctionNode,
|
|
6
|
+
FunctionNode,
|
|
8
7
|
OperandNode,
|
|
9
|
-
|
|
8
|
+
ScalarSubqueryNode,
|
|
9
|
+
WindowFunctionNode
|
|
10
10
|
} from './expression.js';
|
|
11
11
|
import { JoinNode } from './join.js';
|
|
12
12
|
import { OrderDirection } from '../sql/sql.js';
|
|
@@ -34,7 +34,7 @@ export interface FunctionTableNode {
|
|
|
34
34
|
/** Optional schema for the function (some dialects) */
|
|
35
35
|
schema?: string;
|
|
36
36
|
/** Function arguments as operand nodes */
|
|
37
|
-
args?:
|
|
37
|
+
args?: OperandNode[];
|
|
38
38
|
/** Optional alias for the function table */
|
|
39
39
|
alias?: string;
|
|
40
40
|
/** LATERAL flag */
|
|
@@ -60,7 +60,11 @@ export const ntile = (n: number): WindowFunctionNode =>
|
|
|
60
60
|
* @param defaultValue - Default value if no row exists
|
|
61
61
|
* @returns Window function node for LAG
|
|
62
62
|
*/
|
|
63
|
-
export const lag = (
|
|
63
|
+
export const lag = (
|
|
64
|
+
col: ColumnRef | ColumnNode,
|
|
65
|
+
offset: number = 1,
|
|
66
|
+
defaultValue?: LiteralNode['value']
|
|
67
|
+
): WindowFunctionNode => {
|
|
64
68
|
const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
|
|
65
69
|
columnOperand(col),
|
|
66
70
|
{ type: 'Literal', value: offset }
|
|
@@ -78,7 +82,11 @@ export const lag = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValu
|
|
|
78
82
|
* @param defaultValue - Default value if no row exists
|
|
79
83
|
* @returns Window function node for LEAD
|
|
80
84
|
*/
|
|
81
|
-
export const lead = (
|
|
85
|
+
export const lead = (
|
|
86
|
+
col: ColumnRef | ColumnNode,
|
|
87
|
+
offset: number = 1,
|
|
88
|
+
defaultValue?: LiteralNode['value']
|
|
89
|
+
): WindowFunctionNode => {
|
|
82
90
|
const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
|
|
83
91
|
columnOperand(col),
|
|
84
92
|
{ type: 'Literal', value: offset }
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { SchemaDialect, DialectName } from '../schema-dialect.js';
|
|
2
|
-
import { formatLiteral, quoteQualified,
|
|
2
|
+
import { formatLiteral, quoteQualified, LiteralFormatter } from '../sql-writing.js';
|
|
3
3
|
import { ColumnDef, ForeignKeyReference } from '../../../schema/column.js';
|
|
4
4
|
import { IndexDef, TableDef } from '../../../schema/table.js';
|
|
5
5
|
import { DatabaseTable, DatabaseColumn, ColumnDiff } from '../schema-types.js';
|
|
6
6
|
|
|
7
|
+
/** A table-like object with name and optional schema. */
|
|
7
8
|
type TableLike = { name: string; schema?: string };
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -11,10 +12,15 @@ type TableLike = { name: string; schema?: string };
|
|
|
11
12
|
* Concrete dialects only override the small surface area instead of reimplementing everything.
|
|
12
13
|
*/
|
|
13
14
|
export abstract class BaseSchemaDialect implements SchemaDialect {
|
|
15
|
+
/** The name of the dialect. */
|
|
14
16
|
abstract readonly name: DialectName;
|
|
17
|
+
/** Quotes an identifier for use in SQL. */
|
|
15
18
|
abstract quoteIdentifier(id: string): string;
|
|
19
|
+
/** Renders the column type for SQL. */
|
|
16
20
|
abstract renderColumnType(column: ColumnDef): string;
|
|
21
|
+
/** Renders the auto-increment clause for SQL. */
|
|
17
22
|
abstract renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined;
|
|
23
|
+
/** Renders an index for SQL. */
|
|
18
24
|
abstract renderIndex(table: TableDef, index: IndexDef): string;
|
|
19
25
|
supportsPartialIndexes(): boolean {
|
|
20
26
|
return false;
|
|
@@ -25,20 +31,30 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
|
|
|
25
31
|
}
|
|
26
32
|
return this.quoteIdentifier(table.name);
|
|
27
33
|
}
|
|
28
|
-
|
|
34
|
+
/** The literal formatter for the dialect. */
|
|
29
35
|
abstract get literalFormatter(): LiteralFormatter;
|
|
30
36
|
|
|
31
37
|
renderDefault(value: unknown, _column: ColumnDef): string {
|
|
38
|
+
void _column;
|
|
32
39
|
return formatLiteral(this.literalFormatter, value);
|
|
33
40
|
}
|
|
34
41
|
renderReference(ref: ForeignKeyReference, _table: TableDef): string {
|
|
42
|
+
void _table;
|
|
35
43
|
const parts = ['REFERENCES', quoteQualified(this, ref.table), `(${this.quoteIdentifier(ref.column)})`];
|
|
36
44
|
if (ref.onDelete) parts.push('ON DELETE', ref.onDelete);
|
|
37
45
|
if (ref.onUpdate) parts.push('ON UPDATE', ref.onUpdate);
|
|
38
|
-
|
|
46
|
+
const suffix = this.renderReferenceSuffix(ref, _table);
|
|
47
|
+
if (suffix) parts.push(suffix);
|
|
39
48
|
return parts.join(' ');
|
|
40
49
|
}
|
|
50
|
+
|
|
51
|
+
protected renderReferenceSuffix(ref: ForeignKeyReference, _table: TableDef): string | undefined {
|
|
52
|
+
void ref;
|
|
53
|
+
void _table;
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
41
56
|
renderTableOptions(_table: TableDef): string | undefined {
|
|
57
|
+
void _table;
|
|
42
58
|
return undefined;
|
|
43
59
|
}
|
|
44
60
|
dropTableSql(table: DatabaseTable): string[] {
|
|
@@ -51,12 +67,29 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
|
|
|
51
67
|
return [`DROP INDEX ${this.quoteIdentifier(index)};`];
|
|
52
68
|
}
|
|
53
69
|
warnDropColumn(_table: DatabaseTable, _column: string): string | undefined {
|
|
70
|
+
void _table;
|
|
71
|
+
void _column;
|
|
54
72
|
return undefined;
|
|
55
73
|
}
|
|
56
|
-
alterColumnSql?(
|
|
74
|
+
alterColumnSql?(_table: TableDef, _column: ColumnDef, _actualColumn: DatabaseColumn, _diff: ColumnDiff): string[] {
|
|
75
|
+
void _table;
|
|
76
|
+
void _column;
|
|
77
|
+
void _actualColumn;
|
|
78
|
+
void _diff;
|
|
57
79
|
return [];
|
|
58
80
|
}
|
|
59
81
|
warnAlterColumn?(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
|
|
82
|
+
void _table;
|
|
83
|
+
void _column;
|
|
84
|
+
void _actual;
|
|
85
|
+
void _diff;
|
|
60
86
|
return undefined;
|
|
61
87
|
}
|
|
88
|
+
|
|
89
|
+
preferInlinePkAutoincrement(_column: ColumnDef, _table: TableDef, _pk: string[]): boolean {
|
|
90
|
+
void _column;
|
|
91
|
+
void _table;
|
|
92
|
+
void _pk;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
62
95
|
}
|
|
@@ -6,6 +6,7 @@ import { IndexDef, TableDef } from '../../../schema/table.js';
|
|
|
6
6
|
import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
|
|
7
7
|
import { DialectName } from '../schema-dialect.js';
|
|
8
8
|
|
|
9
|
+
/** MSSQL schema dialect implementation. */
|
|
9
10
|
export class MSSqlSchemaDialect extends BaseSchemaDialect {
|
|
10
11
|
name: DialectName = 'mssql';
|
|
11
12
|
|
|
@@ -120,6 +121,7 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string[] {
|
|
124
|
+
void _actual;
|
|
123
125
|
const stmts: string[] = [];
|
|
124
126
|
if (diff.typeChanged || diff.nullabilityChanged) {
|
|
125
127
|
const nullability = column.notNull ? 'NOT NULL' : 'NULL';
|
|
@@ -131,6 +133,9 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
|
|
136
|
+
void _table;
|
|
137
|
+
void _column;
|
|
138
|
+
void _actual;
|
|
134
139
|
if (diff.defaultChanged || diff.autoIncrementChanged) {
|
|
135
140
|
return 'Altering defaults or identity on MSSQL is not automated (requires dropping/adding default or identity constraints manually).';
|
|
136
141
|
}
|
|
@@ -7,6 +7,7 @@ import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
|
|
|
7
7
|
import { renderColumnDefinition } from '../schema-generator.js';
|
|
8
8
|
import { DialectName } from '../schema-dialect.js';
|
|
9
9
|
|
|
10
|
+
/** MySQL schema dialect implementation. */
|
|
10
11
|
export class MySqlSchemaDialect extends BaseSchemaDialect {
|
|
11
12
|
name: DialectName = 'mysql';
|
|
12
13
|
|
|
@@ -125,6 +126,8 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
|
|
129
|
+
void _actual;
|
|
130
|
+
void _diff;
|
|
128
131
|
const rendered = renderColumnDefinition(table, column, this);
|
|
129
132
|
return [`ALTER TABLE ${this.formatTableName(table)} MODIFY COLUMN ${rendered.sql};`];
|
|
130
133
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { BaseSchemaDialect } from './base-schema-dialect.js';
|
|
2
2
|
import { deriveIndexName } from '../naming-strategy.js';
|
|
3
3
|
import { renderIndexColumns, createLiteralFormatter } from '../sql-writing.js';
|
|
4
|
-
import { ColumnDef } from '../../../schema/column.js';
|
|
4
|
+
import { ColumnDef, ForeignKeyReference } from '../../../schema/column.js';
|
|
5
5
|
import { IndexDef, TableDef } from '../../../schema/table.js';
|
|
6
6
|
import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
|
|
7
7
|
import { DialectName } from '../schema-dialect.js';
|
|
8
8
|
|
|
9
|
+
/** PostgreSQL schema dialect implementation. */
|
|
9
10
|
export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
10
11
|
readonly name: DialectName = 'postgres';
|
|
11
12
|
|
|
@@ -102,6 +103,14 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
|
102
103
|
return true;
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
protected renderReferenceSuffix(ref: ForeignKeyReference, _table: TableDef): string | undefined {
|
|
107
|
+
void _table;
|
|
108
|
+
if (ref.deferrable) {
|
|
109
|
+
return 'DEFERRABLE INITIALLY DEFERRED';
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
dropColumnSql(table: DatabaseTable, column: string): string[] {
|
|
106
115
|
return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
|
|
107
116
|
}
|
|
@@ -114,6 +123,7 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
alterColumnSql(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[] {
|
|
126
|
+
void actualColumn;
|
|
117
127
|
const stmts: string[] = [];
|
|
118
128
|
const tableName = this.formatTableName(table);
|
|
119
129
|
const colName = this.quoteIdentifier(column.name);
|
|
@@ -145,6 +155,9 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
|
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
|
|
158
|
+
void _table;
|
|
159
|
+
void _column;
|
|
160
|
+
void _actual;
|
|
148
161
|
if (diff.autoIncrementChanged) {
|
|
149
162
|
return 'Altering identity properties may fail if an existing sequence is attached; verify generated column state.';
|
|
150
163
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { BaseSchemaDialect } from './base-schema-dialect.js';
|
|
3
|
+
import { PostgresSchemaDialect } from './postgres-schema-dialect.js';
|
|
4
|
+
import { TableDef } from '../../../schema/table.js';
|
|
5
|
+
import { ForeignKeyReference } from '../../../schema/column.js';
|
|
6
|
+
import { createLiteralFormatter } from '../sql-writing.js';
|
|
7
|
+
|
|
8
|
+
class DummySchemaDialect extends BaseSchemaDialect {
|
|
9
|
+
readonly name = 'sqlite';
|
|
10
|
+
private readonly formatter = createLiteralFormatter({
|
|
11
|
+
booleanTrue: '1',
|
|
12
|
+
booleanFalse: '0',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
get literalFormatter() {
|
|
16
|
+
return this.formatter;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
quoteIdentifier(id: string): string {
|
|
20
|
+
return `"${id}"`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
renderColumnType(): string {
|
|
24
|
+
return 'INTEGER';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
renderAutoIncrement(): string | undefined {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
renderIndex(): string {
|
|
32
|
+
return 'CREATE INDEX dummy;';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const table: TableDef = {
|
|
37
|
+
name: 'child',
|
|
38
|
+
columns: {},
|
|
39
|
+
relations: {},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const deferrableReference: ForeignKeyReference = {
|
|
43
|
+
table: 'parent',
|
|
44
|
+
column: 'id',
|
|
45
|
+
deferrable: true,
|
|
46
|
+
onDelete: 'CASCADE',
|
|
47
|
+
onUpdate: 'NO ACTION',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('renderReference deferrable handling', () => {
|
|
51
|
+
it('base dialect remains agnostic to deferrable flags', () => {
|
|
52
|
+
const dialect = new DummySchemaDialect();
|
|
53
|
+
const sql = dialect.renderReference(deferrableReference, table);
|
|
54
|
+
expect(sql).toContain('REFERENCES "parent"');
|
|
55
|
+
expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('Postgres dialect renders the deferrable clause', () => {
|
|
59
|
+
const dialect = new PostgresSchemaDialect();
|
|
60
|
+
const sql = dialect.renderReference(deferrableReference, table);
|
|
61
|
+
expect(sql).toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('Postgres dialect skips the clause when the flag is missing', () => {
|
|
65
|
+
const dialect = new PostgresSchemaDialect();
|
|
66
|
+
const sql = dialect.renderReference({ table: 'parent', column: 'id' }, table);
|
|
67
|
+
expect(sql).not.toContain('DEFERRABLE INITIALLY DEFERRED');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -6,6 +6,7 @@ import { IndexDef, TableDef } from '../../../schema/table.js';
|
|
|
6
6
|
import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
|
|
7
7
|
import { DialectName } from '../schema-dialect.js';
|
|
8
8
|
|
|
9
|
+
/** SQLite schema dialect implementation. */
|
|
9
10
|
export class SQLiteSchemaDialect extends BaseSchemaDialect {
|
|
10
11
|
name: DialectName = 'sqlite';
|
|
11
12
|
|
|
@@ -103,10 +104,13 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
dropColumnSql(_table: DatabaseTable, _column: string): string[] {
|
|
107
|
+
void _table;
|
|
108
|
+
void _column;
|
|
106
109
|
return [];
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
dropIndexSql(_table: DatabaseTable, index: string): string[] {
|
|
113
|
+
void _table;
|
|
110
114
|
return [`DROP INDEX IF EXISTS ${this.quoteIdentifier(index)};`];
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -116,10 +120,16 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
|
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
alterColumnSql(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
|
|
123
|
+
void _table;
|
|
124
|
+
void _column;
|
|
125
|
+
void _actual;
|
|
126
|
+
void _diff;
|
|
119
127
|
return [];
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
warnAlterColumn(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
|
|
131
|
+
void _actual;
|
|
132
|
+
void _diff;
|
|
123
133
|
const key = table.schema ? `${table.schema}.${table.name}` : table.name;
|
|
124
134
|
return `SQLite ALTER COLUMN is not supported; rebuild table ${key} to change column ${column.name}.`;
|
|
125
135
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineTable } from '../../../../schema/table.js';
|
|
2
2
|
import { col } from '../../../../schema/column.js';
|
|
3
3
|
|
|
4
|
+
/** Table definition for information_schema.columns, providing metadata about table columns. */
|
|
4
5
|
export const PgInformationSchemaColumns = defineTable(
|
|
5
6
|
'columns',
|
|
6
7
|
{
|
|
@@ -134,6 +135,7 @@ export const PgReferentialConstraints = defineTable(
|
|
|
134
135
|
{ schema: 'information_schema' }
|
|
135
136
|
);
|
|
136
137
|
|
|
138
|
+
/** Default export containing commonly used PostgreSQL catalog table definitions. */
|
|
137
139
|
export default {
|
|
138
140
|
PgInformationSchemaColumns,
|
|
139
141
|
PgClass,
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Dialect } from '../../dialect/abstract.js';
|
|
2
2
|
import type { DbExecutor } from '../../execution/db-executor.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Context for schema introspection operations.
|
|
6
|
+
* Provides the necessary components to perform database schema introspection.
|
|
7
|
+
*/
|
|
4
8
|
export interface IntrospectContext {
|
|
9
|
+
/** The database dialect used for introspection. */
|
|
5
10
|
dialect: Dialect;
|
|
11
|
+
/** The database executor for running introspection queries. */
|
|
6
12
|
executor: DbExecutor;
|
|
7
13
|
}
|
|
8
14
|
|
|
@@ -13,10 +13,23 @@ const fn = (name: string, args: OperandInput[]): FunctionNode => ({
|
|
|
13
13
|
args: args.map(toOperand)
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Creates a pg_get_expr function call AST node.
|
|
18
|
+
* @param expr - The expression.
|
|
19
|
+
* @param relid - The relation ID.
|
|
20
|
+
* @returns The function node.
|
|
21
|
+
*/
|
|
16
22
|
export const pgGetExpr = (expr: OperandInput, relid: OperandInput): FunctionNode =>
|
|
17
23
|
fn('pg_get_expr', [expr, relid]);
|
|
18
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Creates a format_type function call AST node.
|
|
27
|
+
* @param typeOid - The type OID.
|
|
28
|
+
* @param typmod - The type modifier.
|
|
29
|
+
* @returns The function node.
|
|
30
|
+
*/
|
|
19
31
|
export const formatType = (typeOid: OperandInput, typmod: OperandInput): FunctionNode =>
|
|
20
32
|
fn('format_type', [typeOid, typmod]);
|
|
21
33
|
|
|
34
|
+
/** Default export with PostgreSQL-specific function helpers. */
|
|
22
35
|
export default { pgGetExpr, formatType };
|