LCOV - code coverage report
Current view: top level - dashql-app/src/compute/tasks - system_columns.ts Coverage Total Hit
Test: DashQL 915828b Lines: 93.5 % 92 86
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as arrow from 'apache-arrow';
       2              : 
       3              : import { DataFrame, generateTableName } from '../data_frame.js';
       4              : import { TableAggregation, ORDINAL_COLUMN, STRING_COLUMN, LIST_COLUMN, ColumnGroup, SKIPPED_COLUMN, SystemColumnComputationTask, ROWNUMBER_COLUMN } from '../computation_types.js';
       5              : import { LoggableException, LoggerLike, stringifyError } from '../../platform/logger/logger.js';
       6              : import { SQLFrame } from '../sql/sqlframe_builder.js';
       7              : import { ComputeQueryExecution, executeDirectly } from '../computation_tasks.js';
       8              : import { getToNumericFn, isIntegerType, createUniqueColumnName } from '../arrow_columns.js';
       9              : 
      10            1 : const LOG_CTX = "compute";
      11              : 
      12            1 : export async function computeSystemColumns(task: SystemColumnComputationTask, logger: LoggerLike, executeQuery: ComputeQueryExecution = executeDirectly): Promise<[arrow.Table, DataFrame, ColumnGroup[]]> {
      13            1 :     try {
      14            1 :         const [sqlFrame, columnGroups] = buildSystemColumnSQLFrame(task.inputTable.schema, task.columnEntries, task.inputDataFrame.tableName, task.rowNumberFieldName, task.tableAggregate);
      15              : 
      16            1 :         logger.info("Computing system columns", {
      17            1 :             "version": task.tableVersion.toString(),
      18            1 :         }, LOG_CTX);
      19            1 :         const transformStart = performance.now();
      20            1 :         const tableName = generateTableName("__syscols");
      21            1 :         const sql = sqlFrame.toSQL();
      22            1 :         const transformed = await executeQuery(sql, clientContext => DataFrame.fromSQL(task.inputDataFrame.database, sql, tableName, undefined, clientContext));
      23            1 :         const transformEnd = performance.now();
      24            1 :         const transformedTable = await transformed.readTable({ orderBy: [{ field: task.rowNumberFieldName }] });
      25            1 :         logger.info("Computed system columns", {
      26            1 :             "version": task.tableVersion.toString(),
      27            1 :             "duration": Math.floor(transformEnd - transformStart).toString(),
      28            1 :             "sql": sql,
      29            1 :         }, LOG_CTX);
      30              : 
      31            1 :         let rowNumColumnName: string | null = null;
      32            1 :         for (let i = 0; i < columnGroups.length; ++i) {
      33            1 :             const group = columnGroups[i];
      34            1 :             switch (group.type) {
      35            1 :                 case ROWNUMBER_COLUMN:
      36            1 :                     rowNumColumnName = group.value.rowNumberFieldName;
      37              :                     break;
      38            1 :             }
      39            1 :         }
      40            1 :         if (!rowNumColumnName) {
      41            0 :             throw new LoggableException("Missing rownum column group", {}, LOG_CTX);
      42              :         }
      43            1 :         return [transformedTable, transformed, columnGroups];
      44              : 
      45            1 :     } catch (error: any) {
      46            0 :         if (error instanceof LoggableException) {
      47            0 :             throw error;
      48              :         } else {
      49            0 :             throw new LoggableException("Failed to precompute column", { "error": stringifyError(error) }, LOG_CTX);
      50              :         }
      51            0 :     }
      52            1 : }
      53              : 
      54            1 : function buildSystemColumnSQLFrame(schema: arrow.Schema, columns: ColumnGroup[], inputTableName: string, rowNumberFieldName: string, tableAggregate: TableAggregation): [SQLFrame, ColumnGroup[]] {
      55            1 :     let fieldNames = new Set<string>([rowNumberFieldName]);
      56            1 :     for (const field of schema.fields) {
      57            1 :         fieldNames.add(field.name);
      58              :     }
      59              : 
      60              :     const rowNumberGridColumn: ColumnGroup = {
      61            1 :         type: ROWNUMBER_COLUMN,
      62            1 :         value: {
      63            1 :             rowNumberFieldName: rowNumberFieldName
      64            1 :         }
      65              :     };
      66            1 :     let gridColumns: ColumnGroup[] = [
      67            1 :         rowNumberGridColumn,
      68            1 :         ...columns
      69            1 :     ];
      70              : 
      71            1 :     let frame = SQLFrame.from(inputTableName);
      72              : 
      73            1 :     for (let i = 1; i <= columns.length; ++i) {
      74            1 :         let column = gridColumns[i];
      75            1 :         switch (column.type) {
      76            0 :             case ROWNUMBER_COLUMN:
      77            1 :             case SKIPPED_COLUMN:
      78            1 :                 break;
      79            1 :             case ORDINAL_COLUMN: {
      80            1 :                 const binFieldName = createUniqueColumnName(`_${i}_bin`, fieldNames);
      81            1 :                 frame = frame.binning({
      82            1 :                     fieldName: column.value.inputFieldName,
      83            1 :                     statsTable: tableAggregate.dataFrame.tableName,
      84            1 :                     statsMinField: column.value.statsFields!.minAggregateFieldName!,
      85            1 :                     statsMaxField: column.value.statsFields!.maxAggregateFieldName!,
      86            1 :                     binCount: column.value.binCount,
      87            1 :                     minBinWidth: isIntegerType(column.value.inputFieldType.typeId) ? 1 : undefined,
      88            1 :                     outputAlias: binFieldName,
      89            1 :                     toNumericFn: getToNumericFn(column.value.inputFieldType.typeId),
      90            1 :                 });
      91            1 :                 gridColumns[i] = {
      92            1 :                     type: ORDINAL_COLUMN,
      93            1 :                     value: {
      94            1 :                         ...column.value,
      95            1 :                         binFieldName: binFieldName,
      96            1 :                     }
      97            1 :                 };
      98            1 :                 break;
      99            1 :             }
     100            1 :             case STRING_COLUMN: {
     101            1 :                 const valueFieldName = createUniqueColumnName(`_${i}_id`, fieldNames);
     102            1 :                 frame = frame.valueIdentifier(column.value.inputFieldName, valueFieldName);
     103            1 :                 gridColumns[i] = {
     104            1 :                     type: STRING_COLUMN,
     105            1 :                     value: {
     106            1 :                         ...column.value,
     107            1 :                         valueIdFieldName: valueFieldName
     108            1 :                     }
     109            1 :                 };
     110            1 :                 break;
     111            1 :             }
     112            1 :             case LIST_COLUMN:
     113              :                 // No value identifier for LIST columns: a dense_rank() over float32
     114              :                 // embedding arrays sorts the whole table by full-vector comparison at
     115              :                 // high cost, and nothing consumes the id (no summary is rendered).
     116              :                 // Leaving valueIdFieldName null; the layout guards on `!= null`.
     117              :                 break;
     118            1 :         }
     119            1 :     }
     120              : 
     121            1 :     return [frame, gridColumns];
     122            1 : }
        

Generated by: LCOV version 2.0-1