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,853 @@
1
+ // duckdb dialect: concrete builder for duckdb
2
+ import { BaseQueryBuilder } from '@epm-query-builder/base/BaseQueryBuilder';
3
+ import {
4
+ SuperFilterChild,
5
+ RowColumnConfig,
6
+ RankingConfig,
7
+ EpmQueryBuilderOptions,
8
+ } from '@epm-query-builder/types/query-builder-types';
9
+ import { BaseUtilities } from '@epm-query-builder/base/BaseUtilities';
10
+ import { DuckDbSuperFilterBuilder } from './DuckDbSuperFilterBuilder';
11
+ import { DuckDbMeasureBuilder } from './DuckDbMeasureBuilder';
12
+ import { DuckDbRollupBuilder } from './DuckDbRollupBuilder';
13
+ import { DuckDbPaginationBuilder } from './DuckDbPaginationBuilder';
14
+ import { DuckDbOrderBuilder } from './DuckDbOrderBuilder';
15
+ import { BaseCountQueryBuilder } from '@epm-query-builder/base/BaseCountQueryBuilder';
16
+ import { QueryOptionsValidator } from '@epm-query-builder/validation/QueryOptionsValidator';
17
+ import { SqlQueryValidator } from '@epm-query-builder/validation/SqlQueryValidator';
18
+ import { QueryGenerationError } from '@epm-query-builder/errors/QueryBuilderErrors';
19
+ import { IDatabaseQueryBuilder } from '@epm-query-builder/interfaces';
20
+ import { resolveJoinPlan } from '@epm-query-builder/base/RelationshipResolver';
21
+ import { DuckDbJoinBuilder } from './DuckDbJoinBuilder';
22
+ import { SOURCE_TYPES } from '@epm-query-builder/constants/Source';
23
+
24
+ export class DuckDbQueryBuilder extends BaseQueryBuilder implements IDatabaseQueryBuilder {
25
+ private superFilterBuilder: DuckDbSuperFilterBuilder;
26
+ private measureBuilder: DuckDbMeasureBuilder;
27
+ private rollupBuilder: DuckDbRollupBuilder;
28
+ private paginationBuilder: DuckDbPaginationBuilder;
29
+ private orderBuilder: DuckDbOrderBuilder;
30
+ private countQueryBuilder: BaseCountQueryBuilder;
31
+
32
+ constructor(options: EpmQueryBuilderOptions) {
33
+ super(options);
34
+ // order matters: these builders are used throughout generation
35
+ this.superFilterBuilder = new DuckDbSuperFilterBuilder(options);
36
+ this.measureBuilder = new DuckDbMeasureBuilder(options);
37
+ this.rollupBuilder = new DuckDbRollupBuilder(options);
38
+ this.paginationBuilder = new DuckDbPaginationBuilder(options);
39
+ this.orderBuilder = new DuckDbOrderBuilder(options);
40
+ this.countQueryBuilder = new BaseCountQueryBuilder(options);
41
+ }
42
+
43
+ // orchestrates decision between analytical and simple queries
44
+ generateQuery(): string {
45
+ try {
46
+ QueryOptionsValidator.validateBasicConfiguration(this.options);
47
+
48
+ const hasMultipleTables = this.containsDifferentTables();
49
+ const hasMeasureInValues = this.hasMeasureInValues();
50
+
51
+ // multi-table without measures uses ctes path
52
+ if (hasMultipleTables && !hasMeasureInValues) {
53
+ const multiTableQuery = this.generateMultiTableQuery();
54
+ SqlQueryValidator.validateQuery(multiTableQuery);
55
+ return multiTableQuery;
56
+ }
57
+
58
+ let query: string;
59
+ if (this.shouldGenerateAnalyticalQuery()) {
60
+ query = this.generateAnalyticalQuery();
61
+ } else {
62
+ query = this.generateSimpleQuery();
63
+ }
64
+
65
+ SqlQueryValidator.validateQuery(query);
66
+ return query;
67
+ } catch (error) {
68
+ if (error instanceof Error && error.name.includes('Error')) {
69
+ throw error;
70
+ }
71
+
72
+ throw new QueryGenerationError(`Failed to generate query: ${error}`, {
73
+ originalError: error,
74
+ hasRows: Boolean(this.options.rows?.length),
75
+ hasColumns: Boolean(this.options.columns?.length),
76
+ hasValues: Boolean(this.options.values?.length),
77
+ });
78
+ }
79
+ }
80
+
81
+ generateCountQuery(): string {
82
+ const mainQuery = this.generateQuery();
83
+ const countQuery = this.countQueryBuilder.generateCountQuery(mainQuery);
84
+
85
+ SqlQueryValidator.validateCountQuery(countQuery);
86
+ return countQuery;
87
+ }
88
+
89
+ // builds query for multi-table scenarios using ctes
90
+ private generateMultiTableQuery(): string {
91
+ const dimensions = BaseUtilities.extractDimensionsFromOptions(this.options);
92
+ const measures = this.extractMeasures();
93
+ const cteQueries = this.buildCTEs();
94
+ const source = BaseUtilities.getSource(this.options);
95
+ const isXmla = source === SOURCE_TYPES.XMLA;
96
+
97
+ const selectParts: string[] = [];
98
+
99
+ if (dimensions.length > 0) {
100
+ if (this.options.rows?.length) {
101
+ this.options.rows.forEach((row) => {
102
+ const column = BaseUtilities.formatColumnReferenceForDb(row, this.options);
103
+ selectParts.push(`${column} AS "${row.id}"`);
104
+ });
105
+ }
106
+ if (this.options.columns?.length) {
107
+ this.options.columns.forEach((col) => {
108
+ const column = BaseUtilities.formatColumnReferenceForDb(col, this.options);
109
+ selectParts.push(`${column} AS "${col.id}"`);
110
+ });
111
+ }
112
+ }
113
+
114
+ if (measures.length > 0) {
115
+ const measureSelects = this.measureBuilder.buildMeasureSelects();
116
+ selectParts.push(...measureSelects);
117
+ }
118
+
119
+ const fromClause = this.buildFromWithJoins();
120
+ const whereConditions = [
121
+ this.superFilterBuilder.buildWhereClause(),
122
+ this.superFilterBuilder.buildOrGroupExistsClause?.()
123
+ ? this.superFilterBuilder.buildOrGroupExistsClause()
124
+ : '',
125
+ ].filter(Boolean);
126
+
127
+ const hasMeasures = measures.length > 0;
128
+ const shouldGroupBy = dimensions.length > 0 && (!isXmla || hasMeasures);
129
+
130
+ const selectClause =
131
+ isXmla && !hasMeasures
132
+ ? `SELECT DISTINCT ${selectParts.join(', ')}`
133
+ : this.buildSelectClause(selectParts);
134
+
135
+ return this.buildCompleteQuery({
136
+ cte: cteQueries,
137
+ select: selectClause,
138
+ from: fromClause,
139
+ where: this.buildWhereClause(whereConditions),
140
+ groupBy: shouldGroupBy ? this.buildGroupByClause(dimensions) : undefined,
141
+ orderBy: this.orderBuilder.buildOrderByClause(),
142
+ limit: this.paginationBuilder.buildLimitClause(),
143
+ });
144
+ }
145
+
146
+ // analytical path: generates ctes then main
147
+ private generateAnalyticalQuery(): string {
148
+ const cteQueries = this.buildCTEs();
149
+ const mainQuery = this.buildMainQuery();
150
+
151
+ if (cteQueries) {
152
+ return `${cteQueries}\n${mainQuery}`;
153
+ }
154
+ return mainQuery;
155
+ }
156
+
157
+ // simple path: handles select all, distinct, or aggregate
158
+ private generateSimpleQuery(): string {
159
+ const tableName = this.getDefaultTableName();
160
+ const dimensions = BaseUtilities.extractDimensionsFromOptions(this.options);
161
+ const whereClause = this.superFilterBuilder.buildWhereClause();
162
+
163
+ if (this.isSelectAllRequested() && !this.hasMeasures() && !this.hasRankingFilters()) {
164
+ return this.buildSelectAllQuery(tableName, whereClause);
165
+ }
166
+
167
+ if (!this.hasMeasures()) {
168
+ if (this.hasRankingFilters()) {
169
+ return this.buildTopNRankingQuery(tableName, dimensions);
170
+ }
171
+ return this.buildDistinctQuery(tableName, dimensions, whereClause);
172
+ }
173
+ return this.buildSimpleAggregateQuery(tableName, dimensions, whereClause);
174
+ }
175
+
176
+ // collects ctes for filters and pre-processing
177
+ private buildCTEs(): string {
178
+ const ctes: string[] = [];
179
+
180
+ if (this.hasComplexFilters()) {
181
+ const filterCTE = this.superFilterBuilder.buildFilterCTE();
182
+ if (filterCTE) {
183
+ ctes.push(filterCTE);
184
+ }
185
+ }
186
+
187
+ const measureFilterCTEs = this.measureBuilder.buildMeasureFilterCTE();
188
+ if (measureFilterCTEs) {
189
+ ctes.push(measureFilterCTEs);
190
+ }
191
+
192
+ const rankingCTE = this.buildRankingKeysCTE();
193
+ if (rankingCTE) {
194
+ ctes.push(rankingCTE);
195
+ }
196
+
197
+ return ctes.length > 0 ? `WITH ${ctes.join(',\n')}` : '';
198
+ }
199
+
200
+ // assembles main query parts respecting ranking overrides
201
+ private buildMainQuery(): string {
202
+ const selectItems = this.buildSelectItems();
203
+ const whereConditions = [
204
+ this.superFilterBuilder.buildWhereClause(),
205
+ this.measureBuilder.buildMeasureFilterWhereClause(),
206
+ ].filter(Boolean);
207
+ const groupByColumns = this.buildGroupByColumns();
208
+ const havingConditions = [this.measureBuilder.buildHavingClause()].filter(Boolean);
209
+
210
+ const hasRanking = Boolean(this.options.isRankingFilterIncluded) || this.hasRankingFilters();
211
+
212
+ if (this.shouldGenerateRollup() && hasRanking) {
213
+ const nullFilterConditions = this.rollupBuilder.buildNullFilterConditions();
214
+ whereConditions.push(...nullFilterConditions);
215
+ }
216
+
217
+ let orderByClause = this.orderBuilder.buildOrderByClause();
218
+ let limitClause = this.paginationBuilder.buildLimitClause();
219
+
220
+ const ranking = this.findFirstRankingConfig(this.options.superFilters?.children || []);
221
+ if (ranking) {
222
+ const rankingOrder = this.buildRankingOrderClause(ranking);
223
+ const dimsForHaving = BaseUtilities.extractDimensionsFromOptions(this.options);
224
+ const hasRollup = this.shouldGenerateRollup();
225
+
226
+ if (rankingOrder) {
227
+ orderByClause = hasRollup
228
+ ? `ORDER BY "[IsRowGrandTotal]" DESC NULLS LAST, ${rankingOrder}`
229
+ : `ORDER BY ${rankingOrder}`;
230
+ }
231
+
232
+ // do not LIMIT the main result for ranking; restrict row keys via CTE join instead
233
+ limitClause = '';
234
+
235
+ if (dimsForHaving.length > 0) {
236
+ const rowDims = BaseUtilities.buildDimensionReference(this.options.rows, this.options);
237
+ if (rowDims.length > 0) {
238
+ const isLeafAllDims = dimsForHaving.map((d) => `GROUPING(${d}) = 0`).join(' AND ');
239
+ const isRowGrand = rowDims.map((d) => `GROUPING(${d}) = 1`).join(' AND ');
240
+ const allowLeafOrRowGrand = `(${isLeafAllDims}) OR (${isRowGrand})`;
241
+ havingConditions.push(allowLeafOrRowGrand);
242
+ }
243
+ }
244
+
245
+ const rankingWhere = this.buildRankingKeysWhereClause();
246
+ if (rankingWhere) {
247
+ whereConditions.push(rankingWhere);
248
+ }
249
+ }
250
+
251
+ return this.buildCompleteQuery({
252
+ select: this.buildSelectClause(selectItems),
253
+ from: this.buildFromWithJoins(),
254
+ where: this.buildWhereClause(
255
+ [this.paginationBuilder.buildAdditionalWhere(), ...whereConditions].filter(Boolean),
256
+ ),
257
+ groupBy: groupByColumns.length > 0 ? this.buildGroupByClause(groupByColumns) : undefined,
258
+ having: this.buildHavingClause(havingConditions),
259
+ orderBy: orderByClause,
260
+ limit: limitClause,
261
+ });
262
+ }
263
+
264
+ // builds select items based on dims, rollup, and measures
265
+ private buildSelectItems(): string[] {
266
+ const selectItems: string[] = [];
267
+
268
+ if (this.hasDimensions()) {
269
+ selectItems.push(...this.buildDimensionSelects());
270
+ }
271
+
272
+ if (this.shouldGenerateRollup()) {
273
+ selectItems.push(...this.rollupBuilder.buildRollupSelects());
274
+ }
275
+
276
+ if (this.hasMeasures()) {
277
+ selectItems.push(...this.measureBuilder.buildMeasureSelects());
278
+ }
279
+
280
+ return selectItems;
281
+ }
282
+
283
+ // prepares dimension projections respecting dot-notation mode
284
+ private buildDimensionSelects(): string[] {
285
+ const dimensions: string[] = [];
286
+
287
+ if (this.options.rows?.length) {
288
+ this.options.rows.forEach((row) => {
289
+ const column = BaseUtilities.formatColumnReferenceForDb(row, this.options);
290
+ dimensions.push(`${column} AS "${row.id}"`);
291
+ });
292
+ }
293
+
294
+ if (this.options.columns?.length) {
295
+ this.options.columns.forEach((col) => {
296
+ const column = BaseUtilities.formatColumnReferenceForDb(col, this.options);
297
+ dimensions.push(`${column} AS "${col.id}"`);
298
+ });
299
+ }
300
+
301
+ return dimensions;
302
+ }
303
+
304
+ // decides group by list based on rollup and measures
305
+ private buildGroupByColumns(): string[] {
306
+ if (!this.hasMeasures() && !this.shouldGenerateRollup()) {
307
+ return [];
308
+ }
309
+
310
+ if (this.shouldGenerateRollup()) {
311
+ return this.rollupBuilder.buildGroupByClause().replace('GROUP BY ', '').split(', ');
312
+ }
313
+
314
+ return BaseUtilities.extractDimensionsFromOptions(this.options);
315
+ }
316
+
317
+ // checks if dims, cols, filters span multiple tables
318
+ private containsDifferentTables(): boolean {
319
+ const tables = new Set<string>();
320
+ const originalParentTables = new Set<string>();
321
+
322
+ [...(this.options.rows || []), ...(this.options.columns || [])].forEach((item) => {
323
+ if (!item) return;
324
+
325
+ const directTableName = item.tableName;
326
+ const inferredTableName =
327
+ !directTableName && item.id ? BaseUtilities.getTableNameFromId(item.id) : null;
328
+ const effectiveTableName = directTableName || inferredTableName;
329
+
330
+ if (effectiveTableName) {
331
+ tables.add(effectiveTableName);
332
+
333
+ if (item.type === 'Resolved' && item.orderByRef) {
334
+ originalParentTables.add(item.orderByRef);
335
+ }
336
+ }
337
+ });
338
+
339
+ (this.options.values || []).forEach((measure) => {
340
+ if (!measure) return;
341
+
342
+ const directTableName = measure.tableName;
343
+ const inferredTableName =
344
+ !directTableName && measure.id ? BaseUtilities.getTableNameFromId(measure.id) : null;
345
+ const effectiveTableName = directTableName || inferredTableName;
346
+
347
+ if (effectiveTableName) {
348
+ tables.add(effectiveTableName);
349
+ }
350
+ });
351
+
352
+ if (this.options.superFilters?.children) {
353
+ this.extractFilterTables(this.options.superFilters.children, tables);
354
+ }
355
+
356
+ const totalUniqueTables = new Set([...tables, ...originalParentTables]);
357
+ return totalUniqueTables.size > 1;
358
+ }
359
+
360
+ // traverses filter tree collecting table names
361
+ private extractFilterTables(children: SuperFilterChild[], tables: Set<string>): void {
362
+ children.forEach((child) => {
363
+ if (child.filters?.tableNames) {
364
+ child.filters.tableNames.forEach((tableName: string) => {
365
+ if (tableName) tables.add(tableName);
366
+ });
367
+ }
368
+ if (child.children) {
369
+ this.extractFilterTables(child.children, tables);
370
+ }
371
+ });
372
+ }
373
+
374
+ private hasMeasureInValues(): boolean {
375
+ return (this.options.values || []).some((measure) => measure.type?.toUpperCase() === 'MEASURE');
376
+ }
377
+
378
+ private hasFiltersForTable(tableName: string): boolean {
379
+ if (!this.options.superFilters?.children) return false;
380
+
381
+ return this.checkFiltersForTable(this.options.superFilters.children, tableName);
382
+ }
383
+
384
+ private checkFiltersForTable(children: SuperFilterChild[], tableName: string): boolean {
385
+ return children.some((child) => {
386
+ if (child.filters?.tableNames?.includes(tableName)) {
387
+ return true;
388
+ }
389
+ if (child.children) {
390
+ return this.checkFiltersForTable(child.children, tableName);
391
+ }
392
+ return false;
393
+ });
394
+ }
395
+
396
+ private hasMeasuresForTable(tableName: string): boolean {
397
+ return (this.options.values || []).some((measure) => measure.tableName === tableName);
398
+ }
399
+
400
+ // builds distinct projection for dims path
401
+ private buildDistinctQuery(tableName: string, dimensions: string[], whereClause: string): string {
402
+ const fromClause = this.buildFromWithJoins();
403
+
404
+ if (dimensions.length === 0) {
405
+ return `SELECT COUNT(*) ${fromClause}`;
406
+ }
407
+
408
+ const selectDims = this.buildDimensionSelects();
409
+ let query = `SELECT DISTINCT ${selectDims.join(', ')} ${fromClause}`;
410
+
411
+ if (whereClause) {
412
+ query += ` WHERE ${whereClause}`;
413
+ }
414
+
415
+ const orderByClause = this.orderBuilder.buildOrderByClause();
416
+ if (orderByClause) {
417
+ query += ` ${orderByClause}`;
418
+ } else if (dimensions.length > 0) {
419
+ query += ` ORDER BY ${dimensions[0]} ASC`;
420
+ }
421
+
422
+ return query;
423
+ }
424
+
425
+ private hasRankingFilters(): boolean {
426
+ if (!this.options.superFilters?.children) return false;
427
+ return this.findFirstRankingConfig(this.options.superFilters.children) !== null;
428
+ }
429
+
430
+ // finds the first ranking config to override order and limit
431
+ private findFirstRankingConfig(children: SuperFilterChild[]): RankingConfig | null {
432
+ for (const child of children) {
433
+ const filters = child.filters as unknown as {
434
+ rankingFilter?: RankingConfig[];
435
+ filterType?: string;
436
+ };
437
+ if (
438
+ filters &&
439
+ filters.filterType === 'RANKING' &&
440
+ Array.isArray(filters.rankingFilter) &&
441
+ filters.rankingFilter.length > 0
442
+ ) {
443
+ return filters.rankingFilter[0];
444
+ }
445
+ if (child.children) {
446
+ const found = this.findFirstRankingConfig(child.children);
447
+ if (found) return found;
448
+ }
449
+ }
450
+ return null;
451
+ }
452
+
453
+ // generates union/intersect queries for top/bottom selection across rules
454
+ private buildTopNRankingQuery(tableName: string, dimensions: string[]): string {
455
+ const rankings = this.findAllRankingConfigs(this.options.superFilters?.children || []);
456
+ if (rankings.length === 0) {
457
+ return this.buildDistinctQuery(tableName, dimensions, '');
458
+ }
459
+
460
+ const formattedTableName = BaseUtilities.formatTableNameForDb(
461
+ tableName,
462
+ this.options.databaseDetails?.databaseType as string,
463
+ );
464
+ // if no dimensions, rank rows by the measure column value directly
465
+ if (dimensions.length === 0) {
466
+ const rk = rankings[0];
467
+ const direction = rk.type === 'BOTTOM' ? 'ASC' : 'DESC';
468
+
469
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
470
+ rk.rankByColumnId || `${rk.rankByTableName || ''}[${rk.rankByColumnName || ''}]`,
471
+ );
472
+
473
+ const isBlend = BaseUtilities.getSource(this.options) === SOURCE_TYPES.BLEND;
474
+ const tableNameForRef = tableName;
475
+ const aggTypeUpper = (rk.rankByAggregationType || 'SUM').toUpperCase();
476
+ const aggPrefix = aggTypeUpper;
477
+
478
+ const blendMeasureRef = BaseUtilities.formatTableColumnReference(
479
+ tableNameForRef,
480
+ `${aggPrefix}__${actualId}`,
481
+ );
482
+
483
+ let rkRef: string;
484
+ if (isBlend) {
485
+ rkRef = blendMeasureRef;
486
+ } else if (this.options.databaseDetails?.databaseType === 'duckdb') {
487
+ rkRef = `"${actualId}"`;
488
+ } else {
489
+ const tableRef = rk.rankByTableName || BaseUtilities.getDefaultTableName(this.options);
490
+ rkRef = `${tableRef}.${rk.rankByColumnName}`;
491
+ }
492
+
493
+ if (rk.isPercentage) {
494
+ const percentile = Math.max(0, Math.min(100, rk.value)) / 100;
495
+ return [
496
+ 'SELECT * FROM (',
497
+ `SELECT *, PERCENT_RANK() OVER (ORDER BY ${rkRef} ${direction}) AS __pr`,
498
+ `FROM ${formattedTableName}`,
499
+ `) __ranked WHERE __pr <= ${percentile}`,
500
+ ].join('\n');
501
+ }
502
+
503
+ const limit = Math.max(1, Math.floor(rk.value));
504
+ return `SELECT * FROM ${formattedTableName} ORDER BY ${rkRef} ${direction} LIMIT ${limit}`;
505
+ }
506
+
507
+ const fromClause = `FROM ${formattedTableName}`;
508
+ const groupByClause = `GROUP BY ${dimensions.join(', ')}`;
509
+
510
+ const subqueries: string[] = [];
511
+ rankings.forEach((rk) => {
512
+ const agg = BaseUtilities.mapAggregationType(rk.rankByAggregationType || 'SUM');
513
+ const isBlend = BaseUtilities.getSource(this.options) === SOURCE_TYPES.BLEND;
514
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
515
+ rk.rankByColumnId || `${rk.rankByTableName || ''}[${rk.rankByColumnName || ''}]`,
516
+ );
517
+ let rkRef: string;
518
+ let selectClause: string;
519
+
520
+ if (isBlend) {
521
+ const aggTypeUpper = (rk.rankByAggregationType || 'SUM').toUpperCase();
522
+ const aggPrefix = aggTypeUpper; // For BLEND, alias prefix must match input aggregation type
523
+ const measureAlias = `${aggPrefix}__${actualId}`;
524
+ const measureRef = BaseUtilities.formatTableColumnReference(tableName, measureAlias);
525
+ rkRef = measureRef;
526
+ selectClause = `SELECT ${dimensions.join(', ')}, ${agg}("${measureAlias}") AS "${measureAlias}", COUNT(*) OVER () AS "[TotalRowsCountForResult]"`;
527
+ } else if (this.options.databaseDetails?.databaseType === 'duckdb') {
528
+ rkRef = `"${actualId}"`;
529
+ selectClause = `SELECT ${dimensions.join(', ')}, ${agg}(${rkRef}), COUNT(*) OVER () AS "[TotalRowsCountForResult]"`;
530
+ } else {
531
+ const tableRef = rk.rankByTableName || BaseUtilities.getDefaultTableName(this.options);
532
+ rkRef = `${tableRef}.${rk.rankByColumnName}`;
533
+ selectClause = `SELECT ${dimensions.join(', ')}, ${agg}(${rkRef}), COUNT(*) OVER () AS "[TotalRowsCountForResult]"`;
534
+ }
535
+
536
+ if (rk.type === 'BOTH') {
537
+ const topN = Math.ceil(rk.value);
538
+ const bottomN = Math.floor(rk.value);
539
+ subqueries.push(
540
+ [
541
+ selectClause,
542
+ fromClause,
543
+ groupByClause,
544
+ `ORDER BY ${agg}(${rkRef}) DESC`,
545
+ `LIMIT ${topN}`,
546
+ ].join('\n'),
547
+ );
548
+ subqueries.push(
549
+ [
550
+ selectClause,
551
+ fromClause,
552
+ groupByClause,
553
+ `ORDER BY ${agg}(${rkRef}) ASC`,
554
+ `LIMIT ${bottomN}`,
555
+ ].join('\n'),
556
+ );
557
+ } else {
558
+ const dir = rk.type === 'TOP' ? 'DESC' : 'ASC';
559
+ subqueries.push(
560
+ [
561
+ selectClause,
562
+ fromClause,
563
+ groupByClause,
564
+ isBlend
565
+ ? `ORDER BY "${(rk.rankByAggregationType || 'SUM').toUpperCase()}__${actualId}" ${dir}`
566
+ : `ORDER BY ${agg}(${rkRef}) ${dir}`,
567
+ `LIMIT ${rk.value}`,
568
+ ].join('\n'),
569
+ );
570
+ }
571
+ });
572
+
573
+ const logicalOp = this.extractRankingLogicalOperator(this.options.superFilters?.children || []);
574
+ if (logicalOp === 'INTERSECT') {
575
+ return subqueries.join(' INTERSECT ');
576
+ }
577
+ return subqueries.join(' UNION ');
578
+ }
579
+
580
+ private buildRankingOrderClause(ranking: RankingConfig): string {
581
+ const direction = ranking.type === 'BOTTOM' ? 'ASC' : 'DESC';
582
+ const agg = BaseUtilities.mapAggregationType(ranking.rankByAggregationType || 'SUM');
583
+ const isBlend = BaseUtilities.getSource(this.options) === SOURCE_TYPES.BLEND;
584
+
585
+ if (isBlend) {
586
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
587
+ ranking.rankByColumnId ||
588
+ `${ranking.rankByTableName || ''}[${ranking.rankByColumnName || ''}]`,
589
+ );
590
+ const aggTypeUpper = (ranking.rankByAggregationType || 'SUM').toUpperCase();
591
+ const aggPrefix = aggTypeUpper; // For BLEND, alias prefix must match input aggregation type
592
+ const aliasName = `${aggPrefix}__${actualId}`;
593
+ return `"${aliasName}" ${direction}`;
594
+ }
595
+
596
+ let measureRef: string;
597
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
598
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
599
+ ranking.rankByColumnId ||
600
+ `${ranking.rankByTableName || ''}[${ranking.rankByColumnName || ''}]`,
601
+ );
602
+ measureRef = `"${actualId}"`;
603
+ } else {
604
+ const tableRef = ranking.rankByTableName || BaseUtilities.getDefaultTableName(this.options);
605
+ measureRef = `${tableRef}.${ranking.rankByColumnName}`;
606
+ }
607
+ return `${agg}(${measureRef}) ${direction}`;
608
+ }
609
+
610
+ private calculateRankingLimit(ranking: RankingConfig): number {
611
+ const distinctCount = ranking.isPercentage ? this.estimateDistinctCountForRanking() : 0;
612
+ if (ranking.isPercentage && distinctCount > 0) {
613
+ return Math.max(1, Math.floor((ranking.value * distinctCount) / 100));
614
+ }
615
+ return ranking.value;
616
+ }
617
+
618
+ private estimateDistinctCountForRanking(): number {
619
+ const dims = BaseUtilities.extractDimensionsFromOptions(this.options);
620
+ return Math.max(0, dims.length > 0 ? 1000000 : 0);
621
+ }
622
+
623
+ private findAllRankingConfigs(children: SuperFilterChild[]): RankingConfig[] {
624
+ const result: RankingConfig[] = [];
625
+ for (const child of children) {
626
+ if (child.filters?.rankingFilter && Array.isArray(child.filters.rankingFilter)) {
627
+ result.push(...child.filters.rankingFilter);
628
+ }
629
+ if (child.children) {
630
+ result.push(...this.findAllRankingConfigs(child.children));
631
+ }
632
+ }
633
+ return result;
634
+ }
635
+
636
+ private extractRankingLogicalOperator(children: SuperFilterChild[]): string {
637
+ for (const child of children) {
638
+ if (child.filters?.logicalOperator) {
639
+ return child.filters.logicalOperator.toUpperCase();
640
+ }
641
+ if (child.children) {
642
+ const v = this.extractRankingLogicalOperator(child.children);
643
+ if (v) return v;
644
+ }
645
+ }
646
+ return 'UNION';
647
+ }
648
+
649
+ private buildSimpleAggregateQuery(
650
+ tableName: string,
651
+ dimensions: string[],
652
+ whereClause: string,
653
+ ): string {
654
+ const measures = this.extractMeasures();
655
+ const selectParts = [...this.buildDimensionSelects()];
656
+
657
+ measures.forEach((measure) => {
658
+ if (measure.type?.toLowerCase() === 'measure') {
659
+ selectParts.push(`"${measure.id}" AS "${measure.label}"`);
660
+ } else {
661
+ const aggregationType = BaseUtilities.mapAggregationType(measure.aggregationType || 'SUM');
662
+ selectParts.push(`${aggregationType}("${measure.id}") AS "${measure.label}"`);
663
+ }
664
+ });
665
+
666
+ const fromClause = this.buildFromWithJoins();
667
+ let query = `SELECT ${selectParts.join(', ')} ${fromClause}`;
668
+
669
+ if (whereClause) {
670
+ query += ` WHERE ${whereClause}`;
671
+ }
672
+
673
+ if (dimensions.length > 0) {
674
+ query += ` GROUP BY ${dimensions.join(', ')}`;
675
+ query += ` ORDER BY ${dimensions.map((_, index) => `${index + 1}`).join(', ')}`;
676
+ }
677
+
678
+ return query;
679
+ }
680
+
681
+ private isSelectAllRequested(): boolean {
682
+ const sc = this.options.selectedColumns || [];
683
+ if (Array.isArray(sc) && sc.some((c) => c === '*')) return true;
684
+ if (this.options.rows?.length === 1 && this.options.rows[0].columnName === '*') return true;
685
+ return false;
686
+ }
687
+
688
+ private buildSelectAllQuery(tableName: string, whereClause: string): string {
689
+ const fromClause = this.buildFromWithJoins();
690
+
691
+ const selectKeyword = this.options.distinct ? 'SELECT DISTINCT' : 'SELECT';
692
+ let query = `${selectKeyword} * ${fromClause}`;
693
+ if (whereClause) {
694
+ query += ` WHERE ${whereClause}`;
695
+ }
696
+
697
+ const orderByClause = this.orderBuilder.buildOrderByClause();
698
+ if (orderByClause) {
699
+ query += ` ${orderByClause}`;
700
+ }
701
+
702
+ const limitClause = this.paginationBuilder.buildLimitClause();
703
+ if (limitClause) {
704
+ query += ` ${limitClause}`;
705
+ }
706
+
707
+ return query;
708
+ }
709
+
710
+ private hasMeasureFilters(): boolean {
711
+ return Boolean(
712
+ this.options.superFilters?.children?.some(
713
+ (child) =>
714
+ child.filters &&
715
+ (child.filters.filterType === 'MEASURE' ||
716
+ child.filters.columnType?.some((type) => type?.toUpperCase() === 'MEASURE')),
717
+ ),
718
+ );
719
+ }
720
+
721
+ protected formatColumnReference(config: RowColumnConfig): string {
722
+ if (this.options.databaseDetails?.databaseType === 'duckdb') {
723
+ const actualColumnId = BaseUtilities.extractColumnIdFromHierarchy(config.id);
724
+ return `"${actualColumnId}"`;
725
+ }
726
+ return super.formatColumnReference(config);
727
+ }
728
+
729
+ protected buildFromClause(tableName: string, joins?: string[]): string {
730
+ const formattedTableName = BaseUtilities.formatTableNameForDb(
731
+ tableName,
732
+ this.options.databaseDetails?.databaseType as string,
733
+ );
734
+ let fromClause = `FROM ${formattedTableName}`;
735
+
736
+ if (joins?.length) {
737
+ fromClause += ` ${joins.join(' ')}`;
738
+ }
739
+
740
+ return fromClause;
741
+ }
742
+
743
+ private buildFromWithJoins(): string {
744
+ const plan = resolveJoinPlan(this.options);
745
+ const joins = DuckDbJoinBuilder.buildJoinClauses(
746
+ plan,
747
+ this.options.databaseDetails?.databaseType as string,
748
+ this.options,
749
+ );
750
+ return this.buildFromClause(plan.baseTable || this.getDefaultTableName(), joins);
751
+ }
752
+
753
+ private buildRankingKeysCTE(): string {
754
+ const ranking = this.findFirstRankingConfig(this.options.superFilters?.children || []);
755
+ if (!ranking) return '';
756
+ const targetDims = this.getRankingTargetDimensions();
757
+ if (targetDims.length === 0) return '';
758
+
759
+ const isBlend = BaseUtilities.getSource(this.options) === SOURCE_TYPES.BLEND;
760
+ const tableName = this.getDefaultTableName();
761
+ const formattedTableName = BaseUtilities.formatTableNameForDb(
762
+ tableName,
763
+ this.options.databaseDetails?.databaseType as string,
764
+ );
765
+ const direction = ranking.type === 'BOTTOM' ? 'ASC' : 'DESC';
766
+ const agg = BaseUtilities.mapAggregationType(ranking.rankByAggregationType || 'SUM');
767
+
768
+ let measureExpr: string;
769
+ if (isBlend) {
770
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
771
+ ranking.rankByColumnId ||
772
+ `${ranking.rankByTableName || ''}[${ranking.rankByColumnName || ''}]`,
773
+ );
774
+ const aggTypeUpper = (ranking.rankByAggregationType || 'SUM').toUpperCase();
775
+ const aliasName = `${aggTypeUpper}__${actualId}`;
776
+ measureExpr = `${agg}("${aliasName}")`;
777
+ } else if (this.options.databaseDetails?.databaseType === 'duckdb') {
778
+ const actualId = BaseUtilities.extractColumnIdFromHierarchy(
779
+ ranking.rankByColumnId ||
780
+ `${ranking.rankByTableName || ''}[${ranking.rankByColumnName || ''}]`,
781
+ );
782
+ measureExpr = `${agg}("${actualId}")`;
783
+ } else {
784
+ const tableRef = ranking.rankByTableName || BaseUtilities.getDefaultTableName(this.options);
785
+ measureExpr = `${agg}(${tableRef}.${ranking.rankByColumnName})`;
786
+ }
787
+
788
+ const dimAliases = targetDims.map((_, idx) => `__rk_dim${idx + 1}`);
789
+ const selectDims = targetDims.map((d, idx) => `${d} AS ${dimAliases[idx]}`).join(', ');
790
+ const groupByDims = targetDims.join(', ');
791
+
792
+ return [
793
+ `__ranking_keys AS (`,
794
+ ` SELECT ${selectDims}, ${measureExpr} AS __rk_value`,
795
+ ` FROM ${formattedTableName}`,
796
+ ` GROUP BY ${groupByDims}`,
797
+ ` ORDER BY __rk_value ${direction}`,
798
+ ` LIMIT ${this.calculateRankingLimit(ranking)}`,
799
+ `)`,
800
+ ].join('\n');
801
+ }
802
+
803
+ private buildRankingKeysWhereClause(): string {
804
+ const targetDims = this.getRankingTargetDimensions();
805
+ if (targetDims.length === 0) return '';
806
+ const comparisons = targetDims.map((d, idx) => `${d} = rk.__rk_dim${idx + 1}`).join(' AND ');
807
+ return `EXISTS (SELECT 1 FROM __ranking_keys rk WHERE ${comparisons})`;
808
+ }
809
+
810
+ private getRankingTargetDimensions(): string[] {
811
+ const ids = this.findFirstRankingTargetColumnIds(this.options.superFilters?.children || []);
812
+ if (ids.length === 0) {
813
+ const rowDims = BaseUtilities.buildDimensionReference(this.options.rows, this.options);
814
+ if (rowDims.length > 0) return rowDims;
815
+ return BaseUtilities.buildDimensionReference(this.options.columns, this.options);
816
+ }
817
+
818
+ const refs: string[] = [];
819
+ const findConfigById = (id: string) => {
820
+ const inRows = (this.options.rows || []).find((r) => r.id === id);
821
+ if (inRows) return inRows;
822
+ const inCols = (this.options.columns || []).find((c) => c.id === id);
823
+ if (inCols) return inCols;
824
+ return {
825
+ id,
826
+ tableName: BaseUtilities.getDefaultTableNameFromOptions(this.options),
827
+ } as unknown as RowColumnConfig;
828
+ };
829
+ ids.forEach((id) => {
830
+ const cfg = findConfigById(id);
831
+ refs.push(BaseUtilities.formatColumnReferenceForDb(cfg, this.options));
832
+ });
833
+ return refs;
834
+ }
835
+
836
+ private findFirstRankingTargetColumnIds(children: SuperFilterChild[]): string[] {
837
+ for (const child of children) {
838
+ const filters = child.filters as unknown as {
839
+ filterType?: string;
840
+ columnIds?: string[];
841
+ rankingFilter?: RankingConfig[];
842
+ };
843
+ if (filters && filters.filterType === 'RANKING' && Array.isArray(filters.rankingFilter)) {
844
+ return (filters.columnIds || []).filter(Boolean) as string[];
845
+ }
846
+ if (child.children) {
847
+ const found = this.findFirstRankingTargetColumnIds(child.children);
848
+ if (found.length > 0) return found;
849
+ }
850
+ }
851
+ return [];
852
+ }
853
+ }