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,65 @@
1
+ import { BaseUtilities } from '@epm-query-builder/base/BaseUtilities';
2
+ import { JoinPlan, JoinStep } from '@epm-query-builder/base/RelationshipResolver';
3
+ import { SOURCE_TYPES } from '@epm-query-builder/constants/Source';
4
+ import { EpmQueryBuilderOptions } from '@epm-query-builder/types/query-builder-types';
5
+
6
+ function quoteTable(tableName: string, databaseType?: string): string {
7
+ return BaseUtilities.formatTableNameForDb(tableName, databaseType);
8
+ }
9
+
10
+ function quoteColumn(table: string, column: string): string {
11
+ return BaseUtilities.formatTableColumnReference(table, column);
12
+ }
13
+
14
+ function buildXmlaColumnRef(table: string, column: string): string {
15
+ const fullId = `${table}[${column}]`;
16
+ return `"${fullId}"`;
17
+ }
18
+
19
+ function buildJoin(
20
+ step: JoinStep,
21
+ databaseType: string | undefined,
22
+ options?: EpmQueryBuilderOptions,
23
+ ): string {
24
+ const source = options ? BaseUtilities.getSource(options) : '';
25
+ const isXmla = source === SOURCE_TYPES.XMLA;
26
+
27
+ const leftExpr = isXmla
28
+ ? buildXmlaColumnRef(step.leftTable, step.leftColumn)
29
+ : quoteColumn(step.leftTable, step.leftColumn);
30
+ const rightExpr = isXmla
31
+ ? buildXmlaColumnRef(step.rightTable, step.rightColumn)
32
+ : quoteColumn(step.rightTable, step.rightColumn);
33
+
34
+ let rightSource = quoteTable(step.rightTable, databaseType);
35
+ if (step.dedupeRightOn) {
36
+ if (isXmla) {
37
+ const keyRef = buildXmlaColumnRef(step.rightTable, step.dedupeRightOn);
38
+ const fullId = `${step.rightTable}[${step.dedupeRightOn}]`;
39
+ rightSource = `(
40
+ SELECT DISTINCT ${keyRef} AS "${fullId}"
41
+ FROM ${quoteTable(step.rightTable, databaseType)}
42
+ ) ${quoteTable(step.rightTable, databaseType)}`;
43
+ } else {
44
+ const keyCol = quoteColumn(step.rightTable, step.dedupeRightOn);
45
+ rightSource = `(
46
+ SELECT DISTINCT ${keyCol} AS "${step.dedupeRightOn}"
47
+ FROM ${quoteTable(step.rightTable, databaseType)}
48
+ ) ${quoteTable(step.rightTable, databaseType)}`;
49
+ }
50
+ }
51
+
52
+ const on = `${leftExpr} = ${rightExpr}`;
53
+ return `${step.joinType} JOIN ${rightSource} ON ${on}`;
54
+ }
55
+
56
+ export class DuckDbJoinBuilder {
57
+ static buildJoinClauses(
58
+ plan: JoinPlan,
59
+ databaseType: string | undefined,
60
+ options?: EpmQueryBuilderOptions,
61
+ ): string[] {
62
+ if (!plan.joins?.length) return [];
63
+ return plan.joins.map((j) => buildJoin(j, databaseType, options));
64
+ }
65
+ }
@@ -0,0 +1,626 @@
1
+ import { BaseMeasureBuilder } from '@epm-query-builder/base/BaseMeasureBuilder';
2
+ import {
3
+ MeasureConfig,
4
+ FilterConfig,
5
+ EpmQueryBuilderOptions,
6
+ SuperFilterChild,
7
+ } from '@epm-query-builder/types/query-builder-types';
8
+ import { BaseUtilities } from '@epm-query-builder/base/BaseUtilities';
9
+ import { BaseAnalyticalFunctions } from '@epm-query-builder/base/BaseAnalyticalFunctions';
10
+ import { BaseAdvancedAggregations } from '@epm-query-builder/base/BaseAdvancedAggregations';
11
+ import { DuckDbAdvancedAggregations } from './DuckDbAdvancedAggregations';
12
+ import { SOURCE_TYPES } from '@epm-query-builder/constants/Source';
13
+
14
+ export class DuckDbMeasureBuilder extends BaseMeasureBuilder {
15
+ private analyticalFunctions: BaseAnalyticalFunctions;
16
+ private advancedAggregations: BaseAdvancedAggregations;
17
+ private duckDbAdvancedAggregations: DuckDbAdvancedAggregations;
18
+
19
+ constructor(options: EpmQueryBuilderOptions) {
20
+ super(options);
21
+ this.analyticalFunctions = new BaseAnalyticalFunctions(options);
22
+ this.advancedAggregations = new BaseAdvancedAggregations(options);
23
+ this.duckDbAdvancedAggregations = new DuckDbAdvancedAggregations(options);
24
+ }
25
+
26
+ buildMeasureSelects(): string[] {
27
+ const measures = this.getMeasures();
28
+ const columns: string[] = [];
29
+
30
+ measures.forEach((measure) => {
31
+ let measureExpr: string;
32
+ if (this.isMeasureType(measure)) {
33
+ measureExpr = this.buildCalculatedMeasure(measure);
34
+ } else if (this.isAnalyticalMeasure(measure)) {
35
+ measureExpr = this.buildAnalyticalMeasure(measure, measure.aggregationType || 'SUM');
36
+ } else if (this.isAdvancedAggregation(measure)) {
37
+ measureExpr = this.buildAdvancedAggregation(measure, measure.aggregationType || 'SUM');
38
+ } else {
39
+ measureExpr = this.buildDuckDbAggregation(measure);
40
+ }
41
+
42
+ columns.push(measureExpr);
43
+
44
+ if (this.options.isCrossHighlightEnabled && this.hasCrossHighlightFilters()) {
45
+ const crossHighlightExpr = this.buildCrossHighlightMeasure(measure);
46
+ const alias = measure.label || measure.columnName;
47
+ columns.push(`${crossHighlightExpr} AS "${alias}_CrossHighlight"`);
48
+ }
49
+ });
50
+
51
+ this.addCustomMeasures(columns);
52
+ this.addDimensionIdMeasures(columns);
53
+
54
+ return columns;
55
+ }
56
+
57
+ buildMeasureFilterCTE(): string {
58
+ const measureFilters = this.extractMeasureFilters();
59
+
60
+ if (measureFilters.length === 0) {
61
+ return '';
62
+ }
63
+
64
+ const filterCTEs = measureFilters
65
+ .filter((filter): filter is FilterConfig => 'filterType' in filter)
66
+ .map((filter, index) => this.generateMeasureFilterCTE(filter, index + 1));
67
+
68
+ return filterCTEs.join(',\n');
69
+ }
70
+
71
+ buildHavingClause(): string {
72
+ const measureFilters = this.extractMeasureFilters();
73
+
74
+ if (measureFilters.length === 0) {
75
+ return '';
76
+ }
77
+
78
+ const conditions = measureFilters
79
+ .filter((filter): filter is FilterConfig => 'filterType' in filter)
80
+ .map((filter) => this.buildMeasureFilterCondition(filter))
81
+ .filter((condition) => condition);
82
+
83
+ if (conditions.length === 0) {
84
+ return '';
85
+ }
86
+
87
+ const combined = BaseUtilities.combineConditions(conditions, 'AND');
88
+
89
+ const dims = this.extractDimensions();
90
+ if (dims.length > 0) {
91
+ const isLeaf = dims.map((d) => `GROUPING(${d}) = 0`).join(' AND ');
92
+ return `((${isLeaf}) AND (${combined})) OR (NOT (${isLeaf}))`;
93
+ }
94
+
95
+ return combined;
96
+ }
97
+
98
+ protected buildDuckDbAggregation(measure: MeasureConfig): string {
99
+ const aggregationType = this.mapDuckDbAggregationType(measure.aggregationType);
100
+ const originalAgg = measure.aggregationType?.toUpperCase();
101
+ const hasAggregation = measure.aggregationType && measure.aggregationType.trim() !== '';
102
+
103
+ const source = BaseUtilities.getSource(this.options);
104
+ const isBlend = source === SOURCE_TYPES.BLEND;
105
+ if (isBlend) {
106
+ const actualColumnId = BaseUtilities.extractColumnIdFromHierarchy(measure.id);
107
+ const tableForBlend =
108
+ measure.tableName || BaseUtilities.getDefaultTableNameFromOptions(this.options) || '';
109
+
110
+ // If no aggregation type, use column name directly without prefix for the alias
111
+ // but still apply SUM for the aggregation to satisfy GROUP BY requirements
112
+ const aliasName = hasAggregation
113
+ ? `${(measure.aggregationType || aggregationType).toUpperCase()}__${actualColumnId}`
114
+ : actualColumnId;
115
+ const qualifiedInnerRef = `"${tableForBlend}"."${aliasName}"`;
116
+
117
+ // If no aggregation type, apply SUM but use clean alias without prefix
118
+ if (!hasAggregation) {
119
+ return `SUM(${qualifiedInnerRef}) as "${aliasName}"`;
120
+ }
121
+
122
+ if (originalAgg === 'FIRST') {
123
+ return `MIN(${qualifiedInnerRef}) as "${aliasName}"`;
124
+ }
125
+ if (originalAgg === 'LAST') {
126
+ return `MAX(${qualifiedInnerRef}) as "${aliasName}"`;
127
+ }
128
+ if (
129
+ originalAgg === 'COUNT_DISTINCT' ||
130
+ originalAgg === 'DISTINCT_COUNT' ||
131
+ originalAgg === 'DISTINCTCOUNT'
132
+ ) {
133
+ return `COUNT(DISTINCT ${qualifiedInnerRef}) as "${aliasName}"`;
134
+ }
135
+
136
+ return `${aggregationType}(${qualifiedInnerRef}) as "${aliasName}"`;
137
+ }
138
+
139
+ let columnRef: string;
140
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
141
+ const actualColumnId = BaseUtilities.extractColumnIdFromHierarchy(measure.id);
142
+ columnRef = `"${actualColumnId}"`;
143
+ } else {
144
+ const actualColumnId = BaseUtilities.extractColumnIdFromHierarchy(measure.id);
145
+ const effectiveColumnName = measure.columnName || actualColumnId;
146
+ columnRef = `${measure.tableName}.${effectiveColumnName}`;
147
+ }
148
+
149
+ let aggregationExpr: string;
150
+
151
+ // If no aggregation type, apply default SUM aggregation but use clean alias without prefix
152
+ if (!hasAggregation) {
153
+ aggregationExpr = `SUM(${columnRef}) as ${columnRef}`;
154
+ } else if (originalAgg === 'FIRST') {
155
+ aggregationExpr = `MIN(${columnRef}) as "FIRST__${columnRef.replace(/"/g, '')}"`;
156
+ } else if (originalAgg === 'LAST') {
157
+ aggregationExpr = `MAX(${columnRef}) as "LAST__${columnRef.replace(/"/g, '')}"`;
158
+ } else {
159
+ switch (aggregationType) {
160
+ case 'COUNT':
161
+ if (
162
+ measure.aggregationType?.toUpperCase() === 'COUNT_DISTINCT' ||
163
+ measure.aggregationType?.toUpperCase() === 'DISTINCT_COUNT' ||
164
+ measure.aggregationType?.toUpperCase() === 'DISTINCTCOUNT'
165
+ ) {
166
+ aggregationExpr = `COUNT(DISTINCT ${columnRef}) as "COUNT_DISTINCT__${columnRef.replace(/"/g, '')}"`;
167
+ } else {
168
+ aggregationExpr = `COUNT(${columnRef}) as "COUNT__${columnRef.replace(/"/g, '')}"`;
169
+ }
170
+ break;
171
+ case 'STRING_AGG':
172
+ aggregationExpr = `STRING_AGG(${columnRef}) as "STRING_AGG__${columnRef.replace(/"/g, '')}"`;
173
+ break;
174
+ case 'MEDIAN':
175
+ aggregationExpr = `MEDIAN(${columnRef}) as "MEDIAN__${columnRef.replace(/"/g, '')}"`;
176
+ break;
177
+ case 'MODE':
178
+ aggregationExpr = `MODE(${columnRef}) as "MODE__${columnRef.replace(/"/g, '')}"`;
179
+ break;
180
+ case 'CONCATENATED':
181
+ aggregationExpr = `STRING_AGG(${columnRef}) as "CONCATENATED__${columnRef.replace(/"/g, '')}"`;
182
+ break;
183
+ case 'CONCATENATED_IDS':
184
+ aggregationExpr = `STRING_AGG(${columnRef}) as "CONCATENATED_IDS__${columnRef.replace(/"/g, '')}"`;
185
+ break;
186
+ default:
187
+ aggregationExpr = `${aggregationType}(${columnRef}) as "${aggregationType}__${columnRef.replace(/"/g, '')}"`;
188
+ }
189
+ }
190
+
191
+ if (this.needsFilterClause()) {
192
+ const filterCondition = this.buildMeasureFilterForAggregation();
193
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
194
+ return `${aggregationExpr} FILTER (WHERE ${filterCondition})`;
195
+ }
196
+ return `${aggregationExpr} FILTER (WHERE ${filterCondition}) AS "${measure.label}"`;
197
+ }
198
+
199
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
200
+ return aggregationExpr;
201
+ }
202
+ return `${aggregationExpr} AS "${measure.label}"`;
203
+ }
204
+
205
+ private mapDuckDbAggregationType(aggregationType?: string): string {
206
+ if (!aggregationType) return 'SUM';
207
+
208
+ const duckDbMapping: Record<string, string> = {
209
+ SUM: 'SUM',
210
+ COUNT: 'COUNT',
211
+ AVG: 'AVG',
212
+ MIN: 'MIN',
213
+ MAX: 'MAX',
214
+
215
+ FIRST: 'MIN',
216
+ LAST: 'MAX',
217
+ STANDARD_DEVIATION: 'STDDEV_POP',
218
+ VARIANCE: 'VAR_POP',
219
+ COUNT_DISTINCT: 'COUNT',
220
+ MEDIAN: 'MEDIAN',
221
+ MODE: 'MODE',
222
+
223
+ CONCATENATED: 'STRING_AGG',
224
+ CONCATENATED_IDS: 'STRING_AGG',
225
+
226
+ DISTINCTCOUNT: 'COUNT',
227
+ DISTINCT_COUNT: 'COUNT',
228
+ COUNTA: 'COUNT',
229
+ AVERAGE: 'AVG',
230
+ MINIMUM: 'MIN',
231
+ MAXIMUM: 'MAX',
232
+ };
233
+
234
+ return duckDbMapping[aggregationType.toUpperCase()] || aggregationType.toUpperCase();
235
+ }
236
+
237
+ private extractMeasureFilters(): FilterConfig[] {
238
+ if (!this.options.superFilters?.children) {
239
+ return [];
240
+ }
241
+
242
+ const measureFilters: FilterConfig[] = [];
243
+ this.extractMeasureFiltersRecursively(this.options.superFilters.children, measureFilters);
244
+ return measureFilters;
245
+ }
246
+
247
+ private extractMeasureFiltersRecursively(
248
+ children: SuperFilterChild[],
249
+ measureFilters: FilterConfig[],
250
+ ): void {
251
+ children.forEach((child) => {
252
+ if (child.filters) {
253
+ const filter = child.filters;
254
+ const isMeasureFilterType = filter.filterType === 'MEASURE';
255
+ const hasMeasureColumnType = Array.isArray(filter.columnType)
256
+ ? filter.columnType.some((type) => type?.toUpperCase() === 'MEASURE')
257
+ : false;
258
+ const isAggregatedRange =
259
+ filter.filterType === 'RANGE' &&
260
+ Array.isArray(filter.aggregationType) &&
261
+ filter.aggregationType.length > 0;
262
+
263
+ if (isMeasureFilterType || hasMeasureColumnType || isAggregatedRange) {
264
+ measureFilters.push(filter);
265
+ }
266
+ }
267
+
268
+ if (child.children) {
269
+ this.extractMeasureFiltersRecursively(child.children, measureFilters);
270
+ }
271
+ });
272
+ }
273
+
274
+ private generateMeasureFilterCTE(filter: FilterConfig, counter: number): string {
275
+ const variableName = `measure_filter_${counter}`;
276
+
277
+ if (!filter.rangeFilter?.length) {
278
+ return '';
279
+ }
280
+
281
+ const tableName = filter.tableNames?.[0] || '';
282
+ const aggregationType = this.mapDuckDbAggregationType(filter.aggregationType?.[0]);
283
+ const columnRef = this.resolveColumnRef(filter);
284
+
285
+ const conditions = this.buildRangeConditionsForAggregation(columnRef, filter, aggregationType);
286
+
287
+ const dims = this.extractDimensions();
288
+ if (dims.length === 0) {
289
+ return '';
290
+ }
291
+ const selectDims = dims.map((d, idx) => `${d} AS __mf_dim${idx + 1}`).join(', ');
292
+ const groupByDims = dims.join(', ');
293
+
294
+ return `${variableName} AS (
295
+ SELECT ${selectDims}
296
+ FROM ${tableName}
297
+ GROUP BY ${groupByDims}
298
+ HAVING ${conditions.join(' AND ')}
299
+ )`;
300
+ }
301
+
302
+ private buildMeasureFilterCondition(filter: FilterConfig): string {
303
+ if (!filter.rangeFilter?.length) {
304
+ return '';
305
+ }
306
+
307
+ const aggregationType = this.mapDuckDbAggregationType(filter.aggregationType?.[0]);
308
+ const columnRef = this.resolveColumnRef(filter);
309
+ const conditions = this.buildRangeConditionsForAggregation(columnRef, filter, aggregationType);
310
+ return conditions.length === 1 ? conditions[0] : `(${conditions.join(' AND ')})`;
311
+ }
312
+
313
+ private needsFilterClause(): boolean {
314
+ return false;
315
+ }
316
+
317
+ private buildMeasureFilterForAggregation(): string {
318
+ return '1=1';
319
+ }
320
+
321
+ buildMeasureFilterWhereClause(): string {
322
+ const measureFilters = this.extractMeasureFilters();
323
+ if (measureFilters.length === 0) {
324
+ return '';
325
+ }
326
+ const dims = this.extractDimensions();
327
+ if (dims.length === 0) {
328
+ return '';
329
+ }
330
+
331
+ const clauses: string[] = [];
332
+ let counter = 0;
333
+ for (const f of measureFilters) {
334
+ if (!('filterType' in f)) continue;
335
+ if (!f.rangeFilter?.length) continue;
336
+ counter += 1;
337
+ const eqs = dims.map((d, idx) => `${d} = mf.__mf_dim${idx + 1}`).join(' AND ');
338
+ clauses.push(`EXISTS (SELECT 1 FROM measure_filter_${counter} mf WHERE ${eqs})`);
339
+ }
340
+ return BaseUtilities.combineConditions(clauses.filter(Boolean), 'AND');
341
+ }
342
+
343
+ private extractDimensions(): string[] {
344
+ const rowDimensions = BaseUtilities.buildDimensionReference(this.options.rows, this.options);
345
+ const columnDimensions = BaseUtilities.buildDimensionReference(
346
+ this.options.columns,
347
+ this.options,
348
+ );
349
+ return [...rowDimensions, ...columnDimensions];
350
+ }
351
+
352
+ private combineFilterConditions(conditions: string[], operator: 'AND' | 'OR' = 'AND'): string {
353
+ return BaseUtilities.combineConditions(conditions, operator);
354
+ }
355
+
356
+ private hasCrossHighlightFilters(): boolean {
357
+ return Boolean(
358
+ this.options.superFilters?.children?.some(
359
+ (child) =>
360
+ Array.isArray(child.filters) &&
361
+ child.filters.some((filter: FilterConfig) => filter.isCrossHighlight),
362
+ ),
363
+ );
364
+ }
365
+
366
+ private buildCrossHighlightMeasure(measure: MeasureConfig): string {
367
+ const baseAggregation = this.buildDuckDbAggregation(measure);
368
+ const crossHighlightConditions = this.getCrossHighlightConditions();
369
+
370
+ if (!crossHighlightConditions.length) {
371
+ return baseAggregation;
372
+ }
373
+
374
+ const whereClause = crossHighlightConditions.join(' AND ');
375
+ return `CASE WHEN ${whereClause} THEN ${baseAggregation} ELSE NULL END`;
376
+ }
377
+
378
+ private getCrossHighlightConditions(): string[] {
379
+ const conditions: string[] = [];
380
+
381
+ if (!this.options.superFilters?.children) return conditions;
382
+
383
+ this.options.superFilters.children.forEach((child) => {
384
+ if (Array.isArray(child.filters)) {
385
+ child.filters.forEach((filter: FilterConfig) => {
386
+ if (
387
+ filter.isCrossHighlight &&
388
+ filter.columnNames?.length &&
389
+ filter.valuesFilter?.length
390
+ ) {
391
+ const tableName =
392
+ filter.tableNames?.[0] || BaseUtilities.getDefaultTableNameFromOptions(this.options);
393
+ const columnName = filter.columnNames[0];
394
+ const columnRef = `"${tableName}"."${columnName}"`;
395
+
396
+ if (filter.valuesFilter.length === 1) {
397
+ const value = filter.valuesFilter[0];
398
+ const displayValue = typeof value === 'object' ? value.id : value;
399
+ conditions.push(`${columnRef} = '${displayValue}'`);
400
+ } else {
401
+ const valuesList = filter.valuesFilter
402
+ .map((v) => `'${typeof v === 'object' ? v.id : v}'`)
403
+ .join(', ');
404
+ conditions.push(`${columnRef} IN (${valuesList})`);
405
+ }
406
+ }
407
+ });
408
+ }
409
+ });
410
+
411
+ return conditions;
412
+ }
413
+
414
+ private addCustomMeasures(columns: string[]): void {
415
+ if (!this.options.customMeasures?.length) return;
416
+
417
+ this.options.customMeasures.forEach((customMeasure) => {
418
+ const duckDbMeasure = this.convertCustomMeasure(customMeasure);
419
+ if (duckDbMeasure && duckDbMeasure.trim()) {
420
+ columns.push(duckDbMeasure);
421
+ }
422
+ });
423
+ }
424
+
425
+ private convertCustomMeasure(measure: string): string {
426
+ if (measure.includes('CONCATENATEX')) {
427
+ const convertedMeasure = measure
428
+ .replace(/CONCATENATEX\s*\(/gi, 'STRING_AGG(')
429
+ .replace(/,\s*","\s*\)/gi, ", ',' ORDER BY 1)")
430
+ .replace(/,\s*"\|"\s*\)/gi, ", '|' ORDER BY 1)");
431
+ return convertedMeasure;
432
+ }
433
+
434
+ return measure;
435
+ }
436
+
437
+ private addDimensionIdMeasures(columns: string[]): void {
438
+ if (!this.options.dimensionIdMappings) return;
439
+
440
+ Object.entries(this.options.dimensionIdMappings).forEach(([tableName, mappings]) => {
441
+ if (mappings && mappings.length > 0) {
442
+ const concatenatedColumns = mappings
443
+ .map((mapping) => `"${tableName}"."${mapping.targetField}"`)
444
+ .join(" || '|' || ");
445
+
446
+ columns.push(`${concatenatedColumns} AS "${tableName}_ConcatenatedIds"`);
447
+ }
448
+ });
449
+ }
450
+
451
+ private isAnalyticalMeasure(measure: MeasureConfig): boolean {
452
+ const analyticalTypes = [
453
+ 'ROW_NUMBER',
454
+ 'RANK',
455
+ 'DENSE_RANK',
456
+ 'PERCENT_RANK',
457
+ 'LAG',
458
+ 'LEAD',
459
+ 'CUMULATIVE_SUM',
460
+ 'CUMULATIVE_COUNT',
461
+ 'MOVING_AVG',
462
+ 'PERCENTILE_25',
463
+ 'PERCENTILE_75',
464
+ 'MEDIAN',
465
+ ];
466
+ return Boolean(
467
+ measure.aggregationType && analyticalTypes.includes(measure.aggregationType.toUpperCase()),
468
+ );
469
+ }
470
+
471
+ private isAdvancedAggregation(measure: MeasureConfig): boolean {
472
+ const advancedTypes = [
473
+ 'ARRAY_AGG',
474
+ 'ARRAY_AGG_DISTINCT',
475
+ 'JSON_AGG',
476
+ 'APPROX_COUNT_DISTINCT',
477
+ 'PRODUCT',
478
+ 'GEOMETRIC_MEAN',
479
+ 'HARMONIC_MEAN',
480
+ 'MAD',
481
+ 'STRING_AGG_SEMICOLON',
482
+ 'STRING_AGG_PIPE',
483
+ 'HISTOGRAM',
484
+ 'QUANTILES',
485
+ 'RESERVOIR_SAMPLE',
486
+ 'BIT_AND',
487
+ 'BIT_OR',
488
+ 'BIT_XOR',
489
+ 'BOOL_AND',
490
+ 'BOOL_OR',
491
+ ];
492
+ return Boolean(
493
+ measure.aggregationType && advancedTypes.includes(measure.aggregationType.toUpperCase()),
494
+ );
495
+ }
496
+
497
+ buildAnalyticalMeasure(measure: MeasureConfig, analyticalType: string): string {
498
+ switch (analyticalType.toUpperCase()) {
499
+ case 'ROW_NUMBER':
500
+ return this.analyticalFunctions.buildRankingFunction(measure, 'ROW_NUMBER');
501
+ case 'RANK':
502
+ return this.analyticalFunctions.buildRankingFunction(measure, 'RANK');
503
+ case 'DENSE_RANK':
504
+ return this.analyticalFunctions.buildRankingFunction(measure, 'DENSE_RANK');
505
+ case 'PERCENT_RANK':
506
+ return this.analyticalFunctions.buildRankingFunction(measure, 'PERCENT_RANK');
507
+ case 'LAG':
508
+ return this.analyticalFunctions.buildLagLeadFunction(measure, 'LAG', 1);
509
+ case 'LEAD':
510
+ return this.analyticalFunctions.buildLagLeadFunction(measure, 'LEAD', 1);
511
+ case 'CUMULATIVE_SUM':
512
+ return this.analyticalFunctions.buildCumulativeAggregation(measure, 'SUM');
513
+ case 'CUMULATIVE_COUNT':
514
+ return this.analyticalFunctions.buildCumulativeAggregation(measure, 'COUNT');
515
+ case 'MOVING_AVG':
516
+ return this.analyticalFunctions.buildMovingAggregation(measure, 'AVG', 3);
517
+ case 'PERCENTILE_25':
518
+ return this.analyticalFunctions.buildPercentileFunction(measure, 0.25);
519
+ case 'PERCENTILE_75':
520
+ return this.analyticalFunctions.buildPercentileFunction(measure, 0.75);
521
+ case 'MEDIAN':
522
+ return this.buildDuckDbAggregation(measure);
523
+ default:
524
+ return this.buildDuckDbAggregation(measure);
525
+ }
526
+ }
527
+
528
+ buildAdvancedAggregation(
529
+ measure: MeasureConfig,
530
+ aggregationType: string,
531
+ options?: Record<string, unknown>,
532
+ ): string {
533
+ switch (aggregationType.toUpperCase()) {
534
+ case 'STRING_AGG_SEMICOLON':
535
+ return this.advancedAggregations.buildStringAggregation(measure, '; ', false);
536
+ case 'STRING_AGG_PIPE':
537
+ return this.advancedAggregations.buildStringAggregation(measure, ' | ', false);
538
+ case 'HISTOGRAM': {
539
+ const binCount = (options?.binCount as number) || 10;
540
+ return this.duckDbAdvancedAggregations.buildHistogramAggregation(measure, binCount);
541
+ }
542
+ case 'QUANTILES': {
543
+ const quantiles = (options?.quantiles as number[]) || [0.25, 0.5, 0.75];
544
+ return this.duckDbAdvancedAggregations.buildQuantileAggregation(measure, quantiles);
545
+ }
546
+ case 'RESERVOIR_SAMPLE': {
547
+ const sampleSize = (options?.sampleSize as number) || 100;
548
+ return this.duckDbAdvancedAggregations.buildReservoirSample(measure, sampleSize);
549
+ }
550
+ case 'BIT_AND':
551
+ return this.advancedAggregations.buildBitAggregations(measure, 'AND');
552
+ case 'BIT_OR':
553
+ return this.advancedAggregations.buildBitAggregations(measure, 'OR');
554
+ case 'BIT_XOR':
555
+ return this.advancedAggregations.buildBitAggregations(measure, 'XOR');
556
+ case 'BOOL_AND':
557
+ return this.advancedAggregations.buildBoolAggregations(measure, 'AND');
558
+ case 'BOOL_OR':
559
+ return this.advancedAggregations.buildBoolAggregations(measure, 'OR');
560
+ default:
561
+ return this.buildDuckDbAggregation(measure);
562
+ }
563
+ }
564
+
565
+ private resolveColumnRef(filter: FilterConfig): string {
566
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
567
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
568
+ filter.columnIds?.[0] ||
569
+ `${filter.tableNames?.[0] || ''}[${filter.columnNames?.[0] || ''}]`,
570
+ );
571
+ return `"${actualId}"`;
572
+ }
573
+ const tableName = filter.tableNames?.[0] || '';
574
+ const column = filter.columnNames?.[0] || '';
575
+ return `${tableName}.${column}`;
576
+ }
577
+
578
+ private buildRangeConditionsForAggregation(
579
+ columnRef: string,
580
+ filter: FilterConfig,
581
+ aggregationType: string,
582
+ ): string[] {
583
+ const conditions: string[] = [];
584
+ filter.rangeFilter?.forEach((range) => {
585
+ const rangeParts: string[] = [];
586
+ if (range.from !== undefined && range.fromCondition) {
587
+ const op = this.normalizeOperator(range.fromCondition);
588
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
589
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op}`);
590
+ } else {
591
+ const value = typeof range.from === 'string' ? `'${range.from}'` : range.from;
592
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op} ${value}`);
593
+ }
594
+ }
595
+ if (range.to !== undefined && range.toCondition) {
596
+ const op = this.normalizeOperator(range.toCondition);
597
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
598
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op}`);
599
+ } else {
600
+ const value = typeof range.to === 'string' ? `'${range.to}'` : range.to;
601
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op} ${value}`);
602
+ }
603
+ }
604
+ if (rangeParts.length === 0 && range.operator && range.value !== undefined) {
605
+ const op = this.normalizeOperator(range.operator);
606
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
607
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op}`);
608
+ } else {
609
+ const value = typeof range.value === 'string' ? `'${range.value}'` : range.value;
610
+ rangeParts.push(`${aggregationType}(${columnRef}) ${op} ${value}`);
611
+ }
612
+ }
613
+ if (rangeParts.length > 0) {
614
+ const joiner = (range.logicalOperator || 'AND').toUpperCase() === 'OR' ? 'OR' : 'AND';
615
+ conditions.push(
616
+ rangeParts.length === 1 ? rangeParts[0] : `(${rangeParts.join(` ${joiner} `)})`,
617
+ );
618
+ }
619
+ });
620
+ return conditions;
621
+ }
622
+
623
+ private normalizeOperator(op: string): string {
624
+ return BaseUtilities.normalizeComparisonOperator(op);
625
+ }
626
+ }