@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.
- package/lib/components/LegendDataCubeWebApplication.d.ts.map +1 -1
- package/lib/components/LegendDataCubeWebApplication.js +1 -0
- package/lib/components/LegendDataCubeWebApplication.js.map +1 -1
- package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.d.ts.map +1 -1
- package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.js +6 -14
- package/lib/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.js.map +1 -1
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.d.ts.map +1 -1
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.js +13 -4
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceBuilder.js.map +1 -1
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.d.ts.map +1 -1
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.js +3 -0
- package/lib/components/builder/source/LakehouseProducerDataCubeSourceLoader.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/package.json +3 -2
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +4 -0
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeDataCubeEngine.js +147 -71
- package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
- package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +4 -2
- package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeDuckDBEngine.js +52 -0
- package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
- package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.d.ts +2 -8
- package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.d.ts.map +1 -1
- package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.js +21 -54
- package/lib/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.js.map +1 -1
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.d.ts +12 -0
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.d.ts.map +1 -1
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.js +67 -6
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.js.map +1 -1
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.d.ts +3 -0
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.d.ts.map +1 -1
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.js +15 -1
- package/lib/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.js.map +1 -1
- package/lib/stores/model/LakehouseConsumerDataCubeSource.d.ts +2 -2
- package/lib/stores/model/LakehouseConsumerDataCubeSource.d.ts.map +1 -1
- package/lib/stores/model/LakehouseConsumerDataCubeSource.js.map +1 -1
- package/lib/stores/model/LakehouseProducerDataCubeSource.d.ts +13 -0
- package/lib/stores/model/LakehouseProducerDataCubeSource.d.ts.map +1 -1
- package/lib/stores/model/LakehouseProducerDataCubeSource.js +19 -2
- package/lib/stores/model/LakehouseProducerDataCubeSource.js.map +1 -1
- package/lib/stores/model/SecondaryOauthClient.d.ts +26 -0
- package/lib/stores/model/SecondaryOauthClient.d.ts.map +1 -0
- package/lib/stores/model/SecondaryOauthClient.js +48 -0
- package/lib/stores/model/SecondaryOauthClient.js.map +1 -0
- package/package.json +10 -9
- package/src/components/LegendDataCubeWebApplication.tsx +1 -0
- package/src/components/builder/source/LakehouseConsumerDataCubeSourceBuilder.tsx +7 -32
- package/src/components/builder/source/LakehouseProducerDataCubeSourceBuilder.tsx +40 -14
- package/src/components/builder/source/LakehouseProducerDataCubeSourceLoader.tsx +4 -0
- package/src/stores/LegendDataCubeDataCubeEngine.ts +195 -80
- package/src/stores/LegendDataCubeDuckDBEngine.ts +73 -1
- package/src/stores/builder/source/LakehouseConsumerDataCubeSourceBuilderState.ts +31 -74
- package/src/stores/builder/source/LakehouseProducerDataCubeSourceBuilderState.ts +125 -6
- package/src/stores/builder/source/LakehouseProducerDataCubeSourceLoaderState.ts +29 -3
- package/src/stores/model/LakehouseConsumerDataCubeSource.ts +2 -2
- package/src/stores/model/LakehouseProducerDataCubeSource.ts +26 -1
- package/src/stores/model/SecondaryOauthClient.ts +56 -0
- 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
|
|
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 {
|
|
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
|
-
|
|
135
|
-
<div className="query-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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[][];
|