metal-orm 1.0.57 → 1.0.58
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 +22 -15
- package/dist/index.cjs +640 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -45
- package/dist/index.d.ts +113 -45
- package/dist/index.js +639 -83
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/decorators/bootstrap.ts +39 -3
- package/src/orm/entity-meta.ts +6 -3
- package/src/orm/entity.ts +81 -14
- package/src/orm/execute.ts +87 -20
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +27 -13
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-service.ts +399 -95
- package/src/query-builder/relation-types.ts +2 -2
- package/src/query-builder/select.ts +58 -40
- package/src/schema/types.ts +106 -89
package/dist/index.cjs
CHANGED
|
@@ -241,6 +241,7 @@ __export(index_exports, {
|
|
|
241
241
|
registerExpressionDispatcher: () => registerExpressionDispatcher,
|
|
242
242
|
registerOperandDispatcher: () => registerOperandDispatcher,
|
|
243
243
|
registerSchemaIntrospector: () => registerSchemaIntrospector,
|
|
244
|
+
relationLoaderCache: () => relationLoaderCache,
|
|
244
245
|
renderColumnDefinition: () => renderColumnDefinition,
|
|
245
246
|
renderTypeWithArgs: () => renderTypeWithArgs,
|
|
246
247
|
repeat: () => repeat,
|
|
@@ -4111,20 +4112,21 @@ var RelationProjectionHelper = class {
|
|
|
4111
4112
|
var assertNever = (value) => {
|
|
4112
4113
|
throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
|
|
4113
4114
|
};
|
|
4114
|
-
var baseRelationCondition = (root, relation, rootAlias) => {
|
|
4115
|
+
var baseRelationCondition = (root, relation, rootAlias, targetTableName) => {
|
|
4115
4116
|
const rootTable = rootAlias || root.name;
|
|
4117
|
+
const targetTable = targetTableName ?? relation.target.name;
|
|
4116
4118
|
const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
|
|
4117
4119
|
const localKey = relation.localKey || defaultLocalKey;
|
|
4118
4120
|
switch (relation.type) {
|
|
4119
4121
|
case RelationKinds.HasMany:
|
|
4120
4122
|
case RelationKinds.HasOne:
|
|
4121
4123
|
return eq(
|
|
4122
|
-
{ type: "Column", table:
|
|
4124
|
+
{ type: "Column", table: targetTable, name: relation.foreignKey },
|
|
4123
4125
|
{ type: "Column", table: rootTable, name: localKey }
|
|
4124
4126
|
);
|
|
4125
4127
|
case RelationKinds.BelongsTo:
|
|
4126
4128
|
return eq(
|
|
4127
|
-
{ type: "Column", table:
|
|
4129
|
+
{ type: "Column", table: targetTable, name: localKey },
|
|
4128
4130
|
{ type: "Column", table: rootTable, name: relation.foreignKey }
|
|
4129
4131
|
);
|
|
4130
4132
|
case RelationKinds.BelongsToMany:
|
|
@@ -4133,7 +4135,7 @@ var baseRelationCondition = (root, relation, rootAlias) => {
|
|
|
4133
4135
|
return assertNever(relation);
|
|
4134
4136
|
}
|
|
4135
4137
|
};
|
|
4136
|
-
var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias) => {
|
|
4138
|
+
var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias, targetTable, targetTableName) => {
|
|
4137
4139
|
const rootKey = relation.localKey || findPrimaryKey(root);
|
|
4138
4140
|
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
4139
4141
|
const rootTable = rootAlias || root.name;
|
|
@@ -4146,8 +4148,14 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
|
|
|
4146
4148
|
{ type: "Table", name: relation.pivotTable.name, schema: relation.pivotTable.schema },
|
|
4147
4149
|
pivotCondition
|
|
4148
4150
|
);
|
|
4151
|
+
const targetSource = targetTable ?? {
|
|
4152
|
+
type: "Table",
|
|
4153
|
+
name: relation.target.name,
|
|
4154
|
+
schema: relation.target.schema
|
|
4155
|
+
};
|
|
4156
|
+
const effectiveTargetName = targetTableName ?? relation.target.name;
|
|
4149
4157
|
let targetCondition = eq(
|
|
4150
|
-
{ type: "Column", table:
|
|
4158
|
+
{ type: "Column", table: effectiveTargetName, name: targetKey },
|
|
4151
4159
|
{ type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
|
|
4152
4160
|
);
|
|
4153
4161
|
if (extra) {
|
|
@@ -4155,24 +4163,25 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
|
|
|
4155
4163
|
}
|
|
4156
4164
|
const targetJoin = createJoinNode(
|
|
4157
4165
|
joinKind,
|
|
4158
|
-
|
|
4166
|
+
targetSource,
|
|
4159
4167
|
targetCondition,
|
|
4160
4168
|
relationName
|
|
4161
4169
|
);
|
|
4162
4170
|
return [pivotJoin, targetJoin];
|
|
4163
4171
|
};
|
|
4164
|
-
var buildRelationJoinCondition = (root, relation, extra, rootAlias) => {
|
|
4165
|
-
const base = baseRelationCondition(root, relation, rootAlias);
|
|
4172
|
+
var buildRelationJoinCondition = (root, relation, extra, rootAlias, targetTableName) => {
|
|
4173
|
+
const base = baseRelationCondition(root, relation, rootAlias, targetTableName);
|
|
4166
4174
|
return extra ? and(base, extra) : base;
|
|
4167
4175
|
};
|
|
4168
|
-
var buildRelationCorrelation = (root, relation, rootAlias) => {
|
|
4169
|
-
return baseRelationCondition(root, relation, rootAlias);
|
|
4176
|
+
var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
|
|
4177
|
+
return baseRelationCondition(root, relation, rootAlias, targetTableName);
|
|
4170
4178
|
};
|
|
4171
4179
|
|
|
4172
4180
|
// src/core/ast/join-metadata.ts
|
|
4173
4181
|
var getJoinRelationName = (join) => join.meta?.relationName;
|
|
4174
4182
|
|
|
4175
4183
|
// src/query-builder/relation-service.ts
|
|
4184
|
+
var hasRelationForeignKey = (relation) => relation.type !== RelationKinds.BelongsToMany;
|
|
4176
4185
|
var RelationService = class {
|
|
4177
4186
|
/**
|
|
4178
4187
|
* Creates a new RelationService instance
|
|
@@ -4197,8 +4206,8 @@ var RelationService = class {
|
|
|
4197
4206
|
* @param extraCondition - Additional join condition
|
|
4198
4207
|
* @returns Relation result with updated state and hydration
|
|
4199
4208
|
*/
|
|
4200
|
-
joinRelation(relationName, joinKind, extraCondition) {
|
|
4201
|
-
const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
|
|
4209
|
+
joinRelation(relationName, joinKind, extraCondition, tableSource) {
|
|
4210
|
+
const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition, tableSource);
|
|
4202
4211
|
return { state: nextState, hydration: this.hydration };
|
|
4203
4212
|
}
|
|
4204
4213
|
/**
|
|
@@ -4227,14 +4236,58 @@ var RelationService = class {
|
|
|
4227
4236
|
const relation = this.getRelation(relationName);
|
|
4228
4237
|
const aliasPrefix = options?.aliasPrefix ?? relationName;
|
|
4229
4238
|
const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
|
|
4239
|
+
const { selfFilters, crossFilters } = this.splitFilterExpressions(
|
|
4240
|
+
options?.filter,
|
|
4241
|
+
/* @__PURE__ */ new Set([relation.target.name])
|
|
4242
|
+
);
|
|
4243
|
+
const canUseCte = !alreadyJoined && selfFilters.length > 0;
|
|
4244
|
+
const joinFilters = [...crossFilters];
|
|
4245
|
+
if (!canUseCte) {
|
|
4246
|
+
joinFilters.push(...selfFilters);
|
|
4247
|
+
}
|
|
4248
|
+
const joinCondition = this.combineWithAnd(joinFilters);
|
|
4249
|
+
let tableSourceOverride;
|
|
4250
|
+
if (canUseCte) {
|
|
4251
|
+
const cteInfo = this.createFilteredRelationCte(state, relationName, relation, selfFilters);
|
|
4252
|
+
state = cteInfo.state;
|
|
4253
|
+
tableSourceOverride = cteInfo.table;
|
|
4254
|
+
}
|
|
4230
4255
|
if (!alreadyJoined) {
|
|
4231
|
-
|
|
4232
|
-
|
|
4256
|
+
state = this.withJoin(
|
|
4257
|
+
state,
|
|
4258
|
+
relationName,
|
|
4259
|
+
options?.joinKind ?? JOIN_KINDS.LEFT,
|
|
4260
|
+
joinCondition,
|
|
4261
|
+
tableSourceOverride
|
|
4262
|
+
);
|
|
4233
4263
|
}
|
|
4234
4264
|
const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
|
|
4235
4265
|
state = projectionResult.state;
|
|
4236
4266
|
hydration = projectionResult.hydration;
|
|
4237
|
-
|
|
4267
|
+
if (hasRelationForeignKey(relation)) {
|
|
4268
|
+
const fkColumn = this.table.columns[relation.foreignKey];
|
|
4269
|
+
if (fkColumn) {
|
|
4270
|
+
const hasForeignKeySelected = state.ast.columns.some((col2) => {
|
|
4271
|
+
if (col2.type !== "Column") return false;
|
|
4272
|
+
const node = col2;
|
|
4273
|
+
const alias = node.alias ?? node.name;
|
|
4274
|
+
return alias === relation.foreignKey;
|
|
4275
|
+
});
|
|
4276
|
+
if (!hasForeignKeySelected) {
|
|
4277
|
+
const fkSelectionResult = this.selectColumns(state, hydration, {
|
|
4278
|
+
[relation.foreignKey]: fkColumn
|
|
4279
|
+
});
|
|
4280
|
+
state = fkSelectionResult.state;
|
|
4281
|
+
hydration = fkSelectionResult.hydration;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
|
|
4286
|
+
const targetPrimaryKey = findPrimaryKey(relation.target);
|
|
4287
|
+
if (!requestedColumns.includes(targetPrimaryKey)) {
|
|
4288
|
+
requestedColumns.push(targetPrimaryKey);
|
|
4289
|
+
}
|
|
4290
|
+
const targetColumns = requestedColumns;
|
|
4238
4291
|
const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
|
|
4239
4292
|
return keys.reduce((acc, key) => {
|
|
4240
4293
|
const def = columns[key];
|
|
@@ -4318,27 +4371,42 @@ var RelationService = class {
|
|
|
4318
4371
|
* @param extraCondition - Additional join condition
|
|
4319
4372
|
* @returns Updated query state with join
|
|
4320
4373
|
*/
|
|
4321
|
-
withJoin(state, relationName, joinKind, extraCondition) {
|
|
4374
|
+
withJoin(state, relationName, joinKind, extraCondition, tableSource) {
|
|
4322
4375
|
const relation = this.getRelation(relationName);
|
|
4323
4376
|
const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
|
|
4324
4377
|
if (relation.type === RelationKinds.BelongsToMany) {
|
|
4378
|
+
const targetTableSource = tableSource ?? {
|
|
4379
|
+
type: "Table",
|
|
4380
|
+
name: relation.target.name,
|
|
4381
|
+
schema: relation.target.schema
|
|
4382
|
+
};
|
|
4383
|
+
const targetName2 = this.resolveTargetTableName(targetTableSource, relation);
|
|
4325
4384
|
const joins = buildBelongsToManyJoins(
|
|
4326
4385
|
this.table,
|
|
4327
4386
|
relationName,
|
|
4328
4387
|
relation,
|
|
4329
4388
|
joinKind,
|
|
4330
4389
|
extraCondition,
|
|
4331
|
-
rootAlias
|
|
4390
|
+
rootAlias,
|
|
4391
|
+
targetTableSource,
|
|
4392
|
+
targetName2
|
|
4332
4393
|
);
|
|
4333
4394
|
return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
|
|
4334
4395
|
}
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4396
|
+
const targetTable = tableSource ?? {
|
|
4397
|
+
type: "Table",
|
|
4398
|
+
name: relation.target.name,
|
|
4399
|
+
schema: relation.target.schema
|
|
4400
|
+
};
|
|
4401
|
+
const targetName = this.resolveTargetTableName(targetTable, relation);
|
|
4402
|
+
const condition = buildRelationJoinCondition(
|
|
4403
|
+
this.table,
|
|
4404
|
+
relation,
|
|
4405
|
+
extraCondition,
|
|
4406
|
+
rootAlias,
|
|
4407
|
+
targetName
|
|
4341
4408
|
);
|
|
4409
|
+
const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
|
|
4342
4410
|
return this.astService(state).withJoin(joinNode);
|
|
4343
4411
|
}
|
|
4344
4412
|
/**
|
|
@@ -4355,6 +4423,198 @@ var RelationService = class {
|
|
|
4355
4423
|
hydration: hydration.onColumnsSelected(nextState, addedColumns)
|
|
4356
4424
|
};
|
|
4357
4425
|
}
|
|
4426
|
+
combineWithAnd(expressions) {
|
|
4427
|
+
if (expressions.length === 0) return void 0;
|
|
4428
|
+
if (expressions.length === 1) return expressions[0];
|
|
4429
|
+
return {
|
|
4430
|
+
type: "LogicalExpression",
|
|
4431
|
+
operator: "AND",
|
|
4432
|
+
operands: expressions
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
splitFilterExpressions(filter, allowedTables) {
|
|
4436
|
+
const terms = this.flattenAnd(filter);
|
|
4437
|
+
const selfFilters = [];
|
|
4438
|
+
const crossFilters = [];
|
|
4439
|
+
for (const term of terms) {
|
|
4440
|
+
if (this.isExpressionSelfContained(term, allowedTables)) {
|
|
4441
|
+
selfFilters.push(term);
|
|
4442
|
+
} else {
|
|
4443
|
+
crossFilters.push(term);
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return { selfFilters, crossFilters };
|
|
4447
|
+
}
|
|
4448
|
+
flattenAnd(node) {
|
|
4449
|
+
if (!node) return [];
|
|
4450
|
+
if (node.type === "LogicalExpression" && node.operator === "AND") {
|
|
4451
|
+
return node.operands.flatMap((operand) => this.flattenAnd(operand));
|
|
4452
|
+
}
|
|
4453
|
+
return [node];
|
|
4454
|
+
}
|
|
4455
|
+
isExpressionSelfContained(expr, allowedTables) {
|
|
4456
|
+
const collector = this.collectReferencedTables(expr);
|
|
4457
|
+
if (collector.hasSubquery) return false;
|
|
4458
|
+
if (collector.tables.size === 0) return true;
|
|
4459
|
+
for (const table of collector.tables) {
|
|
4460
|
+
if (!allowedTables.has(table)) {
|
|
4461
|
+
return false;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
return true;
|
|
4465
|
+
}
|
|
4466
|
+
collectReferencedTables(expr) {
|
|
4467
|
+
const collector = {
|
|
4468
|
+
tables: /* @__PURE__ */ new Set(),
|
|
4469
|
+
hasSubquery: false
|
|
4470
|
+
};
|
|
4471
|
+
this.collectFromExpression(expr, collector);
|
|
4472
|
+
return collector;
|
|
4473
|
+
}
|
|
4474
|
+
collectFromExpression(expr, collector) {
|
|
4475
|
+
switch (expr.type) {
|
|
4476
|
+
case "BinaryExpression":
|
|
4477
|
+
this.collectFromOperand(expr.left, collector);
|
|
4478
|
+
this.collectFromOperand(expr.right, collector);
|
|
4479
|
+
break;
|
|
4480
|
+
case "LogicalExpression":
|
|
4481
|
+
expr.operands.forEach((operand) => this.collectFromExpression(operand, collector));
|
|
4482
|
+
break;
|
|
4483
|
+
case "NullExpression":
|
|
4484
|
+
this.collectFromOperand(expr.left, collector);
|
|
4485
|
+
break;
|
|
4486
|
+
case "InExpression":
|
|
4487
|
+
this.collectFromOperand(expr.left, collector);
|
|
4488
|
+
if (Array.isArray(expr.right)) {
|
|
4489
|
+
expr.right.forEach((value) => this.collectFromOperand(value, collector));
|
|
4490
|
+
} else {
|
|
4491
|
+
collector.hasSubquery = true;
|
|
4492
|
+
}
|
|
4493
|
+
break;
|
|
4494
|
+
case "ExistsExpression":
|
|
4495
|
+
collector.hasSubquery = true;
|
|
4496
|
+
break;
|
|
4497
|
+
case "BetweenExpression":
|
|
4498
|
+
this.collectFromOperand(expr.left, collector);
|
|
4499
|
+
this.collectFromOperand(expr.lower, collector);
|
|
4500
|
+
this.collectFromOperand(expr.upper, collector);
|
|
4501
|
+
break;
|
|
4502
|
+
case "ArithmeticExpression":
|
|
4503
|
+
case "BitwiseExpression":
|
|
4504
|
+
this.collectFromOperand(expr.left, collector);
|
|
4505
|
+
this.collectFromOperand(expr.right, collector);
|
|
4506
|
+
break;
|
|
4507
|
+
default:
|
|
4508
|
+
break;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
collectFromOperand(node, collector) {
|
|
4512
|
+
switch (node.type) {
|
|
4513
|
+
case "Column":
|
|
4514
|
+
collector.tables.add(node.table);
|
|
4515
|
+
break;
|
|
4516
|
+
case "Function":
|
|
4517
|
+
node.args.forEach((arg) => this.collectFromOperand(arg, collector));
|
|
4518
|
+
if (node.separator) {
|
|
4519
|
+
this.collectFromOperand(node.separator, collector);
|
|
4520
|
+
}
|
|
4521
|
+
if (node.orderBy) {
|
|
4522
|
+
node.orderBy.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
|
|
4523
|
+
}
|
|
4524
|
+
break;
|
|
4525
|
+
case "JsonPath":
|
|
4526
|
+
this.collectFromOperand(node.column, collector);
|
|
4527
|
+
break;
|
|
4528
|
+
case "ScalarSubquery":
|
|
4529
|
+
collector.hasSubquery = true;
|
|
4530
|
+
break;
|
|
4531
|
+
case "CaseExpression":
|
|
4532
|
+
node.conditions.forEach(({ when, then }) => {
|
|
4533
|
+
this.collectFromExpression(when, collector);
|
|
4534
|
+
this.collectFromOperand(then, collector);
|
|
4535
|
+
});
|
|
4536
|
+
if (node.else) {
|
|
4537
|
+
this.collectFromOperand(node.else, collector);
|
|
4538
|
+
}
|
|
4539
|
+
break;
|
|
4540
|
+
case "Cast":
|
|
4541
|
+
this.collectFromOperand(node.expression, collector);
|
|
4542
|
+
break;
|
|
4543
|
+
case "WindowFunction":
|
|
4544
|
+
node.args.forEach((arg) => this.collectFromOperand(arg, collector));
|
|
4545
|
+
node.partitionBy?.forEach((part) => this.collectFromOperand(part, collector));
|
|
4546
|
+
node.orderBy?.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
|
|
4547
|
+
break;
|
|
4548
|
+
case "Collate":
|
|
4549
|
+
this.collectFromOperand(node.expression, collector);
|
|
4550
|
+
break;
|
|
4551
|
+
case "ArithmeticExpression":
|
|
4552
|
+
case "BitwiseExpression":
|
|
4553
|
+
this.collectFromOperand(node.left, collector);
|
|
4554
|
+
this.collectFromOperand(node.right, collector);
|
|
4555
|
+
break;
|
|
4556
|
+
case "Literal":
|
|
4557
|
+
case "AliasRef":
|
|
4558
|
+
break;
|
|
4559
|
+
default:
|
|
4560
|
+
break;
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
collectFromOrderingTerm(term, collector) {
|
|
4564
|
+
if (isOperandNode(term)) {
|
|
4565
|
+
this.collectFromOperand(term, collector);
|
|
4566
|
+
return;
|
|
4567
|
+
}
|
|
4568
|
+
this.collectFromExpression(term, collector);
|
|
4569
|
+
}
|
|
4570
|
+
createFilteredRelationCte(state, relationName, relation, filters) {
|
|
4571
|
+
const cteName = this.generateUniqueCteName(state, relationName);
|
|
4572
|
+
const predicate = this.combineWithAnd(filters);
|
|
4573
|
+
if (!predicate) {
|
|
4574
|
+
throw new Error("Unable to build filter CTE without predicates.");
|
|
4575
|
+
}
|
|
4576
|
+
const columns = Object.keys(relation.target.columns).map((name) => ({
|
|
4577
|
+
type: "Column",
|
|
4578
|
+
table: relation.target.name,
|
|
4579
|
+
name
|
|
4580
|
+
}));
|
|
4581
|
+
const cteQuery = {
|
|
4582
|
+
type: "SelectQuery",
|
|
4583
|
+
from: { type: "Table", name: relation.target.name, schema: relation.target.schema },
|
|
4584
|
+
columns,
|
|
4585
|
+
joins: [],
|
|
4586
|
+
where: predicate
|
|
4587
|
+
};
|
|
4588
|
+
const nextState = this.astService(state).withCte(cteName, cteQuery);
|
|
4589
|
+
const tableNode3 = {
|
|
4590
|
+
type: "Table",
|
|
4591
|
+
name: cteName,
|
|
4592
|
+
alias: relation.target.name
|
|
4593
|
+
};
|
|
4594
|
+
return { state: nextState, table: tableNode3 };
|
|
4595
|
+
}
|
|
4596
|
+
generateUniqueCteName(state, relationName) {
|
|
4597
|
+
const existing = new Set((state.ast.ctes ?? []).map((cte) => cte.name));
|
|
4598
|
+
let candidate = `${relationName}__filtered`;
|
|
4599
|
+
let suffix = 1;
|
|
4600
|
+
while (existing.has(candidate)) {
|
|
4601
|
+
candidate = `${relationName}__filtered_${suffix}`;
|
|
4602
|
+
suffix += 1;
|
|
4603
|
+
}
|
|
4604
|
+
return candidate;
|
|
4605
|
+
}
|
|
4606
|
+
resolveTargetTableName(target, relation) {
|
|
4607
|
+
if (target.type === "Table") {
|
|
4608
|
+
return target.alias ?? target.name;
|
|
4609
|
+
}
|
|
4610
|
+
if (target.type === "DerivedTable") {
|
|
4611
|
+
return target.alias;
|
|
4612
|
+
}
|
|
4613
|
+
if (target.type === "FunctionTable") {
|
|
4614
|
+
return target.alias ?? relation.target.name;
|
|
4615
|
+
}
|
|
4616
|
+
return relation.target.name;
|
|
4617
|
+
}
|
|
4358
4618
|
/**
|
|
4359
4619
|
* Gets a relation definition by name
|
|
4360
4620
|
* @param relationName - Name of the relation
|
|
@@ -4705,6 +4965,18 @@ var DefaultHasManyCollection = class {
|
|
|
4705
4965
|
getItems() {
|
|
4706
4966
|
return this.items;
|
|
4707
4967
|
}
|
|
4968
|
+
/**
|
|
4969
|
+
* Array-compatible length for testing frameworks.
|
|
4970
|
+
*/
|
|
4971
|
+
get length() {
|
|
4972
|
+
return this.items.length;
|
|
4973
|
+
}
|
|
4974
|
+
/**
|
|
4975
|
+
* Enables iteration over the collection like an array.
|
|
4976
|
+
*/
|
|
4977
|
+
[Symbol.iterator]() {
|
|
4978
|
+
return this.items[Symbol.iterator]();
|
|
4979
|
+
}
|
|
4708
4980
|
/**
|
|
4709
4981
|
* Adds a new child entity to the collection.
|
|
4710
4982
|
* @param data - Partial data for the new entity
|
|
@@ -5086,6 +5358,18 @@ var DefaultManyToManyCollection = class {
|
|
|
5086
5358
|
getItems() {
|
|
5087
5359
|
return this.items;
|
|
5088
5360
|
}
|
|
5361
|
+
/**
|
|
5362
|
+
* Array-compatible length for testing frameworks.
|
|
5363
|
+
*/
|
|
5364
|
+
get length() {
|
|
5365
|
+
return this.items.length;
|
|
5366
|
+
}
|
|
5367
|
+
/**
|
|
5368
|
+
* Enables iteration over the collection like an array.
|
|
5369
|
+
*/
|
|
5370
|
+
[Symbol.iterator]() {
|
|
5371
|
+
return this.items[Symbol.iterator]();
|
|
5372
|
+
}
|
|
5089
5373
|
/**
|
|
5090
5374
|
* Attaches an entity to the collection.
|
|
5091
5375
|
* Registers an 'attach' change in the entity context.
|
|
@@ -5193,10 +5477,27 @@ var DefaultManyToManyCollection = class {
|
|
|
5193
5477
|
};
|
|
5194
5478
|
|
|
5195
5479
|
// src/orm/lazy-batch.ts
|
|
5196
|
-
var
|
|
5197
|
-
|
|
5198
|
-
return acc
|
|
5199
|
-
|
|
5480
|
+
var hasColumns = (columns) => Boolean(columns && columns.length > 0);
|
|
5481
|
+
var buildColumnSelection = (table, columns, missingMsg) => {
|
|
5482
|
+
return columns.reduce((acc, column) => {
|
|
5483
|
+
const def = table.columns[column];
|
|
5484
|
+
if (!def) {
|
|
5485
|
+
throw new Error(missingMsg(column));
|
|
5486
|
+
}
|
|
5487
|
+
acc[column] = def;
|
|
5488
|
+
return acc;
|
|
5489
|
+
}, {});
|
|
5490
|
+
};
|
|
5491
|
+
var filterRow = (row, columns) => {
|
|
5492
|
+
const filtered = {};
|
|
5493
|
+
for (const column of columns) {
|
|
5494
|
+
if (column in row) {
|
|
5495
|
+
filtered[column] = row[column];
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
return filtered;
|
|
5499
|
+
};
|
|
5500
|
+
var filterRows = (rows, columns) => rows.map((row) => filterRow(row, columns));
|
|
5200
5501
|
var rowsFromResults = (results) => {
|
|
5201
5502
|
const rows = [];
|
|
5202
5503
|
for (const result of results) {
|
|
@@ -5228,9 +5529,12 @@ var collectKeysFromRoots = (roots, key) => {
|
|
|
5228
5529
|
return collected;
|
|
5229
5530
|
};
|
|
5230
5531
|
var buildInListValues = (keys) => Array.from(keys);
|
|
5231
|
-
var fetchRowsForKeys = async (ctx, table, column, keys) => {
|
|
5232
|
-
|
|
5233
|
-
qb.where(inList(column, buildInListValues(keys)));
|
|
5532
|
+
var fetchRowsForKeys = async (ctx, table, column, keys, selection, filter) => {
|
|
5533
|
+
let qb = new SelectQueryBuilder(table).select(selection);
|
|
5534
|
+
qb = qb.where(inList(column, buildInListValues(keys)));
|
|
5535
|
+
if (filter) {
|
|
5536
|
+
qb = qb.where(filter);
|
|
5537
|
+
}
|
|
5234
5538
|
return executeQuery(ctx, qb);
|
|
5235
5539
|
};
|
|
5236
5540
|
var groupRowsByMany = (rows, keyColumn) => {
|
|
@@ -5257,7 +5561,7 @@ var groupRowsByUnique = (rows, keyColumn) => {
|
|
|
5257
5561
|
}
|
|
5258
5562
|
return lookup;
|
|
5259
5563
|
};
|
|
5260
|
-
var loadHasManyRelation = async (ctx, rootTable,
|
|
5564
|
+
var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
|
|
5261
5565
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
5262
5566
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
5263
5567
|
const keys = collectKeysFromRoots(roots, localKey);
|
|
@@ -5266,10 +5570,30 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
5266
5570
|
}
|
|
5267
5571
|
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
5268
5572
|
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
5269
|
-
const
|
|
5270
|
-
|
|
5573
|
+
const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
|
|
5574
|
+
const targetPrimaryKey = findPrimaryKey(relation.target);
|
|
5575
|
+
const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
|
|
5576
|
+
if (!selectedColumns.includes(targetPrimaryKey)) {
|
|
5577
|
+
selectedColumns.push(targetPrimaryKey);
|
|
5578
|
+
}
|
|
5579
|
+
const queryColumns = new Set(selectedColumns);
|
|
5580
|
+
queryColumns.add(relation.foreignKey);
|
|
5581
|
+
const selection = buildColumnSelection(
|
|
5582
|
+
relation.target,
|
|
5583
|
+
Array.from(queryColumns),
|
|
5584
|
+
(column) => `Column '${column}' not found on relation '${relationName}'`
|
|
5585
|
+
);
|
|
5586
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
|
|
5587
|
+
const grouped = groupRowsByMany(rows, relation.foreignKey);
|
|
5588
|
+
if (!requestedColumns) return grouped;
|
|
5589
|
+
const visibleColumns = new Set(selectedColumns);
|
|
5590
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
5591
|
+
for (const [key, bucket] of grouped.entries()) {
|
|
5592
|
+
filtered.set(key, filterRows(bucket, visibleColumns));
|
|
5593
|
+
}
|
|
5594
|
+
return filtered;
|
|
5271
5595
|
};
|
|
5272
|
-
var loadHasOneRelation = async (ctx, rootTable,
|
|
5596
|
+
var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
|
|
5273
5597
|
const localKey = relation.localKey || findPrimaryKey(rootTable);
|
|
5274
5598
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
5275
5599
|
const keys = collectKeysFromRoots(roots, localKey);
|
|
@@ -5278,22 +5602,98 @@ var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
|
|
|
5278
5602
|
}
|
|
5279
5603
|
const fkColumn = relation.target.columns[relation.foreignKey];
|
|
5280
5604
|
if (!fkColumn) return /* @__PURE__ */ new Map();
|
|
5281
|
-
const
|
|
5282
|
-
|
|
5605
|
+
const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
|
|
5606
|
+
const targetPrimaryKey = findPrimaryKey(relation.target);
|
|
5607
|
+
const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
|
|
5608
|
+
if (!selectedColumns.includes(targetPrimaryKey)) {
|
|
5609
|
+
selectedColumns.push(targetPrimaryKey);
|
|
5610
|
+
}
|
|
5611
|
+
const queryColumns = new Set(selectedColumns);
|
|
5612
|
+
queryColumns.add(relation.foreignKey);
|
|
5613
|
+
const selection = buildColumnSelection(
|
|
5614
|
+
relation.target,
|
|
5615
|
+
Array.from(queryColumns),
|
|
5616
|
+
(column) => `Column '${column}' not found on relation '${relationName}'`
|
|
5617
|
+
);
|
|
5618
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
|
|
5619
|
+
const grouped = groupRowsByUnique(rows, relation.foreignKey);
|
|
5620
|
+
if (!requestedColumns) return grouped;
|
|
5621
|
+
const visibleColumns = new Set(selectedColumns);
|
|
5622
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
5623
|
+
for (const [key, row] of grouped.entries()) {
|
|
5624
|
+
filtered.set(key, filterRow(row, visibleColumns));
|
|
5625
|
+
}
|
|
5626
|
+
return filtered;
|
|
5283
5627
|
};
|
|
5284
|
-
var loadBelongsToRelation = async (ctx, rootTable,
|
|
5628
|
+
var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
|
|
5285
5629
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
5286
|
-
const
|
|
5630
|
+
const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
|
|
5631
|
+
let foreignKeys = getForeignKeys();
|
|
5632
|
+
if (!foreignKeys.size) {
|
|
5633
|
+
const pkName = findPrimaryKey(rootTable);
|
|
5634
|
+
const pkColumn2 = rootTable.columns[pkName];
|
|
5635
|
+
const fkColumn = rootTable.columns[relation.foreignKey];
|
|
5636
|
+
if (pkColumn2 && fkColumn) {
|
|
5637
|
+
const missingKeys = /* @__PURE__ */ new Set();
|
|
5638
|
+
const entityByPk = /* @__PURE__ */ new Map();
|
|
5639
|
+
for (const tracked of roots) {
|
|
5640
|
+
const entity = tracked.entity;
|
|
5641
|
+
const pkValue = entity[pkName];
|
|
5642
|
+
if (pkValue === void 0 || pkValue === null) continue;
|
|
5643
|
+
const fkValue = entity[relation.foreignKey];
|
|
5644
|
+
if (fkValue === void 0 || fkValue === null) {
|
|
5645
|
+
missingKeys.add(pkValue);
|
|
5646
|
+
entityByPk.set(pkValue, entity);
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
if (missingKeys.size) {
|
|
5650
|
+
const selection2 = buildColumnSelection(
|
|
5651
|
+
rootTable,
|
|
5652
|
+
[pkName, relation.foreignKey],
|
|
5653
|
+
(column) => `Column '${column}' not found on table '${rootTable.name}'`
|
|
5654
|
+
);
|
|
5655
|
+
const keyRows = await fetchRowsForKeys(ctx, rootTable, pkColumn2, missingKeys, selection2);
|
|
5656
|
+
for (const row of keyRows) {
|
|
5657
|
+
const pkValue = row[pkName];
|
|
5658
|
+
if (pkValue === void 0 || pkValue === null) continue;
|
|
5659
|
+
const entity = entityByPk.get(pkValue);
|
|
5660
|
+
if (!entity) continue;
|
|
5661
|
+
const fkValue = row[relation.foreignKey];
|
|
5662
|
+
if (fkValue !== void 0 && fkValue !== null) {
|
|
5663
|
+
entity[relation.foreignKey] = fkValue;
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
foreignKeys = getForeignKeys();
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5287
5670
|
if (!foreignKeys.size) {
|
|
5288
5671
|
return /* @__PURE__ */ new Map();
|
|
5289
5672
|
}
|
|
5290
5673
|
const targetKey = relation.localKey || findPrimaryKey(relation.target);
|
|
5291
5674
|
const pkColumn = relation.target.columns[targetKey];
|
|
5292
5675
|
if (!pkColumn) return /* @__PURE__ */ new Map();
|
|
5293
|
-
const
|
|
5294
|
-
|
|
5676
|
+
const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
|
|
5677
|
+
const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
|
|
5678
|
+
if (!selectedColumns.includes(targetKey)) {
|
|
5679
|
+
selectedColumns.push(targetKey);
|
|
5680
|
+
}
|
|
5681
|
+
const selection = buildColumnSelection(
|
|
5682
|
+
relation.target,
|
|
5683
|
+
selectedColumns,
|
|
5684
|
+
(column) => `Column '${column}' not found on relation '${relationName}'`
|
|
5685
|
+
);
|
|
5686
|
+
const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys, selection, options?.filter);
|
|
5687
|
+
const grouped = groupRowsByUnique(rows, targetKey);
|
|
5688
|
+
if (!requestedColumns) return grouped;
|
|
5689
|
+
const visibleColumns = new Set(selectedColumns);
|
|
5690
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
5691
|
+
for (const [key, row] of grouped.entries()) {
|
|
5692
|
+
filtered.set(key, filterRow(row, visibleColumns));
|
|
5693
|
+
}
|
|
5694
|
+
return filtered;
|
|
5295
5695
|
};
|
|
5296
|
-
var loadBelongsToManyRelation = async (ctx, rootTable,
|
|
5696
|
+
var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
|
|
5297
5697
|
const rootKey = relation.localKey || findPrimaryKey(rootTable);
|
|
5298
5698
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
5299
5699
|
const rootIds = collectKeysFromRoots(roots, rootKey);
|
|
@@ -5302,9 +5702,29 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
5302
5702
|
}
|
|
5303
5703
|
const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
|
|
5304
5704
|
if (!pivotColumn) return /* @__PURE__ */ new Map();
|
|
5305
|
-
const
|
|
5705
|
+
const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options.pivot.columns] : void 0;
|
|
5706
|
+
const useIncludeDefaults = options !== void 0;
|
|
5707
|
+
let pivotSelectedColumns;
|
|
5708
|
+
if (pivotColumnsRequested) {
|
|
5709
|
+
pivotSelectedColumns = [...pivotColumnsRequested];
|
|
5710
|
+
} else if (useIncludeDefaults) {
|
|
5711
|
+
const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
|
|
5712
|
+
pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
|
|
5713
|
+
} else {
|
|
5714
|
+
pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
|
|
5715
|
+
}
|
|
5716
|
+
const pivotQueryColumns = new Set(pivotSelectedColumns);
|
|
5717
|
+
pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
|
|
5718
|
+
pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
|
|
5719
|
+
const pivotSelection = buildColumnSelection(
|
|
5720
|
+
relation.pivotTable,
|
|
5721
|
+
Array.from(pivotQueryColumns),
|
|
5722
|
+
(column) => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
|
|
5723
|
+
);
|
|
5724
|
+
const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
|
|
5306
5725
|
const rootLookup = /* @__PURE__ */ new Map();
|
|
5307
5726
|
const targetIds = /* @__PURE__ */ new Set();
|
|
5727
|
+
const pivotVisibleColumns = new Set(pivotSelectedColumns);
|
|
5308
5728
|
for (const pivot of pivotRows) {
|
|
5309
5729
|
const rootValue = pivot[relation.pivotForeignKeyToRoot];
|
|
5310
5730
|
const targetValue = pivot[relation.pivotForeignKeyToTarget];
|
|
@@ -5314,7 +5734,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
5314
5734
|
const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
|
|
5315
5735
|
bucket.push({
|
|
5316
5736
|
targetId: targetValue,
|
|
5317
|
-
pivot:
|
|
5737
|
+
pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
|
|
5318
5738
|
});
|
|
5319
5739
|
rootLookup.set(toKey6(rootValue), bucket);
|
|
5320
5740
|
targetIds.add(targetValue);
|
|
@@ -5325,8 +5745,19 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
5325
5745
|
const targetKey = relation.targetKey || findPrimaryKey(relation.target);
|
|
5326
5746
|
const targetPkColumn = relation.target.columns[targetKey];
|
|
5327
5747
|
if (!targetPkColumn) return /* @__PURE__ */ new Map();
|
|
5328
|
-
const
|
|
5748
|
+
const targetRequestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
|
|
5749
|
+
const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
|
|
5750
|
+
if (!targetSelectedColumns.includes(targetKey)) {
|
|
5751
|
+
targetSelectedColumns.push(targetKey);
|
|
5752
|
+
}
|
|
5753
|
+
const targetSelection = buildColumnSelection(
|
|
5754
|
+
relation.target,
|
|
5755
|
+
targetSelectedColumns,
|
|
5756
|
+
(column) => `Column '${column}' not found on relation '${relationName}'`
|
|
5757
|
+
);
|
|
5758
|
+
const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds, targetSelection, options?.filter);
|
|
5329
5759
|
const targetMap = groupRowsByUnique(targetRows, targetKey);
|
|
5760
|
+
const targetVisibleColumns = new Set(targetSelectedColumns);
|
|
5330
5761
|
const result = /* @__PURE__ */ new Map();
|
|
5331
5762
|
for (const [rootId, entries] of rootLookup.entries()) {
|
|
5332
5763
|
const bucket = [];
|
|
@@ -5334,7 +5765,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
|
|
|
5334
5765
|
const targetRow = targetMap.get(toKey6(entry.targetId));
|
|
5335
5766
|
if (!targetRow) continue;
|
|
5336
5767
|
bucket.push({
|
|
5337
|
-
...targetRow,
|
|
5768
|
+
...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
|
|
5338
5769
|
_pivot: entry.pivot
|
|
5339
5770
|
});
|
|
5340
5771
|
}
|
|
@@ -5364,12 +5795,13 @@ var relationLoaderCache = (meta, relationName, factory) => {
|
|
|
5364
5795
|
}
|
|
5365
5796
|
return promise;
|
|
5366
5797
|
};
|
|
5367
|
-
var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
|
|
5798
|
+
var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
|
|
5368
5799
|
const target = { ...row };
|
|
5369
5800
|
const meta = {
|
|
5370
5801
|
ctx,
|
|
5371
5802
|
table,
|
|
5372
5803
|
lazyRelations: [...lazyRelations],
|
|
5804
|
+
lazyRelationOptions: new Map(lazyRelationOptions),
|
|
5373
5805
|
relationCache: /* @__PURE__ */ new Map(),
|
|
5374
5806
|
relationHydration: /* @__PURE__ */ new Map(),
|
|
5375
5807
|
relationWrappers: /* @__PURE__ */ new Map()
|
|
@@ -5410,14 +5842,14 @@ var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
|
|
|
5410
5842
|
populateHydrationCache(proxy, row, meta);
|
|
5411
5843
|
return proxy;
|
|
5412
5844
|
};
|
|
5413
|
-
var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
|
|
5845
|
+
var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
|
|
5414
5846
|
const pkName = findPrimaryKey(table);
|
|
5415
5847
|
const pkValue = row[pkName];
|
|
5416
5848
|
if (pkValue !== void 0 && pkValue !== null) {
|
|
5417
5849
|
const tracked = ctx.getEntity(table, pkValue);
|
|
5418
5850
|
if (tracked) return tracked;
|
|
5419
5851
|
}
|
|
5420
|
-
const entity = createEntityProxy(ctx, table, row, lazyRelations);
|
|
5852
|
+
const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
|
|
5421
5853
|
if (pkValue !== void 0 && pkValue !== null) {
|
|
5422
5854
|
ctx.trackManaged(table, pkValue, entity);
|
|
5423
5855
|
} else {
|
|
@@ -5467,6 +5899,58 @@ var populateHydrationCache = (entity, row, meta) => {
|
|
|
5467
5899
|
}
|
|
5468
5900
|
}
|
|
5469
5901
|
};
|
|
5902
|
+
var proxifyRelationWrapper = (wrapper) => {
|
|
5903
|
+
return new Proxy(wrapper, {
|
|
5904
|
+
get(target, prop, receiver) {
|
|
5905
|
+
if (typeof prop === "symbol") {
|
|
5906
|
+
return Reflect.get(target, prop, receiver);
|
|
5907
|
+
}
|
|
5908
|
+
if (prop in target) {
|
|
5909
|
+
return Reflect.get(target, prop, receiver);
|
|
5910
|
+
}
|
|
5911
|
+
const getItems = target.getItems;
|
|
5912
|
+
if (typeof getItems === "function") {
|
|
5913
|
+
const items = getItems.call(target);
|
|
5914
|
+
if (items && prop in items) {
|
|
5915
|
+
const propName = prop;
|
|
5916
|
+
const value = items[propName];
|
|
5917
|
+
return typeof value === "function" ? value.bind(items) : value;
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
const getRef = target.get;
|
|
5921
|
+
if (typeof getRef === "function") {
|
|
5922
|
+
const current = getRef.call(target);
|
|
5923
|
+
if (current && prop in current) {
|
|
5924
|
+
const propName = prop;
|
|
5925
|
+
const value = current[propName];
|
|
5926
|
+
return typeof value === "function" ? value.bind(current) : value;
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
return void 0;
|
|
5930
|
+
},
|
|
5931
|
+
set(target, prop, value, receiver) {
|
|
5932
|
+
if (typeof prop === "symbol") {
|
|
5933
|
+
return Reflect.set(target, prop, value, receiver);
|
|
5934
|
+
}
|
|
5935
|
+
if (prop in target) {
|
|
5936
|
+
return Reflect.set(target, prop, value, receiver);
|
|
5937
|
+
}
|
|
5938
|
+
const getRef = target.get;
|
|
5939
|
+
if (typeof getRef === "function") {
|
|
5940
|
+
const current = getRef.call(target);
|
|
5941
|
+
if (current && typeof current === "object") {
|
|
5942
|
+
return Reflect.set(current, prop, value);
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
const getItems = target.getItems;
|
|
5946
|
+
if (typeof getItems === "function") {
|
|
5947
|
+
const items = getItems.call(target);
|
|
5948
|
+
return Reflect.set(items, prop, value);
|
|
5949
|
+
}
|
|
5950
|
+
return Reflect.set(target, prop, value, receiver);
|
|
5951
|
+
}
|
|
5952
|
+
});
|
|
5953
|
+
};
|
|
5470
5954
|
var getRelationWrapper = (meta, relationName, owner) => {
|
|
5471
5955
|
if (meta.relationWrappers.has(relationName)) {
|
|
5472
5956
|
return meta.relationWrappers.get(relationName);
|
|
@@ -5474,12 +5958,13 @@ var getRelationWrapper = (meta, relationName, owner) => {
|
|
|
5474
5958
|
const relation = meta.table.relations[relationName];
|
|
5475
5959
|
if (!relation) return void 0;
|
|
5476
5960
|
const wrapper = instantiateWrapper(meta, relationName, relation, owner);
|
|
5477
|
-
if (wrapper)
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
return
|
|
5961
|
+
if (!wrapper) return void 0;
|
|
5962
|
+
const proxied = proxifyRelationWrapper(wrapper);
|
|
5963
|
+
meta.relationWrappers.set(relationName, proxied);
|
|
5964
|
+
return proxied;
|
|
5481
5965
|
};
|
|
5482
5966
|
var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
5967
|
+
const lazyOptions = meta.lazyRelationOptions.get(relationName);
|
|
5483
5968
|
switch (relation.type) {
|
|
5484
5969
|
case RelationKinds.HasOne: {
|
|
5485
5970
|
const hasOne2 = relation;
|
|
@@ -5487,7 +5972,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
|
5487
5972
|
const loader = () => relationLoaderCache(
|
|
5488
5973
|
meta,
|
|
5489
5974
|
relationName,
|
|
5490
|
-
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
|
|
5975
|
+
() => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
|
|
5491
5976
|
);
|
|
5492
5977
|
return new DefaultHasOneReference(
|
|
5493
5978
|
meta.ctx,
|
|
@@ -5507,7 +5992,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
|
5507
5992
|
const loader = () => relationLoaderCache(
|
|
5508
5993
|
meta,
|
|
5509
5994
|
relationName,
|
|
5510
|
-
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
|
|
5995
|
+
() => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
|
|
5511
5996
|
);
|
|
5512
5997
|
return new DefaultHasManyCollection(
|
|
5513
5998
|
meta.ctx,
|
|
@@ -5527,7 +6012,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
|
5527
6012
|
const loader = () => relationLoaderCache(
|
|
5528
6013
|
meta,
|
|
5529
6014
|
relationName,
|
|
5530
|
-
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
|
|
6015
|
+
() => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
|
|
5531
6016
|
);
|
|
5532
6017
|
return new DefaultBelongsToReference(
|
|
5533
6018
|
meta.ctx,
|
|
@@ -5547,7 +6032,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
|
|
|
5547
6032
|
const loader = () => relationLoaderCache(
|
|
5548
6033
|
meta,
|
|
5549
6034
|
relationName,
|
|
5550
|
-
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
|
|
6035
|
+
() => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
|
|
5551
6036
|
);
|
|
5552
6037
|
return new DefaultManyToManyCollection(
|
|
5553
6038
|
meta.ctx,
|
|
@@ -5586,11 +6071,17 @@ var executeWithContexts = async (execCtx, entityCtx, qb) => {
|
|
|
5586
6071
|
const compiled = execCtx.dialect.compileSelect(ast);
|
|
5587
6072
|
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
5588
6073
|
const rows = flattenResults(executed);
|
|
6074
|
+
const lazyRelations = qb.getLazyRelations();
|
|
6075
|
+
const lazyRelationOptions = qb.getLazyRelationOptions();
|
|
5589
6076
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
5590
|
-
|
|
6077
|
+
const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
6078
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
6079
|
+
return proxies;
|
|
5591
6080
|
}
|
|
5592
6081
|
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
5593
|
-
|
|
6082
|
+
const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
|
|
6083
|
+
await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
|
|
6084
|
+
return entities;
|
|
5594
6085
|
};
|
|
5595
6086
|
async function executeHydrated(session, qb) {
|
|
5596
6087
|
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
@@ -5602,6 +6093,52 @@ async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
|
|
|
5602
6093
|
}
|
|
5603
6094
|
return executeWithContexts(execCtx, entityCtx, qb);
|
|
5604
6095
|
}
|
|
6096
|
+
var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOptions) => {
|
|
6097
|
+
if (!lazyRelations.length) return;
|
|
6098
|
+
const tracked = ctx.getEntitiesForTable(table);
|
|
6099
|
+
if (!tracked.length) return;
|
|
6100
|
+
const meta = getEntityMeta(tracked[0].entity);
|
|
6101
|
+
if (!meta) return;
|
|
6102
|
+
for (const relationName of lazyRelations) {
|
|
6103
|
+
const relation = table.relations[relationName];
|
|
6104
|
+
if (!relation) continue;
|
|
6105
|
+
const key = relationName;
|
|
6106
|
+
const options = lazyRelationOptions.get(key);
|
|
6107
|
+
if (!options) {
|
|
6108
|
+
continue;
|
|
6109
|
+
}
|
|
6110
|
+
switch (relation.type) {
|
|
6111
|
+
case RelationKinds.HasOne:
|
|
6112
|
+
await relationLoaderCache(
|
|
6113
|
+
meta,
|
|
6114
|
+
key,
|
|
6115
|
+
() => loadHasOneRelation(ctx, table, key, relation, options)
|
|
6116
|
+
);
|
|
6117
|
+
break;
|
|
6118
|
+
case RelationKinds.HasMany:
|
|
6119
|
+
await relationLoaderCache(
|
|
6120
|
+
meta,
|
|
6121
|
+
key,
|
|
6122
|
+
() => loadHasManyRelation(ctx, table, key, relation, options)
|
|
6123
|
+
);
|
|
6124
|
+
break;
|
|
6125
|
+
case RelationKinds.BelongsTo:
|
|
6126
|
+
await relationLoaderCache(
|
|
6127
|
+
meta,
|
|
6128
|
+
key,
|
|
6129
|
+
() => loadBelongsToRelation(ctx, table, key, relation, options)
|
|
6130
|
+
);
|
|
6131
|
+
break;
|
|
6132
|
+
case RelationKinds.BelongsToMany:
|
|
6133
|
+
await relationLoaderCache(
|
|
6134
|
+
meta,
|
|
6135
|
+
key,
|
|
6136
|
+
() => loadBelongsToManyRelation(ctx, table, key, relation, options)
|
|
6137
|
+
);
|
|
6138
|
+
break;
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
5605
6142
|
|
|
5606
6143
|
// src/query-builder/query-resolution.ts
|
|
5607
6144
|
function resolveSelectQuery(query) {
|
|
@@ -5618,7 +6155,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5618
6155
|
* @param hydration - Optional hydration manager
|
|
5619
6156
|
* @param dependencies - Optional query builder dependencies
|
|
5620
6157
|
*/
|
|
5621
|
-
constructor(table, state, hydration, dependencies, lazyRelations) {
|
|
6158
|
+
constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
|
|
5622
6159
|
const deps = resolveSelectQueryBuilderDependencies(dependencies);
|
|
5623
6160
|
this.env = { table, deps };
|
|
5624
6161
|
const initialState = state ?? deps.createState(table);
|
|
@@ -5628,6 +6165,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5628
6165
|
hydration: initialHydration
|
|
5629
6166
|
};
|
|
5630
6167
|
this.lazyRelations = new Set(lazyRelations ?? []);
|
|
6168
|
+
this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
|
|
5631
6169
|
this.columnSelector = deps.createColumnSelector(this.env);
|
|
5632
6170
|
this.relationManager = deps.createRelationManager(this.env);
|
|
5633
6171
|
}
|
|
@@ -5637,8 +6175,15 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5637
6175
|
* @param lazyRelations - Updated lazy relations set
|
|
5638
6176
|
* @returns New SelectQueryBuilder instance
|
|
5639
6177
|
*/
|
|
5640
|
-
clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
|
|
5641
|
-
return new _SelectQueryBuilder(
|
|
6178
|
+
clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
|
|
6179
|
+
return new _SelectQueryBuilder(
|
|
6180
|
+
this.env.table,
|
|
6181
|
+
context.state,
|
|
6182
|
+
context.hydration,
|
|
6183
|
+
this.env.deps,
|
|
6184
|
+
lazyRelations,
|
|
6185
|
+
lazyRelationOptions
|
|
6186
|
+
);
|
|
5642
6187
|
}
|
|
5643
6188
|
/**
|
|
5644
6189
|
* Applies an alias to the root FROM table.
|
|
@@ -5885,36 +6430,40 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5885
6430
|
/**
|
|
5886
6431
|
* Includes a relation lazily in the query results
|
|
5887
6432
|
* @param relationName - Name of the relation to include lazily
|
|
6433
|
+
* @param options - Optional include options for lazy loading
|
|
5888
6434
|
* @returns New query builder instance with lazy relation inclusion
|
|
5889
6435
|
*/
|
|
5890
|
-
includeLazy(relationName) {
|
|
5891
|
-
|
|
5892
|
-
nextLazy.add(relationName);
|
|
5893
|
-
return this.clone(this.context, nextLazy);
|
|
5894
|
-
}
|
|
5895
|
-
/**
|
|
5896
|
-
* Selects columns for a related table in a single hop.
|
|
5897
|
-
*/
|
|
5898
|
-
selectRelationColumns(relationName, ...cols) {
|
|
6436
|
+
includeLazy(relationName, options) {
|
|
6437
|
+
let nextContext = this.context;
|
|
5899
6438
|
const relation = this.env.table.relations[relationName];
|
|
5900
|
-
if (
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
)
|
|
6439
|
+
if (relation?.type === RelationKinds.BelongsTo) {
|
|
6440
|
+
const foreignKey = relation.foreignKey;
|
|
6441
|
+
const fkColumn = this.env.table.columns[foreignKey];
|
|
6442
|
+
if (fkColumn) {
|
|
6443
|
+
const hasAlias2 = nextContext.state.ast.columns.some((col2) => {
|
|
6444
|
+
const node = col2;
|
|
6445
|
+
return (node.alias ?? node.name) === foreignKey;
|
|
6446
|
+
});
|
|
6447
|
+
if (!hasAlias2) {
|
|
6448
|
+
nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
|
|
6449
|
+
}
|
|
5909
6450
|
}
|
|
5910
6451
|
}
|
|
5911
|
-
|
|
6452
|
+
const nextLazy = new Set(this.lazyRelations);
|
|
6453
|
+
nextLazy.add(relationName);
|
|
6454
|
+
const nextOptions = new Map(this.lazyRelationOptions);
|
|
6455
|
+
if (options) {
|
|
6456
|
+
nextOptions.set(relationName, options);
|
|
6457
|
+
} else {
|
|
6458
|
+
nextOptions.delete(relationName);
|
|
6459
|
+
}
|
|
6460
|
+
return this.clone(nextContext, nextLazy, nextOptions);
|
|
5912
6461
|
}
|
|
5913
6462
|
/**
|
|
5914
|
-
* Convenience alias for
|
|
6463
|
+
* Convenience alias for including only specific columns from a relation.
|
|
5915
6464
|
*/
|
|
5916
6465
|
includePick(relationName, cols) {
|
|
5917
|
-
return this.
|
|
6466
|
+
return this.include(relationName, { columns: cols });
|
|
5918
6467
|
}
|
|
5919
6468
|
/**
|
|
5920
6469
|
* Selects columns for the root table and relations from an array of entries
|
|
@@ -5927,7 +6476,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5927
6476
|
if (entry.type === "root") {
|
|
5928
6477
|
currBuilder = currBuilder.select(...entry.columns);
|
|
5929
6478
|
} else {
|
|
5930
|
-
currBuilder = currBuilder.
|
|
6479
|
+
currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns });
|
|
5931
6480
|
}
|
|
5932
6481
|
}
|
|
5933
6482
|
return currBuilder;
|
|
@@ -5939,6 +6488,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5939
6488
|
getLazyRelations() {
|
|
5940
6489
|
return Array.from(this.lazyRelations);
|
|
5941
6490
|
}
|
|
6491
|
+
/**
|
|
6492
|
+
* Gets lazy relation include options
|
|
6493
|
+
* @returns Map of relation names to include options
|
|
6494
|
+
*/
|
|
6495
|
+
getLazyRelationOptions() {
|
|
6496
|
+
return new Map(this.lazyRelationOptions);
|
|
6497
|
+
}
|
|
5942
6498
|
/**
|
|
5943
6499
|
* Gets the table definition for this query builder
|
|
5944
6500
|
* @returns Table definition
|
|
@@ -11798,6 +12354,7 @@ function createPooledExecutorFactory(opts) {
|
|
|
11798
12354
|
registerExpressionDispatcher,
|
|
11799
12355
|
registerOperandDispatcher,
|
|
11800
12356
|
registerSchemaIntrospector,
|
|
12357
|
+
relationLoaderCache,
|
|
11801
12358
|
renderColumnDefinition,
|
|
11802
12359
|
renderTypeWithArgs,
|
|
11803
12360
|
repeat,
|