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.
- package/.turbo/turbo-build.log +9 -0
- package/.turbo/turbo-check-types.log +4 -0
- package/.turbo/turbo-lint.log +126 -0
- package/README.md +45 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +236 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/constants.ts.html +826 -0
- package/coverage/src/database_types.ts.html +136 -0
- package/coverage/src/epm-query-builder/EpmQueryBuilder.ts.html +166 -0
- package/coverage/src/epm-query-builder/base/BaseAdvancedAggregations.ts.html +568 -0
- package/coverage/src/epm-query-builder/base/BaseAnalyticalFunctions.ts.html +694 -0
- package/coverage/src/epm-query-builder/base/BaseCTEGenerator.ts.html +1459 -0
- package/coverage/src/epm-query-builder/base/BaseCountQueryBuilder.ts.html +400 -0
- package/coverage/src/epm-query-builder/base/BaseMeasureBuilder.ts.html +295 -0
- package/coverage/src/epm-query-builder/base/BaseOrderBuilder.ts.html +670 -0
- package/coverage/src/epm-query-builder/base/BasePaginationBuilder.ts.html +364 -0
- package/coverage/src/epm-query-builder/base/BaseQueryBuilder.ts.html +238 -0
- package/coverage/src/epm-query-builder/base/BaseRollupBuilder.ts.html +532 -0
- package/coverage/src/epm-query-builder/base/BaseSqlBuilder.ts.html +601 -0
- package/coverage/src/epm-query-builder/base/BaseSuperFilterBuilder.ts.html +1966 -0
- package/coverage/src/epm-query-builder/base/BaseUtilities.ts.html +1798 -0
- package/coverage/src/epm-query-builder/base/ColumnRefUtils.ts.html +211 -0
- package/coverage/src/epm-query-builder/base/RelationshipResolver.ts.html +706 -0
- package/coverage/src/epm-query-builder/base/SharedFilterBuilder.ts.html +1717 -0
- package/coverage/src/epm-query-builder/base/index.html +326 -0
- package/coverage/src/epm-query-builder/constants/Aggregations.ts.html +133 -0
- package/coverage/src/epm-query-builder/constants/Database.ts.html +103 -0
- package/coverage/src/epm-query-builder/constants/Source.ts.html +106 -0
- package/coverage/src/epm-query-builder/constants/index.html +146 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts.html +286 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts.html +280 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts.html +1924 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts.html +769 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts.html +643 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts.html +2644 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts.html +478 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts.html +1195 -0
- package/coverage/src/epm-query-builder/dialects/duckdb/index.html +221 -0
- package/coverage/src/epm-query-builder/errors/QueryBuilderErrors.ts.html +280 -0
- package/coverage/src/epm-query-builder/errors/index.html +116 -0
- package/coverage/src/epm-query-builder/index.html +116 -0
- package/coverage/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts.html +100 -0
- package/coverage/src/epm-query-builder/interfaces/index.html +131 -0
- package/coverage/src/epm-query-builder/interfaces/index.ts.html +88 -0
- package/coverage/src/epm-query-builder/utils/format.ts.html +151 -0
- package/coverage/src/epm-query-builder/utils/index.html +146 -0
- package/coverage/src/epm-query-builder/utils/sql.ts.html +247 -0
- package/coverage/src/epm-query-builder/utils/validation.ts.html +124 -0
- package/coverage/src/epm-query-builder/validation/QueryOptionsValidator.ts.html +631 -0
- package/coverage/src/epm-query-builder/validation/SqlQueryValidator.ts.html +475 -0
- package/coverage/src/epm-query-builder/validation/index.html +131 -0
- package/coverage/src/filters/FilterConditionBuilder.ts.html +427 -0
- package/coverage/src/filters/filter-types.ts.html +484 -0
- package/coverage/src/filters/index.html +131 -0
- package/coverage/src/index.html +176 -0
- package/coverage/src/index.ts.html +103 -0
- package/coverage/src/js-lib/JsToSqlParser.ts.html +736 -0
- package/coverage/src/js-lib/ParseContext.ts.html +532 -0
- package/coverage/src/js-lib/db/azuresql/AzureSqlCallExpressionVisitor.ts.html +196 -0
- package/coverage/src/js-lib/db/azuresql/index.html +116 -0
- package/coverage/src/js-lib/db/base/ArrayExpressionVisitor.ts.html +133 -0
- package/coverage/src/js-lib/db/base/AssignmentExpressionVisitor.ts.html +187 -0
- package/coverage/src/js-lib/db/base/BinaryExpressionVisitor.ts.html +223 -0
- package/coverage/src/js-lib/db/base/CallExpressionVisitor.ts.html +5479 -0
- package/coverage/src/js-lib/db/base/IdentifierVisitor.ts.html +283 -0
- package/coverage/src/js-lib/db/base/LiteralVisitor.ts.html +199 -0
- package/coverage/src/js-lib/db/base/MemberExpressionVisitor.ts.html +193 -0
- package/coverage/src/js-lib/db/base/ProgramVisitor.ts.html +139 -0
- package/coverage/src/js-lib/db/base/UnaryExpressionVisitor.ts.html +181 -0
- package/coverage/src/js-lib/db/base/VisitorInterface.ts.html +103 -0
- package/coverage/src/js-lib/db/base/index.html +251 -0
- package/coverage/src/js-lib/db/bigquery/BigQueryCallExpressionVisitor.ts.html +1747 -0
- package/coverage/src/js-lib/db/bigquery/index.html +116 -0
- package/coverage/src/js-lib/db/commonTransforms.ts.html +2074 -0
- package/coverage/src/js-lib/db/databricks/DatabricksCallExpressionVisitor.ts.html +1303 -0
- package/coverage/src/js-lib/db/databricks/index.html +116 -0
- package/coverage/src/js-lib/db/fabricsql/FabricSqlCallExpressionVisitor.ts.html +196 -0
- package/coverage/src/js-lib/db/fabricsql/index.html +116 -0
- package/coverage/src/js-lib/db/fabricwarehouse/FabricWarehouseCallExpressionVisitor.ts.html +292 -0
- package/coverage/src/js-lib/db/fabricwarehouse/index.html +116 -0
- package/coverage/src/js-lib/db/index.html +116 -0
- package/coverage/src/js-lib/db/postgresql/PostgreSqlCallExpressionVisitor.ts.html +985 -0
- package/coverage/src/js-lib/db/postgresql/index.html +116 -0
- package/coverage/src/js-lib/db/redshift/RedshiftCallExpressionVisitor.ts.html +685 -0
- package/coverage/src/js-lib/db/redshift/index.html +116 -0
- package/coverage/src/js-lib/db/sample/SampleCallExpressionVisitor.ts.html +196 -0
- package/coverage/src/js-lib/db/sample/index.html +116 -0
- package/coverage/src/js-lib/db/snowflake/SnowflakeCallExpressionVisitor.ts.html +1447 -0
- package/coverage/src/js-lib/db/snowflake/index.html +116 -0
- package/coverage/src/js-lib/db/validator/FormulaValidator.ts.html +4162 -0
- package/coverage/src/js-lib/db/validator/index.html +116 -0
- package/coverage/src/js-lib/index.html +131 -0
- package/coverage/src/js-lib/objects/BaseObject.ts.html +169 -0
- package/coverage/src/js-lib/objects/DateObject.ts.html +169 -0
- package/coverage/src/js-lib/objects/PctObject.ts.html +178 -0
- package/coverage/src/js-lib/objects/index.html +146 -0
- package/coverage/src/query-builder/PaginationBuilder.ts.html +142 -0
- package/coverage/src/query-builder/QueryBuilder.ts.html +3118 -0
- package/coverage/src/query-builder/SuperFilterBuilder.ts.html +1969 -0
- package/coverage/src/query-builder/index.html +146 -0
- package/coverage/src/runtime_var.ts.html +109 -0
- package/coverage/src/sql-lib/binary_expr.ts.html +133 -0
- package/coverage/src/sql-lib/case.ts.html +133 -0
- package/coverage/src/sql-lib/column.ts.html +139 -0
- package/coverage/src/sql-lib/else.ts.html +124 -0
- package/coverage/src/sql-lib/function.ts.html +112 -0
- package/coverage/src/sql-lib/index.html +251 -0
- package/coverage/src/sql-lib/join.ts.html +127 -0
- package/coverage/src/sql-lib/literal.ts.html +130 -0
- package/coverage/src/sql-lib/select.ts.html +547 -0
- package/coverage/src/sql-lib/unary_expr.ts.html +112 -0
- package/coverage/src/sql-lib/when.ts.html +130 -0
- package/coverage/src/sql_query_gen.ts.html +535 -0
- package/coverage/src/superFilter/DateFilterFactory.ts.html +625 -0
- package/coverage/src/superFilter/dateFunction.ts.html +193 -0
- package/coverage/src/superFilter/index.html +131 -0
- package/coverage/src/utils.ts.html +571 -0
- package/dist/index.cjs +8440 -0
- package/dist/index.d.cts +927 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +927 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8387 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +4 -0
- package/jest.config.ts +44 -0
- package/package.json +45 -0
- package/src/constants.ts +247 -0
- package/src/epm-query-builder/EpmQueryBuilder.ts +27 -0
- package/src/epm-query-builder/base/BaseAdvancedAggregations.ts +161 -0
- package/src/epm-query-builder/base/BaseAnalyticalFunctions.ts +203 -0
- package/src/epm-query-builder/base/BaseCTEGenerator.ts +458 -0
- package/src/epm-query-builder/base/BaseCountQueryBuilder.ts +105 -0
- package/src/epm-query-builder/base/BaseMeasureBuilder.ts +87 -0
- package/src/epm-query-builder/base/BaseOrderBuilder.ts +195 -0
- package/src/epm-query-builder/base/BasePaginationBuilder.ts +93 -0
- package/src/epm-query-builder/base/BaseQueryBuilder.ts +51 -0
- package/src/epm-query-builder/base/BaseRollupBuilder.ts +149 -0
- package/src/epm-query-builder/base/BaseSqlBuilder.ts +172 -0
- package/src/epm-query-builder/base/BaseSuperFilterBuilder.ts +627 -0
- package/src/epm-query-builder/base/BaseUtilities.ts +571 -0
- package/src/epm-query-builder/base/ColumnRefUtils.ts +42 -0
- package/src/epm-query-builder/base/RelationshipResolver.ts +207 -0
- package/src/epm-query-builder/base/SharedFilterBuilder.ts +544 -0
- package/src/epm-query-builder/constants/Aggregations.ts +16 -0
- package/src/epm-query-builder/constants/Database.ts +6 -0
- package/src/epm-query-builder/constants/Source.ts +7 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts +67 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts +65 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts +626 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts +228 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts +186 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts +853 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts +131 -0
- package/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts +370 -0
- package/src/epm-query-builder/errors/QueryBuilderErrors.ts +65 -0
- package/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts +5 -0
- package/src/epm-query-builder/interfaces/index.ts +1 -0
- package/src/epm-query-builder/types/query-builder-types.d.ts +289 -0
- package/src/epm-query-builder/utils/format.ts +22 -0
- package/src/epm-query-builder/utils/sql.ts +54 -0
- package/src/epm-query-builder/utils/validation.ts +13 -0
- package/src/epm-query-builder/validation/QueryOptionsValidator.ts +182 -0
- package/src/epm-query-builder/validation/SqlQueryValidator.ts +130 -0
- package/src/filters/FilterConditionBuilder.ts +114 -0
- package/src/filters/filter-types.ts +133 -0
- package/src/index.ts +10 -0
- package/src/js-lib/JsToSqlParser.ts +217 -0
- package/src/js-lib/ParseContext.ts +149 -0
- package/src/js-lib/db/base/ArrayExpressionVisitor.ts +16 -0
- package/src/js-lib/db/base/AssignmentExpressionVisitor.ts +34 -0
- package/src/js-lib/db/base/BinaryExpressionVisitor.ts +46 -0
- package/src/js-lib/db/base/CallExpressionVisitor.ts +1798 -0
- package/src/js-lib/db/base/IdentifierVisitor.ts +66 -0
- package/src/js-lib/db/base/LiteralVisitor.ts +38 -0
- package/src/js-lib/db/base/MemberExpressionVisitor.ts +36 -0
- package/src/js-lib/db/base/ProgramVisitor.ts +18 -0
- package/src/js-lib/db/base/UnaryExpressionVisitor.ts +32 -0
- package/src/js-lib/db/base/VisitorInterface.ts +6 -0
- package/src/js-lib/db/validator/FormulaValidator.ts +1235 -0
- package/src/js-lib/objects/BaseObject.ts +28 -0
- package/src/js-lib/objects/DateObject.ts +28 -0
- package/src/js-lib/objects/PctObject.ts +31 -0
- package/src/query-builder/PaginationBuilder.ts +19 -0
- package/src/query-builder/QueryBuilder.ts +1035 -0
- package/src/query-builder/SuperFilterBuilder.ts +628 -0
- package/src/runtime_var.ts +8 -0
- package/src/sql-lib/binary_expr.ts +16 -0
- package/src/sql-lib/case.ts +16 -0
- package/src/sql-lib/column.ts +18 -0
- package/src/sql-lib/else.ts +13 -0
- package/src/sql-lib/function.ts +9 -0
- package/src/sql-lib/join.ts +14 -0
- package/src/sql-lib/literal.ts +15 -0
- package/src/sql-lib/select.ts +154 -0
- package/src/sql-lib/unary_expr.ts +9 -0
- package/src/sql-lib/when.ts +15 -0
- package/src/sql-types.d.ts +565 -0
- package/src/sql_query_gen.ts +150 -0
- package/src/superFilter/DateFilterFactory.ts +180 -0
- package/src/superFilter/dateFunction.ts +36 -0
- package/src/utils.ts +354 -0
- package/test-output/report/junit.xml +329 -0
- package/tests/JsToSqlParser.test.ts +163 -0
- package/tests/QueryBuilder.test.ts +1320 -0
- package/tests/js-lib/CallExpressionVisitor.test.ts +820 -0
- package/tests/mocks/MockQueryResolver.ts +14 -0
- package/tests/sanity.test.ts +146 -0
- package/tests/sql-lib/binary_expr.test.ts +75 -0
- package/tests/sql-lib/case.test.ts +117 -0
- package/tests/sql-lib/column.test.ts +87 -0
- package/tests/sql-lib/else.test.ts +56 -0
- package/tests/sql-lib/function.test.ts +96 -0
- package/tests/sql-lib/literal.test.ts +75 -0
- package/tests/sql-lib/select.test.ts +245 -0
- package/tests/sql-lib/unary_expr.test.ts +32 -0
- package/tests/utils.test.ts +13 -0
- package/tsconfig.json +24 -0
- 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
|
+
}
|