LCOV - code coverage report
Current view: top level - dashql-app/src/connections/catalog - catalog_sql_generator.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 107 107
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : 
       3            1 : export const DEFAULT_DATABASE_NAME = 'hyper';
       4              : 
       5              : /// Column metadata for SQL generation
       6              : export interface ColumnMetadata {
       7              :     name: string;
       8              :     ordinalPosition: number;
       9              :     dataType?: string | null;
      10              : }
      11              : 
      12              : /// Table metadata for SQL generation
      13              : export interface TableMetadata {
      14              :     tableName: string;
      15              :     columns: ColumnMetadata[];
      16              : }
      17              : 
      18              : /// Schema metadata for SQL generation
      19              : export interface SchemaMetadata {
      20              :     databaseName: string | null;
      21              :     schemaName: string;
      22              :     tables: TableMetadata[];
      23              : }
      24              : 
      25            1 : export function quoteIdentifier(identifier: string): string {
      26            1 :     const escaped = identifier.replace(/"/g, '""');
      27            1 :     return `"${escaped}"`;
      28            1 : }
      29              : 
      30              : /// Maps SQL data types to simplified DashQL types.
      31              : /// Falls back to VARCHAR for unknown types.
      32            1 : export function mapDataType(sqlType: string | null | undefined): string {
      33            1 :     if (!sqlType) {
      34            1 :         return 'VARCHAR';
      35              :     }
      36              : 
      37            1 :     const normalized = sqlType.toLowerCase().trim();
      38              : 
      39              :     // Integer types
      40            1 :     if (normalized.includes('int') || normalized.includes('serial') || normalized.includes('bigserial')) {
      41            1 :         return 'INTEGER';
      42              :     }
      43              : 
      44              :     // Floating point types
      45            1 :     if (normalized.includes('float') || normalized.includes('double') ||
      46            1 :         normalized.includes('real') || normalized.includes('numeric') ||
      47            1 :         normalized.includes('decimal')) {
      48            1 :         return 'FLOAT';
      49              :     }
      50              : 
      51              :     // Boolean types
      52            1 :     if (normalized.includes('bool')) {
      53            1 :         return 'BOOLEAN';
      54              :     }
      55              : 
      56              :     // Date/time types
      57            1 :     if (normalized.includes('date') && !normalized.includes('time')) {
      58            1 :         return 'DATE';
      59              :     }
      60            1 :     if (normalized.includes('timestamp') || normalized.includes('datetime')) {
      61            1 :         return 'TIMESTAMP';
      62              :     }
      63              : 
      64              :     // Default to VARCHAR for text and unknown types
      65            1 :     return 'VARCHAR';
      66            1 : }
      67              : 
      68              : /// Generates a qualified table name (database.schema.table).
      69            1 : export function generateQualifiedTableName(
      70            1 :     databaseName: string | null | undefined,
      71            1 :     schemaName: string,
      72            1 :     tableName: string
      73            1 : ): string {
      74            1 :     const db = databaseName || DEFAULT_DATABASE_NAME;
      75            1 :     return `${quoteIdentifier(db)}.${quoteIdentifier(schemaName)}.${quoteIdentifier(tableName)}`;
      76            1 : }
      77              : 
      78              : /// Generates a CREATE TABLE SQL statement for a single table.
      79            1 : export function generateCreateTableSQL(
      80            1 :     databaseName: string | null | undefined,
      81            1 :     schemaName: string | null,
      82            1 :     tableName: string,
      83            1 :     columns: ColumnMetadata[]
      84            1 : ): string {
      85            1 :     const qualifiedName = schemaName == null
      86            1 :         ? quoteIdentifier(tableName)
      87            1 :         : generateQualifiedTableName(databaseName, schemaName, tableName);
      88              : 
      89              :     // Sort columns by ordinal position
      90            1 :     const sortedColumns = [...columns].sort((a, b) => a.ordinalPosition - b.ordinalPosition);
      91              : 
      92              :     // Generate column definitions
      93            1 :     const columnDefs = sortedColumns.map(col => {
      94            1 :         const colName = quoteIdentifier(col.name);
      95            1 :         const colType = mapDataType(col.dataType);
      96            1 :         return `    ${colName} ${colType}`;
      97            1 :     }).join(',\n');
      98              : 
      99            1 :     return `CREATE TABLE ${qualifiedName} (\n${columnDefs}\n);`;
     100            1 : }
     101              : 
     102              : /// Generates CREATE TABLE SQL statements for all tables in a schema.
     103            1 : export function generateSchemaSQL(
     104            1 :     databaseName: string | null | undefined,
     105            1 :     schemaName: string,
     106            1 :     tables: Map<string, ColumnMetadata[]>
     107            1 : ): string {
     108            1 :     const sqlStatements: string[] = [];
     109              : 
     110              :     // Sort tables by name for consistent output
     111            1 :     const sortedTableNames = Array.from(tables.keys()).sort();
     112              : 
     113            1 :     for (const tableName of sortedTableNames) {
     114            1 :         const columns = tables.get(tableName)!;
     115            1 :         const sql = generateCreateTableSQL(databaseName, schemaName, tableName, columns);
     116            1 :         sqlStatements.push(sql);
     117            1 :     }
     118              : 
     119            1 :     return sqlStatements.join('\n\n');
     120            1 : }
     121              : 
     122              : /// Generates unqualified CREATE TABLE statements for all supplied tables.
     123            1 : export function generateUnqualifiedSchemaSQL(tables: Map<string, ColumnMetadata[]>): string {
     124            1 :     return Array.from(tables.keys())
     125            1 :         .sort()
     126            1 :         .map(tableName => generateCreateTableSQL(null, null, tableName, tables.get(tableName)!))
     127            1 :         .join('\n\n');
     128            1 : }
     129              : 
     130            1 : export enum CatalogSource {
     131            1 :     InformationSchema = 0,
     132            1 :     PgClass = 1,
     133            1 :     SalesforceMetadataApi = 2,
     134            1 :     Hyper = 3,
     135            1 :     Unknown = 4,
     136            1 : }
     137              : 
     138            1 : export function generateCatalogScriptHeaderForSource(source: string, updatedAt: Date = new Date()): string {
     139            1 :     return `-- DashQL Connection Relations.
     140              : -- This file is auto-generated and can only be updated through a catalog refresh.
     141              : --
     142            1 : -- Catalog Source: ${source}
     143            1 : -- Last Refresh: ${updatedAt.toISOString()}
     144              : 
     145              : `;
     146            1 : }
     147              : 
     148              : /// Generates a SQL comment header for catalog scripts with the update timestamp and method.
     149            1 : export function generateCatalogScriptHeader(method: CatalogSource, updatedAt: Date = new Date()): string {
     150            1 :     let methodStr: string;
     151            1 :     switch (method) {
     152            1 :         case CatalogSource.InformationSchema: methodStr = 'SQL information_schema'; break;
     153            1 :         case CatalogSource.PgClass: methodStr = 'SQL pg_class'; break;
     154            1 :         case CatalogSource.SalesforceMetadataApi: methodStr = 'Salesforce Connect /ssot/metadata API'; break;
     155            1 :         case CatalogSource.Hyper: methodStr = 'Hyper attached databases'; break;
     156            1 :         default: methodStr = '-'; break;
     157            1 :     }
     158            1 :     return generateCatalogScriptHeaderForSource(methodStr, updatedAt);
     159            1 : }
     160              : 
     161              : /// Generates SQL for multiple schemas.
     162            1 : export function generateCatalogSQL(schemas: SchemaMetadata[]): string {
     163            1 :     const sqlStatements: string[] = [];
     164              : 
     165            1 :     for (const schema of schemas) {
     166            1 :         const tablesMap = new Map<string, ColumnMetadata[]>();
     167            1 :         for (const table of schema.tables) {
     168            1 :             tablesMap.set(table.tableName, table.columns);
     169              :         }
     170              : 
     171            1 :         const schemaSQL = generateSchemaSQL(
     172            1 :             schema.databaseName,
     173            1 :             schema.schemaName,
     174            1 :             tablesMap
     175            1 :         );
     176              : 
     177            1 :         if (schemaSQL.length > 0) {
     178            1 :             sqlStatements.push(schemaSQL);
     179              :         }
     180            1 :     }
     181              : 
     182            1 :     return sqlStatements.join('\n\n');
     183            1 : }
        

Generated by: LCOV version 2.0-1