@finos/legend-application-data-cube 0.6.15 → 0.7.1

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 (59) hide show
  1. package/lib/components/LegendDataCubeWebApplication.d.ts.map +1 -1
  2. package/lib/components/LegendDataCubeWebApplication.js +1 -0
  3. package/lib/components/LegendDataCubeWebApplication.js.map +1 -1
  4. package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.d.ts.map +1 -1
  5. package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.js +6 -14
  6. package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.js.map +1 -1
  7. package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.d.ts.map +1 -1
  8. package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.js +13 -4
  9. package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.js.map +1 -1
  10. package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.d.ts.map +1 -1
  11. package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.js +3 -0
  12. package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.js.map +1 -1
  13. package/lib/index.css +1 -1
  14. package/lib/package.json +3 -2
  15. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +4 -0
  16. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
  17. package/lib/stores/LegendDataCubeDataCubeEngine.js +147 -71
  18. package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
  19. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +4 -2
  20. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
  21. package/lib/stores/LegendDataCubeDuckDBEngine.js +52 -0
  22. package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
  23. package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.d.ts +2 -8
  24. package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.d.ts.map +1 -1
  25. package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.js +21 -54
  26. package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.js.map +1 -1
  27. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.d.ts +12 -0
  28. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.d.ts.map +1 -1
  29. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.js +67 -6
  30. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.js.map +1 -1
  31. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.d.ts +3 -0
  32. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.d.ts.map +1 -1
  33. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.js +15 -1
  34. package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.js.map +1 -1
  35. package/lib/stores/model/LakehouseConsumerDataCubeSource.d.ts +2 -2
  36. package/lib/stores/model/LakehouseConsumerDataCubeSource.d.ts.map +1 -1
  37. package/lib/stores/model/LakehouseConsumerDataCubeSource.js.map +1 -1
  38. package/lib/stores/model/LakehouseProducerDataCubeSource.d.ts +13 -0
  39. package/lib/stores/model/LakehouseProducerDataCubeSource.d.ts.map +1 -1
  40. package/lib/stores/model/LakehouseProducerDataCubeSource.js +19 -2
  41. package/lib/stores/model/LakehouseProducerDataCubeSource.js.map +1 -1
  42. package/lib/stores/model/SecondaryOauthClient.d.ts +26 -0
  43. package/lib/stores/model/SecondaryOauthClient.d.ts.map +1 -0
  44. package/lib/stores/model/SecondaryOauthClient.js +48 -0
  45. package/lib/stores/model/SecondaryOauthClient.js.map +1 -0
  46. package/package.json +10 -9
  47. package/src/components/LegendDataCubeWebApplication.tsx +1 -0
  48. package/src/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.tsx +7 -32
  49. package/src/components/builder/source/LakehouseProducerDataCubeSourceBuilder.tsx +40 -14
  50. package/src/components/builder/source/LakehouseProducerDataCubeSourceLoader.tsx +4 -0
  51. package/src/stores/LegendDataCubeDataCubeEngine.ts +195 -80
  52. package/src/stores/LegendDataCubeDuckDBEngine.ts +73 -1
  53. package/src/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.ts +31 -74
  54. package/src/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.ts +125 -6
  55. package/src/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.ts +29 -3
  56. package/src/stores/model/LakehouseConsumerDataCubeSource.ts +2 -2
  57. package/src/stores/model/LakehouseProducerDataCubeSource.ts +26 -1
  58. package/src/stores/model/SecondaryOauthClient.ts +56 -0
  59. package/tsconfig.json +1 -0
@@ -20,11 +20,13 @@ import { useAuth } from 'react-oidc-context';
20
20
  import { guaranteeNonNullable } from '@finos/legend-shared';
21
21
  import { useEffect } from 'react';
22
22
  import type { LakehouseConsumerDataCubeSourceBuilderState } from '../../../stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.js';
23
+ import { useLegendDataCubeBuilderStore } from '../LegendDataCubeBuilderStoreProvider.js';
23
24
 
24
25
  export const LakehouseConsumerDataCubeSourceBuilder: React.FC<{
25
26
  sourceBuilder: LakehouseConsumerDataCubeSourceBuilderState;
26
27
  }> = observer(({ sourceBuilder: state }) => {
27
28
  const auth = useAuth();
29
+ const store = useLegendDataCubeBuilderStore();
28
30
 
29
31
  useEffect(() => {
30
32
  state.reset();
@@ -50,7 +52,11 @@ export const LakehouseConsumerDataCubeSourceBuilder: React.FC<{
50
52
  isLoading={state.dataProductLoadingState.isInProgress}
51
53
  onChange={(newValue: { label: string; value: string } | null) => {
52
54
  state.setSelectedDataProduct(newValue?.value ?? '');
53
- state.fetchDataProductEnvironments(auth.user?.access_token);
55
+ state
56
+ .fetchAccessPoints(auth.user?.access_token)
57
+ .catch((error) =>
58
+ store.alertService.alertUnhandledError(error),
59
+ );
54
60
  }}
55
61
  value={
56
62
  state.selectedDataProduct
@@ -65,37 +71,6 @@ export const LakehouseConsumerDataCubeSourceBuilder: React.FC<{
65
71
  escapeClearsValue={true}
66
72
  />
67
73
  </div>
68
- {state.ingestEnvironments.length > 0 && (
69
- <div className="query-setup__wizard__group mt-2">
70
- <div className="query-setup__wizard__group__title">
71
- Ingest Environment
72
- </div>
73
- <CustomSelectorInput
74
- className="query-setup__wizard__selector"
75
- options={state.ingestEnvironments.map((env) => ({
76
- label: env,
77
- value: env,
78
- }))}
79
- disabled={false}
80
- isLoading={false}
81
- onChange={(newValue: { label: string; value: string } | null) => {
82
- const env = newValue?.value ?? '';
83
- state.setSelectedIngestEnvironment(env);
84
- state.fetchAccessPoints();
85
- }}
86
- value={
87
- state.selectedIngestEnvironment
88
- ? {
89
- label: state.selectedIngestEnvironment,
90
- value: state.selectedIngestEnvironment,
91
- }
92
- : null
93
- }
94
- isClearable={false}
95
- escapeClearsValue={true}
96
- />
97
- </div>
98
- )}
99
74
  {state.accessPoints.length > 0 && (
100
75
  <div className="query-setup__wizard__group mt-2">
101
76
  <div className="query-setup__wizard__group__title">
@@ -15,23 +15,37 @@
15
15
  */
16
16
  import { observer } from 'mobx-react-lite';
17
17
  import type { LakehouseProducerDataCubeSourceBuilderState } from '../../../stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.js';
18
- import { FormButton, FormTextInput } from '@finos/legend-data-cube';
18
+ import {
19
+ FormButton,
20
+ FormCheckbox,
21
+ FormTextInput,
22
+ } from '@finos/legend-data-cube';
19
23
  import { CustomSelectorInput } from '@finos/legend-art';
20
24
  import { useAuth } from 'react-oidc-context';
21
25
  import { useLegendDataCubeBuilderStore } from '../LegendDataCubeBuilderStoreProvider.js';
22
26
  import { guaranteeNonNullable } from '@finos/legend-shared';
23
- import { useEffect } from 'react';
27
+ import { useEffect, useState } from 'react';
24
28
 
25
29
  export const LakehouseProducerDataCubeSourceBuilder: React.FC<{
26
30
  sourceBuilder: LakehouseProducerDataCubeSourceBuilderState;
27
31
  }> = observer(({ sourceBuilder: state }) => {
28
32
  const auth = useAuth();
29
33
  const store = useLegendDataCubeBuilderStore();
34
+ const [isIcebergFlowSelected, setIsIcebergFlowSelected] = useState(true);
35
+
36
+ const toggleSetisIcebergEnabled = () => {
37
+ setIsIcebergFlowSelected(!isIcebergFlowSelected);
38
+ state.setEnableIceberg(!isIcebergFlowSelected);
39
+ };
30
40
 
31
41
  useEffect(() => {
32
42
  state.reset();
33
43
  }, [state]);
34
44
 
45
+ useEffect(() => {
46
+ state.setUserManagerSettings(auth.settings);
47
+ }, [state, auth]);
48
+
35
49
  function createUrnPairs(
36
50
  urns: string[],
37
51
  ): Record<string, string | undefined>[] {
@@ -70,6 +84,17 @@ export const LakehouseProducerDataCubeSourceBuilder: React.FC<{
70
84
  </FormButton>
71
85
  </div>
72
86
  </div>
87
+ {state.icebergEnabled && (
88
+ <div className="query-setup__wizard__group mt-2">
89
+ <div className="flex h-5 w-[calc(100%_-_40px)] overflow-x-auto">
90
+ <FormCheckbox
91
+ label="Use Iceberg"
92
+ checked={isIcebergFlowSelected}
93
+ onChange={toggleSetisIcebergEnabled}
94
+ />
95
+ </div>
96
+ </div>
97
+ )}
73
98
  {state.ingestUrns.length > 0 && (
74
99
  <div className="query-setup__wizard__group mt-3">
75
100
  <div className="query-setup__wizard__group__title">Ingest Urn</div>
@@ -130,18 +155,19 @@ export const LakehouseProducerDataCubeSourceBuilder: React.FC<{
130
155
  />
131
156
  </div>
132
157
  )}
133
- {state.selectedTable && (
134
- <div className="query-setup__wizard__group mt-2">
135
- <div className="query-setup__wizard__group__title">Warehouse</div>
136
- <FormTextInput
137
- className="w-full text-base text-black"
138
- value={state.warehouse}
139
- onChange={(event) => {
140
- state.setWarehouse(event.target.value);
141
- }}
142
- />
143
- </div>
144
- )}
158
+ {state.selectedTable &&
159
+ (!isIcebergFlowSelected || !state.icebergEnabled) && (
160
+ <div className="query-setup__wizard__group mt-2">
161
+ <div className="query-setup__wizard__group__title">Warehouse</div>
162
+ <FormTextInput
163
+ className="w-full text-base text-black"
164
+ value={state.warehouse}
165
+ onChange={(event) => {
166
+ state.setWarehouse(event.target.value);
167
+ }}
168
+ />
169
+ </div>
170
+ )}
145
171
  </div>
146
172
  </div>
147
173
  );
@@ -33,6 +33,10 @@ export const LakehouseProducerDataCubeSourceLoader = observer(
33
33
  partialSourceLoader.reset();
34
34
  }, [partialSourceLoader]);
35
35
 
36
+ useEffect(() => {
37
+ partialSourceLoader.setUserManagerSettings(auth.settings);
38
+ }, [partialSourceLoader, auth]);
39
+
36
40
  return (
37
41
  <div className="flex h-full w-full">
38
42
  <div className="m-3 flex w-full flex-col items-stretch gap-2 text-neutral-500">
@@ -156,6 +156,7 @@ import { QUERY_BUILDER_PURE_PATH } from '@finos/legend-query-builder';
156
156
  import {
157
157
  LAKEHOUSE_PRODUCER_DATA_CUBE_SOURCE_TYPE,
158
158
  LakehouseProducerDataCubeSource,
159
+ LakehouseProducerIcebergCachedDataCubeSource,
159
160
  RawLakehouseProducerDataCubeSource,
160
161
  } from './model/LakehouseProducerDataCubeSource.js';
161
162
  import {
@@ -198,6 +199,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
198
199
  // ---------------------------------- IMPLEMENTATION ----------------------------------
199
200
 
200
201
  override getDataFromSource(source?: DataCubeSource): PlainObject {
202
+ // TODO: add lakehouse sources
201
203
  if (source instanceof LegendQueryDataCubeSource) {
202
204
  const queryInfo = source.info;
203
205
  return {
@@ -378,6 +380,8 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
378
380
  warehouse: rawSource.warehouse,
379
381
  ingestServerUrl: rawSource.ingestServerUrl,
380
382
  },
383
+ icebergCatalog: rawSource.icebergConfig?.catalogUrl,
384
+ paths: rawSource.paths,
381
385
  sourceType: source._type,
382
386
  };
383
387
  } else if (source.type === LAKEHOUSE_CONSUMER_DATA_CUBE_SOURCE_TYPE) {
@@ -459,62 +463,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
459
463
  // TODO: confirm this is in accordance to engine
460
464
  // check if we have a duckdb enum mapping
461
465
  // See https://duckdb.org/docs/sql/data_types/overview.html
462
- switch (col[1] as string) {
463
- case 'BIT': {
464
- column.type = new V1_Bit();
465
- break;
466
- }
467
- case 'BOOLEAN': {
468
- // TODO: understand why boolean is not present in relationalDataType
469
- column.type = new V1_VarChar();
470
- break;
471
- }
472
- case 'DATE': {
473
- column.type = new V1_Date();
474
- break;
475
- }
476
- case 'DECIMAL': {
477
- column.type = new V1_Decimal();
478
- break;
479
- }
480
- case 'DOUBLE': {
481
- column.type = new V1_Double();
482
- break;
483
- }
484
- case 'FLOAT': {
485
- column.type = new V1_Float();
486
- break;
487
- }
488
- case 'INTEGER': {
489
- column.type = new V1_Integer();
490
- break;
491
- }
492
- case 'TININT': {
493
- column.type = new V1_TinyInt();
494
- break;
495
- }
496
- case 'SMALLINT': {
497
- column.type = new V1_SmallInt();
498
- break;
499
- }
500
- case 'BIGINT': {
501
- column.type = new V1_BigInt();
502
- break;
503
- }
504
- case 'TIMESTAMP': {
505
- column.type = new V1_Timestamp();
506
- break;
507
- }
508
- case 'VARCHAR': {
509
- column.type = new V1_VarChar();
510
- break;
511
- }
512
- default: {
513
- throw new UnsupportedOperationError(
514
- `Can't ingest local file data: failed to find matching relational data type for DuckDB type '${col[1]}' when synthesizing table definition`,
515
- );
516
- }
517
- }
466
+ this._getColumnType(col, column);
518
467
  return column;
519
468
  }),
520
469
  });
@@ -703,33 +652,87 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
703
652
  const rawSource =
704
653
  RawLakehouseProducerDataCubeSource.serialization.fromJson(value);
705
654
 
706
- const source = new LakehouseProducerDataCubeSource();
707
-
708
- const query = new V1_ClassInstance();
709
- query.type = V1_ClassInstanceType.INGEST_ACCESSOR;
710
- const ingestAccesor = new V1_RelationStoreAccessor();
711
- ingestAccesor.path = rawSource.paths;
712
- ingestAccesor.metadata = false;
713
- query.value = ingestAccesor;
714
- source.query = query;
715
-
716
- const model = this._synthesizeLakehouseProducerPMCD(rawSource, source);
717
- source.model = V1_serializePureModelContextData(model);
655
+ if (
656
+ rawSource.icebergConfig?.icebergRef &&
657
+ rawSource.icebergConfig.catalogUrl
658
+ ) {
659
+ const source = new LakehouseProducerIcebergCachedDataCubeSource();
660
+ const tableCatalog = this._duckDBEngine.retrieveCatalogTable(
661
+ rawSource.icebergConfig.icebergRef,
662
+ );
718
663
 
719
- try {
720
- source.columns = (
721
- await this._getLambdaRelationType(
722
- this.serializeValueSpecification(_lambda([], [source.query])),
723
- source.model,
724
- )
725
- ).columns;
726
- } catch (error) {
727
- assertErrorThrown(error);
728
- throw new Error(
729
- `Can't get query result columns. Make sure the source query return a relation (i.e. typed TDS). Error: ${error.message}`,
664
+ const { model, database, schema, table, runtime } =
665
+ this._synthesizeMinimalModelContext({
666
+ schemaName: tableCatalog.schemaName,
667
+ tableName: tableCatalog.tableName,
668
+ tableColumns: tableCatalog.columns.map((col) => {
669
+ const column = new V1_Column();
670
+ column.name = col[0] as string;
671
+ // TODO: confirm this is in accordance to engine
672
+ // check if we have a duckdb enum mapping
673
+ // See https://duckdb.org/docs/sql/data_types/overview.html
674
+ this._getColumnType(col, column);
675
+ return column;
676
+ }),
677
+ });
678
+
679
+ source.db = database.path;
680
+ source.model = model;
681
+ source.table = table.name;
682
+ source.schema = schema.name;
683
+ source.runtime = runtime.path;
684
+
685
+ const query = new V1_ClassInstance();
686
+ query.type = V1_ClassInstanceType.RELATION_STORE_ACCESSOR;
687
+ const storeAccessor = new V1_RelationStoreAccessor();
688
+ storeAccessor.path = [source.db, source.schema, source.table];
689
+ query.value = storeAccessor;
690
+ source.query = query;
691
+ try {
692
+ source.columns = (
693
+ await this._getLambdaRelationType(
694
+ this.serializeValueSpecification(_lambda([], [source.query])),
695
+ source.model,
696
+ )
697
+ ).columns;
698
+ } catch (error) {
699
+ assertErrorThrown(error);
700
+ throw new Error(
701
+ `Can't get query result columns. Make sure the source query return a relation (i.e. typed TDS). Error: ${error.message}`,
702
+ );
703
+ }
704
+ return source;
705
+ } else {
706
+ const source = new LakehouseProducerDataCubeSource();
707
+
708
+ const query = new V1_ClassInstance();
709
+ query.type = V1_ClassInstanceType.INGEST_ACCESSOR;
710
+ const ingestAccesor = new V1_RelationStoreAccessor();
711
+ ingestAccesor.path = rawSource.paths;
712
+ ingestAccesor.metadata = false;
713
+ query.value = ingestAccesor;
714
+ source.query = query;
715
+
716
+ const model = this._synthesizeLakehouseProducerPMCD(
717
+ rawSource,
718
+ source,
730
719
  );
720
+ source.model = V1_serializePureModelContextData(model);
721
+ try {
722
+ source.columns = (
723
+ await this._getLambdaRelationType(
724
+ this.serializeValueSpecification(_lambda([], [source.query])),
725
+ source.model,
726
+ )
727
+ ).columns;
728
+ } catch (error) {
729
+ assertErrorThrown(error);
730
+ throw new Error(
731
+ `Can't get query result columns. Make sure the source query return a relation (i.e. typed TDS). Error: ${error.message}`,
732
+ );
733
+ }
734
+ return source;
731
735
  }
732
- return source;
733
736
  }
734
737
  case LAKEHOUSE_CONSUMER_DATA_CUBE_SOURCE_TYPE: {
735
738
  const rawSource =
@@ -1019,6 +1022,35 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
1019
1022
  result: await this._duckDBEngine.runSQLQuery(sql),
1020
1023
  executionTime: endTime - startTime,
1021
1024
  };
1025
+ } else if (
1026
+ source instanceof LakehouseProducerIcebergCachedDataCubeSource
1027
+ ) {
1028
+ const executionPlan = await this._generateExecutionPlan(
1029
+ query,
1030
+ source.model,
1031
+ [],
1032
+ // NOTE: for local file, we're using DuckDB, but its protocol models
1033
+ // are not available in the latest production protocol version V1_33_0, so
1034
+ // we have to force using VX_X_X
1035
+ // once we either cut another protocol version or backport the DuckDB models
1036
+ // to V1_33_0, we will can remove this
1037
+ { ...options, clientVersion: PureClientVersion.VX_X_X },
1038
+ );
1039
+ const sql = guaranteeNonNullable(
1040
+ executionPlan instanceof V1_SimpleExecutionPlan
1041
+ ? executionPlan.rootExecutionNode.executionNodes
1042
+ .filter(filterByType(V1_SQLExecutionNode))
1043
+ .at(-1)?.sqlQuery
1044
+ : undefined,
1045
+ `Can't process execution plan: failed to extract generated SQL`,
1046
+ );
1047
+ const endTime = performance.now();
1048
+ return {
1049
+ executedQuery: await queryCodePromise,
1050
+ executedSQL: sql,
1051
+ result: await this._duckDBEngine.runSQLQuery(sql),
1052
+ executionTime: endTime - startTime,
1053
+ };
1022
1054
  } else {
1023
1055
  throw new UnsupportedOperationError(
1024
1056
  `Can't execute query with unsupported source`,
@@ -1204,6 +1236,11 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
1204
1236
  DataCubeFunction.FROM,
1205
1237
  [_elementPtr(source.runtime)].filter(isNonNullable),
1206
1238
  );
1239
+ } else if (source instanceof LakehouseProducerIcebergCachedDataCubeSource) {
1240
+ return _function(
1241
+ DataCubeFunction.FROM,
1242
+ [_elementPtr(source.runtime)].filter(isNonNullable),
1243
+ );
1207
1244
  } else if (source instanceof LakehouseProducerDataCubeSource) {
1208
1245
  return _function(
1209
1246
  DataCubeFunction.FROM,
@@ -1360,6 +1397,8 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
1360
1397
  return this._getLambdaRelationType(query, source.model);
1361
1398
  } else if (source instanceof LakehouseProducerDataCubeSource) {
1362
1399
  return this._getLambdaRelationType(query, source.model);
1400
+ } else if (source instanceof LakehouseProducerIcebergCachedDataCubeSource) {
1401
+ return this._getLambdaRelationType(query, source.model);
1363
1402
  } else if (source instanceof LakehouseConsumerDataCubeSource) {
1364
1403
  return this._getLambdaRelationType(query, source.model);
1365
1404
  }
@@ -1590,6 +1629,82 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
1590
1629
  return { dbReference, columnNames };
1591
1630
  }
1592
1631
 
1632
+ async ingestIcebergTable(
1633
+ warehouse: string,
1634
+ paths: string[],
1635
+ catalogApi: string,
1636
+ refId?: string,
1637
+ token?: string,
1638
+ ) {
1639
+ const { dbReference } = await this._duckDBEngine.ingestIcebergTable(
1640
+ warehouse,
1641
+ paths,
1642
+ catalogApi,
1643
+ refId,
1644
+ token,
1645
+ );
1646
+ return { dbReference };
1647
+ }
1648
+
1649
+ private _getColumnType(col: string[], column: V1_Column) {
1650
+ switch (col[1] as string) {
1651
+ case 'BIT': {
1652
+ column.type = new V1_Bit();
1653
+ break;
1654
+ }
1655
+ case 'BOOLEAN': {
1656
+ // TODO: understand why boolean is not present in relationalDataType
1657
+ column.type = new V1_VarChar();
1658
+ break;
1659
+ }
1660
+ case 'DATE': {
1661
+ column.type = new V1_Date();
1662
+ break;
1663
+ }
1664
+ case 'DECIMAL': {
1665
+ column.type = new V1_Decimal();
1666
+ break;
1667
+ }
1668
+ case 'DOUBLE': {
1669
+ column.type = new V1_Double();
1670
+ break;
1671
+ }
1672
+ case 'FLOAT': {
1673
+ column.type = new V1_Float();
1674
+ break;
1675
+ }
1676
+ case 'INTEGER': {
1677
+ column.type = new V1_Integer();
1678
+ break;
1679
+ }
1680
+ case 'TININT': {
1681
+ column.type = new V1_TinyInt();
1682
+ break;
1683
+ }
1684
+ case 'SMALLINT': {
1685
+ column.type = new V1_SmallInt();
1686
+ break;
1687
+ }
1688
+ case 'BIGINT': {
1689
+ column.type = new V1_BigInt();
1690
+ break;
1691
+ }
1692
+ case 'TIMESTAMP': {
1693
+ column.type = new V1_Timestamp();
1694
+ break;
1695
+ }
1696
+ case 'VARCHAR': {
1697
+ column.type = new V1_VarChar();
1698
+ break;
1699
+ }
1700
+ default: {
1701
+ throw new UnsupportedOperationError(
1702
+ `Can't ingest local file data: failed to find matching relational data type for DuckDB type '${col[1]}' when synthesizing table definition`,
1703
+ );
1704
+ }
1705
+ }
1706
+ }
1707
+
1593
1708
  private _synthesizeMinimalModelContext(data: {
1594
1709
  schemaName: string;
1595
1710
  tableName: string;
@@ -106,6 +106,12 @@ export class LegendDataCubeDuckDBEngine {
106
106
  const database = new duckdb.AsyncDuckDB(logger, worker);
107
107
  await database.instantiate(bundle.mainModule, bundle.pthreadWorker);
108
108
  this._database = database;
109
+
110
+ const connection = await this.database.connect();
111
+ await connection.query(`SET builtin_httpfs = false;`);
112
+ await connection.query(
113
+ `INSTALL iceberg FROM 'https://nightly-extensions.duckdb.org/iceberg/caca3ac6';`,
114
+ );
109
115
  }
110
116
 
111
117
  async cache(result: TDSExecutionResult) {
@@ -265,6 +271,72 @@ export class LegendDataCubeDuckDBEngine {
265
271
  };
266
272
  }
267
273
 
274
+ async ingestIcebergTable(
275
+ warehouse: string,
276
+ paths: string[],
277
+ catalogApi: string,
278
+ refId?: string,
279
+ token?: string,
280
+ ) {
281
+ if (!isNullable(refId) && this._catalog.has(refId)) {
282
+ guaranteeNonNullable(this._catalog.get(refId));
283
+ return {
284
+ dbReference: refId,
285
+ };
286
+ }
287
+
288
+ const schemaName = LegendDataCubeDuckDBEngine.DUCKDB_DEFAULT_SCHEMA_NAME;
289
+ LegendDataCubeDuckDBEngine.ingestFileTableCounter += 1;
290
+ const tableName = `${LegendDataCubeDuckDBEngine.INGEST_TABLE_NAME_PREFIX}${LegendDataCubeDuckDBEngine.ingestFileTableCounter}`;
291
+
292
+ const connection = await this.database.connect();
293
+
294
+ const secret = `CREATE OR REPLACE SECRET iceberg_secret (
295
+ TYPE ICEBERG,
296
+ TOKEN '${token}'
297
+ );`;
298
+ await connection.query(secret);
299
+
300
+ const catalog = `ATTACH OR REPLACE '${warehouse}' AS iceberg_catalog (
301
+ TYPE iCEBERG,
302
+ SECRET iceberg_secret,
303
+ ENDPOINT '${catalogApi}',
304
+ SUPPORT_NESTED_NAMESPACES true
305
+ );`;
306
+ await connection.query(catalog);
307
+
308
+ const selectQuery = `SELECT * from iceberg_catalog."${paths[0]}.${paths[1]}".${paths[2]};`;
309
+ const results = await connection.query(selectQuery);
310
+
311
+ await connection.insertArrowTable(results, {
312
+ name: tableName,
313
+ create: true,
314
+ schema: schemaName,
315
+ });
316
+
317
+ const describeQuery = `DESCRIBE ${schemaName}.${tableName};`;
318
+ const describeResult = await connection.query(describeQuery);
319
+
320
+ const tableSpec = describeResult
321
+ .toArray()
322
+ .map((spec) => [
323
+ spec[LegendDataCubeDuckDBEngine.COLUMN_NAME] as string,
324
+ spec[LegendDataCubeDuckDBEngine.COLUMN_TYPE] as string,
325
+ ]);
326
+ await connection.close();
327
+
328
+ const ref = isNullable(refId) ? uuid() : refId;
329
+ this._catalog.set(ref, {
330
+ schemaName,
331
+ tableName,
332
+ columns: tableSpec,
333
+ } satisfies DuckDBCatalogTable);
334
+
335
+ return {
336
+ dbReference: ref,
337
+ };
338
+ }
339
+
268
340
  async runSQLQuery(sql: string) {
269
341
  const connection = await this.database.connect();
270
342
  const result = await connection.query(sql);
@@ -374,7 +446,7 @@ export class LegendDataCubeDuckDBEngine {
374
446
  }
375
447
  }
376
448
 
377
- type DuckDBCatalogTable = {
449
+ export type DuckDBCatalogTable = {
378
450
  schemaName: string;
379
451
  tableName: string;
380
452
  columns: string[][];