LCOV - code coverage report
Current view: top level - dashql-app/src/connections/catalog - catalog_query_pg_attribute.ts Coverage Total Hit
Test: DashQL d805a4b Lines: 100.0 % 79 79
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : import * as arrow from "apache-arrow";
       2              : 
       3              : import { QueryExecutor } from '../query_executor.js';
       4              : import { QueryExecutionArgs } from '../query_execution_args.js';
       5              : import { DynamicConnectionDispatch } from '../connection_registry.js';
       6              : import { CATALOG_UPDATE_REGISTER_QUERY } from "../connection_state.js";
       7              : import { QueryType } from '../../query/query_execution_state.js';
       8              : import { catalogTablesOfSchema, quoteIdentifier, relationFilter, type CatalogTable, type ColumnMetadata, type RelationName } from './catalog_sql_generator.js';
       9              : 
      10            1 : export const CATALOG_QUERY_READ_TIMEOUT_MS = 60_000;
      11              : 
      12              : export type PgAttributeColumnsTable = arrow.Table<{
      13              :     table_schema: arrow.Utf8;
      14              :     table_name: arrow.Utf8;
      15              :     column_name: arrow.Utf8;
      16              :     ordinal_position: arrow.Int32;
      17              :     data_type: arrow.Utf8;
      18              : }>;
      19              : 
      20              : /**
      21              :  * The tables of pg_attribute query results.
      22              :  * Builds a hierarchy of schema -> table -> columns.
      23              :  */
      24            1 : export function catalogTablesFromPgAttribute(result: PgAttributeColumnsTable, databaseName: string): CatalogTable[] {
      25              :     // Build hierarchy: schema -> table -> columns
      26            1 :     const schemas = new Map<string, Map<string, ColumnMetadata[]>>();
      27              : 
      28            1 :     for (const batch of result.batches) {
      29            1 :         const colTableSchema = batch.getChild("table_schema")!;
      30            1 :         const colTableName = batch.getChild("table_name")!;
      31            1 :         const colColumnName = batch.getChild("column_name")!;
      32            1 :         const colOrdinalPos = batch.getChild("ordinal_position")!;
      33            1 :         const colDataType = batch.getChild("data_type")!;
      34              : 
      35              :         // Iterate over all rows in the batch
      36            1 :         for (let i = 0; i < batch.numRows; ++i) {
      37            1 :             const tableSchema = colTableSchema.at(i);
      38            1 :             const tableName = colTableName.at(i);
      39            1 :             const columnName = colColumnName.at(i);
      40            1 :             const ordinalPosition = colOrdinalPos.at(i);
      41            1 :             const dataType = colDataType.at(i);
      42              : 
      43            1 :             if (!tableSchema || !columnName || !tableName) {
      44            1 :                 continue;
      45              :             }
      46              : 
      47              :             // Get or create schema entry
      48            1 :             let tableMap = schemas.get(tableSchema);
      49            1 :             if (!tableMap) {
      50            1 :                 tableMap = new Map<string, ColumnMetadata[]>();
      51            1 :                 schemas.set(tableSchema, tableMap);
      52            1 :             }
      53              : 
      54              :             // Get or create table entry
      55            1 :             let columns = tableMap.get(tableName);
      56            1 :             if (!columns) {
      57            1 :                 columns = [];
      58            1 :                 tableMap.set(tableName, columns);
      59            1 :             }
      60              : 
      61              :             // Add column metadata
      62            1 :             columns.push({
      63            1 :                 name: columnName,
      64            1 :                 ordinalPosition: ordinalPosition ?? columns.length,
      65            1 :                 dataType: dataType ?? null,
      66            1 :             });
      67            1 :         }
      68            1 :     }
      69              : 
      70            1 :     return [...schemas].flatMap(([schemaName, tables]) => catalogTablesOfSchema(databaseName, schemaName, tables));
      71            1 : }
      72              : 
      73              : /// What a query of pg_attribute reads
      74              : export interface PgAttributeScope {
      75              :     /// The database to read the catalog of, the default database if absent
      76              :     queryDatabaseName?: string | null;
      77              :     /// The relations, all relations if absent
      78              :     relations?: RelationName[] | null;
      79              : }
      80              : 
      81              : /// The query of the columns of relations
      82            1 : export function pgAttributeQuery(scope: PgAttributeScope = {}): string {
      83            1 :     const pgCatalog = scope.queryDatabaseName == null
      84            1 :         ? 'pg_catalog'
      85            1 :         : `${quoteIdentifier(scope.queryDatabaseName)}.${quoteIdentifier('pg_catalog')}`;
      86            1 :     const relationsFilter = scope.relations != null ? `AND ${relationFilter('n.nspname', 'c.relname', scope.relations)}` : '';
      87              :     // A name without a schema doesn't mean a table of the system
      88            1 :     const systemFilter = scope.relations != null ? `AND n.nspname NOT IN ('pg_catalog', 'information_schema')` : '';
      89            1 :     return `
      90              :         SELECT
      91              :             n.nspname AS table_schema,
      92              :             c.relname AS table_name,
      93              :             a.attname AS column_name,
      94              :             a.attnum AS ordinal_position,
      95              :             t.typname AS data_type
      96            1 :         FROM ${pgCatalog}.pg_class c
      97            1 :         JOIN ${pgCatalog}.pg_namespace n ON n.oid = c.relnamespace
      98            1 :         JOIN ${pgCatalog}.pg_attribute a ON a.attrelid = c.oid
      99            1 :         JOIN ${pgCatalog}.pg_type t ON t.oid = a.atttypid
     100              :         WHERE c.relkind IN ('r', 'v', 'm', 'f', 'p')
     101              :           AND a.attnum > 0
     102              :           AND NOT a.attisdropped
     103            1 :         ${relationsFilter}
     104            1 :         ${systemFilter}
     105              :         ORDER BY n.nspname, c.relname, a.attnum
     106              :     `;
     107            1 : }
     108              : 
     109            1 : export async function queryPgAttribute(
     110            1 :     connectionId: string,
     111            1 :     connectionDispatch: DynamicConnectionDispatch,
     112            1 :     updateId: number,
     113            1 :     executor: QueryExecutor,
     114            1 :     queryDatabaseName: string | null = null,
     115            1 :     abortSignal?: AbortSignal,
     116            1 :     relations: RelationName[] | null = null,
     117            1 : ): Promise<PgAttributeColumnsTable | null> {
     118            1 :     const query = pgAttributeQuery({ queryDatabaseName, relations });
     119              : 
     120              :     const args: QueryExecutionArgs = {
     121            1 :         query: query,
     122            1 :         abortSignal,
     123            1 :         readTimeoutMs: CATALOG_QUERY_READ_TIMEOUT_MS,
     124            1 :         throwOnError: true,
     125            1 :         metadata: {
     126            1 :             queryType: QueryType.CATALOG_QUERY_PG_ATTRIBUTE,
     127            1 :             title: "Query Postgres Schema Columns",
     128            1 :             description: null,
     129            1 :             issuer: "Catalog Update",
     130            1 :             userProvided: false
     131            1 :         }
     132              :     };
     133            1 :     const [queryId, queryExecution] = executor(connectionId, args);
     134            1 :     connectionDispatch(connectionId, {
     135            1 :         type: CATALOG_UPDATE_REGISTER_QUERY,
     136            1 :         value: [updateId, queryId]
     137            1 :     });
     138              : 
     139            1 :     const queryResult = await queryExecution as PgAttributeColumnsTable;
     140            1 :     return queryResult;
     141            1 : }
        

Generated by: LCOV version 2.0-1