LCOV - code coverage report
Current view: top level - dashql-app/src/connections/salesforce - salesforce_catalog_update.ts Coverage Total Hit
Test: DashQL 915828b Lines: 96.6 % 87 84
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : import * as connection from '@ankoh/dashql-jsonschema/connection.js';
       3              : 
       4              : import { SalesforceApiClientInterface, type SalesforceMetadataProgress } from './salesforce_api_client.js';
       5              : import { getSalesforceDataSpace } from './salesforce_api_client.js';
       6              : import { SalesforceConnectionStateDetails } from './salesforce_connection_state.js';
       7              : import { generateSchemaSQL, generateCatalogScriptHeader, CatalogSource, type ColumnMetadata } from '../catalog/catalog_sql_generator.js';
       8              : import { LoggerLike } from '../../platform/logger/logger.js';
       9              : import { fetchPrefetchedHyperFunctions } from '../catalog/prefetched_hyper_functions.js';
      10              : import { beginCatalogBatch, publishCatalogBatch, type CatalogBatchOptions } from '../catalog/catalog_batch.js';
      11              : import type { AttachedDatabase } from '../hyper/hyperdb_grpc_client.js';
      12              : 
      13            1 : export const SALESFORCE_CATALOG_DATABASE = 'lakehouse';
      14            1 : export const SALESFORCE_CATALOG_SCHEMA = 'public';
      15              : /// The Hyper Cloud database of SIDB connections that don't name one
      16            1 : export const SIDB_DEFAULT_DATABASE_NAME = 'default';
      17              : 
      18              : /// The Hyper Cloud database that SIDB connections attach as the catalog database
      19            1 : export function getSidbAttachedDatabase(params: connection.SalesforceConnectionParams | undefined): AttachedDatabase {
      20            1 :     const databaseName = params?.databaseName || SIDB_DEFAULT_DATABASE_NAME;
      21            1 :     return { path: `hyper.cloud:${databaseName}`, alias: SALESFORCE_CATALOG_DATABASE };
      22            1 : }
      23              : 
      24              : export interface ResolvedSalesforceCatalog {
      25              :     tables: Map<string, ColumnMetadata[]>;
      26              :     functionsSQL: string;
      27              :     tableCount: number;
      28              :     columnCount: number;
      29              : }
      30              : 
      31            1 : export async function resolveSalesforceCatalog(
      32            1 :     logger: LoggerLike,
      33            1 :     coreAccessToken: connection.SalesforceCoreAccessToken,
      34            1 :     dataCloudAccessToken: connection.SalesforceDataCloudAccessToken,
      35            1 :     api: SalesforceApiClientInterface,
      36            1 :     signal: AbortSignal,
      37            1 :     onProgress?: (progress: SalesforceMetadataProgress) => void,
      38            1 : ): Promise<ResolvedSalesforceCatalog> {
      39            1 :     if (!coreAccessToken.accessToken || !coreAccessToken.instanceUrl) {
      40            0 :         throw new Error('Salesforce core access token is missing');
      41              :     }
      42            1 :     const dataSpace = getSalesforceDataSpace(dataCloudAccessToken);
      43            1 :     logger.info('Resolving Salesforce catalog metadata', { dataSpace }, 'salesforce_catalog');
      44            1 :     const metadataStartedAt = performance.now();
      45            1 :     const [metadata, functionsSQL] = await Promise.all([
      46            1 :         api.getDataCloudMetadata(coreAccessToken, dataSpace, signal, onProgress),
      47            1 :         fetchPrefetchedHyperFunctions(signal),
      48            1 :     ]);
      49            1 :     logger.info('Received Salesforce catalog metadata', {
      50            1 :         dataSpace,
      51            1 :         entities: (metadata.metadata?.length ?? 0).toString(),
      52            1 :         durationMs: (performance.now() - metadataStartedAt).toFixed(2),
      53            1 :     }, 'salesforce_catalog');
      54              : 
      55            1 :     const tables = new Map<string, ColumnMetadata[]>();
      56            1 :     for (const entry of metadata.metadata ?? []) {
      57            1 :         tables.set(entry.name, (entry.fields ?? []).map((field, ordinalPosition) => ({
      58            1 :             name: field.name,
      59            1 :             ordinalPosition,
      60            1 :             dataType: field.type ?? null,
      61            1 :         })));
      62              :     }
      63            1 :     const columnCount = Array.from(tables.values()).reduce((total, columns) => total + columns.length, 0);
      64            1 :     if (tables.size === 0 || columnCount === 0) {
      65            1 :         throw new Error('Salesforce metadata returned no usable catalog relations');
      66              :     }
      67            1 :     return { tables, functionsSQL, tableCount: tables.size, columnCount };
      68            1 : }
      69              : 
      70            1 : export async function updateSalesforceCatalog(
      71            1 :     logger: LoggerLike,
      72            1 :     conn: SalesforceConnectionStateDetails,
      73            1 :     catalog: dashql.DashQLCatalog,
      74            1 :     dql: dashql.DashQL,
      75            1 :     catalogRelationScript: dashql.DashQLScript,
      76            1 :     catalogFunctionScript: dashql.DashQLScript,
      77            1 :     api: SalesforceApiClientInterface,
      78            1 :     abortController: AbortController,
      79            1 :     options: CatalogBatchOptions = {},
      80            1 : ): Promise<dashql.DashQLScript> {
      81            1 :     options = { ...options, abortSignal: abortController.signal };
      82            1 :     const validate = beginCatalogBatch(catalog, options);
      83            1 :     const coreAccessToken = conn.proto.oauthState?.coreAccessToken;
      84            1 :     if (!coreAccessToken?.accessToken || !coreAccessToken.instanceUrl) {
      85            0 :         throw new Error(`Salesforce core access token is missing`);
      86              :     }
      87            1 :     if (!conn.proto.oauthState?.dataCloudAccessToken) {
      88            0 :         throw new Error('Salesforce data cloud access token is missing');
      89              :     }
      90            1 :     const dataSpace = getSalesforceDataSpace(conn.proto.oauthState.dataCloudAccessToken);
      91            1 :     const { tables, functionsSQL, columnCount } = await resolveSalesforceCatalog(
      92            1 :         logger,
      93            1 :         coreAccessToken,
      94            1 :         conn.proto.oauthState.dataCloudAccessToken,
      95            1 :         api,
      96            1 :         abortController.signal,
      97            1 :     );
      98            1 :     validate();
      99              : 
     100              :     // Generate SQL from metadata
     101            1 :     const header = generateCatalogScriptHeader(CatalogSource.SalesforceMetadataApi);
     102            1 :     const catalogSQL = generateSchemaSQL(SALESFORCE_CATALOG_DATABASE, SALESFORCE_CATALOG_SCHEMA, tables);
     103            1 :     logger.info("Generated Salesforce catalog script", {
     104            1 :         dataSpace,
     105            1 :         tables: tables.size.toString(),
     106            1 :         columns: columnCount.toString(),
     107            1 :         scriptBytes: new TextEncoder().encode(catalogSQL).byteLength.toString(),
     108            1 :     }, "salesforce_catalog");
     109              : 
     110            1 :     const [relations, functions] = await publishCatalogBatch(dql, catalog, [
     111            1 :         { script: catalogRelationScript, text: `${header}${catalogSQL}` },
     112            1 :         { script: catalogFunctionScript, text: functionsSQL, requireFunctions: true },
     113            1 :     ], options, validate);
     114            1 :     logger.info("Loaded Salesforce catalog script", {
     115            1 :         dataSpace,
     116            1 :         tables: relations.tables.toString(),
     117            1 :         functions: functions.functions.toString(),
     118            1 :     }, "salesforce_catalog");
     119              : 
     120            1 :     return catalogRelationScript;
     121            1 : }
        

Generated by: LCOV version 2.0-1