linkgress-orm 0.4.2 → 0.4.3
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/dist/entity/db-context.d.ts +2 -0
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js.map +1 -1
- package/dist/query/collection-strategy.interface.d.ts +9 -1
- package/dist/query/collection-strategy.interface.d.ts.map +1 -1
- package/dist/query/query-builder.d.ts +16 -1
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +75 -8
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/strategies/cte-collection-strategy.d.ts +5 -0
- package/dist/query/strategies/cte-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/cte-collection-strategy.js +71 -17
- package/dist/query/strategies/cte-collection-strategy.js.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.js +37 -7
- package/dist/query/strategies/lateral-collection-strategy.js.map +1 -1
- package/dist/query/strategies/temptable-collection-strategy.d.ts +5 -0
- package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/temptable-collection-strategy.js +24 -0
- package/dist/query/strategies/temptable-collection-strategy.js.map +1 -1
- package/dist/query/subquery.d.ts +7 -3
- package/dist/query/subquery.d.ts.map +1 -1
- package/dist/query/subquery.js +8 -4
- package/dist/query/subquery.js.map +1 -1
- package/package.json +1 -1
|
@@ -1757,6 +1757,8 @@ class SelectQueryBuilder {
|
|
|
1757
1757
|
return "'{}'";
|
|
1758
1758
|
case 'count':
|
|
1759
1759
|
return '0';
|
|
1760
|
+
case 'exists':
|
|
1761
|
+
return 'false';
|
|
1760
1762
|
case 'min':
|
|
1761
1763
|
case 'max':
|
|
1762
1764
|
case 'sum':
|
|
@@ -1772,7 +1774,11 @@ class SelectQueryBuilder {
|
|
|
1772
1774
|
const builderAny = builder;
|
|
1773
1775
|
if (builderAny.aggregationType) {
|
|
1774
1776
|
// Scalar aggregation
|
|
1775
|
-
|
|
1777
|
+
if (builderAny.aggregationType === 'COUNT')
|
|
1778
|
+
return 0;
|
|
1779
|
+
if (builderAny.aggregationType === 'EXISTS')
|
|
1780
|
+
return false;
|
|
1781
|
+
return null;
|
|
1776
1782
|
}
|
|
1777
1783
|
else if (builderAny.flattenResultType) {
|
|
1778
1784
|
// Array aggregation
|
|
@@ -3450,6 +3456,9 @@ ${joinClauses.join('\n')}`;
|
|
|
3450
3456
|
if (aggregationType === 'COUNT') {
|
|
3451
3457
|
selectParts.push(`COALESCE("${cteName}".data, 0) as "${name}"`);
|
|
3452
3458
|
}
|
|
3459
|
+
else if (aggregationType === 'EXISTS') {
|
|
3460
|
+
selectParts.push(`COALESCE("${cteName}".data, false) as "${name}"`);
|
|
3461
|
+
}
|
|
3453
3462
|
else {
|
|
3454
3463
|
// For MAX/MIN/SUM, keep NULL as-is
|
|
3455
3464
|
selectParts.push(`"${cteName}".data as "${name}"`);
|
|
@@ -4735,6 +4744,8 @@ class CollectionQueryBuilder {
|
|
|
4735
4744
|
this.orderByFields = [];
|
|
4736
4745
|
this.isMarkedAsList = false;
|
|
4737
4746
|
this.isDistinct = false;
|
|
4747
|
+
// Navigation joins added by selectMany() - included in both CTE and LATERAL navigation joins
|
|
4748
|
+
this.selectManyJoins = [];
|
|
4738
4749
|
this.relationName = relationName;
|
|
4739
4750
|
this.targetTable = targetTable;
|
|
4740
4751
|
this.targetTableSchema = targetTableSchema;
|
|
@@ -4768,6 +4779,8 @@ class CollectionQueryBuilder {
|
|
|
4768
4779
|
newBuilder.orderByFields = this.orderByFields;
|
|
4769
4780
|
newBuilder.asName = this.asName;
|
|
4770
4781
|
newBuilder.isDistinct = this.isDistinct;
|
|
4782
|
+
newBuilder.selectManyJoins = this.selectManyJoins;
|
|
4783
|
+
newBuilder.foreignKeyTableAlias = this.foreignKeyTableAlias;
|
|
4771
4784
|
return newBuilder;
|
|
4772
4785
|
}
|
|
4773
4786
|
/**
|
|
@@ -4946,6 +4959,48 @@ class CollectionQueryBuilder {
|
|
|
4946
4959
|
this.aggregationType = 'COUNT';
|
|
4947
4960
|
return this;
|
|
4948
4961
|
}
|
|
4962
|
+
/**
|
|
4963
|
+
* Check if any items exist in the collection
|
|
4964
|
+
* Returns SqlFragment<boolean> for automatic type resolution in selectors
|
|
4965
|
+
*/
|
|
4966
|
+
exists() {
|
|
4967
|
+
this.aggregationType = 'EXISTS';
|
|
4968
|
+
return this;
|
|
4969
|
+
}
|
|
4970
|
+
/**
|
|
4971
|
+
* Flatten a nested collection through this collection.
|
|
4972
|
+
* Similar to LINQ's SelectMany - projects each item to a collection and flattens.
|
|
4973
|
+
*
|
|
4974
|
+
* Example: product.productPrices!.selectMany(pp => pp.capacityGroups!).exists()
|
|
4975
|
+
* SQL: SELECT EXISTS(SELECT 1 FROM capacity_groups JOIN product_prices ON ... WHERE ...)
|
|
4976
|
+
*/
|
|
4977
|
+
selectMany(selector) {
|
|
4978
|
+
const mockItem = this.createMockItem();
|
|
4979
|
+
const innerCollection = selector(mockItem);
|
|
4980
|
+
const innerAny = innerCollection;
|
|
4981
|
+
// Build a navigation join from inner target table → this (intermediate) table
|
|
4982
|
+
// e.g., product_price_capacity_groups.product_price_id → product_prices.id
|
|
4983
|
+
const navJoin = {
|
|
4984
|
+
alias: this.targetTable,
|
|
4985
|
+
targetTable: this.targetTable,
|
|
4986
|
+
foreignKeys: [innerAny.foreignKey], // FK on inner table pointing to intermediate
|
|
4987
|
+
matches: ['id'], // PK on intermediate table
|
|
4988
|
+
isMandatory: true, // INNER JOIN for flattening
|
|
4989
|
+
sourceAlias: innerAny.targetTable, // Source is the inner (target) table
|
|
4990
|
+
};
|
|
4991
|
+
// Create new builder targeting the inner table but with outer's FK for parent correlation
|
|
4992
|
+
const newBuilder = new CollectionQueryBuilder(this.relationName, // Keep outer relation name for CTE naming
|
|
4993
|
+
innerAny.targetTable, // Target is the inner collection's table
|
|
4994
|
+
this.foreignKey, // FK is the outer collection's FK (e.g., product_id)
|
|
4995
|
+
this.sourceTable, // Source is the outer collection's source (e.g., products)
|
|
4996
|
+
innerAny.targetTableSchema, this.schemaRegistry, this.navigationPath);
|
|
4997
|
+
// The FK column lives on the intermediate table, not the target table
|
|
4998
|
+
newBuilder.selectManyJoins = [navJoin];
|
|
4999
|
+
newBuilder.foreignKeyTableAlias = this.targetTable;
|
|
5000
|
+
// Carry over where condition from outer collection if any
|
|
5001
|
+
newBuilder.whereCond = this.whereCond;
|
|
5002
|
+
return newBuilder;
|
|
5003
|
+
}
|
|
4949
5004
|
/**
|
|
4950
5005
|
* Flatten result to number array (for single-column selections)
|
|
4951
5006
|
*/
|
|
@@ -5360,7 +5415,7 @@ class CollectionQueryBuilder {
|
|
|
5360
5415
|
// For scalar aggregations (count, min, max, sum), don't include nestedCollectionInfo
|
|
5361
5416
|
// because the result is a scalar value, not a structured object that needs transformation
|
|
5362
5417
|
const aggregationType = field.getAggregationType();
|
|
5363
|
-
const isScalarAggregation = aggregationType && ['COUNT', 'MIN', 'MAX', 'SUM'].includes(aggregationType);
|
|
5418
|
+
const isScalarAggregation = aggregationType && ['COUNT', 'MIN', 'MAX', 'SUM', 'EXISTS'].includes(aggregationType);
|
|
5364
5419
|
if (isScalarAggregation) {
|
|
5365
5420
|
// Scalar aggregation - just return the expression, no nested transformation needed
|
|
5366
5421
|
return {
|
|
@@ -5509,10 +5564,10 @@ class CollectionQueryBuilder {
|
|
|
5509
5564
|
let arrayField;
|
|
5510
5565
|
let defaultValue;
|
|
5511
5566
|
if (this.aggregationType) {
|
|
5512
|
-
// Scalar aggregations: count, min, max, sum
|
|
5567
|
+
// Scalar aggregations: count, min, max, sum, exists
|
|
5513
5568
|
aggregationType = this.aggregationType.toLowerCase();
|
|
5514
|
-
// For aggregations other than COUNT, determine which field to aggregate
|
|
5515
|
-
if (this.aggregationType !== 'COUNT' && this.selector) {
|
|
5569
|
+
// For aggregations other than COUNT and EXISTS, determine which field to aggregate
|
|
5570
|
+
if (this.aggregationType !== 'COUNT' && this.aggregationType !== 'EXISTS' && this.selector) {
|
|
5516
5571
|
const mockItem = this.createMockItem();
|
|
5517
5572
|
const selectedField = this.selector(mockItem);
|
|
5518
5573
|
if (typeof selectedField === 'object' && selectedField !== null && '__dbColumnName' in selectedField) {
|
|
@@ -5520,7 +5575,15 @@ class CollectionQueryBuilder {
|
|
|
5520
5575
|
}
|
|
5521
5576
|
}
|
|
5522
5577
|
// Set default value based on aggregation type
|
|
5523
|
-
|
|
5578
|
+
if (aggregationType === 'count') {
|
|
5579
|
+
defaultValue = '0';
|
|
5580
|
+
}
|
|
5581
|
+
else if (aggregationType === 'exists') {
|
|
5582
|
+
defaultValue = 'false';
|
|
5583
|
+
}
|
|
5584
|
+
else {
|
|
5585
|
+
defaultValue = 'null';
|
|
5586
|
+
}
|
|
5524
5587
|
}
|
|
5525
5588
|
else if (this.flattenResultType) {
|
|
5526
5589
|
// Array aggregation for toNumberList/toStringList
|
|
@@ -5550,12 +5613,16 @@ class CollectionQueryBuilder {
|
|
|
5550
5613
|
// oi.productPrice.product.resort.productIntegrationDefinitions
|
|
5551
5614
|
// The navigation path contains joins for productPrice, product, resort
|
|
5552
5615
|
// which must be included in the lateral subquery for correlation
|
|
5553
|
-
|
|
5616
|
+
// Include selectMany joins in both all and selector navigation joins
|
|
5617
|
+
// selectMany joins are structural (from flattening) and needed by both CTE and LATERAL
|
|
5618
|
+
const allNavigationJoins = [...this.navigationPath, ...this.selectManyJoins, ...navigationJoins];
|
|
5619
|
+
const allSelectorJoins = [...this.selectManyJoins, ...navigationJoins];
|
|
5554
5620
|
// Step 6: Build CollectionAggregationConfig object
|
|
5555
5621
|
const config = {
|
|
5556
5622
|
relationName: this.relationName,
|
|
5557
5623
|
targetTable: this.targetTable,
|
|
5558
5624
|
foreignKey: this.foreignKey,
|
|
5625
|
+
foreignKeyTableAlias: this.foreignKeyTableAlias,
|
|
5559
5626
|
sourceTable: this.sourceTable,
|
|
5560
5627
|
parentIds, // Pass parent IDs for temp table strategy
|
|
5561
5628
|
selectedFields: selectedFieldConfigs,
|
|
@@ -5575,7 +5642,7 @@ class CollectionQueryBuilder {
|
|
|
5575
5642
|
// Use the reserved counter for LATERAL strategy, otherwise increment as before
|
|
5576
5643
|
counter: reservedCounter !== undefined ? reservedCounter : context.cteCounter++,
|
|
5577
5644
|
navigationJoins: allNavigationJoins.length > 0 ? allNavigationJoins : undefined,
|
|
5578
|
-
selectorNavigationJoins:
|
|
5645
|
+
selectorNavigationJoins: allSelectorJoins.length > 0 ? allSelectorJoins : undefined,
|
|
5579
5646
|
};
|
|
5580
5647
|
// Step 6: Call the strategy
|
|
5581
5648
|
const result = strategy.buildAggregation(config, context, client);
|