1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {z} from 'zod'; 16import { 17 SqlModules, 18 SqlColumn, 19 SqlFunction, 20 SqlArgument, 21 SqlMacro, 22 SqlModule, 23 SqlPackage, 24 SqlTable, 25 SqlTableFunction, 26 SqlType, 27 TableAndColumn, 28 createTableColumnFromPerfettoSql, 29} from './sql_modules'; 30import {SqlTableDescription} from '../../components/widgets/sql/table/table_description'; 31import {TableColumn} from '../../components/widgets/sql/table/table_column'; 32 33export class SqlModulesImpl implements SqlModules { 34 readonly packages: SqlPackage[]; 35 36 constructor(docs: SqlModulesDocsSchema) { 37 this.packages = docs.map((json) => new StdlibPackageImpl(json)); 38 } 39 40 findAllTablesWithLinkedId(tableAndColumn: TableAndColumn): SqlTable[] { 41 const linkedIdTables: SqlTable[] = []; 42 for (const t of this.listTables()) { 43 const allLinkedCols = t.linkedIdColumns; 44 if ( 45 allLinkedCols.find( 46 (c) => 47 c.type.tableAndColumn && 48 c.type.tableAndColumn.isEqual(tableAndColumn), 49 ) 50 ) { 51 linkedIdTables.push(t); 52 } 53 } 54 return linkedIdTables; 55 } 56 57 getTable(tableName: string): SqlTable | undefined { 58 for (const p of this.packages) { 59 const t = p.getTable(tableName); 60 if (t !== undefined) { 61 return t; 62 } 63 } 64 return; 65 } 66 67 listTables(): SqlTable[] { 68 return this.packages.flatMap((p) => p.listTables()); 69 } 70 71 listTablesNames(): string[] { 72 return this.packages.flatMap((p) => p.listTablesNames()); 73 } 74 75 getModuleForTable(tableName: string): SqlModule | undefined { 76 for (const stdlibPackage of this.packages) { 77 const maybeTable = stdlibPackage.getModuleForTable(tableName); 78 if (maybeTable) { 79 return maybeTable; 80 } 81 } 82 return undefined; 83 } 84} 85 86export class StdlibPackageImpl implements SqlPackage { 87 readonly name: string; 88 readonly modules: SqlModule[]; 89 90 constructor(docs: DocsPackageSchemaType) { 91 this.name = docs.name; 92 this.modules = []; 93 for (const moduleJson of docs.modules) { 94 this.modules.push(new StdlibModuleImpl(moduleJson)); 95 } 96 } 97 98 getTable(tableName: string): SqlTable | undefined { 99 for (const module of this.modules) { 100 for (const dataObj of module.dataObjects) { 101 if (dataObj.name == tableName) { 102 return dataObj; 103 } 104 } 105 } 106 return undefined; 107 } 108 109 listTables(): SqlTable[] { 110 return this.modules.flatMap((module) => module.dataObjects); 111 } 112 113 listTablesNames(): string[] { 114 return this.listTables().map((t) => t.name); 115 } 116 117 getModuleForTable(tableName: string): SqlModule | undefined { 118 for (const module of this.modules) { 119 for (const dataObj of module.dataObjects) { 120 if (dataObj.name == tableName) { 121 return module; 122 } 123 } 124 } 125 return undefined; 126 } 127 128 getSqlTableDescription(tableName: string): SqlTableDescription | undefined { 129 for (const module of this.modules) { 130 for (const dataObj of module.dataObjects) { 131 if (dataObj.name == tableName) { 132 return module.getSqlTableDescription(tableName); 133 } 134 } 135 } 136 return undefined; 137 } 138} 139 140export class StdlibModuleImpl implements SqlModule { 141 readonly includeKey: string; 142 readonly dataObjects: SqlTable[]; 143 readonly functions: SqlFunction[]; 144 readonly tableFunctions: SqlTableFunction[]; 145 readonly macros: SqlMacro[]; 146 147 constructor(docs: DocsModuleSchemaType) { 148 this.includeKey = docs.module_name; 149 150 const neededInclude = this.includeKey.startsWith('prelude') 151 ? undefined 152 : this.includeKey; 153 this.dataObjects = docs.data_objects.map( 154 (json) => new SqlTableImpl(json, neededInclude), 155 ); 156 157 this.functions = docs.functions.map((json) => new StdlibFunctionImpl(json)); 158 this.tableFunctions = docs.table_functions.map( 159 (json) => new StdlibTableFunctionImpl(json), 160 ); 161 this.macros = docs.macros.map((json) => new StdlibMacroImpl(json)); 162 } 163 164 getTable(tableName: string): SqlTable | undefined { 165 for (const obj of this.dataObjects) { 166 if (obj.name == tableName) { 167 return obj; 168 } 169 } 170 return undefined; 171 } 172 173 getSqlTableDescription(tableName: string): SqlTableDescription | undefined { 174 const sqlTable = this.getTable(tableName); 175 if (sqlTable === undefined) { 176 return undefined; 177 } 178 return { 179 imports: [this.includeKey], 180 name: sqlTable.name, 181 columns: sqlTable.getTableColumns(), 182 }; 183 } 184} 185 186class StdlibMacroImpl implements SqlMacro { 187 readonly name: string; 188 readonly summaryDesc: string; 189 readonly description: string; 190 readonly args: SqlArgument[]; 191 readonly returnType: string; 192 193 constructor(docs: DocsMacroSchemaType) { 194 this.name = docs.name; 195 this.summaryDesc = docs.summary_desc; 196 this.description = docs.desc; 197 this.returnType = docs.return_type; 198 this.args = []; 199 this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json)); 200 } 201} 202 203class StdlibTableFunctionImpl implements SqlTableFunction { 204 readonly name: string; 205 readonly summaryDesc: string; 206 readonly description: string; 207 readonly args: SqlArgument[]; 208 readonly returnCols: SqlColumn[]; 209 210 constructor(docs: DocsTableFunctionSchemaType) { 211 this.name = docs.name; 212 this.summaryDesc = docs.summary_desc; 213 this.description = docs.desc; 214 this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json)); 215 this.returnCols = docs.cols.map((json) => new StdlibColumnImpl(json)); 216 } 217} 218 219class StdlibFunctionImpl implements SqlFunction { 220 readonly name: string; 221 readonly summaryDesc: string; 222 readonly description: string; 223 readonly args: SqlArgument[]; 224 readonly returnType: string; 225 readonly returnDesc: string; 226 227 constructor(docs: DocsFunctionSchemaType) { 228 this.name = docs.name; 229 this.summaryDesc = docs.summary_desc; 230 this.description = docs.desc; 231 this.returnType = docs.return_type; 232 this.returnDesc = docs.return_desc; 233 this.args = docs.args.map((json) => new StdlibFunctionArgImpl(json)); 234 } 235} 236 237class SqlTableImpl implements SqlTable { 238 name: string; 239 includeKey?: string; 240 description: string; 241 type: string; 242 columns: SqlColumn[]; 243 idColumn: SqlColumn | undefined; 244 linkedIdColumns: SqlColumn[]; 245 joinIdColumns: SqlColumn[]; 246 247 constructor(docs: DocsDataObjectSchemaType, includeKey: string | undefined) { 248 this.name = docs.name; 249 this.includeKey = includeKey; 250 this.description = docs.desc; 251 this.type = docs.type; 252 this.columns = docs.cols.map((json) => new StdlibColumnImpl(json)); 253 254 this.linkedIdColumns = []; 255 this.joinIdColumns = []; 256 for (const c of this.columns) { 257 if (c.type.name === 'id') { 258 this.idColumn = c; 259 continue; 260 } 261 if (c.type.shortName === 'id') { 262 this.linkedIdColumns.push(c); 263 continue; 264 } 265 if (c.type.shortName === 'joinid') { 266 this.joinIdColumns.push(c); 267 continue; 268 } 269 } 270 } 271 272 getIdColumns(): SqlColumn[] { 273 return this.columns.filter((c) => c.type.shortName === 'id'); 274 } 275 276 getJoinIdColumns(): SqlColumn[] { 277 return this.columns.filter((c) => c.type.shortName === 'joinid'); 278 } 279 280 getIdTables(): TableAndColumn[] { 281 return this.getIdColumns() 282 .map((c) => c.type.tableAndColumn) 283 .filter((tAndC) => tAndC !== undefined) as TableAndColumn[]; 284 } 285 286 getJoinIdTables(): TableAndColumn[] { 287 return this.getJoinIdColumns() 288 .map((c) => c.type.tableAndColumn) 289 .filter((tAndC) => tAndC !== undefined) as TableAndColumn[]; 290 } 291 292 getTableColumns(): TableColumn[] { 293 return this.columns.map((col) => 294 createTableColumnFromPerfettoSql(col, this.name), 295 ); 296 } 297} 298 299class StdlibColumnImpl implements SqlColumn { 300 name: string; 301 type: SqlType; 302 description: string; 303 304 constructor(docs: DocsArgOrColSchemaType) { 305 this.type = { 306 name: docs.type.toLowerCase(), 307 shortName: docs.type.split('(')[0].toLowerCase(), 308 tableAndColumn: 309 docs.table && docs.column 310 ? new TableAndColumnImpl( 311 docs.table.toLowerCase(), 312 docs.column.toLowerCase(), 313 ) 314 : undefined, 315 }; 316 this.description = docs.desc; 317 this.name = docs.name; 318 } 319} 320 321class StdlibFunctionArgImpl implements SqlArgument { 322 name: string; 323 description: string; 324 type: string; 325 326 constructor(docs: DocsArgOrColSchemaType) { 327 this.type = docs.type; 328 this.description = docs.desc; 329 this.name = docs.name; 330 } 331} 332 333export class TableAndColumnImpl implements TableAndColumn { 334 table: string; 335 column: string; 336 constructor(table: string, column: string) { 337 this.table = table; 338 this.column = column; 339 } 340 isEqual(o: TableAndColumn): boolean { 341 return o.table === this.table && o.column === this.column; 342 } 343} 344 345const ARG_OR_COL_SCHEMA = z.object({ 346 name: z.string(), 347 type: z.string(), 348 desc: z.string(), 349 table: z.string().nullable(), 350 column: z.string().nullable(), 351}); 352type DocsArgOrColSchemaType = z.infer<typeof ARG_OR_COL_SCHEMA>; 353 354const DATA_OBJECT_SCHEMA = z.object({ 355 name: z.string(), 356 desc: z.string(), 357 summary_desc: z.string(), 358 type: z.string(), 359 cols: z.array(ARG_OR_COL_SCHEMA), 360}); 361type DocsDataObjectSchemaType = z.infer<typeof DATA_OBJECT_SCHEMA>; 362 363const FUNCTION_SCHEMA = z.object({ 364 name: z.string(), 365 desc: z.string(), 366 summary_desc: z.string(), 367 args: z.array(ARG_OR_COL_SCHEMA), 368 return_type: z.string(), 369 return_desc: z.string(), 370}); 371type DocsFunctionSchemaType = z.infer<typeof FUNCTION_SCHEMA>; 372 373const TABLE_FUNCTION_SCHEMA = z.object({ 374 name: z.string(), 375 desc: z.string(), 376 summary_desc: z.string(), 377 args: z.array(ARG_OR_COL_SCHEMA), 378 cols: z.array(ARG_OR_COL_SCHEMA), 379}); 380type DocsTableFunctionSchemaType = z.infer<typeof TABLE_FUNCTION_SCHEMA>; 381 382const MACRO_SCHEMA = z.object({ 383 name: z.string(), 384 desc: z.string(), 385 summary_desc: z.string(), 386 return_desc: z.string(), 387 return_type: z.string(), 388 args: z.array(ARG_OR_COL_SCHEMA), 389}); 390type DocsMacroSchemaType = z.infer<typeof MACRO_SCHEMA>; 391 392const MODULE_SCHEMA = z.object({ 393 module_name: z.string(), 394 data_objects: z.array(DATA_OBJECT_SCHEMA), 395 functions: z.array(FUNCTION_SCHEMA), 396 table_functions: z.array(TABLE_FUNCTION_SCHEMA), 397 macros: z.array(MACRO_SCHEMA), 398}); 399type DocsModuleSchemaType = z.infer<typeof MODULE_SCHEMA>; 400 401const PACKAGE_SCHEMA = z.object({ 402 name: z.string(), 403 modules: z.array(MODULE_SCHEMA), 404}); 405type DocsPackageSchemaType = z.infer<typeof PACKAGE_SCHEMA>; 406 407export const SQL_MODULES_DOCS_SCHEMA = z.array(PACKAGE_SCHEMA); 408export type SqlModulesDocsSchema = z.infer<typeof SQL_MODULES_DOCS_SCHEMA>; 409