metal-orm 1.0.32 → 1.0.34

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.
@@ -1,4933 +0,0 @@
1
- // src/schema/table.ts
2
- var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
3
- const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
4
- acc[key] = { ...def, name: key, table: name };
5
- return acc;
6
- }, {});
7
- return {
8
- name,
9
- schema: options.schema,
10
- columns: colsWithNames,
11
- relations,
12
- hooks,
13
- primaryKey: options.primaryKey,
14
- indexes: options.indexes,
15
- checks: options.checks,
16
- comment: options.comment,
17
- engine: options.engine,
18
- charset: options.charset,
19
- collation: options.collation
20
- };
21
- };
22
-
23
- // src/orm/entity-metadata.ts
24
- var metadataMap = /* @__PURE__ */ new Map();
25
- var ensureEntityMetadata = (target) => {
26
- let meta = metadataMap.get(target);
27
- if (!meta) {
28
- meta = {
29
- target,
30
- tableName: target.name || "unknown",
31
- columns: {},
32
- relations: {}
33
- };
34
- metadataMap.set(target, meta);
35
- }
36
- return meta;
37
- };
38
- var getEntityMetadata = (target) => {
39
- return metadataMap.get(target);
40
- };
41
- var getAllEntityMetadata = () => {
42
- return Array.from(metadataMap.values());
43
- };
44
- var addColumnMetadata = (target, propertyKey, column) => {
45
- const meta = ensureEntityMetadata(target);
46
- meta.columns[propertyKey] = { ...column };
47
- };
48
- var addRelationMetadata = (target, propertyKey, relation) => {
49
- const meta = ensureEntityMetadata(target);
50
- meta.relations[propertyKey] = relation;
51
- };
52
- var setEntityTableName = (target, tableName, hooks) => {
53
- const meta = ensureEntityMetadata(target);
54
- if (tableName && tableName.length > 0) {
55
- meta.tableName = tableName;
56
- }
57
- if (hooks) {
58
- meta.hooks = hooks;
59
- }
60
- };
61
- var buildTableDef = (meta) => {
62
- if (meta.table) {
63
- return meta.table;
64
- }
65
- const columns = Object.entries(meta.columns).reduce((acc, [key, def]) => {
66
- acc[key] = {
67
- ...def,
68
- name: key,
69
- table: meta.tableName
70
- };
71
- return acc;
72
- }, {});
73
- const table = defineTable(meta.tableName, columns, {}, meta.hooks);
74
- meta.table = table;
75
- return table;
76
- };
77
-
78
- // src/decorators/decorator-metadata.ts
79
- var METADATA_KEY = "metal-orm:decorators";
80
- var isStandardDecoratorContext = (value) => {
81
- return typeof value === "object" && value !== null && "kind" in value;
82
- };
83
- var getOrCreateMetadataBag = (context) => {
84
- const metadata = context.metadata || (context.metadata = {});
85
- const existing = metadata[METADATA_KEY];
86
- if (existing) {
87
- return existing;
88
- }
89
- const bag = { columns: [], relations: [] };
90
- metadata[METADATA_KEY] = bag;
91
- return bag;
92
- };
93
- var readMetadataBag = (context) => {
94
- return context.metadata?.[METADATA_KEY];
95
- };
96
- var registerInitializer = (context, initializer) => {
97
- context.addInitializer?.(initializer);
98
- };
99
-
100
- // src/decorators/entity.ts
101
- var toSnakeCase = (value) => {
102
- return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
103
- };
104
- var deriveTableNameFromConstructor = (ctor) => {
105
- const fallback = "unknown";
106
- const rawName = ctor.name || fallback;
107
- const strippedName = rawName.replace(/Entity$/i, "");
108
- const normalized = toSnakeCase(strippedName || rawName);
109
- if (!normalized) {
110
- return fallback;
111
- }
112
- return normalized.endsWith("s") ? normalized : `${normalized}s`;
113
- };
114
- function Entity(options = {}) {
115
- const decorator = (value) => {
116
- const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
117
- setEntityTableName(value, tableName, options.hooks);
118
- return value;
119
- };
120
- const decoratorWithContext = (value, context) => {
121
- const ctor = value;
122
- decorator(ctor);
123
- if (context && isStandardDecoratorContext(context)) {
124
- const bag = readMetadataBag(context);
125
- if (bag) {
126
- const meta = ensureEntityMetadata(ctor);
127
- for (const entry of bag.columns) {
128
- if (!meta.columns[entry.propertyName]) {
129
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
130
- }
131
- }
132
- for (const entry of bag.relations) {
133
- if (!meta.relations[entry.propertyName]) {
134
- addRelationMetadata(ctor, entry.propertyName, entry.relation);
135
- }
136
- }
137
- }
138
- }
139
- return ctor;
140
- };
141
- return decoratorWithContext;
142
- }
143
-
144
- // src/decorators/column.ts
145
- var normalizeColumnInput = (input) => {
146
- const asOptions = input;
147
- const asDefinition = input;
148
- const column = {
149
- type: asOptions.type ?? asDefinition.type,
150
- args: asOptions.args ?? asDefinition.args,
151
- notNull: asOptions.notNull ?? asDefinition.notNull,
152
- primary: asOptions.primary ?? asDefinition.primary,
153
- unique: asDefinition.unique,
154
- default: asDefinition.default,
155
- autoIncrement: asDefinition.autoIncrement,
156
- generated: asDefinition.generated,
157
- check: asDefinition.check,
158
- references: asDefinition.references,
159
- comment: asDefinition.comment
160
- };
161
- if (!column.type) {
162
- throw new Error("Column decorator requires a column type");
163
- }
164
- return column;
165
- };
166
- var normalizePropertyName = (name) => {
167
- if (typeof name === "symbol") {
168
- return name.description ?? name.toString();
169
- }
170
- return name;
171
- };
172
- var resolveConstructor = (target) => {
173
- if (typeof target === "function") {
174
- return target;
175
- }
176
- if (target && typeof target.constructor === "function") {
177
- return target.constructor;
178
- }
179
- return void 0;
180
- };
181
- var registerColumn = (ctor, propertyName, column) => {
182
- const meta = ensureEntityMetadata(ctor);
183
- if (meta.columns[propertyName]) {
184
- return;
185
- }
186
- addColumnMetadata(ctor, propertyName, column);
187
- };
188
- var registerColumnFromContext = (context, column) => {
189
- if (!context.name) {
190
- throw new Error("Column decorator requires a property name");
191
- }
192
- const propertyName = normalizePropertyName(context.name);
193
- const bag = getOrCreateMetadataBag(context);
194
- if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
195
- bag.columns.push({ propertyName, column: { ...column } });
196
- }
197
- registerInitializer(context, function() {
198
- const ctor = resolveConstructor(this);
199
- if (!ctor) {
200
- return;
201
- }
202
- registerColumn(ctor, propertyName, column);
203
- });
204
- };
205
- function Column(definition) {
206
- const normalized = normalizeColumnInput(definition);
207
- const decorator = (targetOrValue, propertyKeyOrContext) => {
208
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
209
- registerColumnFromContext(propertyKeyOrContext, normalized);
210
- return;
211
- }
212
- const propertyName = normalizePropertyName(propertyKeyOrContext);
213
- const ctor = resolveConstructor(targetOrValue);
214
- if (!ctor) {
215
- throw new Error("Unable to resolve constructor when registering column metadata");
216
- }
217
- registerColumn(ctor, propertyName, { ...normalized });
218
- };
219
- return decorator;
220
- }
221
- function PrimaryKey(definition) {
222
- const normalized = normalizeColumnInput(definition);
223
- normalized.primary = true;
224
- return Column(normalized);
225
- }
226
-
227
- // src/schema/relation.ts
228
- var RelationKinds = {
229
- /** One-to-one relationship */
230
- HasOne: "HAS_ONE",
231
- /** One-to-many relationship */
232
- HasMany: "HAS_MANY",
233
- /** Many-to-one relationship */
234
- BelongsTo: "BELONGS_TO",
235
- /** Many-to-many relationship with pivot metadata */
236
- BelongsToMany: "BELONGS_TO_MANY"
237
- };
238
- var hasMany = (target, foreignKey, localKey, cascade) => ({
239
- type: RelationKinds.HasMany,
240
- target,
241
- foreignKey,
242
- localKey,
243
- cascade
244
- });
245
- var hasOne = (target, foreignKey, localKey, cascade) => ({
246
- type: RelationKinds.HasOne,
247
- target,
248
- foreignKey,
249
- localKey,
250
- cascade
251
- });
252
- var belongsTo = (target, foreignKey, localKey, cascade) => ({
253
- type: RelationKinds.BelongsTo,
254
- target,
255
- foreignKey,
256
- localKey,
257
- cascade
258
- });
259
- var belongsToMany = (target, pivotTable, options) => ({
260
- type: RelationKinds.BelongsToMany,
261
- target,
262
- pivotTable,
263
- pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
264
- pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
265
- localKey: options.localKey,
266
- targetKey: options.targetKey,
267
- pivotPrimaryKey: options.pivotPrimaryKey,
268
- defaultPivotColumns: options.defaultPivotColumns,
269
- cascade: options.cascade
270
- });
271
-
272
- // src/decorators/relations.ts
273
- var normalizePropertyName2 = (name) => {
274
- if (typeof name === "symbol") {
275
- return name.description ?? name.toString();
276
- }
277
- return name;
278
- };
279
- var resolveConstructor2 = (instanceOrCtor) => {
280
- if (typeof instanceOrCtor === "function") {
281
- return instanceOrCtor;
282
- }
283
- if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
284
- return instanceOrCtor.constructor;
285
- }
286
- return void 0;
287
- };
288
- var registerRelation = (ctor, propertyName, metadata) => {
289
- addRelationMetadata(ctor, propertyName, metadata);
290
- };
291
- var createFieldDecorator = (metadataFactory) => {
292
- const decorator = (targetOrValue, propertyKeyOrContext) => {
293
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
294
- const ctx = propertyKeyOrContext;
295
- if (!ctx.name) {
296
- throw new Error("Relation decorator requires a property name");
297
- }
298
- const propertyName2 = normalizePropertyName2(ctx.name);
299
- const bag = getOrCreateMetadataBag(ctx);
300
- const relationMetadata = metadataFactory(propertyName2);
301
- if (!bag.relations.some((entry) => entry.propertyName === propertyName2)) {
302
- bag.relations.push({ propertyName: propertyName2, relation: relationMetadata });
303
- }
304
- registerInitializer(ctx, function() {
305
- const ctor2 = resolveConstructor2(this);
306
- if (!ctor2) {
307
- return;
308
- }
309
- registerRelation(ctor2, propertyName2, relationMetadata);
310
- });
311
- return;
312
- }
313
- const propertyName = normalizePropertyName2(propertyKeyOrContext);
314
- const ctor = resolveConstructor2(targetOrValue);
315
- if (!ctor) {
316
- throw new Error("Unable to resolve constructor when registering relation metadata");
317
- }
318
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
319
- };
320
- return decorator;
321
- };
322
- function HasMany(options) {
323
- return createFieldDecorator((propertyName) => ({
324
- kind: RelationKinds.HasMany,
325
- propertyKey: propertyName,
326
- target: options.target,
327
- foreignKey: options.foreignKey,
328
- localKey: options.localKey,
329
- cascade: options.cascade
330
- }));
331
- }
332
- function HasOne(options) {
333
- return createFieldDecorator((propertyName) => ({
334
- kind: RelationKinds.HasOne,
335
- propertyKey: propertyName,
336
- target: options.target,
337
- foreignKey: options.foreignKey,
338
- localKey: options.localKey,
339
- cascade: options.cascade
340
- }));
341
- }
342
- function BelongsTo(options) {
343
- return createFieldDecorator((propertyName) => ({
344
- kind: RelationKinds.BelongsTo,
345
- propertyKey: propertyName,
346
- target: options.target,
347
- foreignKey: options.foreignKey,
348
- localKey: options.localKey,
349
- cascade: options.cascade
350
- }));
351
- }
352
- function BelongsToMany(options) {
353
- return createFieldDecorator((propertyName) => ({
354
- kind: RelationKinds.BelongsToMany,
355
- propertyKey: propertyName,
356
- target: options.target,
357
- pivotTable: options.pivotTable,
358
- pivotForeignKeyToRoot: options.pivotForeignKeyToRoot,
359
- pivotForeignKeyToTarget: options.pivotForeignKeyToTarget,
360
- localKey: options.localKey,
361
- targetKey: options.targetKey,
362
- pivotPrimaryKey: options.pivotPrimaryKey,
363
- defaultPivotColumns: options.defaultPivotColumns,
364
- cascade: options.cascade
365
- }));
366
- }
367
-
368
- // src/core/ast/expression-nodes.ts
369
- var operandTypes = /* @__PURE__ */ new Set([
370
- "Column",
371
- "Literal",
372
- "Function",
373
- "JsonPath",
374
- "ScalarSubquery",
375
- "CaseExpression",
376
- "WindowFunction"
377
- ]);
378
- var isOperandNode = (node) => node && operandTypes.has(node.type);
379
- var isFunctionNode = (node) => node?.type === "Function";
380
- var isCaseExpressionNode = (node) => node?.type === "CaseExpression";
381
- var isWindowFunctionNode = (node) => node?.type === "WindowFunction";
382
- var isExpressionSelectionNode = (node) => isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
383
-
384
- // src/core/ast/expression-builders.ts
385
- var valueToOperand = (value) => {
386
- if (isOperandNode(value)) {
387
- return value;
388
- }
389
- return {
390
- type: "Literal",
391
- value
392
- };
393
- };
394
- var toNode = (col) => {
395
- if (isOperandNode(col)) return col;
396
- const def = col;
397
- return { type: "Column", table: def.table || "unknown", name: def.name };
398
- };
399
- var toLiteralNode = (value) => ({
400
- type: "Literal",
401
- value
402
- });
403
- var isLiteralValue = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
404
- var toOperand = (val) => {
405
- if (isLiteralValue(val)) {
406
- return valueToOperand(val);
407
- }
408
- return toNode(val);
409
- };
410
- var columnOperand = (col) => toNode(col);
411
- var createBinaryExpression = (operator, left, right, escape) => {
412
- const node = {
413
- type: "BinaryExpression",
414
- left: toNode(left),
415
- operator,
416
- right: toOperand(right)
417
- };
418
- if (escape !== void 0) {
419
- node.escape = toLiteralNode(escape);
420
- }
421
- return node;
422
- };
423
- var eq = (left, right) => createBinaryExpression("=", left, right);
424
- var and = (...operands) => ({
425
- type: "LogicalExpression",
426
- operator: "AND",
427
- operands
428
- });
429
- var createInExpression = (operator, left, values) => ({
430
- type: "InExpression",
431
- left: toNode(left),
432
- operator,
433
- right: values.map((v) => toOperand(v))
434
- });
435
- var inList = (left, values) => createInExpression("IN", left, values);
436
- var exists = (subquery) => ({
437
- type: "ExistsExpression",
438
- operator: "EXISTS",
439
- subquery
440
- });
441
- var notExists = (subquery) => ({
442
- type: "ExistsExpression",
443
- operator: "NOT EXISTS",
444
- subquery
445
- });
446
-
447
- // src/core/sql/sql.ts
448
- var JOIN_KINDS = {
449
- /** INNER JOIN type */
450
- INNER: "INNER",
451
- /** LEFT JOIN type */
452
- LEFT: "LEFT",
453
- /** RIGHT JOIN type */
454
- RIGHT: "RIGHT",
455
- /** CROSS JOIN type */
456
- CROSS: "CROSS"
457
- };
458
- var ORDER_DIRECTIONS = {
459
- /** Ascending order */
460
- ASC: "ASC",
461
- /** Descending order */
462
- DESC: "DESC"
463
- };
464
-
465
- // src/core/ast/aggregate-functions.ts
466
- var buildAggregate = (name) => (col) => ({
467
- type: "Function",
468
- name,
469
- args: [columnOperand(col)]
470
- });
471
- var count = buildAggregate("COUNT");
472
- var sum = buildAggregate("SUM");
473
- var avg = buildAggregate("AVG");
474
- var min = buildAggregate("MIN");
475
- var max = buildAggregate("MAX");
476
-
477
- // src/core/ast/builders.ts
478
- var buildColumnNode = (table, column) => {
479
- if (column.type === "Column") {
480
- return column;
481
- }
482
- const def = column;
483
- const baseTable = def.table ? table.alias && def.table === table.name ? table.alias : def.table : table.alias || table.name;
484
- return {
485
- type: "Column",
486
- table: baseTable,
487
- name: def.name
488
- };
489
- };
490
- var derivedTable = (query, alias, columnAliases) => ({
491
- type: "DerivedTable",
492
- query,
493
- alias,
494
- columnAliases
495
- });
496
-
497
- // src/core/functions/standard-strategy.ts
498
- var StandardFunctionStrategy = class _StandardFunctionStrategy {
499
- constructor() {
500
- this.renderers = /* @__PURE__ */ new Map();
501
- this.registerStandard();
502
- }
503
- registerStandard() {
504
- this.add("COUNT", ({ compiledArgs }) => `COUNT(${compiledArgs.join(", ")})`);
505
- this.add("SUM", ({ compiledArgs }) => `SUM(${compiledArgs[0]})`);
506
- this.add("AVG", ({ compiledArgs }) => `AVG(${compiledArgs[0]})`);
507
- this.add("MIN", ({ compiledArgs }) => `MIN(${compiledArgs[0]})`);
508
- this.add("MAX", ({ compiledArgs }) => `MAX(${compiledArgs[0]})`);
509
- this.add("ABS", ({ compiledArgs }) => `ABS(${compiledArgs[0]})`);
510
- this.add("UPPER", ({ compiledArgs }) => `UPPER(${compiledArgs[0]})`);
511
- this.add("LOWER", ({ compiledArgs }) => `LOWER(${compiledArgs[0]})`);
512
- this.add("LENGTH", ({ compiledArgs }) => `LENGTH(${compiledArgs[0]})`);
513
- this.add("TRIM", ({ compiledArgs }) => `TRIM(${compiledArgs[0]})`);
514
- this.add("LTRIM", ({ compiledArgs }) => `LTRIM(${compiledArgs[0]})`);
515
- this.add("RTRIM", ({ compiledArgs }) => `RTRIM(${compiledArgs[0]})`);
516
- this.add("SUBSTRING", ({ compiledArgs }) => `SUBSTRING(${compiledArgs.join(", ")})`);
517
- this.add("CONCAT", ({ compiledArgs }) => `CONCAT(${compiledArgs.join(", ")})`);
518
- this.add("NOW", () => `NOW()`);
519
- this.add("CURRENT_DATE", () => `CURRENT_DATE`);
520
- this.add("CURRENT_TIME", () => `CURRENT_TIME`);
521
- this.add("EXTRACT", ({ compiledArgs }) => `EXTRACT(${compiledArgs[0]} FROM ${compiledArgs[1]})`);
522
- this.add("YEAR", ({ compiledArgs }) => `EXTRACT(YEAR FROM ${compiledArgs[0]})`);
523
- this.add("MONTH", ({ compiledArgs }) => `EXTRACT(MONTH FROM ${compiledArgs[0]})`);
524
- this.add("DAY", ({ compiledArgs }) => `EXTRACT(DAY FROM ${compiledArgs[0]})`);
525
- this.add("DATE_ADD", ({ compiledArgs }) => `(${compiledArgs[0]} + INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
526
- this.add("DATE_SUB", ({ compiledArgs }) => `(${compiledArgs[0]} - INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
527
- this.add("DATE_DIFF", ({ compiledArgs }) => `DATEDIFF(${compiledArgs[0]}, ${compiledArgs[1]})`);
528
- this.add("DATE_FORMAT", ({ compiledArgs }) => `DATE_FORMAT(${compiledArgs[0]}, ${compiledArgs[1]})`);
529
- this.add("UNIX_TIMESTAMP", () => `UNIX_TIMESTAMP()`);
530
- this.add("FROM_UNIXTIME", ({ compiledArgs }) => `FROM_UNIXTIME(${compiledArgs[0]})`);
531
- this.add("END_OF_MONTH", ({ compiledArgs }) => `LAST_DAY(${compiledArgs[0]})`);
532
- this.add("DAY_OF_WEEK", ({ compiledArgs }) => `DAYOFWEEK(${compiledArgs[0]})`);
533
- this.add("WEEK_OF_YEAR", ({ compiledArgs }) => `WEEKOFYEAR(${compiledArgs[0]})`);
534
- this.add("DATE_TRUNC", ({ compiledArgs }) => `DATE_TRUNC(${compiledArgs[0]}, ${compiledArgs[1]})`);
535
- this.add("GROUP_CONCAT", (ctx) => this.renderGroupConcat(ctx));
536
- }
537
- add(name, renderer) {
538
- this.renderers.set(name, renderer);
539
- }
540
- getRenderer(name) {
541
- return this.renderers.get(name);
542
- }
543
- renderGroupConcat(ctx) {
544
- const arg = ctx.compiledArgs[0];
545
- const orderClause = this.buildOrderByExpression(ctx);
546
- const orderSegment = orderClause ? ` ${orderClause}` : "";
547
- const separatorClause = this.formatGroupConcatSeparator(ctx);
548
- return `GROUP_CONCAT(${arg}${orderSegment}${separatorClause})`;
549
- }
550
- buildOrderByExpression(ctx) {
551
- const orderBy = ctx.node.orderBy;
552
- if (!orderBy || orderBy.length === 0) {
553
- return "";
554
- }
555
- const parts = orderBy.map((order) => `${ctx.compileOperand(order.column)} ${order.direction}`);
556
- return `ORDER BY ${parts.join(", ")}`;
557
- }
558
- formatGroupConcatSeparator(ctx) {
559
- if (!ctx.node.separator) {
560
- return "";
561
- }
562
- return ` SEPARATOR ${ctx.compileOperand(ctx.node.separator)}`;
563
- }
564
- getGroupConcatSeparatorOperand(ctx) {
565
- return ctx.node.separator ?? _StandardFunctionStrategy.DEFAULT_GROUP_CONCAT_SEPARATOR;
566
- }
567
- static {
568
- this.DEFAULT_GROUP_CONCAT_SEPARATOR = {
569
- type: "Literal",
570
- value: ","
571
- };
572
- }
573
- };
574
-
575
- // src/core/dialect/abstract.ts
576
- var Dialect = class _Dialect {
577
- /**
578
- * Compiles a SELECT query AST to SQL
579
- * @param ast - Query AST to compile
580
- * @returns Compiled query with SQL and parameters
581
- */
582
- compileSelect(ast) {
583
- const ctx = this.createCompilerContext();
584
- const normalized = this.normalizeSelectAst(ast);
585
- const rawSql = this.compileSelectAst(normalized, ctx).trim();
586
- const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
587
- return {
588
- sql,
589
- params: [...ctx.params]
590
- };
591
- }
592
- compileInsert(ast) {
593
- const ctx = this.createCompilerContext();
594
- const rawSql = this.compileInsertAst(ast, ctx).trim();
595
- const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
596
- return {
597
- sql,
598
- params: [...ctx.params]
599
- };
600
- }
601
- compileUpdate(ast) {
602
- const ctx = this.createCompilerContext();
603
- const rawSql = this.compileUpdateAst(ast, ctx).trim();
604
- const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
605
- return {
606
- sql,
607
- params: [...ctx.params]
608
- };
609
- }
610
- compileDelete(ast) {
611
- const ctx = this.createCompilerContext();
612
- const rawSql = this.compileDeleteAst(ast, ctx).trim();
613
- const sql = rawSql.endsWith(";") ? rawSql : `${rawSql};`;
614
- return {
615
- sql,
616
- params: [...ctx.params]
617
- };
618
- }
619
- supportsReturning() {
620
- return false;
621
- }
622
- /**
623
- * Compiles a WHERE clause
624
- * @param where - WHERE expression
625
- * @param ctx - Compiler context
626
- * @returns SQL WHERE clause or empty string
627
- */
628
- compileWhere(where, ctx) {
629
- if (!where) return "";
630
- return ` WHERE ${this.compileExpression(where, ctx)}`;
631
- }
632
- compileReturning(returning, ctx) {
633
- if (!returning || returning.length === 0) return "";
634
- throw new Error("RETURNING is not supported by this dialect.");
635
- }
636
- /**
637
- * Generates subquery for EXISTS expressions
638
- * Rule: Always forces SELECT 1, ignoring column list
639
- * Maintains FROM, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT/OFFSET
640
- * Does not add ';' at the end
641
- * @param ast - Query AST
642
- * @param ctx - Compiler context
643
- * @returns SQL for EXISTS subquery
644
- */
645
- compileSelectForExists(ast, ctx) {
646
- const normalized = this.normalizeSelectAst(ast);
647
- const full = this.compileSelectAst(normalized, ctx).trim().replace(/;$/, "");
648
- if (normalized.setOps && normalized.setOps.length > 0) {
649
- return `SELECT 1 FROM (${full}) AS _exists`;
650
- }
651
- const upper = full.toUpperCase();
652
- const fromIndex = upper.indexOf(" FROM ");
653
- if (fromIndex === -1) {
654
- return full;
655
- }
656
- const tail = full.slice(fromIndex);
657
- return `SELECT 1${tail}`;
658
- }
659
- /**
660
- * Creates a new compiler context
661
- * @returns Compiler context with parameter management
662
- */
663
- createCompilerContext() {
664
- const params = [];
665
- let counter = 0;
666
- return {
667
- params,
668
- addParameter: (value) => {
669
- counter += 1;
670
- params.push(value);
671
- return this.formatPlaceholder(counter);
672
- }
673
- };
674
- }
675
- /**
676
- * Formats a parameter placeholder
677
- * @param index - Parameter index
678
- * @returns Formatted placeholder string
679
- */
680
- formatPlaceholder(index) {
681
- return "?";
682
- }
683
- /**
684
- * Whether the current dialect supports a given set operation.
685
- * Override in concrete dialects to restrict support.
686
- */
687
- supportsSetOperation(kind) {
688
- return true;
689
- }
690
- /**
691
- * Validates set-operation semantics:
692
- * - Ensures the dialect supports requested operators.
693
- * - Enforces that only the outermost compound query may have ORDER/LIMIT/OFFSET.
694
- * @param ast - Query to validate
695
- * @param isOutermost - Whether this node is the outermost compound query
696
- */
697
- validateSetOperations(ast, isOutermost = true) {
698
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
699
- if (!isOutermost && (ast.orderBy || ast.limit !== void 0 || ast.offset !== void 0)) {
700
- throw new Error("ORDER BY / LIMIT / OFFSET are only allowed on the outermost compound query.");
701
- }
702
- if (hasSetOps) {
703
- for (const op of ast.setOps) {
704
- if (!this.supportsSetOperation(op.operator)) {
705
- throw new Error(`Set operation ${op.operator} is not supported by this dialect.`);
706
- }
707
- this.validateSetOperations(op.query, false);
708
- }
709
- }
710
- }
711
- /**
712
- * Hoists CTEs from set-operation operands to the outermost query so WITH appears once.
713
- * @param ast - Query AST
714
- * @returns Normalized AST without inner CTEs and a list of hoisted CTEs
715
- */
716
- hoistCtes(ast) {
717
- let hoisted = [];
718
- const normalizedSetOps = ast.setOps?.map((op) => {
719
- const { normalized: child, hoistedCtes: childHoisted } = this.hoistCtes(op.query);
720
- const childCtes = child.ctes ?? [];
721
- if (childCtes.length) {
722
- hoisted = hoisted.concat(childCtes);
723
- }
724
- hoisted = hoisted.concat(childHoisted);
725
- const queryWithoutCtes = childCtes.length ? { ...child, ctes: void 0 } : child;
726
- return { ...op, query: queryWithoutCtes };
727
- });
728
- const normalized = normalizedSetOps ? { ...ast, setOps: normalizedSetOps } : ast;
729
- return { normalized, hoistedCtes: hoisted };
730
- }
731
- /**
732
- * Normalizes a SELECT AST before compilation (validation + CTE hoisting).
733
- * @param ast - Query AST
734
- * @returns Normalized query AST
735
- */
736
- normalizeSelectAst(ast) {
737
- this.validateSetOperations(ast, true);
738
- const { normalized, hoistedCtes } = this.hoistCtes(ast);
739
- const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
740
- return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
741
- }
742
- constructor(functionStrategy) {
743
- this.expressionCompilers = /* @__PURE__ */ new Map();
744
- this.operandCompilers = /* @__PURE__ */ new Map();
745
- this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
746
- this.registerDefaultOperandCompilers();
747
- this.registerDefaultExpressionCompilers();
748
- }
749
- /**
750
- * Creates a new Dialect instance (for testing purposes)
751
- * @param functionStrategy - Optional function strategy
752
- * @returns New Dialect instance
753
- */
754
- static create(functionStrategy) {
755
- class TestDialect extends _Dialect {
756
- constructor() {
757
- super(...arguments);
758
- this.dialect = "sqlite";
759
- }
760
- quoteIdentifier(id) {
761
- return `"${id}"`;
762
- }
763
- compileSelectAst() {
764
- throw new Error("Not implemented");
765
- }
766
- compileInsertAst() {
767
- throw new Error("Not implemented");
768
- }
769
- compileUpdateAst() {
770
- throw new Error("Not implemented");
771
- }
772
- compileDeleteAst() {
773
- throw new Error("Not implemented");
774
- }
775
- }
776
- return new TestDialect(functionStrategy);
777
- }
778
- /**
779
- * Registers an expression compiler for a specific node type
780
- * @param type - Expression node type
781
- * @param compiler - Compiler function
782
- */
783
- registerExpressionCompiler(type, compiler) {
784
- this.expressionCompilers.set(type, compiler);
785
- }
786
- /**
787
- * Registers an operand compiler for a specific node type
788
- * @param type - Operand node type
789
- * @param compiler - Compiler function
790
- */
791
- registerOperandCompiler(type, compiler) {
792
- this.operandCompilers.set(type, compiler);
793
- }
794
- /**
795
- * Compiles an expression node
796
- * @param node - Expression node to compile
797
- * @param ctx - Compiler context
798
- * @returns Compiled SQL expression
799
- */
800
- compileExpression(node, ctx) {
801
- const compiler = this.expressionCompilers.get(node.type);
802
- if (!compiler) {
803
- throw new Error(`Unsupported expression node type "${node.type}" for ${this.constructor.name}`);
804
- }
805
- return compiler(node, ctx);
806
- }
807
- /**
808
- * Compiles an operand node
809
- * @param node - Operand node to compile
810
- * @param ctx - Compiler context
811
- * @returns Compiled SQL operand
812
- */
813
- compileOperand(node, ctx) {
814
- const compiler = this.operandCompilers.get(node.type);
815
- if (!compiler) {
816
- throw new Error(`Unsupported operand node type "${node.type}" for ${this.constructor.name}`);
817
- }
818
- return compiler(node, ctx);
819
- }
820
- registerDefaultExpressionCompilers() {
821
- this.registerExpressionCompiler("BinaryExpression", (binary, ctx) => {
822
- const left = this.compileOperand(binary.left, ctx);
823
- const right = this.compileOperand(binary.right, ctx);
824
- const base = `${left} ${binary.operator} ${right}`;
825
- if (binary.escape) {
826
- const escapeOperand = this.compileOperand(binary.escape, ctx);
827
- return `${base} ESCAPE ${escapeOperand}`;
828
- }
829
- return base;
830
- });
831
- this.registerExpressionCompiler("LogicalExpression", (logical, ctx) => {
832
- if (logical.operands.length === 0) return "";
833
- const parts = logical.operands.map((op) => {
834
- const compiled = this.compileExpression(op, ctx);
835
- return op.type === "LogicalExpression" ? `(${compiled})` : compiled;
836
- });
837
- return parts.join(` ${logical.operator} `);
838
- });
839
- this.registerExpressionCompiler("NullExpression", (nullExpr, ctx) => {
840
- const left = this.compileOperand(nullExpr.left, ctx);
841
- return `${left} ${nullExpr.operator}`;
842
- });
843
- this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
844
- const left = this.compileOperand(inExpr.left, ctx);
845
- const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
846
- return `${left} ${inExpr.operator} (${values})`;
847
- });
848
- this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
849
- const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
850
- return `${existsExpr.operator} (${subquerySql})`;
851
- });
852
- this.registerExpressionCompiler("BetweenExpression", (betweenExpr, ctx) => {
853
- const left = this.compileOperand(betweenExpr.left, ctx);
854
- const lower = this.compileOperand(betweenExpr.lower, ctx);
855
- const upper = this.compileOperand(betweenExpr.upper, ctx);
856
- return `${left} ${betweenExpr.operator} ${lower} AND ${upper}`;
857
- });
858
- }
859
- registerDefaultOperandCompilers() {
860
- this.registerOperandCompiler("Literal", (literal, ctx) => ctx.addParameter(literal.value));
861
- this.registerOperandCompiler("Column", (column, _ctx) => {
862
- return `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`;
863
- });
864
- this.registerOperandCompiler(
865
- "Function",
866
- (fnNode, ctx) => this.compileFunctionOperand(fnNode, ctx)
867
- );
868
- this.registerOperandCompiler("JsonPath", (path, _ctx) => this.compileJsonPath(path));
869
- this.registerOperandCompiler("ScalarSubquery", (node, ctx) => {
870
- const sql = this.compileSelectAst(node.query, ctx).trim().replace(/;$/, "");
871
- return `(${sql})`;
872
- });
873
- this.registerOperandCompiler("CaseExpression", (node, ctx) => {
874
- const parts = ["CASE"];
875
- for (const { when, then } of node.conditions) {
876
- parts.push(`WHEN ${this.compileExpression(when, ctx)} THEN ${this.compileOperand(then, ctx)}`);
877
- }
878
- if (node.else) {
879
- parts.push(`ELSE ${this.compileOperand(node.else, ctx)}`);
880
- }
881
- parts.push("END");
882
- return parts.join(" ");
883
- });
884
- this.registerOperandCompiler("WindowFunction", (node, ctx) => {
885
- let result = `${node.name}(`;
886
- if (node.args.length > 0) {
887
- result += node.args.map((arg) => this.compileOperand(arg, ctx)).join(", ");
888
- }
889
- result += ") OVER (";
890
- const parts = [];
891
- if (node.partitionBy && node.partitionBy.length > 0) {
892
- const partitionClause = "PARTITION BY " + node.partitionBy.map(
893
- (col) => `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`
894
- ).join(", ");
895
- parts.push(partitionClause);
896
- }
897
- if (node.orderBy && node.orderBy.length > 0) {
898
- const orderClause = "ORDER BY " + node.orderBy.map(
899
- (o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`
900
- ).join(", ");
901
- parts.push(orderClause);
902
- }
903
- result += parts.join(" ");
904
- result += ")";
905
- return result;
906
- });
907
- }
908
- // Default fallback, should be overridden by dialects if supported
909
- compileJsonPath(node) {
910
- throw new Error("JSON Path not supported by this dialect");
911
- }
912
- /**
913
- * Compiles a function operand, using the dialect's function strategy.
914
- */
915
- compileFunctionOperand(fnNode, ctx) {
916
- const compiledArgs = fnNode.args.map((arg) => this.compileOperand(arg, ctx));
917
- const renderer = this.functionStrategy.getRenderer(fnNode.name);
918
- if (renderer) {
919
- return renderer({
920
- node: fnNode,
921
- compiledArgs,
922
- compileOperand: (operand) => this.compileOperand(operand, ctx)
923
- });
924
- }
925
- return `${fnNode.name}(${compiledArgs.join(", ")})`;
926
- }
927
- };
928
-
929
- // src/core/dialect/base/function-table-formatter.ts
930
- var FunctionTableFormatter = class {
931
- /**
932
- * Formats a function table node into SQL syntax.
933
- * @param fn - The function table node containing schema, name, args, and aliases.
934
- * @param ctx - Optional compiler context for operand compilation.
935
- * @param dialect - The dialect instance for compiling operands.
936
- * @returns SQL function table expression (e.g., "LATERAL schema.func(args) WITH ORDINALITY AS alias(col1, col2)").
937
- */
938
- static format(fn, ctx, dialect) {
939
- const schemaPart = this.formatSchema(fn, dialect);
940
- const args = this.formatArgs(fn, ctx, dialect);
941
- const base = this.formatBase(fn, schemaPart, args, dialect);
942
- const lateral = this.formatLateral(fn);
943
- const alias = this.formatAlias(fn, dialect);
944
- const colAliases = this.formatColumnAliases(fn, dialect);
945
- return `${lateral}${base}${alias}${colAliases}`;
946
- }
947
- /**
948
- * Formats the schema prefix for the function name.
949
- * @param fn - The function table node.
950
- * @param dialect - The dialect instance for quoting identifiers.
951
- * @returns Schema prefix (e.g., "schema.") or empty string.
952
- * @internal
953
- */
954
- static formatSchema(fn, dialect) {
955
- if (!fn.schema) return "";
956
- const quoted = dialect ? dialect.quoteIdentifier(fn.schema) : fn.schema;
957
- return `${quoted}.`;
958
- }
959
- /**
960
- * Formats function arguments into SQL syntax.
961
- * @param fn - The function table node containing arguments.
962
- * @param ctx - Optional compiler context for operand compilation.
963
- * @param dialect - The dialect instance for compiling operands.
964
- * @returns Comma-separated function arguments.
965
- * @internal
966
- */
967
- static formatArgs(fn, ctx, dialect) {
968
- return (fn.args || []).map((a) => {
969
- if (ctx && dialect) {
970
- return dialect.compileOperand(a, ctx);
971
- }
972
- return String(a);
973
- }).join(", ");
974
- }
975
- /**
976
- * Formats the base function call with WITH ORDINALITY if present.
977
- * @param fn - The function table node.
978
- * @param schemaPart - Formatted schema prefix.
979
- * @param args - Formatted function arguments.
980
- * @param dialect - The dialect instance for quoting identifiers.
981
- * @returns Base function call expression (e.g., "schema.func(args) WITH ORDINALITY").
982
- * @internal
983
- */
984
- static formatBase(fn, schemaPart, args, dialect) {
985
- const ordinality = fn.withOrdinality ? " WITH ORDINALITY" : "";
986
- const quoted = dialect ? dialect.quoteIdentifier(fn.name) : fn.name;
987
- return `${schemaPart}${quoted}(${args})${ordinality}`;
988
- }
989
- /**
990
- * Formats the LATERAL keyword if present.
991
- * @param fn - The function table node.
992
- * @returns "LATERAL " or empty string.
993
- * @internal
994
- */
995
- static formatLateral(fn) {
996
- return fn.lateral ? "LATERAL " : "";
997
- }
998
- /**
999
- * Formats the table alias for the function table.
1000
- * @param fn - The function table node.
1001
- * @param dialect - The dialect instance for quoting identifiers.
1002
- * @returns " AS alias" or empty string.
1003
- * @internal
1004
- */
1005
- static formatAlias(fn, dialect) {
1006
- if (!fn.alias) return "";
1007
- const quoted = dialect ? dialect.quoteIdentifier(fn.alias) : fn.alias;
1008
- return ` AS ${quoted}`;
1009
- }
1010
- /**
1011
- * Formats column aliases for the function table result columns.
1012
- * @param fn - The function table node containing column aliases.
1013
- * @param dialect - The dialect instance for quoting identifiers.
1014
- * @returns "(col1, col2, ...)" or empty string.
1015
- * @internal
1016
- */
1017
- static formatColumnAliases(fn, dialect) {
1018
- if (!fn.columnAliases || !fn.columnAliases.length) return "";
1019
- const aliases = fn.columnAliases.map((col) => dialect ? dialect.quoteIdentifier(col) : col).join(", ");
1020
- return `(${aliases})`;
1021
- }
1022
- };
1023
-
1024
- // src/core/dialect/base/pagination-strategy.ts
1025
- var StandardLimitOffsetPagination = class {
1026
- /**
1027
- * Compiles LIMIT/OFFSET pagination clause.
1028
- * @param limit - The maximum number of rows to return.
1029
- * @param offset - The number of rows to skip.
1030
- * @returns SQL pagination clause with LIMIT and/or OFFSET.
1031
- */
1032
- compilePagination(limit, offset) {
1033
- const parts = [];
1034
- if (limit !== void 0) parts.push(`LIMIT ${limit}`);
1035
- if (offset !== void 0) parts.push(`OFFSET ${offset}`);
1036
- return parts.length ? ` ${parts.join(" ")}` : "";
1037
- }
1038
- };
1039
-
1040
- // src/core/dialect/base/cte-compiler.ts
1041
- var CteCompiler = class {
1042
- /**
1043
- * Compiles CTEs (WITH clauses) including recursive CTEs.
1044
- * @param ast - The SELECT query AST containing CTE definitions.
1045
- * @param ctx - The compiler context for expression compilation.
1046
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
1047
- * @param compileSelectAst - Function to recursively compile SELECT query ASTs.
1048
- * @param normalizeSelectAst - Function to normalize SELECT query ASTs before compilation.
1049
- * @param stripTrailingSemicolon - Function to remove trailing semicolons from SQL.
1050
- * @returns SQL WITH clause string (e.g., "WITH cte_name AS (...) ") or empty string if no CTEs.
1051
- */
1052
- static compileCtes(ast, ctx, quoteIdentifier, compileSelectAst, normalizeSelectAst, stripTrailingSemicolon) {
1053
- if (!ast.ctes || ast.ctes.length === 0) return "";
1054
- const hasRecursive = ast.ctes.some((cte) => cte.recursive);
1055
- const prefix = hasRecursive ? "WITH RECURSIVE " : "WITH ";
1056
- const cteDefs = ast.ctes.map((cte) => {
1057
- const name = quoteIdentifier(cte.name);
1058
- const cols = cte.columns && cte.columns.length ? `(${cte.columns.map((c) => quoteIdentifier(c)).join(", ")})` : "";
1059
- const query = stripTrailingSemicolon(compileSelectAst(normalizeSelectAst(cte.query), ctx));
1060
- return `${name}${cols} AS (${query})`;
1061
- }).join(", ");
1062
- return `${prefix}${cteDefs} `;
1063
- }
1064
- };
1065
-
1066
- // src/core/dialect/base/returning-strategy.ts
1067
- var NoReturningStrategy = class {
1068
- /**
1069
- * Throws an error as RETURNING is not supported.
1070
- * @param returning - Columns to return (causes error if non-empty).
1071
- * @param _ctx - Compiler context (unused).
1072
- * @throws Error indicating RETURNING is not supported.
1073
- */
1074
- compileReturning(returning, _ctx) {
1075
- if (!returning || returning.length === 0) return "";
1076
- throw new Error("RETURNING is not supported by this dialect.");
1077
- }
1078
- /**
1079
- * Formats column names for RETURNING clause.
1080
- * @param returning - Columns to format.
1081
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
1082
- * @returns Simple comma-separated column names.
1083
- */
1084
- formatReturningColumns(returning, quoteIdentifier) {
1085
- return returning.map((column) => {
1086
- const tablePart = column.table ? `${quoteIdentifier(column.table)}.` : "";
1087
- const aliasPart = column.alias ? ` AS ${quoteIdentifier(column.alias)}` : "";
1088
- return `${tablePart}${quoteIdentifier(column.name)}${aliasPart}`;
1089
- }).join(", ");
1090
- }
1091
- };
1092
-
1093
- // src/core/dialect/base/join-compiler.ts
1094
- var JoinCompiler = class {
1095
- /**
1096
- * Compiles all JOIN clauses from a SELECT query AST.
1097
- * @param ast - The SELECT query AST containing join definitions.
1098
- * @param ctx - The compiler context for expression compilation.
1099
- * @param compileFrom - Function to compile table sources (tables or subqueries).
1100
- * @param compileExpression - Function to compile join condition expressions.
1101
- * @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
1102
- */
1103
- static compileJoins(ast, ctx, compileFrom, compileExpression) {
1104
- if (!ast.joins || ast.joins.length === 0) return "";
1105
- const parts = ast.joins.map((j) => {
1106
- const table = compileFrom(j.table, ctx);
1107
- const cond = compileExpression(j.condition, ctx);
1108
- return `${j.kind} JOIN ${table} ON ${cond}`;
1109
- });
1110
- return ` ${parts.join(" ")}`;
1111
- }
1112
- };
1113
-
1114
- // src/core/dialect/base/groupby-compiler.ts
1115
- var GroupByCompiler = class {
1116
- /**
1117
- * Compiles GROUP BY clause from a SELECT query AST.
1118
- * @param ast - The SELECT query AST containing grouping columns.
1119
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
1120
- * @returns SQL GROUP BY clause (e.g., " GROUP BY table.col1, table.col2") or empty string if no grouping.
1121
- */
1122
- static compileGroupBy(ast, quoteIdentifier) {
1123
- if (!ast.groupBy || ast.groupBy.length === 0) return "";
1124
- const cols = ast.groupBy.map((c) => `${quoteIdentifier(c.table)}.${quoteIdentifier(c.name)}`).join(", ");
1125
- return ` GROUP BY ${cols}`;
1126
- }
1127
- };
1128
-
1129
- // src/core/dialect/base/orderby-compiler.ts
1130
- var OrderByCompiler = class {
1131
- /**
1132
- * Compiles ORDER BY clause from a SELECT query AST.
1133
- * @param ast - The SELECT query AST containing sort specifications.
1134
- * @param quoteIdentifier - Function to quote identifiers according to dialect rules.
1135
- * @returns SQL ORDER BY clause (e.g., " ORDER BY table.col1 ASC, table.col2 DESC") or empty string if no ordering.
1136
- */
1137
- static compileOrderBy(ast, quoteIdentifier) {
1138
- if (!ast.orderBy || ast.orderBy.length === 0) return "";
1139
- const parts = ast.orderBy.map((o) => `${quoteIdentifier(o.column.table)}.${quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
1140
- return ` ORDER BY ${parts}`;
1141
- }
1142
- };
1143
-
1144
- // src/core/dialect/base/sql-dialect.ts
1145
- var SqlDialectBase = class extends Dialect {
1146
- constructor() {
1147
- super(...arguments);
1148
- this.paginationStrategy = new StandardLimitOffsetPagination();
1149
- this.returningStrategy = new NoReturningStrategy();
1150
- }
1151
- compileSelectAst(ast, ctx) {
1152
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
1153
- const ctes = CteCompiler.compileCtes(
1154
- ast,
1155
- ctx,
1156
- this.quoteIdentifier.bind(this),
1157
- this.compileSelectAst.bind(this),
1158
- this.normalizeSelectAst?.bind(this) ?? ((a) => a),
1159
- this.stripTrailingSemicolon.bind(this)
1160
- );
1161
- const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
1162
- const baseSelect = this.compileSelectCore(baseAst, ctx);
1163
- if (!hasSetOps) {
1164
- return `${ctes}${baseSelect}`;
1165
- }
1166
- return this.compileSelectWithSetOps(ast, baseSelect, ctes, ctx);
1167
- }
1168
- compileSelectWithSetOps(ast, baseSelect, ctes, ctx) {
1169
- const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
1170
- const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
1171
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
1172
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
1173
- return `${ctes}${combined}${orderBy}${pagination}`;
1174
- }
1175
- compileInsertAst(ast, ctx) {
1176
- const table = this.compileTableName(ast.into);
1177
- const columnList = this.compileInsertColumnList(ast.columns);
1178
- const values = this.compileInsertValues(ast.values, ctx);
1179
- const returning = this.compileReturning(ast.returning, ctx);
1180
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
1181
- }
1182
- compileReturning(returning, ctx) {
1183
- return this.returningStrategy.compileReturning(returning, ctx);
1184
- }
1185
- compileInsertColumnList(columns) {
1186
- return columns.map((column) => this.quoteIdentifier(column.name)).join(", ");
1187
- }
1188
- compileInsertValues(values, ctx) {
1189
- return values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1190
- }
1191
- compileSelectCore(ast, ctx) {
1192
- const columns = this.compileSelectColumns(ast, ctx);
1193
- const from = this.compileFrom(ast.from, ctx);
1194
- const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
1195
- const whereClause = this.compileWhere(ast.where, ctx);
1196
- const groupBy = GroupByCompiler.compileGroupBy(ast, this.quoteIdentifier.bind(this));
1197
- const having = this.compileHaving(ast, ctx);
1198
- const orderBy = OrderByCompiler.compileOrderBy(ast, this.quoteIdentifier.bind(this));
1199
- const pagination = this.paginationStrategy.compilePagination(ast.limit, ast.offset);
1200
- return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
1201
- }
1202
- compileUpdateAst(ast, ctx) {
1203
- const table = this.compileTableName(ast.table);
1204
- const assignments = this.compileUpdateAssignments(ast.set, ctx);
1205
- const whereClause = this.compileWhere(ast.where, ctx);
1206
- const returning = this.compileReturning(ast.returning, ctx);
1207
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
1208
- }
1209
- compileUpdateAssignments(assignments, ctx) {
1210
- return assignments.map((assignment) => {
1211
- const col = assignment.column;
1212
- const target = this.quoteIdentifier(col.name);
1213
- const value = this.compileOperand(assignment.value, ctx);
1214
- return `${target} = ${value}`;
1215
- }).join(", ");
1216
- }
1217
- compileDeleteAst(ast, ctx) {
1218
- const table = this.compileTableName(ast.from);
1219
- const whereClause = this.compileWhere(ast.where, ctx);
1220
- const returning = this.compileReturning(ast.returning, ctx);
1221
- return `DELETE FROM ${table}${whereClause}${returning}`;
1222
- }
1223
- formatReturningColumns(returning) {
1224
- return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
1225
- }
1226
- compileDistinct(ast) {
1227
- return ast.distinct ? "DISTINCT " : "";
1228
- }
1229
- compileSelectColumns(ast, ctx) {
1230
- return ast.columns.map((c) => {
1231
- const expr = this.compileOperand(c, ctx);
1232
- if (c.alias) {
1233
- if (c.alias.includes("(")) return c.alias;
1234
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
1235
- }
1236
- return expr;
1237
- }).join(", ");
1238
- }
1239
- compileFrom(ast, ctx) {
1240
- const tableSource = ast;
1241
- if (tableSource.type === "FunctionTable") {
1242
- return this.compileFunctionTable(tableSource, ctx);
1243
- }
1244
- if (tableSource.type === "DerivedTable") {
1245
- return this.compileDerivedTable(tableSource, ctx);
1246
- }
1247
- return this.compileTableSource(tableSource);
1248
- }
1249
- compileFunctionTable(fn, ctx) {
1250
- return FunctionTableFormatter.format(fn, ctx, this);
1251
- }
1252
- compileDerivedTable(table, ctx) {
1253
- if (!table.alias) {
1254
- throw new Error("Derived tables must have an alias.");
1255
- }
1256
- const subquery = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, "");
1257
- const columns = table.columnAliases?.length ? ` (${table.columnAliases.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
1258
- return `(${subquery}) AS ${this.quoteIdentifier(table.alias)}${columns}`;
1259
- }
1260
- compileTableSource(table) {
1261
- if (table.type === "FunctionTable") {
1262
- return this.compileFunctionTable(table);
1263
- }
1264
- if (table.type === "DerivedTable") {
1265
- return this.compileDerivedTable(table);
1266
- }
1267
- const base = this.compileTableName(table);
1268
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
1269
- }
1270
- compileTableName(table) {
1271
- if (table.schema) {
1272
- return `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}`;
1273
- }
1274
- return this.quoteIdentifier(table.name);
1275
- }
1276
- compileHaving(ast, ctx) {
1277
- if (!ast.having) return "";
1278
- return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
1279
- }
1280
- stripTrailingSemicolon(sql) {
1281
- return sql.trim().replace(/;$/, "");
1282
- }
1283
- wrapSetOperand(sql) {
1284
- const trimmed = this.stripTrailingSemicolon(sql);
1285
- return `(${trimmed})`;
1286
- }
1287
- };
1288
-
1289
- // src/core/dialect/postgres/functions.ts
1290
- var PostgresFunctionStrategy = class extends StandardFunctionStrategy {
1291
- constructor() {
1292
- super();
1293
- this.registerOverrides();
1294
- }
1295
- registerOverrides() {
1296
- this.add("UTC_NOW", () => `(NOW() AT TIME ZONE 'UTC')`);
1297
- this.add("UNIX_TIMESTAMP", () => `EXTRACT(EPOCH FROM NOW())::INTEGER`);
1298
- this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
1299
- if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
1300
- return `to_timestamp(${compiledArgs[0]})`;
1301
- });
1302
- this.add("EXTRACT", ({ compiledArgs }) => {
1303
- if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
1304
- const [part, date] = compiledArgs;
1305
- const partClean = part.replace(/['"]/g, "");
1306
- return `EXTRACT(${partClean} FROM ${date})`;
1307
- });
1308
- this.add("YEAR", ({ compiledArgs }) => {
1309
- if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
1310
- return `EXTRACT(YEAR FROM ${compiledArgs[0]})`;
1311
- });
1312
- this.add("MONTH", ({ compiledArgs }) => {
1313
- if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
1314
- return `EXTRACT(MONTH FROM ${compiledArgs[0]})`;
1315
- });
1316
- this.add("DAY", ({ compiledArgs }) => {
1317
- if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
1318
- return `EXTRACT(DAY FROM ${compiledArgs[0]})`;
1319
- });
1320
- this.add("DATE_ADD", ({ node, compiledArgs }) => {
1321
- if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
1322
- const [date, interval] = compiledArgs;
1323
- const unitArg = node.args[2];
1324
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1325
- return `(${date} + (${interval} || ' ${unitClean}')::INTERVAL)`;
1326
- });
1327
- this.add("DATE_SUB", ({ node, compiledArgs }) => {
1328
- if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
1329
- const [date, interval] = compiledArgs;
1330
- const unitArg = node.args[2];
1331
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1332
- return `(${date} - (${interval} || ' ${unitClean}')::INTERVAL)`;
1333
- });
1334
- this.add("DATE_DIFF", ({ compiledArgs }) => {
1335
- if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
1336
- const [date1, date2] = compiledArgs;
1337
- return `(${date1}::DATE - ${date2}::DATE)`;
1338
- });
1339
- this.add("DATE_FORMAT", ({ compiledArgs }) => {
1340
- if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
1341
- const [date, format] = compiledArgs;
1342
- return `TO_CHAR(${date}, ${format})`;
1343
- });
1344
- this.add("END_OF_MONTH", ({ compiledArgs }) => {
1345
- if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
1346
- return `(date_trunc('month', ${compiledArgs[0]}) + interval '1 month' - interval '1 day')::DATE`;
1347
- });
1348
- this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
1349
- if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
1350
- return `EXTRACT(DOW FROM ${compiledArgs[0]})`;
1351
- });
1352
- this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
1353
- if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
1354
- return `EXTRACT(WEEK FROM ${compiledArgs[0]})`;
1355
- });
1356
- this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
1357
- if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
1358
- const [, date] = compiledArgs;
1359
- const partArg = node.args[0];
1360
- const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
1361
- return `DATE_TRUNC('${partClean}', ${date})`;
1362
- });
1363
- this.add("GROUP_CONCAT", (ctx) => {
1364
- const arg = ctx.compiledArgs[0];
1365
- const orderClause = this.buildOrderByExpression(ctx);
1366
- const orderSegment = orderClause ? ` ${orderClause}` : "";
1367
- const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
1368
- const separator = ctx.compileOperand(separatorOperand);
1369
- return `STRING_AGG(${arg}, ${separator}${orderSegment})`;
1370
- });
1371
- }
1372
- };
1373
-
1374
- // src/core/dialect/postgres/index.ts
1375
- var PostgresDialect = class extends SqlDialectBase {
1376
- /**
1377
- * Creates a new PostgresDialect instance
1378
- */
1379
- constructor() {
1380
- super(new PostgresFunctionStrategy());
1381
- this.dialect = "postgres";
1382
- }
1383
- /**
1384
- * Quotes an identifier using PostgreSQL double-quote syntax
1385
- * @param id - Identifier to quote
1386
- * @returns Quoted identifier
1387
- */
1388
- quoteIdentifier(id) {
1389
- return `"${id}"`;
1390
- }
1391
- /**
1392
- * Compiles JSON path expression using PostgreSQL syntax
1393
- * @param node - JSON path node
1394
- * @returns PostgreSQL JSON path expression
1395
- */
1396
- compileJsonPath(node) {
1397
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
1398
- return `${col}->>'${node.path}'`;
1399
- }
1400
- compileReturning(returning, ctx) {
1401
- if (!returning || returning.length === 0) return "";
1402
- const columns = this.formatReturningColumns(returning);
1403
- return ` RETURNING ${columns}`;
1404
- }
1405
- supportsReturning() {
1406
- return true;
1407
- }
1408
- };
1409
-
1410
- // src/core/dialect/mysql/functions.ts
1411
- var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
1412
- constructor() {
1413
- super();
1414
- this.registerOverrides();
1415
- }
1416
- registerOverrides() {
1417
- this.add("NOW", () => `NOW()`);
1418
- this.add("CURRENT_DATE", () => `CURDATE()`);
1419
- this.add("CURRENT_TIME", () => `CURTIME()`);
1420
- this.add("UTC_NOW", () => `UTC_TIMESTAMP()`);
1421
- this.add("EXTRACT", ({ compiledArgs }) => {
1422
- if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
1423
- const [part, date] = compiledArgs;
1424
- const partClean = part.replace(/['"]/g, "");
1425
- return `EXTRACT(${partClean} FROM ${date})`;
1426
- });
1427
- this.add("YEAR", ({ compiledArgs }) => {
1428
- if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
1429
- return `YEAR(${compiledArgs[0]})`;
1430
- });
1431
- this.add("MONTH", ({ compiledArgs }) => {
1432
- if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
1433
- return `MONTH(${compiledArgs[0]})`;
1434
- });
1435
- this.add("DAY", ({ compiledArgs }) => {
1436
- if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
1437
- return `DAY(${compiledArgs[0]})`;
1438
- });
1439
- this.add("DATE_ADD", ({ node, compiledArgs }) => {
1440
- if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
1441
- const [date, interval] = compiledArgs;
1442
- const unitArg = node.args[2];
1443
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
1444
- return `DATE_ADD(${date}, INTERVAL ${interval} ${unitClean})`;
1445
- });
1446
- this.add("DATE_SUB", ({ node, compiledArgs }) => {
1447
- if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
1448
- const [date, interval] = compiledArgs;
1449
- const unitArg = node.args[2];
1450
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toUpperCase();
1451
- return `DATE_SUB(${date}, INTERVAL ${interval} ${unitClean})`;
1452
- });
1453
- this.add("DATE_DIFF", ({ compiledArgs }) => {
1454
- if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
1455
- const [date1, date2] = compiledArgs;
1456
- return `DATEDIFF(${date1}, ${date2})`;
1457
- });
1458
- this.add("DATE_FORMAT", ({ compiledArgs }) => {
1459
- if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
1460
- const [date, format] = compiledArgs;
1461
- return `DATE_FORMAT(${date}, ${format})`;
1462
- });
1463
- this.add("END_OF_MONTH", ({ compiledArgs }) => {
1464
- if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
1465
- return `LAST_DAY(${compiledArgs[0]})`;
1466
- });
1467
- this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
1468
- if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
1469
- return `DAYOFWEEK(${compiledArgs[0]})`;
1470
- });
1471
- this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
1472
- if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
1473
- return `WEEKOFYEAR(${compiledArgs[0]})`;
1474
- });
1475
- this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
1476
- if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
1477
- const [, date] = compiledArgs;
1478
- const partArg = node.args[0];
1479
- const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
1480
- if (partClean === "year") {
1481
- return `DATE_FORMAT(${date}, '%Y-01-01')`;
1482
- } else if (partClean === "month") {
1483
- return `DATE_FORMAT(${date}, '%Y-%m-01')`;
1484
- } else if (partClean === "day") {
1485
- return `DATE(${date})`;
1486
- }
1487
- return `DATE(${date})`;
1488
- });
1489
- }
1490
- };
1491
-
1492
- // src/core/dialect/mysql/index.ts
1493
- var MySqlDialect = class extends SqlDialectBase {
1494
- /**
1495
- * Creates a new MySqlDialect instance
1496
- */
1497
- constructor() {
1498
- super(new MysqlFunctionStrategy());
1499
- this.dialect = "mysql";
1500
- }
1501
- /**
1502
- * Quotes an identifier using MySQL backtick syntax
1503
- * @param id - Identifier to quote
1504
- * @returns Quoted identifier
1505
- */
1506
- quoteIdentifier(id) {
1507
- return `\`${id}\``;
1508
- }
1509
- /**
1510
- * Compiles JSON path expression using MySQL syntax
1511
- * @param node - JSON path node
1512
- * @returns MySQL JSON path expression
1513
- */
1514
- compileJsonPath(node) {
1515
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
1516
- return `${col}->'${node.path}'`;
1517
- }
1518
- };
1519
-
1520
- // src/core/dialect/sqlite/functions.ts
1521
- var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
1522
- constructor() {
1523
- super();
1524
- this.registerOverrides();
1525
- }
1526
- registerOverrides() {
1527
- this.add("NOW", () => `datetime('now', 'localtime')`);
1528
- this.add("CURRENT_DATE", () => `date('now', 'localtime')`);
1529
- this.add("CURRENT_TIME", () => `time('now', 'localtime')`);
1530
- this.add("UTC_NOW", () => `datetime('now')`);
1531
- this.add("EXTRACT", ({ compiledArgs }) => {
1532
- if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
1533
- const [part, date] = compiledArgs;
1534
- const partUpper = part.replace(/['"]/g, "").toUpperCase();
1535
- const formatMap = {
1536
- "YEAR": "%Y",
1537
- "MONTH": "%m",
1538
- "DAY": "%d",
1539
- "HOUR": "%H",
1540
- "MINUTE": "%M",
1541
- "SECOND": "%S",
1542
- "DOW": "%w",
1543
- "WEEK": "%W"
1544
- };
1545
- const format = formatMap[partUpper] || "%Y";
1546
- return `CAST(strftime('${format}', ${date}) AS INTEGER)`;
1547
- });
1548
- this.add("YEAR", ({ compiledArgs }) => {
1549
- if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
1550
- return `CAST(strftime('%Y', ${compiledArgs[0]}) AS INTEGER)`;
1551
- });
1552
- this.add("MONTH", ({ compiledArgs }) => {
1553
- if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
1554
- return `CAST(strftime('%m', ${compiledArgs[0]}) AS INTEGER)`;
1555
- });
1556
- this.add("DAY", ({ compiledArgs }) => {
1557
- if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
1558
- return `CAST(strftime('%d', ${compiledArgs[0]}) AS INTEGER)`;
1559
- });
1560
- this.add("DATE_ADD", ({ node, compiledArgs }) => {
1561
- if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
1562
- const [date, interval] = compiledArgs;
1563
- const unitArg = node.args[2];
1564
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1565
- return `datetime(${date}, '+' || ${interval} || ' ${unitClean}')`;
1566
- });
1567
- this.add("DATE_SUB", ({ node, compiledArgs }) => {
1568
- if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
1569
- const [date, interval] = compiledArgs;
1570
- const unitArg = node.args[2];
1571
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1572
- return `datetime(${date}, '-' || ${interval} || ' ${unitClean}')`;
1573
- });
1574
- this.add("DATE_DIFF", ({ compiledArgs }) => {
1575
- if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
1576
- const [date1, date2] = compiledArgs;
1577
- return `CAST(julianday(${date1}) - julianday(${date2}) AS INTEGER)`;
1578
- });
1579
- this.add("DATE_FORMAT", ({ compiledArgs }) => {
1580
- if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
1581
- const [date, format] = compiledArgs;
1582
- return `strftime(${format}, ${date})`;
1583
- });
1584
- this.add("UNIX_TIMESTAMP", () => `CAST(strftime('%s', 'now') AS INTEGER)`);
1585
- this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
1586
- if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
1587
- return `datetime(${compiledArgs[0]}, 'unixepoch')`;
1588
- });
1589
- this.add("END_OF_MONTH", ({ compiledArgs }) => {
1590
- if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
1591
- return `date(${compiledArgs[0]}, 'start of month', '+1 month', '-1 day')`;
1592
- });
1593
- this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
1594
- if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
1595
- return `CAST(strftime('%w', ${compiledArgs[0]}) AS INTEGER)`;
1596
- });
1597
- this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
1598
- if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
1599
- return `CAST(strftime('%W', ${compiledArgs[0]}) AS INTEGER)`;
1600
- });
1601
- this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
1602
- if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
1603
- const [, date] = compiledArgs;
1604
- const partArg = node.args[0];
1605
- const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
1606
- if (partClean === "year") {
1607
- return `date(${date}, 'start of year')`;
1608
- } else if (partClean === "month") {
1609
- return `date(${date}, 'start of month')`;
1610
- } else if (partClean === "day") {
1611
- return `date(${date})`;
1612
- }
1613
- return `date(${date}, 'start of ${partClean}')`;
1614
- });
1615
- this.add("GROUP_CONCAT", (ctx) => {
1616
- const arg = ctx.compiledArgs[0];
1617
- const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
1618
- const separator = ctx.compileOperand(separatorOperand);
1619
- return `GROUP_CONCAT(${arg}, ${separator})`;
1620
- });
1621
- }
1622
- };
1623
-
1624
- // src/core/dialect/sqlite/index.ts
1625
- var SqliteDialect = class extends SqlDialectBase {
1626
- /**
1627
- * Creates a new SqliteDialect instance
1628
- */
1629
- constructor() {
1630
- super(new SqliteFunctionStrategy());
1631
- this.dialect = "sqlite";
1632
- }
1633
- /**
1634
- * Quotes an identifier using SQLite double-quote syntax
1635
- * @param id - Identifier to quote
1636
- * @returns Quoted identifier
1637
- */
1638
- quoteIdentifier(id) {
1639
- return `"${id}"`;
1640
- }
1641
- /**
1642
- * Compiles JSON path expression using SQLite syntax
1643
- * @param node - JSON path node
1644
- * @returns SQLite JSON path expression
1645
- */
1646
- compileJsonPath(node) {
1647
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
1648
- return `json_extract(${col}, '${node.path}')`;
1649
- }
1650
- compileReturning(returning, ctx) {
1651
- if (!returning || returning.length === 0) return "";
1652
- const columns = this.formatReturningColumns(returning);
1653
- return ` RETURNING ${columns}`;
1654
- }
1655
- formatReturningColumns(returning) {
1656
- return returning.map((column) => {
1657
- const alias = column.alias ? ` AS ${this.quoteIdentifier(column.alias)}` : "";
1658
- return `${this.quoteIdentifier(column.name)}${alias}`;
1659
- }).join(", ");
1660
- }
1661
- supportsReturning() {
1662
- return true;
1663
- }
1664
- };
1665
-
1666
- // src/core/dialect/mssql/functions.ts
1667
- var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
1668
- constructor() {
1669
- super();
1670
- this.registerOverrides();
1671
- }
1672
- registerOverrides() {
1673
- this.add("NOW", () => `GETDATE()`);
1674
- this.add("CURRENT_DATE", () => `CAST(GETDATE() AS DATE)`);
1675
- this.add("CURRENT_TIME", () => `CAST(GETDATE() AS TIME)`);
1676
- this.add("UTC_NOW", () => `GETUTCDATE()`);
1677
- this.add("EXTRACT", ({ compiledArgs }) => {
1678
- if (compiledArgs.length !== 2) throw new Error("EXTRACT expects 2 arguments (part, date)");
1679
- const [part, date] = compiledArgs;
1680
- const partClean = part.replace(/['"]/g, "").toLowerCase();
1681
- return `DATEPART(${partClean}, ${date})`;
1682
- });
1683
- this.add("YEAR", ({ compiledArgs }) => {
1684
- if (compiledArgs.length !== 1) throw new Error("YEAR expects 1 argument");
1685
- return `YEAR(${compiledArgs[0]})`;
1686
- });
1687
- this.add("MONTH", ({ compiledArgs }) => {
1688
- if (compiledArgs.length !== 1) throw new Error("MONTH expects 1 argument");
1689
- return `MONTH(${compiledArgs[0]})`;
1690
- });
1691
- this.add("DAY", ({ compiledArgs }) => {
1692
- if (compiledArgs.length !== 1) throw new Error("DAY expects 1 argument");
1693
- return `DAY(${compiledArgs[0]})`;
1694
- });
1695
- this.add("DATE_ADD", ({ node, compiledArgs }) => {
1696
- if (compiledArgs.length !== 3) throw new Error("DATE_ADD expects 3 arguments (date, interval, unit)");
1697
- const [date, interval] = compiledArgs;
1698
- const unitArg = node.args[2];
1699
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1700
- return `DATEADD(${unitClean}, ${interval}, ${date})`;
1701
- });
1702
- this.add("DATE_SUB", ({ node, compiledArgs }) => {
1703
- if (compiledArgs.length !== 3) throw new Error("DATE_SUB expects 3 arguments (date, interval, unit)");
1704
- const [date, interval] = compiledArgs;
1705
- const unitArg = node.args[2];
1706
- const unitClean = String(unitArg.value).replace(/['"]/g, "").toLowerCase();
1707
- return `DATEADD(${unitClean}, -${interval}, ${date})`;
1708
- });
1709
- this.add("DATE_DIFF", ({ compiledArgs }) => {
1710
- if (compiledArgs.length !== 2) throw new Error("DATE_DIFF expects 2 arguments");
1711
- const [date1, date2] = compiledArgs;
1712
- return `DATEDIFF(day, ${date2}, ${date1})`;
1713
- });
1714
- this.add("DATE_FORMAT", ({ compiledArgs }) => {
1715
- if (compiledArgs.length !== 2) throw new Error("DATE_FORMAT expects 2 arguments");
1716
- const [date, format] = compiledArgs;
1717
- return `FORMAT(${date}, ${format})`;
1718
- });
1719
- this.add("UNIX_TIMESTAMP", () => `DATEDIFF(SECOND, '1970-01-01', GETUTCDATE())`);
1720
- this.add("FROM_UNIXTIME", ({ compiledArgs }) => {
1721
- if (compiledArgs.length !== 1) throw new Error("FROM_UNIXTIME expects 1 argument");
1722
- return `DATEADD(SECOND, ${compiledArgs[0]}, '1970-01-01')`;
1723
- });
1724
- this.add("END_OF_MONTH", ({ compiledArgs }) => {
1725
- if (compiledArgs.length !== 1) throw new Error("END_OF_MONTH expects 1 argument");
1726
- return `EOMONTH(${compiledArgs[0]})`;
1727
- });
1728
- this.add("DAY_OF_WEEK", ({ compiledArgs }) => {
1729
- if (compiledArgs.length !== 1) throw new Error("DAY_OF_WEEK expects 1 argument");
1730
- return `DATEPART(dw, ${compiledArgs[0]})`;
1731
- });
1732
- this.add("WEEK_OF_YEAR", ({ compiledArgs }) => {
1733
- if (compiledArgs.length !== 1) throw new Error("WEEK_OF_YEAR expects 1 argument");
1734
- return `DATEPART(wk, ${compiledArgs[0]})`;
1735
- });
1736
- this.add("DATE_TRUNC", ({ node, compiledArgs }) => {
1737
- if (compiledArgs.length !== 2) throw new Error("DATE_TRUNC expects 2 arguments (part, date)");
1738
- const [, date] = compiledArgs;
1739
- const partArg = node.args[0];
1740
- const partClean = String(partArg.value).replace(/['"]/g, "").toLowerCase();
1741
- return `DATETRUNC(${partClean}, ${date})`;
1742
- });
1743
- this.add("GROUP_CONCAT", (ctx) => {
1744
- const arg = ctx.compiledArgs[0];
1745
- const separatorOperand = this.getGroupConcatSeparatorOperand(ctx);
1746
- const separator = ctx.compileOperand(separatorOperand);
1747
- const orderClause = this.buildOrderByExpression(ctx);
1748
- const withinGroup = orderClause ? ` WITHIN GROUP (${orderClause})` : "";
1749
- return `STRING_AGG(${arg}, ${separator})${withinGroup}`;
1750
- });
1751
- }
1752
- };
1753
-
1754
- // src/core/dialect/mssql/index.ts
1755
- var SqlServerDialect = class extends Dialect {
1756
- /**
1757
- * Creates a new SqlServerDialect instance
1758
- */
1759
- constructor() {
1760
- super(new MssqlFunctionStrategy());
1761
- this.dialect = "mssql";
1762
- }
1763
- /**
1764
- * Quotes an identifier using SQL Server bracket syntax
1765
- * @param id - Identifier to quote
1766
- * @returns Quoted identifier
1767
- */
1768
- quoteIdentifier(id) {
1769
- return `[${id}]`;
1770
- }
1771
- /**
1772
- * Compiles JSON path expression using SQL Server syntax
1773
- * @param node - JSON path node
1774
- * @returns SQL Server JSON path expression
1775
- */
1776
- compileJsonPath(node) {
1777
- const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
1778
- return `JSON_VALUE(${col}, '${node.path}')`;
1779
- }
1780
- /**
1781
- * Formats parameter placeholders using SQL Server named parameter syntax
1782
- * @param index - Parameter index
1783
- * @returns Named parameter placeholder
1784
- */
1785
- formatPlaceholder(index) {
1786
- return `@p${index}`;
1787
- }
1788
- /**
1789
- * Compiles SELECT query AST to SQL Server SQL
1790
- * @param ast - Query AST
1791
- * @param ctx - Compiler context
1792
- * @returns SQL Server SQL string
1793
- */
1794
- compileSelectAst(ast, ctx) {
1795
- const hasSetOps = !!(ast.setOps && ast.setOps.length);
1796
- const ctes = this.compileCtes(ast, ctx);
1797
- const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
1798
- const baseSelect = this.compileSelectCore(baseAst, ctx);
1799
- if (!hasSetOps) {
1800
- return `${ctes}${baseSelect}`;
1801
- }
1802
- const compound = ast.setOps.map((op) => `${op.operator} ${this.wrapSetOperand(this.compileSelectAst(op.query, ctx))}`).join(" ");
1803
- const orderBy = this.compileOrderBy(ast);
1804
- const pagination = this.compilePagination(ast, orderBy);
1805
- const combined = `${this.wrapSetOperand(baseSelect)} ${compound}`;
1806
- const tail = pagination || orderBy;
1807
- return `${ctes}${combined}${tail}`;
1808
- }
1809
- compileInsertAst(ast, ctx) {
1810
- const table = this.quoteIdentifier(ast.into.name);
1811
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
1812
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1813
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
1814
- }
1815
- compileUpdateAst(ast, ctx) {
1816
- const table = this.quoteIdentifier(ast.table.name);
1817
- const assignments = ast.set.map((assignment) => {
1818
- const col = assignment.column;
1819
- const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
1820
- const value = this.compileOperand(assignment.value, ctx);
1821
- return `${target} = ${value}`;
1822
- }).join(", ");
1823
- const whereClause = this.compileWhere(ast.where, ctx);
1824
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
1825
- }
1826
- compileDeleteAst(ast, ctx) {
1827
- if (ast.from.type !== "Table") {
1828
- throw new Error("DELETE only supports base tables in the MSSQL dialect.");
1829
- }
1830
- const table = this.quoteIdentifier(ast.from.name);
1831
- const whereClause = this.compileWhere(ast.where, ctx);
1832
- return `DELETE FROM ${table}${whereClause};`;
1833
- }
1834
- compileSelectCore(ast, ctx) {
1835
- const columns = ast.columns.map((c) => {
1836
- let expr = "";
1837
- if (c.type === "Function") {
1838
- expr = this.compileOperand(c, ctx);
1839
- } else if (c.type === "Column") {
1840
- expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
1841
- } else if (c.type === "ScalarSubquery") {
1842
- expr = this.compileOperand(c, ctx);
1843
- } else if (c.type === "WindowFunction") {
1844
- expr = this.compileOperand(c, ctx);
1845
- }
1846
- if (c.alias) {
1847
- if (c.alias.includes("(")) return c.alias;
1848
- return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
1849
- }
1850
- return expr;
1851
- }).join(", ");
1852
- const distinct = ast.distinct ? "DISTINCT " : "";
1853
- const from = this.compileTableSource(ast.from, ctx);
1854
- const joins = ast.joins.map((j) => {
1855
- const table = this.compileTableSource(j.table, ctx);
1856
- const cond = this.compileExpression(j.condition, ctx);
1857
- return `${j.kind} JOIN ${table} ON ${cond}`;
1858
- }).join(" ");
1859
- const whereClause = this.compileWhere(ast.where, ctx);
1860
- const groupBy = ast.groupBy && ast.groupBy.length > 0 ? " GROUP BY " + ast.groupBy.map((c) => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(", ") : "";
1861
- const having = ast.having ? ` HAVING ${this.compileExpression(ast.having, ctx)}` : "";
1862
- const orderBy = this.compileOrderBy(ast);
1863
- const pagination = this.compilePagination(ast, orderBy);
1864
- if (pagination) {
1865
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${pagination}`;
1866
- }
1867
- return `SELECT ${distinct}${columns} FROM ${from}${joins ? " " + joins : ""}${whereClause}${groupBy}${having}${orderBy}`;
1868
- }
1869
- compileOrderBy(ast) {
1870
- if (!ast.orderBy || ast.orderBy.length === 0) return "";
1871
- return " ORDER BY " + ast.orderBy.map((o) => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(", ");
1872
- }
1873
- compilePagination(ast, orderBy) {
1874
- const hasLimit = ast.limit !== void 0;
1875
- const hasOffset = ast.offset !== void 0;
1876
- if (!hasLimit && !hasOffset) return "";
1877
- const off = ast.offset ?? 0;
1878
- const orderClause = orderBy || " ORDER BY (SELECT NULL)";
1879
- let pagination = `${orderClause} OFFSET ${off} ROWS`;
1880
- if (hasLimit) {
1881
- pagination += ` FETCH NEXT ${ast.limit} ROWS ONLY`;
1882
- }
1883
- return pagination;
1884
- }
1885
- compileTableSource(table, ctx) {
1886
- if (table.type === "FunctionTable") {
1887
- return FunctionTableFormatter.format(table, ctx, this);
1888
- }
1889
- if (table.type === "DerivedTable") {
1890
- return this.compileDerivedTable(table, ctx);
1891
- }
1892
- const base = table.schema ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}` : this.quoteIdentifier(table.name);
1893
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
1894
- }
1895
- compileDerivedTable(table, ctx) {
1896
- const sub = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, "");
1897
- const cols = table.columnAliases?.length ? ` (${table.columnAliases.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
1898
- return `(${sub}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
1899
- }
1900
- compileCtes(ast, ctx) {
1901
- if (!ast.ctes || ast.ctes.length === 0) return "";
1902
- const defs = ast.ctes.map((cte) => {
1903
- const name = this.quoteIdentifier(cte.name);
1904
- const cols = cte.columns ? `(${cte.columns.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
1905
- const query = this.compileSelectAst(this.normalizeSelectAst(cte.query), ctx).trim().replace(/;$/, "");
1906
- return `${name}${cols} AS (${query})`;
1907
- }).join(", ");
1908
- return `WITH ${defs} `;
1909
- }
1910
- wrapSetOperand(sql) {
1911
- const trimmed = sql.trim().replace(/;$/, "");
1912
- return `(${trimmed})`;
1913
- }
1914
- };
1915
-
1916
- // src/core/dialect/dialect-factory.ts
1917
- var DialectFactory = class {
1918
- static {
1919
- this.registry = /* @__PURE__ */ new Map();
1920
- }
1921
- static {
1922
- this.defaultsInitialized = false;
1923
- }
1924
- static ensureDefaults() {
1925
- if (this.defaultsInitialized) return;
1926
- this.defaultsInitialized = true;
1927
- if (!this.registry.has("postgres")) {
1928
- this.registry.set("postgres", () => new PostgresDialect());
1929
- }
1930
- if (!this.registry.has("mysql")) {
1931
- this.registry.set("mysql", () => new MySqlDialect());
1932
- }
1933
- if (!this.registry.has("sqlite")) {
1934
- this.registry.set("sqlite", () => new SqliteDialect());
1935
- }
1936
- if (!this.registry.has("mssql")) {
1937
- this.registry.set("mssql", () => new SqlServerDialect());
1938
- }
1939
- }
1940
- /**
1941
- * Register (or override) a dialect factory for a key.
1942
- *
1943
- * Examples:
1944
- * DialectFactory.register('sqlite', () => new SqliteDialect());
1945
- * DialectFactory.register('my-tenant-dialect', () => new CustomDialect());
1946
- */
1947
- static register(key, factory) {
1948
- this.registry.set(key, factory);
1949
- }
1950
- /**
1951
- * Resolve a key into a Dialect instance.
1952
- * Throws if the key is not registered.
1953
- */
1954
- static create(key) {
1955
- this.ensureDefaults();
1956
- const factory = this.registry.get(key);
1957
- if (!factory) {
1958
- throw new Error(
1959
- `Dialect "${String(
1960
- key
1961
- )}" is not registered. Use DialectFactory.register(...) to register it.`
1962
- );
1963
- }
1964
- return factory();
1965
- }
1966
- /**
1967
- * Clear all registrations (mainly for tests).
1968
- * Built-ins will be re-registered lazily on the next create().
1969
- */
1970
- static clear() {
1971
- this.registry.clear();
1972
- this.defaultsInitialized = false;
1973
- }
1974
- };
1975
- var resolveDialectInput = (dialect) => {
1976
- if (typeof dialect === "string") {
1977
- return DialectFactory.create(dialect);
1978
- }
1979
- return dialect;
1980
- };
1981
-
1982
- // src/query-builder/select-query-state.ts
1983
- var SelectQueryState = class _SelectQueryState {
1984
- /**
1985
- * Creates a new SelectQueryState instance
1986
- * @param table - Table definition
1987
- * @param ast - Optional existing AST
1988
- */
1989
- constructor(table, ast) {
1990
- this.table = table;
1991
- this.ast = ast ?? {
1992
- type: "SelectQuery",
1993
- from: { type: "Table", name: table.name },
1994
- columns: [],
1995
- joins: []
1996
- };
1997
- }
1998
- /**
1999
- * Creates a new SelectQueryState with updated AST
2000
- * @param nextAst - Updated AST
2001
- * @returns New SelectQueryState instance
2002
- */
2003
- clone(nextAst) {
2004
- return new _SelectQueryState(this.table, nextAst);
2005
- }
2006
- /**
2007
- * Adds columns to the query
2008
- * @param newCols - Columns to add
2009
- * @returns New SelectQueryState with added columns
2010
- */
2011
- withColumns(newCols) {
2012
- return this.clone({
2013
- ...this.ast,
2014
- columns: [...this.ast.columns ?? [], ...newCols]
2015
- });
2016
- }
2017
- /**
2018
- * Adds a join to the query
2019
- * @param join - Join node to add
2020
- * @returns New SelectQueryState with added join
2021
- */
2022
- withJoin(join) {
2023
- return this.clone({
2024
- ...this.ast,
2025
- joins: [...this.ast.joins ?? [], join]
2026
- });
2027
- }
2028
- /**
2029
- * Replaces the FROM clause.
2030
- * @param from - Table source for the FROM clause
2031
- * @returns New SelectQueryState with updated FROM
2032
- */
2033
- withFrom(from) {
2034
- return this.clone({
2035
- ...this.ast,
2036
- from
2037
- });
2038
- }
2039
- /**
2040
- * Adds a WHERE clause to the query
2041
- * @param predicate - WHERE predicate expression
2042
- * @returns New SelectQueryState with WHERE clause
2043
- */
2044
- withWhere(predicate) {
2045
- return this.clone({
2046
- ...this.ast,
2047
- where: predicate
2048
- });
2049
- }
2050
- /**
2051
- * Adds a HAVING clause to the query
2052
- * @param predicate - HAVING predicate expression
2053
- * @returns New SelectQueryState with HAVING clause
2054
- */
2055
- withHaving(predicate) {
2056
- return this.clone({
2057
- ...this.ast,
2058
- having: predicate
2059
- });
2060
- }
2061
- /**
2062
- * Adds GROUP BY columns to the query
2063
- * @param columns - Columns to group by
2064
- * @returns New SelectQueryState with GROUP BY clause
2065
- */
2066
- withGroupBy(columns) {
2067
- return this.clone({
2068
- ...this.ast,
2069
- groupBy: [...this.ast.groupBy ?? [], ...columns]
2070
- });
2071
- }
2072
- /**
2073
- * Adds ORDER BY clauses to the query
2074
- * @param orderBy - ORDER BY nodes
2075
- * @returns New SelectQueryState with ORDER BY clause
2076
- */
2077
- withOrderBy(orderBy) {
2078
- return this.clone({
2079
- ...this.ast,
2080
- orderBy: [...this.ast.orderBy ?? [], ...orderBy]
2081
- });
2082
- }
2083
- /**
2084
- * Adds DISTINCT columns to the query
2085
- * @param columns - Columns to make distinct
2086
- * @returns New SelectQueryState with DISTINCT clause
2087
- */
2088
- withDistinct(columns) {
2089
- return this.clone({
2090
- ...this.ast,
2091
- distinct: [...this.ast.distinct ?? [], ...columns]
2092
- });
2093
- }
2094
- /**
2095
- * Adds a LIMIT clause to the query
2096
- * @param limit - Maximum number of rows to return
2097
- * @returns New SelectQueryState with LIMIT clause
2098
- */
2099
- withLimit(limit) {
2100
- return this.clone({
2101
- ...this.ast,
2102
- limit
2103
- });
2104
- }
2105
- /**
2106
- * Adds an OFFSET clause to the query
2107
- * @param offset - Number of rows to skip
2108
- * @returns New SelectQueryState with OFFSET clause
2109
- */
2110
- withOffset(offset) {
2111
- return this.clone({
2112
- ...this.ast,
2113
- offset
2114
- });
2115
- }
2116
- /**
2117
- * Adds a Common Table Expression (CTE) to the query
2118
- * @param cte - CTE node to add
2119
- * @returns New SelectQueryState with CTE
2120
- */
2121
- withCte(cte) {
2122
- return this.clone({
2123
- ...this.ast,
2124
- ctes: [...this.ast.ctes ?? [], cte]
2125
- });
2126
- }
2127
- /**
2128
- * Adds a set operation (UNION/INTERSECT/EXCEPT) to the query
2129
- * @param op - Set operation node to add
2130
- * @returns New SelectQueryState with set operation
2131
- */
2132
- withSetOperation(op) {
2133
- return this.clone({
2134
- ...this.ast,
2135
- setOps: [...this.ast.setOps ?? [], op]
2136
- });
2137
- }
2138
- };
2139
-
2140
- // src/core/ast/join-node.ts
2141
- var createJoinNode = (kind, tableName, condition, relationName) => ({
2142
- type: "Join",
2143
- kind,
2144
- table: typeof tableName === "string" ? { type: "Table", name: tableName } : tableName,
2145
- condition,
2146
- meta: relationName ? { relationName } : void 0
2147
- });
2148
-
2149
- // src/query-builder/hydration-manager.ts
2150
- var HydrationManager = class _HydrationManager {
2151
- /**
2152
- * Creates a new HydrationManager instance
2153
- * @param table - Table definition
2154
- * @param planner - Hydration planner
2155
- */
2156
- constructor(table, planner) {
2157
- this.table = table;
2158
- this.planner = planner;
2159
- }
2160
- /**
2161
- * Creates a new HydrationManager with updated planner
2162
- * @param nextPlanner - Updated hydration planner
2163
- * @returns New HydrationManager instance
2164
- */
2165
- clone(nextPlanner) {
2166
- return new _HydrationManager(this.table, nextPlanner);
2167
- }
2168
- /**
2169
- * Handles column selection for hydration planning
2170
- * @param state - Current query state
2171
- * @param newColumns - Newly selected columns
2172
- * @returns Updated HydrationManager with captured columns
2173
- */
2174
- onColumnsSelected(state, newColumns) {
2175
- const updated = this.planner.captureRootColumns(newColumns);
2176
- return this.clone(updated);
2177
- }
2178
- /**
2179
- * Handles relation inclusion for hydration planning
2180
- * @param state - Current query state
2181
- * @param relation - Relation definition
2182
- * @param relationName - Name of the relation
2183
- * @param aliasPrefix - Alias prefix for the relation
2184
- * @param targetColumns - Target columns to include
2185
- * @returns Updated HydrationManager with included relation
2186
- */
2187
- onRelationIncluded(state, relation, relationName, aliasPrefix, targetColumns, pivot) {
2188
- const withRoots = this.planner.captureRootColumns(state.ast.columns);
2189
- const next = withRoots.includeRelation(relation, relationName, aliasPrefix, targetColumns, pivot);
2190
- return this.clone(next);
2191
- }
2192
- /**
2193
- * Applies hydration plan to the AST
2194
- * @param ast - Query AST to modify
2195
- * @returns AST with hydration metadata
2196
- */
2197
- applyToAst(ast) {
2198
- if (ast.setOps && ast.setOps.length > 0) {
2199
- return ast;
2200
- }
2201
- const plan = this.planner.getPlan();
2202
- if (!plan) return ast;
2203
- const needsPaginationGuard = this.requiresParentPagination(ast, plan);
2204
- const rewritten = needsPaginationGuard ? this.wrapForParentPagination(ast, plan) : ast;
2205
- return this.attachHydrationMeta(rewritten, plan);
2206
- }
2207
- /**
2208
- * Gets the current hydration plan
2209
- * @returns Hydration plan or undefined if none exists
2210
- */
2211
- getPlan() {
2212
- return this.planner.getPlan();
2213
- }
2214
- /**
2215
- * Attaches hydration metadata to a query AST node.
2216
- */
2217
- attachHydrationMeta(ast, plan) {
2218
- return {
2219
- ...ast,
2220
- meta: {
2221
- ...ast.meta || {},
2222
- hydration: plan
2223
- }
2224
- };
2225
- }
2226
- /**
2227
- * Determines whether the query needs pagination rewriting to keep LIMIT/OFFSET
2228
- * applied to parent rows when eager-loading multiplicative relations.
2229
- */
2230
- requiresParentPagination(ast, plan) {
2231
- const hasPagination = ast.limit !== void 0 || ast.offset !== void 0;
2232
- return hasPagination && this.hasMultiplyingRelations(plan);
2233
- }
2234
- hasMultiplyingRelations(plan) {
2235
- return plan.relations.some(
2236
- (rel) => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
2237
- );
2238
- }
2239
- /**
2240
- * Rewrites the query using CTEs so LIMIT/OFFSET target distinct parent rows
2241
- * instead of the joined result set.
2242
- *
2243
- * The strategy:
2244
- * - Hoist the original query (minus limit/offset) into a base CTE.
2245
- * - Select distinct parent ids from that base CTE with the original ordering and pagination.
2246
- * - Join the base CTE against the paged ids to retrieve the joined rows for just that page.
2247
- */
2248
- wrapForParentPagination(ast, plan) {
2249
- const projectionNames = this.getProjectionNames(ast.columns);
2250
- if (!projectionNames) {
2251
- return ast;
2252
- }
2253
- const projectionAliases = this.buildProjectionAliasMap(ast.columns);
2254
- const projectionSet = new Set(projectionNames);
2255
- const rootPkAlias = projectionAliases.get(`${plan.rootTable}.${plan.rootPrimaryKey}`) ?? plan.rootPrimaryKey;
2256
- const baseCteName = this.nextCteName(ast.ctes, "__metal_pagination_base");
2257
- const baseQuery = {
2258
- ...ast,
2259
- ctes: void 0,
2260
- limit: void 0,
2261
- offset: void 0,
2262
- orderBy: void 0,
2263
- meta: void 0
2264
- };
2265
- const baseCte = {
2266
- type: "CommonTableExpression",
2267
- name: baseCteName,
2268
- query: baseQuery,
2269
- recursive: false
2270
- };
2271
- const orderBy = this.mapOrderBy(ast.orderBy, plan, projectionAliases, baseCteName, projectionSet);
2272
- if (orderBy === null) {
2273
- return ast;
2274
- }
2275
- const pageCteName = this.nextCteName([...ast.ctes ?? [], baseCte], "__metal_pagination_page");
2276
- const pagingColumns = this.buildPagingColumns(rootPkAlias, orderBy, baseCteName);
2277
- const pageCte = {
2278
- type: "CommonTableExpression",
2279
- name: pageCteName,
2280
- query: {
2281
- type: "SelectQuery",
2282
- from: { type: "Table", name: baseCteName },
2283
- columns: pagingColumns,
2284
- joins: [],
2285
- distinct: [{ type: "Column", table: baseCteName, name: rootPkAlias }],
2286
- orderBy,
2287
- limit: ast.limit,
2288
- offset: ast.offset
2289
- },
2290
- recursive: false
2291
- };
2292
- const joinCondition = eq(
2293
- { type: "Column", table: baseCteName, name: rootPkAlias },
2294
- { type: "Column", table: pageCteName, name: rootPkAlias }
2295
- );
2296
- const outerColumns = projectionNames.map((name) => ({
2297
- type: "Column",
2298
- table: baseCteName,
2299
- name,
2300
- alias: name
2301
- }));
2302
- return {
2303
- type: "SelectQuery",
2304
- from: { type: "Table", name: baseCteName },
2305
- columns: outerColumns,
2306
- joins: [createJoinNode(JOIN_KINDS.INNER, pageCteName, joinCondition)],
2307
- orderBy,
2308
- ctes: [...ast.ctes ?? [], baseCte, pageCte]
2309
- };
2310
- }
2311
- nextCteName(existing, baseName) {
2312
- const names = new Set((existing ?? []).map((cte) => cte.name));
2313
- let candidate = baseName;
2314
- let suffix = 1;
2315
- while (names.has(candidate)) {
2316
- suffix += 1;
2317
- candidate = `${baseName}_${suffix}`;
2318
- }
2319
- return candidate;
2320
- }
2321
- getProjectionNames(columns) {
2322
- const names = [];
2323
- for (const col of columns) {
2324
- const alias = col.alias ?? col.name;
2325
- if (!alias) return void 0;
2326
- names.push(alias);
2327
- }
2328
- return names;
2329
- }
2330
- buildProjectionAliasMap(columns) {
2331
- const map = /* @__PURE__ */ new Map();
2332
- for (const col of columns) {
2333
- if (col.type !== "Column") continue;
2334
- const node = col;
2335
- const key = `${node.table}.${node.name}`;
2336
- map.set(key, node.alias ?? node.name);
2337
- }
2338
- return map;
2339
- }
2340
- mapOrderBy(orderBy, plan, projectionAliases, baseAlias, availableColumns) {
2341
- if (!orderBy || orderBy.length === 0) {
2342
- return void 0;
2343
- }
2344
- const mapped = [];
2345
- for (const ob of orderBy) {
2346
- if (ob.column.table !== plan.rootTable) {
2347
- return null;
2348
- }
2349
- const alias = projectionAliases.get(`${ob.column.table}.${ob.column.name}`) ?? ob.column.name;
2350
- if (!availableColumns.has(alias)) {
2351
- return null;
2352
- }
2353
- mapped.push({
2354
- type: "OrderBy",
2355
- column: { type: "Column", table: baseAlias, name: alias },
2356
- direction: ob.direction
2357
- });
2358
- }
2359
- return mapped;
2360
- }
2361
- buildPagingColumns(primaryKey, orderBy, tableAlias) {
2362
- const columns = [{ type: "Column", table: tableAlias, name: primaryKey, alias: primaryKey }];
2363
- if (!orderBy) return columns;
2364
- for (const ob of orderBy) {
2365
- if (!columns.some((col) => col.name === ob.column.name)) {
2366
- columns.push({
2367
- type: "Column",
2368
- table: tableAlias,
2369
- name: ob.column.name,
2370
- alias: ob.column.name
2371
- });
2372
- }
2373
- }
2374
- return columns;
2375
- }
2376
- };
2377
-
2378
- // src/query-builder/relation-alias.ts
2379
- var RELATION_SEPARATOR = "__";
2380
- var makeRelationAlias = (relationName, columnName) => `${relationName}${RELATION_SEPARATOR}${columnName}`;
2381
- var isRelationAlias = (alias) => !!alias && alias.includes(RELATION_SEPARATOR);
2382
-
2383
- // src/query-builder/relation-utils.ts
2384
- var buildDefaultPivotColumns = (rel, pivotPk) => {
2385
- const excluded = /* @__PURE__ */ new Set([pivotPk, rel.pivotForeignKeyToRoot, rel.pivotForeignKeyToTarget]);
2386
- return Object.keys(rel.pivotTable.columns).filter((col) => !excluded.has(col));
2387
- };
2388
-
2389
- // src/query-builder/hydration-planner.ts
2390
- var findPrimaryKey = (table) => {
2391
- const pk = Object.values(table.columns).find((c) => c.primary);
2392
- return pk?.name || "id";
2393
- };
2394
- var HydrationPlanner = class _HydrationPlanner {
2395
- /**
2396
- * Creates a new HydrationPlanner instance
2397
- * @param table - Table definition
2398
- * @param plan - Optional existing hydration plan
2399
- */
2400
- constructor(table, plan) {
2401
- this.table = table;
2402
- this.plan = plan;
2403
- }
2404
- /**
2405
- * Captures root table columns for hydration planning
2406
- * @param columns - Columns to capture
2407
- * @returns Updated HydrationPlanner with captured columns
2408
- */
2409
- captureRootColumns(columns) {
2410
- const currentPlan = this.getPlanOrDefault();
2411
- const rootCols = new Set(currentPlan.rootColumns);
2412
- let changed = false;
2413
- columns.forEach((node) => {
2414
- if (node.type !== "Column") return;
2415
- if (node.table !== this.table.name) return;
2416
- const alias = node.alias || node.name;
2417
- if (isRelationAlias(alias)) return;
2418
- if (!rootCols.has(alias)) {
2419
- rootCols.add(alias);
2420
- changed = true;
2421
- }
2422
- });
2423
- if (!changed) return this;
2424
- return new _HydrationPlanner(this.table, {
2425
- ...currentPlan,
2426
- rootColumns: Array.from(rootCols)
2427
- });
2428
- }
2429
- /**
2430
- * Includes a relation in the hydration plan
2431
- * @param rel - Relation definition
2432
- * @param relationName - Name of the relation
2433
- * @param aliasPrefix - Alias prefix for relation columns
2434
- * @param columns - Columns to include from the relation
2435
- * @returns Updated HydrationPlanner with included relation
2436
- */
2437
- includeRelation(rel, relationName, aliasPrefix, columns, pivot) {
2438
- const currentPlan = this.getPlanOrDefault();
2439
- const relations = currentPlan.relations.filter((r) => r.name !== relationName);
2440
- relations.push(this.buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot));
2441
- return new _HydrationPlanner(this.table, {
2442
- ...currentPlan,
2443
- relations
2444
- });
2445
- }
2446
- /**
2447
- * Gets the current hydration plan
2448
- * @returns Current hydration plan or undefined
2449
- */
2450
- getPlan() {
2451
- return this.plan;
2452
- }
2453
- /**
2454
- * Gets the current hydration plan or creates a default one
2455
- * @returns Current hydration plan or default plan
2456
- */
2457
- getPlanOrDefault() {
2458
- return this.plan ?? buildDefaultHydrationPlan(this.table);
2459
- }
2460
- /**
2461
- * Builds a relation plan for hydration
2462
- * @param rel - Relation definition
2463
- * @param relationName - Name of the relation
2464
- * @param aliasPrefix - Alias prefix for relation columns
2465
- * @param columns - Columns to include from the relation
2466
- * @returns Hydration relation plan
2467
- */
2468
- buildRelationPlan(rel, relationName, aliasPrefix, columns, pivot) {
2469
- switch (rel.type) {
2470
- case RelationKinds.HasMany:
2471
- case RelationKinds.HasOne: {
2472
- const localKey = rel.localKey || findPrimaryKey(this.table);
2473
- return {
2474
- name: relationName,
2475
- aliasPrefix,
2476
- type: rel.type,
2477
- targetTable: rel.target.name,
2478
- targetPrimaryKey: findPrimaryKey(rel.target),
2479
- foreignKey: rel.foreignKey,
2480
- localKey,
2481
- columns
2482
- };
2483
- }
2484
- case RelationKinds.BelongsTo: {
2485
- const localKey = rel.localKey || findPrimaryKey(rel.target);
2486
- return {
2487
- name: relationName,
2488
- aliasPrefix,
2489
- type: rel.type,
2490
- targetTable: rel.target.name,
2491
- targetPrimaryKey: findPrimaryKey(rel.target),
2492
- foreignKey: rel.foreignKey,
2493
- localKey,
2494
- columns
2495
- };
2496
- }
2497
- case RelationKinds.BelongsToMany: {
2498
- const many = rel;
2499
- const localKey = many.localKey || findPrimaryKey(this.table);
2500
- const targetPk = many.targetKey || findPrimaryKey(many.target);
2501
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
2502
- const pivotAliasPrefix = pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
2503
- const pivotColumns = pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
2504
- return {
2505
- name: relationName,
2506
- aliasPrefix,
2507
- type: rel.type,
2508
- targetTable: many.target.name,
2509
- targetPrimaryKey: targetPk,
2510
- foreignKey: many.pivotForeignKeyToRoot,
2511
- localKey,
2512
- columns,
2513
- pivot: {
2514
- table: many.pivotTable.name,
2515
- primaryKey: pivotPk,
2516
- aliasPrefix: pivotAliasPrefix,
2517
- columns: pivotColumns
2518
- }
2519
- };
2520
- }
2521
- }
2522
- }
2523
- };
2524
- var buildDefaultHydrationPlan = (table) => ({
2525
- rootTable: table.name,
2526
- rootPrimaryKey: findPrimaryKey(table),
2527
- rootColumns: [],
2528
- relations: []
2529
- });
2530
-
2531
- // src/query-builder/raw-column-parser.ts
2532
- var parseRawColumn = (col, tableName, ctes) => {
2533
- if (col.includes("(")) {
2534
- const [fn, rest] = col.split("(");
2535
- const colName = rest.replace(")", "");
2536
- const [table, name] = colName.includes(".") ? colName.split(".") : [tableName, colName];
2537
- return { type: "Column", table, name, alias: col };
2538
- }
2539
- if (col.includes(".")) {
2540
- const [potentialCteName, columnName] = col.split(".");
2541
- const hasCte = ctes?.some((cte) => cte.name === potentialCteName);
2542
- if (hasCte) {
2543
- return { type: "Column", table: tableName, name: col };
2544
- }
2545
- return { type: "Column", table: potentialCteName, name: columnName };
2546
- }
2547
- return { type: "Column", table: tableName, name: col };
2548
- };
2549
-
2550
- // src/query-builder/query-ast-service.ts
2551
- var QueryAstService = class {
2552
- /**
2553
- * Creates a new QueryAstService instance
2554
- * @param table - Table definition
2555
- * @param state - Current query state
2556
- */
2557
- constructor(table, state) {
2558
- this.table = table;
2559
- this.state = state;
2560
- }
2561
- /**
2562
- * Selects columns for the query
2563
- * @param columns - Columns to select (key: alias, value: column definition or expression)
2564
- * @returns Column selection result with updated state and added columns
2565
- */
2566
- select(columns) {
2567
- const existingAliases = new Set(
2568
- this.state.ast.columns.map((c) => c.alias || c.name)
2569
- );
2570
- const from = this.state.ast.from;
2571
- const rootTableName = from.type === "Table" && from.alias ? from.alias : this.table.name;
2572
- const newCols = Object.entries(columns).reduce((acc, [alias, val]) => {
2573
- if (existingAliases.has(alias)) return acc;
2574
- if (isExpressionSelectionNode(val)) {
2575
- acc.push({ ...val, alias });
2576
- return acc;
2577
- }
2578
- const colDef = val;
2579
- const resolvedTable = colDef.table && colDef.table === this.table.name && from.type === "Table" && from.alias ? from.alias : colDef.table || rootTableName;
2580
- acc.push({
2581
- type: "Column",
2582
- table: resolvedTable,
2583
- name: colDef.name,
2584
- alias
2585
- });
2586
- return acc;
2587
- }, []);
2588
- const nextState = this.state.withColumns(newCols);
2589
- return { state: nextState, addedColumns: newCols };
2590
- }
2591
- /**
2592
- * Selects raw column expressions (best-effort parser for simple references/functions)
2593
- * @param cols - Raw column expressions
2594
- * @returns Column selection result with updated state and added columns
2595
- */
2596
- selectRaw(cols) {
2597
- const from = this.state.ast.from;
2598
- const defaultTable = from.type === "Table" && from.alias ? from.alias : this.table.name;
2599
- const newCols = cols.map((col) => parseRawColumn(col, defaultTable, this.state.ast.ctes));
2600
- const nextState = this.state.withColumns(newCols);
2601
- return { state: nextState, addedColumns: newCols };
2602
- }
2603
- /**
2604
- * Adds a Common Table Expression (CTE) to the query
2605
- * @param name - Name of the CTE
2606
- * @param query - Query for the CTE
2607
- * @param columns - Optional column names for the CTE
2608
- * @param recursive - Whether the CTE is recursive
2609
- * @returns Updated query state with CTE
2610
- */
2611
- withCte(name, query, columns, recursive = false) {
2612
- const cte = {
2613
- type: "CommonTableExpression",
2614
- name,
2615
- query,
2616
- columns,
2617
- recursive
2618
- };
2619
- return this.state.withCte(cte);
2620
- }
2621
- /**
2622
- * Adds a set operation (UNION/UNION ALL/INTERSECT/EXCEPT) to the query
2623
- * @param operator - Set operator
2624
- * @param query - Right-hand side query
2625
- * @returns Updated query state with set operation
2626
- */
2627
- withSetOperation(operator, query) {
2628
- const op = {
2629
- type: "SetOperation",
2630
- operator,
2631
- query
2632
- };
2633
- return this.state.withSetOperation(op);
2634
- }
2635
- /**
2636
- * Replaces the FROM clause for the current query.
2637
- * @param from - Table source to use in the FROM clause
2638
- * @returns Updated query state with new FROM
2639
- */
2640
- withFrom(from) {
2641
- return this.state.withFrom(from);
2642
- }
2643
- /**
2644
- * Selects a subquery as a column
2645
- * @param alias - Alias for the subquery
2646
- * @param query - Subquery to select
2647
- * @returns Updated query state with subquery selection
2648
- */
2649
- selectSubquery(alias, query) {
2650
- const node = { type: "ScalarSubquery", query, alias };
2651
- return this.state.withColumns([node]);
2652
- }
2653
- /**
2654
- * Adds a JOIN clause to the query
2655
- * @param join - Join node to add
2656
- * @returns Updated query state with JOIN
2657
- */
2658
- withJoin(join) {
2659
- return this.state.withJoin(join);
2660
- }
2661
- /**
2662
- * Adds a WHERE clause to the query
2663
- * @param expr - Expression for the WHERE clause
2664
- * @returns Updated query state with WHERE clause
2665
- */
2666
- withWhere(expr) {
2667
- const combined = this.combineExpressions(this.state.ast.where, expr);
2668
- return this.state.withWhere(combined);
2669
- }
2670
- /**
2671
- * Adds a GROUP BY clause to the query
2672
- * @param col - Column to group by
2673
- * @returns Updated query state with GROUP BY clause
2674
- */
2675
- withGroupBy(col) {
2676
- const from = this.state.ast.from;
2677
- const tableRef = from.type === "Table" && from.alias ? { ...this.table, alias: from.alias } : this.table;
2678
- const node = buildColumnNode(tableRef, col);
2679
- return this.state.withGroupBy([node]);
2680
- }
2681
- /**
2682
- * Adds a HAVING clause to the query
2683
- * @param expr - Expression for the HAVING clause
2684
- * @returns Updated query state with HAVING clause
2685
- */
2686
- withHaving(expr) {
2687
- const combined = this.combineExpressions(this.state.ast.having, expr);
2688
- return this.state.withHaving(combined);
2689
- }
2690
- /**
2691
- * Adds an ORDER BY clause to the query
2692
- * @param col - Column to order by
2693
- * @param direction - Order direction (ASC/DESC)
2694
- * @returns Updated query state with ORDER BY clause
2695
- */
2696
- withOrderBy(col, direction) {
2697
- const from = this.state.ast.from;
2698
- const tableRef = from.type === "Table" && from.alias ? { ...this.table, alias: from.alias } : this.table;
2699
- const node = buildColumnNode(tableRef, col);
2700
- return this.state.withOrderBy([{ type: "OrderBy", column: node, direction }]);
2701
- }
2702
- /**
2703
- * Adds a DISTINCT clause to the query
2704
- * @param cols - Columns to make distinct
2705
- * @returns Updated query state with DISTINCT clause
2706
- */
2707
- withDistinct(cols) {
2708
- return this.state.withDistinct(cols);
2709
- }
2710
- /**
2711
- * Adds a LIMIT clause to the query
2712
- * @param limit - Maximum number of rows to return
2713
- * @returns Updated query state with LIMIT clause
2714
- */
2715
- withLimit(limit) {
2716
- return this.state.withLimit(limit);
2717
- }
2718
- /**
2719
- * Adds an OFFSET clause to the query
2720
- * @param offset - Number of rows to skip
2721
- * @returns Updated query state with OFFSET clause
2722
- */
2723
- withOffset(offset) {
2724
- return this.state.withOffset(offset);
2725
- }
2726
- /**
2727
- * Combines expressions with AND operator
2728
- * @param existing - Existing expression
2729
- * @param next - New expression to combine
2730
- * @returns Combined expression
2731
- */
2732
- combineExpressions(existing, next) {
2733
- return existing ? and(existing, next) : next;
2734
- }
2735
- };
2736
-
2737
- // src/query-builder/relation-projection-helper.ts
2738
- var RelationProjectionHelper = class {
2739
- /**
2740
- * Creates a new RelationProjectionHelper instance
2741
- * @param table - Table definition
2742
- * @param selectColumns - Callback for selecting columns
2743
- */
2744
- constructor(table, selectColumns) {
2745
- this.table = table;
2746
- this.selectColumns = selectColumns;
2747
- }
2748
- /**
2749
- * Ensures base projection is included in the query
2750
- * @param state - Current query state
2751
- * @param hydration - Hydration manager
2752
- * @returns Relation result with updated state and hydration
2753
- */
2754
- ensureBaseProjection(state, hydration) {
2755
- const primaryKey = findPrimaryKey(this.table);
2756
- if (!this.hasBaseProjection(state)) {
2757
- return this.selectColumns(state, hydration, this.getBaseColumns());
2758
- }
2759
- if (primaryKey && !this.hasPrimarySelected(state, primaryKey) && this.table.columns[primaryKey]) {
2760
- return this.selectColumns(state, hydration, {
2761
- [primaryKey]: this.table.columns[primaryKey]
2762
- });
2763
- }
2764
- return { state, hydration };
2765
- }
2766
- /**
2767
- * Checks if base projection exists in the query
2768
- * @param state - Current query state
2769
- * @returns True if base projection exists
2770
- */
2771
- hasBaseProjection(state) {
2772
- return state.ast.columns.some((col) => !isRelationAlias(col.alias));
2773
- }
2774
- /**
2775
- * Checks if primary key is selected in the query
2776
- * @param state - Current query state
2777
- * @param primaryKey - Primary key name
2778
- * @returns True if primary key is selected
2779
- */
2780
- hasPrimarySelected(state, primaryKey) {
2781
- return state.ast.columns.some((col) => {
2782
- const alias = col.alias;
2783
- const name = alias || col.name;
2784
- return !isRelationAlias(alias) && name === primaryKey;
2785
- });
2786
- }
2787
- /**
2788
- * Gets all base columns for the table
2789
- * @returns Record of all table columns
2790
- */
2791
- getBaseColumns() {
2792
- return Object.keys(this.table.columns).reduce((acc, key) => {
2793
- acc[key] = this.table.columns[key];
2794
- return acc;
2795
- }, {});
2796
- }
2797
- };
2798
-
2799
- // src/query-builder/relation-conditions.ts
2800
- var assertNever = (value) => {
2801
- throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
2802
- };
2803
- var baseRelationCondition = (root, relation, rootAlias) => {
2804
- const rootTable = rootAlias || root.name;
2805
- const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
2806
- const localKey = relation.localKey || defaultLocalKey;
2807
- switch (relation.type) {
2808
- case RelationKinds.HasMany:
2809
- case RelationKinds.HasOne:
2810
- return eq(
2811
- { type: "Column", table: relation.target.name, name: relation.foreignKey },
2812
- { type: "Column", table: rootTable, name: localKey }
2813
- );
2814
- case RelationKinds.BelongsTo:
2815
- return eq(
2816
- { type: "Column", table: relation.target.name, name: localKey },
2817
- { type: "Column", table: rootTable, name: relation.foreignKey }
2818
- );
2819
- case RelationKinds.BelongsToMany:
2820
- throw new Error("BelongsToMany relations do not support the standard join condition builder");
2821
- default:
2822
- return assertNever(relation);
2823
- }
2824
- };
2825
- var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias) => {
2826
- const rootKey = relation.localKey || findPrimaryKey(root);
2827
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
2828
- const rootTable = rootAlias || root.name;
2829
- const pivotCondition = eq(
2830
- { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
2831
- { type: "Column", table: rootTable, name: rootKey }
2832
- );
2833
- const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
2834
- let targetCondition = eq(
2835
- { type: "Column", table: relation.target.name, name: targetKey },
2836
- { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
2837
- );
2838
- if (extra) {
2839
- targetCondition = and(targetCondition, extra);
2840
- }
2841
- const targetJoin = createJoinNode(
2842
- joinKind,
2843
- relation.target.name,
2844
- targetCondition,
2845
- relationName
2846
- );
2847
- return [pivotJoin, targetJoin];
2848
- };
2849
- var buildRelationJoinCondition = (root, relation, extra, rootAlias) => {
2850
- const base = baseRelationCondition(root, relation, rootAlias);
2851
- return extra ? and(base, extra) : base;
2852
- };
2853
- var buildRelationCorrelation = (root, relation, rootAlias) => {
2854
- return baseRelationCondition(root, relation, rootAlias);
2855
- };
2856
-
2857
- // src/core/ast/join-metadata.ts
2858
- var getJoinRelationName = (join) => join.meta?.relationName;
2859
-
2860
- // src/query-builder/relation-service.ts
2861
- var RelationService = class {
2862
- /**
2863
- * Creates a new RelationService instance
2864
- * @param table - Table definition
2865
- * @param state - Current query state
2866
- * @param hydration - Hydration manager
2867
- */
2868
- constructor(table, state, hydration, createQueryAstService) {
2869
- this.table = table;
2870
- this.state = state;
2871
- this.hydration = hydration;
2872
- this.createQueryAstService = createQueryAstService;
2873
- this.projectionHelper = new RelationProjectionHelper(
2874
- table,
2875
- (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
2876
- );
2877
- }
2878
- /**
2879
- * Joins a relation to the query
2880
- * @param relationName - Name of the relation to join
2881
- * @param joinKind - Type of join to use
2882
- * @param extraCondition - Additional join condition
2883
- * @returns Relation result with updated state and hydration
2884
- */
2885
- joinRelation(relationName, joinKind, extraCondition) {
2886
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
2887
- return { state: nextState, hydration: this.hydration };
2888
- }
2889
- /**
2890
- * Matches records based on a relation with an optional predicate
2891
- * @param relationName - Name of the relation to match
2892
- * @param predicate - Optional predicate expression
2893
- * @returns Relation result with updated state and hydration
2894
- */
2895
- match(relationName, predicate) {
2896
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
2897
- const pk = findPrimaryKey(this.table);
2898
- const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
2899
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
2900
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
2901
- return { state: nextState, hydration: joined.hydration };
2902
- }
2903
- /**
2904
- * Includes a relation in the query result
2905
- * @param relationName - Name of the relation to include
2906
- * @param options - Options for relation inclusion
2907
- * @returns Relation result with updated state and hydration
2908
- */
2909
- include(relationName, options) {
2910
- let state = this.state;
2911
- let hydration = this.hydration;
2912
- const relation = this.getRelation(relationName);
2913
- const aliasPrefix = options?.aliasPrefix ?? relationName;
2914
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
2915
- if (!alreadyJoined) {
2916
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
2917
- state = joined.state;
2918
- }
2919
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
2920
- state = projectionResult.state;
2921
- hydration = projectionResult.hydration;
2922
- const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
2923
- const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
2924
- return keys.reduce((acc, key) => {
2925
- const def = columns[key];
2926
- if (!def) {
2927
- throw new Error(missingMsg(key));
2928
- }
2929
- acc[makeRelationAlias(prefix, key)] = def;
2930
- return acc;
2931
- }, {});
2932
- };
2933
- const targetSelection = buildTypedSelection(
2934
- relation.target.columns,
2935
- aliasPrefix,
2936
- targetColumns,
2937
- (key) => `Column '${key}' not found on relation '${relationName}'`
2938
- );
2939
- if (relation.type !== RelationKinds.BelongsToMany) {
2940
- const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
2941
- state = relationSelectionResult2.state;
2942
- hydration = relationSelectionResult2.hydration;
2943
- hydration = hydration.onRelationIncluded(
2944
- state,
2945
- relation,
2946
- relationName,
2947
- aliasPrefix,
2948
- targetColumns
2949
- );
2950
- return { state, hydration };
2951
- }
2952
- const many = relation;
2953
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
2954
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
2955
- const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
2956
- const pivotSelection = buildTypedSelection(
2957
- many.pivotTable.columns,
2958
- pivotAliasPrefix,
2959
- pivotColumns,
2960
- (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
2961
- );
2962
- const combinedSelection = {
2963
- ...targetSelection,
2964
- ...pivotSelection
2965
- };
2966
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
2967
- state = relationSelectionResult.state;
2968
- hydration = relationSelectionResult.hydration;
2969
- hydration = hydration.onRelationIncluded(
2970
- state,
2971
- relation,
2972
- relationName,
2973
- aliasPrefix,
2974
- targetColumns,
2975
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
2976
- );
2977
- return { state, hydration };
2978
- }
2979
- /**
2980
- * Applies relation correlation to a query AST
2981
- * @param relationName - Name of the relation
2982
- * @param ast - Query AST to modify
2983
- * @returns Modified query AST with relation correlation
2984
- */
2985
- applyRelationCorrelation(relationName, ast, additionalCorrelation) {
2986
- const relation = this.getRelation(relationName);
2987
- const rootAlias = this.state.ast.from.type === "Table" ? this.state.ast.from.alias : void 0;
2988
- let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
2989
- if (additionalCorrelation) {
2990
- correlation = and(correlation, additionalCorrelation);
2991
- }
2992
- const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
2993
- return {
2994
- ...ast,
2995
- where: whereInSubquery
2996
- };
2997
- }
2998
- /**
2999
- * Creates a join node for a relation
3000
- * @param state - Current query state
3001
- * @param relationName - Name of the relation
3002
- * @param joinKind - Type of join to use
3003
- * @param extraCondition - Additional join condition
3004
- * @returns Updated query state with join
3005
- */
3006
- withJoin(state, relationName, joinKind, extraCondition) {
3007
- const relation = this.getRelation(relationName);
3008
- const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
3009
- if (relation.type === RelationKinds.BelongsToMany) {
3010
- const joins = buildBelongsToManyJoins(
3011
- this.table,
3012
- relationName,
3013
- relation,
3014
- joinKind,
3015
- extraCondition,
3016
- rootAlias
3017
- );
3018
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
3019
- }
3020
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
3021
- const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
3022
- return this.astService(state).withJoin(joinNode);
3023
- }
3024
- /**
3025
- * Selects columns for a relation
3026
- * @param state - Current query state
3027
- * @param hydration - Hydration manager
3028
- * @param columns - Columns to select
3029
- * @returns Relation result with updated state and hydration
3030
- */
3031
- selectColumns(state, hydration, columns) {
3032
- const { state: nextState, addedColumns } = this.astService(state).select(columns);
3033
- return {
3034
- state: nextState,
3035
- hydration: hydration.onColumnsSelected(nextState, addedColumns)
3036
- };
3037
- }
3038
- /**
3039
- * Gets a relation definition by name
3040
- * @param relationName - Name of the relation
3041
- * @returns Relation definition
3042
- * @throws Error if relation is not found
3043
- */
3044
- getRelation(relationName) {
3045
- const relation = this.table.relations[relationName];
3046
- if (!relation) {
3047
- throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
3048
- }
3049
- return relation;
3050
- }
3051
- /**
3052
- * Creates a QueryAstService instance
3053
- * @param state - Current query state
3054
- * @returns QueryAstService instance
3055
- */
3056
- astService(state = this.state) {
3057
- return this.createQueryAstService(this.table, state);
3058
- }
3059
- rootTableName() {
3060
- const from = this.state.ast.from;
3061
- if (from.type === "Table" && from.alias) return from.alias;
3062
- return this.table.name;
3063
- }
3064
- };
3065
-
3066
- // src/query-builder/select-query-builder-deps.ts
3067
- var defaultCreateQueryAstService = (table, state) => new QueryAstService(table, state);
3068
- var defaultCreateHydrationPlanner = (table) => new HydrationPlanner(table);
3069
- var defaultCreateHydration = (table, plannerFactory) => new HydrationManager(table, plannerFactory(table));
3070
- var resolveSelectQueryBuilderDependencies = (overrides = {}) => {
3071
- const createQueryAstService = overrides.createQueryAstService ?? defaultCreateQueryAstService;
3072
- const createHydrationPlanner = overrides.createHydrationPlanner ?? defaultCreateHydrationPlanner;
3073
- const createHydration = overrides.createHydration ?? ((table) => defaultCreateHydration(table, createHydrationPlanner));
3074
- const createRelationService = overrides.createRelationService ?? ((table, state, hydration) => new RelationService(table, state, hydration, createQueryAstService));
3075
- return {
3076
- createState: overrides.createState ?? ((table) => new SelectQueryState(table)),
3077
- createHydration,
3078
- createHydrationPlanner,
3079
- createQueryAstService,
3080
- createRelationService
3081
- };
3082
- };
3083
- var defaultSelectQueryBuilderDependencies = resolveSelectQueryBuilderDependencies();
3084
-
3085
- // src/query-builder/column-selector.ts
3086
- var ColumnSelector = class {
3087
- /**
3088
- * Creates a new ColumnSelector instance
3089
- * @param env - Query builder environment
3090
- */
3091
- constructor(env) {
3092
- this.env = env;
3093
- }
3094
- /**
3095
- * Selects columns for the query
3096
- * @param context - Current query context
3097
- * @param columns - Columns to select
3098
- * @returns Updated query context with selected columns
3099
- */
3100
- select(context, columns) {
3101
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
3102
- const { state: nextState, addedColumns } = astService.select(columns);
3103
- return {
3104
- state: nextState,
3105
- hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
3106
- };
3107
- }
3108
- /**
3109
- * Selects raw column expressions
3110
- * @param context - Current query context
3111
- * @param columns - Raw column expressions
3112
- * @returns Updated query context with raw column selections
3113
- */
3114
- selectRaw(context, columns) {
3115
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
3116
- const nextState = astService.selectRaw(columns).state;
3117
- return { state: nextState, hydration: context.hydration };
3118
- }
3119
- /**
3120
- * Selects a subquery as a column
3121
- * @param context - Current query context
3122
- * @param alias - Alias for the subquery
3123
- * @param query - Subquery to select
3124
- * @returns Updated query context with subquery selection
3125
- */
3126
- selectSubquery(context, alias, query) {
3127
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
3128
- const nextState = astService.selectSubquery(alias, query);
3129
- return { state: nextState, hydration: context.hydration };
3130
- }
3131
- /**
3132
- * Adds DISTINCT clause to the query
3133
- * @param context - Current query context
3134
- * @param columns - Columns to make distinct
3135
- * @returns Updated query context with DISTINCT clause
3136
- */
3137
- distinct(context, columns) {
3138
- const from = context.state.ast.from;
3139
- const tableRef = from.type === "Table" && from.alias ? { ...this.env.table, alias: from.alias } : this.env.table;
3140
- const nodes = columns.map((col) => buildColumnNode(tableRef, col));
3141
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
3142
- const nextState = astService.withDistinct(nodes);
3143
- return { state: nextState, hydration: context.hydration };
3144
- }
3145
- };
3146
-
3147
- // src/query-builder/relation-manager.ts
3148
- var RelationManager = class {
3149
- /**
3150
- * Creates a new RelationManager instance
3151
- * @param env - Query builder environment
3152
- */
3153
- constructor(env) {
3154
- this.env = env;
3155
- }
3156
- /**
3157
- * Matches records based on a relation with an optional predicate
3158
- * @param context - Current query context
3159
- * @param relationName - Name of the relation to match
3160
- * @param predicate - Optional predicate expression
3161
- * @returns Updated query context with relation match
3162
- */
3163
- match(context, relationName, predicate) {
3164
- const result = this.createService(context).match(relationName, predicate);
3165
- return { state: result.state, hydration: result.hydration };
3166
- }
3167
- /**
3168
- * Joins a relation to the query
3169
- * @param context - Current query context
3170
- * @param relationName - Name of the relation to join
3171
- * @param joinKind - Type of join to use
3172
- * @param extraCondition - Additional join condition
3173
- * @returns Updated query context with relation join
3174
- */
3175
- joinRelation(context, relationName, joinKind, extraCondition) {
3176
- const result = this.createService(context).joinRelation(relationName, joinKind, extraCondition);
3177
- return { state: result.state, hydration: result.hydration };
3178
- }
3179
- /**
3180
- * Includes a relation in the query result
3181
- * @param context - Current query context
3182
- * @param relationName - Name of the relation to include
3183
- * @param options - Options for relation inclusion
3184
- * @returns Updated query context with included relation
3185
- */
3186
- include(context, relationName, options) {
3187
- const result = this.createService(context).include(relationName, options);
3188
- return { state: result.state, hydration: result.hydration };
3189
- }
3190
- /**
3191
- * Applies relation correlation to a query AST
3192
- * @param context - Current query context
3193
- * @param relationName - Name of the relation
3194
- * @param ast - Query AST to modify
3195
- * @returns Modified query AST with relation correlation
3196
- */
3197
- applyRelationCorrelation(context, relationName, ast, additionalCorrelation) {
3198
- return this.createService(context).applyRelationCorrelation(relationName, ast, additionalCorrelation);
3199
- }
3200
- /**
3201
- * Creates a relation service instance
3202
- * @param context - Current query context
3203
- * @returns Relation service instance
3204
- */
3205
- createService(context) {
3206
- return this.env.deps.createRelationService(this.env.table, context.state, context.hydration);
3207
- }
3208
- };
3209
-
3210
- // src/orm/hydration.ts
3211
- var hydrateRows = (rows, plan) => {
3212
- if (!plan || !rows.length) return rows;
3213
- const rootMap = /* @__PURE__ */ new Map();
3214
- const relationIndex = /* @__PURE__ */ new Map();
3215
- const getOrCreateParent = (row) => {
3216
- const rootId = row[plan.rootPrimaryKey];
3217
- if (rootId === void 0) return void 0;
3218
- if (!rootMap.has(rootId)) {
3219
- rootMap.set(rootId, createBaseRow(row, plan));
3220
- }
3221
- return rootMap.get(rootId);
3222
- };
3223
- const getRelationSeenSet = (rootId, relationName) => {
3224
- let byRelation = relationIndex.get(rootId);
3225
- if (!byRelation) {
3226
- byRelation = {};
3227
- relationIndex.set(rootId, byRelation);
3228
- }
3229
- let seen = byRelation[relationName];
3230
- if (!seen) {
3231
- seen = /* @__PURE__ */ new Set();
3232
- byRelation[relationName] = seen;
3233
- }
3234
- return seen;
3235
- };
3236
- for (const row of rows) {
3237
- const rootId = row[plan.rootPrimaryKey];
3238
- if (rootId === void 0) continue;
3239
- const parent = getOrCreateParent(row);
3240
- if (!parent) continue;
3241
- for (const rel of plan.relations) {
3242
- const childPkKey = makeRelationAlias(rel.aliasPrefix, rel.targetPrimaryKey);
3243
- const childPk = row[childPkKey];
3244
- if (childPk === null || childPk === void 0) continue;
3245
- const seen = getRelationSeenSet(rootId, rel.name);
3246
- if (seen.has(childPk)) continue;
3247
- seen.add(childPk);
3248
- if (rel.type === RelationKinds.HasOne) {
3249
- if (!parent[rel.name]) {
3250
- parent[rel.name] = buildChild(row, rel);
3251
- }
3252
- continue;
3253
- }
3254
- const bucket = parent[rel.name];
3255
- bucket.push(buildChild(row, rel));
3256
- }
3257
- }
3258
- return Array.from(rootMap.values());
3259
- };
3260
- var createBaseRow = (row, plan) => {
3261
- const base = {};
3262
- const baseKeys = plan.rootColumns.length ? plan.rootColumns : Object.keys(row).filter((k) => !isRelationAlias(k));
3263
- for (const key of baseKeys) {
3264
- base[key] = row[key];
3265
- }
3266
- for (const rel of plan.relations) {
3267
- base[rel.name] = rel.type === RelationKinds.HasOne ? null : [];
3268
- }
3269
- return base;
3270
- };
3271
- var buildChild = (row, rel) => {
3272
- const child = {};
3273
- for (const col of rel.columns) {
3274
- const key = makeRelationAlias(rel.aliasPrefix, col);
3275
- child[col] = row[key];
3276
- }
3277
- const pivot = buildPivot(row, rel);
3278
- if (pivot) {
3279
- child._pivot = pivot;
3280
- }
3281
- return child;
3282
- };
3283
- var buildPivot = (row, rel) => {
3284
- if (!rel.pivot) return void 0;
3285
- const pivot = {};
3286
- for (const col of rel.pivot.columns) {
3287
- const key = makeRelationAlias(rel.pivot.aliasPrefix, col);
3288
- pivot[col] = row[key];
3289
- }
3290
- const hasValue = Object.values(pivot).some((v) => v !== null && v !== void 0);
3291
- return hasValue ? pivot : void 0;
3292
- };
3293
-
3294
- // src/orm/entity-meta.ts
3295
- var ENTITY_META = Symbol("EntityMeta");
3296
- var toKey = (value) => value === null || value === void 0 ? "" : String(value);
3297
- var getHydrationRows = (meta, relationName, key) => {
3298
- const map = meta.relationHydration.get(relationName);
3299
- if (!map) return void 0;
3300
- const rows = map.get(toKey(key));
3301
- if (!rows) return void 0;
3302
- return Array.isArray(rows) ? rows : void 0;
3303
- };
3304
- var getHydrationRecord = (meta, relationName, key) => {
3305
- const map = meta.relationHydration.get(relationName);
3306
- if (!map) return void 0;
3307
- const value = map.get(toKey(key));
3308
- if (!value) return void 0;
3309
- if (Array.isArray(value)) {
3310
- return value[0];
3311
- }
3312
- return value;
3313
- };
3314
- var getEntityMeta = (entity) => {
3315
- if (!entity || typeof entity !== "object") return void 0;
3316
- return entity[ENTITY_META];
3317
- };
3318
- var hasEntityMeta = (entity) => {
3319
- return Boolean(getEntityMeta(entity));
3320
- };
3321
-
3322
- // src/orm/relations/has-many.ts
3323
- var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
3324
- var hideInternal = (obj, keys) => {
3325
- for (const key of keys) {
3326
- Object.defineProperty(obj, key, {
3327
- value: obj[key],
3328
- writable: false,
3329
- configurable: false,
3330
- enumerable: false
3331
- });
3332
- }
3333
- };
3334
- var DefaultHasManyCollection = class {
3335
- constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
3336
- this.ctx = ctx;
3337
- this.meta = meta;
3338
- this.root = root;
3339
- this.relationName = relationName;
3340
- this.relation = relation;
3341
- this.rootTable = rootTable;
3342
- this.loader = loader;
3343
- this.createEntity = createEntity;
3344
- this.localKey = localKey;
3345
- this.loaded = false;
3346
- this.items = [];
3347
- this.added = /* @__PURE__ */ new Set();
3348
- this.removed = /* @__PURE__ */ new Set();
3349
- hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
3350
- this.hydrateFromCache();
3351
- }
3352
- async load() {
3353
- if (this.loaded) return this.items;
3354
- const map = await this.loader();
3355
- const key = toKey2(this.root[this.localKey]);
3356
- const rows = map.get(key) ?? [];
3357
- this.items = rows.map((row) => this.createEntity(row));
3358
- this.loaded = true;
3359
- return this.items;
3360
- }
3361
- getItems() {
3362
- return this.items;
3363
- }
3364
- add(data) {
3365
- const keyValue = this.root[this.localKey];
3366
- const childRow = {
3367
- ...data,
3368
- [this.relation.foreignKey]: keyValue
3369
- };
3370
- const entity = this.createEntity(childRow);
3371
- this.added.add(entity);
3372
- this.items.push(entity);
3373
- this.ctx.registerRelationChange(
3374
- this.root,
3375
- this.relationKey,
3376
- this.rootTable,
3377
- this.relationName,
3378
- this.relation,
3379
- { kind: "add", entity }
3380
- );
3381
- return entity;
3382
- }
3383
- attach(entity) {
3384
- const keyValue = this.root[this.localKey];
3385
- entity[this.relation.foreignKey] = keyValue;
3386
- this.ctx.markDirty(entity);
3387
- this.items.push(entity);
3388
- this.ctx.registerRelationChange(
3389
- this.root,
3390
- this.relationKey,
3391
- this.rootTable,
3392
- this.relationName,
3393
- this.relation,
3394
- { kind: "attach", entity }
3395
- );
3396
- }
3397
- remove(entity) {
3398
- this.items = this.items.filter((item) => item !== entity);
3399
- this.removed.add(entity);
3400
- this.ctx.registerRelationChange(
3401
- this.root,
3402
- this.relationKey,
3403
- this.rootTable,
3404
- this.relationName,
3405
- this.relation,
3406
- { kind: "remove", entity }
3407
- );
3408
- }
3409
- clear() {
3410
- for (const entity of [...this.items]) {
3411
- this.remove(entity);
3412
- }
3413
- }
3414
- get relationKey() {
3415
- return `${this.rootTable.name}.${this.relationName}`;
3416
- }
3417
- hydrateFromCache() {
3418
- const keyValue = this.root[this.localKey];
3419
- if (keyValue === void 0 || keyValue === null) return;
3420
- const rows = getHydrationRows(this.meta, this.relationName, keyValue);
3421
- if (!rows?.length) return;
3422
- this.items = rows.map((row) => this.createEntity(row));
3423
- this.loaded = true;
3424
- }
3425
- toJSON() {
3426
- return this.items;
3427
- }
3428
- };
3429
-
3430
- // src/orm/relations/has-one.ts
3431
- var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
3432
- var hideInternal2 = (obj, keys) => {
3433
- for (const key of keys) {
3434
- Object.defineProperty(obj, key, {
3435
- value: obj[key],
3436
- writable: false,
3437
- configurable: false,
3438
- enumerable: false
3439
- });
3440
- }
3441
- };
3442
- var DefaultHasOneReference = class {
3443
- constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
3444
- this.ctx = ctx;
3445
- this.meta = meta;
3446
- this.root = root;
3447
- this.relationName = relationName;
3448
- this.relation = relation;
3449
- this.rootTable = rootTable;
3450
- this.loader = loader;
3451
- this.createEntity = createEntity;
3452
- this.localKey = localKey;
3453
- this.loaded = false;
3454
- this.current = null;
3455
- hideInternal2(this, [
3456
- "ctx",
3457
- "meta",
3458
- "root",
3459
- "relationName",
3460
- "relation",
3461
- "rootTable",
3462
- "loader",
3463
- "createEntity",
3464
- "localKey"
3465
- ]);
3466
- this.populateFromHydrationCache();
3467
- }
3468
- async load() {
3469
- if (this.loaded) return this.current;
3470
- const map = await this.loader();
3471
- const keyValue = this.root[this.localKey];
3472
- if (keyValue === void 0 || keyValue === null) {
3473
- this.loaded = true;
3474
- return this.current;
3475
- }
3476
- const row = map.get(toKey3(keyValue));
3477
- this.current = row ? this.createEntity(row) : null;
3478
- this.loaded = true;
3479
- return this.current;
3480
- }
3481
- get() {
3482
- return this.current;
3483
- }
3484
- set(data) {
3485
- if (data === null) {
3486
- return this.detachCurrent();
3487
- }
3488
- const entity = hasEntityMeta(data) ? data : this.createEntity(data);
3489
- if (this.current && this.current !== entity) {
3490
- this.ctx.registerRelationChange(
3491
- this.root,
3492
- this.relationKey,
3493
- this.rootTable,
3494
- this.relationName,
3495
- this.relation,
3496
- { kind: "remove", entity: this.current }
3497
- );
3498
- }
3499
- this.assignForeignKey(entity);
3500
- this.current = entity;
3501
- this.loaded = true;
3502
- this.ctx.registerRelationChange(
3503
- this.root,
3504
- this.relationKey,
3505
- this.rootTable,
3506
- this.relationName,
3507
- this.relation,
3508
- { kind: "attach", entity }
3509
- );
3510
- return entity;
3511
- }
3512
- toJSON() {
3513
- return this.current;
3514
- }
3515
- detachCurrent() {
3516
- const previous = this.current;
3517
- if (!previous) return null;
3518
- this.current = null;
3519
- this.loaded = true;
3520
- this.ctx.registerRelationChange(
3521
- this.root,
3522
- this.relationKey,
3523
- this.rootTable,
3524
- this.relationName,
3525
- this.relation,
3526
- { kind: "remove", entity: previous }
3527
- );
3528
- return null;
3529
- }
3530
- assignForeignKey(entity) {
3531
- const keyValue = this.root[this.localKey];
3532
- entity[this.relation.foreignKey] = keyValue;
3533
- }
3534
- get relationKey() {
3535
- return `${this.rootTable.name}.${this.relationName}`;
3536
- }
3537
- populateFromHydrationCache() {
3538
- const keyValue = this.root[this.localKey];
3539
- if (keyValue === void 0 || keyValue === null) return;
3540
- const row = getHydrationRecord(this.meta, this.relationName, keyValue);
3541
- if (!row) return;
3542
- this.current = this.createEntity(row);
3543
- this.loaded = true;
3544
- }
3545
- };
3546
-
3547
- // src/orm/relations/belongs-to.ts
3548
- var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
3549
- var hideInternal3 = (obj, keys) => {
3550
- for (const key of keys) {
3551
- Object.defineProperty(obj, key, {
3552
- value: obj[key],
3553
- writable: false,
3554
- configurable: false,
3555
- enumerable: false
3556
- });
3557
- }
3558
- };
3559
- var DefaultBelongsToReference = class {
3560
- constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, targetKey) {
3561
- this.ctx = ctx;
3562
- this.meta = meta;
3563
- this.root = root;
3564
- this.relationName = relationName;
3565
- this.relation = relation;
3566
- this.rootTable = rootTable;
3567
- this.loader = loader;
3568
- this.createEntity = createEntity;
3569
- this.targetKey = targetKey;
3570
- this.loaded = false;
3571
- this.current = null;
3572
- hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
3573
- this.populateFromHydrationCache();
3574
- }
3575
- async load() {
3576
- if (this.loaded) return this.current;
3577
- const map = await this.loader();
3578
- const fkValue = this.root[this.relation.foreignKey];
3579
- if (fkValue === null || fkValue === void 0) {
3580
- this.current = null;
3581
- } else {
3582
- const row = map.get(toKey4(fkValue));
3583
- this.current = row ? this.createEntity(row) : null;
3584
- }
3585
- this.loaded = true;
3586
- return this.current;
3587
- }
3588
- get() {
3589
- return this.current;
3590
- }
3591
- set(data) {
3592
- if (data === null) {
3593
- const previous = this.current;
3594
- this.root[this.relation.foreignKey] = null;
3595
- this.current = null;
3596
- this.ctx.registerRelationChange(
3597
- this.root,
3598
- this.relationKey,
3599
- this.rootTable,
3600
- this.relationName,
3601
- this.relation,
3602
- { kind: "remove", entity: previous }
3603
- );
3604
- return null;
3605
- }
3606
- const entity = hasEntityMeta(data) ? data : this.createEntity(data);
3607
- const pkValue = entity[this.targetKey];
3608
- if (pkValue !== void 0) {
3609
- this.root[this.relation.foreignKey] = pkValue;
3610
- }
3611
- this.current = entity;
3612
- this.ctx.registerRelationChange(
3613
- this.root,
3614
- this.relationKey,
3615
- this.rootTable,
3616
- this.relationName,
3617
- this.relation,
3618
- { kind: "attach", entity }
3619
- );
3620
- return entity;
3621
- }
3622
- get relationKey() {
3623
- return `${this.rootTable.name}.${this.relationName}`;
3624
- }
3625
- populateFromHydrationCache() {
3626
- const fkValue = this.root[this.relation.foreignKey];
3627
- if (fkValue === void 0 || fkValue === null) return;
3628
- const row = getHydrationRecord(this.meta, this.relationName, fkValue);
3629
- if (!row) return;
3630
- this.current = this.createEntity(row);
3631
- this.loaded = true;
3632
- }
3633
- toJSON() {
3634
- return this.current;
3635
- }
3636
- };
3637
-
3638
- // src/orm/relations/many-to-many.ts
3639
- var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
3640
- var hideInternal4 = (obj, keys) => {
3641
- for (const key of keys) {
3642
- Object.defineProperty(obj, key, {
3643
- value: obj[key],
3644
- writable: false,
3645
- configurable: false,
3646
- enumerable: false
3647
- });
3648
- }
3649
- };
3650
- var DefaultManyToManyCollection = class {
3651
- constructor(ctx, meta, root, relationName, relation, rootTable, loader, createEntity, localKey) {
3652
- this.ctx = ctx;
3653
- this.meta = meta;
3654
- this.root = root;
3655
- this.relationName = relationName;
3656
- this.relation = relation;
3657
- this.rootTable = rootTable;
3658
- this.loader = loader;
3659
- this.createEntity = createEntity;
3660
- this.localKey = localKey;
3661
- this.loaded = false;
3662
- this.items = [];
3663
- hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
3664
- this.hydrateFromCache();
3665
- }
3666
- async load() {
3667
- if (this.loaded) return this.items;
3668
- const map = await this.loader();
3669
- const key = toKey5(this.root[this.localKey]);
3670
- const rows = map.get(key) ?? [];
3671
- this.items = rows.map((row) => {
3672
- const entity = this.createEntity(row);
3673
- if (row._pivot) {
3674
- entity._pivot = row._pivot;
3675
- }
3676
- return entity;
3677
- });
3678
- this.loaded = true;
3679
- return this.items;
3680
- }
3681
- getItems() {
3682
- return this.items;
3683
- }
3684
- attach(target) {
3685
- const entity = this.ensureEntity(target);
3686
- const id = this.extractId(entity);
3687
- if (id == null) return;
3688
- if (this.items.some((item) => this.extractId(item) === id)) {
3689
- return;
3690
- }
3691
- this.items.push(entity);
3692
- this.ctx.registerRelationChange(
3693
- this.root,
3694
- this.relationKey,
3695
- this.rootTable,
3696
- this.relationName,
3697
- this.relation,
3698
- { kind: "attach", entity }
3699
- );
3700
- }
3701
- detach(target) {
3702
- const id = typeof target === "number" || typeof target === "string" ? target : this.extractId(target);
3703
- if (id == null) return;
3704
- const existing = this.items.find((item) => this.extractId(item) === id);
3705
- if (!existing) return;
3706
- this.items = this.items.filter((item) => this.extractId(item) !== id);
3707
- this.ctx.registerRelationChange(
3708
- this.root,
3709
- this.relationKey,
3710
- this.rootTable,
3711
- this.relationName,
3712
- this.relation,
3713
- { kind: "detach", entity: existing }
3714
- );
3715
- }
3716
- async syncByIds(ids) {
3717
- await this.load();
3718
- const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
3719
- const normalized = new Set(ids.map((id) => toKey5(id)));
3720
- const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
3721
- for (const id of normalized) {
3722
- if (!currentIds.has(id)) {
3723
- this.attach(id);
3724
- }
3725
- }
3726
- for (const item of [...this.items]) {
3727
- const itemId = toKey5(this.extractId(item));
3728
- if (!normalized.has(itemId)) {
3729
- this.detach(item);
3730
- }
3731
- }
3732
- }
3733
- ensureEntity(target) {
3734
- if (typeof target === "number" || typeof target === "string") {
3735
- const stub = {
3736
- [this.targetKey]: target
3737
- };
3738
- return this.createEntity(stub);
3739
- }
3740
- return target;
3741
- }
3742
- extractId(entity) {
3743
- if (entity === null || entity === void 0) return null;
3744
- if (typeof entity === "number" || typeof entity === "string") {
3745
- return entity;
3746
- }
3747
- return entity[this.targetKey] ?? null;
3748
- }
3749
- get relationKey() {
3750
- return `${this.rootTable.name}.${this.relationName}`;
3751
- }
3752
- get targetKey() {
3753
- return this.relation.targetKey || findPrimaryKey(this.relation.target);
3754
- }
3755
- hydrateFromCache() {
3756
- const keyValue = this.root[this.localKey];
3757
- if (keyValue === void 0 || keyValue === null) return;
3758
- const rows = getHydrationRows(this.meta, this.relationName, keyValue);
3759
- if (!rows?.length) return;
3760
- this.items = rows.map((row) => {
3761
- const entity = this.createEntity(row);
3762
- if (row._pivot) {
3763
- entity._pivot = row._pivot;
3764
- }
3765
- return entity;
3766
- });
3767
- this.loaded = true;
3768
- }
3769
- toJSON() {
3770
- return this.items;
3771
- }
3772
- };
3773
-
3774
- // src/orm/lazy-batch.ts
3775
- var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
3776
- acc[name] = def;
3777
- return acc;
3778
- }, {});
3779
- var rowsFromResults = (results) => {
3780
- const rows = [];
3781
- for (const result of results) {
3782
- const { columns, values } = result;
3783
- for (const valueRow of values) {
3784
- const row = {};
3785
- columns.forEach((column, idx) => {
3786
- row[column] = valueRow[idx];
3787
- });
3788
- rows.push(row);
3789
- }
3790
- }
3791
- return rows;
3792
- };
3793
- var executeQuery = async (ctx, qb) => {
3794
- const compiled = ctx.dialect.compileSelect(qb.getAST());
3795
- const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
3796
- return rowsFromResults(results);
3797
- };
3798
- var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
3799
- var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
3800
- const localKey = relation.localKey || findPrimaryKey(rootTable);
3801
- const roots = ctx.getEntitiesForTable(rootTable);
3802
- const keys = /* @__PURE__ */ new Set();
3803
- for (const tracked of roots) {
3804
- const value = tracked.entity[localKey];
3805
- if (value !== null && value !== void 0) {
3806
- keys.add(value);
3807
- }
3808
- }
3809
- if (!keys.size) {
3810
- return /* @__PURE__ */ new Map();
3811
- }
3812
- const selectMap = selectAllColumns(relation.target);
3813
- const fb = new SelectQueryBuilder(relation.target).select(selectMap);
3814
- const fkColumn = relation.target.columns[relation.foreignKey];
3815
- if (!fkColumn) return /* @__PURE__ */ new Map();
3816
- fb.where(inList(fkColumn, Array.from(keys)));
3817
- const rows = await executeQuery(ctx, fb);
3818
- const grouped = /* @__PURE__ */ new Map();
3819
- for (const row of rows) {
3820
- const fkValue = row[relation.foreignKey];
3821
- if (fkValue === null || fkValue === void 0) continue;
3822
- const key = toKey6(fkValue);
3823
- const bucket = grouped.get(key) ?? [];
3824
- bucket.push(row);
3825
- grouped.set(key, bucket);
3826
- }
3827
- return grouped;
3828
- };
3829
- var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
3830
- const localKey = relation.localKey || findPrimaryKey(rootTable);
3831
- const roots = ctx.getEntitiesForTable(rootTable);
3832
- const keys = /* @__PURE__ */ new Set();
3833
- for (const tracked of roots) {
3834
- const value = tracked.entity[localKey];
3835
- if (value !== null && value !== void 0) {
3836
- keys.add(value);
3837
- }
3838
- }
3839
- if (!keys.size) {
3840
- return /* @__PURE__ */ new Map();
3841
- }
3842
- const selectMap = selectAllColumns(relation.target);
3843
- const qb = new SelectQueryBuilder(relation.target).select(selectMap);
3844
- const fkColumn = relation.target.columns[relation.foreignKey];
3845
- if (!fkColumn) return /* @__PURE__ */ new Map();
3846
- qb.where(inList(fkColumn, Array.from(keys)));
3847
- const rows = await executeQuery(ctx, qb);
3848
- const lookup = /* @__PURE__ */ new Map();
3849
- for (const row of rows) {
3850
- const fkValue = row[relation.foreignKey];
3851
- if (fkValue === null || fkValue === void 0) continue;
3852
- const key = toKey6(fkValue);
3853
- if (!lookup.has(key)) {
3854
- lookup.set(key, row);
3855
- }
3856
- }
3857
- return lookup;
3858
- };
3859
- var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
3860
- const roots = ctx.getEntitiesForTable(rootTable);
3861
- const foreignKeys = /* @__PURE__ */ new Set();
3862
- for (const tracked of roots) {
3863
- const value = tracked.entity[relation.foreignKey];
3864
- if (value !== null && value !== void 0) {
3865
- foreignKeys.add(value);
3866
- }
3867
- }
3868
- if (!foreignKeys.size) {
3869
- return /* @__PURE__ */ new Map();
3870
- }
3871
- const selectMap = selectAllColumns(relation.target);
3872
- const qb = new SelectQueryBuilder(relation.target).select(selectMap);
3873
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
3874
- const pkColumn = relation.target.columns[targetKey];
3875
- if (!pkColumn) return /* @__PURE__ */ new Map();
3876
- qb.where(inList(pkColumn, Array.from(foreignKeys)));
3877
- const rows = await executeQuery(ctx, qb);
3878
- const map = /* @__PURE__ */ new Map();
3879
- for (const row of rows) {
3880
- const keyValue = row[targetKey];
3881
- if (keyValue === null || keyValue === void 0) continue;
3882
- map.set(toKey6(keyValue), row);
3883
- }
3884
- return map;
3885
- };
3886
- var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
3887
- const rootKey = relation.localKey || findPrimaryKey(rootTable);
3888
- const roots = ctx.getEntitiesForTable(rootTable);
3889
- const rootIds = /* @__PURE__ */ new Set();
3890
- for (const tracked of roots) {
3891
- const value = tracked.entity[rootKey];
3892
- if (value !== null && value !== void 0) {
3893
- rootIds.add(value);
3894
- }
3895
- }
3896
- if (!rootIds.size) {
3897
- return /* @__PURE__ */ new Map();
3898
- }
3899
- const pivotSelect = selectAllColumns(relation.pivotTable);
3900
- const pivotQb = new SelectQueryBuilder(relation.pivotTable).select(pivotSelect);
3901
- const pivotFkCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
3902
- if (!pivotFkCol) return /* @__PURE__ */ new Map();
3903
- pivotQb.where(inList(pivotFkCol, Array.from(rootIds)));
3904
- const pivotRows = await executeQuery(ctx, pivotQb);
3905
- const rootLookup = /* @__PURE__ */ new Map();
3906
- const targetIds = /* @__PURE__ */ new Set();
3907
- for (const pivot of pivotRows) {
3908
- const rootValue = pivot[relation.pivotForeignKeyToRoot];
3909
- const targetValue = pivot[relation.pivotForeignKeyToTarget];
3910
- if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
3911
- continue;
3912
- }
3913
- const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
3914
- bucket.push({
3915
- targetId: targetValue,
3916
- pivot: { ...pivot }
3917
- });
3918
- rootLookup.set(toKey6(rootValue), bucket);
3919
- targetIds.add(targetValue);
3920
- }
3921
- if (!targetIds.size) {
3922
- return /* @__PURE__ */ new Map();
3923
- }
3924
- const targetSelect = selectAllColumns(relation.target);
3925
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
3926
- const targetPkColumn = relation.target.columns[targetKey];
3927
- if (!targetPkColumn) return /* @__PURE__ */ new Map();
3928
- const targetQb = new SelectQueryBuilder(relation.target).select(targetSelect);
3929
- targetQb.where(inList(targetPkColumn, Array.from(targetIds)));
3930
- const targetRows = await executeQuery(ctx, targetQb);
3931
- const targetMap = /* @__PURE__ */ new Map();
3932
- for (const row of targetRows) {
3933
- const pkValue = row[targetKey];
3934
- if (pkValue === null || pkValue === void 0) continue;
3935
- targetMap.set(toKey6(pkValue), row);
3936
- }
3937
- const result = /* @__PURE__ */ new Map();
3938
- for (const [rootId, entries] of rootLookup.entries()) {
3939
- const bucket = [];
3940
- for (const entry of entries) {
3941
- const targetRow = targetMap.get(toKey6(entry.targetId));
3942
- if (!targetRow) continue;
3943
- bucket.push({
3944
- ...targetRow,
3945
- _pivot: entry.pivot
3946
- });
3947
- }
3948
- result.set(rootId, bucket);
3949
- }
3950
- return result;
3951
- };
3952
-
3953
- // src/orm/entity.ts
3954
- var relationLoaderCache = (meta, relationName, factory) => {
3955
- if (meta.relationCache.has(relationName)) {
3956
- return meta.relationCache.get(relationName);
3957
- }
3958
- const promise = factory().then((value) => {
3959
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
3960
- const otherMeta = getEntityMeta(tracked.entity);
3961
- if (!otherMeta) continue;
3962
- otherMeta.relationHydration.set(relationName, value);
3963
- }
3964
- return value;
3965
- });
3966
- meta.relationCache.set(relationName, promise);
3967
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
3968
- const otherMeta = getEntityMeta(tracked.entity);
3969
- if (!otherMeta) continue;
3970
- otherMeta.relationCache.set(relationName, promise);
3971
- }
3972
- return promise;
3973
- };
3974
- var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
3975
- const target = { ...row };
3976
- const meta = {
3977
- ctx,
3978
- table,
3979
- lazyRelations: [...lazyRelations],
3980
- relationCache: /* @__PURE__ */ new Map(),
3981
- relationHydration: /* @__PURE__ */ new Map(),
3982
- relationWrappers: /* @__PURE__ */ new Map()
3983
- };
3984
- Object.defineProperty(target, ENTITY_META, {
3985
- value: meta,
3986
- enumerable: false,
3987
- writable: false
3988
- });
3989
- let proxy;
3990
- const handler = {
3991
- get(targetObj, prop, receiver) {
3992
- if (prop === ENTITY_META) {
3993
- return meta;
3994
- }
3995
- if (prop === "$load") {
3996
- return async (relationName) => {
3997
- const wrapper = getRelationWrapper(meta, relationName, proxy);
3998
- if (wrapper && typeof wrapper.load === "function") {
3999
- return wrapper.load();
4000
- }
4001
- return void 0;
4002
- };
4003
- }
4004
- if (typeof prop === "string" && table.relations[prop]) {
4005
- return getRelationWrapper(meta, prop, proxy);
4006
- }
4007
- return Reflect.get(targetObj, prop, receiver);
4008
- },
4009
- set(targetObj, prop, value, receiver) {
4010
- const result = Reflect.set(targetObj, prop, value, receiver);
4011
- if (typeof prop === "string" && table.columns[prop]) {
4012
- ctx.markDirty(proxy);
4013
- }
4014
- return result;
4015
- }
4016
- };
4017
- proxy = new Proxy(target, handler);
4018
- populateHydrationCache(proxy, row, meta);
4019
- return proxy;
4020
- };
4021
- var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
4022
- const pkName = findPrimaryKey(table);
4023
- const pkValue = row[pkName];
4024
- if (pkValue !== void 0 && pkValue !== null) {
4025
- const tracked = ctx.getEntity(table, pkValue);
4026
- if (tracked) return tracked;
4027
- }
4028
- const entity = createEntityProxy(ctx, table, row, lazyRelations);
4029
- if (pkValue !== void 0 && pkValue !== null) {
4030
- ctx.trackManaged(table, pkValue, entity);
4031
- } else {
4032
- ctx.trackNew(table, entity);
4033
- }
4034
- return entity;
4035
- };
4036
- var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
4037
- var populateHydrationCache = (entity, row, meta) => {
4038
- for (const relationName of Object.keys(meta.table.relations)) {
4039
- const relation = meta.table.relations[relationName];
4040
- const data = row[relationName];
4041
- if (relation.type === RelationKinds.HasOne) {
4042
- const localKey = relation.localKey || findPrimaryKey(meta.table);
4043
- const rootValue = entity[localKey];
4044
- if (rootValue === void 0 || rootValue === null) continue;
4045
- if (!data || typeof data !== "object") continue;
4046
- const cache = /* @__PURE__ */ new Map();
4047
- cache.set(toKey7(rootValue), data);
4048
- meta.relationHydration.set(relationName, cache);
4049
- meta.relationCache.set(relationName, Promise.resolve(cache));
4050
- continue;
4051
- }
4052
- if (!Array.isArray(data)) continue;
4053
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
4054
- const localKey = relation.localKey || findPrimaryKey(meta.table);
4055
- const rootValue = entity[localKey];
4056
- if (rootValue === void 0 || rootValue === null) continue;
4057
- const cache = /* @__PURE__ */ new Map();
4058
- cache.set(toKey7(rootValue), data);
4059
- meta.relationHydration.set(relationName, cache);
4060
- meta.relationCache.set(relationName, Promise.resolve(cache));
4061
- continue;
4062
- }
4063
- if (relation.type === RelationKinds.BelongsTo) {
4064
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
4065
- const cache = /* @__PURE__ */ new Map();
4066
- for (const item of data) {
4067
- const pkValue = item[targetKey];
4068
- if (pkValue === void 0 || pkValue === null) continue;
4069
- cache.set(toKey7(pkValue), item);
4070
- }
4071
- if (cache.size) {
4072
- meta.relationHydration.set(relationName, cache);
4073
- meta.relationCache.set(relationName, Promise.resolve(cache));
4074
- }
4075
- }
4076
- }
4077
- };
4078
- var getRelationWrapper = (meta, relationName, owner) => {
4079
- if (meta.relationWrappers.has(relationName)) {
4080
- return meta.relationWrappers.get(relationName);
4081
- }
4082
- const relation = meta.table.relations[relationName];
4083
- if (!relation) return void 0;
4084
- const wrapper = instantiateWrapper(meta, relationName, relation, owner);
4085
- if (wrapper) {
4086
- meta.relationWrappers.set(relationName, wrapper);
4087
- }
4088
- return wrapper;
4089
- };
4090
- var instantiateWrapper = (meta, relationName, relation, owner) => {
4091
- switch (relation.type) {
4092
- case RelationKinds.HasOne: {
4093
- const hasOne2 = relation;
4094
- const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
4095
- const loader = () => relationLoaderCache(
4096
- meta,
4097
- relationName,
4098
- () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
4099
- );
4100
- return new DefaultHasOneReference(
4101
- meta.ctx,
4102
- meta,
4103
- owner,
4104
- relationName,
4105
- hasOne2,
4106
- meta.table,
4107
- loader,
4108
- (row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
4109
- localKey
4110
- );
4111
- }
4112
- case RelationKinds.HasMany: {
4113
- const hasMany2 = relation;
4114
- const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
4115
- const loader = () => relationLoaderCache(
4116
- meta,
4117
- relationName,
4118
- () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
4119
- );
4120
- return new DefaultHasManyCollection(
4121
- meta.ctx,
4122
- meta,
4123
- owner,
4124
- relationName,
4125
- hasMany2,
4126
- meta.table,
4127
- loader,
4128
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
4129
- localKey
4130
- );
4131
- }
4132
- case RelationKinds.BelongsTo: {
4133
- const belongsTo2 = relation;
4134
- const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
4135
- const loader = () => relationLoaderCache(
4136
- meta,
4137
- relationName,
4138
- () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
4139
- );
4140
- return new DefaultBelongsToReference(
4141
- meta.ctx,
4142
- meta,
4143
- owner,
4144
- relationName,
4145
- belongsTo2,
4146
- meta.table,
4147
- loader,
4148
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
4149
- targetKey
4150
- );
4151
- }
4152
- case RelationKinds.BelongsToMany: {
4153
- const many = relation;
4154
- const localKey = many.localKey || findPrimaryKey(meta.table);
4155
- const loader = () => relationLoaderCache(
4156
- meta,
4157
- relationName,
4158
- () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
4159
- );
4160
- return new DefaultManyToManyCollection(
4161
- meta.ctx,
4162
- meta,
4163
- owner,
4164
- relationName,
4165
- many,
4166
- meta.table,
4167
- loader,
4168
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
4169
- localKey
4170
- );
4171
- }
4172
- default:
4173
- return void 0;
4174
- }
4175
- };
4176
-
4177
- // src/orm/execute.ts
4178
- var flattenResults = (results) => {
4179
- const rows = [];
4180
- for (const result of results) {
4181
- const { columns, values } = result;
4182
- for (const valueRow of values) {
4183
- const row = {};
4184
- columns.forEach((column, idx) => {
4185
- row[column] = valueRow[idx];
4186
- });
4187
- rows.push(row);
4188
- }
4189
- }
4190
- return rows;
4191
- };
4192
- var executeWithEntityContext = async (entityCtx, qb) => {
4193
- const ast = qb.getAST();
4194
- const compiled = entityCtx.dialect.compileSelect(ast);
4195
- const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
4196
- const rows = flattenResults(executed);
4197
- if (ast.setOps && ast.setOps.length > 0) {
4198
- return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4199
- }
4200
- const hydrated = hydrateRows(rows, qb.getHydrationPlan());
4201
- return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
4202
- };
4203
- async function executeHydrated(session, qb) {
4204
- return executeWithEntityContext(session, qb);
4205
- }
4206
- async function executeHydratedWithContexts(_execCtx, hydCtx, qb) {
4207
- const entityCtx = hydCtx.entityContext;
4208
- if (!entityCtx) {
4209
- throw new Error("Hydration context is missing an EntityContext");
4210
- }
4211
- return executeWithEntityContext(entityCtx, qb);
4212
- }
4213
-
4214
- // src/query-builder/select.ts
4215
- var SelectQueryBuilder = class _SelectQueryBuilder {
4216
- /**
4217
-
4218
- * Creates a new SelectQueryBuilder instance
4219
-
4220
- * @param table - Table definition to query
4221
-
4222
- * @param state - Optional initial query state
4223
-
4224
- * @param hydration - Optional hydration manager
4225
-
4226
- * @param dependencies - Optional query builder dependencies
4227
-
4228
- */
4229
- constructor(table, state, hydration, dependencies, lazyRelations) {
4230
- const deps = resolveSelectQueryBuilderDependencies(dependencies);
4231
- this.env = { table, deps };
4232
- const initialState = state ?? deps.createState(table);
4233
- const initialHydration = hydration ?? deps.createHydration(table);
4234
- this.context = {
4235
- state: initialState,
4236
- hydration: initialHydration
4237
- };
4238
- this.lazyRelations = new Set(lazyRelations ?? []);
4239
- this.columnSelector = new ColumnSelector(this.env);
4240
- this.relationManager = new RelationManager(this.env);
4241
- }
4242
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
4243
- return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
4244
- }
4245
- /**
4246
- * Applies an alias to the root FROM table.
4247
- * @param alias - Alias to apply
4248
- */
4249
- as(alias) {
4250
- const from = this.context.state.ast.from;
4251
- if (from.type !== "Table") {
4252
- throw new Error("Cannot alias non-table FROM sources");
4253
- }
4254
- const nextFrom = { ...from, alias };
4255
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(nextFrom));
4256
- return this.clone(nextContext);
4257
- }
4258
- resolveQueryNode(query) {
4259
- return typeof query.getAST === "function" ? query.getAST() : query;
4260
- }
4261
- applyCorrelation(ast, correlation) {
4262
- if (!correlation) return ast;
4263
- const combinedWhere = ast.where ? and(correlation, ast.where) : correlation;
4264
- return {
4265
- ...ast,
4266
- where: combinedWhere
4267
- };
4268
- }
4269
- createChildBuilder(table) {
4270
- return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
4271
- }
4272
- applyAst(context, mutator) {
4273
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
4274
- const nextState = mutator(astService);
4275
- return { state: nextState, hydration: context.hydration };
4276
- }
4277
- applyJoin(context, table, condition, kind) {
4278
- const joinNode = createJoinNode(kind, table.name, condition);
4279
- return this.applyAst(context, (service) => service.withJoin(joinNode));
4280
- }
4281
- applySetOperation(operator, query) {
4282
- const subAst = this.resolveQueryNode(query);
4283
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
4284
- }
4285
- /**
4286
-
4287
- * Selects specific columns for the query
4288
-
4289
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
4290
-
4291
- * @returns New query builder instance with selected columns
4292
-
4293
- */
4294
- select(columns) {
4295
- return this.clone(this.columnSelector.select(this.context, columns));
4296
- }
4297
- /**
4298
- * Selects columns from the root table by name (typed).
4299
- * @param cols - Column names on the root table
4300
- */
4301
- selectColumns(...cols) {
4302
- const selection = {};
4303
- for (const key of cols) {
4304
- const col = this.env.table.columns[key];
4305
- if (!col) {
4306
- throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
4307
- }
4308
- selection[key] = col;
4309
- }
4310
- return this.select(selection);
4311
- }
4312
- /**
4313
-
4314
- * Selects raw column expressions
4315
-
4316
- * @param cols - Column expressions as strings
4317
-
4318
- * @returns New query builder instance with raw column selections
4319
-
4320
- */
4321
- selectRaw(...cols) {
4322
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
4323
- }
4324
- /**
4325
-
4326
- * Adds a Common Table Expression (CTE) to the query
4327
-
4328
- * @param name - Name of the CTE
4329
-
4330
- * @param query - Query builder or query node for the CTE
4331
-
4332
- * @param columns - Optional column names for the CTE
4333
-
4334
- * @returns New query builder instance with the CTE
4335
-
4336
- */
4337
- with(name, query, columns) {
4338
- const subAst = this.resolveQueryNode(query);
4339
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
4340
- return this.clone(nextContext);
4341
- }
4342
- /**
4343
-
4344
- * Adds a recursive Common Table Expression (CTE) to the query
4345
-
4346
- * @param name - Name of the CTE
4347
-
4348
- * @param query - Query builder or query node for the CTE
4349
-
4350
- * @param columns - Optional column names for the CTE
4351
-
4352
- * @returns New query builder instance with the recursive CTE
4353
-
4354
- */
4355
- withRecursive(name, query, columns) {
4356
- const subAst = this.resolveQueryNode(query);
4357
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
4358
- return this.clone(nextContext);
4359
- }
4360
- /**
4361
- * Replaces the FROM clause with a derived table (subquery with alias)
4362
- * @param subquery - Subquery to use as the FROM source
4363
- * @param alias - Alias for the derived table
4364
- * @param columnAliases - Optional column alias list
4365
- * @returns New query builder instance with updated FROM
4366
- */
4367
- fromSubquery(subquery, alias, columnAliases) {
4368
- const subAst = this.resolveQueryNode(subquery);
4369
- const fromNode = derivedTable(subAst, alias, columnAliases);
4370
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
4371
- return this.clone(nextContext);
4372
- }
4373
- /**
4374
-
4375
- * Selects a subquery as a column
4376
-
4377
- * @param alias - Alias for the subquery column
4378
-
4379
- * @param sub - Query builder or query node for the subquery
4380
-
4381
- * @returns New query builder instance with the subquery selection
4382
-
4383
- */
4384
- selectSubquery(alias, sub) {
4385
- const query = this.resolveQueryNode(sub);
4386
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
4387
- }
4388
- /**
4389
- * Adds a JOIN against a derived table (subquery with alias)
4390
- * @param subquery - Subquery to join
4391
- * @param alias - Alias for the derived table
4392
- * @param condition - Join condition expression
4393
- * @param joinKind - Join kind (defaults to INNER)
4394
- * @param columnAliases - Optional column alias list for the derived table
4395
- * @returns New query builder instance with the derived-table join
4396
- */
4397
- joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
4398
- const subAst = this.resolveQueryNode(subquery);
4399
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
4400
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
4401
- return this.clone(nextContext);
4402
- }
4403
- /**
4404
-
4405
- * Adds an INNER JOIN to the query
4406
-
4407
- * @param table - Table to join
4408
-
4409
- * @param condition - Join condition expression
4410
-
4411
- * @returns New query builder instance with the INNER JOIN
4412
-
4413
- */
4414
- innerJoin(table, condition) {
4415
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
4416
- return this.clone(nextContext);
4417
- }
4418
- /**
4419
-
4420
- * Adds a LEFT JOIN to the query
4421
-
4422
- * @param table - Table to join
4423
-
4424
- * @param condition - Join condition expression
4425
-
4426
- * @returns New query builder instance with the LEFT JOIN
4427
-
4428
- */
4429
- leftJoin(table, condition) {
4430
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
4431
- return this.clone(nextContext);
4432
- }
4433
- /**
4434
-
4435
- * Adds a RIGHT JOIN to the query
4436
-
4437
- * @param table - Table to join
4438
-
4439
- * @param condition - Join condition expression
4440
-
4441
- * @returns New query builder instance with the RIGHT JOIN
4442
-
4443
- */
4444
- rightJoin(table, condition) {
4445
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
4446
- return this.clone(nextContext);
4447
- }
4448
- /**
4449
-
4450
- * Matches records based on a relationship
4451
-
4452
- * @param relationName - Name of the relationship to match
4453
-
4454
- * @param predicate - Optional predicate expression
4455
-
4456
- * @returns New query builder instance with the relationship match
4457
-
4458
- */
4459
- match(relationName, predicate) {
4460
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
4461
- return this.clone(nextContext);
4462
- }
4463
- /**
4464
-
4465
- * Joins a related table
4466
-
4467
- * @param relationName - Name of the relationship to join
4468
-
4469
- * @param joinKind - Type of join (defaults to INNER)
4470
-
4471
- * @param extraCondition - Optional additional join condition
4472
-
4473
- * @returns New query builder instance with the relationship join
4474
-
4475
- */
4476
- joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
4477
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
4478
- return this.clone(nextContext);
4479
- }
4480
- /**
4481
-
4482
- * Includes related data in the query results
4483
-
4484
- * @param relationName - Name of the relationship to include
4485
-
4486
- * @param options - Optional include options
4487
-
4488
- * @returns New query builder instance with the relationship inclusion
4489
-
4490
- */
4491
- include(relationName, options) {
4492
- const nextContext = this.relationManager.include(this.context, relationName, options);
4493
- return this.clone(nextContext);
4494
- }
4495
- includeLazy(relationName) {
4496
- const nextLazy = new Set(this.lazyRelations);
4497
- nextLazy.add(relationName);
4498
- return this.clone(this.context, nextLazy);
4499
- }
4500
- /**
4501
- * Selects columns for a related table in a single hop.
4502
- */
4503
- selectRelationColumns(relationName, ...cols) {
4504
- const relation = this.env.table.relations[relationName];
4505
- if (!relation) {
4506
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
4507
- }
4508
- const target = relation.target;
4509
- for (const col of cols) {
4510
- if (!target.columns[col]) {
4511
- throw new Error(
4512
- `Column '${col}' not found on related table '${target.name}' for relation '${relationName}'`
4513
- );
4514
- }
4515
- }
4516
- return this.include(relationName, { columns: cols });
4517
- }
4518
- /**
4519
- * Convenience alias for selecting specific columns from a relation.
4520
- */
4521
- includePick(relationName, cols) {
4522
- return this.selectRelationColumns(relationName, ...cols);
4523
- }
4524
- /**
4525
- * Selects columns for the root table and relations from a single config object.
4526
- */
4527
- selectColumnsDeep(config) {
4528
- let qb = this;
4529
- if (config.root?.length) {
4530
- qb = qb.selectColumns(...config.root);
4531
- }
4532
- for (const key of Object.keys(config)) {
4533
- if (key === "root") continue;
4534
- const relName = key;
4535
- const cols = config[relName];
4536
- if (!cols || !cols.length) continue;
4537
- qb = qb.selectRelationColumns(relName, ...cols);
4538
- }
4539
- return qb;
4540
- }
4541
- getLazyRelations() {
4542
- return Array.from(this.lazyRelations);
4543
- }
4544
- getTable() {
4545
- return this.env.table;
4546
- }
4547
- async execute(ctx) {
4548
- return executeHydrated(ctx, this);
4549
- }
4550
- async executeWithContexts(execCtx, hydCtx) {
4551
- return executeHydratedWithContexts(execCtx, hydCtx, this);
4552
- }
4553
- /**
4554
-
4555
- * Adds a WHERE condition to the query
4556
-
4557
- * @param expr - Expression for the WHERE clause
4558
-
4559
- * @returns New query builder instance with the WHERE condition
4560
-
4561
- */
4562
- where(expr) {
4563
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
4564
- return this.clone(nextContext);
4565
- }
4566
- /**
4567
-
4568
- * Adds a GROUP BY clause to the query
4569
-
4570
- * @param col - Column definition or column node to group by
4571
-
4572
- * @returns New query builder instance with the GROUP BY clause
4573
-
4574
- */
4575
- groupBy(col) {
4576
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(col));
4577
- return this.clone(nextContext);
4578
- }
4579
- /**
4580
-
4581
- * Adds a HAVING condition to the query
4582
-
4583
- * @param expr - Expression for the HAVING clause
4584
-
4585
- * @returns New query builder instance with the HAVING condition
4586
-
4587
- */
4588
- having(expr) {
4589
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
4590
- return this.clone(nextContext);
4591
- }
4592
- /**
4593
-
4594
- * Adds an ORDER BY clause to the query
4595
-
4596
- * @param col - Column definition or column node to order by
4597
-
4598
- * @param direction - Order direction (defaults to ASC)
4599
-
4600
- * @returns New query builder instance with the ORDER BY clause
4601
-
4602
- */
4603
- orderBy(col, direction = ORDER_DIRECTIONS.ASC) {
4604
- const nextContext = this.applyAst(this.context, (service) => service.withOrderBy(col, direction));
4605
- return this.clone(nextContext);
4606
- }
4607
- /**
4608
-
4609
- * Adds a DISTINCT clause to the query
4610
-
4611
- * @param cols - Columns to make distinct
4612
-
4613
- * @returns New query builder instance with the DISTINCT clause
4614
-
4615
- */
4616
- distinct(...cols) {
4617
- return this.clone(this.columnSelector.distinct(this.context, cols));
4618
- }
4619
- /**
4620
-
4621
- * Adds a LIMIT clause to the query
4622
-
4623
- * @param n - Maximum number of rows to return
4624
-
4625
- * @returns New query builder instance with the LIMIT clause
4626
-
4627
- */
4628
- limit(n) {
4629
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
4630
- return this.clone(nextContext);
4631
- }
4632
- /**
4633
-
4634
- * Adds an OFFSET clause to the query
4635
-
4636
- * @param n - Number of rows to skip
4637
-
4638
- * @returns New query builder instance with the OFFSET clause
4639
-
4640
- */
4641
- offset(n) {
4642
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
4643
- return this.clone(nextContext);
4644
- }
4645
- /**
4646
-
4647
- * Combines this query with another using UNION
4648
-
4649
- * @param query - Query to union with
4650
-
4651
- * @returns New query builder instance with the set operation
4652
-
4653
- */
4654
- union(query) {
4655
- return this.clone(this.applySetOperation("UNION", query));
4656
- }
4657
- /**
4658
-
4659
- * Combines this query with another using UNION ALL
4660
-
4661
- * @param query - Query to union with
4662
-
4663
- * @returns New query builder instance with the set operation
4664
-
4665
- */
4666
- unionAll(query) {
4667
- return this.clone(this.applySetOperation("UNION ALL", query));
4668
- }
4669
- /**
4670
-
4671
- * Combines this query with another using INTERSECT
4672
-
4673
- * @param query - Query to intersect with
4674
-
4675
- * @returns New query builder instance with the set operation
4676
-
4677
- */
4678
- intersect(query) {
4679
- return this.clone(this.applySetOperation("INTERSECT", query));
4680
- }
4681
- /**
4682
-
4683
- * Combines this query with another using EXCEPT
4684
-
4685
- * @param query - Query to subtract
4686
-
4687
- * @returns New query builder instance with the set operation
4688
-
4689
- */
4690
- except(query) {
4691
- return this.clone(this.applySetOperation("EXCEPT", query));
4692
- }
4693
- /**
4694
-
4695
- * Adds a WHERE EXISTS condition to the query
4696
-
4697
- * @param subquery - Subquery to check for existence
4698
-
4699
- * @returns New query builder instance with the WHERE EXISTS condition
4700
-
4701
- */
4702
- whereExists(subquery, correlate) {
4703
- const subAst = this.resolveQueryNode(subquery);
4704
- const correlated = this.applyCorrelation(subAst, correlate);
4705
- return this.where(exists(correlated));
4706
- }
4707
- /**
4708
-
4709
- * Adds a WHERE NOT EXISTS condition to the query
4710
-
4711
- * @param subquery - Subquery to check for non-existence
4712
-
4713
- * @returns New query builder instance with the WHERE NOT EXISTS condition
4714
-
4715
- */
4716
- whereNotExists(subquery, correlate) {
4717
- const subAst = this.resolveQueryNode(subquery);
4718
- const correlated = this.applyCorrelation(subAst, correlate);
4719
- return this.where(notExists(correlated));
4720
- }
4721
- /**
4722
-
4723
- * Adds a WHERE EXISTS condition based on a relationship
4724
-
4725
- * @param relationName - Name of the relationship to check
4726
-
4727
- * @param callback - Optional callback to modify the relationship query
4728
-
4729
- * @returns New query builder instance with the relationship existence check
4730
-
4731
- */
4732
- whereHas(relationName, callbackOrOptions, maybeOptions) {
4733
- const relation = this.env.table.relations[relationName];
4734
- if (!relation) {
4735
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
4736
- }
4737
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
4738
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
4739
- let subQb = this.createChildBuilder(relation.target);
4740
- if (callback) {
4741
- subQb = callback(subQb);
4742
- }
4743
- const subAst = subQb.getAST();
4744
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
4745
- return this.where(exists(finalSubAst));
4746
- }
4747
- /**
4748
-
4749
- * Adds a WHERE NOT EXISTS condition based on a relationship
4750
-
4751
- * @param relationName - Name of the relationship to check
4752
-
4753
- * @param callback - Optional callback to modify the relationship query
4754
-
4755
- * @returns New query builder instance with the relationship non-existence check
4756
-
4757
- */
4758
- whereHasNot(relationName, callbackOrOptions, maybeOptions) {
4759
- const relation = this.env.table.relations[relationName];
4760
- if (!relation) {
4761
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
4762
- }
4763
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
4764
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
4765
- let subQb = this.createChildBuilder(relation.target);
4766
- if (callback) {
4767
- subQb = callback(subQb);
4768
- }
4769
- const subAst = subQb.getAST();
4770
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
4771
- return this.where(notExists(finalSubAst));
4772
- }
4773
- /**
4774
-
4775
- * Compiles the query to SQL for a specific dialect
4776
-
4777
- * @param dialect - Database dialect to compile for
4778
-
4779
- * @returns Compiled query with SQL and parameters
4780
-
4781
- */
4782
- compile(dialect) {
4783
- const resolved = resolveDialectInput(dialect);
4784
- return resolved.compileSelect(this.context.state.ast);
4785
- }
4786
- /**
4787
-
4788
- * Converts the query to SQL string for a specific dialect
4789
-
4790
- * @param dialect - Database dialect to generate SQL for
4791
-
4792
- * @returns SQL string representation of the query
4793
-
4794
- */
4795
- toSql(dialect) {
4796
- return this.compile(dialect).sql;
4797
- }
4798
- /**
4799
-
4800
- * Gets the hydration plan for the query
4801
-
4802
- * @returns Hydration plan or undefined if none exists
4803
-
4804
- */
4805
- getHydrationPlan() {
4806
- return this.context.hydration.getPlan();
4807
- }
4808
- /**
4809
-
4810
- * Gets the Abstract Syntax Tree (AST) representation of the query
4811
-
4812
- * @returns Query AST with hydration applied
4813
-
4814
- */
4815
- getAST() {
4816
- return this.context.hydration.applyToAst(this.context.state.ast);
4817
- }
4818
- };
4819
-
4820
- // src/decorators/bootstrap.ts
4821
- var isTableDef = (value) => {
4822
- return typeof value === "object" && value !== null && "columns" in value;
4823
- };
4824
- var unwrapTarget = (target) => {
4825
- if (typeof target === "function" && target.prototype === void 0) {
4826
- return target();
4827
- }
4828
- return target;
4829
- };
4830
- var resolveTableTarget = (target, tableMap) => {
4831
- const resolved = unwrapTarget(target);
4832
- if (isTableDef(resolved)) {
4833
- return resolved;
4834
- }
4835
- const table = tableMap.get(resolved);
4836
- if (!table) {
4837
- throw new Error(`Entity '${resolved.name}' is not registered with decorators`);
4838
- }
4839
- return table;
4840
- };
4841
- var buildRelationDefinitions = (meta, tableMap) => {
4842
- const relations = {};
4843
- for (const [name, relation] of Object.entries(meta.relations)) {
4844
- switch (relation.kind) {
4845
- case RelationKinds.HasOne: {
4846
- relations[name] = hasOne(
4847
- resolveTableTarget(relation.target, tableMap),
4848
- relation.foreignKey,
4849
- relation.localKey,
4850
- relation.cascade
4851
- );
4852
- break;
4853
- }
4854
- case RelationKinds.HasMany: {
4855
- relations[name] = hasMany(
4856
- resolveTableTarget(relation.target, tableMap),
4857
- relation.foreignKey,
4858
- relation.localKey,
4859
- relation.cascade
4860
- );
4861
- break;
4862
- }
4863
- case RelationKinds.BelongsTo: {
4864
- relations[name] = belongsTo(
4865
- resolveTableTarget(relation.target, tableMap),
4866
- relation.foreignKey,
4867
- relation.localKey,
4868
- relation.cascade
4869
- );
4870
- break;
4871
- }
4872
- case RelationKinds.BelongsToMany: {
4873
- relations[name] = belongsToMany(
4874
- resolveTableTarget(relation.target, tableMap),
4875
- resolveTableTarget(relation.pivotTable, tableMap),
4876
- {
4877
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
4878
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
4879
- localKey: relation.localKey,
4880
- targetKey: relation.targetKey,
4881
- pivotPrimaryKey: relation.pivotPrimaryKey,
4882
- defaultPivotColumns: relation.defaultPivotColumns,
4883
- cascade: relation.cascade
4884
- }
4885
- );
4886
- break;
4887
- }
4888
- }
4889
- }
4890
- return relations;
4891
- };
4892
- var bootstrapEntities = () => {
4893
- const metas = getAllEntityMetadata();
4894
- const tableMap = /* @__PURE__ */ new Map();
4895
- for (const meta of metas) {
4896
- const table = buildTableDef(meta);
4897
- tableMap.set(meta.target, table);
4898
- }
4899
- for (const meta of metas) {
4900
- const table = meta.table;
4901
- const relations = buildRelationDefinitions(meta, tableMap);
4902
- table.relations = relations;
4903
- }
4904
- return metas.map((meta) => meta.table);
4905
- };
4906
- var getTableDefFromEntity = (ctor) => {
4907
- const meta = getEntityMetadata(ctor);
4908
- if (!meta) return void 0;
4909
- if (!meta.table) {
4910
- bootstrapEntities();
4911
- }
4912
- return meta.table;
4913
- };
4914
- var selectFromEntity = (ctor) => {
4915
- const table = getTableDefFromEntity(ctor);
4916
- if (!table) {
4917
- throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
4918
- }
4919
- return new SelectQueryBuilder(table);
4920
- };
4921
- export {
4922
- BelongsTo,
4923
- BelongsToMany,
4924
- Column,
4925
- Entity,
4926
- HasMany,
4927
- HasOne,
4928
- PrimaryKey,
4929
- bootstrapEntities,
4930
- getTableDefFromEntity,
4931
- selectFromEntity
4932
- };
4933
- //# sourceMappingURL=index.js.map