@finos/legend-data-cube 0.3.54 → 0.3.56

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 (37) hide show
  1. package/lib/components/view/filter/DataCubeFilterEditor.d.ts.map +1 -1
  2. package/lib/components/view/filter/DataCubeFilterEditor.js +228 -3
  3. package/lib/components/view/filter/DataCubeFilterEditor.js.map +1 -1
  4. package/lib/index.css +1 -1
  5. package/lib/package.json +1 -1
  6. package/lib/stores/core/DataCubeEngine.d.ts +3 -1
  7. package/lib/stores/core/DataCubeEngine.d.ts.map +1 -1
  8. package/lib/stores/core/DataCubeEngine.js +4 -0
  9. package/lib/stores/core/DataCubeEngine.js.map +1 -1
  10. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts +1 -1
  11. package/lib/stores/core/DataCubeQueryBuilderUtils.d.ts.map +1 -1
  12. package/lib/stores/core/DataCubeQueryBuilderUtils.js +7 -0
  13. package/lib/stores/core/DataCubeQueryBuilderUtils.js.map +1 -1
  14. package/lib/stores/core/DataCubeQueryEngine.d.ts +1 -0
  15. package/lib/stores/core/DataCubeQueryEngine.d.ts.map +1 -1
  16. package/lib/stores/core/DataCubeQueryEngine.js +17 -0
  17. package/lib/stores/core/DataCubeQueryEngine.js.map +1 -1
  18. package/lib/stores/core/DataCubeSnapshotBuilderUtils.d.ts.map +1 -1
  19. package/lib/stores/core/DataCubeSnapshotBuilderUtils.js +6 -0
  20. package/lib/stores/core/DataCubeSnapshotBuilderUtils.js.map +1 -1
  21. package/lib/stores/core/filter/DataCubeQueryFilterOperation__In.d.ts +38 -0
  22. package/lib/stores/core/filter/DataCubeQueryFilterOperation__In.d.ts.map +1 -0
  23. package/lib/stores/core/filter/DataCubeQueryFilterOperation__In.js +92 -0
  24. package/lib/stores/core/filter/DataCubeQueryFilterOperation__In.js.map +1 -0
  25. package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotIn.d.ts +38 -0
  26. package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotIn.d.ts.map +1 -0
  27. package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotIn.js +93 -0
  28. package/lib/stores/core/filter/DataCubeQueryFilterOperation__NotIn.js.map +1 -0
  29. package/package.json +5 -5
  30. package/src/components/view/filter/DataCubeFilterEditor.tsx +367 -3
  31. package/src/stores/core/DataCubeEngine.tsx +4 -0
  32. package/src/stores/core/DataCubeQueryBuilderUtils.ts +12 -1
  33. package/src/stores/core/DataCubeQueryEngine.ts +17 -0
  34. package/src/stores/core/DataCubeSnapshotBuilderUtils.ts +8 -1
  35. package/src/stores/core/filter/DataCubeQueryFilterOperation__In.tsx +127 -0
  36. package/src/stores/core/filter/DataCubeQueryFilterOperation__NotIn.tsx +138 -0
  37. package/tsconfig.json +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataCubeQueryFilterOperation__NotIn.js","sourceRoot":"","sources":["../../../../src/stores/core/filter/DataCubeQueryFilterOperation__NotIn.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finos/legend-data-cube",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "description": "Legend DataCube",
5
5
  "keywords": [
6
6
  "legend",
@@ -42,10 +42,10 @@
42
42
  "test:watch": "jest --watch"
43
43
  },
44
44
  "dependencies": {
45
- "@finos/legend-application": "16.0.87",
46
- "@finos/legend-art": "7.1.137",
47
- "@finos/legend-code-editor": "2.0.140",
48
- "@finos/legend-graph": "32.3.20",
45
+ "@finos/legend-application": "16.0.88",
46
+ "@finos/legend-art": "7.1.138",
47
+ "@finos/legend-code-editor": "2.0.142",
48
+ "@finos/legend-graph": "32.3.22",
49
49
  "@finos/legend-shared": "11.0.22",
50
50
  "@types/react": "19.0.10",
51
51
  "ag-grid-community": "33.1.1",
@@ -44,14 +44,26 @@ import {
44
44
  FormDropdownMenuTrigger,
45
45
  } from '../../core/DataCubeFormUtils.js';
46
46
  import type { DataCubeViewState } from '../../../stores/view/DataCubeViewState.js';
47
- import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
47
+ import {
48
+ forwardRef,
49
+ useCallback,
50
+ useEffect,
51
+ useRef,
52
+ useState,
53
+ useMemo,
54
+ } from 'react';
48
55
  import {
49
56
  DATE_FORMAT,
50
57
  PRECISE_PRIMITIVE_TYPE,
51
58
  PRIMITIVE_TYPE,
52
59
  } from '@finos/legend-graph';
53
60
 
54
- import { formatDate, guaranteeIsNumber, parseISO } from '@finos/legend-shared';
61
+ import {
62
+ formatDate,
63
+ guaranteeIsNumber,
64
+ isNullable,
65
+ parseISO,
66
+ } from '@finos/legend-shared';
55
67
  import { evaluate } from 'mathjs';
56
68
  import { useDataCube } from '../../DataCubeProvider.js';
57
69
  import { _findCol } from '../../../stores/core/model/DataCubeColumn.js';
@@ -360,6 +372,349 @@ const DataCubeEditorFilterConditionNodeColumnSelector = observer(
360
372
  }),
361
373
  );
362
374
 
375
+ const DataCubeEditorFilterConditionNodeListValueEditor = observer(
376
+ forwardRef<
377
+ HTMLElement,
378
+ {
379
+ value: DataCubeOperationValue;
380
+ updateValue: (value: unknown) => void;
381
+ view: DataCubeViewState;
382
+ }
383
+ >(function DataCubeEditorFilterConditionNodeListValueEditor(props, ref) {
384
+ const { value, updateValue } = props;
385
+ const items = useMemo(
386
+ () =>
387
+ Array.isArray(value.value)
388
+ ? (value.value as DataCubeOperationValue[])
389
+ : [],
390
+ [value.value],
391
+ );
392
+ const idSeq = useRef(0);
393
+ const makeId = () => `${Date.now()}_${idSeq.current++}`;
394
+ type LocalItem = { id: string; value: DataCubeOperationValue };
395
+ const [localItems, setLocalItems] = useState<LocalItem[]>(
396
+ items.map((it) => ({ id: makeId(), value: it })),
397
+ );
398
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
399
+ const inputRef = useRef<HTMLInputElement>(null);
400
+ const [newInput, setNewInput] = useState('');
401
+
402
+ useEffect(() => {
403
+ // rebuild local items from incoming value; generate ids locally
404
+ setLocalItems(items.map((it) => ({ id: makeId(), value: it })));
405
+ }, [items]);
406
+
407
+ const close = () => {
408
+ // commit normalized list shape when closing
409
+ updateValue(localItems.map((e) => e.value));
410
+ setAnchorEl(null);
411
+ };
412
+
413
+ const commit = useCallback((nextItems: LocalItem[]) => {
414
+ setLocalItems(nextItems);
415
+ }, []);
416
+
417
+ const addItem = useCallback(() => {
418
+ const trimmed = newInput.trim();
419
+ // if input provided, try to coerce to column type; else create default typed item
420
+ let newItem: DataCubeOperationValue;
421
+ const firstType = localItems[0]?.value.type ?? PRIMITIVE_TYPE.STRING;
422
+ switch (firstType) {
423
+ case PRIMITIVE_TYPE.BOOLEAN: {
424
+ const v = trimmed.toLowerCase();
425
+ const boolVal = v === 'true' || v === '1';
426
+ newItem = { type: firstType, value: boolVal };
427
+ break;
428
+ }
429
+ case PRIMITIVE_TYPE.NUMBER:
430
+ case PRIMITIVE_TYPE.DECIMAL:
431
+ case PRIMITIVE_TYPE.FLOAT:
432
+ case PRIMITIVE_TYPE.INTEGER:
433
+ case PRECISE_PRIMITIVE_TYPE.INT:
434
+ case PRECISE_PRIMITIVE_TYPE.BIG_INT:
435
+ case PRECISE_PRIMITIVE_TYPE.DECIMAL:
436
+ case PRECISE_PRIMITIVE_TYPE.NUMERIC:
437
+ case PRECISE_PRIMITIVE_TYPE.DOUBLE:
438
+ case PRECISE_PRIMITIVE_TYPE.SMALL_INT:
439
+ case PRECISE_PRIMITIVE_TYPE.FLOAT:
440
+ case PRECISE_PRIMITIVE_TYPE.U_INT:
441
+ case PRECISE_PRIMITIVE_TYPE.TINY_INT:
442
+ case PRECISE_PRIMITIVE_TYPE.U_BIG_INT:
443
+ case PRECISE_PRIMITIVE_TYPE.U_SMALL_INT:
444
+ case PRECISE_PRIMITIVE_TYPE.U_TINY_INT: {
445
+ const n = Number(trimmed);
446
+ newItem = { type: firstType, value: isNaN(n) ? 0 : n };
447
+ break;
448
+ }
449
+ case PRIMITIVE_TYPE.DATE:
450
+ case PRIMITIVE_TYPE.STRICTDATE:
451
+ case PRIMITIVE_TYPE.DATETIME:
452
+ case PRECISE_PRIMITIVE_TYPE.STRICTDATE:
453
+ case PRECISE_PRIMITIVE_TYPE.DATETIME:
454
+ case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
455
+ case PRECISE_PRIMITIVE_TYPE.TIMESTAMP: {
456
+ // try parse ISO, else use formatted now
457
+ let dateVal = trimmed;
458
+ try {
459
+ const d = parseISO(trimmed);
460
+ dateVal = formatDate(d, DATE_FORMAT);
461
+ } catch {
462
+ dateVal = formatDate(new Date(), DATE_FORMAT);
463
+ }
464
+ newItem = { type: firstType, value: dateVal };
465
+ break;
466
+ }
467
+ default:
468
+ newItem = { type: firstType, value: trimmed };
469
+ }
470
+ const next = [...localItems, { id: makeId(), value: newItem }];
471
+ commit(next);
472
+ setNewInput('');
473
+ inputRef.current?.focus();
474
+ }, [localItems, newInput, commit]);
475
+
476
+ // When the popover is open, allow pressing Enter (when focus is not in an
477
+ // input/control) to add a new item. We keep Enter behavior of individual
478
+ // editors (e.g. number evaluator) intact by ignoring keypress when focus
479
+ // is on common form controls.
480
+ useEffect(() => {
481
+ const handler = (e: KeyboardEvent): void => {
482
+ if (e.key !== 'Enter') {
483
+ return;
484
+ }
485
+ if (!anchorEl) {
486
+ return;
487
+ }
488
+ const active = document.activeElement as HTMLElement | null;
489
+ if (!active) {
490
+ addItem();
491
+ e.preventDefault();
492
+ return;
493
+ }
494
+ const tag = active.tagName;
495
+ // if focus is on typical input controls, let their handlers process Enter
496
+ if (['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].includes(tag)) {
497
+ return;
498
+ }
499
+ // otherwise, treat Enter as add
500
+ addItem();
501
+ e.preventDefault();
502
+ };
503
+ window.addEventListener('keydown', handler);
504
+ return () => window.removeEventListener('keydown', handler);
505
+ }, [anchorEl, addItem]);
506
+
507
+ const removeItem = (idx: number) => {
508
+ const next = localItems.filter((_, i) => i !== idx);
509
+ commit(next);
510
+ };
511
+
512
+ const summary =
513
+ localItems.length === 0
514
+ ? 'Add...'
515
+ : `${localItems
516
+ .slice(0, 2)
517
+ .map((entry) => {
518
+ const v = entry.value.value;
519
+ return String(v ?? '');
520
+ })
521
+ .join(
522
+ ', ',
523
+ )}${localItems.length > 2 ? ` (+${localItems.length - 2})` : ''}`;
524
+
525
+ return (
526
+ <>
527
+ <button
528
+ ref={ref as React.RefObject<HTMLButtonElement>}
529
+ className="relative h-5 w-full border border-neutral-400 px-1 text-left text-sm"
530
+ onClick={(e) => setAnchorEl(e.currentTarget)}
531
+ >
532
+ {summary}
533
+ </button>
534
+ <BasePopover
535
+ open={Boolean(anchorEl)}
536
+ anchorEl={anchorEl}
537
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
538
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
539
+ onClose={() => close()}
540
+ classes={{
541
+ paper: 'shadow-md rounded-none border border-neutral-300 p-2',
542
+ }}
543
+ >
544
+ <div className="w-60">
545
+ <div className="max-h-40 overflow-auto">
546
+ {localItems.length === 0 && (
547
+ <div className="mb-2 text-sm text-neutral-600">
548
+ No values added.
549
+ </div>
550
+ )}
551
+ {localItems.map((entry) => {
552
+ const it = entry.value;
553
+ const id = entry.id;
554
+ return (
555
+ <div key={id} className="mb-1 flex items-center gap-2">
556
+ <div className="flex-1">
557
+ {(() => {
558
+ switch (it.type) {
559
+ case PRIMITIVE_TYPE.BOOLEAN:
560
+ return (
561
+ <DataCubeEditorFilterConditionNodeBooleanValueEditor
562
+ value={Boolean(it.value)}
563
+ updateValue={(v: boolean) => {
564
+ const next = localItems.slice();
565
+ const idx = next.findIndex(
566
+ (e) => e.id === id,
567
+ );
568
+ if (idx >= 0) {
569
+ next[idx] = {
570
+ id,
571
+ value: { type: it.type, value: v },
572
+ };
573
+ setLocalItems(next);
574
+ }
575
+ }}
576
+ />
577
+ );
578
+ case PRIMITIVE_TYPE.NUMBER:
579
+ case PRIMITIVE_TYPE.DECIMAL:
580
+ case PRIMITIVE_TYPE.FLOAT:
581
+ case PRIMITIVE_TYPE.INTEGER:
582
+ case PRECISE_PRIMITIVE_TYPE.INT:
583
+ case PRECISE_PRIMITIVE_TYPE.BIG_INT:
584
+ case PRECISE_PRIMITIVE_TYPE.DECIMAL:
585
+ case PRECISE_PRIMITIVE_TYPE.NUMERIC:
586
+ case PRECISE_PRIMITIVE_TYPE.DOUBLE:
587
+ case PRECISE_PRIMITIVE_TYPE.SMALL_INT:
588
+ case PRECISE_PRIMITIVE_TYPE.FLOAT:
589
+ case PRECISE_PRIMITIVE_TYPE.U_INT:
590
+ case PRECISE_PRIMITIVE_TYPE.TINY_INT:
591
+ case PRECISE_PRIMITIVE_TYPE.U_BIG_INT:
592
+ case PRECISE_PRIMITIVE_TYPE.U_SMALL_INT:
593
+ case PRECISE_PRIMITIVE_TYPE.U_TINY_INT:
594
+ return (
595
+ <DataCubeEditorFilterConditionNodeNumberValueEditor
596
+ value={
597
+ !isNullable(it.value)
598
+ ? (it.value as number)
599
+ : 0
600
+ }
601
+ updateValue={(v: number) => {
602
+ const next = localItems.slice();
603
+ const idx = next.findIndex(
604
+ (e) => e.id === id,
605
+ );
606
+ if (idx >= 0) {
607
+ next[idx] = {
608
+ id,
609
+ value: { type: it.type, value: v },
610
+ };
611
+ setLocalItems(next);
612
+ }
613
+ }}
614
+ />
615
+ );
616
+ case PRIMITIVE_TYPE.DATE:
617
+ case PRIMITIVE_TYPE.STRICTDATE:
618
+ case PRIMITIVE_TYPE.DATETIME:
619
+ case PRECISE_PRIMITIVE_TYPE.STRICTDATE:
620
+ case PRECISE_PRIMITIVE_TYPE.DATETIME:
621
+ case PRECISE_PRIMITIVE_TYPE.STRICTTIME:
622
+ case PRECISE_PRIMITIVE_TYPE.TIMESTAMP:
623
+ return (
624
+ <DataCubeEditorFilterConditionNodeDateValueEditor
625
+ value={String(
626
+ it.value ??
627
+ formatDate(new Date(), DATE_FORMAT),
628
+ )}
629
+ updateValue={(v: string) => {
630
+ const next = localItems.slice();
631
+ const idx = next.findIndex(
632
+ (e) => e.id === id,
633
+ );
634
+ if (idx >= 0) {
635
+ next[idx] = {
636
+ id,
637
+ value: { type: it.type, value: v },
638
+ };
639
+ setLocalItems(next);
640
+ }
641
+ }}
642
+ />
643
+ );
644
+ default:
645
+ return (
646
+ <DataCubeEditorFilterConditionNodeTextValueEditor
647
+ value={String(it.value ?? '')}
648
+ updateValue={(v: string) => {
649
+ const next = localItems.slice();
650
+ const idx = next.findIndex(
651
+ (e) => e.id === id,
652
+ );
653
+ if (idx >= 0) {
654
+ next[idx] = {
655
+ id,
656
+ value: { type: it.type, value: v },
657
+ };
658
+ setLocalItems(next);
659
+ }
660
+ }}
661
+ />
662
+ );
663
+ }
664
+ })()}
665
+ </div>
666
+ <button
667
+ className="text-sm text-red-600"
668
+ onClick={() => {
669
+ const idx = localItems.findIndex((e) => e.id === id);
670
+ if (idx >= 0) {
671
+ removeItem(idx);
672
+ }
673
+ }}
674
+ title="Remove"
675
+ >
676
+ ×
677
+ </button>
678
+ </div>
679
+ );
680
+ })}
681
+ </div>
682
+ <div className="mt-2 flex items-center gap-2">
683
+ <input
684
+ ref={inputRef}
685
+ className="flex-1 border border-neutral-300 px-1 text-sm"
686
+ placeholder="Add value"
687
+ value={newInput}
688
+ onChange={(e) => setNewInput(e.target.value)}
689
+ onKeyDown={(e) => {
690
+ if (e.key === 'Enter') {
691
+ addItem();
692
+ e.preventDefault();
693
+ } else if (e.key === 'Escape') {
694
+ close();
695
+ }
696
+ }}
697
+ />
698
+ <button className="ml-1" onClick={addItem} title="Add">
699
+ +
700
+ </button>
701
+ </div>
702
+ <div className="mt-2 flex justify-end">
703
+ <FormButton
704
+ onClick={() => {
705
+ close();
706
+ }}
707
+ >
708
+ Done
709
+ </FormButton>
710
+ </div>
711
+ </div>
712
+ </BasePopover>
713
+ </>
714
+ );
715
+ }),
716
+ );
717
+
363
718
  const DataCubeEditorFilterConditionNodeValueEditor = observer(
364
719
  forwardRef<
365
720
  HTMLElement,
@@ -370,7 +725,7 @@ const DataCubeEditorFilterConditionNodeValueEditor = observer(
370
725
  }
371
726
  >(function DataCubeEditorFilterConditionNodeValueEditor(props, ref) {
372
727
  const { value, updateValue, view } = props;
373
- // WIP: support collection/column
728
+ // WIP: support column
374
729
  switch (value.type) {
375
730
  case PRIMITIVE_TYPE.STRING:
376
731
  case PRECISE_PRIMITIVE_TYPE.VARCHAR:
@@ -435,6 +790,15 @@ const DataCubeEditorFilterConditionNodeValueEditor = observer(
435
790
  view={view}
436
791
  />
437
792
  );
793
+ case DataCubeOperationAdvancedValueType.LIST:
794
+ return (
795
+ <DataCubeEditorFilterConditionNodeListValueEditor
796
+ ref={ref}
797
+ value={value}
798
+ updateValue={(val) => updateValue(val)}
799
+ view={view}
800
+ />
801
+ );
438
802
  case DataCubeOperationAdvancedValueType.VOID:
439
803
  default:
440
804
  return null;
@@ -60,6 +60,8 @@ import { DataCubeQueryFilterOperation__GreaterThanOrEqualColumn } from './filter
60
60
  import { DataCubeQueryFilterOperation__Contain } from './filter/DataCubeQueryFilterOperation__Contain.js';
61
61
  import { DataCubeQueryFilterOperation__ContainCaseInsensitive } from './filter/DataCubeQueryFilterOperation__ContainCaseInsensitive.js';
62
62
  import { DataCubeQueryFilterOperation__NotContain } from './filter/DataCubeQueryFilterOperation__NotContain.js';
63
+ import { DataCubeQueryFilterOperation__In } from './filter/DataCubeQueryFilterOperation__In.js';
64
+ import { DataCubeQueryFilterOperation__NotIn } from './filter/DataCubeQueryFilterOperation__NotIn.js';
63
65
  import { DataCubeQueryFilterOperation__StartWith } from './filter/DataCubeQueryFilterOperation__StartWith.js';
64
66
  import { DataCubeQueryFilterOperation__StartWithCaseInsensitive } from './filter/DataCubeQueryFilterOperation__StartWithCaseInsensitive.js';
65
67
  import { DataCubeQueryFilterOperation__NotStartWith } from './filter/DataCubeQueryFilterOperation__NotStartWith.js';
@@ -148,6 +150,8 @@ export abstract class DataCubeEngine {
148
150
  new DataCubeQueryFilterOperation__Contain(),
149
151
  new DataCubeQueryFilterOperation__ContainCaseInsensitive(),
150
152
  new DataCubeQueryFilterOperation__NotContain(),
153
+ new DataCubeQueryFilterOperation__In(),
154
+ new DataCubeQueryFilterOperation__NotIn(),
151
155
  new DataCubeQueryFilterOperation__StartWith(),
152
156
  new DataCubeQueryFilterOperation__StartWithCaseInsensitive(),
153
157
  new DataCubeQueryFilterOperation__NotStartWith(),
@@ -322,7 +322,7 @@ export function _colSpec(
322
322
  return colSpec;
323
323
  }
324
324
 
325
- export function _value(value: DataCubeOperationValue) {
325
+ export function _value(value: DataCubeOperationValue): V1_ValueSpecification {
326
326
  switch (value.type) {
327
327
  case PRIMITIVE_TYPE.STRING:
328
328
  case PRECISE_PRIMITIVE_TYPE.VARCHAR:
@@ -361,6 +361,17 @@ export function _value(value: DataCubeOperationValue) {
361
361
  }
362
362
  case DataCubeOperationAdvancedValueType.COLUMN:
363
363
  return _property(guaranteeIsString(value.value));
364
+ case DataCubeOperationAdvancedValueType.LIST: {
365
+ // value.value is expected to be an array of DataCubeOperationValue
366
+ if (!Array.isArray(value.value)) {
367
+ throw new UnsupportedOperationError(
368
+ `List value must be an array for type '${DataCubeOperationAdvancedValueType.LIST}'`,
369
+ );
370
+ }
371
+ return _collection(
372
+ (value.value as DataCubeOperationValue[]).map((val) => _value(val)),
373
+ );
374
+ }
364
375
  default:
365
376
  throw new UnsupportedOperationError(
366
377
  `Can't build value instance for unsupported type '${value.type}'`,
@@ -155,6 +155,7 @@ export enum DataCubeColumnKind {
155
155
 
156
156
  export enum DataCubeOperationAdvancedValueType {
157
157
  COLUMN = 'COLUMN',
158
+ LIST = 'LIST',
158
159
  VOID = 'VOID',
159
160
  // PARAMETER
160
161
  }
@@ -472,6 +473,22 @@ export function isPrimitiveType(type: string) {
472
473
  PRIMITIVE_TYPE.DATETIME,
473
474
  PRIMITIVE_TYPE.STRING,
474
475
  PRIMITIVE_TYPE.BOOLEAN,
476
+ PRECISE_PRIMITIVE_TYPE.INT,
477
+ PRECISE_PRIMITIVE_TYPE.BIG_INT,
478
+ PRECISE_PRIMITIVE_TYPE.DECIMAL,
479
+ PRECISE_PRIMITIVE_TYPE.NUMERIC,
480
+ PRECISE_PRIMITIVE_TYPE.DOUBLE,
481
+ PRECISE_PRIMITIVE_TYPE.SMALL_INT,
482
+ PRECISE_PRIMITIVE_TYPE.FLOAT,
483
+ PRECISE_PRIMITIVE_TYPE.U_INT,
484
+ PRECISE_PRIMITIVE_TYPE.TINY_INT,
485
+ PRECISE_PRIMITIVE_TYPE.U_BIG_INT,
486
+ PRECISE_PRIMITIVE_TYPE.U_SMALL_INT,
487
+ PRECISE_PRIMITIVE_TYPE.U_TINY_INT,
488
+ PRECISE_PRIMITIVE_TYPE.STRICTDATE,
489
+ PRECISE_PRIMITIVE_TYPE.STRICTTIME,
490
+ PRECISE_PRIMITIVE_TYPE.TIMESTAMP,
491
+ PRECISE_PRIMITIVE_TYPE.VARCHAR,
475
492
  ] as string[]
476
493
  ).includes(type);
477
494
  }
@@ -233,7 +233,7 @@ export function _operationValue(
233
233
  value: V1_ValueSpecification | undefined,
234
234
  columnGetter: (name: string) => DataCubeColumn,
235
235
  columnChecker?: ((column: DataCubeColumn) => void) | undefined,
236
- ) {
236
+ ): DataCubeOperationValue {
237
237
  if (value instanceof V1_PrimitiveValueSpecification) {
238
238
  return _operationPrimitiveValue(value);
239
239
  } else if (value instanceof V1_AppliedProperty) {
@@ -243,6 +243,13 @@ export function _operationValue(
243
243
  value: column.name,
244
244
  type: DataCubeOperationAdvancedValueType.COLUMN,
245
245
  };
246
+ } else if (value instanceof V1_Collection) {
247
+ return {
248
+ type: DataCubeOperationAdvancedValueType.LIST,
249
+ value: value.values.map((val) =>
250
+ _operationValue(val, columnGetter, columnChecker),
251
+ ),
252
+ };
246
253
  } else if (value === undefined) {
247
254
  return {
248
255
  type: DataCubeOperationAdvancedValueType.VOID,
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Copyright (c) 2025-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { DataCubeQueryFilterOperation } from './DataCubeQueryFilterOperation.js';
17
+ import type { DataCubeSnapshotFilterCondition } from '../DataCubeSnapshot.js';
18
+ import type { DataCubeColumn } from '../model/DataCubeColumn.js';
19
+ import {
20
+ DataCubeColumnDataType,
21
+ DataCubeFunction,
22
+ DataCubeQueryFilterOperator,
23
+ isPrimitiveType,
24
+ ofDataType,
25
+ _defaultPrimitiveTypeValue,
26
+ DataCubeOperationAdvancedValueType,
27
+ type DataCubeOperationValue,
28
+ } from '../DataCubeQueryEngine.js';
29
+ import {
30
+ _function,
31
+ _functionName,
32
+ _property,
33
+ _value,
34
+ } from '../DataCubeQueryBuilderUtils.js';
35
+ import { type V1_AppliedFunction } from '@finos/legend-graph';
36
+ import { _filterCondition_base } from '../DataCubeSnapshotBuilderUtils.js';
37
+
38
+ export class DataCubeQueryFilterOperation__In extends DataCubeQueryFilterOperation {
39
+ override get label() {
40
+ return 'in list of';
41
+ }
42
+
43
+ override get textLabel() {
44
+ return 'in list of';
45
+ }
46
+
47
+ override get description() {
48
+ return 'in list of';
49
+ }
50
+
51
+ override get operator() {
52
+ return DataCubeQueryFilterOperator.IN;
53
+ }
54
+
55
+ isCompatibleWithColumn(column: DataCubeColumn) {
56
+ return ofDataType(column.type, [
57
+ DataCubeColumnDataType.TEXT,
58
+ DataCubeColumnDataType.NUMBER,
59
+ DataCubeColumnDataType.DATE,
60
+ ]);
61
+ }
62
+
63
+ isCompatibleWithValue(value: DataCubeOperationValue) {
64
+ // - advanced LIST type: { type: DataCubeOperationAdvancedValueType.LIST, value: [{ type: <primitive>, value: <primitive> }, ...] }
65
+ if (value.value === undefined) {
66
+ return false;
67
+ }
68
+ if (value.type === DataCubeOperationAdvancedValueType.LIST) {
69
+ if (
70
+ !Array.isArray(value.value) ||
71
+ (value.value as unknown[]).length === 0
72
+ ) {
73
+ return false;
74
+ }
75
+ return (value.value as unknown[]).every((v) => {
76
+ const maybe = v as DataCubeOperationValue;
77
+ return (
78
+ maybe.value !== undefined &&
79
+ isPrimitiveType(maybe.type) &&
80
+ ofDataType(maybe.type, [
81
+ DataCubeColumnDataType.TEXT,
82
+ DataCubeColumnDataType.NUMBER,
83
+ DataCubeColumnDataType.DATE,
84
+ ])
85
+ );
86
+ });
87
+ }
88
+ return (
89
+ isPrimitiveType(value.type) &&
90
+ ofDataType(value.type, [
91
+ DataCubeColumnDataType.TEXT,
92
+ DataCubeColumnDataType.NUMBER,
93
+ DataCubeColumnDataType.DATE,
94
+ ]) &&
95
+ Array.isArray(value.value) &&
96
+ (value.value as unknown[]).length > 0
97
+ );
98
+ }
99
+
100
+ generateDefaultValue(column: DataCubeColumn) {
101
+ return {
102
+ type: DataCubeOperationAdvancedValueType.LIST,
103
+ value: [
104
+ {
105
+ type: column.type,
106
+ value: _defaultPrimitiveTypeValue(column.type),
107
+ },
108
+ ],
109
+ };
110
+ }
111
+
112
+ buildConditionSnapshot(
113
+ expression: V1_AppliedFunction,
114
+ columnGetter: (name: string) => DataCubeColumn,
115
+ ) {
116
+ return this._finalizeConditionSnapshot(
117
+ _filterCondition_base(expression, DataCubeFunction.IN, columnGetter),
118
+ );
119
+ }
120
+
121
+ buildConditionExpression(condition: DataCubeSnapshotFilterCondition) {
122
+ return _function(_functionName(DataCubeFunction.IN), [
123
+ _property(condition.name),
124
+ _value(condition.value),
125
+ ]);
126
+ }
127
+ }