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