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.
- package/README.md +21 -20
- package/dist/index.cjs +821 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +429 -70
- package/dist/index.d.ts +429 -70
- package/dist/index.js +785 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/naming-strategy.ts +3 -1
- package/src/codegen/typescript.ts +20 -10
- package/src/core/ast/aggregate-functions.ts +14 -0
- package/src/core/ast/builders.ts +38 -20
- package/src/core/ast/expression-builders.ts +70 -2
- package/src/core/ast/expression-nodes.ts +305 -274
- package/src/core/ast/expression-visitor.ts +11 -1
- package/src/core/ast/expression.ts +4 -0
- package/src/core/ast/query.ts +3 -0
- package/src/core/ddl/introspect/catalogs/mysql.ts +5 -0
- package/src/core/ddl/introspect/catalogs/sqlite.ts +3 -0
- package/src/core/ddl/introspect/functions/mssql.ts +13 -0
- package/src/core/ddl/introspect/mssql.ts +4 -0
- package/src/core/ddl/introspect/mysql.ts +4 -0
- package/src/core/ddl/introspect/sqlite.ts +4 -0
- package/src/core/dialect/abstract.ts +552 -531
- package/src/core/dialect/base/function-table-formatter.ts +9 -30
- package/src/core/dialect/base/sql-dialect.ts +24 -0
- package/src/core/dialect/mssql/functions.ts +40 -2
- package/src/core/dialect/mysql/functions.ts +16 -2
- package/src/core/dialect/postgres/functions.ts +66 -2
- package/src/core/dialect/postgres/index.ts +17 -4
- package/src/core/dialect/postgres/table-functions.ts +27 -0
- package/src/core/dialect/sqlite/functions.ts +34 -0
- package/src/core/dialect/sqlite/index.ts +17 -1
- package/src/core/driver/database-driver.ts +9 -1
- package/src/core/driver/mssql-driver.ts +3 -0
- package/src/core/driver/mysql-driver.ts +3 -0
- package/src/core/driver/postgres-driver.ts +3 -0
- package/src/core/driver/sqlite-driver.ts +3 -0
- package/src/core/execution/executors/mssql-executor.ts +5 -0
- package/src/core/execution/executors/mysql-executor.ts +5 -0
- package/src/core/execution/executors/postgres-executor.ts +5 -0
- package/src/core/execution/executors/sqlite-executor.ts +5 -0
- package/src/core/functions/array.ts +26 -0
- package/src/core/functions/control-flow.ts +69 -0
- package/src/core/functions/datetime.ts +50 -0
- package/src/core/functions/definitions/aggregate.ts +16 -0
- package/src/core/functions/definitions/control-flow.ts +24 -0
- package/src/core/functions/definitions/datetime.ts +36 -0
- package/src/core/functions/definitions/helpers.ts +29 -0
- package/src/core/functions/definitions/json.ts +49 -0
- package/src/core/functions/definitions/numeric.ts +55 -0
- package/src/core/functions/definitions/string.ts +43 -0
- package/src/core/functions/function-registry.ts +48 -0
- package/src/core/functions/group-concat-helpers.ts +57 -0
- package/src/core/functions/json.ts +38 -0
- package/src/core/functions/numeric.ts +14 -0
- package/src/core/functions/standard-strategy.ts +86 -115
- package/src/core/functions/standard-table-strategy.ts +13 -0
- package/src/core/functions/table-types.ts +15 -0
- package/src/core/functions/text.ts +57 -0
- package/src/core/sql/sql.ts +59 -38
- package/src/decorators/bootstrap.ts +5 -4
- package/src/index.ts +18 -11
- package/src/orm/hydration-context.ts +10 -0
- package/src/orm/identity-map.ts +19 -0
- package/src/orm/interceptor-pipeline.ts +4 -0
- package/src/orm/relations/belongs-to.ts +17 -0
- package/src/orm/relations/has-one.ts +17 -0
- package/src/orm/relations/many-to-many.ts +41 -0
- package/src/query-builder/select.ts +68 -68
- package/src/schema/table-guards.ts +6 -0
- 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 {
|
|
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
|
|
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
|
|
80
|
-
const ordinality = fn.withOrdinality ? ' WITH ORDINALITY' : '';
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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);
|