@platforma-sdk/model 1.53.15 → 1.54.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 (135) hide show
  1. package/dist/annotations/converter.cjs +8 -32
  2. package/dist/annotations/converter.cjs.map +1 -1
  3. package/dist/annotations/converter.d.ts.map +1 -1
  4. package/dist/annotations/converter.js +7 -31
  5. package/dist/annotations/converter.js.map +1 -1
  6. package/dist/block_model.cjs +1 -0
  7. package/dist/block_model.cjs.map +1 -1
  8. package/dist/block_model.d.ts.map +1 -1
  9. package/dist/block_model.js +1 -0
  10. package/dist/block_model.js.map +1 -1
  11. package/dist/builder.cjs +1 -0
  12. package/dist/builder.cjs.map +1 -1
  13. package/dist/builder.d.ts.map +1 -1
  14. package/dist/builder.js +1 -0
  15. package/dist/builder.js.map +1 -1
  16. package/dist/components/PlDataTable/index.d.ts +5 -0
  17. package/dist/components/PlDataTable/index.d.ts.map +1 -0
  18. package/dist/components/PlDataTable/labels.cjs +91 -0
  19. package/dist/components/PlDataTable/labels.cjs.map +1 -0
  20. package/dist/components/PlDataTable/labels.d.ts +7 -0
  21. package/dist/components/PlDataTable/labels.d.ts.map +1 -0
  22. package/dist/components/PlDataTable/labels.js +88 -0
  23. package/dist/components/PlDataTable/labels.js.map +1 -0
  24. package/dist/components/PlDataTable/state-migration.cjs +162 -0
  25. package/dist/components/PlDataTable/state-migration.cjs.map +1 -0
  26. package/dist/components/PlDataTable/state-migration.d.ts +94 -0
  27. package/dist/components/PlDataTable/state-migration.d.ts.map +1 -0
  28. package/dist/components/PlDataTable/state-migration.js +158 -0
  29. package/dist/components/PlDataTable/state-migration.js.map +1 -0
  30. package/dist/components/PlDataTable/table.cjs +187 -0
  31. package/dist/components/PlDataTable/table.cjs.map +1 -0
  32. package/dist/components/PlDataTable/table.d.ts +26 -0
  33. package/dist/components/PlDataTable/table.d.ts.map +1 -0
  34. package/dist/components/PlDataTable/table.js +183 -0
  35. package/dist/components/PlDataTable/table.js.map +1 -0
  36. package/dist/components/PlDataTable/v4.d.ts +157 -0
  37. package/dist/components/PlDataTable/v4.d.ts.map +1 -0
  38. package/dist/components/PlDataTable/v5.d.ts +114 -0
  39. package/dist/components/PlDataTable/v5.d.ts.map +1 -0
  40. package/dist/filters/converters/filterToQuery.cjs +244 -0
  41. package/dist/filters/converters/filterToQuery.cjs.map +1 -0
  42. package/dist/filters/converters/filterToQuery.d.ts +4 -0
  43. package/dist/filters/converters/filterToQuery.d.ts.map +1 -0
  44. package/dist/filters/converters/filterToQuery.js +242 -0
  45. package/dist/filters/converters/filterToQuery.js.map +1 -0
  46. package/dist/filters/{converter.cjs → converters/filterUiToExpressionImpl.cjs} +3 -2
  47. package/dist/filters/converters/filterUiToExpressionImpl.cjs.map +1 -0
  48. package/dist/filters/{converter.d.ts → converters/filterUiToExpressionImpl.d.ts} +2 -2
  49. package/dist/filters/converters/filterUiToExpressionImpl.d.ts.map +1 -0
  50. package/dist/filters/{converter.js → converters/filterUiToExpressionImpl.js} +3 -2
  51. package/dist/filters/converters/filterUiToExpressionImpl.js.map +1 -0
  52. package/dist/filters/converters/index.d.ts +3 -0
  53. package/dist/filters/converters/index.d.ts.map +1 -0
  54. package/dist/filters/distill.cjs +61 -0
  55. package/dist/filters/distill.cjs.map +1 -0
  56. package/dist/filters/distill.d.ts +7 -0
  57. package/dist/filters/distill.d.ts.map +1 -0
  58. package/dist/filters/distill.js +59 -0
  59. package/dist/filters/distill.js.map +1 -0
  60. package/dist/filters/index.d.ts +2 -1
  61. package/dist/filters/index.d.ts.map +1 -1
  62. package/dist/filters/types.d.ts +1 -117
  63. package/dist/filters/types.d.ts.map +1 -1
  64. package/dist/index.cjs +17 -15
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.js +5 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/package.json.cjs +1 -1
  69. package/dist/package.json.js +1 -1
  70. package/dist/pframe_utils/columns.cjs +2 -0
  71. package/dist/pframe_utils/columns.cjs.map +1 -1
  72. package/dist/pframe_utils/columns.js +2 -0
  73. package/dist/pframe_utils/columns.js.map +1 -1
  74. package/dist/pframe_utils/index.cjs +2 -0
  75. package/dist/pframe_utils/index.cjs.map +1 -1
  76. package/dist/pframe_utils/index.js +2 -0
  77. package/dist/pframe_utils/index.js.map +1 -1
  78. package/dist/pframe_utils/querySpec.d.ts +2 -0
  79. package/dist/pframe_utils/querySpec.d.ts.map +1 -0
  80. package/dist/render/api.cjs +7 -0
  81. package/dist/render/api.cjs.map +1 -1
  82. package/dist/render/api.d.ts +3 -2
  83. package/dist/render/api.d.ts.map +1 -1
  84. package/dist/render/api.js +8 -1
  85. package/dist/render/api.js.map +1 -1
  86. package/dist/render/future.d.ts +1 -1
  87. package/dist/render/index.d.ts +2 -1
  88. package/dist/render/index.d.ts.map +1 -1
  89. package/dist/render/internal.cjs.map +1 -1
  90. package/dist/render/internal.d.ts +4 -1
  91. package/dist/render/internal.d.ts.map +1 -1
  92. package/dist/render/internal.js.map +1 -1
  93. package/dist/render/util/column_collection.cjs.map +1 -1
  94. package/dist/render/util/column_collection.d.ts +1 -1
  95. package/dist/render/util/column_collection.d.ts.map +1 -1
  96. package/dist/render/util/column_collection.js.map +1 -1
  97. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  98. package/dist/render/util/pcolumn_data.d.ts +1 -1
  99. package/dist/render/util/pcolumn_data.d.ts.map +1 -1
  100. package/dist/render/util/pcolumn_data.js.map +1 -1
  101. package/package.json +8 -6
  102. package/src/annotations/converter.ts +12 -40
  103. package/src/block_model.ts +1 -0
  104. package/src/builder.ts +1 -0
  105. package/src/components/PlDataTable/index.ts +24 -0
  106. package/src/components/PlDataTable/labels.ts +101 -0
  107. package/src/components/PlDataTable/state-migration.ts +285 -0
  108. package/src/components/PlDataTable/table.ts +278 -0
  109. package/src/components/PlDataTable/v4.ts +193 -0
  110. package/src/components/PlDataTable/v5.ts +140 -0
  111. package/src/filters/converters/filterToQuery.test.ts +417 -0
  112. package/src/filters/converters/filterToQuery.ts +258 -0
  113. package/src/filters/{converter.test.ts → converters/filterUiToExpressionImpl.test.ts} +2 -2
  114. package/src/filters/{converter.ts → converters/filterUiToExpressionImpl.ts} +3 -2
  115. package/src/filters/converters/index.ts +2 -0
  116. package/src/filters/distill.test.ts +191 -0
  117. package/src/filters/distill.ts +67 -0
  118. package/src/filters/index.ts +2 -1
  119. package/src/filters/types.ts +7 -48
  120. package/src/pframe_utils/querySpec.ts +1 -0
  121. package/src/render/api.ts +13 -6
  122. package/src/render/index.ts +2 -1
  123. package/src/render/internal.ts +11 -0
  124. package/src/render/util/column_collection.ts +1 -1
  125. package/src/render/util/pcolumn_data.ts +1 -1
  126. package/dist/components/PlDataTable.cjs +0 -307
  127. package/dist/components/PlDataTable.cjs.map +0 -1
  128. package/dist/components/PlDataTable.d.ts +0 -366
  129. package/dist/components/PlDataTable.d.ts.map +0 -1
  130. package/dist/components/PlDataTable.js +0 -297
  131. package/dist/components/PlDataTable.js.map +0 -1
  132. package/dist/filters/converter.cjs.map +0 -1
  133. package/dist/filters/converter.d.ts.map +0 -1
  134. package/dist/filters/converter.js.map +0 -1
  135. package/src/components/PlDataTable.ts +0 -794
@@ -0,0 +1,417 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { FilterSpec, FilterSpecLeaf } from "@milaboratories/pl-model-common";
3
+ import { filterSpecToSpecQueryExpr } from "./filterToQuery";
4
+
5
+ type QFilterSpec = FilterSpec<FilterSpecLeaf<string>>;
6
+
7
+ /** Helper: creates a CanonicalizedJson<PTableColumnId> for a regular column. */
8
+ function colRef(id: string): string {
9
+ return JSON.stringify({ type: "column", id });
10
+ }
11
+
12
+ /** Helper: creates a CanonicalizedJson<PTableColumnId> for an axis. */
13
+ function axisRef(id: string): string {
14
+ return JSON.stringify({ type: "axis", id });
15
+ }
16
+
17
+ describe("filterSpecToSpecQueryExpr", () => {
18
+ // --- logical combinators ---
19
+
20
+ it('should convert "and" filter', () => {
21
+ const filter: QFilterSpec = {
22
+ type: "and",
23
+ filters: [
24
+ { type: "equal", column: colRef("c1"), x: 1 },
25
+ { type: "equal", column: colRef("c2"), x: 2 },
26
+ ],
27
+ };
28
+ const result = filterSpecToSpecQueryExpr(filter);
29
+ expect(result.type).toBe("and");
30
+ if (result.type === "and") {
31
+ expect(result.input).toHaveLength(2);
32
+ }
33
+ });
34
+
35
+ it('should convert "or" filter', () => {
36
+ const filter: QFilterSpec = {
37
+ type: "or",
38
+ filters: [
39
+ { type: "equal", column: colRef("c1"), x: 1 },
40
+ { type: "equal", column: colRef("c2"), x: 2 },
41
+ ],
42
+ };
43
+ const result = filterSpecToSpecQueryExpr(filter);
44
+ expect(result.type).toBe("or");
45
+ if (result.type === "or") {
46
+ expect(result.input).toHaveLength(2);
47
+ }
48
+ });
49
+
50
+ it('should convert "not" filter', () => {
51
+ const filter: QFilterSpec = {
52
+ type: "not",
53
+ filter: { type: "equal", column: colRef("c1"), x: 5 },
54
+ };
55
+ const result = filterSpecToSpecQueryExpr(filter);
56
+ expect(result.type).toBe("not");
57
+ if (result.type === "not") {
58
+ expect(result.input.type).toBe("numericComparison");
59
+ }
60
+ });
61
+
62
+ it("should skip filters with undefined type in and/or", () => {
63
+ const filter: QFilterSpec = {
64
+ type: "and",
65
+ filters: [{ type: undefined }, { type: "equal", column: colRef("c1"), x: 1 }],
66
+ };
67
+ const result = filterSpecToSpecQueryExpr(filter);
68
+ expect(result.type).toBe("and");
69
+ if (result.type === "and") {
70
+ expect(result.input).toHaveLength(1);
71
+ expect(result.input[0].type).toBe("numericComparison");
72
+ }
73
+ });
74
+
75
+ it("should throw for empty and filter (after skipping undefined)", () => {
76
+ const filter: QFilterSpec = {
77
+ type: "and",
78
+ filters: [{ type: undefined }],
79
+ };
80
+ expect(() => filterSpecToSpecQueryExpr(filter)).toThrow(
81
+ "AND filter requires at least one operand",
82
+ );
83
+ });
84
+
85
+ it("should throw for empty or filter", () => {
86
+ const filter: QFilterSpec = { type: "or", filters: [] };
87
+ expect(() => filterSpecToSpecQueryExpr(filter)).toThrow(
88
+ "OR filter requires at least one operand",
89
+ );
90
+ });
91
+
92
+ // --- string filters ---
93
+
94
+ it('should convert "patternEquals" filter', () => {
95
+ const filter: QFilterSpec = {
96
+ type: "patternEquals",
97
+ column: colRef("col1"),
98
+ value: "abc",
99
+ };
100
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
101
+ type: "stringEquals",
102
+ input: { type: "columnRef", value: "col1" },
103
+ value: "abc",
104
+ caseInsensitive: false,
105
+ });
106
+ });
107
+
108
+ it('should convert "patternNotEquals" filter', () => {
109
+ const filter: QFilterSpec = {
110
+ type: "patternNotEquals",
111
+ column: colRef("col1"),
112
+ value: "abc",
113
+ };
114
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
115
+ type: "not",
116
+ input: {
117
+ type: "stringEquals",
118
+ input: { type: "columnRef", value: "col1" },
119
+ value: "abc",
120
+ caseInsensitive: false,
121
+ },
122
+ });
123
+ });
124
+
125
+ it('should convert "patternContainSubsequence" filter', () => {
126
+ const filter: QFilterSpec = {
127
+ type: "patternContainSubsequence",
128
+ column: colRef("col1"),
129
+ value: "sub",
130
+ };
131
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
132
+ type: "stringContains",
133
+ input: { type: "columnRef", value: "col1" },
134
+ value: "sub",
135
+ caseInsensitive: false,
136
+ });
137
+ });
138
+
139
+ it('should convert "patternNotContainSubsequence" filter', () => {
140
+ const filter: QFilterSpec = {
141
+ type: "patternNotContainSubsequence",
142
+ column: colRef("col1"),
143
+ value: "sub",
144
+ };
145
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
146
+ type: "not",
147
+ input: {
148
+ type: "stringContains",
149
+ input: { type: "columnRef", value: "col1" },
150
+ value: "sub",
151
+ caseInsensitive: false,
152
+ },
153
+ });
154
+ });
155
+
156
+ it('should convert "patternMatchesRegularExpression" filter', () => {
157
+ const filter: QFilterSpec = {
158
+ type: "patternMatchesRegularExpression",
159
+ column: colRef("col1"),
160
+ value: "^abc.*",
161
+ };
162
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
163
+ type: "stringRegex",
164
+ input: { type: "columnRef", value: "col1" },
165
+ value: "^abc.*",
166
+ });
167
+ });
168
+
169
+ it('should convert "patternFuzzyContainSubsequence" filter with defaults', () => {
170
+ const filter: QFilterSpec = {
171
+ type: "patternFuzzyContainSubsequence",
172
+ column: colRef("col1"),
173
+ value: "fuz",
174
+ };
175
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
176
+ type: "stringContainsFuzzy",
177
+ input: { type: "columnRef", value: "col1" },
178
+ value: "fuz",
179
+ maxEdits: 1,
180
+ caseInsensitive: false,
181
+ substitutionsOnly: false,
182
+ wildcard: null,
183
+ });
184
+ });
185
+
186
+ it('should convert "patternFuzzyContainSubsequence" filter with custom options', () => {
187
+ const filter: QFilterSpec = {
188
+ type: "patternFuzzyContainSubsequence",
189
+ column: colRef("col1"),
190
+ value: "fuz",
191
+ maxEdits: 3,
192
+ substitutionsOnly: true,
193
+ wildcard: "?",
194
+ };
195
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
196
+ type: "stringContainsFuzzy",
197
+ input: { type: "columnRef", value: "col1" },
198
+ value: "fuz",
199
+ maxEdits: 3,
200
+ caseInsensitive: false,
201
+ substitutionsOnly: true,
202
+ wildcard: "?",
203
+ });
204
+ });
205
+
206
+ // --- numeric comparison filters ---
207
+
208
+ it("should convert numeric comparison filters", () => {
209
+ const testCases = [
210
+ { type: "equal" as const, operand: "eq" },
211
+ { type: "notEqual" as const, operand: "ne" },
212
+ { type: "lessThan" as const, operand: "lt" },
213
+ { type: "greaterThan" as const, operand: "gt" },
214
+ { type: "lessThanOrEqual" as const, operand: "le" },
215
+ { type: "greaterThanOrEqual" as const, operand: "ge" },
216
+ ];
217
+
218
+ testCases.forEach(({ type, operand }) => {
219
+ const filter: QFilterSpec = { type, column: colRef("num"), x: 42 };
220
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
221
+ type: "numericComparison",
222
+ operand,
223
+ left: { type: "columnRef", value: "num" },
224
+ right: { type: "constant", value: 42 },
225
+ });
226
+ });
227
+ });
228
+
229
+ // --- column-to-column comparisons ---
230
+
231
+ it('should convert "equalToColumn" filter', () => {
232
+ const filter: QFilterSpec = {
233
+ type: "equalToColumn",
234
+ column: colRef("c1"),
235
+ rhs: colRef("c2"),
236
+ };
237
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
238
+ type: "numericComparison",
239
+ operand: "eq",
240
+ left: { type: "columnRef", value: "c1" },
241
+ right: { type: "columnRef", value: "c2" },
242
+ });
243
+ });
244
+
245
+ it("should convert column comparison filters without minDiff", () => {
246
+ const cases = [
247
+ { type: "lessThanColumn" as const, operand: "lt" },
248
+ { type: "greaterThanColumn" as const, operand: "gt" },
249
+ { type: "lessThanColumnOrEqual" as const, operand: "le" },
250
+ { type: "greaterThanColumnOrEqual" as const, operand: "ge" },
251
+ ];
252
+
253
+ cases.forEach(({ type, operand }) => {
254
+ const filter: QFilterSpec = { type, column: colRef("c1"), rhs: colRef("c2") };
255
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
256
+ type: "numericComparison",
257
+ operand,
258
+ left: { type: "columnRef", value: "c1" },
259
+ right: { type: "columnRef", value: "c2" },
260
+ });
261
+ });
262
+ });
263
+
264
+ it("should convert column comparison filters with minDiff", () => {
265
+ const cases = [
266
+ { type: "lessThanColumn" as const, operand: "lt" },
267
+ { type: "greaterThanColumn" as const, operand: "gt" },
268
+ { type: "lessThanColumnOrEqual" as const, operand: "le" },
269
+ { type: "greaterThanColumnOrEqual" as const, operand: "ge" },
270
+ ];
271
+
272
+ cases.forEach(({ type, operand }) => {
273
+ const filter: QFilterSpec = { type, column: colRef("c1"), rhs: colRef("c2"), minDiff: 5 };
274
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
275
+ type: "numericComparison",
276
+ operand,
277
+ left: {
278
+ type: "numericBinary",
279
+ operand: "add",
280
+ left: { type: "columnRef", value: "c1" },
281
+ right: { type: "constant", value: 5 },
282
+ },
283
+ right: { type: "columnRef", value: "c2" },
284
+ });
285
+ });
286
+ });
287
+
288
+ it("should treat minDiff=0 as no minDiff", () => {
289
+ const filter: QFilterSpec = {
290
+ type: "lessThanColumn",
291
+ column: colRef("c1"),
292
+ rhs: colRef("c2"),
293
+ minDiff: 0,
294
+ };
295
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
296
+ type: "numericComparison",
297
+ operand: "lt",
298
+ left: { type: "columnRef", value: "c1" },
299
+ right: { type: "columnRef", value: "c2" },
300
+ });
301
+ });
302
+
303
+ // --- set filters ---
304
+
305
+ it('should convert "inSet" filter', () => {
306
+ const filter: QFilterSpec = {
307
+ type: "inSet",
308
+ column: colRef("col1"),
309
+ value: ["a", "b", "c"],
310
+ };
311
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
312
+ type: "isIn",
313
+ input: { type: "columnRef", value: "col1" },
314
+ set: ["a", "b", "c"],
315
+ });
316
+ });
317
+
318
+ it('should convert "notInSet" filter', () => {
319
+ const filter: QFilterSpec = {
320
+ type: "notInSet",
321
+ column: colRef("col1"),
322
+ value: ["x"],
323
+ };
324
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
325
+ type: "not",
326
+ input: {
327
+ type: "isIn",
328
+ input: { type: "columnRef", value: "col1" },
329
+ set: ["x"],
330
+ },
331
+ });
332
+ });
333
+
334
+ // --- axis references ---
335
+
336
+ it("should resolve axis column references", () => {
337
+ const filter: QFilterSpec = {
338
+ type: "equal",
339
+ column: axisRef("pl7.app/sampleId"),
340
+ x: 10,
341
+ };
342
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
343
+ type: "numericComparison",
344
+ operand: "eq",
345
+ left: { type: "axisRef", value: "pl7.app/sampleId" },
346
+ right: { type: "constant", value: 10 },
347
+ });
348
+ });
349
+
350
+ // --- null check filters ---
351
+
352
+ it('should convert "isNA" filter', () => {
353
+ const filter: QFilterSpec = { type: "isNA", column: colRef("c1") };
354
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
355
+ type: "isNull",
356
+ input: { type: "columnRef", value: "c1" },
357
+ });
358
+ });
359
+
360
+ it('should convert "isNotNA" filter', () => {
361
+ const filter: QFilterSpec = { type: "isNotNA", column: colRef("c1") };
362
+ expect(filterSpecToSpecQueryExpr(filter)).toEqual({
363
+ type: "not",
364
+ input: {
365
+ type: "isNull",
366
+ input: { type: "columnRef", value: "c1" },
367
+ },
368
+ });
369
+ });
370
+
371
+ // --- unsupported types ---
372
+
373
+ it("should throw for topN filter", () => {
374
+ const filter: QFilterSpec = { type: "topN", column: colRef("c1"), n: 5 };
375
+ expect(() => filterSpecToSpecQueryExpr(filter)).toThrow('Filter type "topN" is not supported');
376
+ });
377
+
378
+ it("should throw for bottomN filter", () => {
379
+ const filter: QFilterSpec = { type: "bottomN", column: colRef("c1"), n: 3 };
380
+ expect(() => filterSpecToSpecQueryExpr(filter)).toThrow(
381
+ 'Filter type "bottomN" is not supported',
382
+ );
383
+ });
384
+
385
+ it("should throw for undefined filter type", () => {
386
+ const filter: QFilterSpec = { type: undefined };
387
+ expect(() => filterSpecToSpecQueryExpr(filter)).toThrow("Filter type is undefined");
388
+ });
389
+
390
+ // --- nested ---
391
+
392
+ it("should convert nested and/or/not filters", () => {
393
+ const filter: QFilterSpec = {
394
+ type: "and",
395
+ filters: [
396
+ {
397
+ type: "or",
398
+ filters: [
399
+ { type: "patternEquals", column: colRef("col1"), value: "a" },
400
+ { type: "patternEquals", column: colRef("col1"), value: "b" },
401
+ ],
402
+ },
403
+ {
404
+ type: "not",
405
+ filter: { type: "greaterThan", column: colRef("num"), x: 100 },
406
+ },
407
+ ],
408
+ };
409
+ const result = filterSpecToSpecQueryExpr(filter);
410
+ expect(result.type).toBe("and");
411
+ if (result.type === "and") {
412
+ expect(result.input).toHaveLength(2);
413
+ expect(result.input[0].type).toBe("or");
414
+ expect(result.input[1].type).toBe("not");
415
+ }
416
+ });
417
+ });
@@ -0,0 +1,258 @@
1
+ import { assertNever } from "@milaboratories/pl-model-common";
2
+ import type {
3
+ FilterSpec,
4
+ FilterSpecLeaf,
5
+ PTableColumnId,
6
+ SingleAxisSelector,
7
+ SpecQueryExpression,
8
+ } from "@milaboratories/pl-model-common";
9
+
10
+ /** Parses a CanonicalizedJson<PTableColumnId> string into a SpecQueryExpression reference. */
11
+ function resolveColumnRef(columnStr: string): SpecQueryExpression {
12
+ const parsed = JSON.parse(columnStr) as PTableColumnId;
13
+ if (parsed.type === "axis") {
14
+ return { type: "axisRef", value: parsed.id as SingleAxisSelector };
15
+ }
16
+ return { type: "columnRef", value: parsed.id };
17
+ }
18
+
19
+ /** Converts a FilterSpec tree into a SpecQueryExpression. */
20
+ export function filterSpecToSpecQueryExpr(
21
+ filter: FilterSpec<FilterSpecLeaf<string>>,
22
+ ): SpecQueryExpression {
23
+ switch (filter.type) {
24
+ case "and":
25
+ case "or": {
26
+ const inputs = filter.filters
27
+ .filter((f) => f.type !== undefined)
28
+ .map(filterSpecToSpecQueryExpr);
29
+ if (inputs.length === 0) {
30
+ throw new Error(`${filter.type.toUpperCase()} filter requires at least one operand`);
31
+ }
32
+ return { type: filter.type, input: inputs };
33
+ }
34
+ case "not":
35
+ return { type: "not", input: filterSpecToSpecQueryExpr(filter.filter) };
36
+
37
+ case "patternEquals":
38
+ return {
39
+ type: "stringEquals",
40
+ input: resolveColumnRef(filter.column),
41
+ value: filter.value,
42
+ caseInsensitive: false,
43
+ };
44
+ case "patternNotEquals":
45
+ return {
46
+ type: "not",
47
+ input: {
48
+ type: "stringEquals",
49
+ input: resolveColumnRef(filter.column),
50
+ value: filter.value,
51
+ caseInsensitive: false,
52
+ },
53
+ };
54
+ case "patternContainSubsequence":
55
+ return {
56
+ type: "stringContains",
57
+ input: resolveColumnRef(filter.column),
58
+ value: filter.value,
59
+ caseInsensitive: false,
60
+ };
61
+ case "patternNotContainSubsequence":
62
+ return {
63
+ type: "not",
64
+ input: {
65
+ type: "stringContains",
66
+ input: resolveColumnRef(filter.column),
67
+ value: filter.value,
68
+ caseInsensitive: false,
69
+ },
70
+ };
71
+ case "patternMatchesRegularExpression":
72
+ return {
73
+ type: "stringRegex",
74
+ input: resolveColumnRef(filter.column),
75
+ value: filter.value,
76
+ };
77
+ case "patternFuzzyContainSubsequence":
78
+ return {
79
+ type: "stringContainsFuzzy",
80
+ input: resolveColumnRef(filter.column),
81
+ value: filter.value,
82
+ maxEdits: filter.maxEdits ?? 1,
83
+ caseInsensitive: false,
84
+ substitutionsOnly: filter.substitutionsOnly ?? false,
85
+ wildcard: filter.wildcard ?? null,
86
+ };
87
+
88
+ case "equal":
89
+ return {
90
+ type: "numericComparison",
91
+ operand: "eq",
92
+ left: resolveColumnRef(filter.column),
93
+ right: { type: "constant", value: filter.x },
94
+ };
95
+ case "notEqual":
96
+ return {
97
+ type: "numericComparison",
98
+ operand: "ne",
99
+ left: resolveColumnRef(filter.column),
100
+ right: { type: "constant", value: filter.x },
101
+ };
102
+ case "lessThan":
103
+ return {
104
+ type: "numericComparison",
105
+ operand: "lt",
106
+ left: resolveColumnRef(filter.column),
107
+ right: { type: "constant", value: filter.x },
108
+ };
109
+ case "greaterThan":
110
+ return {
111
+ type: "numericComparison",
112
+ operand: "gt",
113
+ left: resolveColumnRef(filter.column),
114
+ right: { type: "constant", value: filter.x },
115
+ };
116
+ case "lessThanOrEqual":
117
+ return {
118
+ type: "numericComparison",
119
+ operand: "le",
120
+ left: resolveColumnRef(filter.column),
121
+ right: { type: "constant", value: filter.x },
122
+ };
123
+ case "greaterThanOrEqual":
124
+ return {
125
+ type: "numericComparison",
126
+ operand: "ge",
127
+ left: resolveColumnRef(filter.column),
128
+ right: { type: "constant", value: filter.x },
129
+ };
130
+
131
+ case "equalToColumn":
132
+ return {
133
+ type: "numericComparison",
134
+ operand: "eq",
135
+ left: resolveColumnRef(filter.column),
136
+ right: resolveColumnRef(filter.rhs),
137
+ };
138
+ case "lessThanColumn": {
139
+ const left = resolveColumnRef(filter.column);
140
+ const right = resolveColumnRef(filter.rhs);
141
+ if (filter.minDiff !== undefined && filter.minDiff !== 0) {
142
+ return {
143
+ type: "numericComparison",
144
+ operand: "lt",
145
+ left: {
146
+ type: "numericBinary",
147
+ operand: "add",
148
+ left,
149
+ right: { type: "constant", value: filter.minDiff },
150
+ },
151
+ right,
152
+ };
153
+ }
154
+ return { type: "numericComparison", operand: "lt", left, right };
155
+ }
156
+ case "greaterThanColumn": {
157
+ const left = resolveColumnRef(filter.column);
158
+ const right = resolveColumnRef(filter.rhs);
159
+ if (filter.minDiff !== undefined && filter.minDiff !== 0) {
160
+ return {
161
+ type: "numericComparison",
162
+ operand: "gt",
163
+ left: {
164
+ type: "numericBinary",
165
+ operand: "add",
166
+ left,
167
+ right: { type: "constant", value: filter.minDiff },
168
+ },
169
+ right,
170
+ };
171
+ }
172
+ return { type: "numericComparison", operand: "gt", left, right };
173
+ }
174
+ case "lessThanColumnOrEqual": {
175
+ const left = resolveColumnRef(filter.column);
176
+ const right = resolveColumnRef(filter.rhs);
177
+ if (filter.minDiff !== undefined && filter.minDiff !== 0) {
178
+ return {
179
+ type: "numericComparison",
180
+ operand: "le",
181
+ left: {
182
+ type: "numericBinary",
183
+ operand: "add",
184
+ left,
185
+ right: { type: "constant", value: filter.minDiff },
186
+ },
187
+ right,
188
+ };
189
+ }
190
+ return { type: "numericComparison", operand: "le", left, right };
191
+ }
192
+ case "greaterThanColumnOrEqual": {
193
+ const left = resolveColumnRef(filter.column);
194
+ const right = resolveColumnRef(filter.rhs);
195
+ if (filter.minDiff !== undefined && filter.minDiff !== 0) {
196
+ return {
197
+ type: "numericComparison",
198
+ operand: "ge",
199
+ left: {
200
+ type: "numericBinary",
201
+ operand: "add",
202
+ left,
203
+ right: { type: "constant", value: filter.minDiff },
204
+ },
205
+ right,
206
+ };
207
+ }
208
+ return { type: "numericComparison", operand: "ge", left, right };
209
+ }
210
+
211
+ case "inSet":
212
+ return {
213
+ type: "isIn",
214
+ input: resolveColumnRef(filter.column),
215
+ set: filter.value,
216
+ };
217
+ case "notInSet":
218
+ return {
219
+ type: "not",
220
+ input: {
221
+ type: "isIn",
222
+ input: resolveColumnRef(filter.column),
223
+ set: filter.value,
224
+ },
225
+ };
226
+
227
+ case "isNA":
228
+ return {
229
+ type: "isNull",
230
+ input: resolveColumnRef(filter.column),
231
+ };
232
+ case "isNotNA":
233
+ return {
234
+ type: "not",
235
+ input: {
236
+ type: "isNull",
237
+ input: resolveColumnRef(filter.column),
238
+ },
239
+ };
240
+
241
+ case "ifNa":
242
+ return {
243
+ type: "ifNull",
244
+ input: resolveColumnRef(filter.column),
245
+ replacement: { type: "constant", value: filter.replacement },
246
+ };
247
+
248
+ case "topN":
249
+ case "bottomN":
250
+ throw new Error(`Filter type "${filter.type}" is not supported in query expressions`);
251
+
252
+ case undefined:
253
+ throw new Error("Filter type is undefined");
254
+
255
+ default:
256
+ assertNever(filter);
257
+ }
258
+ }
@@ -1,7 +1,7 @@
1
1
  import type { SUniversalPColumnId } from "@milaboratories/pl-model-common";
2
2
  import { describe, expect, it } from "vitest";
3
- import { convertFilterUiToExpressions } from "./converter";
4
- import { FilterSpec } from "./types";
3
+ import { convertFilterUiToExpressions } from "./filterUiToExpressionImpl";
4
+ import { FilterSpec } from "../types";
5
5
 
6
6
  describe("convertFilterUiToExpressions", () => {
7
7
  it('should compile "or" filter to ptabler expression', () => {
@@ -8,7 +8,7 @@ import {
8
8
  type Expression,
9
9
  type ExpressionImpl,
10
10
  } from "@milaboratories/ptabler-expression-js";
11
- import type { FilterSpec } from "../filters";
11
+ import type { FilterSpec } from "../types";
12
12
 
13
13
  export function convertFilterUiToExpressionImpl(value: FilterSpec): ExpressionImpl {
14
14
  if (value.type === "or") {
@@ -127,7 +127,8 @@ export function convertFilterUiToExpressionImpl(value: FilterSpec): ExpressionIm
127
127
  value.type === "patternMatchesRegularExpression" ||
128
128
  value.type === "patternFuzzyContainSubsequence" ||
129
129
  value.type === "inSet" ||
130
- value.type === "notInSet"
130
+ value.type === "notInSet" ||
131
+ value.type === "ifNa"
131
132
  ) {
132
133
  throw new Error("Not implemented filter type: " + value.type);
133
134
  }
@@ -0,0 +1,2 @@
1
+ export * from "./filterToQuery";
2
+ export * from "./filterUiToExpressionImpl";