@@ -25,6 +25,7 @@ import {Dialect, DialectFieldList, getDialect} from '../dialect';
25
25
import { StandardSQLDialect } from '../dialect/standardsql/standardsql' ;
26
26
import {
27
27
AggregateFragment ,
28
+ AggregateFunctionType ,
28
29
CompiledQuery ,
29
30
DialectFragment ,
30
31
Expr ,
@@ -127,6 +128,21 @@ interface OutputPipelinedSQL {
127
128
pipelineSQL : string ;
128
129
}
129
130
131
+ // Track the times we might need a unique key
132
+ type UniqueKeyPossibleUse = AggregateFunctionType | 'generic_aggregate' ;
133
+
134
+ class UniqueKeyUse extends Set < UniqueKeyPossibleUse > {
135
+ add_use ( k : UniqueKeyPossibleUse | undefined ) {
136
+ if ( k !== undefined ) {
137
+ return this . add ( k ) ;
138
+ }
139
+ }
140
+
141
+ hasAsymetricFunctions ( ) : boolean {
142
+ return this . has ( 'sum' ) || this . has ( 'avg' ) || this . has ( 'count' ) ;
143
+ }
144
+ }
145
+
130
146
class StageWriter {
131
147
withs : string [ ] = [ ] ;
132
148
udfs : string [ ] = [ ] ;
@@ -307,8 +323,8 @@ class QueryField extends QueryNode {
307
323
this . fieldDef = fieldDef ;
308
324
}
309
325
310
- mayNeedUniqueKey ( ) : boolean {
311
- return false ;
326
+ uniqueKeyPossibleUse ( ) : UniqueKeyPossibleUse | undefined {
327
+ return undefined ;
312
328
}
313
329
314
330
getJoinableParent ( ) : QueryStruct {
@@ -750,16 +766,31 @@ class QueryField extends QueryNode {
750
766
) : string {
751
767
let func = 'COUNT(' ;
752
768
let thing = '1' ;
753
- const distinctKeySQL = this . generateDistinctKeyIfNecessary (
754
- resultSet ,
755
- context ,
756
- expr . structPath
757
- ) ;
758
- if ( distinctKeySQL ) {
769
+
770
+ let struct = context ;
771
+ if ( expr . structPath ) {
772
+ struct = this . parent . root ( ) . getStructByName ( expr . structPath ) ;
773
+ }
774
+ const joinName = struct . getJoinableParent ( ) . getIdentifier ( ) ;
775
+ const join = resultSet . root ( ) . joins . get ( joinName ) ;
776
+ if ( ! join ) {
777
+ throw new Error ( `Join ${ joinName } not found in result set` ) ;
778
+ }
779
+ if ( ! join . leafiest || join . makeUniqueKey ) {
759
780
func = 'COUNT(DISTINCT' ;
760
- thing = distinctKeySQL ;
781
+ thing = struct . getDistinctKey ( ) . generateExpression ( resultSet ) ;
761
782
}
762
783
784
+ // const distinctKeySQL = this.generateDistinctKeyIfNecessary(
785
+ // resultSet,
786
+ // context,
787
+ // expr.structPath
788
+ // );
789
+ // if (distinctKeySQL) {
790
+ // func = 'COUNT(DISTINCT';
791
+ // thing = distinctKeySQL;
792
+ // }
793
+
763
794
// find the structDef and return the path to the field...
764
795
if ( state . whereSQL ) {
765
796
return `${ func } CASE WHEN ${ state . whereSQL } THEN ${ thing } END)` ;
@@ -1149,13 +1180,17 @@ class QueryFieldDistinctKey extends QueryAtomicField {
1149
1180
const parentKey = this . parent . parent
1150
1181
?. getDistinctKey ( )
1151
1182
. generateExpression ( resultSet ) ;
1152
- return `CONCAT(${ parentKey } , 'x', ${ this . parent . dialect . sqlFieldReference (
1153
- this . parent . getIdentifier ( ) ,
1154
- '__row_id' ,
1155
- 'string' ,
1156
- true ,
1157
- false
1158
- ) } )`;
1183
+ return this . parent . dialect . concat (
1184
+ parentKey || '' , // shouldn't have to do this...
1185
+ "'x'" ,
1186
+ this . parent . dialect . sqlFieldReference (
1187
+ this . parent . getIdentifier ( ) ,
1188
+ '__row_id' ,
1189
+ 'string' ,
1190
+ true ,
1191
+ false
1192
+ )
1193
+ ) ;
1159
1194
} else {
1160
1195
// return this.parent.getIdentifier() + "." + "__distinct_key";
1161
1196
return this . parent . dialect . sqlFieldReference (
@@ -1499,7 +1534,7 @@ class FieldInstanceResult implements FieldInstance {
1499
1534
addStructToJoin (
1500
1535
qs : QueryStruct ,
1501
1536
query : QueryQuery ,
1502
- mayNeedUniqueKey : boolean ,
1537
+ uniqueKeyPossibleUse : UniqueKeyPossibleUse | undefined ,
1503
1538
joinStack : string [ ]
1504
1539
) : void {
1505
1540
const name = qs . getIdentifier ( ) ;
@@ -1509,9 +1544,9 @@ class FieldInstanceResult implements FieldInstance {
1509
1544
return ;
1510
1545
}
1511
1546
1512
- let join ;
1547
+ let join : JoinInstance | undefined ;
1513
1548
if ( ( join = this . root ( ) . joins . get ( name ) ) ) {
1514
- join . mayNeedUniqueKey ||= mayNeedUniqueKey ;
1549
+ join . uniqueKeyPossibleUses . add_use ( uniqueKeyPossibleUse ) ;
1515
1550
return ;
1516
1551
}
1517
1552
@@ -1520,7 +1555,7 @@ class FieldInstanceResult implements FieldInstance {
1520
1555
const parentStruct = qs . parent ?. getJoinableParent ( ) ;
1521
1556
if ( parentStruct ) {
1522
1557
// add dependant expressions first...
1523
- this . addStructToJoin ( parentStruct , query , false , joinStack ) ;
1558
+ this . addStructToJoin ( parentStruct , query , undefined , joinStack ) ;
1524
1559
parent = this . root ( ) . joins . get ( parentStruct . getIdentifier ( ) ) ;
1525
1560
}
1526
1561
@@ -1542,15 +1577,15 @@ class FieldInstanceResult implements FieldInstance {
1542
1577
join = new JoinInstance ( qs , name , parent ) ;
1543
1578
this . root ( ) . joins . set ( name , join ) ;
1544
1579
}
1545
- join . mayNeedUniqueKey ||= mayNeedUniqueKey ;
1580
+ join . uniqueKeyPossibleUses . add_use ( uniqueKeyPossibleUse ) ;
1546
1581
}
1547
1582
1548
1583
findJoins ( query : QueryQuery ) {
1549
1584
for ( const dim of this . fields ( ) ) {
1550
1585
this . addStructToJoin (
1551
1586
dim . f . getJoinableParent ( ) ,
1552
1587
query ,
1553
- dim . f . mayNeedUniqueKey ( ) ,
1588
+ dim . f . uniqueKeyPossibleUse ( ) ,
1554
1589
[ ]
1555
1590
) ;
1556
1591
}
@@ -1667,7 +1702,7 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
1667
1702
// look at all the fields again in the structs in the query
1668
1703
1669
1704
calculateSymmetricAggregates ( ) {
1670
- let leafiest ;
1705
+ let leafiest : string | undefined ;
1671
1706
for ( const [ name , join ] of this . joins ) {
1672
1707
// first join is by default the
1673
1708
const relationship = join . parentRelationship ( ) ;
@@ -1702,8 +1737,23 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
1702
1737
// Nested Unique keys are dependant on the primary key of the parent
1703
1738
// and the table.
1704
1739
for ( const [ _name , join ] of this . joins ) {
1705
- // don't need keys on leafiest
1706
- if ( ! join . leafiest && join . mayNeedUniqueKey ) {
1740
+ // in a one_to_many join we need a key to count there may be a failed
1741
+ // match in a left join.
1742
+ // users -> {
1743
+ // group_by: user_id
1744
+ // aggregate: order_count is orders.count()
1745
+ if ( join . leafiest ) {
1746
+ if (
1747
+ join . parent !== null &&
1748
+ join . uniqueKeyPossibleUses . has ( 'count' ) &&
1749
+ ! join . queryStruct . primaryKey ( )
1750
+ ) {
1751
+ join . makeUniqueKey = true ;
1752
+ }
1753
+ } else if (
1754
+ ! join . leafiest &&
1755
+ join . uniqueKeyPossibleUses . hasAsymetricFunctions ( )
1756
+ ) {
1707
1757
let j : JoinInstance | undefined = join ;
1708
1758
while ( j ) {
1709
1759
if ( ! j . queryStruct . primaryKey ( ) ) {
@@ -1721,7 +1771,7 @@ class FieldInstanceResultRoot extends FieldInstanceResult {
1721
1771
}
1722
1772
1723
1773
class JoinInstance {
1724
- mayNeedUniqueKey = false ;
1774
+ uniqueKeyPossibleUses : UniqueKeyUse = new UniqueKeyUse ( ) ;
1725
1775
makeUniqueKey = false ;
1726
1776
leafiest = false ;
1727
1777
joinFilterConditions ?: QueryFieldBoolean [ ] ;
@@ -2133,7 +2183,7 @@ class QueryQuery extends QueryField {
2133
2183
resultStruct : FieldInstanceResult ,
2134
2184
context : QueryStruct ,
2135
2185
path : string ,
2136
- mayNeedUniqueKey : boolean ,
2186
+ uniqueKeyPossibleUse : UniqueKeyPossibleUse | undefined ,
2137
2187
joinStack : string [ ]
2138
2188
) {
2139
2189
const node = context . getFieldByName ( path ) ;
@@ -2150,7 +2200,7 @@ class QueryQuery extends QueryField {
2150
2200
. addStructToJoin (
2151
2201
struct . getJoinableParent ( ) ,
2152
2202
this ,
2153
- mayNeedUniqueKey ,
2203
+ uniqueKeyPossibleUse ,
2154
2204
joinStack
2155
2205
) ;
2156
2206
}
@@ -2202,7 +2252,7 @@ class QueryQuery extends QueryField {
2202
2252
. addStructToJoin (
2203
2253
field . parent . getJoinableParent ( ) ,
2204
2254
this ,
2205
- false ,
2255
+ undefined ,
2206
2256
joinStack
2207
2257
) ;
2208
2258
// this.addDependantPath(resultStruct, field.parent, expr.path, false);
@@ -2261,12 +2311,17 @@ class QueryQuery extends QueryField {
2261
2311
resultStruct ,
2262
2312
context ,
2263
2313
expr . structPath ,
2264
- true ,
2314
+ expr . function ,
2265
2315
joinStack
2266
2316
) ;
2267
2317
} else {
2268
2318
// we are doing a sum in the root. It may need symetric aggregates
2269
- resultStruct . addStructToJoin ( context , this , true , joinStack ) ;
2319
+ resultStruct . addStructToJoin (
2320
+ context ,
2321
+ this ,
2322
+ expr . function ,
2323
+ joinStack
2324
+ ) ;
2270
2325
}
2271
2326
}
2272
2327
this . addDependantExpr ( resultStruct , context , expr . e , joinStack ) ;
@@ -2276,7 +2331,7 @@ class QueryQuery extends QueryField {
2276
2331
resultStruct ,
2277
2332
context ,
2278
2333
expr . structPath ,
2279
- true ,
2334
+ 'generic_aggregate' ,
2280
2335
joinStack
2281
2336
) ;
2282
2337
}
@@ -2408,7 +2463,7 @@ class QueryQuery extends QueryField {
2408
2463
prepare ( _stageWriter : StageWriter | undefined ) {
2409
2464
if ( ! this . prepared ) {
2410
2465
this . expandFields ( this . rootResult ) ;
2411
- this . rootResult . addStructToJoin ( this . parent , this , false , [ ] ) ;
2466
+ this . rootResult . addStructToJoin ( this . parent , this , undefined , [ ] ) ;
2412
2467
this . rootResult . findJoins ( this ) ;
2413
2468
this . rootResult . calculateSymmetricAggregates ( ) ;
2414
2469
this . prepared = true ;
0 commit comments