dsl-to-sql 1.0.0-fabric-1p-development.1

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.
Files changed (225) hide show
  1. package/.turbo/turbo-build.log +9 -0
  2. package/.turbo/turbo-check-types.log +4 -0
  3. package/.turbo/turbo-lint.log +126 -0
  4. package/README.md +45 -0
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +236 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/coverage/src/constants.ts.html +826 -0
  14. package/coverage/src/database_types.ts.html +136 -0
  15. package/coverage/src/epm-query-builder/EpmQueryBuilder.ts.html +166 -0
  16. package/coverage/src/epm-query-builder/base/BaseAdvancedAggregations.ts.html +568 -0
  17. package/coverage/src/epm-query-builder/base/BaseAnalyticalFunctions.ts.html +694 -0
  18. package/coverage/src/epm-query-builder/base/BaseCTEGenerator.ts.html +1459 -0
  19. package/coverage/src/epm-query-builder/base/BaseCountQueryBuilder.ts.html +400 -0
  20. package/coverage/src/epm-query-builder/base/BaseMeasureBuilder.ts.html +295 -0
  21. package/coverage/src/epm-query-builder/base/BaseOrderBuilder.ts.html +670 -0
  22. package/coverage/src/epm-query-builder/base/BasePaginationBuilder.ts.html +364 -0
  23. package/coverage/src/epm-query-builder/base/BaseQueryBuilder.ts.html +238 -0
  24. package/coverage/src/epm-query-builder/base/BaseRollupBuilder.ts.html +532 -0
  25. package/coverage/src/epm-query-builder/base/BaseSqlBuilder.ts.html +601 -0
  26. package/coverage/src/epm-query-builder/base/BaseSuperFilterBuilder.ts.html +1966 -0
  27. package/coverage/src/epm-query-builder/base/BaseUtilities.ts.html +1798 -0
  28. package/coverage/src/epm-query-builder/base/ColumnRefUtils.ts.html +211 -0
  29. package/coverage/src/epm-query-builder/base/RelationshipResolver.ts.html +706 -0
  30. package/coverage/src/epm-query-builder/base/SharedFilterBuilder.ts.html +1717 -0
  31. package/coverage/src/epm-query-builder/base/index.html +326 -0
  32. package/coverage/src/epm-query-builder/constants/Aggregations.ts.html +133 -0
  33. package/coverage/src/epm-query-builder/constants/Database.ts.html +103 -0
  34. package/coverage/src/epm-query-builder/constants/Source.ts.html +106 -0
  35. package/coverage/src/epm-query-builder/constants/index.html +146 -0
  36. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts.html +286 -0
  37. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts.html +280 -0
  38. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts.html +1924 -0
  39. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts.html +769 -0
  40. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts.html +643 -0
  41. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts.html +2644 -0
  42. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts.html +478 -0
  43. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts.html +1195 -0
  44. package/coverage/src/epm-query-builder/dialects/duckdb/index.html +221 -0
  45. package/coverage/src/epm-query-builder/errors/QueryBuilderErrors.ts.html +280 -0
  46. package/coverage/src/epm-query-builder/errors/index.html +116 -0
  47. package/coverage/src/epm-query-builder/index.html +116 -0
  48. package/coverage/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts.html +100 -0
  49. package/coverage/src/epm-query-builder/interfaces/index.html +131 -0
  50. package/coverage/src/epm-query-builder/interfaces/index.ts.html +88 -0
  51. package/coverage/src/epm-query-builder/utils/format.ts.html +151 -0
  52. package/coverage/src/epm-query-builder/utils/index.html +146 -0
  53. package/coverage/src/epm-query-builder/utils/sql.ts.html +247 -0
  54. package/coverage/src/epm-query-builder/utils/validation.ts.html +124 -0
  55. package/coverage/src/epm-query-builder/validation/QueryOptionsValidator.ts.html +631 -0
  56. package/coverage/src/epm-query-builder/validation/SqlQueryValidator.ts.html +475 -0
  57. package/coverage/src/epm-query-builder/validation/index.html +131 -0
  58. package/coverage/src/filters/FilterConditionBuilder.ts.html +427 -0
  59. package/coverage/src/filters/filter-types.ts.html +484 -0
  60. package/coverage/src/filters/index.html +131 -0
  61. package/coverage/src/index.html +176 -0
  62. package/coverage/src/index.ts.html +103 -0
  63. package/coverage/src/js-lib/JsToSqlParser.ts.html +736 -0
  64. package/coverage/src/js-lib/ParseContext.ts.html +532 -0
  65. package/coverage/src/js-lib/db/azuresql/AzureSqlCallExpressionVisitor.ts.html +196 -0
  66. package/coverage/src/js-lib/db/azuresql/index.html +116 -0
  67. package/coverage/src/js-lib/db/base/ArrayExpressionVisitor.ts.html +133 -0
  68. package/coverage/src/js-lib/db/base/AssignmentExpressionVisitor.ts.html +187 -0
  69. package/coverage/src/js-lib/db/base/BinaryExpressionVisitor.ts.html +223 -0
  70. package/coverage/src/js-lib/db/base/CallExpressionVisitor.ts.html +5479 -0
  71. package/coverage/src/js-lib/db/base/IdentifierVisitor.ts.html +283 -0
  72. package/coverage/src/js-lib/db/base/LiteralVisitor.ts.html +199 -0
  73. package/coverage/src/js-lib/db/base/MemberExpressionVisitor.ts.html +193 -0
  74. package/coverage/src/js-lib/db/base/ProgramVisitor.ts.html +139 -0
  75. package/coverage/src/js-lib/db/base/UnaryExpressionVisitor.ts.html +181 -0
  76. package/coverage/src/js-lib/db/base/VisitorInterface.ts.html +103 -0
  77. package/coverage/src/js-lib/db/base/index.html +251 -0
  78. package/coverage/src/js-lib/db/bigquery/BigQueryCallExpressionVisitor.ts.html +1747 -0
  79. package/coverage/src/js-lib/db/bigquery/index.html +116 -0
  80. package/coverage/src/js-lib/db/commonTransforms.ts.html +2074 -0
  81. package/coverage/src/js-lib/db/databricks/DatabricksCallExpressionVisitor.ts.html +1303 -0
  82. package/coverage/src/js-lib/db/databricks/index.html +116 -0
  83. package/coverage/src/js-lib/db/fabricsql/FabricSqlCallExpressionVisitor.ts.html +196 -0
  84. package/coverage/src/js-lib/db/fabricsql/index.html +116 -0
  85. package/coverage/src/js-lib/db/fabricwarehouse/FabricWarehouseCallExpressionVisitor.ts.html +292 -0
  86. package/coverage/src/js-lib/db/fabricwarehouse/index.html +116 -0
  87. package/coverage/src/js-lib/db/index.html +116 -0
  88. package/coverage/src/js-lib/db/postgresql/PostgreSqlCallExpressionVisitor.ts.html +985 -0
  89. package/coverage/src/js-lib/db/postgresql/index.html +116 -0
  90. package/coverage/src/js-lib/db/redshift/RedshiftCallExpressionVisitor.ts.html +685 -0
  91. package/coverage/src/js-lib/db/redshift/index.html +116 -0
  92. package/coverage/src/js-lib/db/sample/SampleCallExpressionVisitor.ts.html +196 -0
  93. package/coverage/src/js-lib/db/sample/index.html +116 -0
  94. package/coverage/src/js-lib/db/snowflake/SnowflakeCallExpressionVisitor.ts.html +1447 -0
  95. package/coverage/src/js-lib/db/snowflake/index.html +116 -0
  96. package/coverage/src/js-lib/db/validator/FormulaValidator.ts.html +4162 -0
  97. package/coverage/src/js-lib/db/validator/index.html +116 -0
  98. package/coverage/src/js-lib/index.html +131 -0
  99. package/coverage/src/js-lib/objects/BaseObject.ts.html +169 -0
  100. package/coverage/src/js-lib/objects/DateObject.ts.html +169 -0
  101. package/coverage/src/js-lib/objects/PctObject.ts.html +178 -0
  102. package/coverage/src/js-lib/objects/index.html +146 -0
  103. package/coverage/src/query-builder/PaginationBuilder.ts.html +142 -0
  104. package/coverage/src/query-builder/QueryBuilder.ts.html +3118 -0
  105. package/coverage/src/query-builder/SuperFilterBuilder.ts.html +1969 -0
  106. package/coverage/src/query-builder/index.html +146 -0
  107. package/coverage/src/runtime_var.ts.html +109 -0
  108. package/coverage/src/sql-lib/binary_expr.ts.html +133 -0
  109. package/coverage/src/sql-lib/case.ts.html +133 -0
  110. package/coverage/src/sql-lib/column.ts.html +139 -0
  111. package/coverage/src/sql-lib/else.ts.html +124 -0
  112. package/coverage/src/sql-lib/function.ts.html +112 -0
  113. package/coverage/src/sql-lib/index.html +251 -0
  114. package/coverage/src/sql-lib/join.ts.html +127 -0
  115. package/coverage/src/sql-lib/literal.ts.html +130 -0
  116. package/coverage/src/sql-lib/select.ts.html +547 -0
  117. package/coverage/src/sql-lib/unary_expr.ts.html +112 -0
  118. package/coverage/src/sql-lib/when.ts.html +130 -0
  119. package/coverage/src/sql_query_gen.ts.html +535 -0
  120. package/coverage/src/superFilter/DateFilterFactory.ts.html +625 -0
  121. package/coverage/src/superFilter/dateFunction.ts.html +193 -0
  122. package/coverage/src/superFilter/index.html +131 -0
  123. package/coverage/src/utils.ts.html +571 -0
  124. package/dist/index.cjs +8440 -0
  125. package/dist/index.d.cts +927 -0
  126. package/dist/index.d.cts.map +1 -0
  127. package/dist/index.d.ts +927 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +8387 -0
  130. package/dist/index.js.map +1 -0
  131. package/eslint.config.js +4 -0
  132. package/jest.config.ts +44 -0
  133. package/package.json +45 -0
  134. package/src/constants.ts +247 -0
  135. package/src/epm-query-builder/EpmQueryBuilder.ts +27 -0
  136. package/src/epm-query-builder/base/BaseAdvancedAggregations.ts +161 -0
  137. package/src/epm-query-builder/base/BaseAnalyticalFunctions.ts +203 -0
  138. package/src/epm-query-builder/base/BaseCTEGenerator.ts +458 -0
  139. package/src/epm-query-builder/base/BaseCountQueryBuilder.ts +105 -0
  140. package/src/epm-query-builder/base/BaseMeasureBuilder.ts +87 -0
  141. package/src/epm-query-builder/base/BaseOrderBuilder.ts +195 -0
  142. package/src/epm-query-builder/base/BasePaginationBuilder.ts +93 -0
  143. package/src/epm-query-builder/base/BaseQueryBuilder.ts +51 -0
  144. package/src/epm-query-builder/base/BaseRollupBuilder.ts +149 -0
  145. package/src/epm-query-builder/base/BaseSqlBuilder.ts +172 -0
  146. package/src/epm-query-builder/base/BaseSuperFilterBuilder.ts +627 -0
  147. package/src/epm-query-builder/base/BaseUtilities.ts +571 -0
  148. package/src/epm-query-builder/base/ColumnRefUtils.ts +42 -0
  149. package/src/epm-query-builder/base/RelationshipResolver.ts +207 -0
  150. package/src/epm-query-builder/base/SharedFilterBuilder.ts +544 -0
  151. package/src/epm-query-builder/constants/Aggregations.ts +16 -0
  152. package/src/epm-query-builder/constants/Database.ts +6 -0
  153. package/src/epm-query-builder/constants/Source.ts +7 -0
  154. package/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts +67 -0
  155. package/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts +65 -0
  156. package/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts +626 -0
  157. package/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts +228 -0
  158. package/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts +186 -0
  159. package/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts +853 -0
  160. package/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts +131 -0
  161. package/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts +370 -0
  162. package/src/epm-query-builder/errors/QueryBuilderErrors.ts +65 -0
  163. package/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts +5 -0
  164. package/src/epm-query-builder/interfaces/index.ts +1 -0
  165. package/src/epm-query-builder/types/query-builder-types.d.ts +289 -0
  166. package/src/epm-query-builder/utils/format.ts +22 -0
  167. package/src/epm-query-builder/utils/sql.ts +54 -0
  168. package/src/epm-query-builder/utils/validation.ts +13 -0
  169. package/src/epm-query-builder/validation/QueryOptionsValidator.ts +182 -0
  170. package/src/epm-query-builder/validation/SqlQueryValidator.ts +130 -0
  171. package/src/filters/FilterConditionBuilder.ts +114 -0
  172. package/src/filters/filter-types.ts +133 -0
  173. package/src/index.ts +10 -0
  174. package/src/js-lib/JsToSqlParser.ts +217 -0
  175. package/src/js-lib/ParseContext.ts +149 -0
  176. package/src/js-lib/db/base/ArrayExpressionVisitor.ts +16 -0
  177. package/src/js-lib/db/base/AssignmentExpressionVisitor.ts +34 -0
  178. package/src/js-lib/db/base/BinaryExpressionVisitor.ts +46 -0
  179. package/src/js-lib/db/base/CallExpressionVisitor.ts +1798 -0
  180. package/src/js-lib/db/base/IdentifierVisitor.ts +66 -0
  181. package/src/js-lib/db/base/LiteralVisitor.ts +38 -0
  182. package/src/js-lib/db/base/MemberExpressionVisitor.ts +36 -0
  183. package/src/js-lib/db/base/ProgramVisitor.ts +18 -0
  184. package/src/js-lib/db/base/UnaryExpressionVisitor.ts +32 -0
  185. package/src/js-lib/db/base/VisitorInterface.ts +6 -0
  186. package/src/js-lib/db/validator/FormulaValidator.ts +1235 -0
  187. package/src/js-lib/objects/BaseObject.ts +28 -0
  188. package/src/js-lib/objects/DateObject.ts +28 -0
  189. package/src/js-lib/objects/PctObject.ts +31 -0
  190. package/src/query-builder/PaginationBuilder.ts +19 -0
  191. package/src/query-builder/QueryBuilder.ts +1035 -0
  192. package/src/query-builder/SuperFilterBuilder.ts +628 -0
  193. package/src/runtime_var.ts +8 -0
  194. package/src/sql-lib/binary_expr.ts +16 -0
  195. package/src/sql-lib/case.ts +16 -0
  196. package/src/sql-lib/column.ts +18 -0
  197. package/src/sql-lib/else.ts +13 -0
  198. package/src/sql-lib/function.ts +9 -0
  199. package/src/sql-lib/join.ts +14 -0
  200. package/src/sql-lib/literal.ts +15 -0
  201. package/src/sql-lib/select.ts +154 -0
  202. package/src/sql-lib/unary_expr.ts +9 -0
  203. package/src/sql-lib/when.ts +15 -0
  204. package/src/sql-types.d.ts +565 -0
  205. package/src/sql_query_gen.ts +150 -0
  206. package/src/superFilter/DateFilterFactory.ts +180 -0
  207. package/src/superFilter/dateFunction.ts +36 -0
  208. package/src/utils.ts +354 -0
  209. package/test-output/report/junit.xml +329 -0
  210. package/tests/JsToSqlParser.test.ts +163 -0
  211. package/tests/QueryBuilder.test.ts +1320 -0
  212. package/tests/js-lib/CallExpressionVisitor.test.ts +820 -0
  213. package/tests/mocks/MockQueryResolver.ts +14 -0
  214. package/tests/sanity.test.ts +146 -0
  215. package/tests/sql-lib/binary_expr.test.ts +75 -0
  216. package/tests/sql-lib/case.test.ts +117 -0
  217. package/tests/sql-lib/column.test.ts +87 -0
  218. package/tests/sql-lib/else.test.ts +56 -0
  219. package/tests/sql-lib/function.test.ts +96 -0
  220. package/tests/sql-lib/literal.test.ts +75 -0
  221. package/tests/sql-lib/select.test.ts +245 -0
  222. package/tests/sql-lib/unary_expr.test.ts +32 -0
  223. package/tests/utils.test.ts +13 -0
  224. package/tsconfig.json +24 -0
  225. package/tsdown.config.ts +23 -0
@@ -0,0 +1,1798 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
2
+
3
+ import * as acorn from 'acorn';
4
+ import { MDM_COLUMN_TYPE, ParseContext } from '../../ParseContext';
5
+ import { VisitorInterface } from './VisitorInterface';
6
+ import { toBinaryExpression } from '../../../sql-lib/binary_expr';
7
+ import {
8
+ Binary,
9
+ Case,
10
+ ColumnRefItem,
11
+ ExpressionValue,
12
+ From,
13
+ Select,
14
+ Value,
15
+ ValueExpr,
16
+ } from '../../../sql-types';
17
+ import { PctObject } from '../../objects/PctObject';
18
+ import { toFunction } from '../../../sql-lib/function';
19
+ import { toWhen, WhenType } from '../../../sql-lib/when';
20
+ import { ElseType, toElse } from '../../../sql-lib/else';
21
+ import { toCase } from '../../../sql-lib/case';
22
+ import { toUnaryExpression } from '../../../sql-lib/unary_expr';
23
+ import { generateSqlQuery } from '../../../sql_query_gen';
24
+ import { SelectBuilder, WithBuilder } from '../../../sql-lib/select';
25
+ import { toColumn, toColumnRef } from '../../../sql-lib/column';
26
+ import { createJsToSqlParser } from '../../JsToSqlParser';
27
+ import { RUNTIME_TABLE_NAME } from '../../../runtime_var';
28
+ import { toJoin } from '../../../sql-lib/join';
29
+ import { formatDBEntityName } from '../../../utils';
30
+ import {
31
+ absTransformValidator,
32
+ andTransformValidator,
33
+ averageTransformValidator,
34
+ averageXNegTransformValidator,
35
+ averageXZeroNegTransformValidator,
36
+ averageXZeroTransformValidator,
37
+ ceilTransformValidator,
38
+ concatenateTransformValidator,
39
+ dateAddTransformValidator,
40
+ dateDiffTransformValidator,
41
+ dateTransformValidator,
42
+ dayTransformValidator,
43
+ divideTransformValidator,
44
+ eomonthTransformValidator,
45
+ evenTransformValidator,
46
+ expTransformValidator,
47
+ filterIfTransformValidator,
48
+ floorTransformValidator,
49
+ fromExcelDateTransformValidator,
50
+ hasAllTransformValidator,
51
+ hasSomeTransformValidator,
52
+ hasTransformValidator,
53
+ ifTransformValidator,
54
+ indexOfTransformValidator,
55
+ inTransformValidator,
56
+ isEmptyTransformValidator,
57
+ isNumberTransformValidator,
58
+ leftTransformValidator,
59
+ lenTransformValidator,
60
+ logTransformValidator,
61
+ lowerTransformValidator,
62
+ matchTransformValidator,
63
+ maxAggregateTransformValidator,
64
+ medianTransformValidator,
65
+ midTransformValidator,
66
+ minAggregateTransformValidator,
67
+ modeTransformValidator,
68
+ modTransformValidator,
69
+ monthTransformValidator,
70
+ notTransformValidator,
71
+ nowTransformValidator,
72
+ numberValueTransformValidator,
73
+ oddTransformValidator,
74
+ orTransformValidator,
75
+ pctTransformValidator,
76
+ powTransformValidator,
77
+ randBetweenTransformValidator,
78
+ randTransformValidator,
79
+ replaceTransformValidator,
80
+ reptTransformValidator,
81
+ rightTransformValidator,
82
+ roundTransformValidator,
83
+ sqrtTransformValidator,
84
+ sumTransformValidator,
85
+ switchTransformValidator,
86
+ textTransformValidator,
87
+ todayTransformValidator,
88
+ trimTransformValidator,
89
+ upperTransformValidator,
90
+ uuidTransformValidator,
91
+ valueTransformValidator,
92
+ xorTransformValidator,
93
+ yearTransformValidator,
94
+ } from '../validator/FormulaValidator';
95
+
96
+ export const DEFAULT_FUNCTION_TRANSFORMS: Record<string, (...args: any[]) => void> = {
97
+ // Common functions
98
+ DATE: dateTransform,
99
+
100
+ // Logical functions
101
+ AND: andTransform,
102
+ IF: ifTransform,
103
+ OR: orTransform,
104
+ NOT: notTransform,
105
+ XOR: xorTransform,
106
+ IFNA: ifNullTransform,
107
+ HAS: hasTransform,
108
+ HAS_SOME: hasSomeTransform,
109
+ FILTERIF: filterIfTransform,
110
+ IN: inTransform,
111
+ HAS_ALL: hasAllTransform,
112
+ SWITCH: switchTransform,
113
+
114
+ // Date functions
115
+ DATEADD: dateAddTransform,
116
+ DATEDIFF: dateDiffTransform,
117
+ DAY: dayTransform,
118
+ EOMONTH: eomonthTransform,
119
+ MONTH: monthTransform,
120
+ NOW: nowTransform,
121
+ TODAY: todayTransform,
122
+ YEAR: yearTransform,
123
+ FROMEXCELDATE: fromExcelDateTransform,
124
+
125
+ // String functions
126
+ CONCAT: concatenateTransform,
127
+ CONCATENATE: concatenateTransform,
128
+ ISBLANK: isEmptyTransform,
129
+ ISEMPTY: isEmptyTransform,
130
+ LEFT: leftTransform,
131
+ LEN: lenTransform,
132
+ LOWER: lowerTransform,
133
+ MATCH: matchTransform,
134
+ MID: midTransform,
135
+ NUMBERVALUE: numberValueTransform,
136
+ REPLACE: replaceTransform,
137
+ REPT: reptTransform,
138
+ RIGHT: rightTransform,
139
+ TRIM: trimTransform,
140
+ TEXT: textTransform,
141
+ UPPER: upperTransform,
142
+ VALUE: valueTransform,
143
+ UUID: uuidTransform,
144
+ FIND: findTransform,
145
+
146
+ // Math functions
147
+ ABS: absTransform,
148
+ AVERAGE: averageTransform,
149
+ AVERAGEIF: averageIfTransform,
150
+ AVERAGEEXNEG: averageXNegTransform,
151
+ AVERAGEEXZERO: averageXZeroTransform,
152
+ AVERAGEEXZERONEG: averageXZeroNegTransform,
153
+ CEIL: ceilTransform,
154
+ CEILING: ceilTransform,
155
+ DIVIDE: divideTransform,
156
+ INDEXOF: indexOfTransform,
157
+ ISNUMBER: isNumberTransform,
158
+ EXP: expTransform,
159
+ FLOOR: floorTransform,
160
+ LOG: logTransform,
161
+ MAX: maxAggregateTransform,
162
+ MEDIAN: medianTransform,
163
+ MIN: minAggregateTransform,
164
+ MOD: modTransform,
165
+ ODD: oddTransform,
166
+ EVEN: evenTransform,
167
+ POW: powTransform,
168
+ PCT: pctTransform,
169
+ RAND: randTransform,
170
+ RANDBETWEEN: randBetweenTransform,
171
+ ROUND: roundTransform,
172
+ SUM: sumTransform,
173
+ SQRT: sqrtTransform,
174
+ MODE: modeTransform,
175
+ };
176
+
177
+ /**
178
+ * This class can be customized by extending this class and provide custom function transforms as part of `init()` implementation
179
+ */
180
+ export class CallExpressionVisitor implements VisitorInterface<acorn.CallExpression> {
181
+ protected functionTransformMap: Map<string, (...args: any[]) => void> | undefined;
182
+
183
+ constructor() {
184
+ this.init();
185
+ }
186
+
187
+ protected init(): void {
188
+ this.functionTransformMap = new Map<string, (...args: any[]) => void>(
189
+ Object.entries(DEFAULT_FUNCTION_TRANSFORMS),
190
+ );
191
+ }
192
+
193
+ /**
194
+ * visitor needs to be implemented as arrow function to avoid `this` binding issue since it will be
195
+ * passed to `acorn-walk` library
196
+ */
197
+ public visitor = (node: acorn.CallExpression, context: ParseContext) => {
198
+ if (node.callee.type === 'MemberExpression') {
199
+ return this.processMemberExpression(node, context);
200
+ }
201
+ const fn = node.callee as acorn.Identifier;
202
+ const args = node.arguments.map((arg) => context.get(arg));
203
+ const transform = this.functionTransformMap!.get(fn.name.toUpperCase());
204
+ if (!transform) {
205
+ throw new Error(`Unsupported function: ${fn.name}`);
206
+ }
207
+ context.set(node, transform(node, context, ...args));
208
+ };
209
+
210
+ /**
211
+ * method to process call expression with member expression
212
+ */
213
+ protected processMemberExpression(node: acorn.CallExpression, context: ParseContext) {
214
+ const memberExpression = node.callee as acorn.MemberExpression;
215
+ const objectInstance = context.get(memberExpression);
216
+ const fnName = (memberExpression.property as acorn.Identifier).name.toUpperCase();
217
+ const fn = objectInstance.fnMap.get(fnName);
218
+ if (!fn) {
219
+ throw new Error(`Unsupported function: ${fnName}`);
220
+ }
221
+ const args = node.arguments.map((arg) => context.get(arg));
222
+ context.set(node, fn(context, ...args));
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Transforms `if` call expression to `case-when-then-else` SQL expression
228
+ */
229
+ export function ifTransform(
230
+ node: acorn.CallExpression,
231
+ context: ParseContext,
232
+ cond: Binary,
233
+ trueValue: ExpressionValue,
234
+ falseValue: ExpressionValue,
235
+ ): ExpressionValue {
236
+ ifTransformValidator(context, [cond, trueValue, falseValue]);
237
+ let whenList = [toWhen(cond, trueValue)];
238
+ let elseValue: ElseType | undefined = undefined;
239
+ if (falseValue.type == 'case') {
240
+ const args = (falseValue as Case).args;
241
+ elseValue = args.pop() as ElseType;
242
+ whenList = whenList.concat(args as WhenType[]);
243
+ } else {
244
+ elseValue = toElse(falseValue);
245
+ }
246
+
247
+ return toCase({
248
+ whenList,
249
+ elseResult: elseValue,
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Transforms `today` call expression to `current_date` SQL expression
255
+ */
256
+ function todayTransform(_node: acorn.CallExpression, context: ParseContext): ExpressionValue {
257
+ todayTransformValidator(context);
258
+ const sqlFnName = 'CURRENT_DATE';
259
+ return { type: 'sql_expr', value: sqlFnName };
260
+ }
261
+
262
+ /**
263
+ * Transforms `now` call expression to `current_timestamp` SQL expression
264
+ */
265
+ function nowTransform(_node: acorn.CallExpression, context: ParseContext): ExpressionValue {
266
+ nowTransformValidator(context);
267
+ const sqlFnName = 'CURRENT_TIMESTAMP';
268
+ return { type: 'sql_expr', value: sqlFnName };
269
+ }
270
+
271
+ /**
272
+ * Transforms `concat` call expression to `concat` SQL expression
273
+ */
274
+ function concatenateTransform(
275
+ _node: acorn.CallExpression,
276
+ context: ParseContext,
277
+ ...args: ExpressionValue[]
278
+ ): ExpressionValue {
279
+ concatenateTransformValidator(context, args);
280
+
281
+ const sqlFnName = 'CONCAT';
282
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
283
+ return toFunction(name, args);
284
+ }
285
+
286
+ /**
287
+ * Transforms `lower` call expression to `lower` SQL expression
288
+ */
289
+ function lowerTransform(
290
+ node: acorn.CallExpression,
291
+ context: ParseContext,
292
+ arg: ExpressionValue,
293
+ ): ExpressionValue {
294
+ lowerTransformValidator(context, arg);
295
+ const sqlFnName = 'LOWER';
296
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
297
+ context.returnType = 'string';
298
+ return toFunction(name, [arg]);
299
+ }
300
+
301
+ /**
302
+ * Transforms `trim` call expression to `trim` SQL expression
303
+ */
304
+ function trimTransform(
305
+ node: acorn.CallExpression,
306
+ context: ParseContext,
307
+ arg: ExpressionValue,
308
+ ): ExpressionValue {
309
+ trimTransformValidator(context, arg);
310
+ const sqlFnName = 'TRIM';
311
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
312
+ return toFunction(name, [arg]);
313
+ }
314
+
315
+ /**
316
+ * Transforms `upper` call expression to `upper` SQL expression
317
+ */
318
+ function upperTransform(
319
+ node: acorn.CallExpression,
320
+ context: ParseContext,
321
+ arg: ExpressionValue,
322
+ ): ExpressionValue {
323
+ upperTransformValidator(context, arg);
324
+ const sqlFnName = 'UPPER';
325
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
326
+ return toFunction(name, [arg]);
327
+ }
328
+
329
+ /**
330
+ * Transforms `len` call expression to `len` SQL expression
331
+ */
332
+ function lenTransform(
333
+ node: acorn.CallExpression,
334
+ context: ParseContext,
335
+ arg: ExpressionValue,
336
+ ): ExpressionValue {
337
+ lenTransformValidator(context, arg);
338
+ context.returnType = 'number';
339
+ const sqlFnName = 'LEN';
340
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
341
+ return toFunction(name, [arg]);
342
+ }
343
+
344
+ /**
345
+ * Transforms `left` call expression to `left` SQL expression
346
+ */
347
+ function leftTransform(
348
+ node: acorn.CallExpression,
349
+ context: ParseContext,
350
+ ...args: ExpressionValue[]
351
+ ): ExpressionValue {
352
+ leftTransformValidator(context, args);
353
+ const text = args[0];
354
+ const len = args[1] ?? { type: 'number', value: 0 };
355
+ const name = { type: 'default', value: 'LEFT' } as ValueExpr<string>;
356
+ return toFunction(name, [text, len]);
357
+ }
358
+
359
+ /**
360
+ * Transforms `right` call expression to `right` SQL expression
361
+ */
362
+ function rightTransform(
363
+ node: acorn.CallExpression,
364
+ context: ParseContext,
365
+ ...args: ExpressionValue[]
366
+ ): ExpressionValue {
367
+ rightTransformValidator(context, args);
368
+ const sqlFnName = 'RIGHT';
369
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
370
+ const text = args[0];
371
+ const len = args[1] ?? { type: 'number', value: 0 };
372
+ return toFunction(name, [text, len]);
373
+ }
374
+
375
+ /**
376
+ * Transforms `replace` call expression to `stuff` SQL expression
377
+ */
378
+ function replaceTransform(
379
+ node: acorn.CallExpression,
380
+ context: ParseContext,
381
+ ...args: ExpressionValue[]
382
+ ): ExpressionValue {
383
+ replaceTransformValidator(context, args);
384
+ const sqlFnName = 'STUFF';
385
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
386
+ return toFunction(name, args);
387
+ }
388
+
389
+ /**
390
+ * Transforms `rept` call expression to `rept` SQL expression
391
+ */
392
+ function reptTransform(
393
+ node: acorn.CallExpression,
394
+ context: ParseContext,
395
+ ...args: ExpressionValue[]
396
+ ): ExpressionValue {
397
+ reptTransformValidator(context, args);
398
+ const sqlFnName = 'REPLICATE';
399
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
400
+ return toFunction(name, args);
401
+ }
402
+
403
+ /**
404
+ * Transforms `match` call expression to `case-when-then-else using like in condition` SQL expression
405
+ */
406
+ function matchTransform(
407
+ node: acorn.CallExpression,
408
+ context: ParseContext,
409
+ ...args: ExpressionValue[]
410
+ ): ExpressionValue {
411
+ matchTransformValidator(context, args);
412
+ const [value, pattern] = args;
413
+ return ifTransform(
414
+ node,
415
+ context,
416
+ { type: 'binary_expr', operator: 'LIKE', left: value, right: pattern } as Binary,
417
+ { type: 'string', value: 'true' } as ExpressionValue,
418
+ { type: 'string', value: 'false' } as ExpressionValue,
419
+ );
420
+ }
421
+
422
+ /**
423
+ * Transforms `dateadd` call expression to `DATEADD` SQL expression
424
+ */
425
+ function dateAddTransform(
426
+ node: acorn.CallExpression,
427
+ context: ParseContext,
428
+ date: ExpressionValue,
429
+ numDays: ExpressionValue,
430
+ ): ExpressionValue {
431
+ dateAddTransformValidator(context, [date, numDays]);
432
+ const startDate = generateSqlQuery(date);
433
+ const daysToAdd = generateSqlQuery(numDays);
434
+ return { type: 'sql_expr', value: `DATEADD(DAY, ${daysToAdd}, ${startDate})` };
435
+ }
436
+
437
+ /**
438
+ * Transforms `datediff` call expression to `DATEDIFF` SQL expression
439
+ */
440
+ function dateDiffTransform(
441
+ node: acorn.CallExpression,
442
+ context: ParseContext,
443
+ date1: ExpressionValue,
444
+ date2: ExpressionValue,
445
+ ): ExpressionValue {
446
+ dateDiffTransformValidator(context, [date1, date2]);
447
+ const startDate = generateSqlQuery(date1);
448
+ const endDate = generateSqlQuery(date2);
449
+ return { type: 'sql_expr', value: `DATEDIFF(DAY, ${startDate}, ${endDate})` };
450
+ }
451
+
452
+ /**
453
+ * Transforms `day` call expression to `day` SQL expression
454
+ */
455
+ function dayTransform(
456
+ node: acorn.CallExpression,
457
+ context: ParseContext,
458
+ date: ExpressionValue,
459
+ ): ExpressionValue {
460
+ dayTransformValidator(context, date);
461
+ const sqlFnName = 'DAY';
462
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
463
+ return toFunction(name, [date]);
464
+ }
465
+
466
+ /**
467
+ * Transforms `month` call expression to `month` SQL expression
468
+ */
469
+ function monthTransform(
470
+ node: acorn.CallExpression,
471
+ context: ParseContext,
472
+ date: ExpressionValue,
473
+ ): ExpressionValue {
474
+ monthTransformValidator(context, date);
475
+ const sqlFnName = 'MONTH';
476
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
477
+ return toFunction(name, [date]);
478
+ }
479
+
480
+ /**
481
+ * Transforms `year` call expression to `year` SQL expression
482
+ */
483
+ function yearTransform(
484
+ node: acorn.CallExpression,
485
+ context: ParseContext,
486
+ date: ExpressionValue,
487
+ ): ExpressionValue {
488
+ yearTransformValidator(context, date);
489
+ const sqlFnName = 'YEAR';
490
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
491
+ return toFunction(name, [date]);
492
+ }
493
+
494
+ /**
495
+ * Transforms `eomonth` call expression to `eomonth` SQL expression
496
+ */
497
+ function eomonthTransform(
498
+ node: acorn.CallExpression,
499
+ context: ParseContext,
500
+ ...args: ExpressionValue[]
501
+ ): ExpressionValue {
502
+ eomonthTransformValidator(context, args);
503
+ const sqlFnName = 'EOMONTH';
504
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
505
+ if (args.length < 1 || args.length > 2) {
506
+ throw new Error('EOMONTH() requires 1 or 2 arguments');
507
+ }
508
+ return toFunction(name, args);
509
+ }
510
+
511
+ /**
512
+ * Transforms `divide` call expression to `divide` SQL expression
513
+ */
514
+ function divideTransform(
515
+ node: acorn.CallExpression,
516
+ context: ParseContext,
517
+ dividend: ExpressionValue,
518
+ divisor: ExpressionValue,
519
+ ): ExpressionValue {
520
+ divideTransformValidator(context, [dividend, divisor]);
521
+ const cast = (expr: ExpressionValue) =>
522
+ toFunction({ type: 'default', value: 'CAST' }, [
523
+ toBinaryExpression('AS', expr, { type: 'default', value: 'FLOAT' }),
524
+ ]);
525
+
526
+ const zeroCheck = toBinaryExpression('=', cast(divisor), { type: 'number', value: 0 });
527
+ const whenArg = toWhen(zeroCheck, { type: 'null', value: null });
528
+ const elseArg = toElse(toBinaryExpression('/', cast(dividend), cast(divisor)));
529
+
530
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
531
+ }
532
+
533
+ /**
534
+ * Transforms `and` call expression to `and` SQL expression
535
+ */
536
+ function andTransform(
537
+ node: acorn.CallExpression,
538
+ context: ParseContext,
539
+ ...conds: ExpressionValue[]
540
+ ): ExpressionValue {
541
+ andTransformValidator(context, conds);
542
+ const sqlOperator = 'AND';
543
+ const [first, ...rest] = conds;
544
+ const condition = rest.reduce((acc, cond) => {
545
+ return toBinaryExpression(sqlOperator, acc, cond);
546
+ }, first);
547
+
548
+ if (!context.ifCondition) {
549
+ return ifTransform(
550
+ node,
551
+ context,
552
+ condition as Binary,
553
+ { type: 'string', value: 'true' },
554
+ { type: 'string', value: 'false' },
555
+ );
556
+ }
557
+
558
+ return condition;
559
+ }
560
+ /**
561
+ * Transforms `or` call expression to `or` SQL expression
562
+ */
563
+ function orTransform(
564
+ node: acorn.CallExpression,
565
+ context: ParseContext,
566
+ ...conds: ExpressionValue[]
567
+ ): ExpressionValue {
568
+ orTransformValidator(context, conds);
569
+ const sqlOperator = 'OR';
570
+ const [first, ...rest] = conds;
571
+ const condition = rest.reduce((acc, cond) => {
572
+ return toBinaryExpression(sqlOperator, acc, cond);
573
+ }, first);
574
+
575
+ if (!context.ifCondition) {
576
+ return ifTransform(
577
+ node,
578
+ context,
579
+ condition as Binary,
580
+ { type: 'string', value: 'true' },
581
+ { type: 'string', value: 'false' },
582
+ );
583
+ }
584
+
585
+ return condition;
586
+ }
587
+
588
+ /**
589
+ * Transforms `ceil` call expression to `ceil` SQL expression
590
+ */
591
+ function ceilTransform(
592
+ node: acorn.CallExpression,
593
+ context: ParseContext,
594
+ arg: ExpressionValue,
595
+ ): ExpressionValue {
596
+ ceilTransformValidator(context, arg);
597
+ const sqlFnName = 'CEILING';
598
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
599
+ return toFunction(name, [arg]);
600
+ }
601
+
602
+ /**
603
+ * Transforms `floor` call expression to `floor` SQL expression
604
+ */
605
+ function floorTransform(
606
+ node: acorn.CallExpression,
607
+ context: ParseContext,
608
+ arg: ExpressionValue,
609
+ ): ExpressionValue {
610
+ floorTransformValidator(context, arg);
611
+ const sqlFnName = 'FLOOR';
612
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
613
+ return toFunction(name, [arg]);
614
+ }
615
+
616
+ /**
617
+ * Transforms `abs` call expression to `abs` SQL expression
618
+ */
619
+ function absTransform(
620
+ node: acorn.CallExpression,
621
+ context: ParseContext,
622
+ arg: ExpressionValue,
623
+ ): ExpressionValue {
624
+ absTransformValidator(context, arg);
625
+ const sqlFnName = 'ABS';
626
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
627
+ return toFunction(name, [arg]);
628
+ }
629
+
630
+ /**
631
+ * Transforms `round` call expression to `round` SQL expression
632
+ */
633
+ function roundTransform(
634
+ node: acorn.CallExpression,
635
+ context: ParseContext,
636
+ ...args: ExpressionValue[]
637
+ ): ExpressionValue {
638
+ roundTransformValidator(context, args);
639
+
640
+ const sqlFnName = 'ROUND';
641
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
642
+ const [value, decimals] = args;
643
+ return toFunction(name, [value, decimals ?? { type: 'number', value: 0 }]);
644
+ }
645
+
646
+ /**
647
+ * Transforms `exp` call expression to `exp` SQL expression
648
+ */
649
+ function expTransform(
650
+ node: acorn.CallExpression,
651
+ context: ParseContext,
652
+ arg: ExpressionValue,
653
+ ): ExpressionValue {
654
+ expTransformValidator(context, arg);
655
+ const sqlFnName = 'EXP';
656
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
657
+ return toFunction(name, [arg]);
658
+ }
659
+
660
+ /**
661
+ * Transforms `pow` call expression to `pow` SQL expression
662
+ */
663
+ function powTransform(
664
+ node: acorn.CallExpression,
665
+ context: ParseContext,
666
+ ...args: ExpressionValue[]
667
+ ): ExpressionValue {
668
+ powTransformValidator(context, args);
669
+ const sqlFnName = 'POWER';
670
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
671
+ if (args.length < 2) {
672
+ throw new Error('POW() requires 2 arguments');
673
+ }
674
+ return toFunction(name, args);
675
+ }
676
+
677
+ /**
678
+ * Transforms `log` call expression to `log` SQL expression
679
+ */
680
+ function logTransform(
681
+ node: acorn.CallExpression,
682
+ context: ParseContext,
683
+ ...args: ExpressionValue[]
684
+ ): ExpressionValue {
685
+ logTransformValidator(context, args);
686
+ const sqlFnName = 'LOG';
687
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
688
+ if (args.length < 1 || args.length > 2) {
689
+ throw new Error('LOG() requires 1 or 2 arguments');
690
+ }
691
+ return toFunction(name, args);
692
+ }
693
+
694
+ /**
695
+ * Transforms `sqrt` call expression to `sqrt` SQL expression
696
+ */
697
+ function sqrtTransform(
698
+ node: acorn.CallExpression,
699
+ context: ParseContext,
700
+ arg: ExpressionValue,
701
+ ): ExpressionValue {
702
+ sqrtTransformValidator(context, arg);
703
+ const sqlFnName = 'SQRT';
704
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
705
+ return toFunction(name, [arg]);
706
+ }
707
+
708
+ /**
709
+ * Transforms `mod` call expression to `mod` SQL expression
710
+ */
711
+ function modTransform(
712
+ node: acorn.CallExpression,
713
+ context: ParseContext,
714
+ ...args: ExpressionValue[]
715
+ ): ExpressionValue {
716
+ modeTransformValidator(context, args);
717
+ const [dividend, divisor] = args;
718
+ return toBinaryExpression('%', dividend, divisor);
719
+ }
720
+
721
+ /**
722
+ * Transforms `not` call expression to `not` SQL expression
723
+ */
724
+ function notTransform(
725
+ node: acorn.CallExpression,
726
+ context: ParseContext,
727
+ arg: Binary,
728
+ ): ExpressionValue {
729
+ notTransformValidator(context, arg);
730
+ if (!context.ifCondition) {
731
+ const whenArg = toWhen(
732
+ arg,
733
+ toFunction({ type: 'default', value: 'CAST' }, [
734
+ toBinaryExpression('AS', { type: 'number', value: 0 }, { type: 'default', value: 'BIT' }),
735
+ ]),
736
+ );
737
+ const elseArg = toElse(
738
+ toFunction({ type: 'default', value: 'CAST' }, [
739
+ toBinaryExpression('AS', { type: 'number', value: 1 }, { type: 'default', value: 'BIT' }),
740
+ ]),
741
+ );
742
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
743
+ }
744
+ return arg;
745
+ }
746
+
747
+ /**
748
+ * Transforms `rand` call expression to `rand` SQL expression
749
+ */
750
+ function randTransform(node: acorn.CallExpression, context: ParseContext): ExpressionValue {
751
+ randTransformValidator(context);
752
+ const sqlFnName = 'RAND';
753
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
754
+ return toFunction(name, []);
755
+ }
756
+
757
+ /**
758
+ * Transforms `randbetween` call expression to `rand_between` SQL expression
759
+ */
760
+ function randBetweenTransform(
761
+ node: acorn.CallExpression,
762
+ context: ParseContext,
763
+ ...args: ExpressionValue[]
764
+ ): ExpressionValue {
765
+ randBetweenTransformValidator(context, args);
766
+ const [minValue, maxValue] = args.map(generateSqlQuery);
767
+ return { type: 'sql_expr', value: `(RAND() * (${maxValue} - ${minValue}) + ${minValue})` };
768
+ }
769
+
770
+ /**
771
+ * Transforms `average` call expression to SQL expression. This performs average of the arguments
772
+ * which is different than SQL's AVG() function.
773
+ */
774
+ function averageTransform(
775
+ node: acorn.CallExpression,
776
+ context: ParseContext,
777
+ ...args: ExpressionValue[]
778
+ ): ExpressionValue {
779
+ averageTransformValidator(context, args);
780
+ const argsCount = args.length;
781
+ const argList = args.map((arg) => `COALESCE(${generateSqlQuery(arg)}, 0)`);
782
+ return { type: 'sql_expr', value: `CAST(((${argList.join(' + ')})/${argsCount}) AS FLOAT)` };
783
+ }
784
+
785
+ function averageConditionalTransform(
786
+ node: acorn.CallExpression,
787
+ context: ParseContext,
788
+ valueList: ExpressionValue[],
789
+ conditionBuilder: (valueColumnName: string) => Binary,
790
+ ): Select {
791
+ const currentColumnId = context.currentColumnId;
792
+ const valueColumnName = `val_${currentColumnId}`;
793
+ const unionCte = new WithBuilder(context)
794
+ .addName(`cte_union_${currentColumnId}`)
795
+ .addStmt(
796
+ valueList.reverse().reduce(
797
+ (acc, arg) => {
798
+ const builder = new SelectBuilder()
799
+ .addColumns([
800
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
801
+ toColumn(arg, valueColumnName),
802
+ ])
803
+ .addFrom([{ type: 'from', table: RUNTIME_TABLE_NAME } as From]);
804
+
805
+ if (acc) {
806
+ builder.addSetOp('UNION ALL').addNext(acc);
807
+ }
808
+ return builder.build();
809
+ },
810
+ undefined as Select | undefined,
811
+ )!,
812
+ )
813
+ .build();
814
+
815
+ const filterCte = new WithBuilder(context)
816
+ .addName(`cte_filter_${currentColumnId}`)
817
+ .addStmt(
818
+ new SelectBuilder()
819
+ .addColumns([
820
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
821
+ toColumn(toColumnRef(valueColumnName)),
822
+ ])
823
+ .addFrom([{ type: 'from', table: unionCte.name.value } as From])
824
+ .addWhere(conditionBuilder(valueColumnName))
825
+ .build(),
826
+ )
827
+ .build();
828
+
829
+ const avgCte = new WithBuilder(context)
830
+ .addName(`cte_avg_${currentColumnId}`)
831
+ .addStmt(
832
+ new SelectBuilder()
833
+ .addColumns([
834
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
835
+ toColumn(
836
+ toFunction({ type: 'default', value: 'AVG' }, [toColumnRef(valueColumnName)]),
837
+ currentColumnId,
838
+ ),
839
+ ])
840
+ .addFrom([{ type: 'from', table: filterCte.name.value } as From])
841
+ .addGroupby({
842
+ columns: context.primaryKeyColumns.map((id) => toColumnRef(id)),
843
+ modifiers: [],
844
+ })
845
+ .build(),
846
+ )
847
+ .build();
848
+
849
+ const finalCte = new WithBuilder(context)
850
+ .addName(`cte_${currentColumnId}`)
851
+ .addStmt(
852
+ new SelectBuilder()
853
+ .addColumns([
854
+ toColumn(toColumnRef('*', RUNTIME_TABLE_NAME)),
855
+ toColumn(toColumnRef(currentColumnId)),
856
+ ])
857
+ .addFrom([
858
+ { type: 'from', table: RUNTIME_TABLE_NAME } as From,
859
+ toJoin(
860
+ 'INNER JOIN',
861
+ avgCte.name.value,
862
+ context.primaryKeyColumns
863
+ .map((id) =>
864
+ toBinaryExpression(
865
+ '=',
866
+ toColumnRef(id, RUNTIME_TABLE_NAME),
867
+ toColumnRef(id, avgCte.name.value),
868
+ ),
869
+ )
870
+ .reduce((acc, expr) => toBinaryExpression('AND', acc, expr)),
871
+ ),
872
+ ])
873
+ .build(),
874
+ )
875
+ .build();
876
+
877
+ // since this transformation depends on primary key columns, manually add primary columns to the dependencies
878
+ context.primaryKeyColumns.forEach((id) => context.addColumnDep(id));
879
+ const select = new SelectBuilder().addWith([unionCte, filterCte, avgCte, finalCte]).build();
880
+ return select;
881
+ }
882
+
883
+ /**
884
+ * Transforms `averageif` call expression to SQL expression. This performs average of the arguments
885
+ * which is different than SQL's AVG() function.
886
+ */
887
+ function averageIfTransform(
888
+ node: acorn.CallExpression,
889
+ context: ParseContext,
890
+ valueList: ExpressionValue[],
891
+ condition: ExpressionValue,
892
+ ): Select {
893
+ const conditionBuilder = (valueColumnName: string) => {
894
+ if (condition.type === 'string') {
895
+ const conditionWithLhs = `${valueColumnName} ${condition.value}`;
896
+ const localContext = new ParseContext({
897
+ columnConfigMap: {
898
+ ...context.columnConfigMap,
899
+ [valueColumnName]: {
900
+ columnName: valueColumnName,
901
+ columnType: MDM_COLUMN_TYPE.NUMBER,
902
+ columnMeta: { tableName: context.tableName },
903
+ },
904
+ },
905
+ currentColumnId: context.currentColumnId,
906
+ primaryKeyColumns: context.primaryKeyColumns,
907
+ tableName: context.tableName,
908
+ });
909
+ const parser = createJsToSqlParser(localContext);
910
+ return parser.parse(conditionWithLhs);
911
+ }
912
+ throw new Error('Invalid condition type');
913
+ };
914
+ return averageConditionalTransform(node, context, valueList, conditionBuilder);
915
+ }
916
+
917
+ /**
918
+ * Transforms `averagexneg` call expression to SQL expression. This performs average of the arguments
919
+ * which is different than SQL's AVG() function.
920
+ */
921
+ function averageXNegTransform(
922
+ node: acorn.CallExpression,
923
+ context: ParseContext,
924
+ valueList: ExpressionValue[],
925
+ ): Select {
926
+ averageXNegTransformValidator(context, valueList);
927
+ const conditionBuilder = (valueColumnName: string) => {
928
+ return toBinaryExpression('>=', toColumnRef(valueColumnName), { type: 'number', value: 0 });
929
+ };
930
+ return averageConditionalTransform(node, context, valueList, conditionBuilder);
931
+ }
932
+
933
+ /**
934
+ * Transforms `averagexzero` call expression to SQL expression. This performs average of the arguments
935
+ * which is different than SQL's AVG() function.
936
+ */
937
+ function averageXZeroTransform(
938
+ node: acorn.CallExpression,
939
+ context: ParseContext,
940
+ valueList: ExpressionValue[],
941
+ ): Select {
942
+ averageXZeroTransformValidator(context, valueList);
943
+ const conditionBuilder = (valueColumnName: string) => {
944
+ return toBinaryExpression('!=', toColumnRef(valueColumnName), { type: 'number', value: 0 });
945
+ };
946
+ return averageConditionalTransform(node, context, valueList, conditionBuilder);
947
+ }
948
+
949
+ /**
950
+ * Transforms `averagexzeroneg` call expression to SQL expression. This performs average of the arguments
951
+ * which is different than SQL's AVG() function.
952
+ */
953
+ function averageXZeroNegTransform(
954
+ node: acorn.CallExpression,
955
+ context: ParseContext,
956
+ valueList: ExpressionValue[],
957
+ ): Select {
958
+ averageXZeroNegTransformValidator(context, valueList);
959
+ const conditionBuilder = (valueColumnName: string) => {
960
+ return toBinaryExpression('>', toColumnRef(valueColumnName), { type: 'number', value: 0 });
961
+ };
962
+ return averageConditionalTransform(node, context, valueList, conditionBuilder);
963
+ }
964
+
965
+ /**
966
+ * Transforms `numbervalue` call expression to SQL expression.
967
+ */
968
+ function numberValueTransform(
969
+ node: acorn.CallExpression,
970
+ context: ParseContext,
971
+ ...args: ExpressionValue[]
972
+ ): ExpressionValue {
973
+ numberValueTransformValidator(context, args);
974
+ const [valueToCast, decimalSeparator, thousandSeparator] = args;
975
+ let expr = generateSqlQuery(valueToCast);
976
+ if (thousandSeparator) {
977
+ expr = `REPLACE(${expr}, '${generateSqlQuery(thousandSeparator)}', '')`;
978
+ }
979
+ if (decimalSeparator) {
980
+ expr = `REPLACE(${expr}, '${generateSqlQuery(decimalSeparator)}', '.')`;
981
+ }
982
+ return { type: 'sql_expr', value: `CAST(${expr} AS FLOAT)` };
983
+ }
984
+
985
+ /**
986
+ * Transforms `odd` call expression to SQL expression.
987
+ *
988
+ * Sql Expression:
989
+ * CASE WHEN arg % 2 = 1 THEN arg
990
+ * ELSE arg + 1
991
+ * END
992
+ */
993
+ function oddTransform(
994
+ node: acorn.CallExpression,
995
+ context: ParseContext,
996
+ arg: ExpressionValue,
997
+ ): ExpressionValue {
998
+ oddTransformValidator(context, arg);
999
+ const moduloCondition = toBinaryExpression('%', arg, { type: 'number', value: 2 });
1000
+ const isOdd = toBinaryExpression('=', moduloCondition, { type: 'number', value: 1 });
1001
+ const whenArg = toWhen(isOdd, arg);
1002
+ const elseArg = toElse(toBinaryExpression('+', arg, { type: 'number', value: 1 }));
1003
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
1004
+ }
1005
+
1006
+ /**
1007
+ * Transforms `even` call expression to SQL expression.
1008
+ *
1009
+ * Sql Expression:
1010
+ * CASE WHEN arg % 2 = 0 THEN arg
1011
+ * ELSE arg + 1
1012
+ * END
1013
+ */
1014
+ function evenTransform(
1015
+ node: acorn.CallExpression,
1016
+ context: ParseContext,
1017
+ ...args: ExpressionValue[]
1018
+ ): ExpressionValue {
1019
+ if (args.length !== 1) {
1020
+ throw new Error('EVEN() requires 1 argument');
1021
+ }
1022
+
1023
+ const arg = args[0];
1024
+ const sqlFnName = 'FLOOR';
1025
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1026
+ const floored = toFunction(name, [arg]);
1027
+
1028
+ const intCast = toFunction({ type: 'default', value: 'CAST' }, [
1029
+ toBinaryExpression('AS', floored, { type: 'default', value: 'INT' }),
1030
+ ]);
1031
+
1032
+ const moduloCondition = toBinaryExpression('%', intCast, { type: 'number', value: 2 });
1033
+ const isEven = toBinaryExpression('=', moduloCondition, { type: 'number', value: 0 });
1034
+
1035
+ const whenArg = toWhen(isEven, intCast);
1036
+ const elseArg = toElse(toBinaryExpression('+', intCast, { type: 'number', value: 1 }));
1037
+
1038
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
1039
+ }
1040
+
1041
+ /**
1042
+ * Transforms `IN(column, [value1, value2, ...])` call expression to SQL CASE expression.
1043
+ *
1044
+ * SQL:
1045
+ * CASE
1046
+ * WHEN column IN ('val1', 'val2', ...) THEN TRUE
1047
+ * ELSE FALSE
1048
+ * END
1049
+ */
1050
+ function inTransform(
1051
+ node: acorn.CallExpression,
1052
+ context: ParseContext,
1053
+ ...args: ExpressionValue[]
1054
+ ): ExpressionValue {
1055
+ inTransformValidator(context, args);
1056
+ const [column, values] = args;
1057
+ const inExpr: ExpressionValue = {
1058
+ type: 'binary_expr',
1059
+ operator: 'IN',
1060
+ left: column,
1061
+ right: {
1062
+ type: 'expr_list',
1063
+ value: values,
1064
+ },
1065
+ };
1066
+
1067
+ const whenClause = toWhen(inExpr, { type: 'number', value: 1 });
1068
+ const elseClause = toElse({ type: 'number', value: 0 });
1069
+
1070
+ return toCase({
1071
+ whenList: [whenClause],
1072
+ elseResult: elseClause,
1073
+ });
1074
+ }
1075
+
1076
+ /**
1077
+ * Transforms `sum` call expression to SQL expression.
1078
+ */
1079
+ function sumTransform(
1080
+ node: acorn.CallExpression,
1081
+ context: ParseContext,
1082
+ ...args: ExpressionValue[]
1083
+ ): ExpressionValue {
1084
+ sumTransformValidator(context, args);
1085
+ const argList = args.map((arg) => `COALESCE(${generateSqlQuery(arg)}, 0)`);
1086
+ return { type: 'sql_expr', value: `(${argList.join(' + ')})` };
1087
+ }
1088
+
1089
+ /**
1090
+ * Transforms `indexof` call expression to SQL expression.
1091
+ */
1092
+ function indexOfTransform(
1093
+ node: acorn.CallExpression,
1094
+ context: ParseContext,
1095
+ valueList: ExpressionValue[],
1096
+ valueToFind: ExpressionValue,
1097
+ ): ExpressionValue {
1098
+ indexOfTransformValidator(context, valueList, valueToFind);
1099
+ const whenList = valueList.map((value, index) =>
1100
+ toWhen(toBinaryExpression('=', value, valueToFind), { type: 'number', value: index }),
1101
+ );
1102
+ return toCase({ whenList, elseResult: toElse({ type: 'number', value: -1 }) });
1103
+ }
1104
+
1105
+ /**
1106
+ * Transforms `isblank` call expression to SQL expression.
1107
+ */
1108
+ function isEmptyTransform(
1109
+ node: acorn.CallExpression,
1110
+ context: ParseContext,
1111
+ arg: ExpressionValue,
1112
+ ): ExpressionValue {
1113
+ isEmptyTransformValidator(context, arg);
1114
+ const condition = toBinaryExpression('IS', arg, { type: 'null', value: null });
1115
+
1116
+ if (!context.ifCondition) {
1117
+ const whenArg = toWhen(
1118
+ condition,
1119
+ toFunction({ type: 'default', value: 'CAST' }, [
1120
+ toBinaryExpression('AS', { type: 'number', value: 1 }, { type: 'default', value: 'BIT' }),
1121
+ ]),
1122
+ );
1123
+
1124
+ const elseArg = toElse(
1125
+ toFunction({ type: 'default', value: 'CAST' }, [
1126
+ toBinaryExpression('AS', { type: 'number', value: 0 }, { type: 'default', value: 'BIT' }),
1127
+ ]),
1128
+ );
1129
+
1130
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
1131
+ }
1132
+
1133
+ return condition;
1134
+ }
1135
+
1136
+ /**
1137
+ * Transforms `isnumber` call expression to SQL expression.
1138
+ */
1139
+ function isNumberTransform(
1140
+ node: acorn.CallExpression,
1141
+ context: ParseContext,
1142
+ arg: ExpressionValue,
1143
+ ): ExpressionValue {
1144
+ isNumberTransformValidator(context, arg);
1145
+ const sqlFnName = 'ISNUMERIC';
1146
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1147
+ return toFunction(name, [arg]);
1148
+ }
1149
+
1150
+ /**
1151
+ * Transforms `MAX` call expression to aggregate `MAX` SQL expression
1152
+ */
1153
+ function maxAggregateTransform(
1154
+ node: acorn.CallExpression,
1155
+ context: ParseContext,
1156
+ ...args: ExpressionValue[]
1157
+ ): ExpressionValue {
1158
+ maxAggregateTransformValidator(context, args);
1159
+ const sqlFnName = 'GREATEST';
1160
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1161
+ return toFunction(name, args);
1162
+ }
1163
+
1164
+ /**
1165
+ * Transforms `MIN` call expression to aggregate `MIN` SQL expression
1166
+ */
1167
+ function minAggregateTransform(
1168
+ node: acorn.CallExpression,
1169
+ context: ParseContext,
1170
+ ...args: ExpressionValue[]
1171
+ ): ExpressionValue {
1172
+ minAggregateTransformValidator(context, args);
1173
+ const sqlFnName = 'LEAST';
1174
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1175
+ return toFunction(name, args);
1176
+ }
1177
+
1178
+ /**
1179
+ * Transforms `mid` call expression to SQL expression.
1180
+ */
1181
+ function midTransform(
1182
+ node: acorn.CallExpression,
1183
+ context: ParseContext,
1184
+ ...args: ExpressionValue[]
1185
+ ): ExpressionValue {
1186
+ midTransformValidator(context, args);
1187
+ const sqlFnName = 'SUBSTRING';
1188
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1189
+ if (args.length !== 3) {
1190
+ throw new Error('MID() requires 3 arguments');
1191
+ }
1192
+ return toFunction(name, args);
1193
+ }
1194
+
1195
+ /**
1196
+ * Transforms `median` call expression to SQL expression.
1197
+ */
1198
+ function medianTransform(
1199
+ node: acorn.CallExpression,
1200
+ context: ParseContext,
1201
+ ...args: ExpressionValue[]
1202
+ ): Select {
1203
+ medianTransformValidator(context, args);
1204
+ const currentColumnId = context.currentColumnId;
1205
+ const valueColumnName = formatDBEntityName(`val_${currentColumnId}`);
1206
+ const unionName = formatDBEntityName(`cte_union_${currentColumnId}`);
1207
+ const unionCte = new WithBuilder(context)
1208
+ .addName(unionName)
1209
+ .addStmt(
1210
+ args.reverse().reduce(
1211
+ (acc, arg) => {
1212
+ const builder = new SelectBuilder()
1213
+ .addColumns([
1214
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1215
+ toColumn(arg, valueColumnName),
1216
+ ])
1217
+ .addFrom([{ type: 'from', table: RUNTIME_TABLE_NAME } as From]);
1218
+
1219
+ if (acc) {
1220
+ builder.addSetOp('UNION ALL').addNext(acc);
1221
+ }
1222
+ return builder.build();
1223
+ },
1224
+ undefined as Select | undefined,
1225
+ )!,
1226
+ )
1227
+ .build();
1228
+
1229
+ const aggName = formatDBEntityName(`cte_agg_${currentColumnId}`);
1230
+ const aggCte = new WithBuilder(context)
1231
+ .addName(aggName)
1232
+ .addStmt(
1233
+ new SelectBuilder()
1234
+ .addDistinct()
1235
+ .addColumns([
1236
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1237
+ toColumn(
1238
+ {
1239
+ type: 'sql_expr',
1240
+ value: `PERCENTILE_CONT(0.50) WITHIN GROUP(ORDER BY ${valueColumnName}) OVER (PARTITION BY ${context.primaryKeyColumns.map((id) => id).join(', ')})`,
1241
+ },
1242
+ currentColumnId,
1243
+ ),
1244
+ ])
1245
+ .addFrom([{ type: 'from', table: unionCte.name.value } as From])
1246
+ .build(),
1247
+ )
1248
+ .build();
1249
+
1250
+ const finalCteName = formatDBEntityName(`cte_${currentColumnId}`);
1251
+ const finalCte = new WithBuilder(context)
1252
+ .addName(finalCteName)
1253
+ .addStmt(
1254
+ new SelectBuilder()
1255
+ .addColumns([
1256
+ toColumn(toColumnRef('*', RUNTIME_TABLE_NAME)),
1257
+ toColumn(toColumnRef(currentColumnId)),
1258
+ ])
1259
+ .addFrom([
1260
+ { type: 'from', table: RUNTIME_TABLE_NAME } as From,
1261
+ toJoin(
1262
+ 'INNER JOIN',
1263
+ aggCte.name.value,
1264
+ context.primaryKeyColumns
1265
+ .map((id) =>
1266
+ toBinaryExpression(
1267
+ '=',
1268
+ toColumnRef(id, RUNTIME_TABLE_NAME),
1269
+ toColumnRef(id, aggCte.name.value),
1270
+ ),
1271
+ )
1272
+ .reduce((acc, expr) => toBinaryExpression('AND', acc, expr)),
1273
+ ),
1274
+ ])
1275
+ .build(),
1276
+ )
1277
+ .build();
1278
+
1279
+ // since this transformation depends on primary key columns, manually add primary columns to the dependencies
1280
+ context.primaryKeyColumns.forEach((id) => context.addColumnDep(id));
1281
+ const select = new SelectBuilder().addWith([unionCte, aggCte, finalCte]).build();
1282
+ return select;
1283
+ }
1284
+
1285
+ /**
1286
+ * Transforms `MODE` call expression to SQL expression.
1287
+ */
1288
+ function modeTransform(
1289
+ node: acorn.CallExpression,
1290
+ context: ParseContext,
1291
+ ...args: ExpressionValue[]
1292
+ ): Select {
1293
+ modeTransformValidator(context, args);
1294
+ const currentColumnId = context.currentColumnId;
1295
+ const valueColumnName = `val_${currentColumnId}`;
1296
+ const freqColumn = `freq_${currentColumnId}`;
1297
+ const rankColumn = `rank_${currentColumnId}`;
1298
+
1299
+ // Step 1: Union CTE
1300
+ const unionCte = new WithBuilder(context)
1301
+ .addName(`cte_union_${currentColumnId}`)
1302
+ .addStmt(
1303
+ args.reverse().reduce(
1304
+ (acc, arg) => {
1305
+ const builder = new SelectBuilder()
1306
+ .addColumns([
1307
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1308
+ toColumn(arg, valueColumnName),
1309
+ ])
1310
+ .addFrom([
1311
+ {
1312
+ type: 'from',
1313
+ db: null,
1314
+ schema: undefined,
1315
+ table: RUNTIME_TABLE_NAME,
1316
+ as: null,
1317
+ },
1318
+ ]);
1319
+
1320
+ if (acc) {
1321
+ builder.addSetOp('UNION ALL').addNext(acc);
1322
+ }
1323
+
1324
+ return builder.build();
1325
+ },
1326
+ undefined as Select | undefined,
1327
+ )!,
1328
+ )
1329
+ .build();
1330
+
1331
+ // Step 2: Frequency CTE
1332
+ const freqCte = new WithBuilder(context)
1333
+ .addName(`cte_freq_${currentColumnId}`)
1334
+ .addStmt(
1335
+ new SelectBuilder()
1336
+ .addColumns([
1337
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1338
+ toColumn(toColumnRef(valueColumnName), valueColumnName),
1339
+ toColumn(
1340
+ {
1341
+ type: 'sql_expr',
1342
+ value: `COUNT(*) OVER (PARTITION BY ${context.primaryKeyColumns.join(', ')}, ${valueColumnName})`,
1343
+ },
1344
+ freqColumn,
1345
+ ),
1346
+ ])
1347
+ .addFrom([
1348
+ {
1349
+ type: 'from',
1350
+ db: null,
1351
+ schema: undefined,
1352
+ table: `cte_union_${currentColumnId}`,
1353
+ as: null,
1354
+ },
1355
+ ])
1356
+ .build(),
1357
+ )
1358
+ .build();
1359
+
1360
+ // Step 3: Ranked CTE
1361
+ const rankedCte = new WithBuilder(context)
1362
+ .addName(`cte_ranked_${currentColumnId}`)
1363
+ .addStmt(
1364
+ new SelectBuilder()
1365
+ .addColumns([
1366
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1367
+ toColumn(toColumnRef(valueColumnName), valueColumnName),
1368
+ toColumn(
1369
+ {
1370
+ type: 'sql_expr',
1371
+ value: `ROW_NUMBER() OVER (PARTITION BY ${context.primaryKeyColumns.join(', ')} ORDER BY ${freqColumn} DESC)`,
1372
+ },
1373
+ rankColumn,
1374
+ ),
1375
+ ])
1376
+ .addFrom([
1377
+ {
1378
+ type: 'from',
1379
+ db: null,
1380
+ schema: undefined,
1381
+ table: `cte_freq_${currentColumnId}`,
1382
+ as: null,
1383
+ },
1384
+ ])
1385
+ .build(),
1386
+ )
1387
+ .build();
1388
+
1389
+ // Step 4: Mode CTE (where rank = 1)
1390
+ const modeCte = new WithBuilder(context)
1391
+ .addName(`cte_mode_${currentColumnId}`)
1392
+ .addStmt(
1393
+ new SelectBuilder()
1394
+ .addColumns([
1395
+ ...context.primaryKeyColumns.map((id) => toColumn(toColumnRef(id))),
1396
+ toColumn(toColumnRef(valueColumnName)),
1397
+ ])
1398
+ .addFrom([
1399
+ {
1400
+ type: 'from',
1401
+ db: null,
1402
+ schema: undefined,
1403
+ table: `cte_ranked_${currentColumnId}`,
1404
+ as: null,
1405
+ },
1406
+ ])
1407
+ .addWhere({
1408
+ type: 'binary_expr',
1409
+ operator: '=',
1410
+ left: toColumnRef(rankColumn),
1411
+ right: { type: 'number', value: 1 },
1412
+ })
1413
+ .build(),
1414
+ )
1415
+ .build();
1416
+
1417
+ // Step 5: Final CTE (Join runtime table with mode)
1418
+ const finalCte = new WithBuilder(context)
1419
+ .addName(`cte_${currentColumnId}`)
1420
+ .addStmt(
1421
+ new SelectBuilder()
1422
+ .addColumns([
1423
+ toColumn(toColumnRef('*', RUNTIME_TABLE_NAME)),
1424
+ toColumn(toColumnRef(valueColumnName, `cte_mode_${currentColumnId}`), currentColumnId),
1425
+ ])
1426
+ .addFrom([
1427
+ {
1428
+ type: 'from',
1429
+ table: RUNTIME_TABLE_NAME,
1430
+ } as From,
1431
+ toJoin(
1432
+ 'INNER JOIN',
1433
+ `cte_mode_${currentColumnId}`,
1434
+ context.primaryKeyColumns
1435
+ .map((id) =>
1436
+ toBinaryExpression(
1437
+ '=',
1438
+ toColumnRef(id, RUNTIME_TABLE_NAME),
1439
+ toColumnRef(id, `cte_mode_${currentColumnId}`),
1440
+ ),
1441
+ )
1442
+ .reduce((acc, expr) => (acc ? toBinaryExpression('AND', acc, expr) : expr))!,
1443
+ ),
1444
+ ])
1445
+ .build(),
1446
+ )
1447
+ .build();
1448
+ context.primaryKeyColumns.forEach((id) => context.addColumnDep(id));
1449
+
1450
+ // Final SELECT with all CTEs
1451
+ return new SelectBuilder().addWith([unionCte, freqCte, rankedCte, modeCte, finalCte]).build();
1452
+ }
1453
+
1454
+ /**
1455
+ * Transforms `value` call expression to SQL expression.
1456
+ */
1457
+ function valueTransform(
1458
+ node: acorn.CallExpression,
1459
+ context: ParseContext,
1460
+ arg: ExpressionValue,
1461
+ ): ExpressionValue {
1462
+ valueTransformValidator(context, arg);
1463
+ const sqlFnName = 'CAST';
1464
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1465
+ return toFunction(name, [toBinaryExpression('AS', arg, { type: 'default', value: 'FLOAT' })]);
1466
+ }
1467
+
1468
+ /**
1469
+ * Transforms `pct` call expression to SQL expression.
1470
+ */
1471
+ function pctTransform(
1472
+ node: acorn.CallExpression,
1473
+ context: ParseContext,
1474
+ arg: ExpressionValue,
1475
+ ): PctObject {
1476
+ pctTransformValidator(context, arg);
1477
+ return new PctObject(arg);
1478
+ }
1479
+
1480
+ /**
1481
+ * Transforms `xor` call expression to SQL expression.
1482
+ */
1483
+ function xorTransform(
1484
+ node: acorn.CallExpression,
1485
+ context: ParseContext,
1486
+ ...args: ExpressionValue[]
1487
+ ): ExpressionValue {
1488
+ xorTransformValidator(context, args);
1489
+ const [arg1, arg2] = args;
1490
+ const notArg2 = toUnaryExpression('NOT', arg2);
1491
+ const andLeft = toBinaryExpression('AND', arg1, notArg2);
1492
+
1493
+ const notArg1 = toUnaryExpression('NOT', arg1);
1494
+ const andRight = toBinaryExpression('AND', notArg1, arg2);
1495
+
1496
+ // Combining with OR: (arg1 AND NOT arg2) OR (NOT arg1 AND arg2)
1497
+ const xorCondition = toBinaryExpression('OR', andLeft, andRight);
1498
+
1499
+ const whenArg = toWhen(xorCondition, { type: 'bit', value: 1 });
1500
+ const elseArg = toElse({ type: 'bit', value: 0 });
1501
+
1502
+ return toCase({ whenList: [whenArg], elseResult: elseArg });
1503
+ }
1504
+
1505
+ /**
1506
+ * Transforms `text` call expression to SQL expression.
1507
+ */
1508
+ function textTransform(
1509
+ node: acorn.CallExpression,
1510
+ context: ParseContext,
1511
+ ...args: ExpressionValue[]
1512
+ ): ExpressionValue {
1513
+ textTransformValidator(context, args);
1514
+ const sqlFnName = 'FORMAT';
1515
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1516
+ const [value, format, locale = { type: 'string', value: 'en-US' }] = args;
1517
+ const formatPattern = (format as Value).value as string;
1518
+ let resolvedPatternExp = format;
1519
+ if (!formatPattern.includes('#') && !formatPattern.includes('%')) {
1520
+ resolvedPatternExp = { type: 'string', value: datePattern(formatPattern) };
1521
+ }
1522
+ return toFunction(name, [value, resolvedPatternExp, locale]);
1523
+ }
1524
+
1525
+ export function datePattern(formatPattern: string): string {
1526
+ // https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
1527
+ const patterns: Record<string, string> = {
1528
+ yyyy: 'yyyy',
1529
+ yyy: 'yyyy',
1530
+ yy: 'yy',
1531
+ y: 'y',
1532
+ mmmm: 'MMMM',
1533
+ mmm: 'MMM',
1534
+ mm: 'MM',
1535
+ m: '%M',
1536
+ dddd: 'dddd',
1537
+ ddd: 'ddd',
1538
+ dd: 'dd',
1539
+ d: '%d',
1540
+ };
1541
+
1542
+ if (patterns[formatPattern]) {
1543
+ return patterns[formatPattern];
1544
+ }
1545
+
1546
+ // Convert date format patterns to their SQL equivalents
1547
+ return formatPattern.replace(/[dmy]{1,4}/g, (match: string) => {
1548
+ if (patterns[match]) {
1549
+ return patterns[match];
1550
+ }
1551
+ throw new Error(`Invalid date format pattern: ${match}`); // this should never happen unless the regex and pattern map are inconsistent
1552
+ });
1553
+ }
1554
+
1555
+ /**
1556
+ * Transforms `uuid` call expression to `NEWID()` SQL expression
1557
+ */
1558
+ function uuidTransform(_node: acorn.CallExpression, context: ParseContext): ExpressionValue {
1559
+ uuidTransformValidator(context);
1560
+ const sqlFnName = 'NEWID()';
1561
+ return { type: 'sql_expr', value: sqlFnName };
1562
+ }
1563
+
1564
+ /**
1565
+ * Transforms `IFNA` call expression to SQL expression.
1566
+ */
1567
+ function ifNullTransform(
1568
+ node: acorn.CallExpression,
1569
+ context: ParseContext,
1570
+ ...args: ExpressionValue[]
1571
+ ): ExpressionValue {
1572
+ const argsCount = args.length;
1573
+ if (argsCount < 2) {
1574
+ throw new Error('IFNA() requires 2 arguments');
1575
+ }
1576
+ const [value, fallback] = args;
1577
+ const response = toFunction({ type: 'default', value: 'COALESCE' }, [value, fallback]);
1578
+ return response;
1579
+ }
1580
+
1581
+ function fromExcelDateTransform(
1582
+ node: acorn.CallExpression,
1583
+ context: ParseContext,
1584
+ arg: ExpressionValue,
1585
+ ): ExpressionValue {
1586
+ fromExcelDateTransformValidator(context, arg);
1587
+ const correctedSerial = toBinaryExpression('-', arg, {
1588
+ type: 'number',
1589
+ value: 2,
1590
+ });
1591
+
1592
+ return toFunction({ type: 'default', value: 'DATEADD' }, [
1593
+ { type: 'default', value: 'DAY' },
1594
+ correctedSerial,
1595
+ { type: 'string', value: '1900-01-01' },
1596
+ ]);
1597
+ }
1598
+
1599
+ function hasTransform(
1600
+ node: acorn.CallExpression,
1601
+ context: ParseContext,
1602
+ args: ExpressionValue[],
1603
+ searchValue: ExpressionValue,
1604
+ ): ExpressionValue {
1605
+ hasTransformValidator(context, args, searchValue);
1606
+ const conditions = args.map((arg) => toBinaryExpression('=', arg, searchValue));
1607
+ const [first, ...rest] = conditions;
1608
+ const orCondition = rest.reduce((acc, cond) => toBinaryExpression('OR', acc, cond), first);
1609
+
1610
+ const whenClause = toWhen(orCondition, { type: 'number', value: 1 });
1611
+ const elseClause = toElse({ type: 'number', value: 0 });
1612
+
1613
+ return toCase({ whenList: [whenClause], elseResult: elseClause });
1614
+ }
1615
+
1616
+ function hasSomeTransform(
1617
+ node: acorn.CallExpression,
1618
+ context: ParseContext,
1619
+ args: ExpressionValue[],
1620
+ ...valueArray: ExpressionValue[]
1621
+ ): ExpressionValue {
1622
+ hasSomeTransformValidator(context, args, valueArray);
1623
+ const comparisons: Binary[] = [];
1624
+ args.forEach((col) => {
1625
+ valueArray.forEach((valGroup) => {
1626
+ if (Array.isArray(valGroup)) {
1627
+ valGroup.forEach((val) => {
1628
+ const comp = toBinaryExpression('=', col, val);
1629
+ comparisons.push(comp);
1630
+ });
1631
+ } else {
1632
+ const comp = toBinaryExpression('=', col, valGroup);
1633
+ comparisons.push(comp);
1634
+ }
1635
+ });
1636
+ });
1637
+
1638
+ const [first, ...rest] = comparisons;
1639
+ const orCondition = rest.reduce((acc, cond) => toBinaryExpression('OR', acc, cond), first);
1640
+ const whenClause = toWhen(orCondition, { type: 'number', value: 1 });
1641
+ const elseClause = toElse({ type: 'number', value: 0 });
1642
+
1643
+ return toCase({ whenList: [whenClause], elseResult: elseClause });
1644
+ }
1645
+
1646
+ function filterIfTransform(
1647
+ node: acorn.CallExpression,
1648
+ context: ParseContext,
1649
+ columnArg: ExpressionValue,
1650
+ conditionArg: ExpressionValue,
1651
+ ): ExpressionValue {
1652
+ filterIfTransformValidator(context, columnArg, conditionArg);
1653
+ const currentColumnId = context.currentColumnId;
1654
+ const valueColumnName = `val_${currentColumnId}`;
1655
+ const colName =
1656
+ columnArg.type === 'column_ref'
1657
+ ? (columnArg as ColumnRefItem).column
1658
+ : (columnArg as Value).value;
1659
+ const conditionBuilder = (valueColumnName: string) => {
1660
+ if (conditionArg.type === 'string') {
1661
+ const conditionWithLhs = `${colName} ${conditionArg.value}`;
1662
+ const localContext = new ParseContext({
1663
+ columnConfigMap: {
1664
+ ...context.columnConfigMap,
1665
+ [valueColumnName]: {
1666
+ columnName: valueColumnName,
1667
+ columnType: MDM_COLUMN_TYPE.NUMBER,
1668
+ columnMeta: { tableName: context.tableName },
1669
+ },
1670
+ },
1671
+ currentColumnId: context.currentColumnId,
1672
+ primaryKeyColumns: context.primaryKeyColumns,
1673
+ tableName: context.tableName,
1674
+ });
1675
+ const parser = createJsToSqlParser(localContext);
1676
+ return parser.parse(conditionWithLhs);
1677
+ }
1678
+ throw new Error('Invalid condition type');
1679
+ };
1680
+ const conditionExpr = conditionBuilder(valueColumnName);
1681
+ const whenClause = toWhen(conditionExpr, columnArg);
1682
+ const elseClause = toElse({ type: 'null', value: null });
1683
+
1684
+ return toCase({
1685
+ whenList: [whenClause],
1686
+ elseResult: elseClause,
1687
+ });
1688
+ }
1689
+
1690
+ function dateTransform(
1691
+ node: acorn.CallExpression,
1692
+ context: ParseContext,
1693
+ date: ExpressionValue,
1694
+ format: ExpressionValue,
1695
+ year: ExpressionValue,
1696
+ ): ExpressionValue {
1697
+ dateTransformValidator(context, date, format, year);
1698
+ if (!year) {
1699
+ const sqlFnName = 'FORMAT';
1700
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1701
+ return toFunction(name, [date, format]);
1702
+ }
1703
+ const sqlFnName = 'DATEFROMPARTS';
1704
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1705
+ return toFunction(name, [date, format, year]);
1706
+ }
1707
+
1708
+ function switchTransform(
1709
+ node: acorn.CallExpression,
1710
+ context: ParseContext,
1711
+ ...args: ExpressionValue[]
1712
+ ): ExpressionValue {
1713
+ switchTransformValidator(context, args);
1714
+ const switchExpr = args[0];
1715
+ const remainingArgs = args.slice(1);
1716
+ const whenList: {
1717
+ cond: Binary;
1718
+ result: ExpressionValue;
1719
+ type: 'when';
1720
+ }[] = [];
1721
+
1722
+ const hasElse = remainingArgs.length % 2 === 1;
1723
+ const elseValue = hasElse
1724
+ ? remainingArgs[remainingArgs.length - 1]
1725
+ : { type: 'null', value: null };
1726
+
1727
+ // Iterate over condition-result pairs
1728
+ remainingArgs.slice(0, hasElse ? -1 : undefined).forEach((arg, index, array) => {
1729
+ if (index % 2 === 0) {
1730
+ const matchValue = arg;
1731
+ const resultValue = array[index + 1];
1732
+ const condition = toBinaryExpression('=', switchExpr, matchValue);
1733
+ whenList.push({
1734
+ cond: condition,
1735
+ result: resultValue,
1736
+ type: 'when',
1737
+ });
1738
+ }
1739
+ });
1740
+
1741
+ return toCase({
1742
+ whenList,
1743
+ elseResult: toElse(elseValue),
1744
+ });
1745
+ }
1746
+
1747
+ function hasAllTransform(
1748
+ node: acorn.CallExpression,
1749
+ context: ParseContext,
1750
+ args: ExpressionValue[],
1751
+ ...valueArray: ExpressionValue[][]
1752
+ ): ExpressionValue {
1753
+ hasAllTransformValidator(context, args, valueArray);
1754
+ const andConditions: Binary[] = [];
1755
+
1756
+ valueArray.forEach((valGroup) => {
1757
+ const innerComparisons: Binary[] = [];
1758
+ if (Array.isArray(valGroup)) {
1759
+ valGroup.forEach((val) => {
1760
+ args.forEach((col) => {
1761
+ innerComparisons.push(toBinaryExpression('=', col, val));
1762
+ });
1763
+ });
1764
+ }
1765
+
1766
+ const [first, ...rest] = innerComparisons;
1767
+ const orCondition = rest.reduce((acc, cond) => toBinaryExpression('OR', acc, cond), first);
1768
+ andConditions.push(orCondition);
1769
+ });
1770
+
1771
+ const [firstAnd, ...restAnd] = andConditions;
1772
+ const finalCondition = restAnd.reduce(
1773
+ (acc, cond) => toBinaryExpression('AND', acc, cond),
1774
+ firstAnd,
1775
+ );
1776
+
1777
+ const whenClause = toWhen(finalCondition, { type: 'number', value: 1 });
1778
+ const elseClause = toElse({ type: 'number', value: 0 });
1779
+
1780
+ return toCase({ whenList: [whenClause], elseResult: elseClause });
1781
+ }
1782
+
1783
+ /**
1784
+ * Transforms `find` call expression to SQL expression.
1785
+ */
1786
+ function findTransform(
1787
+ node: acorn.CallExpression,
1788
+ context: ParseContext,
1789
+ ...args: ExpressionValue[]
1790
+ ): ExpressionValue {
1791
+ const sqlFnName = 'CHARINDEX';
1792
+ const name = { type: 'default', value: sqlFnName } as ValueExpr<string>;
1793
+ if (args.length < 2) {
1794
+ throw new Error('FIND() requires at least 2 arguments (substring, string)');
1795
+ }
1796
+
1797
+ return toFunction(name, args);
1798
+ }