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