blob: 44488e332b0637027535c4f527a9736b0fb7c975 [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {AggregationAttrs, PivotAttrs} from './pivot_table_data';
export function getPivotAlias(pivot: PivotAttrs): string {
return `${pivot.tableName} ${pivot.columnName}`;
}
export function getAggregationAlias(aggregation: AggregationAttrs): string {
return `${aggregation.tableName} ${aggregation.columnName} (${
aggregation.aggregation})`;
}
export function getSqlPivotAlias(pivot: PivotAttrs): string {
return `"${getPivotAlias(pivot)}"`;
}
export function getSqlAggregationAlias(aggregation: AggregationAttrs): string {
return `"${getAggregationAlias(aggregation)}"`;
}
export class PivotTableQueryGenerator {
// Generates a query that selects all pivots and aggregations and joins any
// tables needed by them together. All pivots are renamed into the format
// tableName columnName and all aggregations are renamed into
// tableName columnName (aggregation) (see getPivotAlias or
// getAggregationAlias).
private generateJoinQuery(
pivots: PivotAttrs[], aggregations: AggregationAttrs[],
whereFilters: string[]): string {
let joinQuery = 'SELECT\n';
const pivotCols = [];
for (const pivot of pivots) {
pivotCols.push(
`${pivot.tableName}.${pivot.columnName} AS ` +
`${getSqlPivotAlias(pivot)}`);
}
const aggCols = [];
for (const aggregation of aggregations) {
aggCols.push(
`${aggregation.tableName}.${aggregation.columnName} AS ` +
`${getSqlAggregationAlias(aggregation)}`);
}
joinQuery += pivotCols.concat(aggCols).join(',\n ');
joinQuery += '\n';
joinQuery += 'FROM slice\n';
joinQuery += 'WHERE\n';
joinQuery += whereFilters.join(' AND\n ');
joinQuery += '\n';
return joinQuery;
}
// Partitions the aggregations from the subquery generateJoinQuery over
// all sets of appended pivots ({pivot1}, {pivot1, pivot2}, etc).
private generateAggregationQuery(
pivots: PivotAttrs[], aggregations: AggregationAttrs[],
whereFilters: string[]): string {
// No need for this query if there are no aggregations.
if (aggregations.length === 0) {
return this.generateJoinQuery(pivots, aggregations, whereFilters);
}
let aggQuery = 'SELECT\n';
const pivotCols = pivots.map(pivot => getSqlPivotAlias(pivot));
const aggCols = [];
for (const aggregation of aggregations) {
const aggColPrefix =
`${aggregation.aggregation}(${getSqlAggregationAlias(aggregation)})`;
if (pivots.length === 0) {
// Don't partition over pivots if there are no pivots.
aggCols.push(
`${aggColPrefix} AS ${getSqlAggregationAlias(aggregation)}`);
continue;
}
aggCols.push(
`${aggColPrefix} OVER (PARTITION BY ` +
`${pivotCols.join(', ')}) AS ` +
`${getSqlAggregationAlias(aggregation)}`);
}
aggQuery += pivotCols.concat(aggCols).join(',\n ');
aggQuery += '\n';
aggQuery += 'FROM (\n';
aggQuery += this.generateJoinQuery(pivots, aggregations, whereFilters);
aggQuery += ')\n';
return aggQuery;
}
// Takes a list of pivots and aggregations and generates a query that
// extracts all pivots and aggregation partitions and groups by all
// columns and orders by each aggregation as requested.
generateQuery(
pivots: PivotAttrs[], aggregations: AggregationAttrs[],
whereFilters: string[]): string {
// No need to generate query if there is no selected pivots or
// aggregations.
if (pivots.length === 0 && aggregations.length === 0) {
return '';
}
let query = '\nSELECT\n';
const pivotCols = pivots.map(pivot => getSqlPivotAlias(pivot));
const aggCols =
aggregations.map(aggregation => getSqlAggregationAlias(aggregation));
query += pivotCols.concat(aggCols).join(',\n ');
query += '\n';
query += 'FROM (\n';
query += this.generateAggregationQuery(pivots, aggregations, whereFilters);
query += ')\n';
query += 'GROUP BY ';
// Generate an array from 1 to size (number of pivots and aggregation
// partitions) into a string to group by all columns.
const size = pivots.length + aggregations.length;
const groupByQuery =
new Array(size).fill(1).map((_, i) => i + 1).join(', ');
query += groupByQuery;
query += '\n';
// For each aggregation partition (found after pivot columns) we order by
// either 'DESC' or 'ASC' as requested (DESC by default).
if (aggregations.length > 0) {
query += 'ORDER BY ';
const orderString = (i: number) => `${i + 1 + pivots.length} ` +
`${aggregations[i].order}`;
const orderByQuery = new Array(aggregations.length)
.fill(1)
.map((_, i) => orderString(i))
.join(', ');
query += orderByQuery;
query += '\n';
}
return query;
}
}