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,4 +1,4 @@
1
- import { TableNode } from './query.js';
1
+ import { TableNode, FunctionTableNode } from './query.js';
2
2
  import { ExpressionNode } from './expression.js';
3
3
  import { JoinKind } from '../sql/sql.js';
4
4
 
@@ -10,9 +10,9 @@ export interface JoinNode {
10
10
  /** Type of join (INNER, LEFT, RIGHT, etc.) */
11
11
  kind: JoinKind;
12
12
  /** Table to join */
13
- table: TableNode;
14
- /** Join condition expression */
13
+ table: TableNode | FunctionTableNode;
14
+ /** Join condition expression */
15
15
  condition: ExpressionNode;
16
- /** Optional relation name for code generation */
17
- relationName?: string;
16
+ /** Optional metadata for non-SQL concerns (e.g., relation name) */
17
+ meta?: Record<string, unknown>;
18
18
  }
@@ -8,7 +8,6 @@ import {
8
8
  OperandNode
9
9
  } from './expression.js';
10
10
  import { JoinNode } from './join.js';
11
- import { RelationType } from '../../schema/relation.js';
12
11
  import { OrderDirection } from '../sql/sql.js';
13
12
 
14
13
  /**
@@ -24,6 +23,27 @@ export interface TableNode {
24
23
  alias?: string;
25
24
  }
26
25
 
26
+ /**
27
+ * AST node representing a function used as a table source (table-valued function)
28
+ */
29
+ export interface FunctionTableNode {
30
+ type: 'FunctionTable';
31
+ /** Function name */
32
+ name: string;
33
+ /** Optional schema for the function (some dialects) */
34
+ schema?: string;
35
+ /** Function arguments as operand nodes */
36
+ args?: any[]; // use any to avoid circular import here; caller should supply OperandNode
37
+ /** Optional alias for the function table */
38
+ alias?: string;
39
+ /** LATERAL flag */
40
+ lateral?: boolean;
41
+ /** WITH ORDINALITY flag */
42
+ withOrdinality?: boolean;
43
+ /** Optional column aliases */
44
+ columnAliases?: string[];
45
+ }
46
+
27
47
  /**
28
48
  * AST node representing an ORDER BY clause
29
49
  */
@@ -36,101 +56,45 @@ export interface OrderByNode {
36
56
  }
37
57
 
38
58
  /**
39
- * Plan describing pivot columns needed for hydration
40
- */
41
- export interface HydrationPivotPlan {
42
- table: string;
43
- primaryKey: string;
44
- aliasPrefix: string;
45
- columns: string[];
46
- }
47
-
48
- /**
49
- * Plan for hydrating relationship data
59
+ * AST node representing a Common Table Expression (CTE)
50
60
  */
51
- export interface HydrationRelationPlan {
52
- /** Name of the relationship */
61
+ export interface CommonTableExpressionNode {
62
+ type: 'CommonTableExpression';
63
+ /** CTE name */
53
64
  name: string;
54
- /** Alias prefix for the relationship */
55
- aliasPrefix: string;
56
- /** Type of relationship */
57
- type: RelationType;
58
- /** Target table name */
59
- targetTable: string;
60
- /** Target table primary key */
61
- targetPrimaryKey: string;
62
- /** Foreign key column */
63
- foreignKey: string;
64
- /** Local key column */
65
- localKey: string;
66
- /** Columns to include */
67
- columns: string[];
68
- /** Optional pivot plan for many-to-many relationships */
69
- pivot?: HydrationPivotPlan;
65
+ /** Optional column names */
66
+ columns?: string[];
67
+ /** CTE query */
68
+ query: SelectQueryNode;
69
+ /** Whether the CTE is recursive */
70
+ recursive: boolean;
70
71
  }
71
72
 
72
73
  /**
73
- * Complete hydration plan for a query
74
+ * Supported set operation kinds for compound SELECT queries
74
75
  */
75
- export interface HydrationPlan {
76
- /** Root table name */
77
- rootTable: string;
78
- /** Root table primary key */
79
- rootPrimaryKey: string;
80
- /** Root table columns */
81
- rootColumns: string[];
82
- /** Relationship hydration plans */
83
- relations: HydrationRelationPlan[];
84
- }
76
+ export type SetOperationKind = 'UNION' | 'UNION ALL' | 'INTERSECT' | 'EXCEPT';
85
77
 
86
78
  /**
87
- * Query metadata including hydration information
79
+ * AST node representing a set operation (UNION, INTERSECT, etc.)
88
80
  */
89
- export interface QueryMetadata {
90
- /** Optional hydration plan */
91
- hydration?: HydrationPlan;
81
+ export interface SetOperationNode {
82
+ type: 'SetOperation';
83
+ /** Operator to combine queries */
84
+ operator: SetOperationKind;
85
+ /** Right-hand query in the compound expression */
86
+ query: SelectQueryNode;
92
87
  }
93
88
 
94
89
  /**
95
- * AST node representing a Common Table Expression (CTE)
90
+ * AST node representing a complete SELECT query
96
91
  */
97
- export interface CommonTableExpressionNode {
98
- type: 'CommonTableExpression';
99
- /** CTE name */
100
- name: string;
101
- /** Optional column names */
102
- columns?: string[];
103
- /** CTE query */
104
- query: SelectQueryNode;
105
- /** Whether the CTE is recursive */
106
- recursive: boolean;
107
- }
108
-
109
- /**
110
- * Supported set operation kinds for compound SELECT queries
111
- */
112
- export type SetOperationKind = 'UNION' | 'UNION ALL' | 'INTERSECT' | 'EXCEPT';
113
-
114
- /**
115
- * AST node representing a set operation (UNION, INTERSECT, etc.)
116
- */
117
- export interface SetOperationNode {
118
- type: 'SetOperation';
119
- /** Operator to combine queries */
120
- operator: SetOperationKind;
121
- /** Right-hand query in the compound expression */
122
- query: SelectQueryNode;
123
- }
124
-
125
- /**
126
- * AST node representing a complete SELECT query
127
- */
128
- export interface SelectQueryNode {
129
- type: 'SelectQuery';
92
+ export interface SelectQueryNode {
93
+ type: 'SelectQuery';
130
94
  /** Optional CTEs (WITH clauses) */
131
95
  ctes?: CommonTableExpressionNode[];
132
- /** FROM clause table */
133
- from: TableNode;
96
+ /** FROM clause table (either a Table or a FunctionTable) */
97
+ from: TableNode | FunctionTableNode;
134
98
  /** SELECT clause columns */
135
99
  columns: (ColumnNode | FunctionNode | ScalarSubqueryNode | CaseExpressionNode | WindowFunctionNode)[];
136
100
  /** JOIN clauses */
@@ -147,13 +111,13 @@ export interface SelectQueryNode {
147
111
  limit?: number;
148
112
  /** Optional OFFSET clause */
149
113
  offset?: number;
150
- /** Optional query metadata */
151
- meta?: QueryMetadata;
152
- /** Optional DISTINCT clause */
153
- distinct?: ColumnNode[];
154
- /** Optional set operations chaining this query with others */
155
- setOps?: SetOperationNode[];
156
- }
114
+ /** Optional query metadata */
115
+ meta?: Record<string, unknown>;
116
+ /** Optional DISTINCT clause */
117
+ distinct?: ColumnNode[];
118
+ /** Optional set operations chaining this query with others */
119
+ setOps?: SetOperationNode[];
120
+ }
157
121
 
158
122
  export interface InsertQueryNode {
159
123
  type: 'InsertQuery';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal column reference used by AST builders.
3
+ * Accepts any object with a name and optional table/alias fields
4
+ * (schema ColumnDef/TableDef remain structurally compatible).
5
+ */
6
+ export interface ColumnRef {
7
+ name: string;
8
+ table?: string;
9
+ alias?: string;
10
+ }
11
+
12
+ /**
13
+ * Minimal table reference used by AST builders.
14
+ * Keeps AST decoupled from full schema TableDef shape.
15
+ */
16
+ export interface TableRef {
17
+ name: string;
18
+ schema?: string;
19
+ alias?: string;
20
+ }
@@ -1,8 +1,8 @@
1
- import { ColumnDef } from '../../schema/column.js';
2
- import { ColumnNode, LiteralNode, JsonPathNode, WindowFunctionNode } from './expression-nodes.js';
3
- import { columnOperand } from './expression-builders.js';
4
- import { OrderDirection } from '../sql/sql.js';
5
- import { OrderByNode } from './query.js';
1
+ import { ColumnNode, LiteralNode, JsonPathNode, WindowFunctionNode } from './expression-nodes.js';
2
+ import { columnOperand } from './expression-builders.js';
3
+ import { OrderDirection } from '../sql/sql.js';
4
+ import { OrderByNode } from './query.js';
5
+ import { ColumnRef } from './types.js';
6
6
 
7
7
  const buildWindowFunction = (
8
8
  name: string,
@@ -50,21 +50,21 @@ export const denseRank = (): WindowFunctionNode => buildWindowFunction('DENSE_RA
50
50
  * @param n - Number of buckets
51
51
  * @returns Window function node for NTILE
52
52
  */
53
- export const ntile = (n: number): WindowFunctionNode =>
54
- buildWindowFunction('NTILE', [{ type: 'Literal', value: n }]);
53
+ export const ntile = (n: number): WindowFunctionNode =>
54
+ buildWindowFunction('NTILE', [{ type: 'Literal', value: n }]);
55
55
 
56
56
  /**
57
57
  * Creates a LAG window function
58
58
  * @param col - Column to lag
59
59
  * @param offset - Offset (defaults to 1)
60
- * @param defaultValue - Default value if no row exists
61
- * @returns Window function node for LAG
62
- */
63
- export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
64
- const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
65
- columnOperand(col),
66
- { type: 'Literal', value: offset }
67
- ];
60
+ * @param defaultValue - Default value if no row exists
61
+ * @returns Window function node for LAG
62
+ */
63
+ export const lag = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
64
+ const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
65
+ columnOperand(col),
66
+ { type: 'Literal', value: offset }
67
+ ];
68
68
  if (defaultValue !== undefined) {
69
69
  args.push({ type: 'Literal', value: defaultValue });
70
70
  }
@@ -75,14 +75,14 @@ export const lag = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValu
75
75
  * Creates a LEAD window function
76
76
  * @param col - Column to lead
77
77
  * @param offset - Offset (defaults to 1)
78
- * @param defaultValue - Default value if no row exists
79
- * @returns Window function node for LEAD
80
- */
81
- export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
82
- const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
83
- columnOperand(col),
84
- { type: 'Literal', value: offset }
85
- ];
78
+ * @param defaultValue - Default value if no row exists
79
+ * @returns Window function node for LEAD
80
+ */
81
+ export const lead = (col: ColumnRef | ColumnNode, offset: number = 1, defaultValue?: any): WindowFunctionNode => {
82
+ const args: (ColumnNode | LiteralNode | JsonPathNode)[] = [
83
+ columnOperand(col),
84
+ { type: 'Literal', value: offset }
85
+ ];
86
86
  if (defaultValue !== undefined) {
87
87
  args.push({ type: 'Literal', value: defaultValue });
88
88
  }
@@ -91,19 +91,19 @@ export const lead = (col: ColumnDef | ColumnNode, offset: number = 1, defaultVal
91
91
 
92
92
  /**
93
93
  * Creates a FIRST_VALUE window function
94
- * @param col - Column to get first value from
95
- * @returns Window function node for FIRST_VALUE
96
- */
97
- export const firstValue = (col: ColumnDef | ColumnNode): WindowFunctionNode =>
98
- buildWindowFunction('FIRST_VALUE', [columnOperand(col)]);
94
+ * @param col - Column to get first value from
95
+ * @returns Window function node for FIRST_VALUE
96
+ */
97
+ export const firstValue = (col: ColumnRef | ColumnNode): WindowFunctionNode =>
98
+ buildWindowFunction('FIRST_VALUE', [columnOperand(col)]);
99
99
 
100
100
  /**
101
101
  * Creates a LAST_VALUE window function
102
- * @param col - Column to get last value from
103
- * @returns Window function node for LAST_VALUE
104
- */
105
- export const lastValue = (col: ColumnDef | ColumnNode): WindowFunctionNode =>
106
- buildWindowFunction('LAST_VALUE', [columnOperand(col)]);
102
+ * @param col - Column to get last value from
103
+ * @returns Window function node for LAST_VALUE
104
+ */
105
+ export const lastValue = (col: ColumnRef | ColumnNode): WindowFunctionNode =>
106
+ buildWindowFunction('LAST_VALUE', [columnOperand(col)]);
107
107
 
108
108
  /**
109
109
  * Creates a custom window function
@@ -113,28 +113,28 @@ export const lastValue = (col: ColumnDef | ColumnNode): WindowFunctionNode =>
113
113
  * @param orderBy - Optional ORDER BY clauses
114
114
  * @returns Window function node
115
115
  */
116
- export const windowFunction = (
117
- name: string,
118
- args: (ColumnDef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
119
- partitionBy?: (ColumnDef | ColumnNode)[],
120
- orderBy?: { column: ColumnDef | ColumnNode; direction: OrderDirection }[]
121
- ): WindowFunctionNode => {
122
- const nodeArgs = args.map(arg => {
123
- if (typeof (arg as LiteralNode).value !== 'undefined') {
124
- return arg as LiteralNode;
125
- }
126
- if ('path' in arg) {
127
- return arg as JsonPathNode;
128
- }
129
- return columnOperand(arg as ColumnDef | ColumnNode);
130
- });
131
-
132
- const partitionNodes = partitionBy?.map(col => columnOperand(col)) ?? undefined;
133
- const orderNodes: OrderByNode[] | undefined = orderBy?.map(o => ({
134
- type: 'OrderBy',
135
- column: columnOperand(o.column),
136
- direction: o.direction
137
- }));
116
+ export const windowFunction = (
117
+ name: string,
118
+ args: (ColumnRef | ColumnNode | LiteralNode | JsonPathNode)[] = [],
119
+ partitionBy?: (ColumnRef | ColumnNode)[],
120
+ orderBy?: { column: ColumnRef | ColumnNode; direction: OrderDirection }[]
121
+ ): WindowFunctionNode => {
122
+ const nodeArgs = args.map(arg => {
123
+ if (typeof (arg as LiteralNode).value !== 'undefined') {
124
+ return arg as LiteralNode;
125
+ }
126
+ if ('path' in arg) {
127
+ return arg as JsonPathNode;
128
+ }
129
+ return columnOperand(arg as ColumnRef | ColumnNode);
130
+ });
131
+
132
+ const partitionNodes = partitionBy?.map(col => columnOperand(col)) ?? undefined;
133
+ const orderNodes: OrderByNode[] | undefined = orderBy?.map(o => ({
134
+ type: 'OrderBy',
135
+ column: columnOperand(o.column),
136
+ direction: o.direction
137
+ }));
138
138
 
139
139
  return buildWindowFunction(name, nodeArgs, partitionNodes, orderNodes);
140
140
  };
@@ -1,7 +1,8 @@
1
- import { SchemaDialect, DialectName, formatLiteral, quoteQualified } from '../schema-generator.js';
1
+ import { SchemaDialect, DialectName } from '../schema-dialect.js';
2
+ import { formatLiteral, quoteQualified, createLiteralFormatter, LiteralFormatter } from '../sql-writing.js';
2
3
  import { ColumnDef, ForeignKeyReference } from '../../../schema/column.js';
3
4
  import { IndexDef, TableDef } from '../../../schema/table.js';
4
- import { DatabaseTable } from '../schema-types.js';
5
+ import { DatabaseTable, DatabaseColumn, ColumnDiff } from '../schema-types.js';
5
6
 
6
7
  type TableLike = { name: string; schema?: string };
7
8
 
@@ -10,7 +11,7 @@ type TableLike = { name: string; schema?: string };
10
11
  * Concrete dialects only override the small surface area instead of reimplementing everything.
11
12
  */
12
13
  export abstract class BaseSchemaDialect implements SchemaDialect {
13
- abstract name: DialectName;
14
+ abstract readonly name: DialectName;
14
15
  abstract quoteIdentifier(id: string): string;
15
16
  abstract renderColumnType(column: ColumnDef): string;
16
17
  abstract renderAutoIncrement(column: ColumnDef, table: TableDef): string | undefined;
@@ -24,8 +25,11 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
24
25
  }
25
26
  return this.quoteIdentifier(table.name);
26
27
  }
28
+ // Each dialect should provide its own formatter
29
+ abstract get literalFormatter(): LiteralFormatter;
30
+
27
31
  renderDefault(value: unknown, _column: ColumnDef): string {
28
- return formatLiteral(value, this.name);
32
+ return formatLiteral(this.literalFormatter, value);
29
33
  }
30
34
  renderReference(ref: ForeignKeyReference, _table: TableDef): string {
31
35
  const parts = ['REFERENCES', quoteQualified(this, ref.table), `(${this.quoteIdentifier(ref.column)})`];
@@ -40,9 +44,19 @@ export abstract class BaseSchemaDialect implements SchemaDialect {
40
44
  dropTableSql(table: DatabaseTable): string[] {
41
45
  return [`DROP TABLE IF EXISTS ${this.formatTableName(table)};`];
42
46
  }
43
- abstract dropColumnSql(table: DatabaseTable, column: string): string[];
44
- abstract dropIndexSql(table: DatabaseTable, index: string): string[];
47
+ dropColumnSql(table: DatabaseTable, column: string): string[] {
48
+ return [`ALTER TABLE ${this.formatTableName(table)} DROP COLUMN ${this.quoteIdentifier(column)};`];
49
+ }
50
+ dropIndexSql(table: DatabaseTable, index: string): string[] {
51
+ return [`DROP INDEX ${this.quoteIdentifier(index)};`];
52
+ }
45
53
  warnDropColumn(_table: DatabaseTable, _column: string): string | undefined {
46
54
  return undefined;
47
55
  }
56
+ alterColumnSql?(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[] {
57
+ return [];
58
+ }
59
+ warnAlterColumn?(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
60
+ return undefined;
61
+ }
48
62
  }
@@ -1,17 +1,23 @@
1
1
  import { BaseSchemaDialect } from './base-schema-dialect.js';
2
- import {
3
- deriveIndexName,
4
- renderIndexColumns,
5
- DialectName,
6
- formatLiteral
7
- } from '../schema-generator.js';
2
+ import { deriveIndexName } from '../naming-strategy.js';
3
+ import { renderIndexColumns, createLiteralFormatter } from '../sql-writing.js';
8
4
  import { ColumnDef } from '../../../schema/column.js';
9
5
  import { IndexDef, TableDef } from '../../../schema/table.js';
10
- import { DatabaseTable } from '../schema-types.js';
6
+ import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
+ import { DialectName } from '../schema-dialect.js';
11
8
 
12
9
  export class MSSqlSchemaDialect extends BaseSchemaDialect {
13
10
  name: DialectName = 'mssql';
14
11
 
12
+ private _literalFormatter = createLiteralFormatter({
13
+ booleanTrue: '1',
14
+ booleanFalse: '0',
15
+ });
16
+
17
+ get literalFormatter() {
18
+ return this._literalFormatter;
19
+ }
20
+
15
21
  quoteIdentifier(id: string): string {
16
22
  return `[${id.replace(/]/g, ']]')}]`;
17
23
  }
@@ -68,7 +74,7 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
68
74
  }
69
75
 
70
76
  renderDefault(value: unknown): string {
71
- return formatLiteral(value, this.name);
77
+ return this.literalFormatter.formatLiteral(value);
72
78
  }
73
79
 
74
80
  renderAutoIncrement(column: ColumnDef): string | undefined {
@@ -94,4 +100,22 @@ export class MSSqlSchemaDialect extends BaseSchemaDialect {
94
100
  dropIndexSql(table: DatabaseTable, index: string): string[] {
95
101
  return [`DROP INDEX ${this.quoteIdentifier(index)} ON ${this.formatTableName(table)};`];
96
102
  }
103
+
104
+ alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string[] {
105
+ const stmts: string[] = [];
106
+ if (diff.typeChanged || diff.nullabilityChanged) {
107
+ const nullability = column.notNull ? 'NOT NULL' : 'NULL';
108
+ stmts.push(
109
+ `ALTER TABLE ${this.formatTableName(table)} ALTER COLUMN ${this.quoteIdentifier(column.name)} ${this.renderColumnType(column)} ${nullability};`
110
+ );
111
+ }
112
+ return stmts;
113
+ }
114
+
115
+ warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
116
+ if (diff.defaultChanged || diff.autoIncrementChanged) {
117
+ return 'Altering defaults or identity on MSSQL is not automated (requires dropping/adding default or identity constraints manually).';
118
+ }
119
+ return undefined;
120
+ }
97
121
  }
@@ -1,18 +1,24 @@
1
1
  import { BaseSchemaDialect } from './base-schema-dialect.js';
2
- import {
3
- deriveIndexName,
4
- renderIndexColumns,
5
- DialectName,
6
- formatLiteral,
7
- escapeLiteral
8
- } from '../schema-generator.js';
2
+ import { deriveIndexName } from '../naming-strategy.js';
3
+ import { renderIndexColumns, escapeSqlString, createLiteralFormatter } from '../sql-writing.js';
9
4
  import { ColumnDef } from '../../../schema/column.js';
10
5
  import { IndexDef, TableDef } from '../../../schema/table.js';
11
- import { DatabaseTable } from '../schema-types.js';
6
+ import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
+ import { renderColumnDefinition } from '../schema-generator.js';
8
+ import { DialectName } from '../schema-dialect.js';
12
9
 
13
10
  export class MySqlSchemaDialect extends BaseSchemaDialect {
14
11
  name: DialectName = 'mysql';
15
12
 
13
+ private _literalFormatter = createLiteralFormatter({
14
+ booleanTrue: '1',
15
+ booleanFalse: '0',
16
+ });
17
+
18
+ get literalFormatter() {
19
+ return this._literalFormatter;
20
+ }
21
+
16
22
  quoteIdentifier(id: string): string {
17
23
  return `\`${id}\``;
18
24
  }
@@ -66,7 +72,7 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
66
72
  case 'ENUM':
67
73
  case 'enum':
68
74
  return column.args && Array.isArray(column.args) && column.args.length
69
- ? `ENUM(${column.args.map((v: string) => `'${escapeLiteral(v)}'`).join(',')})`
75
+ ? `ENUM(${column.args.map((v: string) => `'${escapeSqlString(v)}'`).join(',')})`
70
76
  : 'ENUM';
71
77
  default:
72
78
  return String(column.type).toUpperCase();
@@ -74,7 +80,7 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
74
80
  }
75
81
 
76
82
  renderDefault(value: unknown): string {
77
- return formatLiteral(value, this.name);
83
+ return this.literalFormatter.formatLiteral(value);
78
84
  }
79
85
 
80
86
  renderAutoIncrement(column: ColumnDef): string | undefined {
@@ -106,4 +112,9 @@ export class MySqlSchemaDialect extends BaseSchemaDialect {
106
112
  dropIndexSql(table: DatabaseTable, index: string): string[] {
107
113
  return [`DROP INDEX ${this.quoteIdentifier(index)} ON ${this.formatTableName(table)};`];
108
114
  }
115
+
116
+ alterColumnSql(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
117
+ const rendered = renderColumnDefinition(table, column, this);
118
+ return [`ALTER TABLE ${this.formatTableName(table)} MODIFY COLUMN ${rendered.sql};`];
119
+ }
109
120
  }
@@ -1,15 +1,22 @@
1
1
  import { BaseSchemaDialect } from './base-schema-dialect.js';
2
- import {
3
- deriveIndexName,
4
- renderIndexColumns,
5
- DialectName
6
- } from '../schema-generator.js';
2
+ import { deriveIndexName } from '../naming-strategy.js';
3
+ import { renderIndexColumns, createLiteralFormatter } from '../sql-writing.js';
7
4
  import { ColumnDef } from '../../../schema/column.js';
8
5
  import { IndexDef, TableDef } from '../../../schema/table.js';
9
- import { DatabaseTable } from '../schema-types.js';
6
+ import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
+ import { DialectName } from '../schema-dialect.js';
10
8
 
11
9
  export class PostgresSchemaDialect extends BaseSchemaDialect {
12
- name: DialectName = 'postgres';
10
+ readonly name: DialectName = 'postgres';
11
+
12
+ private _literalFormatter = createLiteralFormatter({
13
+ booleanTrue: 'TRUE',
14
+ booleanFalse: 'FALSE',
15
+ });
16
+
17
+ get literalFormatter() {
18
+ return this._literalFormatter;
19
+ }
13
20
 
14
21
  quoteIdentifier(id: string): string {
15
22
  return `"${id}"`;
@@ -96,4 +103,42 @@ export class PostgresSchemaDialect extends BaseSchemaDialect {
96
103
  : this.quoteIdentifier(index);
97
104
  return [`DROP INDEX IF EXISTS ${qualified};`];
98
105
  }
106
+
107
+ alterColumnSql(table: TableDef, column: ColumnDef, actualColumn: DatabaseColumn, diff: ColumnDiff): string[] {
108
+ const stmts: string[] = [];
109
+ const tableName = this.formatTableName(table);
110
+ const colName = this.quoteIdentifier(column.name);
111
+
112
+ if (diff.typeChanged) {
113
+ stmts.push(`ALTER TABLE ${tableName} ALTER COLUMN ${colName} TYPE ${this.renderColumnType(column)};`);
114
+ }
115
+ if (diff.defaultChanged) {
116
+ if (column.default === undefined) {
117
+ stmts.push(`ALTER TABLE ${tableName} ALTER COLUMN ${colName} DROP DEFAULT;`);
118
+ } else {
119
+ stmts.push(
120
+ `ALTER TABLE ${tableName} ALTER COLUMN ${colName} SET DEFAULT ${this.renderDefault(column.default, column)};`
121
+ );
122
+ }
123
+ }
124
+ if (diff.nullabilityChanged) {
125
+ stmts.push(`ALTER TABLE ${tableName} ALTER COLUMN ${colName} ${column.notNull ? 'SET' : 'DROP'} NOT NULL;`);
126
+ }
127
+ if (diff.autoIncrementChanged) {
128
+ if (column.autoIncrement) {
129
+ const strategy = column.generated === 'always' ? 'ALWAYS' : 'BY DEFAULT';
130
+ stmts.push(`ALTER TABLE ${tableName} ALTER COLUMN ${colName} ADD GENERATED ${strategy} AS IDENTITY;`);
131
+ } else {
132
+ stmts.push(`ALTER TABLE ${tableName} ALTER COLUMN ${colName} DROP IDENTITY IF EXISTS;`);
133
+ }
134
+ }
135
+ return stmts;
136
+ }
137
+
138
+ warnAlterColumn(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, diff: ColumnDiff): string | undefined {
139
+ if (diff.autoIncrementChanged) {
140
+ return 'Altering identity properties may fail if an existing sequence is attached; verify generated column state.';
141
+ }
142
+ return undefined;
143
+ }
99
144
  }
@@ -1,18 +1,23 @@
1
1
  import { BaseSchemaDialect } from './base-schema-dialect.js';
2
- import {
3
- deriveIndexName,
4
- renderIndexColumns,
5
- DialectName,
6
- formatLiteral,
7
- resolvePrimaryKey
8
- } from '../schema-generator.js';
2
+ import { deriveIndexName } from '../naming-strategy.js';
3
+ import { renderIndexColumns, resolvePrimaryKey, createLiteralFormatter } from '../sql-writing.js';
9
4
  import { ColumnDef } from '../../../schema/column.js';
10
5
  import { IndexDef, TableDef } from '../../../schema/table.js';
11
- import { DatabaseTable } from '../schema-types.js';
6
+ import { ColumnDiff, DatabaseColumn, DatabaseTable } from '../schema-types.js';
7
+ import { DialectName } from '../schema-dialect.js';
12
8
 
13
9
  export class SQLiteSchemaDialect extends BaseSchemaDialect {
14
10
  name: DialectName = 'sqlite';
15
11
 
12
+ private _literalFormatter = createLiteralFormatter({
13
+ booleanTrue: '1',
14
+ booleanFalse: '0',
15
+ });
16
+
17
+ get literalFormatter() {
18
+ return this._literalFormatter;
19
+ }
20
+
16
21
  quoteIdentifier(id: string): string {
17
22
  return `"${id}"`;
18
23
  }
@@ -75,7 +80,7 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
75
80
  }
76
81
 
77
82
  renderDefault(value: unknown): string {
78
- return formatLiteral(value, this.name);
83
+ return this.literalFormatter.formatLiteral(value);
79
84
  }
80
85
 
81
86
  renderIndex(table: TableDef, index: IndexDef): string {
@@ -100,4 +105,13 @@ export class SQLiteSchemaDialect extends BaseSchemaDialect {
100
105
  const key = table.schema ? `${table.schema}.${table.name}` : table.name;
101
106
  return `Dropping columns on SQLite requires table rebuild (column ${column} on ${key}).`;
102
107
  }
108
+
109
+ alterColumnSql(_table: TableDef, _column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string[] {
110
+ return [];
111
+ }
112
+
113
+ warnAlterColumn(table: TableDef, column: ColumnDef, _actual: DatabaseColumn, _diff: ColumnDiff): string | undefined {
114
+ const key = table.schema ? `${table.schema}.${table.name}` : table.name;
115
+ return `SQLite ALTER COLUMN is not supported; rebuild table ${key} to change column ${column.name}.`;
116
+ }
103
117
  }