@milaboratories/pl-model-common 1.24.6 → 1.24.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/drivers/pframe/filter_spec.d.ts +123 -0
  2. package/dist/drivers/pframe/filter_spec.d.ts.map +1 -0
  3. package/dist/drivers/pframe/index.d.ts +1 -0
  4. package/dist/drivers/pframe/index.d.ts.map +1 -1
  5. package/dist/drivers/pframe/query/index.d.ts +1 -0
  6. package/dist/drivers/pframe/query/index.d.ts.map +1 -1
  7. package/dist/drivers/pframe/query/query_common.d.ts +60 -8
  8. package/dist/drivers/pframe/query/query_common.d.ts.map +1 -1
  9. package/dist/drivers/pframe/query/query_data.d.ts +15 -15
  10. package/dist/drivers/pframe/query/query_data.d.ts.map +1 -1
  11. package/dist/drivers/pframe/query/query_spec.d.ts +19 -15
  12. package/dist/drivers/pframe/query/query_spec.d.ts.map +1 -1
  13. package/dist/drivers/pframe/query/utils.cjs +205 -0
  14. package/dist/drivers/pframe/query/utils.cjs.map +1 -0
  15. package/dist/drivers/pframe/query/utils.d.ts +6 -0
  16. package/dist/drivers/pframe/query/utils.d.ts.map +1 -0
  17. package/dist/drivers/pframe/query/utils.js +201 -0
  18. package/dist/drivers/pframe/query/utils.js.map +1 -0
  19. package/dist/drivers/pframe/table_calculate.cjs +153 -0
  20. package/dist/drivers/pframe/table_calculate.cjs.map +1 -1
  21. package/dist/drivers/pframe/table_calculate.d.ts +12 -1
  22. package/dist/drivers/pframe/table_calculate.d.ts.map +1 -1
  23. package/dist/drivers/pframe/table_calculate.js +150 -1
  24. package/dist/drivers/pframe/table_calculate.js.map +1 -1
  25. package/dist/flags/block_flags.cjs +5 -1
  26. package/dist/flags/block_flags.cjs.map +1 -1
  27. package/dist/flags/block_flags.d.ts +2 -1
  28. package/dist/flags/block_flags.d.ts.map +1 -1
  29. package/dist/flags/block_flags.js +5 -1
  30. package/dist/flags/block_flags.js.map +1 -1
  31. package/dist/index.cjs +8 -0
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +2 -1
  34. package/dist/index.js.map +1 -1
  35. package/package.json +4 -4
  36. package/src/drivers/pframe/filter_spec.ts +51 -0
  37. package/src/drivers/pframe/index.ts +1 -0
  38. package/src/drivers/pframe/query/index.ts +1 -0
  39. package/src/drivers/pframe/query/query_common.ts +60 -8
  40. package/src/drivers/pframe/query/query_data.ts +40 -34
  41. package/src/drivers/pframe/query/query_spec.ts +50 -34
  42. package/src/drivers/pframe/query/utils.ts +205 -0
  43. package/src/drivers/pframe/table_calculate.ts +163 -1
  44. package/src/flags/block_flags.ts +6 -1
@@ -0,0 +1,51 @@
1
+ import type { SUniversalPColumnId } from "./spec/ids";
2
+
3
+ export type FilterSpecNode<Leaf, CommonNode = {}, CommonLeaf = {}> =
4
+ | (CommonLeaf & Leaf)
5
+ | (CommonNode & { type: "not"; filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf> })
6
+ | (CommonNode & { type: "or"; filters: FilterSpecNode<Leaf, CommonNode, CommonLeaf>[] })
7
+ | (CommonNode & { type: "and"; filters: FilterSpecNode<Leaf, CommonNode, CommonLeaf>[] });
8
+
9
+ export type FilterSpecLeaf<T = SUniversalPColumnId> =
10
+ | { type: undefined }
11
+ | { type: "isNA"; column: T }
12
+ | { type: "isNotNA"; column: T }
13
+ | { type: "ifNa"; column: T; replacement: string }
14
+ | { type: "patternEquals"; column: T; value: string }
15
+ | { type: "patternNotEquals"; column: T; value: string }
16
+ | { type: "patternContainSubsequence"; column: T; value: string }
17
+ | { type: "patternNotContainSubsequence"; column: T; value: string }
18
+ | { type: "patternMatchesRegularExpression"; column: T; value: string }
19
+ | {
20
+ type: "patternFuzzyContainSubsequence";
21
+ column: T;
22
+ value: string;
23
+ maxEdits?: number;
24
+ substitutionsOnly?: boolean;
25
+ wildcard?: string;
26
+ }
27
+ | { type: "inSet"; column: T; value: string[] }
28
+ | { type: "notInSet"; column: T; value: string[] }
29
+ | { type: "topN"; column: T; n: number }
30
+ | { type: "bottomN"; column: T; n: number }
31
+ | { type: "equal"; column: T; x: number }
32
+ | { type: "notEqual"; column: T; x: number }
33
+ | { type: "lessThan"; column: T; x: number }
34
+ | { type: "greaterThan"; column: T; x: number }
35
+ | { type: "lessThanOrEqual"; column: T; x: number }
36
+ | { type: "greaterThanOrEqual"; column: T; x: number }
37
+ | { type: "equalToColumn"; column: T; rhs: T }
38
+ | { type: "lessThanColumn"; column: T; rhs: T; minDiff?: number }
39
+ | { type: "greaterThanColumn"; column: T; rhs: T; minDiff?: number }
40
+ | { type: "lessThanColumnOrEqual"; column: T; rhs: T; minDiff?: number }
41
+ | { type: "greaterThanColumnOrEqual"; column: T; rhs: T; minDiff?: number };
42
+
43
+ export type FilterSpec<
44
+ Leaf extends FilterSpecLeaf<unknown> = FilterSpecLeaf<SUniversalPColumnId>,
45
+ CommonNode = {},
46
+ CommonLeaf = CommonNode,
47
+ > = FilterSpecNode<Leaf, CommonNode, CommonLeaf>;
48
+
49
+ export type FilterSpecType = Exclude<FilterSpec, { type: undefined }>["type"];
50
+
51
+ export type FilterSpecOfType<T extends FilterSpecType> = Extract<FilterSpec, { type: T }>;
@@ -1,5 +1,6 @@
1
1
  export * from "./column_filter";
2
2
  export * from "./data_info";
3
+ export * from "./filter_spec";
3
4
  export * from "./data_types";
4
5
  export * from "./find_columns";
5
6
  export * from "./pframe";
@@ -1,2 +1,3 @@
1
1
  export * from "./query_spec";
2
2
  export * from "./query_data";
3
+ export * from "./utils";
@@ -117,6 +117,54 @@ export type ExprConstant = {
117
117
  value: string | number | boolean;
118
118
  };
119
119
 
120
+ /**
121
+ * Null check expression.
122
+ *
123
+ * Tests if an expression evaluates to null.
124
+ * **Input**: Any expression.
125
+ * **Output**: Boolean (true if input is null, false otherwise).
126
+ *
127
+ * @template I - The expression type (for recursion)
128
+ *
129
+ * @example
130
+ * // Check if column value is null
131
+ * { type: 'isNull', input: columnRef }
132
+ *
133
+ * // Combine with NOT to check for non-null
134
+ * { type: 'not', input: { type: 'isNull', input: columnRef } }
135
+ */
136
+ export interface ExprIsNull<I> {
137
+ type: "isNull";
138
+ /** Input expression to check for null */
139
+ input: I;
140
+ }
141
+
142
+ /**
143
+ * Null coalescing expression.
144
+ *
145
+ * Returns the input value if it is not null, otherwise returns the replacement value.
146
+ * Equivalent to SQL's `IFNULL(input, replacement)` or `COALESCE(input, replacement)`.
147
+ * **Input**: Any expression.
148
+ * **Output**: Same type as input/replacement.
149
+ * **Null handling**: If input is null, returns replacement; otherwise returns input.
150
+ *
151
+ * @template I - The expression type (for recursion)
152
+ *
153
+ * @example
154
+ * // Replace null values with 0
155
+ * { type: 'ifNull', input: columnRef, replacement: { type: 'constant', value: 0 } }
156
+ *
157
+ * // Replace null strings with 'unknown'
158
+ * { type: 'ifNull', input: nameColumn, replacement: { type: 'constant', value: 'unknown' } }
159
+ */
160
+ export interface ExprIfNull<I> {
161
+ type: "ifNull";
162
+ /** Value to check for null */
163
+ input: I;
164
+ /** Replacement value if input is null */
165
+ replacement: I;
166
+ }
167
+
120
168
  // ============ Generic Expression Interfaces ============
121
169
  // I = expression type (recursive), S = selector type
122
170
 
@@ -479,6 +527,7 @@ export type InferBooleanExpressionUnion<E> = [
479
527
  E extends ExprStringContains<unknown> ? Extract<E, { type: "stringContains" }> : never,
480
528
  E extends ExprStringContainsFuzzy<unknown> ? Extract<E, { type: "stringContainsFuzzy" }> : never,
481
529
  E extends ExprStringRegex<unknown> ? Extract<E, { type: "stringRegex" }> : never,
530
+ E extends ExprIsNull<unknown> ? Extract<E, { type: "isNull" }> : never,
482
531
  E extends ExprLogicalUnary<unknown> ? Extract<E, { type: "not" }> : never,
483
532
  E extends ExprLogicalVariadic<unknown> ? Extract<E, { type: "and" | "or" }> : never,
484
533
  E extends ExprIsIn<unknown, string | number> ? Extract<E, { type: "isIn" }> : never,
@@ -702,12 +751,14 @@ export interface QueryFilter<Q, E> {
702
751
  *
703
752
  * @example
704
753
  * // Reference column by ID
705
- * { type: 'column', columnId: 'col_abc123' }
754
+ * { type: 'column', column: 'col_abc123' }
755
+ *
756
+ * @template C - Column reference type (e.g., PObjectId for spec, full PColumn for rich queries)
706
757
  */
707
- export interface QueryColumn {
758
+ export interface QueryColumn<C = PObjectId> {
708
759
  type: "column";
709
- /** Unique identifier of the column to reference */
710
- columnId: PObjectId;
760
+ /** Column reference (ID or full column object depending on context) */
761
+ column: C;
711
762
  }
712
763
 
713
764
  /**
@@ -751,21 +802,22 @@ export interface QueryInlineColumn<T> {
751
802
  * - Expands it across specified axes indices
752
803
  * - Result has Cartesian product of original axes × new axes
753
804
  *
805
+ * @template C - Column reference type
754
806
  * @template SO - Spec override type
755
807
  *
756
808
  * @example
757
809
  * // Expand column across axes at indices 0 and 2
758
810
  * {
759
811
  * type: 'sparseToDenseColumn',
760
- * columnId: 'col_abc123',
812
+ * column: 'col_abc123',
761
813
  * axesIndices: [0, 2],
762
814
  * specOverride: { ... } // optional spec modifications
763
815
  * }
764
816
  */
765
- export interface QuerySparseToDenseColumn<SO> {
817
+ export interface QuerySparseToDenseColumn<C, SO> {
766
818
  type: "sparseToDenseColumn";
767
- /** ID of the column to cross-join */
768
- columnId: PObjectId;
819
+ /** Column reference (ID or full column object depending on context) */
820
+ column: C;
769
821
  /** Optional override for the column specification */
770
822
  specOverride?: SO;
771
823
  /** Indices of axes to expand across */
@@ -3,8 +3,11 @@ import type {
3
3
  ExprAxisRef,
4
4
  ExprColumnRef,
5
5
  ExprNumericBinary,
6
+ ExprNumericComparison,
6
7
  ExprConstant,
7
8
  ExprIsIn,
9
+ ExprIsNull,
10
+ ExprIfNull,
8
11
  ExprLogicalUnary,
9
12
  ExprLogicalVariadic,
10
13
  ExprStringContains,
@@ -51,27 +54,27 @@ type ColumnIdAndTypeSpec = {
51
54
  * // - This entry's axis 1 maps to result axis 2
52
55
  * { entry: queryData, axesMapping: [0, 2] }
53
56
  */
54
- export interface QueryJoinEntryData extends QueryJoinEntry<QueryData> {
57
+ export interface DataQueryJoinEntry extends QueryJoinEntry<DataQuery> {
55
58
  /** Maps this entry's axes to the result axes by index */
56
59
  axesMapping: number[];
57
60
  }
58
61
 
59
62
  /** @see QueryColumn */
60
- export type QueryColumnData = QueryColumn;
63
+ export type DataQueryColumn = QueryColumn;
61
64
  /** @see QueryInlineColumn */
62
- export type QueryInlineColumnData = QueryInlineColumn<ColumnIdAndTypeSpec>;
65
+ export type DataQueryInlineColumn = QueryInlineColumn<ColumnIdAndTypeSpec>;
63
66
  /** @see QuerySparseToDenseColumn */
64
- export type QuerySparseToDenseColumnData = QuerySparseToDenseColumn<ColumnIdAndTypeSpec>;
67
+ export type DataQuerySparseToDenseColumn = QuerySparseToDenseColumn<PObjectId, ColumnIdAndTypeSpec>;
65
68
  /** @see QuerySymmetricJoin */
66
- export type QuerySymmetricJoinData = QuerySymmetricJoin<QueryJoinEntryData>;
69
+ export type DataQuerySymmetricJoin = QuerySymmetricJoin<DataQueryJoinEntry>;
67
70
  /** @see QueryOuterJoin */
68
- export type QueryOuterJoinData = QueryOuterJoin<QueryJoinEntryData>;
71
+ export type DataQueryOuterJoin = QueryOuterJoin<DataQueryJoinEntry>;
69
72
  /** @see QuerySliceAxes */
70
- export type QuerySliceAxesData = QuerySliceAxes<QueryData, QueryAxisSelector<number>>;
73
+ export type DataQuerySliceAxes = QuerySliceAxes<DataQuery, QueryAxisSelector<number>>;
71
74
  /** @see QuerySort */
72
- export type QuerySortData = QuerySort<QueryData, QueryExpressionData>;
75
+ export type DataQuerySort = QuerySort<DataQuery, DataQueryExpression>;
73
76
  /** @see QueryFilter */
74
- export type QueryFilterData = QueryFilter<QueryData, QueryBooleanExpressionData>;
77
+ export type DataQueryFilter = QueryFilter<DataQuery, DataQueryBooleanExpression>;
75
78
 
76
79
  /**
77
80
  * Union of all data layer query types.
@@ -84,34 +87,37 @@ export type QueryFilterData = QueryFilter<QueryData, QueryBooleanExpressionData>
84
87
  * - Join operations: innerJoin, fullJoin, outerJoin
85
88
  * - Transformations: sliceAxes, sort, filter
86
89
  */
87
- export type QueryData =
88
- | QueryColumnData
89
- | QueryInlineColumnData
90
- | QuerySparseToDenseColumnData
91
- | QuerySymmetricJoinData
92
- | QueryOuterJoinData
93
- | QuerySliceAxesData
94
- | QuerySortData
95
- | QueryFilterData;
90
+ export type DataQuery =
91
+ | DataQueryColumn
92
+ | DataQueryInlineColumn
93
+ | DataQuerySparseToDenseColumn
94
+ | DataQuerySymmetricJoin
95
+ | DataQueryOuterJoin
96
+ | DataQuerySliceAxes
97
+ | DataQuerySort
98
+ | DataQueryFilter;
96
99
 
97
100
  /** @see ExprAxisRef */
98
- export type ExprAxisRefData = ExprAxisRef<number>;
101
+ export type DataExprAxisRef = ExprAxisRef<number>;
99
102
  /** @see ExprColumnRef */
100
- export type ExprColumnRefData = ExprColumnRef<number>;
103
+ export type DataExprColumnRef = ExprColumnRef<number>;
101
104
 
102
- export type QueryExpressionData =
103
- | ExprColumnRefData
104
- | ExprAxisRefData
105
+ export type DataQueryExpression =
106
+ | DataExprColumnRef
107
+ | DataExprAxisRef
105
108
  | ExprConstant
106
- | ExprNumericBinary<QueryExpressionData>
107
- | ExprNumericUnary<QueryExpressionData>
108
- | ExprStringEquals<QueryExpressionData>
109
- | ExprStringContains<QueryExpressionData>
110
- | ExprStringRegex<QueryExpressionData>
111
- | ExprStringContainsFuzzy<QueryExpressionData>
112
- | ExprLogicalUnary<QueryExpressionData>
113
- | ExprLogicalVariadic<QueryExpressionData>
114
- | ExprIsIn<QueryExpressionData, string>
115
- | ExprIsIn<QueryExpressionData, number>;
109
+ | ExprNumericBinary<DataQueryExpression>
110
+ | ExprNumericComparison<DataQueryExpression>
111
+ | ExprNumericUnary<DataQueryExpression>
112
+ | ExprStringEquals<DataQueryExpression>
113
+ | ExprStringContains<DataQueryExpression>
114
+ | ExprStringRegex<DataQueryExpression>
115
+ | ExprStringContainsFuzzy<DataQueryExpression>
116
+ | ExprIsNull<DataQueryExpression>
117
+ | ExprIfNull<DataQueryExpression>
118
+ | ExprLogicalUnary<DataQueryExpression>
119
+ | ExprLogicalVariadic<DataQueryExpression>
120
+ | ExprIsIn<DataQueryExpression, string>
121
+ | ExprIsIn<DataQueryExpression, number>;
116
122
 
117
- export type QueryBooleanExpressionData = InferBooleanExpressionUnion<QueryExpressionData>;
123
+ export type DataQueryBooleanExpression = InferBooleanExpressionUnion<DataQueryExpression>;
@@ -3,8 +3,11 @@ import type {
3
3
  ExprAxisRef,
4
4
  ExprColumnRef,
5
5
  ExprNumericBinary,
6
+ ExprNumericComparison,
6
7
  ExprConstant,
7
8
  ExprIsIn,
9
+ ExprIsNull,
10
+ ExprIfNull,
8
11
  ExprLogicalUnary,
9
12
  ExprLogicalVariadic,
10
13
  ExprStringContains,
@@ -55,7 +58,7 @@ type ColumnIdAndSpec = {
55
58
  * ]
56
59
  * }
57
60
  */
58
- export type QueryJoinEntrySpec = QueryJoinEntry<QuerySpec> & {
61
+ export type SpecQueryJoinEntry<C = PObjectId> = QueryJoinEntry<SpecQuery<C>> & {
59
62
  /** Axis qualifications with additional domain constraints */
60
63
  qualifications: {
61
64
  /** Axis selector identifying which axis to qualify */
@@ -66,21 +69,27 @@ export type QueryJoinEntrySpec = QueryJoinEntry<QuerySpec> & {
66
69
  };
67
70
 
68
71
  /** @see QueryColumn */
69
- export type QueryColumnSpec = QueryColumn;
72
+ export type SpecQueryColumn<C = PObjectId> = QueryColumn<C>;
70
73
  /** @see QueryInlineColumn */
71
- export type QueryInlineColumnSpec = QueryInlineColumn<ColumnIdAndSpec>;
74
+ export type SpecQueryInlineColumn = QueryInlineColumn<ColumnIdAndSpec>;
72
75
  /** @see QuerySparseToDenseColumn */
73
- export type QuerySparseToDenseColumnSpec = QuerySparseToDenseColumn<PColumnIdAndSpec>;
76
+ export type SpecQuerySparseToDenseColumn<C = PObjectId> = QuerySparseToDenseColumn<
77
+ C,
78
+ PColumnIdAndSpec
79
+ >;
74
80
  /** @see QuerySymmetricJoin */
75
- export type QuerySymmetricJoinSpec = QuerySymmetricJoin<QueryJoinEntrySpec>;
81
+ export type SpecQuerySymmetricJoin<C = PObjectId> = QuerySymmetricJoin<SpecQueryJoinEntry<C>>;
76
82
  /** @see QueryOuterJoin */
77
- export type QueryOuterJoinSpec = QueryOuterJoin<QueryJoinEntrySpec>;
83
+ export type SpecQueryOuterJoin<C = PObjectId> = QueryOuterJoin<SpecQueryJoinEntry<C>>;
78
84
  /** @see QuerySliceAxes */
79
- export type QuerySliceAxesSpec = QuerySliceAxes<QuerySpec, QueryAxisSelector<SingleAxisSelector>>;
85
+ export type SpecQuerySliceAxes<C = PObjectId> = QuerySliceAxes<
86
+ SpecQuery<C>,
87
+ QueryAxisSelector<SingleAxisSelector>
88
+ >;
80
89
  /** @see QuerySort */
81
- export type QuerySortSpec = QuerySort<QuerySpec, QueryExpressionSpec>;
90
+ export type SpecQuerySort<C = PObjectId> = QuerySort<SpecQuery<C>, SpecQueryExpression>;
82
91
  /** @see QueryFilter */
83
- export type QueryFilterSpec = QueryFilter<QuerySpec, QueryBooleanExpressionSpec>;
92
+ export type SpecQueryFilter<C = PObjectId> = QueryFilter<SpecQuery<C>, SpecQueryBooleanExpression>;
84
93
 
85
94
  /**
86
95
  * Union of all spec layer query types.
@@ -88,39 +97,46 @@ export type QueryFilterSpec = QueryFilter<QuerySpec, QueryBooleanExpressionSpec>
88
97
  * The spec layer operates with named selectors and column IDs,
89
98
  * making it suitable for user-facing query construction and validation.
90
99
  *
100
+ * @template C - Column reference type. Defaults to PObjectId (ID-only).
101
+ * Can be parameterized with richer types (e.g., PColumn<Data>) to carry
102
+ * full column data directly in the query tree.
103
+ *
91
104
  * Includes:
92
105
  * - Leaf nodes: column, inlineColumn, sparseToDenseColumn
93
106
  * - Join operations: innerJoin, fullJoin, outerJoin
94
107
  * - Transformations: sliceAxes, sort, filter
95
108
  */
96
- export type QuerySpec =
97
- | QueryColumnSpec
98
- | QueryInlineColumnSpec
99
- | QuerySparseToDenseColumnSpec
100
- | QuerySymmetricJoinSpec
101
- | QueryOuterJoinSpec
102
- | QuerySliceAxesSpec
103
- | QuerySortSpec
104
- | QueryFilterSpec;
109
+ export type SpecQuery<C = PObjectId> =
110
+ | SpecQueryColumn<C>
111
+ | SpecQueryInlineColumn
112
+ | SpecQuerySparseToDenseColumn<C>
113
+ | SpecQuerySymmetricJoin<C>
114
+ | SpecQueryOuterJoin<C>
115
+ | SpecQuerySliceAxes<C>
116
+ | SpecQuerySort<C>
117
+ | SpecQueryFilter<C>;
105
118
 
106
119
  /** @see ExprAxisRef */
107
- export type ExprAxisRefSpec = ExprAxisRef<SingleAxisSelector>;
120
+ export type SpecExprAxisRef = ExprAxisRef<SingleAxisSelector>;
108
121
  /** @see ExprColumnRef */
109
- export type ExprColumnRefSpec = ExprColumnRef<PObjectId>;
122
+ export type SpecExprColumnRef = ExprColumnRef<PObjectId>;
110
123
 
111
- export type QueryExpressionSpec =
112
- | ExprColumnRefSpec
113
- | ExprAxisRefSpec
124
+ export type SpecQueryExpression =
125
+ | SpecExprColumnRef
126
+ | SpecExprAxisRef
114
127
  | ExprConstant
115
- | ExprNumericBinary<QueryExpressionSpec>
116
- | ExprNumericUnary<QueryExpressionSpec>
117
- | ExprStringEquals<QueryExpressionSpec>
118
- | ExprStringContains<QueryExpressionSpec>
119
- | ExprStringRegex<QueryExpressionSpec>
120
- | ExprStringContainsFuzzy<QueryExpressionSpec>
121
- | ExprLogicalUnary<QueryExpressionSpec>
122
- | ExprLogicalVariadic<QueryExpressionSpec>
123
- | ExprIsIn<QueryExpressionSpec, string>
124
- | ExprIsIn<QueryExpressionSpec, number>;
128
+ | ExprNumericBinary<SpecQueryExpression>
129
+ | ExprNumericComparison<SpecQueryExpression>
130
+ | ExprNumericUnary<SpecQueryExpression>
131
+ | ExprStringEquals<SpecQueryExpression>
132
+ | ExprStringContains<SpecQueryExpression>
133
+ | ExprStringRegex<SpecQueryExpression>
134
+ | ExprStringContainsFuzzy<SpecQueryExpression>
135
+ | ExprIsNull<SpecQueryExpression>
136
+ | ExprIfNull<SpecQueryExpression>
137
+ | ExprLogicalUnary<SpecQueryExpression>
138
+ | ExprLogicalVariadic<SpecQueryExpression>
139
+ | ExprIsIn<SpecQueryExpression, string>
140
+ | ExprIsIn<SpecQueryExpression, number>;
125
141
 
126
- export type QueryBooleanExpressionSpec = InferBooleanExpressionUnion<QueryExpressionSpec>;
142
+ export type SpecQueryBooleanExpression = InferBooleanExpressionUnion<SpecQueryExpression>;
@@ -0,0 +1,205 @@
1
+ import { canonicalizeJson } from "../../../json";
2
+ import { assertNever } from "../../../util";
3
+ import type {
4
+ SpecQueryBooleanExpression,
5
+ SpecQueryExpression,
6
+ SpecQuery,
7
+ SpecQueryJoinEntry,
8
+ } from "./query_spec";
9
+
10
+ const booleanTypesSet = new Set<SpecQueryBooleanExpression["type"]>([
11
+ "numericComparison",
12
+ "stringEquals",
13
+ "stringContains",
14
+ "stringContainsFuzzy",
15
+ "stringRegex",
16
+ "isNull",
17
+ "not",
18
+ "and",
19
+ "or",
20
+ "isIn",
21
+ ]);
22
+
23
+ export function isBooleanExpression(expr: SpecQueryExpression): expr is SpecQueryBooleanExpression {
24
+ return booleanTypesSet.has(
25
+ // @ts-expect-error -- TypeScript doesn't understand the discriminated union here, but we do at runtime
26
+ expr.type,
27
+ );
28
+ }
29
+
30
+ /** Collects all column references from a SpecQuery tree. */
31
+ export function collectQueryColumns<C>(query: SpecQuery<C>): C[] {
32
+ const result: C[] = [];
33
+ collectQueryColumnsImpl(query, result);
34
+ return result;
35
+ }
36
+
37
+ function collectQueryColumnsImpl<C>(query: SpecQuery<C>, result: C[]): void {
38
+ switch (query.type) {
39
+ case "column":
40
+ result.push(query.column);
41
+ break;
42
+ case "sparseToDenseColumn":
43
+ result.push(query.column);
44
+ break;
45
+ case "inlineColumn":
46
+ break;
47
+ case "innerJoin":
48
+ case "fullJoin":
49
+ for (const e of query.entries) collectQueryColumnsImpl(e.entry, result);
50
+ break;
51
+ case "outerJoin":
52
+ collectQueryColumnsImpl(query.primary.entry, result);
53
+ for (const e of query.secondary) collectQueryColumnsImpl(e.entry, result);
54
+ break;
55
+ case "filter":
56
+ case "sort":
57
+ case "sliceAxes":
58
+ collectQueryColumnsImpl(query.input, result);
59
+ break;
60
+ default:
61
+ assertNever(query);
62
+ }
63
+ }
64
+
65
+ export function sortQuerySpec(query: SpecQuery): SpecQuery {
66
+ switch (query.type) {
67
+ case "column":
68
+ case "inlineColumn":
69
+ return query;
70
+ case "sparseToDenseColumn": {
71
+ const sortedAxesIndices = query.axesIndices.toSorted((lhs, rhs) => lhs - rhs);
72
+ return {
73
+ ...query,
74
+ axesIndices: sortedAxesIndices,
75
+ };
76
+ }
77
+ case "innerJoin":
78
+ case "fullJoin": {
79
+ const sortedEntries = query.entries.map(sortQueryJoinEntrySpec);
80
+ sortedEntries.sort(cmpQueryJoinEntrySpec);
81
+ return {
82
+ ...query,
83
+ entries: sortedEntries,
84
+ };
85
+ }
86
+ case "outerJoin": {
87
+ const sortedSecondary = query.secondary.map(sortQueryJoinEntrySpec);
88
+ sortedSecondary.sort(cmpQueryJoinEntrySpec);
89
+ return {
90
+ ...query,
91
+ primary: sortQueryJoinEntrySpec(query.primary),
92
+ secondary: sortedSecondary,
93
+ };
94
+ }
95
+ case "sliceAxes": {
96
+ const sortedAxisFilters = query.axisFilters.toSorted((lhs, rhs) => {
97
+ const lhsKey = canonicalizeJson(lhs.axisSelector);
98
+ const rhsKey = canonicalizeJson(rhs.axisSelector);
99
+ return lhsKey < rhsKey ? -1 : lhsKey === rhsKey ? 0 : 1;
100
+ });
101
+ return {
102
+ ...query,
103
+ input: sortQuerySpec(query.input),
104
+ axisFilters: sortedAxisFilters,
105
+ };
106
+ }
107
+ case "sort":
108
+ return {
109
+ ...query,
110
+ input: sortQuerySpec(query.input),
111
+ };
112
+ case "filter":
113
+ return {
114
+ ...query,
115
+ input: sortQuerySpec(query.input),
116
+ };
117
+ default:
118
+ assertNever(query);
119
+ }
120
+ }
121
+
122
+ function sortQueryJoinEntrySpec(entry: SpecQueryJoinEntry): SpecQueryJoinEntry {
123
+ const sortedQualifications = entry.qualifications.toSorted((lhs, rhs) => {
124
+ const lhsKey = canonicalizeJson(lhs.axis);
125
+ const rhsKey = canonicalizeJson(rhs.axis);
126
+ return lhsKey < rhsKey ? -1 : lhsKey === rhsKey ? 0 : 1;
127
+ });
128
+ return {
129
+ entry: sortQuerySpec(entry.entry),
130
+ qualifications: sortedQualifications,
131
+ };
132
+ }
133
+
134
+ function cmpQuerySpec(lhs: SpecQuery, rhs: SpecQuery): number {
135
+ if (lhs.type !== rhs.type) {
136
+ return lhs.type < rhs.type ? -1 : 1;
137
+ }
138
+ switch (lhs.type) {
139
+ case "column":
140
+ return lhs.column < (rhs as typeof lhs).column
141
+ ? -1
142
+ : lhs.column === (rhs as typeof lhs).column
143
+ ? 0
144
+ : 1;
145
+ case "inlineColumn":
146
+ return lhs.spec.id < (rhs as typeof lhs).spec.id
147
+ ? -1
148
+ : lhs.spec.id === (rhs as typeof lhs).spec.id
149
+ ? 0
150
+ : 1;
151
+ case "sparseToDenseColumn":
152
+ return lhs.column < (rhs as typeof lhs).column
153
+ ? -1
154
+ : lhs.column === (rhs as typeof lhs).column
155
+ ? 0
156
+ : 1;
157
+ case "innerJoin":
158
+ case "fullJoin": {
159
+ const rhsJoin = rhs as typeof lhs;
160
+ if (lhs.entries.length !== rhsJoin.entries.length) {
161
+ return lhs.entries.length - rhsJoin.entries.length;
162
+ }
163
+ for (let i = 0; i < lhs.entries.length; i++) {
164
+ const cmp = cmpQueryJoinEntrySpec(lhs.entries[i], rhsJoin.entries[i]);
165
+ if (cmp !== 0) return cmp;
166
+ }
167
+ return 0;
168
+ }
169
+ case "outerJoin": {
170
+ const rhsOuter = rhs as typeof lhs;
171
+ const cmp = cmpQueryJoinEntrySpec(lhs.primary, rhsOuter.primary);
172
+ if (cmp !== 0) return cmp;
173
+ if (lhs.secondary.length !== rhsOuter.secondary.length) {
174
+ return lhs.secondary.length - rhsOuter.secondary.length;
175
+ }
176
+ for (let i = 0; i < lhs.secondary.length; i++) {
177
+ const cmp = cmpQueryJoinEntrySpec(lhs.secondary[i], rhsOuter.secondary[i]);
178
+ if (cmp !== 0) return cmp;
179
+ }
180
+ return 0;
181
+ }
182
+ case "sliceAxes":
183
+ return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
184
+ case "sort":
185
+ return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
186
+ case "filter":
187
+ return cmpQuerySpec(lhs.input, (rhs as typeof lhs).input);
188
+ default:
189
+ assertNever(lhs);
190
+ }
191
+ }
192
+
193
+ function cmpQueryJoinEntrySpec(lhs: SpecQueryJoinEntry, rhs: SpecQueryJoinEntry): number {
194
+ const cmp = cmpQuerySpec(lhs.entry, rhs.entry);
195
+ if (cmp !== 0) return cmp;
196
+ if (lhs.qualifications.length !== rhs.qualifications.length) {
197
+ return lhs.qualifications.length - rhs.qualifications.length;
198
+ }
199
+ for (let i = 0; i < lhs.qualifications.length; i++) {
200
+ const lhsQ = canonicalizeJson(lhs.qualifications[i]);
201
+ const rhsQ = canonicalizeJson(rhs.qualifications[i]);
202
+ if (lhsQ !== rhsQ) return lhsQ < rhsQ ? -1 : 1;
203
+ }
204
+ return 0;
205
+ }