LCOV - code coverage report
Current view: top level - dashql-app/src/compute - computation_types.ts Coverage Total Hit
Test: DashQL 6e74948 Lines: 92.0 % 75 69
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : import * as arrow from 'apache-arrow';
       2              : 
       3              : import { OrderByConstraint, ScalarFilter } from './sql/sqlframe_builder.js';
       4              : import { VariantKind } from '../utils/variant.js';
       5              : import { DataFrame } from './data_frame.js';
       6              : import { ArrowTableFormatter } from './arrow_formatter.js';
       7              : import { LoggableException } from '../platform/logger/logger.js';
       8              : import { AsyncValue } from '../utils/async_value.js';
       9              : 
      10              : export const TASK_FAILED = Symbol("TASK_FAILED");
      11              : export const TASK_RUNNING = Symbol("TASK_RUNNING");
      12              : export const TASK_SUCCEDED = Symbol("TASK_SUCCEDED");
      13            1 : export const ORDINAL_COLUMN = Symbol("ORDINAL_COLUMN");
      14            1 : export const STRING_COLUMN = Symbol("STRING_COLUMN");
      15            1 : export const LIST_COLUMN = Symbol("LIST_COLUMN");
      16            1 : export const SKIPPED_COLUMN = Symbol("SKIPPED_COLUMN");
      17            1 : export const ROWNUMBER_COLUMN = Symbol("ROWNUMBER_COLUMN");
      18              : 
      19              : // ------------------------------------------------------------
      20              : 
      21            1 : export class ComputationStateVersion {
      22              :     /// The data version - incremented when underlying data changes
      23            1 :     public data: number;
      24              :     /// The filter version - incremented when filters change
      25            1 :     public filter: number;
      26              : 
      27            1 :     constructor(data: number = 0, filter: number = 0) {
      28            1 :         this.data = data;
      29            1 :         this.filter = filter;
      30            1 :     }
      31              : 
      32              :     /// Clone a computation state version
      33            1 :     clone(): ComputationStateVersion {
      34            1 :         return new ComputationStateVersion(this.data, this.filter);
      35            1 :     }
      36              : 
      37              :     /// Create a new version with data incremented
      38            1 :     withDataIncrement(): ComputationStateVersion {
      39            1 :         return new ComputationStateVersion(this.data + 1, this.filter);
      40            1 :     }
      41              : 
      42              :     /// Create a new version with filter incremented
      43            1 :     withFilterIncrement(): ComputationStateVersion {
      44            1 :         return new ComputationStateVersion(this.data, this.filter + 1);
      45            1 :     }
      46              : 
      47              :     /// Check if this version matches another in the data dimension
      48            1 :     dataMatches(other: ComputationStateVersion): boolean {
      49            1 :         return this.data === other.data;
      50            1 :     }
      51              : 
      52              :     /// Check if this version matches another in data + filter dimensions
      53            1 :     filterMatches(other: ComputationStateVersion): boolean {
      54            1 :         return this.data === other.data && this.filter === other.filter;
      55            1 :     }
      56              : 
      57              :     /// Serialize the version
      58            1 :     public toString(): string {
      59            1 :         return `${this.data}.${this.filter}`;
      60            1 :     }
      61            1 : };
      62              : 
      63              : export interface TableFilteringTask {
      64              :     /// The table id
      65              :     tableId: number;
      66              :     /// The table version
      67              :     tableVersion: ComputationStateVersion;
      68              :     /// The data frame
      69              :     inputDataTable: arrow.Table;
      70              :     /// The data frame
      71              :     inputDataTableFieldIndex: Map<string, number>;
      72              :     /// The data frame
      73              :     inputDataFrame: DataFrame;
      74              :     /// The row number columns
      75              :     rowNumberColumnName: string;
      76              :     /// The scalar filters
      77              :     filters: ScalarFilter[];
      78              : }
      79              : 
      80              : export interface TableOrderingTask {
      81              :     /// The table id
      82              :     tableId: number;
      83              :     /// The table version
      84              :     tableVersion: ComputationStateVersion;
      85              :     /// The data frame
      86              :     inputDataTable: arrow.Table;
      87              :     /// The data frame
      88              :     inputDataTableFieldIndex: Map<string, number>;
      89              :     /// The data frame
      90              :     inputDataFrame: DataFrame;
      91              :     /// The active filter table, if ordering should happen on a filtered subset
      92              :     filterTable: FilterTable | null;
      93              :     /// The active Data-search table, if ordering should happen on searched rows
      94              :     dataSearchTable: DataSearchTable | null;
      95              :     /// The row number column that provides stable ids into the immutable base table
      96              :     rowNumberColumnName: string;
      97              :     /// The ordering constraints
      98              :     orderingConstraints: OrderByConstraint[];
      99              : }
     100              : 
     101              : export type ResultSearchKind = 'columns' | 'data';
     102              : 
     103              : export interface ResultSearchState {
     104              :     /// The plain-text search requested by the user
     105              :     requestedPattern: string;
     106              :     /// The last successfully applied search text
     107              :     appliedPattern: string;
     108              :     /// Incremented for every requested search
     109              :     requestId: number;
     110              :     /// The last applied request
     111              :     appliedRequestId: number;
     112              :     /// Matching column-group indexes for a Columns search
     113              :     matchingColumnGroups: number[] | null;
     114              :     /// Matching original-column indexes keyed by 1-based row number
     115              :     matchingRows: Map<number, number[]> | null;
     116              :     /// Whether a search is currently running
     117              :     pending: boolean;
     118              :     /// Latest search error, if any
     119              :     error: string | null;
     120              : }
     121              : 
     122              : export interface DataSearchTask {
     123              :     /// The table id
     124              :     tableId: number;
     125              :     /// The data/filter version when the search was requested
     126              :     tableVersion: ComputationStateVersion;
     127              :     /// The source data frame
     128              :     inputDataFrame: DataFrame;
     129              :     /// The stable row-number column in the source data frame
     130              :     rowNumberColumnName: string;
     131              :     /// Original result columns searched by the query
     132              :     columns: Array<{ columnIdx: number; columnGroupIdx: number; fieldName: string }>;
     133              :     /// The plain-text search pattern
     134              :     pattern: string;
     135              :     /// The request id used to reject stale results
     136              :     requestId: number;
     137              :     /// Cancels materialization when superseded or when the source table is destroyed
     138              :     abortController: AbortController;
     139              : }
     140              : 
     141            1 : export function createResultSearchState(): ResultSearchState {
     142            1 :     return {
     143            1 :         requestedPattern: '',
     144            1 :         appliedPattern: '',
     145            1 :         requestId: 0,
     146            1 :         appliedRequestId: 0,
     147            1 :         matchingColumnGroups: null,
     148            1 :         matchingRows: null,
     149            1 :         pending: false,
     150            1 :         error: null,
     151            1 :     };
     152            1 : }
     153              : 
     154              : export interface TableAggregationTask {
     155              :     /// The table id
     156              :     tableId: number;
     157              :     /// The table version
     158              :     tableVersion: ComputationStateVersion;
     159              :     /// The column entries
     160              :     columnEntries: ColumnGroup[];
     161              :     /// The data frame
     162              :     inputDataFrame: DataFrame;
     163              : }
     164              : 
     165              : export interface SystemColumnComputationTask {
     166              :     /// The table id
     167              :     tableId: number;
     168              :     /// The table version
     169              :     tableVersion: ComputationStateVersion;
     170              :     /// The column entries
     171              :     columnEntries: ColumnGroup[];
     172              :     /// The input table
     173              :     inputTable: arrow.Table;
     174              :     /// The input data frame
     175              :     inputDataFrame: DataFrame;
     176              :     /// The column of the input data frame with the row numbers of the input table, from 1
     177              :     rowNumberFieldName: string;
     178              :     /// The stats table
     179              :     tableAggregate: TableAggregation;
     180              : }
     181              : 
     182              : export interface ColumnAggregationTask {
     183              :     /// The table id
     184              :     tableId: number;
     185              :     /// The table version
     186              :     tableVersion: ComputationStateVersion;
     187              :     /// The task id
     188              :     columnId: number;
     189              :     /// The column entry
     190              :     columnEntry: ColumnGroup;
     191              :     /// The input data frame
     192              :     inputDataFrame: DataFrame;
     193              :     /// The table summary
     194              :     tableAggregate: TableAggregation;
     195              : }
     196              : 
     197              : // ------------------------------------------------------------
     198              : 
     199            1 : export enum TaskStatus {
     200            1 :     TASK_RUNNING = 1,
     201            1 :     TASK_SUCCEEDED = 2,
     202            1 :     TASK_FAILED = 3,
     203            1 : };
     204              : 
     205              : export interface TaskProgress {
     206              :     /// Task status
     207              :     status: TaskStatus;
     208              :     /// Task started at timestamp
     209              :     startedAt: Date;
     210              :     /// Task completed at timestamp
     211              :     completedAt: Date | null;
     212              :     /// Task failed at timestamp
     213              :     failedAt: Date | null;
     214              :     /// Task failed with error
     215              :     failedWithError: LoggableException | null;
     216              : }
     217              : 
     218              : export type WithProgress<T> = T & { progress: TaskProgress };
     219              : 
     220              : // ------------------------------------------------------------
     221              : 
     222              : export type ColumnGroup =
     223              :     | VariantKind<typeof ROWNUMBER_COLUMN, RowNumberGridColumnGroup>
     224              :     | VariantKind<typeof SKIPPED_COLUMN, SkippedGridColumnGroup>
     225              :     | VariantKind<typeof ORDINAL_COLUMN, OrdinalGridColumnGroup>
     226              :     | VariantKind<typeof STRING_COLUMN, StringGridColumnGroup>
     227              :     | VariantKind<typeof LIST_COLUMN, ListGridColumnGroup>
     228              :     ;
     229              : 
     230            1 : export function getGridColumnTypeName(variant: ColumnGroup) {
     231            1 :     switch (variant.type) {
     232            0 :         case ROWNUMBER_COLUMN: return "ROWNUMBER";
     233            0 :         case SKIPPED_COLUMN: return "SKIPPED";
     234            1 :         case ORDINAL_COLUMN: return "ORDINAL";
     235            1 :         case STRING_COLUMN: return "STRING";
     236            1 :         case LIST_COLUMN: return "LIST";
     237            1 :     }
     238            1 : }
     239              : 
     240              : export interface ColumnStatsFields {
     241              :     /// Entry count (!= null)
     242              :     countFieldName: string;
     243              :     /// Distinct entry count (only for strings and lists)
     244              :     distinctCountFieldName: string | null;
     245              :     /// Maximum value
     246              :     minAggregateFieldName: string | null;
     247              :     /// Minimum value
     248              :     maxAggregateFieldName: string | null;
     249              : }
     250              : 
     251              : export interface ColumnBinningFields {
     252              :     /// The bin field
     253              :     binFieldName: string;
     254              : }
     255              : 
     256              : export interface RowNumberGridColumnGroup {
     257              :     /// The input field
     258              :     rowNumberFieldName: string;
     259              : }
     260              : 
     261              : export interface OrdinalGridColumnGroup {
     262              :     /// The input field name
     263              :     inputFieldName: string;
     264              :     /// The input field type
     265              :     inputFieldType: arrow.DataType;
     266              :     /// Is the input nullable?
     267              :     inputFieldNullable: boolean;
     268              :     /// The column stats
     269              :     statsFields: ColumnStatsFields | null;
     270              :     /// The bin field name
     271              :     binFieldName: string | null;
     272              :     /// The bin count
     273              :     binCount: number;
     274              : }
     275              : 
     276              : export interface StringGridColumnGroup {
     277              :     /// The input field name
     278              :     inputFieldName: string;
     279              :     /// The input field type
     280              :     inputFieldType: arrow.DataType;
     281              :     /// Is the input nullable?
     282              :     inputFieldNullable: boolean;
     283              :     /// The column stats
     284              :     statsFields: ColumnStatsFields | null;
     285              :     /// The identifier field
     286              :     valueIdFieldName: string | null;
     287              : }
     288              : 
     289              : export interface ListGridColumnGroup {
     290              :     /// The input field name
     291              :     inputFieldName: string;
     292              :     /// The input field type
     293              :     inputFieldType: arrow.DataType;
     294              :     /// Is the input nullable?
     295              :     inputFieldNullable: boolean;
     296              :     /// The column stats
     297              :     statsFields: ColumnStatsFields | null;
     298              :     /// The identifier field
     299              :     valueIdFieldName: string | null;
     300              : }
     301              : 
     302              : export interface SkippedGridColumnGroup {
     303              :     /// The input field name
     304              :     inputFieldName: string;
     305              :     /// The input field type
     306              :     inputFieldType: arrow.DataType;
     307              :     /// Is the input nullable?
     308              :     inputFieldNullable: boolean;
     309              : }
     310              : 
     311              : // ------------------------------------------------------------
     312              : 
     313              : export interface OrderingTable {
     314              :     /// The row number column that is used for the ordering ids
     315              :     inputRowNumberColumnName: string;
     316              :     /// The ordering constraints
     317              :     orderingConstraints: OrderByConstraint[];
     318              :     /// The arrow table, only containing the row ids in display order
     319              :     dataTable: arrow.Table;
     320              :     /// The data frame
     321              :     dataFrame: DataFrame;
     322              :     /// The version when this ordering was computed
     323              :     version: ComputationStateVersion;
     324              :     /// The Data-search request included in the ordering, if any
     325              :     dataSearchRequestId: number | null;
     326              : }
     327              : 
     328              : export interface FilterTable {
     329              :     /// The row number column that is used for the filter
     330              :     inputRowNumberColumnName: string;
     331              :     /// The arrow table, only containing the row ids of the filtered rows
     332              :     dataTable: arrow.Table;
     333              :     /// The data frame
     334              :     dataFrame: DataFrame;
     335              :     /// The version when this filter was computed
     336              :     version: ComputationStateVersion;
     337              : }
     338              : 
     339              : export interface DataSearchTable {
     340              :     /// The source row-number column matched by this search
     341              :     inputRowNumberColumnName: string;
     342              :     /// The Arrow table containing matched row ids and matching original-column ids
     343              :     dataTable: arrow.Table;
     344              :     /// The materialized search result
     345              :     dataFrame: DataFrame;
     346              :     /// The source data version searched
     347              :     version: ComputationStateVersion;
     348              :     /// The request represented by this table
     349              :     requestId: number;
     350              : }
     351              : 
     352              : // ------------------------------------------------------------
     353              : 
     354              : export type ColumnAggregationVariant =
     355              :     VariantKind<typeof ORDINAL_COLUMN, OrdinalColumnAggregation>
     356              :     | VariantKind<typeof STRING_COLUMN, StringColumnAggregation>
     357              :     | VariantKind<typeof LIST_COLUMN, ListColumnAggregation>
     358              :     | VariantKind<typeof SKIPPED_COLUMN, null>
     359              :     ;
     360              : 
     361              : export type WithFilter<T> = T & {
     362              :     /// The cross-filter table, if active
     363              :     filterTable: FilterTable | null,
     364              :     /// The Data-search table, if active
     365              :     dataSearchTable: DataSearchTable | null,
     366              :     /// Number of rows in the effective intersection
     367              :     selectionRowCount: number,
     368              :     /// The unfiltered aggregate
     369              :     unfilteredAggregate: ColumnAggregationVariant;
     370              : };
     371              : 
     372              : export type WithFilterEpoch<T> = T & {
     373              :     /// The filter version when this aggregate was computed
     374              :     filterVersion: ComputationStateVersion,
     375              :     /// The Data-search request when this aggregate was computed
     376              :     dataSearchRequestId: number | null,
     377              : };
     378              : 
     379              : export interface TableAggregation {
     380              :     /// The statistics
     381              :     dataFrame: DataFrame;
     382              :     /// The statistics
     383              :     table: arrow.Table;
     384              :     /// The statistics field index
     385              :     tableFieldsByName: Map<string, number>;
     386              :     /// The formatter for the stats table
     387              :     tableFormatter: ArrowTableFormatter;
     388              :     /// Maximum value
     389              :     countStarFieldName: string;
     390              : }
     391              : 
     392              : export interface OrdinalColumnAggregation<WidthType extends arrow.DataType = arrow.DataType, BoundType extends arrow.DataType = arrow.DataType> {
     393              :     /// The column entry
     394              :     columnEntry: OrdinalGridColumnGroup;
     395              :     /// The binned data frame
     396              :     binnedDataFrame: DataFrame;
     397              :     /// The binned values
     398              :     binnedValues: BinnedValuesTable;
     399              :     /// The formatter for the binned values
     400              :     binnedValuesFormatter: ArrowTableFormatter;
     401              :     /// The analyzed information for an ordinal column
     402              :     columnAnalysis: OrdinalColumnAnalysis;
     403              : }
     404              : 
     405              : export interface OrdinalColumnAnalysis {
     406              :     /// The total row count
     407              :     totalCount: number;
     408              :     /// The value count
     409              :     countNotNull: number;
     410              :     /// The null count
     411              :     countNull: number;
     412              :     /// The minimum value
     413              :     minValue: string;
     414              :     /// The maximum value
     415              :     maxValue: string;
     416              :     /// The bin count
     417              :     binCount: number;
     418              :     /// The bin counts
     419              :     binValueCounts: BigInt64Array;
     420              :     /// The bin percentages
     421              :     binPercentages: Float64Array;
     422              :     /// The bin lower bounds
     423              :     binLowerBounds: string[];
     424              : }
     425              : 
     426              : export interface OrdinalColumnFilterAnalysis {
     427              :     /// The bin counts
     428              :     binValueCounts: BigInt64Array;
     429              :     /// The bin percentages
     430              :     binPercentages: Float64Array;
     431              : }
     432              : 
     433              : export interface StringColumnAggregation {
     434              :     /// The string column entry
     435              :     columnEntry: StringGridColumnGroup;
     436              :     /// The frequent values
     437              :     frequentValuesDataFrame: DataFrame;
     438              :     /// The frequent values
     439              :     frequentValuesTable: FrequentValuesTable;
     440              :     /// The formatter for the frequent values
     441              :     frequentValuesFormatter: ArrowTableFormatter;
     442              :     /// The analyzed column information
     443              :     analysis: StringColumnAnalysis;
     444              : }
     445              : 
     446              : export interface StringColumnAnalysis {
     447              :     /// The total row count
     448              :     totalCount: number;
     449              :     /// The value count
     450              :     countNotNull: number;
     451              :     /// The null count
     452              :     countNull: number;
     453              :     /// The distinct count
     454              :     countDistinct: number;
     455              :     /// Is unique?
     456              :     isUnique: boolean;
     457              :     /// The frequent values
     458              :     frequentValueStrings: (string | null)[];
     459              :     /// The frequent value ids (for plotting)
     460              :     frequentValueIds: BigInt64Array;
     461              :     /// The frequent value counts
     462              :     frequentValueCounts: BigInt64Array;
     463              :     /// The frequent value percentages
     464              :     frequentValuePercentages: Float64Array;
     465              : }
     466              : 
     467              : export interface ListColumnAggregation {
     468              :     /// The list column entry
     469              :     columnEntry: ListGridColumnGroup;
     470              :     /// The frequent values
     471              :     frequentValuesDataFrame: DataFrame;
     472              :     /// The frequent values
     473              :     frequentValuesTable: FrequentValuesTable;
     474              :     /// The formatter for the frequent values
     475              :     frequentValuesFormatter: ArrowTableFormatter;
     476              :     /// The analyzed information for a list column
     477              :     analysis: ListColumnAnalysis;
     478              : }
     479              : 
     480              : export interface ListColumnAnalysis {
     481              :     /// The value count
     482              :     countNotNull: number;
     483              :     /// The null count
     484              :     countNull: number;
     485              :     /// The distinct count
     486              :     countDistinct: number;
     487              :     /// Is unique?
     488              :     isUnique: boolean;
     489              :     /// The frequent value is null
     490              :     frequentValueIsNull: Uint8Array;
     491              :     /// The frequent value counts
     492              :     frequentValueCounts: BigInt64Array;
     493              :     /// The frequent value percentages
     494              :     frequentValuePercentages: Float64Array;
     495              : }
     496              : 
     497              : // ------------------------------------------------------------
     498              : 
     499              : export type BinnedValuesTable<WidthType extends arrow.DataType = arrow.DataType, BoundType extends arrow.DataType = arrow.DataType> = arrow.Table<{
     500              :     bin: arrow.Int32,
     501              :     binWidth: WidthType,
     502              :     binLowerBound: BoundType,
     503              :     binUpperBound: BoundType,
     504              :     count: arrow.Int64,
     505              : }>;
     506              : 
     507              : export type FrequentValuesTable<KeyType extends arrow.DataType = arrow.DataType> = arrow.Table<{
     508              :     key: KeyType,
     509              :     keyId: arrow.Int64,
     510              :     count: arrow.Int64,
     511              : }>
     512              : 
     513              : // ------------------------------------------------------------
     514              : 
     515              : export type ComputationTask<Type, Task, Result> = VariantKind<Type, Task> & {
     516              :     result: AsyncValue<Result, LoggableException>,
     517              :     taskId?: number,
     518              : };
     519              : 
     520            1 : export const FILTERED_COLUMN_AGGREGATION_TASK = Symbol("FILTERED_COLUMN_AGGREGATION_TASK");
     521            1 : export const COLUMN_AGGREGATION_TASK = Symbol("COLUMN_AGGREGATION_TASK");
     522            1 : export const TABLE_FILTERING_TASK = Symbol("TABLE_FILTERING_TASK");
     523            1 : export const TABLE_ORDERING_TASK = Symbol("TABLE_ORDERING_TASK");
     524            1 : export const TABLE_AGGREGATION_TASK = Symbol("TABLE_AGGREGATION_TASK");
     525            1 : export const SYSTEM_COLUMN_COMPUTATION_TASK = Symbol("SYSTEM_COLUMN_COMPUTATION_TASK");
     526            1 : export const DATA_SEARCH_TASK = Symbol("DATA_SEARCH_TASK");
     527              : 
     528              : export type TaskVariant =
     529              :     | ComputationTask<typeof TABLE_FILTERING_TASK, TableFilteringTask, FilterTable | null>
     530              :     | ComputationTask<typeof TABLE_ORDERING_TASK, TableOrderingTask, OrderingTable>
     531              :     | ComputationTask<typeof TABLE_AGGREGATION_TASK, TableAggregationTask, [TableAggregation, ColumnGroup[]]>
     532              :     | ComputationTask<typeof SYSTEM_COLUMN_COMPUTATION_TASK, SystemColumnComputationTask, [arrow.Table, DataFrame, ColumnGroup[]]>
     533              :     | ComputationTask<typeof COLUMN_AGGREGATION_TASK, ColumnAggregationTask, ColumnAggregationVariant>
     534              :     | ComputationTask<typeof FILTERED_COLUMN_AGGREGATION_TASK, WithFilter<ColumnAggregationTask>, WithFilterEpoch<ColumnAggregationVariant> | null>
     535              :     | ComputationTask<typeof DATA_SEARCH_TASK, DataSearchTask, DataSearchTable>
     536              :     ;
     537              : 
     538              : /// The title of the queries of a task in the query history
     539            1 : export function computationTaskTitle(task: TaskVariant): string {
     540            1 :     switch (task.type) {
     541            1 :         case COLUMN_AGGREGATION_TASK: return 'Column aggregation';
     542            0 :         case TABLE_FILTERING_TASK: return 'Table filtering';
     543            0 :         case TABLE_ORDERING_TASK: return 'Table ordering';
     544            1 :         case TABLE_AGGREGATION_TASK: return 'Table aggregation';
     545            1 :         case SYSTEM_COLUMN_COMPUTATION_TASK: return 'System column computation';
     546            0 :         case FILTERED_COLUMN_AGGREGATION_TASK: return 'Filtered column aggregation';
     547            0 :         case DATA_SEARCH_TASK: return 'Data search';
     548            1 :     }
     549            1 : }
        

Generated by: LCOV version 2.0-1