@quillsql/react 2.13.38 → 2.13.40

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 (91) hide show
  1. package/dist/cjs/Chart.d.ts.map +1 -1
  2. package/dist/cjs/Chart.js +0 -1
  3. package/dist/cjs/ChartBuilder.d.ts.map +1 -1
  4. package/dist/cjs/ChartBuilder.js +73 -66
  5. package/dist/cjs/ReportBuilder.d.ts.map +1 -1
  6. package/dist/cjs/ReportBuilder.js +69 -51
  7. package/dist/cjs/components/Chart/ChartTooltip.d.ts +1 -1
  8. package/dist/cjs/components/Chart/ChartTooltip.d.ts.map +1 -1
  9. package/dist/cjs/components/Chart/ChartTooltip.js +3 -3
  10. package/dist/cjs/components/Chart/LineChart.d.ts.map +1 -1
  11. package/dist/cjs/components/Chart/LineChart.js +4 -4
  12. package/dist/cjs/components/QuillMultiSelectSectionList.js +5 -5
  13. package/dist/cjs/components/ReportBuilder/convert.d.ts +1 -1
  14. package/dist/cjs/components/ReportBuilder/convert.d.ts.map +1 -1
  15. package/dist/cjs/components/ReportBuilder/convert.js +73 -21
  16. package/dist/cjs/components/UiComponents.d.ts +2 -1
  17. package/dist/cjs/components/UiComponents.d.ts.map +1 -1
  18. package/dist/cjs/components/UiComponents.js +11 -4
  19. package/dist/cjs/hooks/useExport.d.ts.map +1 -1
  20. package/dist/cjs/hooks/useExport.js +4 -5
  21. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts +19 -11
  22. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  23. package/dist/cjs/internals/ReportBuilder/PivotForm.js +62 -48
  24. package/dist/cjs/internals/ReportBuilder/PivotList.js +5 -4
  25. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts +28 -31
  26. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  27. package/dist/cjs/internals/ReportBuilder/PivotModal.js +315 -633
  28. package/dist/cjs/models/Pivot.d.ts +27 -7
  29. package/dist/cjs/models/Pivot.d.ts.map +1 -1
  30. package/dist/cjs/utils/dashboard.d.ts.map +1 -1
  31. package/dist/cjs/utils/dashboard.js +36 -11
  32. package/dist/cjs/utils/merge.d.ts.map +1 -1
  33. package/dist/cjs/utils/merge.js +2 -1
  34. package/dist/cjs/utils/pivotConstructor.d.ts +1 -0
  35. package/dist/cjs/utils/pivotConstructor.d.ts.map +1 -1
  36. package/dist/cjs/utils/pivotConstructor.js +39 -8
  37. package/dist/cjs/utils/pivotProcessing.d.ts.map +1 -1
  38. package/dist/cjs/utils/pivotProcessing.js +10 -14
  39. package/dist/cjs/utils/queryConstructor.d.ts.map +1 -1
  40. package/dist/cjs/utils/queryConstructor.js +421 -134
  41. package/dist/cjs/utils/report.d.ts.map +1 -1
  42. package/dist/cjs/utils/report.js +2 -2
  43. package/dist/cjs/utils/textProcessing.d.ts +1 -1
  44. package/dist/cjs/utils/textProcessing.d.ts.map +1 -1
  45. package/dist/cjs/utils/textProcessing.js +3 -0
  46. package/dist/esm/Chart.d.ts.map +1 -1
  47. package/dist/esm/Chart.js +0 -1
  48. package/dist/esm/ChartBuilder.d.ts.map +1 -1
  49. package/dist/esm/ChartBuilder.js +73 -66
  50. package/dist/esm/ReportBuilder.d.ts.map +1 -1
  51. package/dist/esm/ReportBuilder.js +69 -51
  52. package/dist/esm/components/Chart/ChartTooltip.d.ts +1 -1
  53. package/dist/esm/components/Chart/ChartTooltip.d.ts.map +1 -1
  54. package/dist/esm/components/Chart/ChartTooltip.js +3 -3
  55. package/dist/esm/components/Chart/LineChart.d.ts.map +1 -1
  56. package/dist/esm/components/Chart/LineChart.js +4 -4
  57. package/dist/esm/components/QuillMultiSelectSectionList.js +5 -5
  58. package/dist/esm/components/ReportBuilder/convert.d.ts +1 -1
  59. package/dist/esm/components/ReportBuilder/convert.d.ts.map +1 -1
  60. package/dist/esm/components/ReportBuilder/convert.js +74 -22
  61. package/dist/esm/components/UiComponents.d.ts +2 -1
  62. package/dist/esm/components/UiComponents.d.ts.map +1 -1
  63. package/dist/esm/components/UiComponents.js +11 -4
  64. package/dist/esm/hooks/useExport.d.ts.map +1 -1
  65. package/dist/esm/hooks/useExport.js +4 -5
  66. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts +19 -11
  67. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  68. package/dist/esm/internals/ReportBuilder/PivotForm.js +63 -49
  69. package/dist/esm/internals/ReportBuilder/PivotList.js +5 -4
  70. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts +28 -31
  71. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  72. package/dist/esm/internals/ReportBuilder/PivotModal.js +327 -635
  73. package/dist/esm/models/Pivot.d.ts +27 -7
  74. package/dist/esm/models/Pivot.d.ts.map +1 -1
  75. package/dist/esm/utils/dashboard.d.ts.map +1 -1
  76. package/dist/esm/utils/dashboard.js +36 -11
  77. package/dist/esm/utils/merge.d.ts.map +1 -1
  78. package/dist/esm/utils/merge.js +2 -1
  79. package/dist/esm/utils/pivotConstructor.d.ts +1 -0
  80. package/dist/esm/utils/pivotConstructor.d.ts.map +1 -1
  81. package/dist/esm/utils/pivotConstructor.js +39 -9
  82. package/dist/esm/utils/pivotProcessing.d.ts.map +1 -1
  83. package/dist/esm/utils/pivotProcessing.js +10 -14
  84. package/dist/esm/utils/queryConstructor.d.ts.map +1 -1
  85. package/dist/esm/utils/queryConstructor.js +421 -134
  86. package/dist/esm/utils/report.d.ts.map +1 -1
  87. package/dist/esm/utils/report.js +2 -2
  88. package/dist/esm/utils/textProcessing.d.ts +1 -1
  89. package/dist/esm/utils/textProcessing.d.ts.map +1 -1
  90. package/dist/esm/utils/textProcessing.js +3 -0
  91. package/package.json +1 -1
@@ -226,7 +226,7 @@ function generateDistinctQueryMySQL(stringFields, query) {
226
226
  }
227
227
  function generateDistinctQueryPostgres(stringFields, query) {
228
228
  const distinctQueries = stringFields.map((field) => {
229
- return `SELECT '${field}' AS ${processColumnReference('field', 'postgresql')}, to_json(ARRAY_AGG(DISTINCT ${field})) AS ${processColumnReference('string_values', 'postgresql')} FROM querytable`;
229
+ return `SELECT '${field}' AS ${processColumnReference('field', 'postgresql')}, to_json(ARRAY_AGG(DISTINCT "${field}")) AS ${processColumnReference('string_values', 'postgresql')} FROM querytable`;
230
230
  });
231
231
  const distinctQuery = distinctQueries.join(' UNION ALL ');
232
232
  return `WITH querytable AS (${query.replace(';', '')}) ` + distinctQuery;
@@ -297,53 +297,115 @@ function create2DPivotQuery(pivot, itemQuery, databaseType, columnFieldValues, d
297
297
  if (!pivot || !pivot.columnField) {
298
298
  return undefined;
299
299
  }
300
- if ((0, columnProcessing_1.isStringType)(pivot.rowFieldType || '') || !pivot.rowFieldType) {
300
+ if ((0, columnProcessing_1.isStringType)(pivot.rowFieldType ?? '') || !pivot.rowFieldType) {
301
301
  return create2DStringPivotQuery(pivot, itemQuery, columnFieldValues, databaseType);
302
302
  }
303
303
  return create2DDatePivotQuery(pivot, itemQuery, columnFieldValues, databaseType, dateBucket);
304
304
  }
305
305
  function create2DStringPivotQuery(pivot, itemQuery, columnFieldValues, databaseType) {
306
306
  const isValidBaseQuery = itemQuery.match(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/);
307
- if (!isValidBaseQuery || !pivot.columnField || !pivot.rowField) {
307
+ if (!isValidBaseQuery || !pivot.columnField || !pivot.rowField)
308
308
  return undefined;
309
- }
310
309
  const rowField = pivot.rowField;
311
- const valueField = pivot.valueField;
310
+ if (!pivot.aggregations?.[0]?.valueField && !pivot.valueField)
311
+ throw new Error('No value field provided for pivot');
312
+ if (!pivot.aggregations?.[0]?.aggregationType && !pivot.aggregationType)
313
+ throw new Error('No aggregation type provided for pivot');
312
314
  const columnField = pivot.columnField;
313
315
  const rowFieldAlias = processColumnReference(rowField, databaseType, undefined, false, true);
314
- const valueFieldAlias = processColumnReference(valueField ?? rowField, databaseType, undefined, false, true);
315
316
  const columnFieldAlias = processColumnReference(columnField, databaseType, undefined, false, true);
316
- const valueAliasSubstring = valueField
317
- ? `${processColumnReference(valueField, databaseType, undefined, true)} AS ${valueFieldAlias},`
318
- : '';
319
- let value2AliasSubstring = '';
320
- let caseWhens = columnFieldValues.map((column) => {
321
- return `${processAggType(pivot.aggregationType, true)}(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) AS ${processColumnReference(column, databaseType, '_', true)}`;
322
- });
323
- if (pivot.aggregationType === 'percentage' && pivot.valueField2) {
324
- const valueField2Alias = processColumnReference(pivot.valueField2, databaseType, undefined, false, true);
325
- if (pivot.valueField === pivot.valueField2) {
326
- caseWhens = columnFieldValues.map((column) => {
327
- return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) / GREATEST(sum(${processColumnReference(pivot.valueField2, databaseType)}), 1) AS ${processColumnReference(column, databaseType, '_', true)}`;
328
- });
317
+ let caseWhens = [];
318
+ let valueAliases = [];
319
+ const seenAggs = {};
320
+ pivot.aggregations?.forEach((currentAgg) => {
321
+ // Track duplicate aggregation combos for disambiguation.
322
+ if (seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']) {
323
+ seenAggs[currentAgg.aggregationType ?? ''][currentAgg.valueField ?? ''] += 1;
329
324
  }
330
325
  else {
331
- value2AliasSubstring = `${processColumnReference(pivot.valueField2, databaseType, undefined, true)} AS ${valueField2Alias},`;
332
- caseWhens = columnFieldValues.map((column) => {
333
- return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) / GREATEST(sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueField2Alias)} END), 1) AS ${processColumnReference(column, databaseType, '_', true)}`;
334
- });
326
+ seenAggs[currentAgg.aggregationType ?? ''] = {
327
+ ...seenAggs[currentAgg.aggregationType ?? ''],
328
+ [currentAgg.valueField ?? '']: 1,
329
+ };
335
330
  }
336
- }
337
- // pivot sort matters in the base query when there is a rowLimit. In mssql, an orderby must be accompanied by a limit in a subquery and not allowed in a cte
338
- const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} ` : ''}`;
331
+ let disambiguationIndex = seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']?.toString();
332
+ if (disambiguationIndex === '1')
333
+ disambiguationIndex = '';
334
+ const valueFieldAlias = processColumnReference((currentAgg.valueField || rowField || 'count'), databaseType, undefined, false, true);
335
+ const valueAliasSubstring = currentAgg.valueField
336
+ ? `${processColumnReference(currentAgg.valueField, databaseType, undefined, true)} AS ${valueFieldAlias}`
337
+ : '';
338
+ let value2AliasSubstring = '';
339
+ const disambiguationField = Object.values(seenAggs[currentAgg.aggregationType ?? ''] ?? {}).reduce((acc, v) => acc + v) > 1
340
+ ? `_${currentAgg.valueField}${disambiguationIndex}`
341
+ : '';
342
+ const disambiguation = pivot.aggregations?.length > 1
343
+ ? `${disambiguationField}_${disambiguationField ? (0, textProcessing_1.matchCasing)(currentAgg.aggregationType, currentAgg.valueField) : currentAgg.aggregationType}`
344
+ : '';
345
+ // Wrap the value field in CASE WHEN if its type is bool.
346
+ const valueExpr = currentAgg.valueFieldType === 'bool'
347
+ ? `CASE WHEN ${valueFieldAlias} THEN 1 ELSE 0 END`
348
+ : processValueField(currentAgg.aggregationType, databaseType, valueFieldAlias);
349
+ if (currentAgg.aggregationType === 'percentage') {
350
+ if (!currentAgg.valueField)
351
+ throw new Error('No value field provided for pivot');
352
+ const valueField2Alias = processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, false, true);
353
+ // Wrap the second value field if its type is bool.
354
+ const value2Expr = (currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool'
355
+ ? `CASE WHEN ${valueField2Alias} THEN 1 ELSE 0 END`
356
+ : valueField2Alias;
357
+ if (currentAgg.valueField === currentAgg.valueField2 || !currentAgg.valueField2) {
358
+ caseWhens = columnFieldValues.map((column) => {
359
+ return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) / GREATEST(sum(${value2Expr}), 1) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
360
+ });
361
+ }
362
+ else {
363
+ value2AliasSubstring = `${processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, true)} AS ${valueField2Alias}`;
364
+ caseWhens = columnFieldValues.map((column) => {
365
+ return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) / GREATEST(sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${value2Expr} END), 1) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
366
+ });
367
+ }
368
+ }
369
+ else {
370
+ caseWhens = [
371
+ ...caseWhens,
372
+ ...columnFieldValues.map((column) => {
373
+ return `${processAggType(currentAgg.aggregationType, true)}(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
374
+ }),
375
+ ];
376
+ }
377
+ if (valueAliasSubstring)
378
+ valueAliases.push(valueAliasSubstring);
379
+ if (value2AliasSubstring)
380
+ valueAliases.push(value2AliasSubstring);
381
+ });
382
+ valueAliases = [
383
+ `${processColumnReference(rowField, databaseType, undefined, true)} AS ${rowFieldAlias}`,
384
+ ...valueAliases,
385
+ `${processColumnReference(columnField, databaseType, undefined, true)} AS ${columnFieldAlias}`,
386
+ ];
387
+ valueAliases = Array.from(new Set(valueAliases));
388
+ const sortQuery = pivot.sort && pivot.sortField && pivot.rowLimit
389
+ ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} `
390
+ : '';
339
391
  const pivotQuery = `
340
- ,quill_alias AS (SELECT ${processColumnReference(rowField, databaseType, undefined, true)} AS ${rowFieldAlias}, ${valueAliasSubstring} ${value2AliasSubstring}
341
- ${processColumnReference(columnField, databaseType, undefined, true)} AS ${columnFieldAlias} FROM quill_base_table),
342
- quill_qt_cw AS (SELECT ${rowFieldAlias}${caseWhens.length > 0 ? `, ${caseWhens.join(', ')}` : ''} FROM quill_alias GROUP BY ${rowFieldAlias}),
343
- quill_base_pivot AS (SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} * FROM quill_qt_cw qt
344
- ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''})
392
+ ,quill_alias AS (
393
+ SELECT ${valueAliases.length > 0 ? `${valueAliases.join(', ')}` : ''}
394
+ FROM quill_base_table
395
+ ),
396
+ quill_qt_cw AS (
397
+ SELECT ${rowFieldAlias}
398
+ ${caseWhens.length > 0 ? `, ${caseWhens.join(', ')}` : ''}
399
+ FROM quill_alias
400
+ GROUP BY ${rowFieldAlias}
401
+ ),
402
+ quill_base_pivot AS (
403
+ SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} *
404
+ FROM quill_qt_cw qt
405
+ ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''}
406
+ )
345
407
  SELECT * FROM quill_base_pivot
346
- `
408
+ `
347
409
  .replace(/\s+/g, ' ')
348
410
  .trim();
349
411
  return itemQuery.replace(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/, pivotQuery);
@@ -353,38 +415,95 @@ function create2DDatePivotQuery(pivot, itemQuery, columnFieldValues, databaseTyp
353
415
  if (!isValidBaseQuery || !pivot.columnField || !pivot.rowField) {
354
416
  return undefined;
355
417
  }
418
+ if (!pivot.aggregations?.[0]?.valueField && !pivot.valueField)
419
+ throw new Error('No value field provided for pivot');
420
+ if (!pivot.aggregations?.[0]?.aggregationType && !pivot.aggregationType)
421
+ throw new Error('No aggregation type provided for pivot');
422
+ // const aggregationType = (pivot.aggregations?.[0]?.aggregationType ?? pivot.aggregationType) as any;
356
423
  const rowField = pivot.rowField;
357
424
  const columnField = pivot.columnField;
358
425
  const rowFieldAlias = processColumnReference(rowField, databaseType, undefined, false, true);
359
- const valueFieldAlias = processColumnReference(pivot.valueField ?? rowField, databaseType, undefined, false, true);
360
426
  const columnFieldAlias = processColumnReference(columnField, databaseType, undefined, false, true);
361
- const valueAliasSubstring = pivot.valueField
362
- ? `${processColumnReference(pivot.valueField, databaseType, undefined, true)} AS ${valueFieldAlias},`
363
- : '';
364
- let value2AliasSubstring = '';
365
- let caseWhens = columnFieldValues.map((column) => {
366
- return `${processAggType(pivot.aggregationType, true)}(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) AS ${processColumnReference(column, databaseType, '_', true)}`;
367
- });
368
- if (pivot.aggregationType === 'percentage' && pivot.valueField2) {
369
- const valueField2Alias = processColumnReference(pivot.valueField2, databaseType, undefined, false, true);
370
- // edge case. if the user picks amount and amount, we assume they want a pie chart like breakdown of amount. so the summation of valueField2 has to be moved outside of the case when
371
- if (pivot.valueField === pivot.valueField2) {
372
- caseWhens = columnFieldValues.map((column) => {
373
- return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) / GREATEST(sum(${processColumnReference(valueField2Alias, databaseType)}), 1) AS ${processColumnReference(column, databaseType, '_', true)}`;
374
- });
427
+ let caseWhens = [];
428
+ let valueFieldAliases = [];
429
+ const seenAggs = {};
430
+ pivot.aggregations?.forEach((currentAgg) => {
431
+ // if the aggregation combo has been seen before, increment the count, else add it to the seenAggs array
432
+ if (seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']) {
433
+ seenAggs[currentAgg.aggregationType ?? ''][currentAgg.valueField ?? ''] += 1;
375
434
  }
376
435
  else {
377
- value2AliasSubstring = `${processColumnReference(pivot.valueField2, databaseType, undefined, true)} AS ${valueField2Alias},`;
378
- caseWhens = columnFieldValues.map((column) => {
379
- return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueFieldAlias)} END) / GREATEST(sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${processValueField(pivot.aggregationType, databaseType, valueField2Alias)} END), 1) AS ${processColumnReference(column, databaseType, '_', true)}`;
380
- });
436
+ seenAggs[currentAgg.aggregationType ?? ''] = {
437
+ ...seenAggs[currentAgg.aggregationType ?? ''],
438
+ [currentAgg.valueField ?? '']: 1,
439
+ };
381
440
  }
382
- }
441
+ let disambiguationIndex = seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']?.toString();
442
+ if (disambiguationIndex === '1')
443
+ disambiguationIndex = '';
444
+ const valueFieldAlias = processColumnReference((currentAgg.valueField ?? rowField), databaseType, undefined, false, true);
445
+ const valueAliasSubstring = currentAgg.valueField
446
+ ? `${processColumnReference(currentAgg.valueField, databaseType, undefined, true)} AS ${valueFieldAlias}`
447
+ : '';
448
+ let value2AliasSubstring = '';
449
+ const disambiguationField = Object.values(seenAggs[currentAgg.aggregationType ?? ''] ?? {}).reduce((acc, v) => acc + v) > 1 ? `_${currentAgg.valueField}${disambiguationIndex}` : '';
450
+ const disambiguation = pivot.aggregations?.length > 1
451
+ ? `${disambiguationField}_${disambiguationField ? (0, textProcessing_1.matchCasing)(currentAgg.aggregationType, currentAgg.valueField) : currentAgg.aggregationType}`
452
+ : '';
453
+ // Wrap the value field in CASE WHEN if its type is bool.
454
+ const valueExpr = currentAgg.valueFieldType === 'bool'
455
+ ? `CASE WHEN ${valueFieldAlias} THEN 1 ELSE 0 END`
456
+ : processValueField(currentAgg.aggregationType, databaseType, valueFieldAlias);
457
+ if (currentAgg.aggregationType === 'percentage') {
458
+ if (!currentAgg.valueField)
459
+ throw new Error('No value field provided for pivot');
460
+ const valueField2Alias = processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, false, true);
461
+ // Wrap the second value field if its type is bool.
462
+ const value2Expr = (currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool'
463
+ ? `CASE WHEN ${valueField2Alias} THEN 1 ELSE 0 END`
464
+ : valueField2Alias;
465
+ // edge case. if the user picks amount and amount, we assume they want a pie chart like breakdown of amount. so the summation of valueField2 has to be moved outside of the case whe
466
+ if (currentAgg.valueField === currentAgg.valueField2 || !currentAgg.valueField2) {
467
+ caseWhens = [
468
+ ...caseWhens,
469
+ ...columnFieldValues.map((column) => {
470
+ return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) / GREATEST(sum(${value2Expr}), 1) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
471
+ }),
472
+ ];
473
+ }
474
+ else {
475
+ value2AliasSubstring = `${processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, true)} AS ${valueField2Alias}`;
476
+ caseWhens = [
477
+ ...caseWhens,
478
+ ...columnFieldValues.map((column) => {
479
+ return `sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) / GREATEST(sum(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${value2Expr} END), 1) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
480
+ }),
481
+ ];
482
+ }
483
+ }
484
+ else {
485
+ caseWhens = [
486
+ ...caseWhens,
487
+ ...columnFieldValues.map((column) => {
488
+ return `${processAggType(currentAgg.aggregationType, true)}(CASE WHEN ${columnFieldAlias} = '${processSingleQuotes(column, databaseType)}' THEN ${valueExpr} END) AS ${processColumnReference(column + disambiguation, databaseType, '_', true)}`;
489
+ }),
490
+ ];
491
+ }
492
+ if (valueAliasSubstring)
493
+ valueFieldAliases.push(valueAliasSubstring);
494
+ if (value2AliasSubstring)
495
+ valueFieldAliases.push(value2AliasSubstring);
496
+ });
497
+ valueFieldAliases = [
498
+ `${processColumnReference(rowField, databaseType, undefined, true)} AS ${rowFieldAlias}`,
499
+ ...valueFieldAliases,
500
+ `${processColumnReference(columnField, databaseType, undefined, true)} AS ${columnFieldAlias}`,
501
+ ];
502
+ valueFieldAliases = Array.from(new Set(valueFieldAliases));
383
503
  // pivot sort matters in the base query when there is a rowLimit. In mssql, an orderby must be accompanied by a limit in a subquery and not allowed in a cte
384
504
  const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} ` : ''}`;
385
505
  const pivotQuery = `
386
- , quill_alias AS (SELECT ${processColumnReference(`${rowField}`, databaseType, undefined, true)} AS ${rowFieldAlias}, ${valueAliasSubstring} ${value2AliasSubstring}
387
- ${processColumnReference(columnField, databaseType, undefined, true, true)} AS ${columnFieldAlias} FROM quill_base_table),
506
+ , quill_alias AS (SELECT ${valueFieldAliases.length > 0 ? `${valueFieldAliases.join(', ')}` : ''} FROM quill_base_table),
388
507
  quill_qt_agg AS (SELECT ${processDateTrunc(dateBucket, rowFieldAlias, databaseType)} as ${rowFieldAlias}${caseWhens.length > 0 ? `, ${caseWhens.join(', ')}` : ''} FROM quill_alias GROUP BY ${databaseType.toLowerCase() === 'clickhouse' ? processColumnReference(`${rowField}`, databaseType) : processDateTrunc(dateBucket, rowFieldAlias, databaseType)}),
389
508
  quill_base_pivot AS (SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} * FROM quill_qt_agg qt
390
509
  ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''})
@@ -405,37 +524,91 @@ function create1DPivotQuery(pivot, itemQuery, dateBucket = 'month', databaseType
405
524
  // their own with statements and withs with the same name will conflict
406
525
  function create1DStringPivotQuery(pivot, itemQuery, databaseType) {
407
526
  const isValidBaseQuery = itemQuery.match(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/);
408
- if (!isValidBaseQuery) {
527
+ if (!isValidBaseQuery)
409
528
  return undefined;
410
- }
411
529
  const rowField = pivot.rowField;
412
- const valueField = pivot.valueField;
413
- const rowAlias = processColumnReference(rowField, databaseType, undefined, false, true);
414
- const valueAlias = processColumnReference(valueField ?? rowField, databaseType, undefined, false, true);
415
- const countAlias = processColumnReference('count', databaseType);
416
- const valueAliasSubstring = valueField
417
- ? `, ${processColumnReference(`${valueField}`, databaseType, undefined, true)} AS ${valueAlias}`
418
- : ``;
419
- let value2AliasSubstring = '';
420
- let quillSelectSubstring = `${rowAlias}, ${processAggType(pivot.aggregationType)}(${valueField ? valueAlias : '*'}) as ${valueField ? valueAlias : countAlias}`;
421
- if (pivot.aggregationType === 'percentage' && pivot.valueField2) {
422
- const value2Alias = processColumnReference(pivot.valueField2, databaseType, undefined, false, true);
423
- if (pivot.valueField !== pivot.valueField2) {
424
- value2AliasSubstring =
425
- valueField && valueField !== pivot.valueField2
426
- ? `, ${processColumnReference(`${pivot.valueField2}`, databaseType, undefined, true)} AS ${value2Alias}`
427
- : '';
530
+ const rowAlias = processColumnReference(rowField, databaseType, undefined, true);
531
+ let quillAggSelects = [rowAlias];
532
+ let valueFieldAliases = [];
533
+ const seenAggs = {};
534
+ pivot.aggregations?.forEach((currentAgg) => {
535
+ if (!currentAgg.valueField)
536
+ currentAgg.valueField = undefined;
537
+ // Track duplicates to disambiguate names
538
+ if (seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']) {
539
+ seenAggs[currentAgg.aggregationType ?? ''][currentAgg.valueField ?? ''] += 1;
428
540
  }
429
- quillSelectSubstring = `${rowAlias}, sum(${valueAlias}) / GREATEST(sum(${value2Alias}), 1) as ${processColumnReference(`${valueField ?? ''}_${(0, textProcessing_1.matchCasing)('percentage', valueField)}`, databaseType, undefined, false, true)}`;
430
- }
431
- // pivot sort matters in the base query when there is a rowLimit. In mssql, an orderby must be accompanied by a limit in a subquery and not allowed in a cte
432
- const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} ` : ''}`;
433
- const pivotQuery = `, quill_alias AS (SELECT ${processColumnReference(`${rowField}`, databaseType, undefined, true)} AS ${rowAlias}${valueAliasSubstring}${value2AliasSubstring} FROM quill_base_table),
434
- quill_qt_cw AS (SELECT ${quillSelectSubstring} FROM quill_alias GROUP BY ${rowAlias}),
435
- quill_base_pivot AS (SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} * FROM quill_qt_cw qt
436
- ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''})
437
- SELECT * FROM quill_base_pivot
438
- `
541
+ else {
542
+ seenAggs[currentAgg.aggregationType ?? ''] = {
543
+ ...seenAggs[currentAgg.aggregationType ?? ''],
544
+ [currentAgg.valueField ?? '']: 1,
545
+ };
546
+ }
547
+ let disambiguationIndex = seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']?.toString() ?? '';
548
+ if (disambiguationIndex === '1')
549
+ disambiguationIndex = '';
550
+ // This is the alias (from quill_alias CTE) for the field
551
+ const valueFieldAlias = processColumnReference((currentAgg.valueField || rowField || 'count'), databaseType, undefined, false, true);
552
+ // In the base query, we select the raw column.
553
+ const valueAliasSubstring = currentAgg.valueField
554
+ ? `${processColumnReference(currentAgg.valueField, databaseType, undefined, true)} AS ${valueFieldAlias}`
555
+ : '';
556
+ let value2AliasSubstring = '';
557
+ const disambiguation = pivot.aggregations?.length > 1
558
+ ? `${disambiguationIndex}_${currentAgg.aggregationType}`
559
+ : '';
560
+ // Wrap the field in a CASE WHEN if it's boolean
561
+ let valueExpr = !currentAgg.valueField ? '*' : valueFieldAlias;
562
+ if (currentAgg.valueFieldType === 'bool') {
563
+ valueExpr = `CASE WHEN ${valueFieldAlias} THEN 1 ELSE 0 END`;
564
+ }
565
+ if (currentAgg.aggregationType === 'percentage') {
566
+ if (!currentAgg.valueField) {
567
+ throw new Error('No value field provided for percentage aggregation');
568
+ }
569
+ const valueField2Alias = processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, false, true);
570
+ let value2Expr = valueField2Alias;
571
+ if ((currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool') {
572
+ value2Expr = `CASE WHEN ${valueField2Alias} THEN 1 ELSE 0 END`;
573
+ }
574
+ value2AliasSubstring = currentAgg.valueField2 && currentAgg.valueField !== currentAgg.valueField2
575
+ ? `${processColumnReference(currentAgg.valueField2, databaseType, undefined, true)} AS ${valueField2Alias}`
576
+ : '';
577
+ const percentageExpr = currentAgg.valueField === currentAgg.valueField2 || !currentAgg.valueField2
578
+ ? `sum(${valueExpr}) / ${(currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool' ? 'COUNT(*)' : 'SUM(sum(' + valueExpr + ')) OVER ()'}`
579
+ : `sum(${valueExpr}) / GREATEST(sum(${value2Expr}), 1)`;
580
+ quillAggSelects = [
581
+ ...quillAggSelects,
582
+ `${percentageExpr} as ${processColumnReference(`${currentAgg.valueField ?? ''}${disambiguation}`, databaseType, undefined, false, true)}`,
583
+ ];
584
+ }
585
+ else {
586
+ quillAggSelects = [
587
+ ...quillAggSelects,
588
+ `${processAggType(currentAgg.aggregationType)}(${valueExpr}) AS ${processColumnReference((currentAgg.valueField || 'count') + disambiguation, databaseType)}`,
589
+ ];
590
+ }
591
+ if (valueAliasSubstring)
592
+ valueFieldAliases.push(valueAliasSubstring);
593
+ if (value2AliasSubstring)
594
+ valueFieldAliases.push(value2AliasSubstring);
595
+ });
596
+ valueFieldAliases = Array.from(new Set(valueFieldAliases));
597
+ const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit
598
+ ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} `
599
+ : ''}`;
600
+ const pivotQuery = `, quill_alias AS (
601
+ SELECT ${processColumnReference(`${rowField}`, databaseType, undefined, true)} AS ${rowAlias}${valueFieldAliases.length > 0 ? `, ${valueFieldAliases.join(', ')}` : ''}
602
+ FROM quill_base_table
603
+ ),
604
+ quill_qt_cw AS (
605
+ SELECT ${quillAggSelects.join(', ')} FROM quill_alias GROUP BY ${rowAlias}
606
+ ),
607
+ quill_base_pivot AS (
608
+ SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} *
609
+ FROM quill_qt_cw qt ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''}
610
+ )
611
+ SELECT * FROM quill_base_pivot`
439
612
  .replace(/\s+/g, ' ')
440
613
  .trim();
441
614
  return itemQuery.replace(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/, pivotQuery);
@@ -446,33 +619,76 @@ function create1DDatePivotQuery(pivot, itemQuery, dateBucket = 'month', database
446
619
  return undefined;
447
620
  }
448
621
  const rowField = pivot.rowField || '';
449
- const valueField = pivot.valueField;
450
622
  const rowFieldAlias = processColumnReference(rowField, databaseType, undefined);
451
- const valueFieldAlias = processColumnReference(valueField ?? rowField, databaseType, undefined, false, true);
452
- const comparisonValueFieldAlias = processColumnReference(valueField ?? rowField, databaseType, undefined, false, true);
453
- const countAlias = processColumnReference(`count`, databaseType);
454
- const valueAliasSubstring = valueField ? `, ${valueFieldAlias}` : ``;
455
- let value2AliasSubstring = '';
456
- let quillAggSubstring = `
457
- ${processDateTrunc(dateBucket, rowFieldAlias, databaseType)} as ${processColumnReference(`${rowField}`, databaseType)}
458
- , ${processAggType(pivot.aggregationType)}(${valueField ? valueFieldAlias : rowFieldAlias}) as ${valueField ? comparisonValueFieldAlias : countAlias}
459
- `;
460
- // this "and" is for typescript. in reality, pivot.valueField2 must exist if pivot.aggregationType === 'percentage'
461
- if (pivot.aggregationType === 'percentage' && pivot.valueField2) {
462
- const valueField2Alias = processColumnReference(pivot.valueField2, databaseType, undefined, false, true);
463
- value2AliasSubstring =
464
- valueField !== pivot.valueField2 ? `, ${valueField2Alias}` : '';
465
- quillAggSubstring = `
466
- ${processDateTrunc(dateBucket, rowFieldAlias, databaseType)} as ${processColumnReference(rowField, databaseType)}
467
- , sum(${valueFieldAlias}) / GREATEST(sum(${valueField2Alias}), 1) as ${processColumnReference(`${valueField ?? ''}_${(0, textProcessing_1.matchCasing)('percentage', valueField)}`, databaseType, undefined, false, true)}
468
- `;
469
- }
623
+ let quillAggSelects = [`${processDateTrunc(dateBucket, rowFieldAlias, databaseType)} as ${processColumnReference(rowField, databaseType)}`];
624
+ let valueFieldAliases = [];
625
+ const seenAggs = {};
626
+ pivot.aggregations?.forEach((currentAgg) => {
627
+ if (!currentAgg.valueField)
628
+ currentAgg.valueField = undefined;
629
+ // if the aggregation combo has been seen before, increment the count, else add it to the seenAggs array
630
+ if (seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']) {
631
+ seenAggs[currentAgg.aggregationType ?? ''][currentAgg.valueField ?? ''] += 1;
632
+ }
633
+ else {
634
+ seenAggs[currentAgg.aggregationType ?? ''] = {
635
+ ...seenAggs[currentAgg.aggregationType ?? ''],
636
+ [currentAgg.valueField ?? '']: 1,
637
+ };
638
+ }
639
+ let disambiguationIndex = seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']?.toString() ?? '';
640
+ if (disambiguationIndex === '1')
641
+ disambiguationIndex = '';
642
+ const valueFieldAlias = processColumnReference((currentAgg.valueField || rowField || 'count'), databaseType, undefined, false, true);
643
+ const valueAliasSubstring = currentAgg.valueField
644
+ ? `${processColumnReference(currentAgg.valueField, databaseType, undefined, true)} AS ${valueFieldAlias}`
645
+ : '';
646
+ let value2AliasSubstring = '';
647
+ const disambiguation = pivot.aggregations?.length > 1
648
+ ? `${disambiguationIndex}_${currentAgg.aggregationType}`
649
+ : '';
650
+ // Wrap the field in a CASE WHEN if it's boolean
651
+ let valueExpr = !currentAgg.valueField ? '*' : valueFieldAlias;
652
+ if (currentAgg.valueFieldType === 'bool') {
653
+ valueExpr = `CASE WHEN ${valueFieldAlias} THEN 1 ELSE 0 END`;
654
+ }
655
+ if (currentAgg.aggregationType === 'percentage') {
656
+ if (!currentAgg.valueField) {
657
+ throw new Error('No value field provided for percentage aggregation');
658
+ }
659
+ const valueField2Alias = processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, false, true);
660
+ value2AliasSubstring = currentAgg.valueField2 && currentAgg.valueField !== currentAgg.valueField2
661
+ ? `${processColumnReference(currentAgg.valueField2, databaseType, undefined, true)} AS ${valueField2Alias}`
662
+ : '';
663
+ let value2Expr = valueField2Alias;
664
+ if ((currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool') {
665
+ value2Expr = `CASE WHEN ${valueField2Alias} THEN 1 ELSE 0 END`;
666
+ }
667
+ const percentageExpr = currentAgg.valueField === currentAgg.valueField2 || !currentAgg.valueField2
668
+ ? `sum(${valueExpr}) / ${(currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool' ? 'COUNT(*)' : 'SUM(sum(' + valueExpr + ')) OVER ()'}`
669
+ : `sum(${valueExpr}) / GREATEST(sum(${value2Expr}), 1)`;
670
+ quillAggSelects = [
671
+ ...quillAggSelects,
672
+ `${percentageExpr} as ${processColumnReference(`${currentAgg.valueField}${disambiguation}`, databaseType, undefined, false, true)}`,
673
+ ];
674
+ }
675
+ else {
676
+ quillAggSelects = [
677
+ ...quillAggSelects,
678
+ `${processAggType(currentAgg.aggregationType)}(${valueExpr}) AS ${processColumnReference((currentAgg.valueField || 'count') + disambiguation, databaseType)}`,
679
+ ];
680
+ }
681
+ if (valueAliasSubstring)
682
+ valueFieldAliases.push(valueAliasSubstring);
683
+ if (value2AliasSubstring)
684
+ valueFieldAliases.push(value2AliasSubstring);
685
+ });
686
+ valueFieldAliases = Array.from(new Set(valueFieldAliases));
470
687
  // pivot sort matters in the base query when there is a rowLimit. In mssql, an orderby must be accompanied by a limit in a subquery and not allowed in a cte
471
688
  const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} ` : ''}`;
472
- const pivotQuery = `, quill_alias AS (SELECT ${processColumnReference(`${rowField}`, databaseType, undefined, true)} AS ${rowFieldAlias}${valueAliasSubstring}
473
- ${value2AliasSubstring}
689
+ const pivotQuery = `, quill_alias AS (SELECT ${processColumnReference(`${rowField}`, databaseType, undefined, true)} AS ${rowFieldAlias}${valueFieldAliases.length > 0 ? `, ${valueFieldAliases.join(', ')}` : ''}
474
690
  FROM quill_base_table),
475
- quill_qt_agg AS (SELECT ${quillAggSubstring}
691
+ quill_qt_agg AS (SELECT ${quillAggSelects.join(', ')}
476
692
  FROM quill_alias GROUP BY ${databaseType.toLowerCase() === 'clickhouse' ? processColumnReference(`${rowField}`, databaseType) : processDateTrunc(dateBucket, rowFieldAlias, databaseType)}),
477
693
  quill_base_pivot AS (SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} * FROM quill_qt_agg qt
478
694
  ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''})
@@ -484,27 +700,89 @@ function create1DDatePivotQuery(pivot, itemQuery, dateBucket = 'month', database
484
700
  }
485
701
  function createAggregationValuePivot(pivot, itemQuery, databaseType) {
486
702
  const isValidBaseQuery = itemQuery.match(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/);
487
- if (!isValidBaseQuery || !pivot.valueField) {
703
+ if (!isValidBaseQuery)
488
704
  return undefined;
489
- }
490
- const valueField = pivot.valueField;
491
- const aggregationType = processAggType(pivot.aggregationType);
492
- const valueAlias = processColumnReference(valueField, databaseType, undefined, false, true);
493
- let value2AliasSubstring = '';
494
- let quillAggSubstring = `SELECT ${aggregationType}(${valueAlias}) as ${processColumnReference(valueField, databaseType)}`;
495
- if (aggregationType === 'percentage' && pivot.valueField2) {
496
- const value2Alias = processColumnReference(pivot.valueField2, databaseType, undefined, false, true);
497
- value2AliasSubstring = `, ${value2Alias}`;
498
- quillAggSubstring = `SELECT sum(${valueAlias}) / GREATEST(sum(${value2Alias}), 1) as ${processColumnReference(`${valueField}_${(0, textProcessing_1.matchCasing)('percentage', valueField)}`, databaseType, undefined, false, true)}`;
499
- }
500
- // pivot sort matters in the base query when there is a rowLimit. In mssql, an orderby must be accompanied by a limit in a subquery and not allowed in a cte
501
- const sortQuery = `${pivot.sort && pivot.sortField && pivot.rowLimit ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} ` : ''}`;
502
- const pivotQuery = `, quill_alias AS (SELECT ${valueAlias}${value2AliasSubstring} FROM quill_base_table),
503
- quill_qt_agg AS (${quillAggSubstring} FROM quill_alias),
504
- quill_base_pivot AS (SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql' ? `TOP ${pivot.rowLimit}` : ''} * FROM quill_qt_agg qt
505
- ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql' ? ` LIMIT ${pivot.rowLimit}` : ''})
506
- SELECT * FROM quill_base_pivot
507
- `
705
+ let quillAggSelects = [];
706
+ let valueFieldAliases = [];
707
+ const seenAggs = {};
708
+ pivot.aggregations?.forEach((currentAgg) => {
709
+ if (!currentAgg.valueField)
710
+ currentAgg.valueField = undefined;
711
+ // Track duplicate aggregation combos for disambiguation.
712
+ if (seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']) {
713
+ seenAggs[currentAgg.aggregationType ?? ''][currentAgg.valueField ?? ''] += 1;
714
+ }
715
+ else {
716
+ seenAggs[currentAgg.aggregationType ?? ''] = {
717
+ ...seenAggs[currentAgg.aggregationType ?? ''],
718
+ [currentAgg.valueField ?? '']: 1,
719
+ };
720
+ }
721
+ let disambiguationIndex = seenAggs[currentAgg.aggregationType ?? '']?.[currentAgg.valueField ?? '']?.toString() ?? '';
722
+ if (disambiguationIndex === '1')
723
+ disambiguationIndex = '';
724
+ const valueFieldAlias = processColumnReference((currentAgg.valueField || 'count'), databaseType, undefined, false, true);
725
+ const valueAliasSubstring = currentAgg.valueField
726
+ ? `${processColumnReference(currentAgg.valueField, databaseType, undefined, true)} AS ${valueFieldAlias}`
727
+ : '';
728
+ let value2AliasSubstring = '';
729
+ const disambiguation = pivot.aggregations?.length > 1
730
+ ? `${disambiguationIndex}_${currentAgg.aggregationType}`
731
+ : '';
732
+ // If the field type is bool, wrap it in a CASE WHEN
733
+ let valueExpr = !currentAgg.valueField ? '*' : valueFieldAlias;
734
+ valueExpr = currentAgg.valueFieldType === 'bool'
735
+ ? `CASE WHEN ${valueFieldAlias} THEN 1 ELSE 0 END`
736
+ : valueExpr;
737
+ if (currentAgg.aggregationType === 'percentage') {
738
+ if (!currentAgg.valueField) {
739
+ throw new Error('No value field provided for percentage aggregation');
740
+ }
741
+ const valueField2Alias = processColumnReference(currentAgg.valueField2 ?? currentAgg.valueField, databaseType, undefined, false, true);
742
+ const value2Expr = (currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool'
743
+ ? `CASE WHEN ${valueField2Alias} THEN 1 ELSE 0 END`
744
+ : valueField2Alias;
745
+ value2AliasSubstring = currentAgg.valueField2 && currentAgg.valueField !== currentAgg.valueField2
746
+ ? `${processColumnReference(currentAgg.valueField2, databaseType, undefined, true)} AS ${valueField2Alias}`
747
+ : '';
748
+ const percentageExpr = currentAgg.valueField === currentAgg.valueField2 || !currentAgg.valueField2
749
+ ? `sum(${valueExpr}) / ${(currentAgg.valueField2Type ?? currentAgg.valueFieldType) === 'bool' ? 'COUNT(*)' : 'SUM(sum(' + valueExpr + ')) OVER ()'}`
750
+ : `sum(${valueExpr}) / GREATEST(sum(${value2Expr}), 1)`;
751
+ quillAggSelects = [
752
+ ...quillAggSelects,
753
+ `${percentageExpr} as ${processColumnReference(`${currentAgg.valueField ?? ''}${disambiguation}`, databaseType, undefined, false, true)}`,
754
+ ];
755
+ }
756
+ else {
757
+ quillAggSelects = [
758
+ ...quillAggSelects,
759
+ `${processAggType(currentAgg.aggregationType)}(${valueExpr}) AS ${processColumnReference((currentAgg.valueField || 'count') + disambiguation, databaseType)}`,
760
+ ];
761
+ }
762
+ if (valueAliasSubstring)
763
+ valueFieldAliases.push(valueAliasSubstring);
764
+ if (value2AliasSubstring)
765
+ valueFieldAliases.push(value2AliasSubstring);
766
+ });
767
+ valueFieldAliases = Array.from(new Set(valueFieldAliases));
768
+ const sortQuery = pivot.sort && pivot.sortField && pivot.rowLimit
769
+ ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType)} ${pivot.sortDirection || ''} `
770
+ : '';
771
+ const pivotQuery = `, quill_alias AS (
772
+ SELECT ${valueFieldAliases.join(', ')} FROM quill_base_table
773
+ ),
774
+ quill_qt_agg AS (
775
+ SELECT ${quillAggSelects.join(', ')} FROM quill_alias
776
+ ),
777
+ quill_base_pivot AS (
778
+ SELECT ${pivot.rowLimit && databaseType.toLowerCase() === 'mssql'
779
+ ? `TOP ${pivot.rowLimit}`
780
+ : ''} * FROM quill_qt_agg qt
781
+ ${sortQuery}${pivot.rowLimit && databaseType.toLowerCase() !== 'mssql'
782
+ ? ` LIMIT ${pivot.rowLimit}`
783
+ : ''}
784
+ )
785
+ SELECT * FROM quill_base_pivot`
508
786
  .replace(/\s+/g, ' ')
509
787
  .trim();
510
788
  return itemQuery.replace(/SELECT \* FROM\s+["'[`]?quill_base_table["'\]`]?\s*$/, pivotQuery);
@@ -516,19 +794,19 @@ function additionalProcessingOnPivotQuery(pivot, query, additionalProcessing, da
516
794
  if (!isValidBaseQuery) {
517
795
  return undefined;
518
796
  }
797
+ if (!pivot.aggregations || pivot.aggregations.length === 0) {
798
+ if (pivot.aggregationType) {
799
+ pivot.aggregations = [{ aggregationType: pivot.aggregationType, valueField: pivot.valueField, valueField2: pivot.valueField2 }];
800
+ }
801
+ else {
802
+ throw new Error('No aggregations provided for pivot');
803
+ }
804
+ }
519
805
  let rowsPerPage = 0;
520
806
  let currentInterval = 0;
521
807
  let offset = 0;
522
808
  let limit = 1000;
523
- let sortQuery = `${pivot.sort && pivot.sortField
524
- ? ` ORDER BY ${processColumnReference(pivot.sortField, databaseType, undefined, true)} ${pivot.sortDirection || ''}`
525
- : pivot.rowField
526
- ? `ORDER BY ${processColumnReference(`${pivot.rowField}`, databaseType)}`
527
- : pivot.valueField
528
- ? pivot.aggregationType === 'percentage'
529
- ? `ORDER BY ${processColumnReference(`${pivot.valueField}_${(0, textProcessing_1.matchCasing)('percentage', pivot.valueField)}`, databaseType, undefined, false, true)}`
530
- : `ORDER BY ${processColumnReference(pivot.valueField, databaseType, undefined, false, true)}`
531
- : ''}`;
809
+ let sortQuery = '';
532
810
  if (additionalProcessing.page) {
533
811
  const page = additionalProcessing.page.page || 0;
534
812
  if (additionalProcessing.page.rowsPerRequest) {
@@ -538,9 +816,18 @@ function additionalProcessingOnPivotQuery(pivot, query, additionalProcessing, da
538
816
  currentInterval = page ? Math.floor(page / (limit / rowsPerPage)) : 0;
539
817
  offset = currentInterval * limit;
540
818
  }
819
+ const disambiguation = pivot.aggregations.length > 1
820
+ ? `_${(0, textProcessing_1.matchCasing)(pivot.aggregations?.[0]?.aggregationType, pivot.aggregations?.[0]?.valueField)}`
821
+ : '';
541
822
  if (additionalProcessing.sort) {
542
823
  sortQuery = `ORDER BY ${processColumnReference(additionalProcessing.sort.field, databaseType)} ${additionalProcessing.sort.direction || ''}`;
543
824
  }
825
+ else {
826
+ const valueFieldAlias = processColumnReference((pivot.aggregations?.[0]?.valueField ?? '') + disambiguation, databaseType, undefined, false, true);
827
+ const defaultSortField = pivot.sortField || pivot.rowField || valueFieldAlias;
828
+ const defaultSortDirection = pivot.sortDirection || '';
829
+ sortQuery = `ORDER BY ${processColumnReference(defaultSortField, databaseType)} ${defaultSortDirection}`;
830
+ }
544
831
  const additionalProcessingQuery = `
545
832
  SELECT *
546
833
  FROM quill_base_pivot ${sortQuery}${databaseType === 'mssql' ? ` OFFSET ${offset} ROWS FETCH NEXT ${limit} ROWS ONLY` : ` LIMIT ${limit} OFFSET ${offset}`}