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,289 @@
|
|
|
1
|
+
export interface Relationship {
|
|
2
|
+
fromTable: string;
|
|
3
|
+
fromColumn: string;
|
|
4
|
+
toTable: string;
|
|
5
|
+
toColumn: string;
|
|
6
|
+
isActive?: boolean;
|
|
7
|
+
crossFilterDirection?: 'SINGLE' | 'BOTH' | null;
|
|
8
|
+
fromColumnMultiplicity: '*' | '0..1';
|
|
9
|
+
toColumnMultiplicity: '*' | '0..1';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EpmQueryBuilderOptions {
|
|
13
|
+
config: { source: string };
|
|
14
|
+
selectedColumns?: string[];
|
|
15
|
+
databaseDetails: Record<string, unknown>;
|
|
16
|
+
primaryKeyColumns?: string[];
|
|
17
|
+
pagination?: PaginationConfig;
|
|
18
|
+
searchExpression?: string;
|
|
19
|
+
distinct?: boolean;
|
|
20
|
+
superFilters?: SuperFilterConfig;
|
|
21
|
+
rows: RowColumnConfig[];
|
|
22
|
+
columns: RowColumnConfig[];
|
|
23
|
+
values: MeasureConfig[];
|
|
24
|
+
orderBy: OrderByConfig[];
|
|
25
|
+
skipRollup?: boolean;
|
|
26
|
+
isCrossHighlightEnabled?: boolean;
|
|
27
|
+
measureFilters?: MeasureFilterConfig[];
|
|
28
|
+
dimensionIdMappings?: Record<string, DimensionIdMapping[]>;
|
|
29
|
+
customMeasures?: string[];
|
|
30
|
+
staticFormatStrings?: Record<string, string>;
|
|
31
|
+
isRankingFilterIncluded?: boolean;
|
|
32
|
+
relationships?: Relationship[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RowColumnConfig {
|
|
36
|
+
id: string;
|
|
37
|
+
label: string;
|
|
38
|
+
type: string;
|
|
39
|
+
columnName: string;
|
|
40
|
+
tableName?: string;
|
|
41
|
+
aggregationType?: string;
|
|
42
|
+
dataType?: string;
|
|
43
|
+
orderByRef?: string;
|
|
44
|
+
formatString?: string;
|
|
45
|
+
expansionConfig?: ExpansionConfig;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface MeasureConfig {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
columnName: string;
|
|
52
|
+
tableName: string;
|
|
53
|
+
type: string;
|
|
54
|
+
aggregationType?: string;
|
|
55
|
+
dataType?: string;
|
|
56
|
+
formatString?: string;
|
|
57
|
+
isGetRangeEnabled?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface OrderByConfig {
|
|
61
|
+
id: string;
|
|
62
|
+
columnName: string;
|
|
63
|
+
tableName?: string;
|
|
64
|
+
type: 'ASC' | 'DESC';
|
|
65
|
+
aggregationType?: string;
|
|
66
|
+
isColumnPresentInOutput?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface RangeFilter {
|
|
70
|
+
operator?: string;
|
|
71
|
+
value?: string | number;
|
|
72
|
+
from?: string | number;
|
|
73
|
+
fromCondition?: string;
|
|
74
|
+
to?: string | number;
|
|
75
|
+
toCondition?: string;
|
|
76
|
+
logicalOperator?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface DateRangeFilter {
|
|
80
|
+
startDate: string;
|
|
81
|
+
endDate: string;
|
|
82
|
+
operator?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SearchCondition {
|
|
86
|
+
operator: string;
|
|
87
|
+
value: string;
|
|
88
|
+
logicalOperator?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface RankingConfig {
|
|
92
|
+
type: RankingFilterType;
|
|
93
|
+
rankByColumnId: string;
|
|
94
|
+
rankByAggregationType: AggregationType;
|
|
95
|
+
rankByColumnName?: string;
|
|
96
|
+
rankByTableName?: string;
|
|
97
|
+
rankByColumnType?: string;
|
|
98
|
+
value: number;
|
|
99
|
+
isPercentage?: boolean;
|
|
100
|
+
operator?: string;
|
|
101
|
+
measureId?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface FilterConfig {
|
|
105
|
+
filterType: string;
|
|
106
|
+
columnIds: string[];
|
|
107
|
+
columnNames: string[];
|
|
108
|
+
tableNames: string[];
|
|
109
|
+
columnDataType?: string[];
|
|
110
|
+
sourceType?: string[];
|
|
111
|
+
dataType?: string[];
|
|
112
|
+
valuesFilterApplicationType?: ValuesFilterApplicationType;
|
|
113
|
+
valuesFilter?: { id: string | string[]; label: string | string[] }[];
|
|
114
|
+
isCrossHighlight?: boolean;
|
|
115
|
+
rangeFilter?: RangeFilter[];
|
|
116
|
+
dateRangeFilter?: DateRangeFilter[];
|
|
117
|
+
relativePeriodFilter?: RelativePeriodFilterConfig;
|
|
118
|
+
aggregationType?: string[];
|
|
119
|
+
columnType?: string[];
|
|
120
|
+
logicalOperator?: string;
|
|
121
|
+
isTupleFilter?: boolean;
|
|
122
|
+
searchConditions?: SearchCondition[];
|
|
123
|
+
isRankingFilter?: boolean;
|
|
124
|
+
rankingConfig?: RankingConfig;
|
|
125
|
+
rankingFilter?: RankingConfig[];
|
|
126
|
+
blankFilterConfig?: BlankFilterConfig;
|
|
127
|
+
dateHierarchyConfig?: DateHierarchyConfig;
|
|
128
|
+
compositeColumnConfig?: CompositeColumnConfig;
|
|
129
|
+
fieldParameterMapping?: FieldParameterMapping;
|
|
130
|
+
distinctRowsCount?: number;
|
|
131
|
+
isRelativePeriodFilter?: boolean;
|
|
132
|
+
isDateHierarchyFilter?: boolean;
|
|
133
|
+
isFieldParameterFilter?: boolean;
|
|
134
|
+
isCompositeFilter?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SuperFilterChild {
|
|
138
|
+
filters?: FilterConfig;
|
|
139
|
+
isGroup?: boolean;
|
|
140
|
+
isFilter?: boolean;
|
|
141
|
+
groupId?: string;
|
|
142
|
+
groupLabel?: string;
|
|
143
|
+
condition?: string;
|
|
144
|
+
children?: SuperFilterChild[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface SuperFilterConfig {
|
|
148
|
+
groupId: string;
|
|
149
|
+
groupLabel: string;
|
|
150
|
+
condition: string;
|
|
151
|
+
children: SuperFilterChild[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface MeasureFilterConfig {
|
|
155
|
+
id: string;
|
|
156
|
+
tableName: string;
|
|
157
|
+
columnName: string;
|
|
158
|
+
aggregationType?: string;
|
|
159
|
+
dataType: string;
|
|
160
|
+
operator: string;
|
|
161
|
+
value: string | number;
|
|
162
|
+
logicalOperator?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface PaginationConfig {
|
|
166
|
+
limit?: number;
|
|
167
|
+
skip?: number;
|
|
168
|
+
take?: number;
|
|
169
|
+
reference?: PaginationReference;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface PaginationReference {
|
|
173
|
+
dimensions?: string[];
|
|
174
|
+
instances?: string[];
|
|
175
|
+
command?: PaginationCommand;
|
|
176
|
+
nextToken?: string[];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface ExpansionConfig {
|
|
180
|
+
expansionType: string;
|
|
181
|
+
items?: string[];
|
|
182
|
+
start?: string;
|
|
183
|
+
end?: string;
|
|
184
|
+
step?: number;
|
|
185
|
+
interval?: string;
|
|
186
|
+
hierarchies?: string[];
|
|
187
|
+
hierarchyTable?: string[][];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type AggregationType =
|
|
191
|
+
| 'SUM'
|
|
192
|
+
| 'COUNT'
|
|
193
|
+
| 'COUNT_DISTINCT'
|
|
194
|
+
| 'DISTINCT_COUNT'
|
|
195
|
+
| 'AVG'
|
|
196
|
+
| 'MIN'
|
|
197
|
+
| 'MAX'
|
|
198
|
+
| 'FIRST'
|
|
199
|
+
| 'LAST'
|
|
200
|
+
| 'STANDARD_DEVIATION'
|
|
201
|
+
| 'VARIANCE'
|
|
202
|
+
| 'MEDIAN'
|
|
203
|
+
| 'MODE'
|
|
204
|
+
| 'CONCATENATED'
|
|
205
|
+
| 'CONCATENATED_IDS';
|
|
206
|
+
|
|
207
|
+
export type FilterType =
|
|
208
|
+
| 'VALUES'
|
|
209
|
+
| 'RANGE'
|
|
210
|
+
| 'DATE'
|
|
211
|
+
| 'MEASURE'
|
|
212
|
+
| 'TUPLE'
|
|
213
|
+
| 'RANKING'
|
|
214
|
+
| 'SEARCH'
|
|
215
|
+
| 'DATE_RANGE'
|
|
216
|
+
| 'BLANK'
|
|
217
|
+
| 'FIELD_PARAMETER'
|
|
218
|
+
| 'RELATIVE_PERIOD'
|
|
219
|
+
| 'DATE_HIERARCHY'
|
|
220
|
+
| 'COMPOSITE'
|
|
221
|
+
| 'EXPRESSION';
|
|
222
|
+
|
|
223
|
+
export type RankingFilterType = 'TOP' | 'BOTTOM' | 'BOTH';
|
|
224
|
+
|
|
225
|
+
export type ValuesFilterApplicationType = 'INCLUDE' | 'EXCLUDE';
|
|
226
|
+
|
|
227
|
+
export type PaginationCommand = 'NEXT_HEIR' | 'SIBLING' | 'CHILDREN';
|
|
228
|
+
|
|
229
|
+
export type RelativePeriodFilter =
|
|
230
|
+
| 'DAYS'
|
|
231
|
+
| 'WEEKS'
|
|
232
|
+
| 'MONTHS'
|
|
233
|
+
| 'YEARS'
|
|
234
|
+
| 'CALENDER_WEEKS'
|
|
235
|
+
| 'CALENDER_MONTHS'
|
|
236
|
+
| 'CALENDER_YEARS'
|
|
237
|
+
| 'CALENDER_QUARTERS'
|
|
238
|
+
| 'FISCAL_YEAR'
|
|
239
|
+
| 'FISCAL_QUARTER'
|
|
240
|
+
| 'HOURS'
|
|
241
|
+
| 'MINUTES';
|
|
242
|
+
|
|
243
|
+
export type DateRelativeFilter =
|
|
244
|
+
| 'IS_IN_THE_LAST'
|
|
245
|
+
| 'IS_IN_THE_NEXT'
|
|
246
|
+
| 'IS_IN_THIS'
|
|
247
|
+
| 'IS_BEFORE'
|
|
248
|
+
| 'IS_AFTER';
|
|
249
|
+
|
|
250
|
+
export type SortDirection = 'ASC' | 'DESC';
|
|
251
|
+
|
|
252
|
+
export interface DimensionIdMapping {
|
|
253
|
+
sourceField: string;
|
|
254
|
+
targetTable: string;
|
|
255
|
+
targetField: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface FieldParameterMapping {
|
|
259
|
+
sourceField: string;
|
|
260
|
+
targetTable: string;
|
|
261
|
+
targetField: string;
|
|
262
|
+
filterValues?: string[];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface RelativePeriodFilterConfig {
|
|
266
|
+
duration: number;
|
|
267
|
+
period: RelativePeriodFilter;
|
|
268
|
+
relativeDateFilter: DateRelativeFilter;
|
|
269
|
+
includeToday?: boolean;
|
|
270
|
+
fiscalYearStartMonth?: number;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface DateHierarchyConfig {
|
|
274
|
+
level: 'YEAR' | 'QUARTER' | 'MONTH' | 'DAY' | 'WEEK' | 'DAYOFWEEK';
|
|
275
|
+
values: number[];
|
|
276
|
+
format?: string;
|
|
277
|
+
fiscalYearStartMonth?: number;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface BlankFilterConfig {
|
|
281
|
+
operator: 'IS_BLANK' | 'IS_NOT_BLANK';
|
|
282
|
+
treatEmptyStringAsBlank?: boolean;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface CompositeColumnConfig {
|
|
286
|
+
columnIds: string[];
|
|
287
|
+
operator: 'CONCATENATE' | 'COMBINE';
|
|
288
|
+
separator?: string;
|
|
289
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function formatValue(value: string | number | boolean): string {
|
|
2
|
+
if (typeof value === 'string') {
|
|
3
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
4
|
+
}
|
|
5
|
+
return String(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function sanitizeIdentifier(identifier: string): string {
|
|
9
|
+
return identifier.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function buildColumnReference(
|
|
13
|
+
tableName: string | undefined,
|
|
14
|
+
columnName: string,
|
|
15
|
+
useQuotes = true,
|
|
16
|
+
): string {
|
|
17
|
+
if (!tableName) return columnName;
|
|
18
|
+
if (useQuotes) {
|
|
19
|
+
return `"${tableName}"."${columnName}"`;
|
|
20
|
+
}
|
|
21
|
+
return `${tableName}.${columnName}`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function combineConditions(conditions: string[], operator: 'AND' | 'OR' = 'AND'): string {
|
|
2
|
+
const validConditions = conditions.filter((condition) => condition && condition.trim());
|
|
3
|
+
if (validConditions.length === 0) return '';
|
|
4
|
+
if (validConditions.length === 1) return validConditions[0];
|
|
5
|
+
return `(${validConditions.join(` ${operator} `)})`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildSqlSelect(
|
|
9
|
+
items: string[],
|
|
10
|
+
tableName: string,
|
|
11
|
+
whereClause?: string,
|
|
12
|
+
groupBy?: string[],
|
|
13
|
+
orderBy?: string,
|
|
14
|
+
limit?: string,
|
|
15
|
+
): string {
|
|
16
|
+
let query = `SELECT ${items.join(', ')} FROM ${tableName}`;
|
|
17
|
+
if (whereClause) {
|
|
18
|
+
query += ` WHERE ${whereClause}`;
|
|
19
|
+
}
|
|
20
|
+
if (groupBy?.length) {
|
|
21
|
+
query += ` GROUP BY ${groupBy.join(', ')}`;
|
|
22
|
+
}
|
|
23
|
+
if (orderBy) {
|
|
24
|
+
query += ` ORDER BY ${orderBy}`;
|
|
25
|
+
}
|
|
26
|
+
if (limit) {
|
|
27
|
+
query += ` ${limit}`;
|
|
28
|
+
}
|
|
29
|
+
return query;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildDistinctSelect(
|
|
33
|
+
columns: string[],
|
|
34
|
+
tableName: string,
|
|
35
|
+
whereClause?: string,
|
|
36
|
+
orderBy?: string,
|
|
37
|
+
): string {
|
|
38
|
+
let query = `SELECT DISTINCT ${columns.join(', ')} FROM ${tableName}`;
|
|
39
|
+
if (whereClause) {
|
|
40
|
+
query += ` WHERE ${whereClause}`;
|
|
41
|
+
}
|
|
42
|
+
if (orderBy) {
|
|
43
|
+
query += ` ORDER BY ${orderBy}`;
|
|
44
|
+
}
|
|
45
|
+
return query;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildLimitClause(offset?: number, limit?: number): string {
|
|
49
|
+
if (limit === undefined) return '';
|
|
50
|
+
if (offset !== undefined && offset > 0) {
|
|
51
|
+
return `LIMIT ${limit} OFFSET ${offset}`;
|
|
52
|
+
}
|
|
53
|
+
return `LIMIT ${limit}`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function validateRequiredFields(obj: unknown, fields: string[]): boolean {
|
|
2
|
+
if (!obj) return false;
|
|
3
|
+
const rec = obj as Record<string, unknown>;
|
|
4
|
+
return fields.every((field) => rec[field] !== undefined && rec[field] !== null);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isEmptyOrWhitespace(value: string | undefined | null): boolean {
|
|
8
|
+
return !value || value.trim().length === 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isValidIdentifier(identifier: string): boolean {
|
|
12
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier);
|
|
13
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { EpmQueryBuilderOptions } from '@epm-query-builder/types/query-builder-types';
|
|
2
|
+
import { InvalidConfigurationError } from '@epm-query-builder/errors/QueryBuilderErrors';
|
|
3
|
+
import { BaseUtilities } from '@epm-query-builder/base/BaseUtilities';
|
|
4
|
+
|
|
5
|
+
export class QueryOptionsValidator {
|
|
6
|
+
static validateBasicConfiguration(options: EpmQueryBuilderOptions): void {
|
|
7
|
+
if (!options.databaseDetails) {
|
|
8
|
+
throw new InvalidConfigurationError('databaseDetails is required');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hasRows = options.rows?.length;
|
|
12
|
+
const hasColumns = options.columns?.length;
|
|
13
|
+
const hasValues = options.values?.length;
|
|
14
|
+
const dbType = String(options.databaseDetails?.databaseType || '').toLowerCase();
|
|
15
|
+
const hasSelectAll =
|
|
16
|
+
dbType === 'duckdb' &&
|
|
17
|
+
Array.isArray(options.selectedColumns) &&
|
|
18
|
+
options.selectedColumns.some((c) => {
|
|
19
|
+
const s = String(c || '').trim();
|
|
20
|
+
return s === '*' || (/\*$/i.test(s) && /\./.test(s)) || /^table(name)?[:=]/i.test(s);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!hasRows && !hasColumns && !hasValues && !hasSelectAll) {
|
|
24
|
+
throw new InvalidConfigurationError(
|
|
25
|
+
'At least one of rows, columns, or values must be specified',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.validateTableNames(options);
|
|
30
|
+
this.validateMeasures(options);
|
|
31
|
+
this.validatePagination(options);
|
|
32
|
+
this.validateRelationships(options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private static validateTableNames(options: EpmQueryBuilderOptions): void {
|
|
36
|
+
const allItems = [
|
|
37
|
+
...(options.rows || []),
|
|
38
|
+
...(options.columns || []),
|
|
39
|
+
...(options.values || []),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const missingTableNames = allItems.filter((item) => {
|
|
43
|
+
const hasDirectTableName = Boolean(item.tableName);
|
|
44
|
+
const hasExtractableTableName = Boolean(item.id && BaseUtilities.getTableNameFromId(item.id));
|
|
45
|
+
return !hasDirectTableName && !hasExtractableTableName;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (missingTableNames.length > 0) {
|
|
49
|
+
throw new InvalidConfigurationError(
|
|
50
|
+
'All items must have a tableName specified or extractable from id',
|
|
51
|
+
{
|
|
52
|
+
itemsWithoutTableName: missingTableNames.length,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static validateMeasures(options: EpmQueryBuilderOptions): void {
|
|
59
|
+
if (!options.values?.length) return;
|
|
60
|
+
|
|
61
|
+
const validAggregationTypes = [
|
|
62
|
+
'EARLIEST',
|
|
63
|
+
'LATEST',
|
|
64
|
+
'SUM',
|
|
65
|
+
'COUNT',
|
|
66
|
+
'DISTINCT_COUNT',
|
|
67
|
+
'AVG',
|
|
68
|
+
'AVERAGE',
|
|
69
|
+
'MIN',
|
|
70
|
+
'MINIMUM',
|
|
71
|
+
'MAX',
|
|
72
|
+
'MAXIMUM',
|
|
73
|
+
'FIRST',
|
|
74
|
+
'FIRST_VALUE',
|
|
75
|
+
'LAST',
|
|
76
|
+
'LAST_VALUE',
|
|
77
|
+
'STANDARD_DEVIATION',
|
|
78
|
+
'VARIANCE',
|
|
79
|
+
'VAR_POP',
|
|
80
|
+
'COUNT_DISTINCT',
|
|
81
|
+
'COUNT_DISTINCT_VALUE',
|
|
82
|
+
'MEDIAN',
|
|
83
|
+
'MODE',
|
|
84
|
+
'MODE_VALUE',
|
|
85
|
+
'CONCATENATED',
|
|
86
|
+
'CONCATENATED_IDS',
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
options.values.forEach((measure) => {
|
|
90
|
+
if (!measure.columnName && !measure.id) {
|
|
91
|
+
throw new InvalidConfigurationError('Measure must have either columnName or id', {
|
|
92
|
+
measure: measure.label || 'unnamed',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
measure.aggregationType &&
|
|
98
|
+
!validAggregationTypes.includes(measure.aggregationType.toUpperCase())
|
|
99
|
+
) {
|
|
100
|
+
throw new InvalidConfigurationError(
|
|
101
|
+
`Invalid aggregation type: ${measure.aggregationType}`,
|
|
102
|
+
{
|
|
103
|
+
measure: measure.label || measure.columnName,
|
|
104
|
+
validTypes: validAggregationTypes,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static validatePagination(options: EpmQueryBuilderOptions): void {
|
|
112
|
+
if (!options.pagination) return;
|
|
113
|
+
|
|
114
|
+
const { limit, take, reference } = options.pagination;
|
|
115
|
+
|
|
116
|
+
if (!limit && !take) {
|
|
117
|
+
throw new InvalidConfigurationError(
|
|
118
|
+
'Pagination requires either limit or take to be specified',
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (reference) {
|
|
123
|
+
const { command, dimensions, instances, nextToken } = reference;
|
|
124
|
+
|
|
125
|
+
if (!command) {
|
|
126
|
+
throw new InvalidConfigurationError('Pagination reference requires command');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!['NEXT_HEIR', 'SIBLING', 'CHILDREN'].includes(command)) {
|
|
130
|
+
throw new InvalidConfigurationError(`Invalid pagination command: ${command}`, {
|
|
131
|
+
validCommands: ['NEXT_HEIR', 'SIBLING', 'CHILDREN'],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (command === 'NEXT_HEIR' && (!dimensions?.length || !nextToken?.length)) {
|
|
136
|
+
throw new InvalidConfigurationError(
|
|
137
|
+
'NEXT_HEIR pagination requires dimensions and nextToken',
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
(command === 'SIBLING' || command === 'CHILDREN') &&
|
|
143
|
+
(!dimensions?.length || !instances?.length)
|
|
144
|
+
) {
|
|
145
|
+
throw new InvalidConfigurationError(
|
|
146
|
+
`${command} pagination requires dimensions and instances`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private static validateRelationships(options: EpmQueryBuilderOptions): void {
|
|
153
|
+
const rels = options.relationships || [];
|
|
154
|
+
if (!rels.length) return;
|
|
155
|
+
rels.forEach((r, idx) => {
|
|
156
|
+
if (!r) {
|
|
157
|
+
throw new InvalidConfigurationError(`Relationship at index ${idx} is empty`);
|
|
158
|
+
}
|
|
159
|
+
const required: [string, unknown][] = [
|
|
160
|
+
['fromTable', r.fromTable],
|
|
161
|
+
['fromColumn', r.fromColumn],
|
|
162
|
+
['toTable', r.toTable],
|
|
163
|
+
['toColumn', r.toColumn],
|
|
164
|
+
['fromColumnMultiplicity', r.fromColumnMultiplicity],
|
|
165
|
+
['toColumnMultiplicity', r.toColumnMultiplicity],
|
|
166
|
+
];
|
|
167
|
+
required.forEach(([k, v]) => {
|
|
168
|
+
if (typeof v !== 'string' || String(v).trim().length === 0) {
|
|
169
|
+
throw new InvalidConfigurationError(
|
|
170
|
+
`Relationship[${idx}] field '${k}' must be a non-empty string`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
const multOk = (m: string) => m === '*' || m === '0..1';
|
|
175
|
+
if (!multOk(r.fromColumnMultiplicity) || !multOk(r.toColumnMultiplicity)) {
|
|
176
|
+
throw new InvalidConfigurationError(
|
|
177
|
+
`Relationship[${idx}] multiplicity must be '*' or '0..1'`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { QueryGenerationError } from '@epm-query-builder/errors/QueryBuilderErrors';
|
|
2
|
+
|
|
3
|
+
const SELECT_PATTERN = /^\s*(WITH\s+.*\s+)?SELECT\s+/;
|
|
4
|
+
const SUSPICIOUS_PATTERNS: RegExp[] = [
|
|
5
|
+
/;\s*(DROP|DELETE|INSERT|UPDATE|ALTER|CREATE|EXEC|EXECUTE)\s+/i,
|
|
6
|
+
/--\s*$/m,
|
|
7
|
+
/\/\*.*?\*\//s,
|
|
8
|
+
/'\s*;\s*--/i,
|
|
9
|
+
/union\s+select/i,
|
|
10
|
+
/'\s*or\s*'.*'=/i,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export class SqlQueryValidator {
|
|
14
|
+
static validateQuery(query: string): void {
|
|
15
|
+
if (!query || query.trim().length === 0) {
|
|
16
|
+
throw new QueryGenerationError('Generated query is empty');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const trimmedQuery = query.trim();
|
|
20
|
+
|
|
21
|
+
if (!this.hasValidSqlStructure(trimmedQuery)) {
|
|
22
|
+
throw new QueryGenerationError('Generated query has invalid SQL structure', {
|
|
23
|
+
query: trimmedQuery.substring(0, 200),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.validateBasicSyntax(trimmedQuery);
|
|
28
|
+
this.validateSqlInjection(trimmedQuery);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private static hasValidSqlStructure(query: string): boolean {
|
|
32
|
+
const upperQuery = query.toUpperCase();
|
|
33
|
+
|
|
34
|
+
const hasSelect = SELECT_PATTERN.test(upperQuery);
|
|
35
|
+
|
|
36
|
+
if (!hasSelect) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const hasFrom = upperQuery.includes('FROM');
|
|
41
|
+
const isCountQuery = upperQuery.includes('COUNT(');
|
|
42
|
+
|
|
43
|
+
return hasFrom || isCountQuery;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private static validateBasicSyntax(query: string): void {
|
|
47
|
+
const issues: string[] = [];
|
|
48
|
+
|
|
49
|
+
const parenthesesCount = this.countParentheses(query);
|
|
50
|
+
if (parenthesesCount.open !== parenthesesCount.close) {
|
|
51
|
+
issues.push(
|
|
52
|
+
`Unbalanced parentheses: ${parenthesesCount.open} opening, ${parenthesesCount.close} closing`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const quoteCount = this.countQuotes(query);
|
|
57
|
+
if (quoteCount.single % 2 !== 0) {
|
|
58
|
+
issues.push('Unbalanced single quotes');
|
|
59
|
+
}
|
|
60
|
+
if (quoteCount.double % 2 !== 0) {
|
|
61
|
+
issues.push('Unbalanced double quotes');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.hasDanglingCommas(query)) {
|
|
65
|
+
issues.push('Query contains dangling commas');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.hasEmptySelectList(query)) {
|
|
69
|
+
issues.push('SELECT statement has empty column list');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (issues.length > 0) {
|
|
73
|
+
throw new QueryGenerationError(`SQL syntax validation failed: ${issues.join(', ')}`, {
|
|
74
|
+
issues,
|
|
75
|
+
query: query.substring(0, 200),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static validateSqlInjection(query: string): void {
|
|
81
|
+
const foundPattern = SUSPICIOUS_PATTERNS.find((pattern) => pattern.test(query));
|
|
82
|
+
if (foundPattern) {
|
|
83
|
+
throw new QueryGenerationError('Query contains potentially dangerous SQL patterns', {
|
|
84
|
+
pattern: foundPattern.toString(),
|
|
85
|
+
query: query.substring(0, 100),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private static countParentheses(query: string): { open: number; close: number } {
|
|
91
|
+
const open = (query.match(/\(/g) || []).length;
|
|
92
|
+
const close = (query.match(/\)/g) || []).length;
|
|
93
|
+
return { open, close };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private static countQuotes(query: string): { single: number; double: number } {
|
|
97
|
+
const single = (query.match(/'/g) || []).length;
|
|
98
|
+
const double = (query.match(/"/g) || []).length;
|
|
99
|
+
return { single, double };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static hasDanglingCommas(query: string): boolean {
|
|
103
|
+
const patterns = [
|
|
104
|
+
/,\s*FROM\s+/i,
|
|
105
|
+
/,\s*WHERE\s+/i,
|
|
106
|
+
/,\s*ORDER\s+BY\s+/i,
|
|
107
|
+
/,\s*GROUP\s+BY\s+/i,
|
|
108
|
+
/,\s*HAVING\s+/i,
|
|
109
|
+
/,\s*LIMIT\s+/i,
|
|
110
|
+
/,\s*\)/,
|
|
111
|
+
/SELECT\s+,/i,
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
return patterns.some((pattern) => pattern.test(query));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static hasEmptySelectList(query: string): boolean {
|
|
118
|
+
const selectPattern = /SELECT\s+FROM\s+/i;
|
|
119
|
+
return selectPattern.test(query);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static validateCountQuery(query: string): void {
|
|
123
|
+
this.validateQuery(query);
|
|
124
|
+
|
|
125
|
+
const upperQuery = query.toUpperCase();
|
|
126
|
+
if (!upperQuery.includes('COUNT(')) {
|
|
127
|
+
throw new QueryGenerationError('Count query must contain COUNT() function');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|