dsl-to-sql 1.0.0-fabric-1p-development.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.turbo/turbo-build.log +9 -0
  2. package/.turbo/turbo-check-types.log +4 -0
  3. package/.turbo/turbo-lint.log +126 -0
  4. package/README.md +45 -0
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +236 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/coverage/src/constants.ts.html +826 -0
  14. package/coverage/src/database_types.ts.html +136 -0
  15. package/coverage/src/epm-query-builder/EpmQueryBuilder.ts.html +166 -0
  16. package/coverage/src/epm-query-builder/base/BaseAdvancedAggregations.ts.html +568 -0
  17. package/coverage/src/epm-query-builder/base/BaseAnalyticalFunctions.ts.html +694 -0
  18. package/coverage/src/epm-query-builder/base/BaseCTEGenerator.ts.html +1459 -0
  19. package/coverage/src/epm-query-builder/base/BaseCountQueryBuilder.ts.html +400 -0
  20. package/coverage/src/epm-query-builder/base/BaseMeasureBuilder.ts.html +295 -0
  21. package/coverage/src/epm-query-builder/base/BaseOrderBuilder.ts.html +670 -0
  22. package/coverage/src/epm-query-builder/base/BasePaginationBuilder.ts.html +364 -0
  23. package/coverage/src/epm-query-builder/base/BaseQueryBuilder.ts.html +238 -0
  24. package/coverage/src/epm-query-builder/base/BaseRollupBuilder.ts.html +532 -0
  25. package/coverage/src/epm-query-builder/base/BaseSqlBuilder.ts.html +601 -0
  26. package/coverage/src/epm-query-builder/base/BaseSuperFilterBuilder.ts.html +1966 -0
  27. package/coverage/src/epm-query-builder/base/BaseUtilities.ts.html +1798 -0
  28. package/coverage/src/epm-query-builder/base/ColumnRefUtils.ts.html +211 -0
  29. package/coverage/src/epm-query-builder/base/RelationshipResolver.ts.html +706 -0
  30. package/coverage/src/epm-query-builder/base/SharedFilterBuilder.ts.html +1717 -0
  31. package/coverage/src/epm-query-builder/base/index.html +326 -0
  32. package/coverage/src/epm-query-builder/constants/Aggregations.ts.html +133 -0
  33. package/coverage/src/epm-query-builder/constants/Database.ts.html +103 -0
  34. package/coverage/src/epm-query-builder/constants/Source.ts.html +106 -0
  35. package/coverage/src/epm-query-builder/constants/index.html +146 -0
  36. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts.html +286 -0
  37. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts.html +280 -0
  38. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts.html +1924 -0
  39. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts.html +769 -0
  40. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts.html +643 -0
  41. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts.html +2644 -0
  42. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts.html +478 -0
  43. package/coverage/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts.html +1195 -0
  44. package/coverage/src/epm-query-builder/dialects/duckdb/index.html +221 -0
  45. package/coverage/src/epm-query-builder/errors/QueryBuilderErrors.ts.html +280 -0
  46. package/coverage/src/epm-query-builder/errors/index.html +116 -0
  47. package/coverage/src/epm-query-builder/index.html +116 -0
  48. package/coverage/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts.html +100 -0
  49. package/coverage/src/epm-query-builder/interfaces/index.html +131 -0
  50. package/coverage/src/epm-query-builder/interfaces/index.ts.html +88 -0
  51. package/coverage/src/epm-query-builder/utils/format.ts.html +151 -0
  52. package/coverage/src/epm-query-builder/utils/index.html +146 -0
  53. package/coverage/src/epm-query-builder/utils/sql.ts.html +247 -0
  54. package/coverage/src/epm-query-builder/utils/validation.ts.html +124 -0
  55. package/coverage/src/epm-query-builder/validation/QueryOptionsValidator.ts.html +631 -0
  56. package/coverage/src/epm-query-builder/validation/SqlQueryValidator.ts.html +475 -0
  57. package/coverage/src/epm-query-builder/validation/index.html +131 -0
  58. package/coverage/src/filters/FilterConditionBuilder.ts.html +427 -0
  59. package/coverage/src/filters/filter-types.ts.html +484 -0
  60. package/coverage/src/filters/index.html +131 -0
  61. package/coverage/src/index.html +176 -0
  62. package/coverage/src/index.ts.html +103 -0
  63. package/coverage/src/js-lib/JsToSqlParser.ts.html +736 -0
  64. package/coverage/src/js-lib/ParseContext.ts.html +532 -0
  65. package/coverage/src/js-lib/db/azuresql/AzureSqlCallExpressionVisitor.ts.html +196 -0
  66. package/coverage/src/js-lib/db/azuresql/index.html +116 -0
  67. package/coverage/src/js-lib/db/base/ArrayExpressionVisitor.ts.html +133 -0
  68. package/coverage/src/js-lib/db/base/AssignmentExpressionVisitor.ts.html +187 -0
  69. package/coverage/src/js-lib/db/base/BinaryExpressionVisitor.ts.html +223 -0
  70. package/coverage/src/js-lib/db/base/CallExpressionVisitor.ts.html +5479 -0
  71. package/coverage/src/js-lib/db/base/IdentifierVisitor.ts.html +283 -0
  72. package/coverage/src/js-lib/db/base/LiteralVisitor.ts.html +199 -0
  73. package/coverage/src/js-lib/db/base/MemberExpressionVisitor.ts.html +193 -0
  74. package/coverage/src/js-lib/db/base/ProgramVisitor.ts.html +139 -0
  75. package/coverage/src/js-lib/db/base/UnaryExpressionVisitor.ts.html +181 -0
  76. package/coverage/src/js-lib/db/base/VisitorInterface.ts.html +103 -0
  77. package/coverage/src/js-lib/db/base/index.html +251 -0
  78. package/coverage/src/js-lib/db/bigquery/BigQueryCallExpressionVisitor.ts.html +1747 -0
  79. package/coverage/src/js-lib/db/bigquery/index.html +116 -0
  80. package/coverage/src/js-lib/db/commonTransforms.ts.html +2074 -0
  81. package/coverage/src/js-lib/db/databricks/DatabricksCallExpressionVisitor.ts.html +1303 -0
  82. package/coverage/src/js-lib/db/databricks/index.html +116 -0
  83. package/coverage/src/js-lib/db/fabricsql/FabricSqlCallExpressionVisitor.ts.html +196 -0
  84. package/coverage/src/js-lib/db/fabricsql/index.html +116 -0
  85. package/coverage/src/js-lib/db/fabricwarehouse/FabricWarehouseCallExpressionVisitor.ts.html +292 -0
  86. package/coverage/src/js-lib/db/fabricwarehouse/index.html +116 -0
  87. package/coverage/src/js-lib/db/index.html +116 -0
  88. package/coverage/src/js-lib/db/postgresql/PostgreSqlCallExpressionVisitor.ts.html +985 -0
  89. package/coverage/src/js-lib/db/postgresql/index.html +116 -0
  90. package/coverage/src/js-lib/db/redshift/RedshiftCallExpressionVisitor.ts.html +685 -0
  91. package/coverage/src/js-lib/db/redshift/index.html +116 -0
  92. package/coverage/src/js-lib/db/sample/SampleCallExpressionVisitor.ts.html +196 -0
  93. package/coverage/src/js-lib/db/sample/index.html +116 -0
  94. package/coverage/src/js-lib/db/snowflake/SnowflakeCallExpressionVisitor.ts.html +1447 -0
  95. package/coverage/src/js-lib/db/snowflake/index.html +116 -0
  96. package/coverage/src/js-lib/db/validator/FormulaValidator.ts.html +4162 -0
  97. package/coverage/src/js-lib/db/validator/index.html +116 -0
  98. package/coverage/src/js-lib/index.html +131 -0
  99. package/coverage/src/js-lib/objects/BaseObject.ts.html +169 -0
  100. package/coverage/src/js-lib/objects/DateObject.ts.html +169 -0
  101. package/coverage/src/js-lib/objects/PctObject.ts.html +178 -0
  102. package/coverage/src/js-lib/objects/index.html +146 -0
  103. package/coverage/src/query-builder/PaginationBuilder.ts.html +142 -0
  104. package/coverage/src/query-builder/QueryBuilder.ts.html +3118 -0
  105. package/coverage/src/query-builder/SuperFilterBuilder.ts.html +1969 -0
  106. package/coverage/src/query-builder/index.html +146 -0
  107. package/coverage/src/runtime_var.ts.html +109 -0
  108. package/coverage/src/sql-lib/binary_expr.ts.html +133 -0
  109. package/coverage/src/sql-lib/case.ts.html +133 -0
  110. package/coverage/src/sql-lib/column.ts.html +139 -0
  111. package/coverage/src/sql-lib/else.ts.html +124 -0
  112. package/coverage/src/sql-lib/function.ts.html +112 -0
  113. package/coverage/src/sql-lib/index.html +251 -0
  114. package/coverage/src/sql-lib/join.ts.html +127 -0
  115. package/coverage/src/sql-lib/literal.ts.html +130 -0
  116. package/coverage/src/sql-lib/select.ts.html +547 -0
  117. package/coverage/src/sql-lib/unary_expr.ts.html +112 -0
  118. package/coverage/src/sql-lib/when.ts.html +130 -0
  119. package/coverage/src/sql_query_gen.ts.html +535 -0
  120. package/coverage/src/superFilter/DateFilterFactory.ts.html +625 -0
  121. package/coverage/src/superFilter/dateFunction.ts.html +193 -0
  122. package/coverage/src/superFilter/index.html +131 -0
  123. package/coverage/src/utils.ts.html +571 -0
  124. package/dist/index.cjs +8440 -0
  125. package/dist/index.d.cts +927 -0
  126. package/dist/index.d.cts.map +1 -0
  127. package/dist/index.d.ts +927 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +8387 -0
  130. package/dist/index.js.map +1 -0
  131. package/eslint.config.js +4 -0
  132. package/jest.config.ts +44 -0
  133. package/package.json +45 -0
  134. package/src/constants.ts +247 -0
  135. package/src/epm-query-builder/EpmQueryBuilder.ts +27 -0
  136. package/src/epm-query-builder/base/BaseAdvancedAggregations.ts +161 -0
  137. package/src/epm-query-builder/base/BaseAnalyticalFunctions.ts +203 -0
  138. package/src/epm-query-builder/base/BaseCTEGenerator.ts +458 -0
  139. package/src/epm-query-builder/base/BaseCountQueryBuilder.ts +105 -0
  140. package/src/epm-query-builder/base/BaseMeasureBuilder.ts +87 -0
  141. package/src/epm-query-builder/base/BaseOrderBuilder.ts +195 -0
  142. package/src/epm-query-builder/base/BasePaginationBuilder.ts +93 -0
  143. package/src/epm-query-builder/base/BaseQueryBuilder.ts +51 -0
  144. package/src/epm-query-builder/base/BaseRollupBuilder.ts +149 -0
  145. package/src/epm-query-builder/base/BaseSqlBuilder.ts +172 -0
  146. package/src/epm-query-builder/base/BaseSuperFilterBuilder.ts +627 -0
  147. package/src/epm-query-builder/base/BaseUtilities.ts +571 -0
  148. package/src/epm-query-builder/base/ColumnRefUtils.ts +42 -0
  149. package/src/epm-query-builder/base/RelationshipResolver.ts +207 -0
  150. package/src/epm-query-builder/base/SharedFilterBuilder.ts +544 -0
  151. package/src/epm-query-builder/constants/Aggregations.ts +16 -0
  152. package/src/epm-query-builder/constants/Database.ts +6 -0
  153. package/src/epm-query-builder/constants/Source.ts +7 -0
  154. package/src/epm-query-builder/dialects/duckdb/DuckDbAdvancedAggregations.ts +67 -0
  155. package/src/epm-query-builder/dialects/duckdb/DuckDbJoinBuilder.ts +65 -0
  156. package/src/epm-query-builder/dialects/duckdb/DuckDbMeasureBuilder.ts +626 -0
  157. package/src/epm-query-builder/dialects/duckdb/DuckDbOrderBuilder.ts +228 -0
  158. package/src/epm-query-builder/dialects/duckdb/DuckDbPaginationBuilder.ts +186 -0
  159. package/src/epm-query-builder/dialects/duckdb/DuckDbQueryBuilder.ts +853 -0
  160. package/src/epm-query-builder/dialects/duckdb/DuckDbRollupBuilder.ts +131 -0
  161. package/src/epm-query-builder/dialects/duckdb/DuckDbSuperFilterBuilder.ts +370 -0
  162. package/src/epm-query-builder/errors/QueryBuilderErrors.ts +65 -0
  163. package/src/epm-query-builder/interfaces/IDatabaseQueryBuilder.ts +5 -0
  164. package/src/epm-query-builder/interfaces/index.ts +1 -0
  165. package/src/epm-query-builder/types/query-builder-types.d.ts +289 -0
  166. package/src/epm-query-builder/utils/format.ts +22 -0
  167. package/src/epm-query-builder/utils/sql.ts +54 -0
  168. package/src/epm-query-builder/utils/validation.ts +13 -0
  169. package/src/epm-query-builder/validation/QueryOptionsValidator.ts +182 -0
  170. package/src/epm-query-builder/validation/SqlQueryValidator.ts +130 -0
  171. package/src/filters/FilterConditionBuilder.ts +114 -0
  172. package/src/filters/filter-types.ts +133 -0
  173. package/src/index.ts +10 -0
  174. package/src/js-lib/JsToSqlParser.ts +217 -0
  175. package/src/js-lib/ParseContext.ts +149 -0
  176. package/src/js-lib/db/base/ArrayExpressionVisitor.ts +16 -0
  177. package/src/js-lib/db/base/AssignmentExpressionVisitor.ts +34 -0
  178. package/src/js-lib/db/base/BinaryExpressionVisitor.ts +46 -0
  179. package/src/js-lib/db/base/CallExpressionVisitor.ts +1798 -0
  180. package/src/js-lib/db/base/IdentifierVisitor.ts +66 -0
  181. package/src/js-lib/db/base/LiteralVisitor.ts +38 -0
  182. package/src/js-lib/db/base/MemberExpressionVisitor.ts +36 -0
  183. package/src/js-lib/db/base/ProgramVisitor.ts +18 -0
  184. package/src/js-lib/db/base/UnaryExpressionVisitor.ts +32 -0
  185. package/src/js-lib/db/base/VisitorInterface.ts +6 -0
  186. package/src/js-lib/db/validator/FormulaValidator.ts +1235 -0
  187. package/src/js-lib/objects/BaseObject.ts +28 -0
  188. package/src/js-lib/objects/DateObject.ts +28 -0
  189. package/src/js-lib/objects/PctObject.ts +31 -0
  190. package/src/query-builder/PaginationBuilder.ts +19 -0
  191. package/src/query-builder/QueryBuilder.ts +1035 -0
  192. package/src/query-builder/SuperFilterBuilder.ts +628 -0
  193. package/src/runtime_var.ts +8 -0
  194. package/src/sql-lib/binary_expr.ts +16 -0
  195. package/src/sql-lib/case.ts +16 -0
  196. package/src/sql-lib/column.ts +18 -0
  197. package/src/sql-lib/else.ts +13 -0
  198. package/src/sql-lib/function.ts +9 -0
  199. package/src/sql-lib/join.ts +14 -0
  200. package/src/sql-lib/literal.ts +15 -0
  201. package/src/sql-lib/select.ts +154 -0
  202. package/src/sql-lib/unary_expr.ts +9 -0
  203. package/src/sql-lib/when.ts +15 -0
  204. package/src/sql-types.d.ts +565 -0
  205. package/src/sql_query_gen.ts +150 -0
  206. package/src/superFilter/DateFilterFactory.ts +180 -0
  207. package/src/superFilter/dateFunction.ts +36 -0
  208. package/src/utils.ts +354 -0
  209. package/test-output/report/junit.xml +329 -0
  210. package/tests/JsToSqlParser.test.ts +163 -0
  211. package/tests/QueryBuilder.test.ts +1320 -0
  212. package/tests/js-lib/CallExpressionVisitor.test.ts +820 -0
  213. package/tests/mocks/MockQueryResolver.ts +14 -0
  214. package/tests/sanity.test.ts +146 -0
  215. package/tests/sql-lib/binary_expr.test.ts +75 -0
  216. package/tests/sql-lib/case.test.ts +117 -0
  217. package/tests/sql-lib/column.test.ts +87 -0
  218. package/tests/sql-lib/else.test.ts +56 -0
  219. package/tests/sql-lib/function.test.ts +96 -0
  220. package/tests/sql-lib/literal.test.ts +75 -0
  221. package/tests/sql-lib/select.test.ts +245 -0
  222. package/tests/sql-lib/unary_expr.test.ts +32 -0
  223. package/tests/utils.test.ts +13 -0
  224. package/tsconfig.json +24 -0
  225. package/tsdown.config.ts +23 -0
@@ -0,0 +1,1320 @@
1
+ // write tests for QueryBuilder
2
+
3
+ import { MDM_COLUMN_TYPE, ResolvedExpressionType } from '../src/js-lib/ParseContext';
4
+ import { IMDMColumnConfigWithParsedMeta } from '../src/js-lib/ParseContext';
5
+ import { QueryBuilder, QueryBuilderImpl } from '../src/query-builder/QueryBuilder';
6
+ import { MockQueryResolver } from './mocks/MockQueryResolver';
7
+ import { createPaginationBuilder } from '../src/query-builder/PaginationBuilder';
8
+ import { RUNTIME_LOGGEDIN_EMAIL, RUNTIME_LOGGEDIN_NAME } from '../src/runtime_var';
9
+
10
+ describe('QueryBuilder', () => {
11
+ const mockQueryResolver = new MockQueryResolver('TestTable');
12
+ const mockRuntimeVariables = {
13
+ $RUNTIME_LOGGEDIN_EMAIL: 'test@mdm.com',
14
+ $RUNTIME_LOGGEDIN_NAME: 'Test User',
15
+ };
16
+
17
+ test('should create sql query when table does not have formula columns', () => {
18
+ const filterResolverSpy = jest.spyOn(mockQueryResolver, 'filterResolver');
19
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
20
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
21
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
22
+ };
23
+ const queryBuilder = new QueryBuilder({
24
+ selectedColumns: ['Amount', 'Status'],
25
+ databaseDetails: {
26
+ dataTableName: 'TestTable',
27
+
28
+ schema: 'dbo',
29
+ database: 'mdm',
30
+ },
31
+ primaryKeyColumns: ['PK'],
32
+ filterConfig: {
33
+ rules: [
34
+ { column: 'Amount', operator: '>', value: 100 },
35
+ { column: 'Status', operator: '=', value: 'Active' },
36
+ ],
37
+ combinator: 'and',
38
+ not: false,
39
+ },
40
+ pagination: { skip: 100, take: 200 },
41
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
42
+ columnConfigMap: mockColumnConfigMap,
43
+ queryResolver: mockQueryResolver,
44
+ runtimeVariables: mockRuntimeVariables,
45
+ });
46
+ const sql = queryBuilder.build();
47
+ expect(sql?.finalQuery).toBe(
48
+ `SELECT [Amount], [Status] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' ORDER BY [Amount] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
49
+ );
50
+ expect(filterResolverSpy).toHaveBeenCalledTimes(1);
51
+ filterResolverSpy.mockRestore();
52
+ });
53
+
54
+ test('should create sql query with pagination based on mssql database', () => {
55
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
56
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
57
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
58
+ };
59
+ const queryBuilder = new QueryBuilder({
60
+ selectedColumns: ['Amount', 'Status'],
61
+ databaseDetails: {
62
+ dataTableName: 'TestTable',
63
+
64
+ schema: 'dbo',
65
+ database: 'mdm',
66
+ },
67
+ primaryKeyColumns: ['PK'],
68
+ filterConfig: {
69
+ rules: [
70
+ { column: 'Amount', operator: '>', value: 100 },
71
+ { column: 'Status', operator: '=', value: 'Active' },
72
+ ],
73
+ combinator: 'and',
74
+ not: false,
75
+ },
76
+ pagination: { skip: 100, take: 200 },
77
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
78
+ columnConfigMap: mockColumnConfigMap,
79
+ queryResolver: new MockQueryResolver('TestTable'),
80
+ runtimeVariables: mockRuntimeVariables,
81
+ });
82
+ const sql = queryBuilder.build();
83
+ expect(sql?.finalQuery).toBe(
84
+ `SELECT [Amount], [Status] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' ORDER BY [Amount] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
85
+ );
86
+ });
87
+
88
+ test('should create sql query when table does not have formula columns and distinct is true', () => {
89
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
90
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
91
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
92
+ };
93
+ const queryBuilder = new QueryBuilder({
94
+ distinct: true,
95
+ selectedColumns: ['Amount'],
96
+ databaseDetails: {
97
+ dataTableName: 'TestTable',
98
+
99
+ schema: 'dbo',
100
+ database: 'mdm',
101
+ },
102
+ primaryKeyColumns: ['PK'],
103
+ filterConfig: {
104
+ rules: [
105
+ { column: 'Amount', operator: '>', value: 100 },
106
+ { column: 'Status', operator: '=', value: 'Active' },
107
+ ],
108
+ combinator: 'and',
109
+ not: false,
110
+ },
111
+ pagination: { skip: 100, take: 200 },
112
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
113
+ columnConfigMap: mockColumnConfigMap,
114
+ queryResolver: mockQueryResolver,
115
+ runtimeVariables: mockRuntimeVariables,
116
+ });
117
+ const sql = queryBuilder.build();
118
+ expect(sql?.finalQuery).toBe(
119
+ `SELECT DISTINCT [Amount] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' ORDER BY [Amount] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
120
+ );
121
+ });
122
+
123
+ test('should create sql query when table has formula columns with resolved expression as column expression', () => {
124
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
125
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
126
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
127
+ Result: {
128
+ columnType: MDM_COLUMN_TYPE.FORMULA,
129
+ columnName: 'Result',
130
+ columnMeta: {
131
+ sqlQueryProps: {
132
+ resolvedExpression: {
133
+ type: ResolvedExpressionType.COLUMN,
134
+ value: ['[Amount] + 100'],
135
+ },
136
+ },
137
+ },
138
+ },
139
+ };
140
+ const queryBuilder = new QueryBuilder({
141
+ selectedColumns: ['Amount', 'Status', 'Result'],
142
+ databaseDetails: {
143
+ dataTableName: 'TestTable',
144
+
145
+ schema: 'dbo',
146
+ database: 'mdm',
147
+ },
148
+ primaryKeyColumns: ['PK'],
149
+ filterConfig: {
150
+ rules: [
151
+ { column: 'Amount', operator: '>', value: 100 },
152
+ { column: 'Status', operator: '=', value: 'Active' },
153
+ ],
154
+ combinator: 'and',
155
+ not: false,
156
+ },
157
+ pagination: { skip: 100, take: 200 },
158
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
159
+ columnConfigMap: mockColumnConfigMap,
160
+ queryResolver: mockQueryResolver,
161
+ runtimeVariables: mockRuntimeVariables,
162
+ });
163
+ const sql = queryBuilder.build();
164
+ expect(sql?.finalQuery).toBe(
165
+ `SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' ORDER BY [Amount] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
166
+ );
167
+ });
168
+
169
+ test('should create sql query when table has formula columns with resolved expression as column expression and distinct is true', () => {
170
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
171
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
172
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
173
+ Result: {
174
+ columnType: MDM_COLUMN_TYPE.FORMULA,
175
+ columnName: 'Result',
176
+ columnMeta: {
177
+ sqlQueryProps: {
178
+ resolvedExpression: {
179
+ type: ResolvedExpressionType.COLUMN,
180
+ value: ['[Amount] + 100'],
181
+ },
182
+ },
183
+ deps: ['Amount'],
184
+ },
185
+ },
186
+ };
187
+ const queryBuilder = new QueryBuilder({
188
+ distinct: true,
189
+ selectedColumns: ['Status', 'Result'],
190
+ databaseDetails: {
191
+ dataTableName: 'TestTable',
192
+
193
+ schema: 'dbo',
194
+ database: 'mdm',
195
+ },
196
+ primaryKeyColumns: ['PK'],
197
+ filterConfig: {
198
+ rules: [
199
+ { column: 'Amount', operator: '>', value: 100 },
200
+ { column: 'Status', operator: '=', value: 'Active' },
201
+ ],
202
+ combinator: 'and',
203
+ not: false,
204
+ },
205
+ pagination: { skip: 100, take: 200 },
206
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
207
+ columnConfigMap: mockColumnConfigMap,
208
+ queryResolver: mockQueryResolver,
209
+ runtimeVariables: mockRuntimeVariables,
210
+ });
211
+ const sql = queryBuilder.build();
212
+ expect(sql?.finalQuery).toBe(
213
+ `SELECT DISTINCT [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
214
+ );
215
+ });
216
+
217
+ test('should create sql query when table has formula columns with resolved expression as column expression with dependency only on native columns', () => {
218
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
219
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
220
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
221
+ Result: {
222
+ columnType: MDM_COLUMN_TYPE.FORMULA,
223
+ columnName: 'Result',
224
+ columnMeta: {
225
+ sqlQueryProps: {
226
+ resolvedExpression: {
227
+ type: ResolvedExpressionType.COLUMN,
228
+ value: ['[Amount] + 100'],
229
+ },
230
+ },
231
+ deps: ['Amount'],
232
+ },
233
+ },
234
+ Revenue: {
235
+ columnType: MDM_COLUMN_TYPE.FORMULA,
236
+ columnName: 'Revenue',
237
+ columnMeta: {
238
+ sqlQueryProps: {
239
+ resolvedExpression: {
240
+ type: ResolvedExpressionType.COLUMN,
241
+ value: ['[Amount] * 0.25'],
242
+ },
243
+ },
244
+ deps: ['Amount'],
245
+ },
246
+ },
247
+ };
248
+ const queryBuilder = new QueryBuilder({
249
+ selectedColumns: ['Amount', 'Status', 'Result', 'Revenue'],
250
+ databaseDetails: {
251
+ dataTableName: 'TestTable',
252
+
253
+ schema: 'dbo',
254
+ database: 'mdm',
255
+ },
256
+ primaryKeyColumns: ['PK'],
257
+ filterConfig: {
258
+ rules: [
259
+ { column: 'Amount', operator: '>', value: 100 },
260
+ { column: 'Status', operator: '=', value: 'Active' },
261
+ ],
262
+ combinator: 'and',
263
+ not: false,
264
+ },
265
+ pagination: { skip: 100, take: 200 },
266
+ sortConfig: [{ column: 'Amount', order: 'ASC' }],
267
+ columnConfigMap: mockColumnConfigMap,
268
+ queryResolver: mockQueryResolver,
269
+ runtimeVariables: mockRuntimeVariables,
270
+ });
271
+ const sql = queryBuilder.build();
272
+ expect(sql?.finalQuery).toBe(
273
+ `SELECT [Amount], [Status], [Amount] + 100 AS [Result], [Amount] * 0.25 AS [Revenue] FROM [mdm].[dbo].[TestTable] WHERE [Amount] > 100 AND LOWER([Status]) = 'active' ORDER BY [Amount] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
274
+ );
275
+ });
276
+
277
+ test('should create sql query when table has multiple formula columns with resolved expression as column expression', () => {
278
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
279
+ Price: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Price', columnMeta: {} },
280
+ Quantity: {
281
+ columnType: MDM_COLUMN_TYPE.NUMBER,
282
+ columnName: 'Quantity',
283
+ columnMeta: {},
284
+ },
285
+ SalePrice: {
286
+ columnType: MDM_COLUMN_TYPE.NUMBER,
287
+ columnName: 'SalePrice',
288
+ columnMeta: {},
289
+ },
290
+ Cost: {
291
+ columnType: MDM_COLUMN_TYPE.FORMULA,
292
+ columnName: 'Cost',
293
+ columnMeta: {
294
+ sqlQueryProps: {
295
+ resolvedExpression: {
296
+ type: ResolvedExpressionType.COLUMN,
297
+ value: ['[Price] * [Quantity]'],
298
+ },
299
+ },
300
+ deps: ['Price', 'Quantity'],
301
+ },
302
+ },
303
+ Profit: {
304
+ columnType: MDM_COLUMN_TYPE.FORMULA,
305
+ columnName: 'Profit',
306
+ columnMeta: {
307
+ sqlQueryProps: {
308
+ resolvedExpression: {
309
+ type: ResolvedExpressionType.COLUMN,
310
+ value: ['[SalePrice] - [Cost]'],
311
+ },
312
+ },
313
+ deps: ['SalePrice', 'Cost'],
314
+ },
315
+ },
316
+ };
317
+ const queryBuilder = new QueryBuilder({
318
+ selectedColumns: ['Price', 'Quantity', 'Cost', 'Profit'],
319
+ databaseDetails: {
320
+ dataTableName: 'TestTable',
321
+
322
+ schema: 'dbo',
323
+ database: 'mdm',
324
+ },
325
+ primaryKeyColumns: ['PK'],
326
+ filterConfig: {
327
+ rules: [
328
+ { column: 'Price', operator: '>', value: 100 },
329
+ { column: 'Quantity', operator: '=', value: 100 },
330
+ ],
331
+ combinator: 'and',
332
+ not: false,
333
+ },
334
+ pagination: { skip: 100, take: 200 },
335
+ sortConfig: [{ column: 'Price', order: 'ASC' }],
336
+ columnConfigMap: mockColumnConfigMap,
337
+ queryResolver: mockQueryResolver,
338
+ runtimeVariables: mockRuntimeVariables,
339
+ });
340
+ const sql = queryBuilder.build();
341
+ expect(sql?.finalQuery).toBe(
342
+ `WITH cte_main AS (SELECT [Price], [Quantity], [SalePrice], [Price] * [Quantity] AS [Cost] FROM [mdm].[dbo].[TestTable] WHERE [Price] > 100 AND [Quantity] = 100 ORDER BY [Price] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY), cte_staging_cte_main AS (SELECT *, [SalePrice] - [Cost] AS [Profit] FROM cte_main) SELECT * FROM cte_staging_cte_main`,
343
+ );
344
+ });
345
+
346
+ test('should create sql query when table has formula columns with resolved expression as cte', () => {
347
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
348
+ Price: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Price', columnMeta: {} },
349
+ Quantity: {
350
+ columnType: MDM_COLUMN_TYPE.NUMBER,
351
+ columnName: 'Quantity',
352
+ columnMeta: {},
353
+ },
354
+ SalePrice: {
355
+ columnType: MDM_COLUMN_TYPE.NUMBER,
356
+ columnName: 'SalePrice',
357
+ columnMeta: {},
358
+ },
359
+ Cost: {
360
+ columnType: MDM_COLUMN_TYPE.FORMULA,
361
+ columnName: 'Cost',
362
+ columnMeta: {
363
+ sqlQueryProps: {
364
+ resolvedExpression: {
365
+ type: ResolvedExpressionType.CTE,
366
+ cteName: 'cte_cost',
367
+ value: [
368
+ 'cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM $RUNTIME_TABLE_NAME)',
369
+ ],
370
+ },
371
+ },
372
+ },
373
+ },
374
+ Profit: {
375
+ columnType: MDM_COLUMN_TYPE.FORMULA,
376
+ columnName: 'Profit',
377
+ columnMeta: {
378
+ sqlQueryProps: {
379
+ resolvedExpression: {
380
+ type: ResolvedExpressionType.COLUMN,
381
+ value: ['[SalePrice] - [Cost]'],
382
+ },
383
+ },
384
+ },
385
+ },
386
+ };
387
+ const queryBuilder = new QueryBuilder({
388
+ selectedColumns: ['Price', 'Quantity', 'Cost', 'Profit'],
389
+ databaseDetails: {
390
+ dataTableName: 'TestTable',
391
+
392
+ schema: 'dbo',
393
+ database: 'mdm',
394
+ },
395
+ primaryKeyColumns: ['PK'],
396
+ filterConfig: {
397
+ rules: [
398
+ { column: 'Price', operator: '>', value: 100 },
399
+ { column: 'Quantity', operator: '=', value: 100 },
400
+ ],
401
+ combinator: 'and',
402
+ not: false,
403
+ },
404
+ pagination: { skip: 100, take: 200 },
405
+ sortConfig: [{ column: 'Price', order: 'ASC' }],
406
+ columnConfigMap: mockColumnConfigMap,
407
+ queryResolver: mockQueryResolver,
408
+ runtimeVariables: mockRuntimeVariables,
409
+ });
410
+ const sql = queryBuilder.build();
411
+ expect(sql?.finalQuery).toBe(
412
+ `WITH cte_main AS (SELECT [Price], [Quantity] FROM [mdm].[dbo].[TestTable] WHERE [Price] > 100 AND [Quantity] = 100 ORDER BY [Price] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY), cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM cte_main), cte_staging_cte_cost AS (SELECT *, [SalePrice] - [Cost] AS [Profit] FROM cte_cost) SELECT * FROM cte_staging_cte_cost`,
413
+ );
414
+ });
415
+
416
+ test('should create sql query when table has formula columns with resolved expression as column expression with filter and sort and distinct is true', () => {
417
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
418
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
419
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
420
+ Result: {
421
+ columnType: MDM_COLUMN_TYPE.FORMULA,
422
+ columnName: 'Result',
423
+ columnMeta: {
424
+ sqlQueryProps: {
425
+ resolvedExpression: {
426
+ type: ResolvedExpressionType.COLUMN,
427
+ value: ['[Amount] + 100'],
428
+ },
429
+ },
430
+ deps: ['Amount'],
431
+ },
432
+ },
433
+ };
434
+ const queryBuilder = new QueryBuilder({
435
+ distinct: true,
436
+ selectedColumns: ['Result'],
437
+ databaseDetails: {
438
+ dataTableName: 'TestTable',
439
+
440
+ schema: 'dbo',
441
+ database: 'mdm',
442
+ },
443
+ primaryKeyColumns: ['PK'],
444
+ filterConfig: {
445
+ rules: [
446
+ { column: 'Result', operator: '>', value: 100 },
447
+ { column: 'Status', operator: '=', value: 'Active' },
448
+ ],
449
+ combinator: 'and',
450
+ not: false,
451
+ },
452
+ pagination: { skip: 100, take: 200 },
453
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
454
+ columnConfigMap: mockColumnConfigMap,
455
+ queryResolver: mockQueryResolver,
456
+ runtimeVariables: mockRuntimeVariables,
457
+ });
458
+ const sql = queryBuilder.build();
459
+ expect(sql?.finalQuery).toBe(
460
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active') SELECT DISTINCT [Result] FROM cte_main WHERE [Result] > 100 ORDER BY [Result] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
461
+ );
462
+ });
463
+
464
+ test('should create sql query when table has formula columns with resolved expression as cte with filter and sort', () => {
465
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
466
+ Price: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Price', columnMeta: {} },
467
+ Quantity: {
468
+ columnType: MDM_COLUMN_TYPE.NUMBER,
469
+ columnName: 'Quantity',
470
+ columnMeta: {},
471
+ },
472
+ SalePrice: {
473
+ columnType: MDM_COLUMN_TYPE.NUMBER,
474
+ columnName: 'SalePrice',
475
+ columnMeta: {},
476
+ },
477
+ Profit: {
478
+ columnType: MDM_COLUMN_TYPE.FORMULA,
479
+ columnName: 'Profit',
480
+ columnMeta: {
481
+ sqlQueryProps: {
482
+ resolvedExpression: {
483
+ type: ResolvedExpressionType.COLUMN,
484
+ value: ['[SalePrice] - [Cost]'],
485
+ },
486
+ },
487
+ deps: ['SalePrice', 'Cost'],
488
+ },
489
+ },
490
+ Cost: {
491
+ columnType: MDM_COLUMN_TYPE.FORMULA,
492
+ columnName: 'Cost',
493
+ columnMeta: {
494
+ sqlQueryProps: {
495
+ resolvedExpression: {
496
+ type: ResolvedExpressionType.CTE,
497
+ cteName: 'cte_cost',
498
+ value: [
499
+ 'cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM $RUNTIME_TABLE_NAME)',
500
+ ],
501
+ },
502
+ },
503
+ deps: ['Price', 'Quantity'],
504
+ },
505
+ },
506
+ };
507
+ const queryBuilder = new QueryBuilder({
508
+ selectedColumns: ['Price', 'Quantity', 'Cost', 'Profit'],
509
+ databaseDetails: {
510
+ dataTableName: 'TestTable',
511
+
512
+ schema: 'dbo',
513
+ database: 'mdm',
514
+ },
515
+ primaryKeyColumns: ['PK'],
516
+ filterConfig: {
517
+ rules: [
518
+ { column: 'Cost', operator: '>', value: 100 },
519
+ { column: 'Quantity', operator: '=', value: 100 },
520
+ ],
521
+ combinator: 'and',
522
+ not: false,
523
+ },
524
+ pagination: { skip: 100, take: 200 },
525
+ sortConfig: [{ column: 'Profit', order: 'ASC' }],
526
+ columnConfigMap: mockColumnConfigMap,
527
+ queryResolver: mockQueryResolver,
528
+ runtimeVariables: mockRuntimeVariables,
529
+ });
530
+
531
+ const sql = queryBuilder.build();
532
+ expect(sql?.finalQuery).toBe(
533
+ `WITH cte_main AS (SELECT [Price], [Quantity], [SalePrice] FROM [mdm].[dbo].[TestTable] WHERE [Quantity] = 100), cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM cte_main), cte_staging_cte_cost AS (SELECT *, [SalePrice] - [Cost] AS [Profit] FROM cte_cost WHERE [Cost] > 100 ORDER BY [Profit] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY) SELECT * FROM cte_staging_cte_cost`,
534
+ );
535
+ });
536
+
537
+ test('should create sql query when table has formula columns with resolved expression containing aggregate function as cte with filter and sort', () => {
538
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
539
+ Price: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Price', columnMeta: {} },
540
+ Quantity: {
541
+ columnType: MDM_COLUMN_TYPE.NUMBER,
542
+ columnName: 'Quantity',
543
+ columnMeta: {},
544
+ },
545
+ SalePrice: {
546
+ columnType: MDM_COLUMN_TYPE.NUMBER,
547
+ columnName: 'SalePrice',
548
+ columnMeta: {},
549
+ },
550
+ Cost: {
551
+ columnType: MDM_COLUMN_TYPE.FORMULA,
552
+ columnName: 'Cost',
553
+ columnMeta: {
554
+ sqlQueryProps: {
555
+ resolvedExpression: {
556
+ type: ResolvedExpressionType.CTE,
557
+ cteName: 'cte_cost',
558
+ value: [
559
+ 'cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM $RUNTIME_TABLE_NAME)',
560
+ ],
561
+ },
562
+ },
563
+ deps: ['Price', 'Quantity'],
564
+ },
565
+ },
566
+ MinPrice: {
567
+ columnType: MDM_COLUMN_TYPE.FORMULA,
568
+ columnName: 'MinPrice',
569
+ columnMeta: {
570
+ sqlQueryProps: {
571
+ resolvedExpression: {
572
+ type: ResolvedExpressionType.CTE,
573
+ cteName: 'cte_MinAmount_0_17',
574
+ value: [
575
+ 'cte_union_MinAmount_0_17 AS (SELECT PK, [Cost] AS [val_MinAmount] FROM $RUNTIME_TABLE_NAME UNION ALL SELECT PK, [SalePrice] AS [val_MinAmount] FROM $RUNTIME_TABLE_NAME), cte_agg_MinAmount_0_17 AS (SELECT PK, MIN([val_MinAmount]) AS [MinAmount] FROM cte_union_MinAmount_0_17 GROUP BY PK), cte_MinAmount_0_17 AS (SELECT $RUNTIME_TABLE_NAME.*, [MinAmount] FROM $RUNTIME_TABLE_NAME INNER JOIN cte_agg_MinAmount_0_17 ON $RUNTIME_TABLE_NAME.PK = cte_agg_MinAmount_0_17.PK)',
576
+ ],
577
+ },
578
+ },
579
+ deps: ['Cost', 'SalePrice'],
580
+ },
581
+ },
582
+ Profit: {
583
+ columnType: MDM_COLUMN_TYPE.FORMULA,
584
+ columnName: 'Profit',
585
+ columnMeta: {
586
+ sqlQueryProps: {
587
+ resolvedExpression: {
588
+ type: ResolvedExpressionType.COLUMN,
589
+ value: ['[SalePrice] - [Cost]'],
590
+ },
591
+ },
592
+ deps: ['SalePrice', 'Cost'],
593
+ },
594
+ },
595
+ };
596
+ const queryBuilder = new QueryBuilder({
597
+ selectedColumns: ['Price', 'Quantity', 'Cost', 'Profit', 'MinPrice'],
598
+ databaseDetails: {
599
+ dataTableName: 'TestTable',
600
+
601
+ schema: 'dbo',
602
+ database: 'mdm',
603
+ },
604
+ primaryKeyColumns: ['PK'],
605
+ filterConfig: {
606
+ rules: [
607
+ { column: 'Cost', operator: '>', value: 100 },
608
+ { column: 'Quantity', operator: '=', value: 100 },
609
+ ],
610
+ combinator: 'and',
611
+ not: false,
612
+ },
613
+ pagination: { skip: 100, take: 200 },
614
+ sortConfig: [{ column: 'MinPrice', order: 'ASC' }],
615
+ columnConfigMap: mockColumnConfigMap,
616
+ queryResolver: mockQueryResolver,
617
+ runtimeVariables: mockRuntimeVariables,
618
+ });
619
+
620
+ const sql = queryBuilder.build();
621
+ expect(sql?.finalQuery).toBe(
622
+ `WITH cte_main AS (SELECT [Price], [Quantity], [SalePrice] FROM [mdm].[dbo].[TestTable] WHERE [Quantity] = 100), cte_cost AS (SELECT *, [Price] * [Quantity] AS [Cost] FROM cte_main), cte_staging_cte_cost AS (SELECT *, [SalePrice] - [Cost] AS [Profit] FROM cte_cost WHERE [Cost] > 100), cte_union_MinAmount_0_17 AS (SELECT PK, [Cost] AS [val_MinAmount] FROM cte_staging_cte_cost UNION ALL SELECT PK, [SalePrice] AS [val_MinAmount] FROM cte_staging_cte_cost), cte_agg_MinAmount_0_17 AS (SELECT PK, MIN([val_MinAmount]) AS [MinAmount] FROM cte_union_MinAmount_0_17 GROUP BY PK), cte_MinAmount_0_17 AS (SELECT cte_staging_cte_cost.*, [MinAmount] FROM cte_staging_cte_cost INNER JOIN cte_agg_MinAmount_0_17 ON cte_staging_cte_cost.PK = cte_agg_MinAmount_0_17.PK), cte_post_cte_MinAmount_0_17 AS (SELECT * FROM cte_MinAmount_0_17 ORDER BY [MinPrice] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY) SELECT * FROM cte_post_cte_MinAmount_0_17`,
623
+ );
624
+ });
625
+
626
+ test('shouild create query including dependencies which are not part of selected columns', () => {
627
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
628
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
629
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
630
+ Result: {
631
+ columnType: MDM_COLUMN_TYPE.FORMULA,
632
+ columnName: 'Result',
633
+ columnMeta: {
634
+ sqlQueryProps: {
635
+ resolvedExpression: {
636
+ type: ResolvedExpressionType.COLUMN,
637
+ value: ['[Amount] + 100'],
638
+ },
639
+ },
640
+ deps: ['Amount'],
641
+ },
642
+ },
643
+ };
644
+ const queryBuilder = new QueryBuilder({
645
+ selectedColumns: ['Result'], // does not include 'Amount' column but it is a dependency of 'Result' column
646
+ databaseDetails: {
647
+ dataTableName: 'TestTable',
648
+
649
+ schema: 'dbo',
650
+ database: 'mdm',
651
+ },
652
+ primaryKeyColumns: ['PK'],
653
+ filterConfig: {
654
+ rules: [
655
+ { column: 'Result', operator: '>', value: 100 },
656
+ { column: 'Status', operator: '=', value: 'Active' },
657
+ ],
658
+ combinator: 'and',
659
+ not: false,
660
+ },
661
+ pagination: { skip: 100, take: 200 },
662
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
663
+ columnConfigMap: mockColumnConfigMap,
664
+ queryResolver: mockQueryResolver,
665
+ runtimeVariables: mockRuntimeVariables,
666
+ });
667
+ const sql = queryBuilder.build();
668
+ expect(sql?.finalQuery).toBe(
669
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active') SELECT * FROM cte_main WHERE [Result] > 100 ORDER BY [Result] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
670
+ );
671
+ });
672
+
673
+ test('should return calculation order', () => {
674
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
675
+ Price: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Price', columnMeta: {} },
676
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
677
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
678
+ F1: {
679
+ columnType: MDM_COLUMN_TYPE.FORMULA,
680
+ columnName: 'F1',
681
+ columnMeta: { deps: ['Amount'] },
682
+ },
683
+ F2: {
684
+ columnType: MDM_COLUMN_TYPE.FORMULA,
685
+ columnName: 'F2',
686
+ columnMeta: { deps: ['Amount', 'Status'] },
687
+ },
688
+ F3: {
689
+ columnType: MDM_COLUMN_TYPE.FORMULA,
690
+ columnName: 'F3',
691
+ columnMeta: { deps: ['Amount', 'F1'] },
692
+ },
693
+ F4: {
694
+ columnType: MDM_COLUMN_TYPE.FORMULA,
695
+ columnName: 'F4',
696
+ columnMeta: { deps: ['F2', 'Status'] },
697
+ },
698
+ F5: {
699
+ columnType: MDM_COLUMN_TYPE.FORMULA,
700
+ columnName: 'F5',
701
+ columnMeta: { deps: ['F1', 'F3'] },
702
+ },
703
+ };
704
+
705
+ const queryBuilder = new QueryBuilderImpl(
706
+ {
707
+ distinct: true,
708
+ selectedColumns: ['Status', 'Result'], // does not include 'Amount' column but it is a dependency of 'Result' column
709
+ databaseDetails: {
710
+ dataTableName: 'TestTable',
711
+
712
+ schema: 'dbo',
713
+ database: 'mdm',
714
+ },
715
+ primaryKeyColumns: ['PK'],
716
+ filterConfig: { rules: [], combinator: 'and', not: false },
717
+ pagination: { skip: 100, take: 200 },
718
+ sortConfig: [{ column: 'F1', order: 'ASC' }],
719
+ columnConfigMap: mockColumnConfigMap,
720
+ queryResolver: mockQueryResolver,
721
+ runtimeVariables: mockRuntimeVariables,
722
+ },
723
+ createPaginationBuilder(),
724
+ );
725
+
726
+ const calcOrder = queryBuilder.getCalculationOrder(new Set(['F1', 'F2', 'F3', 'F4', 'F5']));
727
+ expect(calcOrder.nativeColumns).toEqual(new Set(['Amount', 'Status']));
728
+ expect(
729
+ calcOrder.formulaColumnsByDependencyLevel.map((c) => c.map((c) => c.columnName)),
730
+ ).toEqual([['F1', 'F2'], ['F3', 'F4'], ['F5']]);
731
+ });
732
+
733
+ test('should create count query', () => {
734
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
735
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
736
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
737
+ Result: {
738
+ columnType: MDM_COLUMN_TYPE.FORMULA,
739
+ columnName: 'Result',
740
+ columnMeta: {
741
+ sqlQueryProps: {
742
+ resolvedExpression: {
743
+ type: ResolvedExpressionType.COLUMN,
744
+ value: ['[Amount] + 100'],
745
+ },
746
+ },
747
+ deps: ['Amount'],
748
+ },
749
+ },
750
+ };
751
+ const queryBuilder = new QueryBuilder({
752
+ selectedColumns: ['Status', 'Result'],
753
+ databaseDetails: {
754
+ dataTableName: 'TestTable',
755
+
756
+ schema: 'dbo',
757
+ database: 'mdm',
758
+ },
759
+ primaryKeyColumns: ['PK'],
760
+ filterConfig: {
761
+ rules: [
762
+ { column: 'Result', operator: '>', value: 100 },
763
+ { column: 'Status', operator: '=', value: 'Active' },
764
+ ],
765
+ combinator: 'and',
766
+ not: false,
767
+ },
768
+ pagination: { skip: 100, take: 200 },
769
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
770
+ columnConfigMap: mockColumnConfigMap,
771
+ queryResolver: mockQueryResolver,
772
+ runtimeVariables: mockRuntimeVariables,
773
+ });
774
+ const sql = queryBuilder.buildCountQuery();
775
+ expect(sql?.finalQuery).toBe(
776
+ `WITH cte_main AS (SELECT [Status], [Amount], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active') SELECT COUNT(*) AS row_count FROM cte_main WHERE [Result] > 100`,
777
+ );
778
+ });
779
+
780
+ test('should create distinct count query with formula column having filter', () => {
781
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
782
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
783
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
784
+ Result: {
785
+ columnType: MDM_COLUMN_TYPE.FORMULA,
786
+ columnName: 'Result',
787
+ columnMeta: {
788
+ sqlQueryProps: {
789
+ resolvedExpression: {
790
+ type: ResolvedExpressionType.COLUMN,
791
+ value: ['[Amount] + 100'],
792
+ },
793
+ },
794
+ deps: ['Amount'],
795
+ },
796
+ },
797
+ };
798
+ const queryBuilder = new QueryBuilder({
799
+ distinct: true,
800
+ selectedColumns: ['Result'],
801
+ databaseDetails: {
802
+ dataTableName: 'TestTable',
803
+
804
+ schema: 'dbo',
805
+ database: 'mdm',
806
+ },
807
+ primaryKeyColumns: ['PK'],
808
+ filterConfig: {
809
+ rules: [
810
+ { column: 'Result', operator: '>', value: 100 },
811
+ { column: 'Status', operator: '=', value: 'Active' },
812
+ ],
813
+ combinator: 'and',
814
+ not: false,
815
+ },
816
+ pagination: { skip: 100, take: 200 },
817
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
818
+ columnConfigMap: mockColumnConfigMap,
819
+ queryResolver: mockQueryResolver,
820
+ runtimeVariables: mockRuntimeVariables,
821
+ });
822
+ const sql = queryBuilder.buildCountQuery();
823
+ expect(sql?.finalQuery).toBe(
824
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_distinct_cte_main AS (SELECT DISTINCT [Result] FROM cte_main WHERE [Result] > 100) SELECT COUNT(*) AS row_count FROM cte_distinct_cte_main`,
825
+ );
826
+ });
827
+
828
+ test('should create distinct count query with formula column having no filters on formula column', () => {
829
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
830
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
831
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
832
+ Result: {
833
+ columnType: MDM_COLUMN_TYPE.FORMULA,
834
+ columnName: 'Result',
835
+ columnMeta: {
836
+ sqlQueryProps: {
837
+ resolvedExpression: {
838
+ type: ResolvedExpressionType.COLUMN,
839
+ value: ['[Amount] + 100'],
840
+ },
841
+ },
842
+ deps: ['Amount'],
843
+ },
844
+ },
845
+ };
846
+ const queryBuilder = new QueryBuilder({
847
+ distinct: true,
848
+ selectedColumns: ['Result'],
849
+ databaseDetails: {
850
+ dataTableName: 'TestTable',
851
+
852
+ schema: 'dbo',
853
+ database: 'mdm',
854
+ },
855
+ primaryKeyColumns: ['PK'],
856
+ filterConfig: {
857
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
858
+ combinator: 'and',
859
+ not: false,
860
+ },
861
+ pagination: { skip: 100, take: 200 },
862
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
863
+ columnConfigMap: mockColumnConfigMap,
864
+ queryResolver: mockQueryResolver,
865
+ runtimeVariables: mockRuntimeVariables,
866
+ });
867
+ const sql = queryBuilder.buildCountQuery();
868
+ expect(sql?.finalQuery).toBe(
869
+ `WITH cte_main AS (SELECT DISTINCT [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active') SELECT COUNT(*) AS row_count FROM cte_main`,
870
+ );
871
+ });
872
+
873
+ test('should create count query for multiple columns with formula column dependency on just native column', () => {
874
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
875
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
876
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
877
+ Result: {
878
+ columnType: MDM_COLUMN_TYPE.FORMULA,
879
+ columnName: 'Result',
880
+ columnMeta: {
881
+ sqlQueryProps: {
882
+ resolvedExpression: {
883
+ type: ResolvedExpressionType.COLUMN,
884
+ value: ['[Amount] + 100'],
885
+ },
886
+ },
887
+ deps: ['Amount'],
888
+ },
889
+ },
890
+ };
891
+ const queryBuilder = new QueryBuilder({
892
+ selectedColumns: ['Status', 'Result'],
893
+ databaseDetails: {
894
+ dataTableName: 'TestTable',
895
+
896
+ schema: 'dbo',
897
+ database: 'mdm',
898
+ },
899
+ primaryKeyColumns: ['PK'],
900
+ filterConfig: {
901
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
902
+ combinator: 'and',
903
+ not: false,
904
+ },
905
+ pagination: { skip: 100, take: 200 },
906
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
907
+ columnConfigMap: mockColumnConfigMap,
908
+ queryResolver: mockQueryResolver,
909
+ runtimeVariables: mockRuntimeVariables,
910
+ });
911
+ const sql = queryBuilder.buildCountQuery();
912
+ expect(sql?.finalQuery).toBe(
913
+ `SELECT COUNT(*) AS row_count FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'`,
914
+ );
915
+ });
916
+
917
+ test('should create count query for multiple columns with formula column dependency on another formula column', () => {
918
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
919
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
920
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
921
+ Result: {
922
+ columnType: MDM_COLUMN_TYPE.FORMULA,
923
+ columnName: 'Result',
924
+ columnMeta: {
925
+ sqlQueryProps: {
926
+ resolvedExpression: {
927
+ type: ResolvedExpressionType.COLUMN,
928
+ value: ['[Amount] + 100'],
929
+ },
930
+ },
931
+ deps: ['Amount'],
932
+ },
933
+ },
934
+ Result_2: {
935
+ columnType: MDM_COLUMN_TYPE.FORMULA,
936
+ columnName: 'Result_2',
937
+ columnMeta: {
938
+ sqlQueryProps: {
939
+ resolvedExpression: {
940
+ type: ResolvedExpressionType.COLUMN,
941
+ value: ['[Result] * 2'],
942
+ },
943
+ },
944
+ deps: ['Result'],
945
+ },
946
+ },
947
+ };
948
+ const queryBuilder = new QueryBuilder({
949
+ selectedColumns: ['Result', 'Result_2'],
950
+ databaseDetails: {
951
+ dataTableName: 'TestTable',
952
+
953
+ schema: 'dbo',
954
+ database: 'mdm',
955
+ },
956
+ primaryKeyColumns: ['PK'],
957
+ filterConfig: {
958
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
959
+ combinator: 'and',
960
+ not: false,
961
+ },
962
+ pagination: { skip: 100, take: 200 },
963
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
964
+ columnConfigMap: mockColumnConfigMap,
965
+ queryResolver: mockQueryResolver,
966
+ runtimeVariables: mockRuntimeVariables,
967
+ });
968
+ const sql = queryBuilder.buildCountQuery();
969
+ expect(sql?.finalQuery).toBe(
970
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_staging_cte_main AS (SELECT *, [Result] * 2 AS [Result_2] FROM cte_main) SELECT COUNT(*) AS row_count FROM cte_staging_cte_main`,
971
+ );
972
+ });
973
+
974
+ test('should create distinct count query for multiple columns with formula column dependency on another formula column', () => {
975
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
976
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
977
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
978
+ Result: {
979
+ columnType: MDM_COLUMN_TYPE.FORMULA,
980
+ columnName: 'Result',
981
+ columnMeta: {
982
+ sqlQueryProps: {
983
+ resolvedExpression: {
984
+ type: ResolvedExpressionType.COLUMN,
985
+ value: ['[Amount] + 100'],
986
+ },
987
+ },
988
+ deps: ['Amount'],
989
+ },
990
+ },
991
+ Result_2: {
992
+ columnType: MDM_COLUMN_TYPE.FORMULA,
993
+ columnName: 'Result_2',
994
+ columnMeta: {
995
+ sqlQueryProps: {
996
+ resolvedExpression: {
997
+ type: ResolvedExpressionType.COLUMN,
998
+ value: ['[Result] * 2'],
999
+ },
1000
+ },
1001
+ deps: ['Result'],
1002
+ },
1003
+ },
1004
+ };
1005
+ const queryBuilder = new QueryBuilder({
1006
+ distinct: true,
1007
+ selectedColumns: ['Result_2'],
1008
+ databaseDetails: {
1009
+ dataTableName: 'TestTable',
1010
+
1011
+ schema: 'dbo',
1012
+ database: 'mdm',
1013
+ },
1014
+ primaryKeyColumns: ['PK'],
1015
+ filterConfig: {
1016
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
1017
+ combinator: 'and',
1018
+ not: false,
1019
+ },
1020
+ pagination: { skip: 100, take: 200 },
1021
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
1022
+ columnConfigMap: mockColumnConfigMap,
1023
+ queryResolver: mockQueryResolver,
1024
+ runtimeVariables: mockRuntimeVariables,
1025
+ });
1026
+ const sql = queryBuilder.buildCountQuery();
1027
+ expect(sql?.finalQuery).toBe(
1028
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_staging_cte_main AS (SELECT *, [Result] * 2 AS [Result_2] FROM cte_main), cte_distinct_cte_staging_cte_main AS (SELECT DISTINCT [Result_2] FROM cte_staging_cte_main) SELECT COUNT(*) AS row_count FROM cte_distinct_cte_staging_cte_main`,
1029
+ );
1030
+ });
1031
+
1032
+ test('should add search expression to the query with only native columns', () => {
1033
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1034
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1035
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1036
+ };
1037
+ const queryBuilder = new QueryBuilder({
1038
+ selectedColumns: ['Amount', 'Status'],
1039
+ databaseDetails: {
1040
+ dataTableName: 'TestTable',
1041
+
1042
+ schema: 'dbo',
1043
+ database: 'mdm',
1044
+ },
1045
+ primaryKeyColumns: ['PK'],
1046
+ filterConfig: {
1047
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
1048
+ combinator: 'and',
1049
+ not: false,
1050
+ },
1051
+ pagination: { skip: 100, take: 200 },
1052
+ sortConfig: [{ column: 'Status', order: 'ASC' }],
1053
+ searchExpression: `WHERE (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%')`,
1054
+ columnConfigMap: mockColumnConfigMap,
1055
+ queryResolver: mockQueryResolver,
1056
+ runtimeVariables: mockRuntimeVariables,
1057
+ });
1058
+ const sql = queryBuilder.build();
1059
+ expect(sql?.finalQuery).toBe(
1060
+ `SELECT [Amount], [Status] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active' AND (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%') ORDER BY [Status] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
1061
+ );
1062
+ });
1063
+
1064
+ test('should add search expression to the query with formula columns having resolved expression as column expression', () => {
1065
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1066
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1067
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1068
+ Result: {
1069
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1070
+ columnName: 'Result',
1071
+ columnMeta: {
1072
+ sqlQueryProps: {
1073
+ resolvedExpression: {
1074
+ type: ResolvedExpressionType.COLUMN,
1075
+ value: ['[Amount] + 100'],
1076
+ },
1077
+ },
1078
+ deps: ['Amount'],
1079
+ },
1080
+ },
1081
+ };
1082
+ const queryBuilder = new QueryBuilder({
1083
+ selectedColumns: ['Amount', 'Status', 'Result'],
1084
+ databaseDetails: {
1085
+ dataTableName: 'TestTable',
1086
+
1087
+ schema: 'dbo',
1088
+ database: 'mdm',
1089
+ },
1090
+ primaryKeyColumns: ['PK'],
1091
+ filterConfig: {
1092
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
1093
+ combinator: 'and',
1094
+ not: false,
1095
+ },
1096
+ pagination: { skip: 100, take: 200 },
1097
+ sortConfig: [{ column: 'Status', order: 'ASC' }],
1098
+ searchExpression: `WHERE (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Result] AS VARCHAR)) LIKE '%2%')`,
1099
+ columnConfigMap: mockColumnConfigMap,
1100
+ queryResolver: mockQueryResolver,
1101
+ runtimeVariables: mockRuntimeVariables,
1102
+ });
1103
+ const sql = queryBuilder.build();
1104
+ expect(sql?.finalQuery).toBe(
1105
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active') SELECT * FROM cte_main WHERE (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Result] AS VARCHAR)) LIKE '%2%') ORDER BY [Status] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
1106
+ );
1107
+ });
1108
+
1109
+ test('should add search expression to the query with formula columns dependent on another formula column', () => {
1110
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1111
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1112
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1113
+ Result: {
1114
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1115
+ columnName: 'Result',
1116
+ columnMeta: {
1117
+ sqlQueryProps: {
1118
+ resolvedExpression: {
1119
+ type: ResolvedExpressionType.COLUMN,
1120
+ value: ['[Amount] + 100'],
1121
+ },
1122
+ },
1123
+ deps: ['Amount'],
1124
+ },
1125
+ },
1126
+ Total: {
1127
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1128
+ columnName: 'Total',
1129
+ columnMeta: {
1130
+ sqlQueryProps: {
1131
+ resolvedExpression: {
1132
+ type: ResolvedExpressionType.COLUMN,
1133
+ value: ['[Result] + 100'],
1134
+ },
1135
+ },
1136
+ deps: ['Result'],
1137
+ },
1138
+ },
1139
+ };
1140
+ const queryBuilder = new QueryBuilder({
1141
+ selectedColumns: ['Amount', 'Status', 'Result', 'Total'],
1142
+ databaseDetails: {
1143
+ dataTableName: 'TestTable',
1144
+
1145
+ schema: 'dbo',
1146
+ database: 'mdm',
1147
+ },
1148
+ primaryKeyColumns: ['PK'],
1149
+ filterConfig: {
1150
+ rules: [
1151
+ { column: 'Status', operator: '=', value: 'Active' },
1152
+ { column: 'Result', operator: '=', value: 200 },
1153
+ ],
1154
+ combinator: 'and',
1155
+ not: false,
1156
+ },
1157
+ pagination: { skip: 100, take: 200 },
1158
+ sortConfig: [{ column: 'Status', order: 'ASC' }],
1159
+ searchExpression: `WHERE (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Result] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Total] AS VARCHAR)) LIKE '%2%')`,
1160
+ columnConfigMap: mockColumnConfigMap,
1161
+ queryResolver: mockQueryResolver,
1162
+ runtimeVariables: mockRuntimeVariables,
1163
+ });
1164
+ const sql = queryBuilder.build();
1165
+ expect(sql?.finalQuery).toBe(
1166
+ `WITH cte_main AS (SELECT [Amount], [Status], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_staging_cte_main AS (SELECT *, [Result] + 100 AS [Total] FROM cte_main WHERE [Result] = 200) SELECT * FROM cte_staging_cte_main WHERE (LOWER(CAST([Amount] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Status] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Result] AS VARCHAR)) LIKE '%2%' OR LOWER(CAST([Total] AS VARCHAR)) LIKE '%2%') ORDER BY [Status] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
1167
+ );
1168
+ });
1169
+
1170
+ test('should resolve runtime variables', () => {
1171
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1172
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1173
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1174
+ LoggedInInfo: {
1175
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1176
+ columnName: 'LoggedInInfo',
1177
+ columnMeta: {
1178
+ sqlQueryProps: {
1179
+ resolvedExpression: {
1180
+ type: ResolvedExpressionType.COLUMN,
1181
+ value: [
1182
+ `CONCAT('Name: ', '${RUNTIME_LOGGEDIN_NAME}', ', Email: ', '${RUNTIME_LOGGEDIN_EMAIL}')`,
1183
+ ],
1184
+ },
1185
+ },
1186
+ },
1187
+ },
1188
+ };
1189
+ const queryBuilder = new QueryBuilder({
1190
+ selectedColumns: ['Amount', 'Status', 'LoggedInInfo'],
1191
+ databaseDetails: {
1192
+ dataTableName: 'TestTable',
1193
+
1194
+ schema: 'dbo',
1195
+ database: 'mdm',
1196
+ },
1197
+ primaryKeyColumns: ['PK'],
1198
+ filterConfig: {
1199
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
1200
+ combinator: 'and',
1201
+ not: false,
1202
+ },
1203
+ pagination: { skip: 100, take: 200 },
1204
+ sortConfig: [{ column: 'Status', order: 'ASC' }],
1205
+ columnConfigMap: mockColumnConfigMap,
1206
+ queryResolver: mockQueryResolver,
1207
+ runtimeVariables: mockRuntimeVariables,
1208
+ });
1209
+ const sql = queryBuilder.build();
1210
+ expect(sql?.finalQuery).toBe(
1211
+ `SELECT [Amount], [Status], CONCAT('Name: ', 'Test User', ', Email: ', 'test@mdm.com') AS [LoggedInInfo] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active' ORDER BY [Status] ASC OFFSET 100 ROWS FETCH NEXT 200 ROWS ONLY`,
1212
+ );
1213
+ });
1214
+
1215
+ test('should create distinct count query with formula column dependency on just native column and more than one selected column', () => {
1216
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1217
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1218
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1219
+ Result: {
1220
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1221
+ columnName: 'Result',
1222
+ columnMeta: {
1223
+ sqlQueryProps: {
1224
+ resolvedExpression: {
1225
+ type: ResolvedExpressionType.COLUMN,
1226
+ value: ['[Amount] + 100'],
1227
+ },
1228
+ },
1229
+ deps: ['Amount'],
1230
+ },
1231
+ },
1232
+ };
1233
+ const queryBuilder = new QueryBuilder({
1234
+ distinct: true,
1235
+ selectedColumns: ['Status', 'Result'],
1236
+ databaseDetails: {
1237
+ dataTableName: 'TestTable',
1238
+
1239
+ schema: 'dbo',
1240
+ database: 'mdm',
1241
+ },
1242
+ primaryKeyColumns: ['PK'],
1243
+ filterConfig: {
1244
+ rules: [
1245
+ { column: 'Result', operator: '>', value: 100 },
1246
+ { column: 'Status', operator: '=', value: 'Active' },
1247
+ ],
1248
+ combinator: 'and',
1249
+ not: false,
1250
+ },
1251
+ pagination: { skip: 100, take: 200 },
1252
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
1253
+ columnConfigMap: mockColumnConfigMap,
1254
+ queryResolver: mockQueryResolver,
1255
+ runtimeVariables: mockRuntimeVariables,
1256
+ });
1257
+ const sql = queryBuilder.buildCountQuery();
1258
+ expect(sql?.finalQuery).toBe(
1259
+ `WITH cte_main AS (SELECT [Status], [Amount], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_distinct_cte_main AS (SELECT DISTINCT [Status], [Result] FROM cte_main WHERE [Result] > 100) SELECT COUNT(*) AS row_count FROM cte_distinct_cte_main`,
1260
+ );
1261
+ });
1262
+
1263
+ test('should create distinct count query for multiple columns with formula column dependency on another formula column and more than one selected column', () => {
1264
+ const mockColumnConfigMap: Record<string, IMDMColumnConfigWithParsedMeta> = {
1265
+ Amount: { columnType: MDM_COLUMN_TYPE.NUMBER, columnName: 'Amount', columnMeta: {} },
1266
+ Status: { columnType: MDM_COLUMN_TYPE.TEXT, columnName: 'Status', columnMeta: {} },
1267
+ Result: {
1268
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1269
+ columnName: 'Result',
1270
+ columnMeta: {
1271
+ sqlQueryProps: {
1272
+ resolvedExpression: {
1273
+ type: ResolvedExpressionType.COLUMN,
1274
+ value: ['[Amount] + 100'],
1275
+ },
1276
+ },
1277
+ deps: ['Amount'],
1278
+ },
1279
+ },
1280
+ Result_2: {
1281
+ columnType: MDM_COLUMN_TYPE.FORMULA,
1282
+ columnName: 'Result_2',
1283
+ columnMeta: {
1284
+ sqlQueryProps: {
1285
+ resolvedExpression: {
1286
+ type: ResolvedExpressionType.COLUMN,
1287
+ value: ['[Result] * 2'],
1288
+ },
1289
+ },
1290
+ deps: ['Result'],
1291
+ },
1292
+ },
1293
+ };
1294
+ const queryBuilder = new QueryBuilder({
1295
+ distinct: true,
1296
+ selectedColumns: ['Status', 'Result_2'],
1297
+ databaseDetails: {
1298
+ dataTableName: 'TestTable',
1299
+
1300
+ schema: 'dbo',
1301
+ database: 'mdm',
1302
+ },
1303
+ primaryKeyColumns: ['PK'],
1304
+ filterConfig: {
1305
+ rules: [{ column: 'Status', operator: '=', value: 'Active' }],
1306
+ combinator: 'and',
1307
+ not: false,
1308
+ },
1309
+ pagination: { skip: 100, take: 200 },
1310
+ sortConfig: [{ column: 'Result', order: 'ASC' }],
1311
+ columnConfigMap: mockColumnConfigMap,
1312
+ queryResolver: mockQueryResolver,
1313
+ runtimeVariables: mockRuntimeVariables,
1314
+ });
1315
+ const sql = queryBuilder.buildCountQuery();
1316
+ expect(sql?.finalQuery).toBe(
1317
+ `WITH cte_main AS (SELECT [Status], [Amount], [Amount] + 100 AS [Result] FROM [mdm].[dbo].[TestTable] WHERE LOWER([Status]) = 'active'), cte_staging_cte_main AS (SELECT *, [Result] * 2 AS [Result_2] FROM cte_main), cte_distinct_cte_staging_cte_main AS (SELECT DISTINCT [Status], [Result_2] FROM cte_staging_cte_main) SELECT COUNT(*) AS row_count FROM cte_distinct_cte_staging_cte_main`,
1318
+ );
1319
+ });
1320
+ });