1
+ import { SQLParser } from "./SQLParser" ;
2
+ import {
3
+ ConstituentCounts ,
4
+ SQLConstituentType ,
5
+ isQueryConstituent
6
+ } from "./types"
7
+
8
+
9
+ type QueryComplexityFormula = {
10
+ [ key in SQLConstituentType ] ?: IQueryConstituentComplexityFormula
11
+ }
12
+
13
+ interface IQueryConstituentComplexityFormula extends QueryComplexityFormula {
14
+ weight : number ;
15
+ diminishingReturnIntervals : Array < IDiminishingReturnInterval >
16
+ }
17
+
18
+ interface IDiminishingReturnInterval {
19
+ lowerBound : number ;
20
+ upperBound : number ;
21
+ penaltyFactor : number ;
22
+ }
23
+
24
+ const trivialComplexityIntervals = [
25
+ {
26
+ lowerBound : 0 ,
27
+ upperBound : 1 ,
28
+ penaltyFactor : 1
29
+ } ,
30
+ {
31
+ lowerBound : 2 ,
32
+ upperBound : Infinity ,
33
+ penaltyFactor : 0
34
+ }
35
+ ] ;
36
+
37
+ const column = {
38
+ weight : 1 ,
39
+ diminishingReturnIntervals : trivialComplexityIntervals
40
+ } ;
41
+ const aggregate = {
42
+ weight : 1.5 ,
43
+ diminishingReturnIntervals : [
44
+ {
45
+ lowerBound : 0 ,
46
+ upperBound : 1 ,
47
+ penaltyFactor : 1
48
+ } ,
49
+ {
50
+ lowerBound : 0 ,
51
+ upperBound : Infinity ,
52
+ penaltyFactor : 0
53
+ }
54
+ ]
55
+ } ;
56
+ const selectList = {
57
+ column,
58
+ aggregate,
59
+ } ;
60
+
61
+ const innerJoin = { } ;
62
+ const partialOuterJoin = { } ;
63
+ const fullOuterJoin = { } ;
64
+ const crossJoin = { } ;
65
+ const tableReference = {
66
+ innerJoin,
67
+ partialOuterJoin,
68
+ fullOuterJoin,
69
+ crossJoin
70
+ } ;
71
+
72
+ const whereClause = { } ;
73
+ const groupBy = { } ;
74
+ const havingClause = { } ;
75
+ const orderBy = { } ;
76
+
77
+ const queryComplexityFormula : QueryComplexityFormula = {
78
+ selectList : {
79
+ ...selectList ,
80
+ weight : 1 ,
81
+ diminishingReturnIntervals : trivialComplexityIntervals
82
+ } ,
83
+ // tableReference,
84
+ // whereClause,
85
+ // groupBy,
86
+ // havingClause,
87
+ // orderBy
88
+ } ;
89
+
90
+ interface IStepFunction {
91
+ ( n : number ) : Array < number > ;
92
+ } ;
93
+
94
+ export class SQLComplexity {
95
+ constructor ( private complexityFormula : QueryComplexityFormula , private sqlParser : SQLParser ) { }
96
+
97
+ public computeQueryComplexity ( query : string , constrainComplexity : boolean = false ) : number {
98
+ const queryAST = this . sqlParser . parseSQLToAST ( [ query ] ) [ 0 ] ;
99
+ const constituentCounts = this . sqlParser . retrieveConstituentCounts ( queryAST ) ;
100
+
101
+ const queryComplexity = this . calculateConstituentComplexity ( this . complexityFormula , constituentCounts as unknown as ConstituentCounts ) ;
102
+ const normalizedQueryComplexity = this . normalizeConstituentComplexity (
103
+ queryComplexity ,
104
+ this . complexityFormula ,
105
+ constrainComplexity ? constituentCounts : { } as ConstituentCounts
106
+ )
107
+
108
+ return normalizedQueryComplexity ;
109
+ }
110
+
111
+ private calculateConstituentComplexity ( complexityFormula : QueryComplexityFormula , constituentCounts : ConstituentCounts ) : number {
112
+ return Object . entries ( complexityFormula ) . reduce ( ( sum , [ queryConstituent , complexityClause ] ) => {
113
+ const { weight, diminishingReturnIntervals, ...nestedConstituents } = complexityClause ;
114
+
115
+ const stepFunction = this . constructStepFunction ( diminishingReturnIntervals ) ;
116
+
117
+ let localComplexityScore = 0 ;
118
+ if ( isQueryConstituent ( queryConstituent ) ) {
119
+ const n = constituentCounts [ queryConstituent ] ;
120
+ localComplexityScore = this . calculateLocalComplexityScore ( stepFunction ( n ) , weight ) ;
121
+ }
122
+
123
+ return sum + localComplexityScore + this . calculateConstituentComplexity ( nestedConstituents , constituentCounts ) ;
124
+ } , 0 )
125
+ }
126
+
127
+ private normalizeConstituentComplexity ( queryComplexityScore : number , complexityFormula : QueryComplexityFormula , constituentCounts : ConstituentCounts ) {
128
+ const maxComplexityScore = this . calculateMaxComplexityScore ( complexityFormula , constituentCounts ) ;
129
+ return queryComplexityScore / maxComplexityScore ;
130
+ }
131
+
132
+ private calculateMaxComplexityScore ( complexityFormula : QueryComplexityFormula , constituentCounts : ConstituentCounts ) : number {
133
+ const maxConstituentCounts = Object . keys ( constituentCounts ) . length ?
134
+ this . getMaxConstituentCounts ( complexityFormula ) :
135
+ this . getMaxConstituentCounts ( this . pruneConstituents ( complexityFormula , constituentCounts ) ) ;
136
+
137
+ return this . calculateConstituentComplexity ( complexityFormula , maxConstituentCounts ) ;
138
+ }
139
+
140
+ private pruneConstituents ( complexityFormula : QueryComplexityFormula , constituentCounts : ConstituentCounts ) : QueryComplexityFormula {
141
+ return Object . keys ( complexityFormula ) . reduce ( ( prunedComplexityFormula , queryConstituent ) => {
142
+ // requires custom typeguard as Object.-methods don't infer type from generics due to JS-compatibility
143
+ if ( isQueryConstituent ( queryConstituent ) ) {
144
+ const { weight, diminishingReturnIntervals, ...nestedConstituents } = complexityFormula [ queryConstituent ]
145
+ if ( constituentCounts [ queryConstituent ] > 0 ) {
146
+ prunedComplexityFormula [ queryConstituent ] = {
147
+ weight,
148
+ diminishingReturnIntervals,
149
+ ...this . pruneConstituents ( nestedConstituents , constituentCounts )
150
+ }
151
+ }
152
+ }
153
+ return prunedComplexityFormula ;
154
+ } , { } as QueryComplexityFormula ) ;
155
+ }
156
+
157
+ private getMaxConstituentCounts ( complexityFormula : QueryComplexityFormula ) : ConstituentCounts {
158
+ return Object . entries ( complexityFormula ) . map ( ( [ queryConstituent , complexityClause ] ) => {
159
+ const { weight, diminishingReturnIntervals, ...nestedConstituents } = complexityClause ;
160
+
161
+ const stepFunction = this . constructStepFunction ( diminishingReturnIntervals ) ;
162
+ const maxN = diminishingReturnIntervals [ diminishingReturnIntervals . length - 1 ] . lowerBound ;
163
+ const maxLocalComplexityScore = this . calculateLocalComplexityScore ( stepFunction ( maxN ) , weight ) ;
164
+
165
+ return maxLocalComplexityScore ;
166
+ } ) as unknown as ConstituentCounts ;
167
+ }
168
+
169
+ private calculateLocalComplexityScore ( penaltyFactors : Array < number > , weight : number ) {
170
+ return penaltyFactors . reduce ( ( sum , penaltyFactor ) => {
171
+ return sum + ( weight * penaltyFactor ) ;
172
+ } , 0 ) ;
173
+ }
174
+
175
+ private constructStepFunction ( intervals : Array < IDiminishingReturnInterval > ) : IStepFunction {
176
+ return ( n : number ) => {
177
+ return range ( n ) . map ( ( i ) => {
178
+ for ( const interval of intervals ) {
179
+ if ( i >= interval . lowerBound && i <= interval . upperBound ) {
180
+ return interval . penaltyFactor ;
181
+ }
182
+ }
183
+ } ) ;
184
+ }
185
+ }
186
+ }
187
+
188
+ const range = (
189
+ size : number ,
190
+ startAt : number = 0 ,
191
+ stepSize : number = 1
192
+ ) : ReadonlyArray < number > => Array . from (
193
+ {
194
+ length : ( size - startAt ) / stepSize + 1
195
+ } ,
196
+ ( _ , i ) => startAt + ( i * stepSize )
197
+ ) ;
0 commit comments