blob: c79dfd8dd191a667ff26c587c69469b955a03217 [file]
// Copyright (C) 2025 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.
/**
* Utility functions for the query builder.
*/
import {ColumnInfo} from './column_info';
/**
* Checks if a column type is numeric/quantitative.
* Numeric types include: INT, DOUBLE, DURATION, TIMESTAMP, BOOLEAN, ID types, and ARG_SET_ID.
*
* @param typeStr The column type string (case-insensitive)
* @returns true if the type is numeric
*/
export function isNumericType(typeStr: string): boolean {
const normalized = typeStr.toUpperCase();
return (
normalized === 'INT' ||
normalized === 'DOUBLE' ||
normalized === 'DURATION' ||
normalized === 'TIMESTAMP' ||
normalized === 'BOOLEAN' ||
normalized.startsWith('ID(') ||
normalized.startsWith('JOINID(') ||
normalized === 'ARG_SET_ID'
);
}
/**
* Checks if a column type is a string type.
*
* @param typeStr The column type string (case-insensitive)
* @returns true if the type is a string
*/
export function isStringType(typeStr: string): boolean {
return typeStr.toUpperCase() === 'STRING';
}
/**
* Checks if a column is compatible with a specific aggregation operation.
*
* @param col The column to check
* @param op The aggregation operation (e.g., 'SUM', 'COUNT', 'MEAN', etc.)
* @returns true if the column is compatible with the operation
*/
export function isColumnValidForAggregation(
col: ColumnInfo,
op?: string,
): boolean {
if (!op) return true;
const typeStr = col.type;
const isNumeric = isNumericType(typeStr);
const isString = isStringType(typeStr);
switch (op) {
case 'MEAN':
case 'MEDIAN':
case 'PERCENTILE':
case 'DURATION_WEIGHTED_MEAN':
// These operations require numeric types
return isNumeric;
case 'GLOB':
// GLOB requires string types
return isString;
case 'COUNT':
case 'COUNT(*)':
case 'SUM':
case 'MIN':
case 'MAX':
default:
// These operations work on all types
return true;
}
}
/**
* Gets a human-readable description of the type requirements for an aggregation operation.
*
* @param op The aggregation operation
* @returns A description of the type requirements
*/
export function getAggregationTypeRequirements(op: string): string {
switch (op) {
case 'MEAN':
case 'MEDIAN':
case 'PERCENTILE':
case 'DURATION_WEIGHTED_MEAN':
return 'Requires numeric column';
case 'GLOB':
return 'Requires string column';
case 'COUNT(*)':
return 'No column required';
case 'COUNT':
case 'SUM':
case 'MIN':
case 'MAX':
return 'Works with any column type';
default:
return 'Unknown operation';
}
}
export interface GetCommonColumnsOptions {
// Column names to exclude from the result
excludedColumns?: Set<string>;
// Column types to exclude from the result (e.g., 'STRING', 'BYTES')
excludedTypes?: Set<string>;
}
/**
* Finds columns that exist in all provided column arrays.
* Returns the intersection of column names, optionally filtered by exclusions.
*
* @param columnArrays Array of ColumnInfo arrays to find common columns across
* @param options Optional exclusion filters for column names and types
* @returns Sorted array of common column names
*/
export function getCommonColumns(
columnArrays: ColumnInfo[][],
options?: GetCommonColumnsOptions,
): string[] {
if (columnArrays.length === 0) {
return [];
}
const excludedColumns = options?.excludedColumns ?? new Set();
const excludedTypes = options?.excludedTypes ?? new Set();
// Start with columns from the first array
const firstArray = columnArrays[0];
const commonColumns = new Set(
firstArray
.filter((c) => !excludedColumns.has(c.name) && !excludedTypes.has(c.type))
.map((c) => c.name),
);
// Intersect with columns from remaining arrays
for (let i = 1; i < columnArrays.length; i++) {
const colsMap = new Map(columnArrays[i].map((c) => [c.name, c.type]));
for (const col of commonColumns) {
const colType = colsMap.get(col);
if (colType === undefined || excludedTypes.has(colType)) {
commonColumns.delete(col);
}
}
}
return Array.from(commonColumns).sort();
}