blob: 6f1604cec7592dd771940ce50395257dc1e46d5b [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.
import {z} from 'zod';
import {LONG, STR} from '../../trace_processor/query_result';
import type {Trace} from '../../public/trace';
import {getOrCreate} from '../../base/utils';
import type {SqlModules} from './sql_modules';
import {
SQL_MODULES_DOCS_SCHEMA,
SqlModulesImpl,
type ARG_OR_COL_SCHEMA,
type DATA_OBJECT_SCHEMA,
type FUNCTION_SCHEMA,
type TABLE_FUNCTION_SCHEMA,
type MACRO_SCHEMA,
} from './sql_modules_impl';
// Schema for the metadata-only JSON generated by gen_stdlib_docs_json.py.
// Contains only what the TP table functions don't expose: tags, includes,
// data availability checks, and table importance.
const TABLE_META_SCHEMA = z.object({
importance: z.enum(['core', 'high', 'mid', 'low']).nullish(),
data_check_sql: z.string().nullish(),
});
const MODULE_META_SCHEMA = z.object({
tags: z.array(z.string()),
includes: z.array(z.string()),
data_check_sql: z.string().nullish(),
tables: z.record(z.string(), TABLE_META_SCHEMA).optional(),
});
export const STDLIB_METADATA_SCHEMA = z.record(z.string(), MODULE_META_SCHEMA);
export type StdlibMetadata = z.infer<typeof STDLIB_METADATA_SCHEMA>;
// Types derived from the schema so any field-name mismatch is a compile error.
type ArgOrCol = z.infer<typeof ARG_OR_COL_SCHEMA>;
type DataObject = z.infer<typeof DATA_OBJECT_SCHEMA>;
type FnObject = z.infer<typeof FUNCTION_SCHEMA>;
type TblFnObject = z.infer<typeof TABLE_FUNCTION_SCHEMA>;
type MacroObject = z.infer<typeof MACRO_SCHEMA>;
// Schema for a single serialized entry in the cols/args JSON blobs from TP.
const RAW_ENTRY_SCHEMA = z.object({
name: z.string(),
type: z.string(),
description: z.string(),
});
type RawEntry = z.infer<typeof RAW_ENTRY_SCHEMA>;
function parseEntries(json: string): RawEntry[] {
try {
const parsed: unknown = JSON.parse(json);
if (!Array.isArray(parsed)) return [];
return parsed.flatMap((e) => {
const r = RAW_ENTRY_SCHEMA.safeParse(e);
return r.success ? [r.data] : [];
});
} catch {
return [];
}
}
// Extracts the referenced table and column from a long type of the form
// "TYPE(table.column)" (e.g. "JOINID(thread.utid)"). Returns [null, null]
// when the type carries no such reference. Mirrors the parsing the Python
// generator used to do over long_type.
const LONG_TYPE_RE = /^[A-Z]*\(([a-z_]*)\.([a-z_]*)\)/;
function toArgOrCol(e: RawEntry): ArgOrCol {
const m = LONG_TYPE_RE.exec(e.type);
return {
name: e.name,
type: e.type,
desc: e.description,
table: m ? m[1] : null,
column: m ? m[2] : null,
};
}
function summaryDesc(desc: string): string {
const trimmed = desc.replace(/\n/g, ' ');
const dot = trimmed.indexOf('. ');
if (dot !== -1) return trimmed.slice(0, dot);
const lastDot = trimmed.lastIndexOf('.');
if (lastDot !== -1) return trimmed.slice(0, lastDot);
return trimmed;
}
// Queries the TP's __intrinsic_stdlib_* table functions and merges with the
// pre-generated metadata JSON to build a SqlModules object.
// Uses 4 parallel queries (one per entity type) instead of per-module queries.
export async function loadSqlModulesFromTp(
trace: Trace,
metadata: StdlibMetadata,
): Promise<SqlModules> {
const engine = trace.engine;
const [modsResult, tablesResult, fnsResult, macrosResult] = await Promise.all(
[
engine.query('SELECT module, package FROM __intrinsic_stdlib_modules()'),
engine.query(
`SELECT module, name, type, description, exposed, cols
FROM __intrinsic_stdlib_tables('*')`,
),
engine.query(
`SELECT module, name, description, exposed, is_table_function,
return_type, return_description, args, cols
FROM __intrinsic_stdlib_functions('*')`,
),
engine.query(
`SELECT module, name, description, exposed,
return_type, return_description, args
FROM __intrinsic_stdlib_macros('*')`,
),
],
);
// Build package → [module] map.
const packageMap = new Map<string, string[]>();
const modsIter = modsResult.iter({module: STR, package: STR});
for (; modsIter.valid(); modsIter.next()) {
getOrCreate(packageMap, modsIter.package, () => []).push(modsIter.module);
}
// Group tables by module.
const tablesByModule = new Map<string, DataObject[]>();
const tIter = tablesResult.iter({
module: STR,
name: STR,
type: STR,
description: STR,
exposed: LONG,
cols: STR,
});
for (; tIter.valid(); tIter.next()) {
if (!tIter.exposed) continue;
const modKey = tIter.module;
const tableMeta = metadata[modKey]?.tables?.[tIter.name];
const desc = tIter.description;
getOrCreate(tablesByModule, modKey, () => []).push({
name: tIter.name,
desc,
summary_desc: summaryDesc(desc),
type: tIter.type,
importance: tableMeta?.importance ?? null,
data_check_sql: tableMeta?.data_check_sql ?? null,
cols: parseEntries(tIter.cols).map((e) => toArgOrCol(e)),
});
}
// Group functions and table functions by module.
const fnsByModule = new Map<string, FnObject[]>();
const tblFnsByModule = new Map<string, TblFnObject[]>();
const fIter = fnsResult.iter({
module: STR,
name: STR,
description: STR,
exposed: LONG,
is_table_function: LONG,
return_type: STR,
return_description: STR,
args: STR,
cols: STR,
});
for (; fIter.valid(); fIter.next()) {
if (!fIter.exposed) continue;
const modKey = fIter.module;
const desc = fIter.description;
const args = parseEntries(fIter.args).map((e) => toArgOrCol(e));
if (fIter.is_table_function) {
getOrCreate(tblFnsByModule, modKey, () => []).push({
name: fIter.name,
desc,
summary_desc: summaryDesc(desc),
args,
cols: parseEntries(fIter.cols).map((e) => toArgOrCol(e)),
});
} else {
getOrCreate(fnsByModule, modKey, () => []).push({
name: fIter.name,
desc,
summary_desc: summaryDesc(desc),
return_type: fIter.return_type,
return_desc: fIter.return_description,
args,
});
}
}
// Group macros by module.
const macrosByModule = new Map<string, MacroObject[]>();
const mIter = macrosResult.iter({
module: STR,
name: STR,
description: STR,
exposed: LONG,
return_type: STR,
return_description: STR,
args: STR,
});
for (; mIter.valid(); mIter.next()) {
if (!mIter.exposed) continue;
const modKey = mIter.module;
const desc = mIter.description;
getOrCreate(macrosByModule, modKey, () => []).push({
name: mIter.name,
desc,
summary_desc: summaryDesc(desc),
return_type: mIter.return_type,
return_desc: mIter.return_description,
args: parseEntries(mIter.args).map((e) => toArgOrCol(e)),
});
}
// Assemble the final docs structure.
const docs = [];
for (const [pkgName, moduleKeys] of packageMap) {
const pkgModules = [];
for (const modKey of moduleKeys) {
const meta = metadata[modKey];
pkgModules.push({
module_name: modKey,
tags: meta?.tags ?? [],
includes: meta?.includes ?? [],
data_check_sql: meta?.data_check_sql ?? null,
data_objects: tablesByModule.get(modKey) ?? [],
functions: fnsByModule.get(modKey) ?? [],
table_functions: tblFnsByModule.get(modKey) ?? [],
macros: macrosByModule.get(modKey) ?? [],
});
}
docs.push({name: pkgName, modules: pkgModules});
}
const parsed = SQL_MODULES_DOCS_SCHEMA.safeParse(docs);
if (!parsed.success) {
throw new Error(
`Failed to parse stdlib docs from TP: ${parsed.error.message}`,
);
}
return new SqlModulesImpl(trace, parsed.data);
}