metal-orm 1.0.13 → 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.
- package/README.md +75 -82
- package/dist/decorators/index.cjs +1600 -27
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -2
- package/dist/decorators/index.d.ts +6 -2
- package/dist/decorators/index.js +1599 -27
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +4608 -3429
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +511 -159
- package/dist/index.d.ts +511 -159
- package/dist/index.js +4526 -3415
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
- package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
- package/package.json +1 -1
- package/src/codegen/typescript.ts +38 -35
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +16 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +144 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +30 -19
- package/src/orm/entity-metadata.ts +7 -0
- package/src/orm/entity.ts +58 -27
- package/src/orm/hydration.ts +25 -17
- package/src/orm/lazy-batch.ts +46 -2
- package/src/orm/orm-context.ts +60 -60
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +43 -2
- package/src/orm/relations/has-one.ts +139 -0
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +60 -60
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +66 -61
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { StandardFunctionStrategy } from '../../functions/standard-strategy.js';
|
|
2
|
+
import { FunctionRenderContext } from '../../functions/types.js';
|
|
3
|
+
import { LiteralNode } from '../../ast/expression.js';
|
|
4
|
+
|
|
5
|
+
export class SqliteFunctionStrategy extends StandardFunctionStrategy {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.registerOverrides();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private registerOverrides() {
|
|
12
|
+
// Override Standard/Abstract definitions with SQLite specifics
|
|
13
|
+
|
|
14
|
+
// Date/Time functions
|
|
15
|
+
this.add('NOW', () => `datetime('now', 'localtime')`);
|
|
16
|
+
this.add('CURRENT_DATE', () => `date('now', 'localtime')`);
|
|
17
|
+
this.add('CURRENT_TIME', () => `time('now', 'localtime')`);
|
|
18
|
+
this.add('UTC_NOW', () => `datetime('now')`);
|
|
19
|
+
|
|
20
|
+
this.add('EXTRACT', ({ compiledArgs }) => {
|
|
21
|
+
if (compiledArgs.length !== 2) throw new Error('EXTRACT expects 2 arguments (part, date)');
|
|
22
|
+
const [part, date] = compiledArgs;
|
|
23
|
+
// Map common parts to strftime format
|
|
24
|
+
const partUpper = part.replace(/['"]/g, '').toUpperCase();
|
|
25
|
+
const formatMap: Record<string, string> = {
|
|
26
|
+
'YEAR': '%Y', 'MONTH': '%m', 'DAY': '%d',
|
|
27
|
+
'HOUR': '%H', 'MINUTE': '%M', 'SECOND': '%S',
|
|
28
|
+
'DOW': '%w', 'WEEK': '%W'
|
|
29
|
+
};
|
|
30
|
+
const format = formatMap[partUpper] || '%Y';
|
|
31
|
+
return `CAST(strftime('${format}', ${date}) AS INTEGER)`;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.add('YEAR', ({ compiledArgs }) => {
|
|
35
|
+
if (compiledArgs.length !== 1) throw new Error('YEAR expects 1 argument');
|
|
36
|
+
return `CAST(strftime('%Y', ${compiledArgs[0]}) AS INTEGER)`;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.add('MONTH', ({ compiledArgs }) => {
|
|
40
|
+
if (compiledArgs.length !== 1) throw new Error('MONTH expects 1 argument');
|
|
41
|
+
return `CAST(strftime('%m', ${compiledArgs[0]}) AS INTEGER)`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.add('DAY', ({ compiledArgs }) => {
|
|
45
|
+
if (compiledArgs.length !== 1) throw new Error('DAY expects 1 argument');
|
|
46
|
+
return `CAST(strftime('%d', ${compiledArgs[0]}) AS INTEGER)`;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.add('DATE_ADD', ({ node, compiledArgs }) => {
|
|
50
|
+
if (compiledArgs.length !== 3) throw new Error('DATE_ADD expects 3 arguments (date, interval, unit)');
|
|
51
|
+
const [date, interval] = compiledArgs;
|
|
52
|
+
const unitArg = node.args[2] as LiteralNode;
|
|
53
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, '').toLowerCase();
|
|
54
|
+
return `datetime(${date}, '+' || ${interval} || ' ${unitClean}')`;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.add('DATE_SUB', ({ node, compiledArgs }) => {
|
|
58
|
+
if (compiledArgs.length !== 3) throw new Error('DATE_SUB expects 3 arguments (date, interval, unit)');
|
|
59
|
+
const [date, interval] = compiledArgs;
|
|
60
|
+
const unitArg = node.args[2] as LiteralNode;
|
|
61
|
+
const unitClean = String(unitArg.value).replace(/['"]/g, '').toLowerCase();
|
|
62
|
+
return `datetime(${date}, '-' || ${interval} || ' ${unitClean}')`;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.add('DATE_DIFF', ({ compiledArgs }) => {
|
|
66
|
+
if (compiledArgs.length !== 2) throw new Error('DATE_DIFF expects 2 arguments');
|
|
67
|
+
const [date1, date2] = compiledArgs;
|
|
68
|
+
return `CAST(julianday(${date1}) - julianday(${date2}) AS INTEGER)`;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.add('DATE_FORMAT', ({ compiledArgs }) => {
|
|
72
|
+
if (compiledArgs.length !== 2) throw new Error('DATE_FORMAT expects 2 arguments');
|
|
73
|
+
const [date, format] = compiledArgs;
|
|
74
|
+
return `strftime(${format}, ${date})`;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.add('UNIX_TIMESTAMP', () => `CAST(strftime('%s', 'now') AS INTEGER)`);
|
|
78
|
+
|
|
79
|
+
this.add('FROM_UNIXTIME', ({ compiledArgs }) => {
|
|
80
|
+
if (compiledArgs.length !== 1) throw new Error('FROM_UNIXTIME expects 1 argument');
|
|
81
|
+
return `datetime(${compiledArgs[0]}, 'unixepoch')`;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.add('END_OF_MONTH', ({ compiledArgs }) => {
|
|
85
|
+
if (compiledArgs.length !== 1) throw new Error('END_OF_MONTH expects 1 argument');
|
|
86
|
+
return `date(${compiledArgs[0]}, 'start of month', '+1 month', '-1 day')`;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.add('DAY_OF_WEEK', ({ compiledArgs }) => {
|
|
90
|
+
if (compiledArgs.length !== 1) throw new Error('DAY_OF_WEEK expects 1 argument');
|
|
91
|
+
return `CAST(strftime('%w', ${compiledArgs[0]}) AS INTEGER)`;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => {
|
|
95
|
+
if (compiledArgs.length !== 1) throw new Error('WEEK_OF_YEAR expects 1 argument');
|
|
96
|
+
return `CAST(strftime('%W', ${compiledArgs[0]}) AS INTEGER)`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.add('DATE_TRUNC', ({ node, compiledArgs }) => {
|
|
100
|
+
if (compiledArgs.length !== 2) throw new Error('DATE_TRUNC expects 2 arguments (part, date)');
|
|
101
|
+
const [, date] = compiledArgs;
|
|
102
|
+
const partArg = node.args[0] as LiteralNode;
|
|
103
|
+
const partClean = String(partArg.value).replace(/['"]/g, '').toLowerCase();
|
|
104
|
+
// SQLite uses date modifiers
|
|
105
|
+
if (partClean === 'year') {
|
|
106
|
+
return `date(${date}, 'start of year')`;
|
|
107
|
+
} else if (partClean === 'month') {
|
|
108
|
+
return `date(${date}, 'start of month')`;
|
|
109
|
+
} else if (partClean === 'day') {
|
|
110
|
+
return `date(${date})`;
|
|
111
|
+
}
|
|
112
|
+
return `date(${date}, 'start of ${partClean}')`;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { CompilerContext } from '../abstract.js';
|
|
2
|
-
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
-
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
4
|
+
import { SqliteFunctionStrategy } from './functions.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SQLite dialect implementation
|
|
8
|
+
*/
|
|
9
|
+
export class SqliteDialect extends SqlDialectBase {
|
|
10
|
+
protected readonly dialect = 'sqlite';
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new SqliteDialect instance
|
|
13
|
+
*/
|
|
14
|
+
public constructor() {
|
|
15
|
+
super(new SqliteFunctionStrategy());
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -27,19 +29,19 @@ export class SqliteDialect extends SqlDialectBase {
|
|
|
27
29
|
* @param node - JSON path node
|
|
28
30
|
* @returns SQLite JSON path expression
|
|
29
31
|
*/
|
|
30
|
-
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
-
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
-
// SQLite uses json_extract(col, '$.path')
|
|
33
|
-
return `json_extract(${col}, '${node.path}')`;
|
|
34
|
-
}
|
|
32
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
33
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
34
|
+
// SQLite uses json_extract(col, '$.path')
|
|
35
|
+
return `json_extract(${col}, '${node.path}')`;
|
|
36
|
+
}
|
|
35
37
|
|
|
36
|
-
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
37
|
-
if (!returning || returning.length === 0) return '';
|
|
38
|
-
const columns = this.formatReturningColumns(returning);
|
|
39
|
-
return ` RETURNING ${columns}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
supportsReturning(): boolean {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
38
|
+
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
39
|
+
if (!returning || returning.length === 0) return '';
|
|
40
|
+
const columns = this.formatReturningColumns(returning);
|
|
41
|
+
return ` RETURNING ${columns}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
supportsReturning(): boolean {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Dialect } from '../dialect/abstract.js';
|
|
2
|
+
import type { SchemaDialect } from '../ddl/schema-dialect.js';
|
|
3
|
+
import type { SchemaIntrospector } from '../ddl/schema-introspect.js';
|
|
4
|
+
|
|
5
|
+
export interface DatabaseDriver {
|
|
6
|
+
readonly name: string; // e.g. "postgres"
|
|
7
|
+
|
|
8
|
+
createDialect(): Dialect;
|
|
9
|
+
createSchemaDialect(): SchemaDialect;
|
|
10
|
+
createIntrospector(): SchemaIntrospector;
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseDriver } from './database-driver.js';
|
|
2
|
+
import { SqlServerDialect } from '../dialect/mssql/index.js';
|
|
3
|
+
import { MSSqlSchemaDialect } from '../ddl/dialects/mssql-schema-dialect.js';
|
|
4
|
+
import { mssqlIntrospector } from '../ddl/introspect/mssql.js';
|
|
5
|
+
|
|
6
|
+
export class MssqlDriver implements DatabaseDriver {
|
|
7
|
+
readonly name = 'mssql';
|
|
8
|
+
|
|
9
|
+
createDialect() {
|
|
10
|
+
return new SqlServerDialect();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createSchemaDialect() {
|
|
14
|
+
return new MSSqlSchemaDialect();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createIntrospector() {
|
|
18
|
+
return mssqlIntrospector;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseDriver } from './database-driver.js';
|
|
2
|
+
import { MySqlDialect } from '../dialect/mysql/index.js';
|
|
3
|
+
import { MySqlSchemaDialect } from '../ddl/dialects/mysql-schema-dialect.js';
|
|
4
|
+
import { mysqlIntrospector } from '../ddl/introspect/mysql.js';
|
|
5
|
+
|
|
6
|
+
export class MySqlDriver implements DatabaseDriver {
|
|
7
|
+
readonly name = 'mysql';
|
|
8
|
+
|
|
9
|
+
createDialect() {
|
|
10
|
+
return new MySqlDialect();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createSchemaDialect() {
|
|
14
|
+
return new MySqlSchemaDialect();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createIntrospector() {
|
|
18
|
+
return mysqlIntrospector;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseDriver } from './database-driver.js';
|
|
2
|
+
import { PostgresDialect } from '../dialect/postgres/index.js';
|
|
3
|
+
import { PostgresSchemaDialect } from '../ddl/dialects/postgres-schema-dialect.js';
|
|
4
|
+
import { postgresIntrospector } from '../ddl/introspect/postgres.js';
|
|
5
|
+
|
|
6
|
+
export class PostgresDriver implements DatabaseDriver {
|
|
7
|
+
readonly name = 'postgres';
|
|
8
|
+
|
|
9
|
+
createDialect() {
|
|
10
|
+
return new PostgresDialect();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createSchemaDialect() {
|
|
14
|
+
return new PostgresSchemaDialect();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createIntrospector() {
|
|
18
|
+
return postgresIntrospector;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseDriver } from './database-driver.js';
|
|
2
|
+
import { SqliteDialect } from '../dialect/sqlite/index.js';
|
|
3
|
+
import { SQLiteSchemaDialect } from '../ddl/dialects/sqlite-schema-dialect.js';
|
|
4
|
+
import { sqliteIntrospector } from '../ddl/introspect/sqlite.js';
|
|
5
|
+
|
|
6
|
+
export class SqliteDriver implements DatabaseDriver {
|
|
7
|
+
readonly name = 'sqlite';
|
|
8
|
+
|
|
9
|
+
createDialect() {
|
|
10
|
+
return new SqliteDialect();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
createSchemaDialect() {
|
|
14
|
+
return new SQLiteSchemaDialect();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
createIntrospector() {
|
|
18
|
+
return sqliteIntrospector;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/core/execution/db-executor.ts
|
|
2
|
+
|
|
3
|
+
// low-level canonical shape
|
|
4
|
+
export type QueryResult = {
|
|
5
|
+
columns: string[];
|
|
6
|
+
values: unknown[][];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface DbExecutor {
|
|
10
|
+
executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
|
|
11
|
+
|
|
12
|
+
beginTransaction?(): Promise<void>;
|
|
13
|
+
commitTransaction?(): Promise<void>;
|
|
14
|
+
rollbackTransaction?(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- helpers ---
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert an array of row objects into a QueryResult.
|
|
21
|
+
*/
|
|
22
|
+
export function rowsToQueryResult(
|
|
23
|
+
rows: Array<Record<string, unknown>>
|
|
24
|
+
): QueryResult {
|
|
25
|
+
if (rows.length === 0) {
|
|
26
|
+
return { columns: [], values: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const columns = Object.keys(rows[0]);
|
|
30
|
+
const values = rows.map(row => columns.map(c => (row as any)[c]));
|
|
31
|
+
return { columns, values };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Minimal contract that most SQL clients can implement.
|
|
36
|
+
*/
|
|
37
|
+
export interface SimpleQueryRunner {
|
|
38
|
+
query(
|
|
39
|
+
sql: string,
|
|
40
|
+
params?: unknown[]
|
|
41
|
+
): Promise<Array<Record<string, unknown>>>;
|
|
42
|
+
beginTransaction?(): Promise<void>;
|
|
43
|
+
commitTransaction?(): Promise<void>;
|
|
44
|
+
rollbackTransaction?(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generic factory: turn any SimpleQueryRunner into a DbExecutor.
|
|
49
|
+
*/
|
|
50
|
+
export function createExecutorFromQueryRunner(
|
|
51
|
+
runner: SimpleQueryRunner
|
|
52
|
+
): DbExecutor {
|
|
53
|
+
return {
|
|
54
|
+
async executeSql(sql, params) {
|
|
55
|
+
const rows = await runner.query(sql, params);
|
|
56
|
+
const result = rowsToQueryResult(rows);
|
|
57
|
+
return [result];
|
|
58
|
+
},
|
|
59
|
+
beginTransaction: runner.beginTransaction?.bind(runner),
|
|
60
|
+
commitTransaction: runner.commitTransaction?.bind(runner),
|
|
61
|
+
rollbackTransaction: runner.rollbackTransaction?.bind(runner),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/core/execution/executors/mssql-executor.ts
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
rowsToQueryResult
|
|
5
|
+
} from '../db-executor.js';
|
|
6
|
+
|
|
7
|
+
export interface MssqlClientLike {
|
|
8
|
+
query(
|
|
9
|
+
sql: string,
|
|
10
|
+
params?: unknown[]
|
|
11
|
+
): Promise<{ recordset: Array<Record<string, unknown>> }>;
|
|
12
|
+
beginTransaction?(): Promise<void>;
|
|
13
|
+
commit?(): Promise<void>;
|
|
14
|
+
rollback?(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createMssqlExecutor(
|
|
18
|
+
client: MssqlClientLike
|
|
19
|
+
): DbExecutor {
|
|
20
|
+
return {
|
|
21
|
+
async executeSql(sql, params) {
|
|
22
|
+
const { recordset } = await client.query(sql, params);
|
|
23
|
+
const result = rowsToQueryResult(recordset ?? []);
|
|
24
|
+
return [result];
|
|
25
|
+
},
|
|
26
|
+
async beginTransaction() {
|
|
27
|
+
if (!client.beginTransaction) return;
|
|
28
|
+
await client.beginTransaction();
|
|
29
|
+
},
|
|
30
|
+
async commitTransaction() {
|
|
31
|
+
if (!client.commit) return;
|
|
32
|
+
await client.commit();
|
|
33
|
+
},
|
|
34
|
+
async rollbackTransaction() {
|
|
35
|
+
if (!client.rollback) return;
|
|
36
|
+
await client.rollback();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/core/execution/executors/mysql-executor.ts
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
rowsToQueryResult
|
|
5
|
+
} from '../db-executor.js';
|
|
6
|
+
|
|
7
|
+
export interface MysqlClientLike {
|
|
8
|
+
query(
|
|
9
|
+
sql: string,
|
|
10
|
+
params?: unknown[]
|
|
11
|
+
): Promise<[any, any?]>; // rows, metadata
|
|
12
|
+
beginTransaction?(): Promise<void>;
|
|
13
|
+
commit?(): Promise<void>;
|
|
14
|
+
rollback?(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createMysqlExecutor(
|
|
18
|
+
client: MysqlClientLike
|
|
19
|
+
): DbExecutor {
|
|
20
|
+
return {
|
|
21
|
+
async executeSql(sql, params) {
|
|
22
|
+
const [rows] = await client.query(sql, params as any[]);
|
|
23
|
+
|
|
24
|
+
if (!Array.isArray(rows)) {
|
|
25
|
+
// e.g. insert/update returning only headers, treat as no rows
|
|
26
|
+
return [{ columns: [], values: [] }];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = rowsToQueryResult(
|
|
30
|
+
rows as Array<Record<string, unknown>>
|
|
31
|
+
);
|
|
32
|
+
return [result];
|
|
33
|
+
},
|
|
34
|
+
async beginTransaction() {
|
|
35
|
+
if (!client.beginTransaction) return;
|
|
36
|
+
await client.beginTransaction();
|
|
37
|
+
},
|
|
38
|
+
async commitTransaction() {
|
|
39
|
+
if (!client.commit) return;
|
|
40
|
+
await client.commit();
|
|
41
|
+
},
|
|
42
|
+
async rollbackTransaction() {
|
|
43
|
+
if (!client.rollback) return;
|
|
44
|
+
await client.rollback();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/core/execution/executors/postgres-executor.ts
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
createExecutorFromQueryRunner
|
|
5
|
+
} from '../db-executor.js';
|
|
6
|
+
|
|
7
|
+
export interface PostgresClientLike {
|
|
8
|
+
query(
|
|
9
|
+
text: string,
|
|
10
|
+
params?: unknown[]
|
|
11
|
+
): Promise<{ rows: Array<Record<string, unknown>> }>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createPostgresExecutor(
|
|
15
|
+
client: PostgresClientLike
|
|
16
|
+
): DbExecutor {
|
|
17
|
+
return createExecutorFromQueryRunner({
|
|
18
|
+
async query(sql, params) {
|
|
19
|
+
const { rows } = await client.query(sql, params as any[]);
|
|
20
|
+
return rows;
|
|
21
|
+
},
|
|
22
|
+
async beginTransaction() {
|
|
23
|
+
await client.query('BEGIN');
|
|
24
|
+
},
|
|
25
|
+
async commitTransaction() {
|
|
26
|
+
await client.query('COMMIT');
|
|
27
|
+
},
|
|
28
|
+
async rollbackTransaction() {
|
|
29
|
+
await client.query('ROLLBACK');
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/core/execution/executors/sqlite-executor.ts
|
|
2
|
+
import {
|
|
3
|
+
DbExecutor,
|
|
4
|
+
rowsToQueryResult
|
|
5
|
+
} from '../db-executor.js';
|
|
6
|
+
|
|
7
|
+
export interface SqliteClientLike {
|
|
8
|
+
all(
|
|
9
|
+
sql: string,
|
|
10
|
+
params?: unknown[]
|
|
11
|
+
): Promise<Array<Record<string, unknown>>>;
|
|
12
|
+
run?(sql: string, params?: unknown[]): Promise<unknown>;
|
|
13
|
+
beginTransaction?(): Promise<void>;
|
|
14
|
+
commitTransaction?(): Promise<void>;
|
|
15
|
+
rollbackTransaction?(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createSqliteExecutor(
|
|
19
|
+
client: SqliteClientLike
|
|
20
|
+
): DbExecutor {
|
|
21
|
+
return {
|
|
22
|
+
async executeSql(sql, params) {
|
|
23
|
+
const rows = await client.all(sql, params);
|
|
24
|
+
const result = rowsToQueryResult(rows);
|
|
25
|
+
return [result];
|
|
26
|
+
},
|
|
27
|
+
beginTransaction: client.beginTransaction?.bind(client),
|
|
28
|
+
commitTransaction: client.commitTransaction?.bind(client),
|
|
29
|
+
rollbackTransaction: client.rollbackTransaction?.bind(client),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Pure AST Builders - No Dialect Logic Here!
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from '../../schema/column.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: any): 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
|
+
return valueToOperand(input as any);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const fn = (key: string, args: OperandInput[]): FunctionNode => ({
|
|
18
|
+
type: 'Function',
|
|
19
|
+
name: key,
|
|
20
|
+
args: args.map(toOperand)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ----------------------
|
|
24
|
+
// Helper Functions
|
|
25
|
+
// ----------------------
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Helper: NOW() - Returns the current local date and time
|
|
29
|
+
*/
|
|
30
|
+
export const now = (): FunctionNode => fn('NOW', []);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Helper: CURRENT_DATE - Returns only the current date (no time)
|
|
34
|
+
*/
|
|
35
|
+
export const currentDate = (): FunctionNode => fn('CURRENT_DATE', []);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Helper: CURRENT_TIME - Returns only the current time
|
|
39
|
+
*/
|
|
40
|
+
export const currentTime = (): FunctionNode => fn('CURRENT_TIME', []);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Helper: UTC_NOW() - Returns current UTC/GMT date and time
|
|
44
|
+
*/
|
|
45
|
+
export const utcNow = (): FunctionNode => fn('UTC_NOW', []);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Helper: EXTRACT(part FROM date) - Extracts a part (year, month, day, hour, etc.) from a date
|
|
49
|
+
* @param part - The date part to extract (e.g., 'YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND')
|
|
50
|
+
* @param date - The date/datetime value
|
|
51
|
+
*/
|
|
52
|
+
export const extract = (part: OperandInput, date: OperandInput): FunctionNode => fn('EXTRACT', [part, date]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper: YEAR(date) - Extracts the year from a date
|
|
56
|
+
*/
|
|
57
|
+
export const year = (date: OperandInput): FunctionNode => fn('YEAR', [date]);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Helper: MONTH(date) - Extracts the month from a date
|
|
61
|
+
*/
|
|
62
|
+
export const month = (date: OperandInput): FunctionNode => fn('MONTH', [date]);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Helper: DAY(date) - Extracts the day from a date
|
|
66
|
+
*/
|
|
67
|
+
export const day = (date: OperandInput): FunctionNode => fn('DAY', [date]);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Helper: DATE_ADD(date, interval, unit) - Adds a specific time interval to a date
|
|
71
|
+
* @param date - The date/datetime value
|
|
72
|
+
* @param interval - The number of units to add
|
|
73
|
+
* @param unit - The unit type (e.g., 'DAY', 'MONTH', 'YEAR', 'HOUR', 'MINUTE', 'SECOND')
|
|
74
|
+
*/
|
|
75
|
+
export const dateAdd = (date: OperandInput, interval: OperandInput, unit: OperandInput): FunctionNode =>
|
|
76
|
+
fn('DATE_ADD', [date, interval, unit]);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Helper: DATE_SUB(date, interval, unit) - Subtracts a specific time interval from a date
|
|
80
|
+
* @param date - The date/datetime value
|
|
81
|
+
* @param interval - The number of units to subtract
|
|
82
|
+
* @param unit - The unit type (e.g., 'DAY', 'MONTH', 'YEAR', 'HOUR', 'MINUTE', 'SECOND')
|
|
83
|
+
*/
|
|
84
|
+
export const dateSub = (date: OperandInput, interval: OperandInput, unit: OperandInput): FunctionNode =>
|
|
85
|
+
fn('DATE_SUB', [date, interval, unit]);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Helper: DATE_DIFF(date1, date2) - Returns the difference between two dates in days
|
|
89
|
+
* @param date1 - The end date
|
|
90
|
+
* @param date2 - The start date
|
|
91
|
+
*/
|
|
92
|
+
export const dateDiff = (date1: OperandInput, date2: OperandInput): FunctionNode => fn('DATE_DIFF', [date1, date2]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Helper: DATE_FORMAT(date, format) - Converts a date to a formatted string
|
|
96
|
+
* @param date - The date/datetime value
|
|
97
|
+
* @param format - The format string (dialect-specific)
|
|
98
|
+
*/
|
|
99
|
+
export const dateFormat = (date: OperandInput, format: OperandInput): FunctionNode => fn('DATE_FORMAT', [date, format]);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Helper: UNIX_TIMESTAMP() - Returns the current Unix epoch (seconds since 1970)
|
|
103
|
+
*/
|
|
104
|
+
export const unixTimestamp = (): FunctionNode => fn('UNIX_TIMESTAMP', []);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Helper: FROM_UNIXTIME(timestamp) - Converts Unix epoch seconds to a date
|
|
108
|
+
* @param timestamp - Unix timestamp in seconds
|
|
109
|
+
*/
|
|
110
|
+
export const fromUnixTime = (timestamp: OperandInput): FunctionNode => fn('FROM_UNIXTIME', [timestamp]);
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Helper: END_OF_MONTH(date) - Returns the last day of the month for a given date
|
|
114
|
+
*/
|
|
115
|
+
export const endOfMonth = (date: OperandInput): FunctionNode => fn('END_OF_MONTH', [date]);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Helper: DAY_OF_WEEK(date) - Returns the index of the weekday
|
|
119
|
+
*/
|
|
120
|
+
export const dayOfWeek = (date: OperandInput): FunctionNode => fn('DAY_OF_WEEK', [date]);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Helper: WEEK_OF_YEAR(date) - Returns the week number of the year
|
|
124
|
+
*/
|
|
125
|
+
export const weekOfYear = (date: OperandInput): FunctionNode => fn('WEEK_OF_YEAR', [date]);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Helper: DATE_TRUNC(part, date) - Resets date precision (e.g., first day of the month/year)
|
|
129
|
+
* @param part - The truncation precision (e.g., 'YEAR', 'MONTH', 'DAY')
|
|
130
|
+
* @param date - The date/datetime value
|
|
131
|
+
*/
|
|
132
|
+
export const dateTrunc = (part: OperandInput, date: OperandInput): FunctionNode => fn('DATE_TRUNC', [part, date]);
|