@quillsql/react 2.10.39 → 2.11.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 (123) hide show
  1. package/dist/cjs/Chart.d.ts +4 -0
  2. package/dist/cjs/Chart.d.ts.map +1 -1
  3. package/dist/cjs/Chart.js +5 -5
  4. package/dist/cjs/ChartBuilder.js +2 -2
  5. package/dist/cjs/Context.d.ts +1 -1
  6. package/dist/cjs/Context.d.ts.map +1 -1
  7. package/dist/cjs/Context.js +3 -1
  8. package/dist/cjs/Dashboard.d.ts +3 -1
  9. package/dist/cjs/Dashboard.d.ts.map +1 -1
  10. package/dist/cjs/Dashboard.js +4 -4
  11. package/dist/cjs/QuillProvider.d.ts +3 -1
  12. package/dist/cjs/QuillProvider.d.ts.map +1 -1
  13. package/dist/cjs/QuillProvider.js +2 -2
  14. package/dist/cjs/ReportBuilder.d.ts +40 -40
  15. package/dist/cjs/ReportBuilder.d.ts.map +1 -1
  16. package/dist/cjs/ReportBuilder.js +2036 -909
  17. package/dist/cjs/components/Chart/LineChart.d.ts +5 -1
  18. package/dist/cjs/components/Chart/LineChart.d.ts.map +1 -1
  19. package/dist/cjs/components/Chart/LineChart.js +18 -6
  20. package/dist/cjs/components/QuillTable.d.ts +1 -1
  21. package/dist/cjs/components/QuillTable.d.ts.map +1 -1
  22. package/dist/cjs/components/QuillTable.js +157 -157
  23. package/dist/cjs/components/ReportBuilder/AddColumnPopover.d.ts +2 -0
  24. package/dist/cjs/components/ReportBuilder/AddColumnPopover.d.ts.map +1 -0
  25. package/dist/cjs/components/ReportBuilder/AddColumnPopover.js +128 -0
  26. package/dist/cjs/components/ReportBuilder/ast.d.ts +512 -0
  27. package/dist/cjs/components/ReportBuilder/ast.d.ts.map +1 -0
  28. package/dist/cjs/components/ReportBuilder/ast.js +210 -0
  29. package/dist/cjs/components/ReportBuilder/bigDateMap.d.ts +7 -0
  30. package/dist/cjs/components/ReportBuilder/bigDateMap.d.ts.map +1 -0
  31. package/dist/cjs/components/ReportBuilder/bigDateMap.js +689 -0
  32. package/dist/cjs/components/ReportBuilder/constants.d.ts +89 -0
  33. package/dist/cjs/components/ReportBuilder/constants.d.ts.map +1 -0
  34. package/dist/cjs/components/ReportBuilder/constants.js +130 -0
  35. package/dist/cjs/components/ReportBuilder/convert.d.ts +41 -0
  36. package/dist/cjs/components/ReportBuilder/convert.d.ts.map +1 -0
  37. package/dist/cjs/components/ReportBuilder/convert.js +730 -0
  38. package/dist/cjs/components/ReportBuilder/operators.d.ts +445 -0
  39. package/dist/cjs/components/ReportBuilder/operators.d.ts.map +1 -0
  40. package/dist/cjs/components/ReportBuilder/operators.js +552 -0
  41. package/dist/cjs/components/ReportBuilder/pivot.d.ts +10 -0
  42. package/dist/cjs/components/ReportBuilder/pivot.d.ts.map +1 -0
  43. package/dist/cjs/components/ReportBuilder/pivot.js +2 -0
  44. package/dist/cjs/components/ReportBuilder/postgres.d.ts +150 -0
  45. package/dist/cjs/components/ReportBuilder/postgres.d.ts.map +1 -0
  46. package/dist/cjs/components/ReportBuilder/postgres.js +365 -0
  47. package/dist/cjs/components/ReportBuilder/schema.d.ts +23 -0
  48. package/dist/cjs/components/ReportBuilder/schema.d.ts.map +1 -0
  49. package/dist/cjs/components/ReportBuilder/schema.js +2 -0
  50. package/dist/cjs/components/ReportBuilder/ui.d.ts +34 -0
  51. package/dist/cjs/components/ReportBuilder/ui.d.ts.map +1 -0
  52. package/dist/cjs/components/ReportBuilder/ui.js +389 -0
  53. package/dist/cjs/components/ReportBuilder/util.d.ts +76 -0
  54. package/dist/cjs/components/ReportBuilder/util.d.ts.map +1 -0
  55. package/dist/cjs/components/ReportBuilder/util.js +648 -0
  56. package/dist/cjs/components/UiComponents.d.ts +15 -2
  57. package/dist/cjs/components/UiComponents.d.ts.map +1 -1
  58. package/dist/cjs/components/UiComponents.js +50 -3
  59. package/dist/cjs/utils/crypto.d.ts +1 -1
  60. package/dist/cjs/utils/crypto.d.ts.map +1 -1
  61. package/dist/cjs/utils/crypto.js +9 -5
  62. package/dist/esm/Chart.d.ts +4 -0
  63. package/dist/esm/Chart.d.ts.map +1 -1
  64. package/dist/esm/Chart.js +5 -5
  65. package/dist/esm/ChartBuilder.js +1 -1
  66. package/dist/esm/Context.d.ts +1 -1
  67. package/dist/esm/Context.d.ts.map +1 -1
  68. package/dist/esm/Context.js +3 -1
  69. package/dist/esm/Dashboard.d.ts +3 -1
  70. package/dist/esm/Dashboard.d.ts.map +1 -1
  71. package/dist/esm/Dashboard.js +4 -4
  72. package/dist/esm/QuillProvider.d.ts +3 -1
  73. package/dist/esm/QuillProvider.d.ts.map +1 -1
  74. package/dist/esm/QuillProvider.js +2 -2
  75. package/dist/esm/ReportBuilder.d.ts +40 -40
  76. package/dist/esm/ReportBuilder.d.ts.map +1 -1
  77. package/dist/esm/ReportBuilder.js +2038 -909
  78. package/dist/esm/components/Chart/LineChart.d.ts +5 -1
  79. package/dist/esm/components/Chart/LineChart.d.ts.map +1 -1
  80. package/dist/esm/components/Chart/LineChart.js +18 -6
  81. package/dist/esm/components/QuillTable.d.ts +1 -1
  82. package/dist/esm/components/QuillTable.d.ts.map +1 -1
  83. package/dist/esm/components/QuillTable.js +157 -157
  84. package/dist/esm/components/ReportBuilder/AddColumnPopover.d.ts +2 -0
  85. package/dist/esm/components/ReportBuilder/AddColumnPopover.d.ts.map +1 -0
  86. package/dist/esm/components/ReportBuilder/AddColumnPopover.js +125 -0
  87. package/dist/esm/components/ReportBuilder/ast.d.ts +512 -0
  88. package/dist/esm/components/ReportBuilder/ast.d.ts.map +1 -0
  89. package/dist/esm/components/ReportBuilder/ast.js +186 -0
  90. package/dist/esm/components/ReportBuilder/bigDateMap.d.ts +7 -0
  91. package/dist/esm/components/ReportBuilder/bigDateMap.d.ts.map +1 -0
  92. package/dist/esm/components/ReportBuilder/bigDateMap.js +686 -0
  93. package/dist/esm/components/ReportBuilder/constants.d.ts +89 -0
  94. package/dist/esm/components/ReportBuilder/constants.d.ts.map +1 -0
  95. package/dist/esm/components/ReportBuilder/constants.js +127 -0
  96. package/dist/esm/components/ReportBuilder/convert.d.ts +41 -0
  97. package/dist/esm/components/ReportBuilder/convert.d.ts.map +1 -0
  98. package/dist/esm/components/ReportBuilder/convert.js +719 -0
  99. package/dist/esm/components/ReportBuilder/operators.d.ts +445 -0
  100. package/dist/esm/components/ReportBuilder/operators.d.ts.map +1 -0
  101. package/dist/esm/components/ReportBuilder/operators.js +548 -0
  102. package/dist/esm/components/ReportBuilder/pivot.d.ts +10 -0
  103. package/dist/esm/components/ReportBuilder/pivot.d.ts.map +1 -0
  104. package/dist/esm/components/ReportBuilder/pivot.js +1 -0
  105. package/dist/esm/components/ReportBuilder/postgres.d.ts +150 -0
  106. package/dist/esm/components/ReportBuilder/postgres.d.ts.map +1 -0
  107. package/dist/esm/components/ReportBuilder/postgres.js +355 -0
  108. package/dist/esm/components/ReportBuilder/schema.d.ts +23 -0
  109. package/dist/esm/components/ReportBuilder/schema.d.ts.map +1 -0
  110. package/dist/esm/components/ReportBuilder/schema.js +1 -0
  111. package/dist/esm/components/ReportBuilder/ui.d.ts +34 -0
  112. package/dist/esm/components/ReportBuilder/ui.d.ts.map +1 -0
  113. package/dist/esm/components/ReportBuilder/ui.js +366 -0
  114. package/dist/esm/components/ReportBuilder/util.d.ts +76 -0
  115. package/dist/esm/components/ReportBuilder/util.d.ts.map +1 -0
  116. package/dist/esm/components/ReportBuilder/util.js +616 -0
  117. package/dist/esm/components/UiComponents.d.ts +15 -2
  118. package/dist/esm/components/UiComponents.d.ts.map +1 -1
  119. package/dist/esm/components/UiComponents.js +47 -2
  120. package/dist/esm/utils/crypto.d.ts +1 -1
  121. package/dist/esm/utils/crypto.d.ts.map +1 -1
  122. package/dist/esm/utils/crypto.js +9 -5
  123. package/package.json +1 -1
@@ -0,0 +1,730 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertRemoveSimpleParentheses = exports.convertStringComparison = exports.applyPivot = exports.convertGroupBy = exports.convertWildcardColumns = exports.convertBigQuery = exports.recursiveSearchAndReplace = exports.convertDateComparison = void 0;
4
+ const ast_1 = require("./ast");
5
+ const util_1 = require("./util");
6
+ // Helper functions that preprocess the AST.
7
+ // Find and replace certain pre-set date comparison queries with their
8
+ // more stable counterparts.
9
+ // TODO: implement me
10
+ // eslint-disable-next-line no-unused-vars
11
+ function convertDateComparison(node) {
12
+ function searchAndReplace(obj) {
13
+ if (Array.isArray(obj)) {
14
+ // If the current node is an array, process each element of the array
15
+ obj.forEach((element) => {
16
+ searchAndReplace(element);
17
+ });
18
+ }
19
+ else if (typeof obj === 'object' && obj !== null) {
20
+ // "Created in the past 90 days" should be written in SQL as:
21
+ // "created_at" >= date_trunc('day', CURRENT_DATE) - INTERVAL '90 days';
22
+ // "created_at" >= date_trunc('month', CURRENT_DATE) - INTERVAL '6 months';
23
+ // TODO: add more matchers here
24
+ }
25
+ else {
26
+ // If the current object does not match the pattern, continue searching its properties
27
+ Object.values(obj).forEach((value) => {
28
+ searchAndReplace(value);
29
+ });
30
+ }
31
+ }
32
+ // Start the recursive search and replace from the root node
33
+ searchAndReplace(node);
34
+ return node;
35
+ }
36
+ exports.convertDateComparison = convertDateComparison;
37
+ // THIS IS ONLY USEFUL TO CONVERT FUNCTIONS INTO OTHER FUNCTIONS FOR BIGQUERY FOR FILTERS
38
+ function recursiveSearchAndReplace(node, search, replace) {
39
+ if (typeof node !== 'object') {
40
+ return;
41
+ }
42
+ if (node) {
43
+ Object.keys(node).forEach((key) => {
44
+ recursiveSearchAndReplace(node[key], search, replace);
45
+ if (node[key] === search) {
46
+ node[key] = replace;
47
+ }
48
+ });
49
+ }
50
+ }
51
+ exports.recursiveSearchAndReplace = recursiveSearchAndReplace;
52
+ /**
53
+ * Takes a bigquery AST as input, cleans it, and returns a Select AST.
54
+ */
55
+ function convertBigQuery(ast) {
56
+ // For BigQuery, the info we want is nested inside the 'select' key
57
+ if ((0, ast_1.isBigQuery)(ast)) {
58
+ let newAst = ast.select;
59
+ newAst.from?.forEach((tbl) => {
60
+ tbl.table = tbl.table.replaceAll('`', '');
61
+ });
62
+ if (newAst.columns && Array.isArray(newAst.columns)) {
63
+ newAst.columns.forEach((col) => {
64
+ if (col.expr && !col.type) {
65
+ col.type = 'expr';
66
+ }
67
+ });
68
+ }
69
+ // Look through every node in the AST convert every instance of DATE_SUB to TIMESTAMP_SUB
70
+ recursiveSearchAndReplace(newAst.where, 'DATE_SUB', 'TIMESTAMP_SUB');
71
+ recursiveSearchAndReplace(newAst.where, 'CURRENT_DATE', 'CURRENT_TIMESTAMP');
72
+ recursiveSearchAndReplace(newAst.where, 'DATE_TRUNC', 'TIMESTAMP_TRUNC');
73
+ recursiveSearchAndReplace(newAst.where, 'DATE', 'TIMESTAMP');
74
+ // TODO: handle joins nicely
75
+ if (newAst.from?.length > 1) {
76
+ newAst.from = [newAst.from[0]];
77
+ }
78
+ // TODO: handle UNION ALL joins
79
+ if (newAst._next) {
80
+ delete newAst._next;
81
+ }
82
+ return newAst;
83
+ }
84
+ else {
85
+ return ast;
86
+ }
87
+ }
88
+ exports.convertBigQuery = convertBigQuery;
89
+ /**
90
+ * Replaces the wildcard column selector with their literal column names.
91
+ */
92
+ function convertWildcardColumns(ast, schemaTables) {
93
+ if (ast.columns !== '*' && ast.columns.length !== 0)
94
+ return ast;
95
+ if (!ast.from)
96
+ return ast;
97
+ // Map SELECT(*) to their actual columns
98
+ const tableNamesInQuery = ast.from.map((tbl) => tbl.table);
99
+ const allColumns = schemaTables
100
+ .filter((t) => tableNamesInQuery.includes(t.displayName))
101
+ .flatMap((table) => table.columns);
102
+ return {
103
+ ...ast,
104
+ columns: allColumns.map((column) => ({
105
+ type: 'expr',
106
+ expr: {
107
+ type: 'column_ref',
108
+ table: null,
109
+ column: column.name,
110
+ },
111
+ as: null,
112
+ })),
113
+ };
114
+ }
115
+ exports.convertWildcardColumns = convertWildcardColumns;
116
+ /**
117
+ * Processes the AST and builds up a new pivot, given the old AST, the old pivot
118
+ * and a list of tables in the schema.
119
+ */
120
+ function convertGroupBy(ast, prevPivot = null, schemaTables) {
121
+ if (!(0, ast_1.isSelect)(ast))
122
+ return { ast, pivot: prevPivot };
123
+ let newAst = {};
124
+ let pivot = {};
125
+ let newColumns = [];
126
+ let aliasMap = {};
127
+ const tableNamesInQuery = (ast.from ?? []).map((tbl) => {
128
+ // This assumes that all the entries in the from clause are BaseFrom.
129
+ // TODO: Handle Join, TableExpr, and Dual (maybe not the last one).
130
+ return tbl.table;
131
+ });
132
+ const columns = schemaTables
133
+ .filter((t) => tableNamesInQuery.includes(t.displayName))
134
+ .flatMap((table) => table.columns)
135
+ .map((column) => ({
136
+ field: column.displayName,
137
+ fieldType: column.fieldType,
138
+ }));
139
+ // If there is not GROUP BY clause in the AST, we still need to process the
140
+ // AST but it should pass-through in nature.
141
+ if (!ast.groupby) {
142
+ // If there was a pivot before, evaluate the pivot.
143
+ if (prevPivot) {
144
+ // Make sure that the AST includes all the columns on the prevPivot
145
+ const columnAlreadyExists = (col) => ast.columns.find((c) => [c.expr?.value, c.expr?.column, c.as].includes(col));
146
+ if (prevPivot.valueField) {
147
+ const existingCol = columnAlreadyExists(prevPivot.valueField);
148
+ newColumns.push(existingCol ?? (0, util_1.generateColumnExpr)(prevPivot.valueField));
149
+ }
150
+ if (prevPivot.rowField) {
151
+ const existingCol = columnAlreadyExists(prevPivot.rowField);
152
+ newColumns.push(existingCol ?? (0, util_1.generateColumnExpr)(prevPivot.rowField));
153
+ }
154
+ if (prevPivot.columnField) {
155
+ const existingCol = columnAlreadyExists(prevPivot.columnField);
156
+ newColumns.push(existingCol ?? (0, util_1.generateColumnExpr)(prevPivot.columnField));
157
+ }
158
+ ast.columns = newColumns;
159
+ return { pivot: prevPivot, ast };
160
+ }
161
+ for (let i = 0; i < ast.columns.length; i++) {
162
+ const column = ast.columns[i];
163
+ // Extract out the column name, note that there are several ways
164
+ // that a column node could be structured so we need to check.
165
+ let columnName;
166
+ let columnAs = null;
167
+ let columnTable = null;
168
+ if (column.type === 'expr') {
169
+ columnAs = column.as;
170
+ columnTable = column.expr.table;
171
+ if (column.expr.type === 'column_ref') {
172
+ columnName = column.expr.column;
173
+ }
174
+ else if (column.expr.type === 'string') {
175
+ columnName = column.expr.value;
176
+ }
177
+ else if (column.expr.type === 'double_quote_string') {
178
+ columnName = column.expr?.value;
179
+ }
180
+ else if (column.expr?.args?.expr) {
181
+ columnName = column.expr?.args?.expr?.value;
182
+ }
183
+ if (column.expr.type === 'aggr_func') {
184
+ pivot.aggregationType = column.expr.name.toLowerCase();
185
+ pivot.valueField = column.expr.args?.expr?.value;
186
+ if (column.expr.args.expr.type === 'column_ref') {
187
+ columnTable = column.expr.args.expr.table;
188
+ columnName = column.expr.args.expr.column;
189
+ }
190
+ else if (column.expr.args.expr.type === 'double_quote_string') {
191
+ columnName = column.expr.args.expr?.value;
192
+ }
193
+ else if (column.expr?.args?.expr) {
194
+ columnName = column.expr?.args?.expr?.value;
195
+ }
196
+ }
197
+ }
198
+ if (columnName === '*') {
199
+ // TODO: make valueField be a number
200
+ pivot.valueField = columns[0].field;
201
+ columnName = columns[0].field;
202
+ }
203
+ newColumns.push({
204
+ type: 'expr',
205
+ expr: {
206
+ type: 'column_ref',
207
+ table: columnTable,
208
+ column: columnName,
209
+ },
210
+ as: columnAs,
211
+ });
212
+ }
213
+ newAst = { ...ast };
214
+ newAst.columns = newColumns;
215
+ const newPivot = Object.keys(pivot).length === 0 ? null : pivot;
216
+ return { pivot: newPivot, ast: newAst };
217
+ }
218
+ // Iterate over columns to find aggr_func -> set pivot.aggregationType and pivot.valueField
219
+ for (let i = 0; i < ast.columns.length; i++) {
220
+ const column = ast.columns[i];
221
+ const field = columns.find((c) => c.field === column.expr?.args?.expr?.column);
222
+ if (column?.expr?.type === 'aggr_func') {
223
+ // CASE WHEN
224
+ if (column.expr?.args?.expr?.type.toLowerCase() === 'case' &&
225
+ ['double_quote_string', 'column_ref'].includes(column.expr?.args?.expr?.args[0]?.result?.type)) {
226
+ // The result of the CASE is the column to use
227
+ const columnNode = column.expr?.args?.expr?.args[0]?.result;
228
+ const columnName = columnNode?.value ?? columnNode?.column;
229
+ pivot.valueField = columnName;
230
+ pivot.aggregationType = column?.expr?.name?.toLowerCase();
231
+ const findByName = (x) => [x.expr?.value, x.expr?.column].includes(columnName);
232
+ if (!newColumns.find(findByName)) {
233
+ newColumns.push({
234
+ type: 'expr',
235
+ expr: {
236
+ type: 'column_ref',
237
+ table: null,
238
+ column: columnName,
239
+ },
240
+ as: null,
241
+ });
242
+ }
243
+ // We need to extract a column name from the condition expression of
244
+ // the CASE and we'll use that as the columnField in our pivot.
245
+ const condNode = column.expr?.args?.expr?.args[0]?.cond;
246
+ const condColumnName = condNode.left?.column ?? condNode.left?.value;
247
+ pivot.columnField = condColumnName;
248
+ const findByColName = (x) => [x.expr?.value, x.expr?.column].includes(condColumnName);
249
+ if (!newColumns.find(findByColName)) {
250
+ // Make sure the column we extracted is in our column list.
251
+ newColumns.push({
252
+ type: 'expr',
253
+ expr: {
254
+ type: 'column_ref',
255
+ table: null,
256
+ column: condColumnName,
257
+ },
258
+ as: null,
259
+ });
260
+ }
261
+ // REGULAR
262
+ }
263
+ else if (
264
+ // sum("amount")
265
+ column.expr?.args?.expr?.type === 'double_quote_string' ||
266
+ // sum(amount)
267
+ column.expr?.args?.expr?.type === 'column_ref' ||
268
+ // count(*)
269
+ column.expr?.args?.expr?.type === 'star') {
270
+ console.log(column);
271
+ const columnNode = column.expr?.args?.expr;
272
+ const columnName = columnNode?.value || columnNode?.column;
273
+ // if count(*), make the value field an actual column
274
+ if (columnName === '*') {
275
+ // TODO: make valueField be a number
276
+ pivot.valueField = columns[0].field;
277
+ pivot.aggregationType = column?.expr?.name.toLowerCase();
278
+ const findByName = (x) => [x.expr?.value, x.expr?.column].includes(columns[0].field);
279
+ if (!newColumns.find(findByName)) {
280
+ newColumns.push({
281
+ type: 'expr',
282
+ expr: {
283
+ type: 'column_ref',
284
+ table: null,
285
+ column: columns[0].field,
286
+ },
287
+ as: null,
288
+ });
289
+ }
290
+ }
291
+ else {
292
+ // ONLY SET VALUE FIELD IF THE FIELD TYPE IS A NUMBER
293
+ if ((0, ast_1.isNumericColumnType)(field?.fieldType)) {
294
+ pivot.valueField = columnName;
295
+ pivot.aggregationType = column?.expr?.name.toLowerCase();
296
+ }
297
+ const findByName = (x) => [x.expr?.value, x.expr?.column].includes(columnName);
298
+ if (!newColumns.find(findByName)) {
299
+ newColumns.push({
300
+ type: 'expr',
301
+ expr: {
302
+ type: 'column_ref',
303
+ table: null,
304
+ column: columnName,
305
+ },
306
+ as: null,
307
+ });
308
+ }
309
+ }
310
+ }
311
+ else {
312
+ // normal column?
313
+ const columnName = column.expr?.column || column.expr?.value;
314
+ const findByName = (x) => [x.expr?.value, x.expr?.column].includes(columnName);
315
+ if (!newColumns.find(findByName)) {
316
+ newColumns.push(column);
317
+ }
318
+ }
319
+ }
320
+ else if (column.expr?.type === 'function') {
321
+ // date_trunc("month", "created_at") as month
322
+ if (column.type === 'expr' &&
323
+ column.expr?.type.toLowerCase() === 'function' &&
324
+ column.expr?.name.toLowerCase() === 'date_trunc' &&
325
+ column.expr?.args?.type === 'expr_list' &&
326
+ column.as) {
327
+ const columnObj = column.expr?.args?.value[1];
328
+ const periodObj = column.expr?.args?.value[0];
329
+ aliasMap[column.as] = columnObj?.value ?? columnObj.column;
330
+ newColumns.push({
331
+ type: 'expr',
332
+ expr: {
333
+ type: 'function',
334
+ name: 'DATE_TRUNC',
335
+ args: {
336
+ type: 'expr_list',
337
+ value: [
338
+ { type: 'single_quote_string', value: periodObj?.value },
339
+ {
340
+ type: 'column_ref',
341
+ table: null,
342
+ column: columnObj?.value ?? columnObj.column,
343
+ },
344
+ ],
345
+ },
346
+ },
347
+ as: columnObj?.value ?? columnObj.column,
348
+ });
349
+ }
350
+ else if (column.type === 'expr' &&
351
+ column.expr?.type.toLowerCase() === 'function' &&
352
+ column.expr?.name.toLowerCase() === 'date' &&
353
+ column.expr?.args?.type === 'expr_list' &&
354
+ column.as) {
355
+ const columnObj = column.expr?.args?.value[0];
356
+ aliasMap[column.as] = columnObj?.value ?? columnObj.column;
357
+ newColumns.push({
358
+ type: 'expr',
359
+ expr: {
360
+ type: 'function',
361
+ name: 'DATE_TRUNC',
362
+ args: {
363
+ type: 'expr_list',
364
+ value: [
365
+ { type: 'single_quote_string', value: 'day' },
366
+ {
367
+ type: 'column_ref',
368
+ table: null,
369
+ column: columnObj?.value ?? columnObj.column,
370
+ },
371
+ ],
372
+ },
373
+ },
374
+ as: columnObj?.value ?? columnObj.column,
375
+ });
376
+ }
377
+ else {
378
+ newColumns.push(column);
379
+ }
380
+ }
381
+ else if (column.type === 'expr' &&
382
+ column.expr?.type.toLowerCase() === 'extract' &&
383
+ column.as) {
384
+ const cast_type = column.expr?.args.cast_type;
385
+ const source = column.expr?.args.source;
386
+ const field = column.expr?.args.field;
387
+ aliasMap[column.as] = source?.value ?? source.column;
388
+ newColumns.push({
389
+ type: 'expr',
390
+ expr: {
391
+ type: 'extract',
392
+ args: { field, cast_type, source },
393
+ },
394
+ as: source?.value ?? source.column,
395
+ });
396
+ }
397
+ else {
398
+ const columnNode = column.expr;
399
+ newColumns.push({
400
+ type: 'expr',
401
+ expr: {
402
+ type: 'column_ref',
403
+ table: columnNode.table,
404
+ column: columnNode.args?.length
405
+ ? columnNode.args?.value.find((elem) => ['double_quote_string', 'column_ref'].includes(elem.type))?.value
406
+ : columnNode.column ?? columnNode?.value,
407
+ },
408
+ as: null,
409
+ });
410
+ }
411
+ }
412
+ // Look for a date_trunc in the GROUP BY and set it to the rowField.
413
+ // If there are no date_trunc in the GROUP BY, we also need to check
414
+ // to see if there was a date_trunc column that was given an alias and
415
+ // that alias is used in the GROUP BY (which is also set to rowField).
416
+ const isDateTrunc = (item) => ['function', 'date_trunc'].includes(item.type);
417
+ const isExtract = (item) => 'extract' === item.type;
418
+ const isCol = (x) => ['double_quote_string', 'column_ref'].includes(x.type);
419
+ const isAlias = (x) => isCol(x) && aliasMap[(0, ast_1.extractColumnish)(x)];
420
+ const dateTruncGroup = ast.groupby.find(isDateTrunc);
421
+ const extractGroup = ast.groupby.find(isExtract);
422
+ if (dateTruncGroup) {
423
+ const column = dateTruncGroup.args?.value.find(isCol);
424
+ pivot.rowField = (0, ast_1.extractColumnish)(column);
425
+ }
426
+ else if (extractGroup) {
427
+ const column = extractGroup.args?.source;
428
+ pivot.rowField = (0, ast_1.extractColumnish)(column);
429
+ }
430
+ else if (aliasMap) {
431
+ const match = ast.groupby.find(isAlias);
432
+ const matchColumnish = (0, ast_1.extractColumnish)(match);
433
+ if (matchColumnish) {
434
+ const actualColumnName = aliasMap[matchColumnish];
435
+ pivot.rowField = actualColumnName;
436
+ }
437
+ }
438
+ // Otherwise, set the first groupby to rowField then set
439
+ // next field to columnField (if there is one)
440
+ for (let j = 0; j < ast.groupby.length; j++) {
441
+ const group = ast.groupby[j];
442
+ let groupValue = (0, ast_1.extractColumnish)(group);
443
+ // check if this value is an alias, if so use concrete value
444
+ if (groupValue && aliasMap[groupValue]) {
445
+ groupValue = aliasMap[groupValue];
446
+ }
447
+ // Try to set the rowField and columnField.
448
+ if (!pivot.rowField) {
449
+ pivot.rowField = groupValue;
450
+ }
451
+ else if (!pivot.columnField && groupValue !== pivot.rowField) {
452
+ pivot.columnField = groupValue;
453
+ }
454
+ }
455
+ newAst = { ...ast };
456
+ newAst.groupby = null;
457
+ newAst.columns = newColumns;
458
+ // Automatically order by the rowField if this is a 1D pivot (ie. no columnField)
459
+ // we also want to automatically order by rowField if the rowField is date-ish.
460
+ // TODO: dateish check breaks for plain date-type columns (ie. non date_trunc/extract)
461
+ const isPivot1D = pivot.rowField && !pivot.columnField;
462
+ const isRowFieldDateish = (0, util_1.isColumnDateish)(newAst.columns.find((c) => c.as === pivot?.rowField));
463
+ const isAutoOrderBy = isPivot1D || isRowFieldDateish;
464
+ newAst.orderby = isAutoOrderBy
465
+ ? [
466
+ {
467
+ expr: { type: 'column_ref', table: null, column: pivot.rowField },
468
+ type: newAst.orderby !== null
469
+ ? newAst.orderby[0]?.type ?? 'DESC'
470
+ : 'DESC',
471
+ },
472
+ ]
473
+ : null;
474
+ // If the AI didn't generate an aggregate, add one.
475
+ if (!pivot.valueField) {
476
+ pivot.valueField = columns[0].field;
477
+ pivot.aggregationType = 'sum';
478
+ }
479
+ return { pivot, ast: newAst };
480
+ }
481
+ exports.convertGroupBy = convertGroupBy;
482
+ /**
483
+ * Applies a pivot to an object with {rows, fields} keys, returning
484
+ * a new object with the aggragation applied to the rows and the
485
+ * corresponding fields updated to reflect the accurate types.
486
+ */
487
+ function applyPivot(data, pivot, schema) {
488
+ const newRows = [];
489
+ const { rows, fields } = data;
490
+ const { aggregationType, valueField, rowField, columnField } = pivot;
491
+ if (rows.length === 0)
492
+ return { rows, fields };
493
+ if ((rowField || columnField) && !valueField)
494
+ return { rows, fields };
495
+ if (columnField && !Object.keys(schema).includes(columnField))
496
+ return { rows, fields };
497
+ const AGGREGATORS = {
498
+ sum: (prev, next, _count = 0) => Number(prev) + Number(next),
499
+ max: (prev, next, _count = 0) => Math.max(Number(prev), Number(next)),
500
+ min: (prev, next, _count = 0) => Math.min(Number(prev), Number(next)),
501
+ count: (_prev, _next, count = 0) => Number(count) + 1,
502
+ avg: (prev, next, count = 0) => (Number(prev) * count + Number(next)) / (count + 1),
503
+ };
504
+ if (!columnField) {
505
+ // Simple 1D pivot
506
+ const keys = Object.keys(rows[0]);
507
+ for (const row of rows) {
508
+ let newRow = newRows.find((r) => r[rowField] === row[rowField]);
509
+ if (newRow) {
510
+ // Aggregate the existing row with this row
511
+ newRow[valueField] = AGGREGATORS[aggregationType](newRow[valueField], row[valueField], newRow.count ?? 0);
512
+ newRow.count = (newRow.count ?? 0) + 1;
513
+ }
514
+ else {
515
+ // Build up a new row and add it to our new rows array.
516
+ newRow = keys.reduce((obj, key) => {
517
+ obj[key] = row[key];
518
+ return obj;
519
+ }, {});
520
+ newRow.count = 1;
521
+ newRows.push(newRow);
522
+ }
523
+ }
524
+ // Remove the count property before returning
525
+ for (const row of newRows) {
526
+ delete row.count;
527
+ }
528
+ return { rows: newRows, fields };
529
+ }
530
+ else {
531
+ // 2D Pivot with rowField and columnField
532
+ const columnVariants = Object.keys(schema[columnField]);
533
+ const rowFieldColumnType = fields.find((f) => f.name === rowField);
534
+ const valueFieldColumnType = fields.find((f) => f.name === valueField);
535
+ const newFields = [
536
+ rowFieldColumnType,
537
+ ...columnVariants.map((name) => {
538
+ return { ...valueFieldColumnType, name };
539
+ }),
540
+ ];
541
+ for (const row of rows) {
542
+ let newRow = newRows.find((r) => r[rowField] === row[rowField]);
543
+ if (newRow) {
544
+ // Aggregate the existing row with this row
545
+ newRow[row[columnField]] = AGGREGATORS[aggregationType](newRow[row[columnField]], row[valueField], newRow[`${row[columnField]}_count`] ?? 0);
546
+ newRow[`${row[columnField]}_count`] += 1;
547
+ }
548
+ else {
549
+ // Build up a new row and add it to our new rows array.
550
+ const defaultAllColumnVariants = columnVariants.reduce((obj, key) => {
551
+ // set each variant to the default value (eg. 0)
552
+ obj[key] = aggregationType === 'min' ? Number.MAX_VALUE : 0;
553
+ obj[`${key}_count`] = 0;
554
+ return obj;
555
+ }, {});
556
+ newRow = {
557
+ [rowField]: row[rowField],
558
+ ...defaultAllColumnVariants,
559
+ };
560
+ newRow[row[columnField]] =
561
+ aggregationType === 'count' ? 1 : row[valueField];
562
+ newRow[`${row[columnField]}_count`] = 1;
563
+ newRows.push(newRow);
564
+ }
565
+ }
566
+ // Remove the _count properties before returning
567
+ for (const row of newRows) {
568
+ for (const key in row) {
569
+ if (key.endsWith('_count')) {
570
+ // Show a special output for no entries so that we
571
+ // don't show our dummy defaults like 0 or Number.MAX_VALUE
572
+ if (row[key] === 0) {
573
+ row[key.replace('_count', '')] = '-';
574
+ }
575
+ delete row[key];
576
+ }
577
+ }
578
+ }
579
+ return { rows: newRows, fields: newFields };
580
+ }
581
+ }
582
+ exports.applyPivot = applyPivot;
583
+ function convertStringComparison(node, databaseType) {
584
+ // Function to recursively search and replace the pattern in the object
585
+ function searchAndReplace(obj) {
586
+ if (Array.isArray(obj)) {
587
+ // If the current node is an array, process each element of the array
588
+ obj.forEach((element) => {
589
+ searchAndReplace(element);
590
+ });
591
+ }
592
+ else if (typeof obj === 'object' && obj !== null) {
593
+ // If the current node is an object, check for the pattern
594
+ if (obj.type === 'binary_expr' &&
595
+ (obj.operator === '=' ||
596
+ obj.operator === 'LIKE' ||
597
+ obj.operator === 'ILIKE') &&
598
+ obj.left &&
599
+ (obj.left.type === 'column_ref' ||
600
+ obj.left.type === 'double_quote_string') &&
601
+ obj.right &&
602
+ obj.right.type === 'single_quote_string') {
603
+ // Pattern found, modify the current object to use LOWER and LIKE
604
+ obj.operator = 'LIKE';
605
+ obj.left = {
606
+ type: 'function',
607
+ name: 'LOWER',
608
+ args: {
609
+ type: 'expr_list',
610
+ value: [
611
+ databaseType === 'BigQuery'
612
+ ? { type: 'column_ref', value: obj.left.column }
613
+ : { type: 'double_quote_string', value: obj.left.column },
614
+ ],
615
+ },
616
+ };
617
+ obj.right = {
618
+ type: 'function',
619
+ name: 'LOWER',
620
+ args: {
621
+ type: 'expr_list',
622
+ value: [
623
+ {
624
+ type: 'single_quote_string',
625
+ value: `%${obj.right.value.replaceAll('%', '')}%`,
626
+ },
627
+ ],
628
+ },
629
+ };
630
+ }
631
+ else if (obj.type === 'binary_expr' &&
632
+ (obj.operator === '!=' ||
633
+ obj.operator === 'NOT LIKE' ||
634
+ obj.operator === 'NOT ILIKE' ||
635
+ obj.operator === '<>') &&
636
+ obj.left &&
637
+ (obj.left.type === 'column_ref' ||
638
+ obj.left.type === 'double_quote_string') &&
639
+ obj.right &&
640
+ obj.right.type === 'single_quote_string') {
641
+ // Pattern found, modify the current object to use LOWER and LIKE
642
+ obj.operator = 'NOT LIKE';
643
+ obj.left = {
644
+ type: 'function',
645
+ name: 'LOWER',
646
+ args: {
647
+ type: 'expr_list',
648
+ value: [
649
+ databaseType === 'BigQuery'
650
+ ? { type: 'column_ref', value: obj.left.column }
651
+ : { type: 'double_quote_string', value: obj.left.column },
652
+ ],
653
+ },
654
+ };
655
+ obj.right = {
656
+ type: 'function',
657
+ name: 'LOWER',
658
+ args: {
659
+ type: 'expr_list',
660
+ value: [
661
+ {
662
+ type: 'single_quote_string',
663
+ value: `%${obj.right.value.replaceAll('%', '')}%`,
664
+ },
665
+ ],
666
+ },
667
+ };
668
+ }
669
+ else if (obj.type === 'binary_expr' &&
670
+ (obj.operator === 'NOT IN' || obj.operator === 'IN') &&
671
+ obj.left &&
672
+ (obj.left.type === 'column_ref' ||
673
+ obj.left.type === 'double_quote_string') &&
674
+ obj.right &&
675
+ obj.right.type === 'expr_list' &&
676
+ obj.right.value &&
677
+ obj.right.value.length &&
678
+ obj.right.value[0].type === 'single_quote_string') {
679
+ obj.left = {
680
+ type: 'function',
681
+ name: 'LOWER',
682
+ args: {
683
+ type: 'expr_list',
684
+ value: [
685
+ databaseType === 'BigQuery'
686
+ ? { type: 'column_ref', value: obj.left.column }
687
+ : { type: 'double_quote_string', value: obj.left.column },
688
+ ],
689
+ },
690
+ };
691
+ obj.right = {
692
+ type: 'expr_list',
693
+ // convert NOT IN ('fuel', 'food') to NOT IN (LOWER('fuel'), LOWER('food'))
694
+ value: obj.right.value.map((elem) => ({
695
+ type: 'function',
696
+ name: 'LOWER',
697
+ args: {
698
+ type: 'expr_list',
699
+ value: [{ type: 'single_quote_string', value: elem.value }],
700
+ },
701
+ })),
702
+ };
703
+ }
704
+ else {
705
+ // If the current object does not match the pattern, continue searching its properties
706
+ Object.values(obj).forEach((value) => {
707
+ searchAndReplace(value);
708
+ });
709
+ }
710
+ }
711
+ }
712
+ // Start the recursive search and replace from the root node
713
+ searchAndReplace(node);
714
+ return node;
715
+ }
716
+ exports.convertStringComparison = convertStringComparison;
717
+ // When there is a single "OR" boolean operator, we ensure that there
718
+ // are no parentheses on that object.
719
+ function convertRemoveSimpleParentheses(ast) {
720
+ const node = ast.where;
721
+ if ((0, ast_1.isLogicalBinaryExpr)(node) &&
722
+ node.operator === 'OR' &&
723
+ !(0, ast_1.isLogicalBinaryExpr)(node.left) &&
724
+ !(0, ast_1.isLogicalBinaryExpr)(node.right) &&
725
+ node.parentheses) {
726
+ delete node.parentheses;
727
+ }
728
+ return { ...ast, where: node };
729
+ }
730
+ exports.convertRemoveSimpleParentheses = convertRemoveSimpleParentheses;