| // 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. | |
| import {arrayEquals} from '../../../../base/array_utils'; | |
| import type {SortDirection} from '../../../../base/comparison_utils'; | |
| // A column in the SQL query. It can be either be: | |
| // - A column in the table (represented by a string). | |
| // - An expression,computing the value based on other columns (represented by SqlExpression). | |
| // - A derived column: `column` from `source.table`, with `source.joinOn` describing | |
| // how `source.table` should be joined with the primary table (usually join on ID). | |
| export type SqlColumn = | |
| | string | |
| | SqlExpression | |
| | { | |
| column: string; | |
| source: SourceTable; | |
| // Optional unique id for this column, which will be shown to the user (e.g. in column header and filters). | |
| id?: string; | |
| }; | |
| // A class representing a SQL column which is computed based on other columns. | |
| export class SqlExpression { | |
| // op: Operation, which takes the expressions for columns and returns a valid SQL expression. | |
| // columns: List of columns that the operation references. | |
| // id: optional unique id for this column, which will be shown to the user (e.g. in column header and filters). | |
| constructor( | |
| public op: (cols: string[]) => string, | |
| public columns: SqlColumn[], | |
| public id?: string, | |
| ) { | |
| this.op = op; | |
| } | |
| } | |
| // A source table for a derived SQL column, describining the additional table to be joined with the primary source table and the join contraints. | |
| export type SourceTable = { | |
| table: string; | |
| joinOn: {[key: string]: SqlColumn}; | |
| // Whether more performant 'INNER JOIN' can be used instead of 'LEFT JOIN'. | |
| // Special care should be taken to ensure that a) all rows exist in a target table, and b) the source is not null, otherwise the rows will be filtered out. | |
| // false by default. | |
| innerJoin?: boolean; | |
| }; | |
| // A unique identifier for the SQL column. | |
| export function sqlColumnId(column: SqlColumn): string { | |
| // For table columns, use the column name as an id. | |
| if (typeof column === 'string') { | |
| return column; | |
| } | |
| // For expressions, use the specified id, or plug the ids of the columns into the expression. | |
| if (column instanceof SqlExpression) { | |
| if (column.id !== undefined) return column.id; | |
| return `${column.op(column.columns.map(sqlColumnId))}`; | |
| } | |
| if (column.id !== undefined) { | |
| return column.id; | |
| } | |
| // Special case: If the join is performed on a single column `id`, we can use a simpler representation (i.e. `table[id].column`). | |
| if (arrayEquals(Object.keys(column.source.joinOn), ['id'])) { | |
| return `${column.source.table}[${sqlColumnId(Object.values(column.source.joinOn)[0]!)}].${column.column}`; | |
| } | |
| // Otherwise, we need to list all the join constraints. | |
| const lookup = Object.entries(column.source.joinOn) | |
| .map(([key, value]): string => { | |
| const valueStr = sqlColumnId(value); | |
| if (key === valueStr) return key; | |
| return `${key}=${sqlColumnId(value)}`; | |
| }) | |
| .join(', '); | |
| return `${column.source.table}[${lookup}].${column.column}`; | |
| } | |
| export function isSqlColumnEqual(a: SqlColumn, b: SqlColumn): boolean { | |
| return sqlColumnId(a) === sqlColumnId(b); | |
| } | |
| // A column order clause, which specifies the column and the direction in which it should be sorted. | |
| export interface ColumnOrderClause { | |
| column: SqlColumn; | |
| direction: SortDirection; | |
| } |