metal-orm 1.0.56 → 1.0.57

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 (72) hide show
  1. package/README.md +21 -20
  2. package/dist/index.cjs +821 -112
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +429 -70
  5. package/dist/index.d.ts +429 -70
  6. package/dist/index.js +785 -112
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/codegen/naming-strategy.ts +3 -1
  10. package/src/codegen/typescript.ts +20 -10
  11. package/src/core/ast/aggregate-functions.ts +14 -0
  12. package/src/core/ast/builders.ts +38 -20
  13. package/src/core/ast/expression-builders.ts +70 -2
  14. package/src/core/ast/expression-nodes.ts +305 -274
  15. package/src/core/ast/expression-visitor.ts +11 -1
  16. package/src/core/ast/expression.ts +4 -0
  17. package/src/core/ast/query.ts +3 -0
  18. package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
  19. package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
  20. package/src/core/ddl/introspect/functions/mssql.ts +13 -0
  21. package/src/core/ddl/introspect/mssql.ts +4 -0
  22. package/src/core/ddl/introspect/mysql.ts +4 -0
  23. package/src/core/ddl/introspect/sqlite.ts +4 -0
  24. package/src/core/dialect/abstract.ts +552 -531
  25. package/src/core/dialect/base/function-table-formatter.ts +9 -30
  26. package/src/core/dialect/base/sql-dialect.ts +24 -0
  27. package/src/core/dialect/mssql/functions.ts +40 -2
  28. package/src/core/dialect/mysql/functions.ts +16 -2
  29. package/src/core/dialect/postgres/functions.ts +66 -2
  30. package/src/core/dialect/postgres/index.ts +17 -4
  31. package/src/core/dialect/postgres/table-functions.ts +27 -0
  32. package/src/core/dialect/sqlite/functions.ts +34 -0
  33. package/src/core/dialect/sqlite/index.ts +17 -1
  34. package/src/core/driver/database-driver.ts +9 -1
  35. package/src/core/driver/mssql-driver.ts +3 -0
  36. package/src/core/driver/mysql-driver.ts +3 -0
  37. package/src/core/driver/postgres-driver.ts +3 -0
  38. package/src/core/driver/sqlite-driver.ts +3 -0
  39. package/src/core/execution/executors/mssql-executor.ts +5 -0
  40. package/src/core/execution/executors/mysql-executor.ts +5 -0
  41. package/src/core/execution/executors/postgres-executor.ts +5 -0
  42. package/src/core/execution/executors/sqlite-executor.ts +5 -0
  43. package/src/core/functions/array.ts +26 -0
  44. package/src/core/functions/control-flow.ts +69 -0
  45. package/src/core/functions/datetime.ts +50 -0
  46. package/src/core/functions/definitions/aggregate.ts +16 -0
  47. package/src/core/functions/definitions/control-flow.ts +24 -0
  48. package/src/core/functions/definitions/datetime.ts +36 -0
  49. package/src/core/functions/definitions/helpers.ts +29 -0
  50. package/src/core/functions/definitions/json.ts +49 -0
  51. package/src/core/functions/definitions/numeric.ts +55 -0
  52. package/src/core/functions/definitions/string.ts +43 -0
  53. package/src/core/functions/function-registry.ts +48 -0
  54. package/src/core/functions/group-concat-helpers.ts +57 -0
  55. package/src/core/functions/json.ts +38 -0
  56. package/src/core/functions/numeric.ts +14 -0
  57. package/src/core/functions/standard-strategy.ts +86 -115
  58. package/src/core/functions/standard-table-strategy.ts +13 -0
  59. package/src/core/functions/table-types.ts +15 -0
  60. package/src/core/functions/text.ts +57 -0
  61. package/src/core/sql/sql.ts +59 -38
  62. package/src/decorators/bootstrap.ts +5 -4
  63. package/src/index.ts +18 -11
  64. package/src/orm/hydration-context.ts +10 -0
  65. package/src/orm/identity-map.ts +19 -0
  66. package/src/orm/interceptor-pipeline.ts +4 -0
  67. package/src/orm/relations/belongs-to.ts +17 -0
  68. package/src/orm/relations/has-one.ts +17 -0
  69. package/src/orm/relations/many-to-many.ts +41 -0
  70. package/src/query-builder/select.ts +68 -68
  71. package/src/schema/table-guards.ts +6 -0
  72. package/src/schema/types.ts +8 -1
@@ -1,17 +1,7 @@
1
1
  import { CompilerContext } from '../abstract.js';
2
- import { OperandNode } from '../../ast/expression.js';
3
- import { SqlDialectBase } from './sql-dialect.js';
4
-
5
- export interface FunctionTableNode {
6
- type: 'FunctionTable';
7
- schema?: string;
8
- name: string;
9
- args?: unknown[];
10
- lateral?: boolean;
11
- withOrdinality?: boolean;
12
- alias?: string;
13
- columnAliases?: string[];
14
- }
2
+ import { OperandNode } from '../../ast/expression.js';
3
+ import { FunctionTableNode } from '../../ast/query.js';
4
+ import { SqlDialectBase } from './sql-dialect.js';
15
5
 
16
6
  /**
17
7
  * Formatter for function table expressions (e.g., LATERAL unnest(...) WITH ORDINALITY).
@@ -25,10 +15,10 @@ export class FunctionTableFormatter {
25
15
  * @param dialect - The dialect instance for compiling operands.
26
16
  * @returns SQL function table expression (e.g., "LATERAL schema.func(args) WITH ORDINALITY AS alias(col1, col2)").
27
17
  */
28
- static format(fn: FunctionTableNode, ctx?: CompilerContext, dialect?: SqlDialectBase): string {
18
+ static format(fn: FunctionTableNode, ctx?: CompilerContext, dialect?: SqlDialectBase): string {
29
19
  const schemaPart = this.formatSchema(fn, dialect);
30
20
  const args = this.formatArgs(fn, ctx, dialect);
31
- const base = this.formatBase(fn, schemaPart, args, dialect);
21
+ const base = this.formatBase(fn, schemaPart, args);
32
22
  const lateral = this.formatLateral(fn);
33
23
  const alias = this.formatAlias(fn, dialect);
34
24
  const colAliases = this.formatColumnAliases(fn, dialect);
@@ -76,11 +66,10 @@ export class FunctionTableFormatter {
76
66
  * @returns Base function call expression (e.g., "schema.func(args) WITH ORDINALITY").
77
67
  * @internal
78
68
  */
79
- private static formatBase(fn: FunctionTableNode, schemaPart: string, args: string, dialect?: SqlDialectBase): string {
80
- const ordinality = fn.withOrdinality ? ' WITH ORDINALITY' : '';
81
- const quoted = dialect ? dialect.quoteIdentifier(fn.name) : fn.name;
82
- return `${schemaPart}${quoted}(${args})${ordinality}`;
83
- }
69
+ private static formatBase(fn: FunctionTableNode, schemaPart: string, args: string): string {
70
+ const ordinality = fn.withOrdinality ? ' WITH ORDINALITY' : '';
71
+ return `${schemaPart}${fn.name}(${args})${ordinality}`;
72
+ }
84
73
 
85
74
  /**
86
75
  * Formats the LATERAL keyword if present.
@@ -121,13 +110,3 @@ export class FunctionTableFormatter {
121
110
  }
122
111
  }
123
112
 
124
- export interface FunctionTableNode {
125
- type: 'FunctionTable';
126
- schema?: string;
127
- name: string;
128
- args?: unknown[];
129
- lateral?: boolean;
130
- withOrdinality?: boolean;
131
- alias?: string;
132
- columnAliases?: string[];
133
- }
@@ -21,6 +21,11 @@ import { GroupByCompiler } from './groupby-compiler.js';
21
21
  import { OrderByCompiler } from './orderby-compiler.js';
22
22
 
23
23
 
24
+ /**
25
+ * Base class for SQL dialects.
26
+ * Provides a common framework for compiling AST nodes into SQL strings.
27
+ * Specific dialects should extend this class and implement dialect-specific logic.
28
+ */
24
29
  export abstract class SqlDialectBase extends Dialect {
25
30
  abstract quoteIdentifier(id: string): string;
26
31
 
@@ -201,6 +206,25 @@ export abstract class SqlDialectBase extends Dialect {
201
206
  }
202
207
 
203
208
  protected compileFunctionTable(fn: FunctionTableNode, ctx?: CompilerContext): string {
209
+ const key = fn.key ?? fn.name;
210
+
211
+ if (ctx) {
212
+ const renderer = this.tableFunctionStrategy.getRenderer(key);
213
+ if (renderer) {
214
+ const compiledArgs = (fn.args ?? []).map(arg => this.compileOperand(arg, ctx));
215
+ return renderer({
216
+ node: fn,
217
+ compiledArgs,
218
+ compileOperand: operand => this.compileOperand(operand, ctx),
219
+ quoteIdentifier: this.quoteIdentifier.bind(this)
220
+ });
221
+ }
222
+
223
+ if (fn.key) {
224
+ throw new Error(`Table function "${key}" is not supported by dialect "${this.dialect}".`);
225
+ }
226
+ }
227
+
204
228
  return FunctionTableFormatter.format(fn, ctx, this);
205
229
  }
206
230
 
@@ -1,6 +1,10 @@
1
1
  import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
2
2
  import { LiteralNode } from '../../ast/expression.js';
3
3
 
4
+ /**
5
+ * Microsoft SQL Server specific function strategy.
6
+ * Implements and overrides SQL function compilation rules for MSSQL.
7
+ */
4
8
  export class MssqlFunctionStrategy extends StandardFunctionStrategy {
5
9
  constructor() {
6
10
  super();
@@ -105,5 +109,39 @@ export class MssqlFunctionStrategy extends StandardFunctionStrategy {
105
109
  const withinGroup = orderClause ? ` WITHIN GROUP (${orderClause})` : '';
106
110
  return `STRING_AGG(${arg}, ${separator})${withinGroup}`;
107
111
  });
108
- }
109
- }
112
+
113
+ this.add('LENGTH', ({ compiledArgs }) => `LEN(${compiledArgs[0]})`);
114
+ this.add('CHAR_LENGTH', ({ compiledArgs }) => `LEN(${compiledArgs[0]})`);
115
+ this.add('CHARACTER_LENGTH', ({ compiledArgs }) => `LEN(${compiledArgs[0]})`);
116
+ this.add('POSITION', ({ compiledArgs }) => `CHARINDEX(${compiledArgs[0]}, ${compiledArgs[1]})`);
117
+ this.add('LOCATE', ({ compiledArgs }) => compiledArgs.length === 3 ? `CHARINDEX(${compiledArgs[0]}, ${compiledArgs[1]}, ${compiledArgs[2]})` : `CHARINDEX(${compiledArgs[0]}, ${compiledArgs[1]})`);
118
+ this.add('INSTR', ({ compiledArgs }) => `CHARINDEX(${compiledArgs[1]}, ${compiledArgs[0]})`);
119
+ this.add('CHR', ({ compiledArgs }) => `CHAR(${compiledArgs[0]})`);
120
+
121
+ this.add('HOUR', ({ compiledArgs }) => `DATEPART(hour, ${compiledArgs[0]})`);
122
+ this.add('MINUTE', ({ compiledArgs }) => `DATEPART(minute, ${compiledArgs[0]})`);
123
+ this.add('SECOND', ({ compiledArgs }) => `DATEPART(second, ${compiledArgs[0]})`);
124
+ this.add('QUARTER', ({ compiledArgs }) => `DATEPART(quarter, ${compiledArgs[0]})`);
125
+
126
+ this.add('JSON_SET', ({ compiledArgs }) => {
127
+ if (compiledArgs.length !== 3) throw new Error('JSON_SET expects 3 arguments on SQL Server');
128
+ return `JSON_MODIFY(${compiledArgs[0]}, ${compiledArgs[1]}, ${compiledArgs[2]})`;
129
+ });
130
+
131
+ this.add('JSON_LENGTH', () => {
132
+ throw new Error('JSON_LENGTH is not supported on SQL Server');
133
+ });
134
+
135
+ this.add('JSON_ARRAYAGG', () => {
136
+ throw new Error('JSON_ARRAYAGG is not supported on SQL Server');
137
+ });
138
+
139
+ this.add('JSON_CONTAINS', () => {
140
+ throw new Error('JSON_CONTAINS is not supported on SQL Server');
141
+ });
142
+
143
+ this.add('ARRAY_APPEND', () => {
144
+ throw new Error('ARRAY_APPEND is not supported on SQL Server');
145
+ });
146
+ }
147
+ }
@@ -1,6 +1,10 @@
1
1
  import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
2
2
  import { LiteralNode } from '../../ast/expression.js';
3
3
 
4
+ /**
5
+ * MySQL/MariaDB specific function strategy.
6
+ * Implements and overrides SQL function compilation rules for MySQL.
7
+ */
4
8
  export class MysqlFunctionStrategy extends StandardFunctionStrategy {
5
9
  constructor() {
6
10
  super();
@@ -96,5 +100,15 @@ export class MysqlFunctionStrategy extends StandardFunctionStrategy {
96
100
  }
97
101
  return `DATE(${date})`;
98
102
  });
99
- }
100
- }
103
+
104
+ this.add('HOUR', ({ compiledArgs }) => `HOUR(${compiledArgs[0]})`);
105
+ this.add('MINUTE', ({ compiledArgs }) => `MINUTE(${compiledArgs[0]})`);
106
+ this.add('SECOND', ({ compiledArgs }) => `SECOND(${compiledArgs[0]})`);
107
+ this.add('QUARTER', ({ compiledArgs }) => `QUARTER(${compiledArgs[0]})`);
108
+
109
+ this.add('ARRAY_APPEND', ({ compiledArgs }) => {
110
+ if (compiledArgs.length !== 2) throw new Error('ARRAY_APPEND expects 2 arguments (array, value)');
111
+ return `JSON_ARRAY_APPEND(${compiledArgs[0]}, '$', ${compiledArgs[1]})`;
112
+ });
113
+ }
114
+ }
@@ -1,6 +1,10 @@
1
1
  import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
2
2
  import { LiteralNode } from '../../ast/expression.js';
3
3
 
4
+ /**
5
+ * PostgreSQL specific function strategy.
6
+ * Implements and overrides SQL function compilation rules for PostgreSQL.
7
+ */
4
8
  export class PostgresFunctionStrategy extends StandardFunctionStrategy {
5
9
  constructor() {
6
10
  super();
@@ -99,5 +103,65 @@ export class PostgresFunctionStrategy extends StandardFunctionStrategy {
99
103
  const separator = ctx.compileOperand(separatorOperand);
100
104
  return `STRING_AGG(${arg}, ${separator}${orderSegment})`;
101
105
  });
102
- }
103
- }
106
+
107
+ this.add('CHR', ({ compiledArgs }) => `CHR(${compiledArgs[0]})`);
108
+
109
+ this.add('HOUR', ({ compiledArgs }) => `EXTRACT(HOUR FROM ${compiledArgs[0]})`);
110
+ this.add('MINUTE', ({ compiledArgs }) => `EXTRACT(MINUTE FROM ${compiledArgs[0]})`);
111
+ this.add('SECOND', ({ compiledArgs }) => `EXTRACT(SECOND FROM ${compiledArgs[0]})`);
112
+ this.add('QUARTER', ({ compiledArgs }) => `EXTRACT(QUARTER FROM ${compiledArgs[0]})`);
113
+
114
+ this.add('JSON_LENGTH', ({ compiledArgs }) => {
115
+ if (compiledArgs.length !== 1) throw new Error('JSON_LENGTH expects 1 argument on PostgreSQL');
116
+ return `jsonb_array_length(${compiledArgs[0]})`;
117
+ });
118
+
119
+ this.add('JSON_ARRAYAGG', ({ compiledArgs }) => {
120
+ if (compiledArgs.length !== 1) throw new Error('JSON_ARRAYAGG expects 1 argument on PostgreSQL');
121
+ return `jsonb_agg(${compiledArgs[0]})`;
122
+ });
123
+
124
+ this.add('JSON_CONTAINS', ({ compiledArgs }) => {
125
+ if (compiledArgs.length !== 2) throw new Error('JSON_CONTAINS expects 2 arguments on PostgreSQL');
126
+ return `(${compiledArgs[0]}::jsonb @> ${compiledArgs[1]}::jsonb)`;
127
+ });
128
+
129
+ this.add('ARRAY_APPEND', ({ compiledArgs }) => {
130
+ if (compiledArgs.length !== 2) throw new Error('ARRAY_APPEND expects 2 arguments on PostgreSQL');
131
+ return `array_append(${compiledArgs[0]}, ${compiledArgs[1]})`;
132
+ });
133
+
134
+ this.add('JSON_SET', ({ node, compiledArgs }) => {
135
+ if (compiledArgs.length !== 3) throw new Error('JSON_SET expects exactly 3 arguments on PostgreSQL');
136
+ const pathNode = node.args[1];
137
+ if (pathNode.type !== 'Literal') {
138
+ throw new Error('PostgreSQL JSON_SET currently supports literal paths only');
139
+ }
140
+ const pathArray = this.formatJsonbPathArray(pathNode);
141
+ return `jsonb_set(${compiledArgs[0]}, ${pathArray}, ${compiledArgs[2]}::jsonb, true)`;
142
+ });
143
+ }
144
+
145
+ private formatJsonbPathArray(pathNode: LiteralNode): string {
146
+ const rawPath = String(pathNode.value ?? '');
147
+ if (!rawPath.startsWith('$')) {
148
+ throw new Error('PostgreSQL JSON_SET paths must start with "$"');
149
+ }
150
+ const trimmed = rawPath === '$' ? '' : rawPath.startsWith('$.') ? rawPath.slice(2) : rawPath.slice(1);
151
+ if (!trimmed) {
152
+ throw new Error('PostgreSQL JSON_SET requires a non-root path');
153
+ }
154
+ if (trimmed.includes('[') || trimmed.includes(']')) {
155
+ throw new Error('PostgreSQL JSON_SET currently only supports simple dot-separated paths');
156
+ }
157
+ const segments = trimmed
158
+ .split('.')
159
+ .map(segment => segment.replace(/^['"]?/, '').replace(/['"]?$/, '').trim())
160
+ .filter(Boolean);
161
+ if (!segments.length) {
162
+ throw new Error('PostgreSQL JSON_SET requires at least one path segment');
163
+ }
164
+ const escapedSegments = segments.map(segment => `'${segment.replace(/'/g, "''")}'`);
165
+ return `ARRAY[${escapedSegments.join(', ')}]`;
166
+ }
167
+ }
@@ -1,7 +1,8 @@
1
1
  import { CompilerContext } from '../abstract.js';
2
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
3
- import { SqlDialectBase } from '../base/sql-dialect.js';
4
- import { PostgresFunctionStrategy } from './functions.js';
2
+ import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
+ import { SqlDialectBase } from '../base/sql-dialect.js';
4
+ import { PostgresFunctionStrategy } from './functions.js';
5
+ import { PostgresTableFunctionStrategy } from './table-functions.js';
5
6
 
6
7
  /**
7
8
  * PostgreSQL dialect implementation
@@ -12,7 +13,19 @@ export class PostgresDialect extends SqlDialectBase {
12
13
  * Creates a new PostgresDialect instance
13
14
  */
14
15
  public constructor() {
15
- super(new PostgresFunctionStrategy());
16
+ super(new PostgresFunctionStrategy(), new PostgresTableFunctionStrategy());
17
+ this.registerExpressionCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
18
+ const left = this.compileOperand(node.left, ctx);
19
+ const right = this.compileOperand(node.right, ctx);
20
+ const op = node.operator === '^' ? '#' : node.operator;
21
+ return `${left} ${op} ${right}`;
22
+ });
23
+ this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
24
+ const left = this.compileOperand(node.left, ctx);
25
+ const right = this.compileOperand(node.right, ctx);
26
+ const op = node.operator === '^' ? '#' : node.operator;
27
+ return `(${left} ${op} ${right})`;
28
+ });
16
29
  }
17
30
 
18
31
  /**
@@ -0,0 +1,27 @@
1
+ import { StandardTableFunctionStrategy } from '../../functions/standard-table-strategy.js';
2
+
3
+ export class PostgresTableFunctionStrategy extends StandardTableFunctionStrategy {
4
+ constructor() {
5
+ super();
6
+ this.registerOverrides();
7
+ }
8
+
9
+ private registerOverrides() {
10
+ this.add('ARRAY_UNNEST', ({ node, compiledArgs, quoteIdentifier }) => {
11
+ const lateral = node.lateral ?? true;
12
+ const withOrd = node.withOrdinality ?? false;
13
+ const base = `unnest(${compiledArgs.join(', ')})${withOrd ? ' WITH ORDINALITY' : ''}`;
14
+
15
+ if (node.columnAliases?.length && !node.alias) {
16
+ throw new Error('tvf(ARRAY_UNNEST) with columnAliases requires an alias.');
17
+ }
18
+
19
+ const alias = node.alias ? ` AS ${quoteIdentifier(node.alias)}` : '';
20
+ const cols = node.columnAliases?.length
21
+ ? `(${node.columnAliases.map(quoteIdentifier).join(', ')})`
22
+ : '';
23
+
24
+ return `${lateral ? 'LATERAL ' : ''}${base}${alias}${cols}`;
25
+ });
26
+ }
27
+ }
@@ -1,6 +1,10 @@
1
1
  import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
2
2
  import { LiteralNode } from '../../ast/expression.js';
3
3
 
4
+ /**
5
+ * SQLite specific function strategy.
6
+ * Implements and overrides SQL function compilation rules for SQLite.
7
+ */
4
8
  export class SqliteFunctionStrategy extends StandardFunctionStrategy {
5
9
  constructor() {
6
10
  super();
@@ -117,5 +121,35 @@ export class SqliteFunctionStrategy extends StandardFunctionStrategy {
117
121
  const separator = ctx.compileOperand(separatorOperand);
118
122
  return `GROUP_CONCAT(${arg}, ${separator})`;
119
123
  });
124
+
125
+ this.add('HOUR', ({ compiledArgs }) => `CAST(strftime('%H', ${compiledArgs[0]}) AS INTEGER)`);
126
+ this.add('MINUTE', ({ compiledArgs }) => `CAST(strftime('%M', ${compiledArgs[0]}) AS INTEGER)`);
127
+ this.add('SECOND', ({ compiledArgs }) => `CAST(strftime('%S', ${compiledArgs[0]}) AS INTEGER)`);
128
+ this.add('QUARTER', ({ compiledArgs }) => `((CAST(strftime('%m', ${compiledArgs[0]}) AS INTEGER) + 2) / 3)`);
129
+
130
+ this.add('JSON_LENGTH', ({ compiledArgs }) => {
131
+ if (compiledArgs.length === 0 || compiledArgs.length > 2) {
132
+ throw new Error('JSON_LENGTH expects 1 or 2 arguments on SQLite');
133
+ }
134
+ return `json_array_length(${compiledArgs.join(', ')})`;
135
+ });
136
+
137
+ this.add('JSON_ARRAYAGG', ({ compiledArgs }) => {
138
+ if (compiledArgs.length !== 1) {
139
+ throw new Error('JSON_ARRAYAGG expects 1 argument on SQLite');
140
+ }
141
+ return `json_group_array(${compiledArgs[0]})`;
142
+ });
143
+
144
+ this.add('JSON_CONTAINS', () => {
145
+ throw new Error('JSON_CONTAINS is not supported on SQLite');
146
+ });
147
+
148
+ this.add('ARRAY_APPEND', ({ compiledArgs }) => {
149
+ if (compiledArgs.length !== 2) throw new Error('ARRAY_APPEND expects 2 arguments (array, value)');
150
+ return `json_array_append(${compiledArgs[0]}, '$', ${compiledArgs[1]})`;
151
+ });
152
+
153
+ this.add('CHR', ({ compiledArgs }) => `CHAR(${compiledArgs[0]})`);
120
154
  }
121
155
  }
@@ -1,5 +1,5 @@
1
1
  import { CompilerContext } from '../abstract.js';
2
- import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
2
+ import { JsonPathNode, ColumnNode, BitwiseExpressionNode } from '../../ast/expression.js';
3
3
  import { TableNode } from '../../ast/query.js';
4
4
  import { SqlDialectBase } from '../base/sql-dialect.js';
5
5
  import { SqliteFunctionStrategy } from './functions.js';
@@ -14,6 +14,22 @@ export class SqliteDialect extends SqlDialectBase {
14
14
  */
15
15
  public constructor() {
16
16
  super(new SqliteFunctionStrategy());
17
+ this.registerExpressionCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
18
+ const left = this.compileOperand(node.left, ctx);
19
+ const right = this.compileOperand(node.right, ctx);
20
+ if (node.operator === '^') {
21
+ return `(${left} | ${right}) & ~(${left} & ${right})`;
22
+ }
23
+ return `${left} ${node.operator} ${right}`;
24
+ });
25
+ this.registerOperandCompiler('BitwiseExpression', (node: BitwiseExpressionNode, ctx) => {
26
+ const left = this.compileOperand(node.left, ctx);
27
+ const right = this.compileOperand(node.right, ctx);
28
+ if (node.operator === '^') {
29
+ return `((${left} | ${right}) & ~(${left} & ${right}))`;
30
+ }
31
+ return `(${left} ${node.operator} ${right})`;
32
+ });
17
33
  }
18
34
 
19
35
  /**
@@ -2,10 +2,18 @@ import type { Dialect } from '../dialect/abstract.js';
2
2
  import type { SchemaDialect } from '../ddl/schema-dialect.js';
3
3
  import type { SchemaIntrospector } from '../ddl/schema-introspect.js';
4
4
 
5
+ /**
6
+ * Interface for database drivers.
7
+ * A driver provides access to dialect-specific compilers and introspectors.
8
+ */
5
9
  export interface DatabaseDriver {
6
- readonly name: string; // e.g. "postgres"
10
+ /** The name of the driver (e.g., "postgres", "mysql"). */
11
+ readonly name: string;
7
12
 
13
+ /** Creates a query compiler dialect. */
8
14
  createDialect(): Dialect;
15
+ /** Creates a DDL/schema dialect. */
9
16
  createSchemaDialect(): SchemaDialect;
17
+ /** Creates a schema introspector. */
10
18
  createIntrospector(): SchemaIntrospector;
11
19
  }
@@ -3,6 +3,9 @@ import { SqlServerDialect } from '../dialect/mssql/index.js';
3
3
  import { MSSqlSchemaDialect } from '../ddl/dialects/mssql-schema-dialect.js';
4
4
  import { mssqlIntrospector } from '../ddl/introspect/mssql.js';
5
5
 
6
+ /**
7
+ * Database driver for Microsoft SQL Server.
8
+ */
6
9
  export class MssqlDriver implements DatabaseDriver {
7
10
  readonly name = 'mssql';
8
11
 
@@ -3,6 +3,9 @@ import { MySqlDialect } from '../dialect/mysql/index.js';
3
3
  import { MySqlSchemaDialect } from '../ddl/dialects/mysql-schema-dialect.js';
4
4
  import { mysqlIntrospector } from '../ddl/introspect/mysql.js';
5
5
 
6
+ /**
7
+ * Database driver for MySQL.
8
+ */
6
9
  export class MySqlDriver implements DatabaseDriver {
7
10
  readonly name = 'mysql';
8
11
 
@@ -3,6 +3,9 @@ import { PostgresDialect } from '../dialect/postgres/index.js';
3
3
  import { PostgresSchemaDialect } from '../ddl/dialects/postgres-schema-dialect.js';
4
4
  import { postgresIntrospector } from '../ddl/introspect/postgres.js';
5
5
 
6
+ /**
7
+ * Database driver for PostgreSQL.
8
+ */
6
9
  export class PostgresDriver implements DatabaseDriver {
7
10
  readonly name = 'postgres';
8
11
 
@@ -3,6 +3,9 @@ import { SqliteDialect } from '../dialect/sqlite/index.js';
3
3
  import { SQLiteSchemaDialect } from '../ddl/dialects/sqlite-schema-dialect.js';
4
4
  import { sqliteIntrospector } from '../ddl/introspect/sqlite.js';
5
5
 
6
+ /**
7
+ * Database driver for SQLite.
8
+ */
6
9
  export class SqliteDriver implements DatabaseDriver {
7
10
  readonly name = 'sqlite';
8
11
 
@@ -14,6 +14,11 @@ export interface MssqlClientLike {
14
14
  rollback?(): Promise<void>;
15
15
  }
16
16
 
17
+ /**
18
+ * Creates a database executor for Microsoft SQL Server.
19
+ * @param client A SQL Server client instance.
20
+ * @returns A DbExecutor implementation for MSSQL.
21
+ */
17
22
  export function createMssqlExecutor(
18
23
  client: MssqlClientLike
19
24
  ): DbExecutor {
@@ -14,6 +14,11 @@ export interface MysqlClientLike {
14
14
  rollback?(): Promise<void>;
15
15
  }
16
16
 
17
+ /**
18
+ * Creates a database executor for MySQL.
19
+ * @param client A MySQL client instance.
20
+ * @returns A DbExecutor implementation for MySQL.
21
+ */
17
22
  export function createMysqlExecutor(
18
23
  client: MysqlClientLike
19
24
  ): DbExecutor {
@@ -11,6 +11,11 @@ export interface PostgresClientLike {
11
11
  ): Promise<{ rows: Array<Record<string, unknown>> }>;
12
12
  }
13
13
 
14
+ /**
15
+ * Creates a database executor for PostgreSQL.
16
+ * @param client A PostgreSQL client or pool instance.
17
+ * @returns A DbExecutor implementation for Postgres.
18
+ */
14
19
  export function createPostgresExecutor(
15
20
  client: PostgresClientLike
16
21
  ): DbExecutor {
@@ -15,6 +15,11 @@ export interface SqliteClientLike {
15
15
  rollbackTransaction?(): Promise<void>;
16
16
  }
17
17
 
18
+ /**
19
+ * Creates a database executor for SQLite.
20
+ * @param client A SQLite client instance.
21
+ * @returns A DbExecutor implementation for SQLite.
22
+ */
18
23
  export function createSqliteExecutor(
19
24
  client: SqliteClientLike
20
25
  ): DbExecutor {
@@ -0,0 +1,26 @@
1
+ // Pure AST Builders - No Dialect Logic Here!
2
+
3
+ import { ColumnDef } from '../../schema/column-types.js';
4
+ import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
+ import { FunctionNode, OperandNode, isOperandNode } from '../ast/expression.js';
6
+
7
+ type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
+
9
+ const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
+
11
+ const toOperand = (input: OperandInput): OperandNode => {
12
+ if (isOperandNode(input)) return input;
13
+ if (isColumnDef(input)) return columnOperand(input);
14
+
15
+ return valueToOperand(input);
16
+ };
17
+
18
+ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
+ type: 'Function',
20
+ name: key,
21
+ fn: key,
22
+ args: args.map(toOperand)
23
+ });
24
+
25
+ export const arrayAppend = (array: OperandInput, value: OperandInput): FunctionNode =>
26
+ fn('ARRAY_APPEND', [array, value]);
@@ -0,0 +1,69 @@
1
+ // Pure AST Builders - No Dialect Logic Here!
2
+
3
+ import { ColumnDef } from '../../schema/column-types.js';
4
+ import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
+ import { FunctionNode, OperandNode, isOperandNode } from '../ast/expression.js';
6
+
7
+ type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
+
9
+ const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
+
11
+ const toOperand = (input: OperandInput): OperandNode => {
12
+ if (isOperandNode(input)) return input;
13
+ if (isColumnDef(input)) return columnOperand(input);
14
+
15
+ return valueToOperand(input);
16
+ };
17
+
18
+ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
+ type: 'Function',
20
+ name: key,
21
+ fn: key,
22
+ args: args.map(toOperand)
23
+ });
24
+
25
+ /**
26
+ * Returns the first non-null value in a list.
27
+ * @param args - The list of values to check.
28
+ * @returns A FunctionNode representing the COALESCE SQL function.
29
+ */
30
+ export const coalesce = (...args: OperandInput[]): FunctionNode => {
31
+ if (args.length < 2) throw new Error('coalesce() expects at least 2 arguments');
32
+ return fn('COALESCE', args);
33
+ };
34
+
35
+ /**
36
+ * Returns null if the two arguments are equal, otherwise returns the first argument.
37
+ * @param val1 - The first value.
38
+ * @param val2 - The second value.
39
+ * @returns A FunctionNode representing the NULLIF SQL function.
40
+ */
41
+ export const nullif = (val1: OperandInput, val2: OperandInput): FunctionNode => fn('NULLIF', [val1, val2]);
42
+
43
+ /**
44
+ * Returns the largest value in a list.
45
+ * @param args - The list of values to compare.
46
+ * @returns A FunctionNode representing the GREATEST SQL function.
47
+ */
48
+ export const greatest = (...args: OperandInput[]): FunctionNode => {
49
+ if (args.length < 2) throw new Error('greatest() expects at least 2 arguments');
50
+ return fn('GREATEST', args);
51
+ };
52
+
53
+ /**
54
+ * Returns the smallest value in a list.
55
+ * @param args - The list of values to compare.
56
+ * @returns A FunctionNode representing the LEAST SQL function.
57
+ */
58
+ export const least = (...args: OperandInput[]): FunctionNode => {
59
+ if (args.length < 2) throw new Error('least() expects at least 2 arguments');
60
+ return fn('LEAST', args);
61
+ };
62
+
63
+ /**
64
+ * Returns the first argument if it is not null, otherwise returns the second argument.
65
+ * @param val - The value to check.
66
+ * @param defaultValue - The default value to return if val is null.
67
+ * @returns A FunctionNode representing the COALESCE SQL function.
68
+ */
69
+ export const ifNull = (val: OperandInput, defaultValue: OperandInput): FunctionNode => coalesce(val, defaultValue);