@finos/legend-application-data-cube 0.1.20 → 0.2.0
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/query-builder/LegendDataCubeQueryBuilder.d.ts.map +1 -1
- package/lib/components/query-builder/LegendDataCubeQueryBuilder.js +11 -2
- package/lib/components/query-builder/LegendDataCubeQueryBuilder.js.map +1 -1
- package/lib/index.css +1 -1
- package/lib/package.json +2 -1
- package/lib/stores/LegendDataCubeCacheManager.d.ts +25 -0
- package/lib/stores/LegendDataCubeCacheManager.d.ts.map +1 -0
- package/lib/stores/LegendDataCubeCacheManager.js +107 -0
- package/lib/stores/LegendDataCubeCacheManager.js.map +1 -0
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +13 -3
- package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
- package/lib/stores/LegendDataCubeDataCubeEngine.js +157 -3
- package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
- package/lib/stores/query-builder/LegendDataCubeNewQueryState.d.ts +1 -2
- package/lib/stores/query-builder/LegendDataCubeNewQueryState.d.ts.map +1 -1
- package/lib/stores/query-builder/LegendDataCubeNewQueryState.js +3 -22
- package/lib/stores/query-builder/LegendDataCubeNewQueryState.js.map +1 -1
- package/package.json +7 -6
- package/src/components/query-builder/LegendDataCubeQueryBuilder.tsx +12 -3
- package/src/stores/DuckDBWASM.d.ts +22 -0
- package/src/stores/LegendDataCubeCacheManager.ts +132 -0
- package/src/stores/LegendDataCubeDataCubeEngine.ts +244 -2
- package/src/stores/query-builder/LegendDataCubeNewQueryState.tsx +3 -31
- package/tsconfig.json +2 -0
|
@@ -23,7 +23,6 @@ import { LegendDataCubeNewQueryBuilder } from '../../components/query-builder/Le
|
|
|
23
23
|
import { AdhocQueryDataCubeSourceBuilderState } from './source-builder/AdhocQueryDataCubeSourceBuilderState.js';
|
|
24
24
|
import { LegendDataCubeQueryBuilderState, } from './LegendDataCubeQueryBuilderStore.js';
|
|
25
25
|
import { generateQueryBuilderRoute } from '../../__lib__/LegendDataCubeNavigation.js';
|
|
26
|
-
import { LEGEND_QUERY_DATA_CUBE_SOURCE_TYPE, RawLegendQueryDataCubeSource, } from '../model/LegendQueryDataCubeSource.js';
|
|
27
26
|
export class LegendDataCubeNewQueryState {
|
|
28
27
|
_application;
|
|
29
28
|
_store;
|
|
@@ -48,24 +47,6 @@ export class LegendDataCubeNewQueryState {
|
|
|
48
47
|
});
|
|
49
48
|
this.sourceBuilder = this.createSourceBuilder(LegendDataCubeSourceBuilderType.LEGEND_QUERY);
|
|
50
49
|
}
|
|
51
|
-
initWithSourceData(source) {
|
|
52
|
-
if (source._type === LEGEND_QUERY_DATA_CUBE_SOURCE_TYPE) {
|
|
53
|
-
this.changeSourceBuilder(LegendDataCubeSourceBuilderType.LEGEND_QUERY);
|
|
54
|
-
const serializedSourceData = RawLegendQueryDataCubeSource.serialization.fromJson(source);
|
|
55
|
-
this._store.graphManager
|
|
56
|
-
.getLightQuery(serializedSourceData.queryId)
|
|
57
|
-
.then(async (currentQuery) => {
|
|
58
|
-
if (this.sourceBuilder instanceof LegendQueryDataCubeSourceBuilderState) {
|
|
59
|
-
this.sourceBuilder.query = currentQuery;
|
|
60
|
-
await this.finalize();
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
.catch(this._application.alertUnhandledError);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
this._application.notificationService.notifyError(`Can't generate query: source of type ${source._type} is not supported`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
50
|
changeSourceBuilder(type, skipCheck) {
|
|
70
51
|
if (this.sourceBuilder.label !== type || skipCheck) {
|
|
71
52
|
this.sourceBuilder = this.createSourceBuilder(type);
|
|
@@ -81,13 +62,13 @@ export class LegendDataCubeNewQueryState {
|
|
|
81
62
|
throw new UnsupportedOperationError(`Can't create source builder for unsupported type '${type}'`);
|
|
82
63
|
}
|
|
83
64
|
}
|
|
84
|
-
async finalize() {
|
|
85
|
-
if (!this.sourceBuilder.isValid) {
|
|
65
|
+
async finalize(sourceData) {
|
|
66
|
+
if (!this.sourceBuilder.isValid && !sourceData) {
|
|
86
67
|
throw new IllegalStateError(`Can't generate query: source is not valid`);
|
|
87
68
|
}
|
|
88
69
|
this.finalizeState.inProgress();
|
|
89
70
|
try {
|
|
90
|
-
const source = await this.sourceBuilder.build();
|
|
71
|
+
const source = sourceData ?? (await this.sourceBuilder.build());
|
|
91
72
|
const query = new DataCubeQuery();
|
|
92
73
|
const processedSource = await this._engine.processQuerySource(source);
|
|
93
74
|
query.source = source;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegendDataCubeNewQueryState.js","sourceRoot":"","sources":["../../../src/stores/query-builder/LegendDataCubeNewQueryState.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,GAE1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,qCAAqC,EAAE,MAAM,2DAA2D,CAAC;AAElH,OAAO,EACL,+BAA+B,GAEhC,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EACL,eAAe,EAEf,aAAa,EACb,gCAAgC,GAEjC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,6BAA6B,EAAE,MAAM,iEAAiE,CAAC;AAChH,OAAO,EAAE,oCAAoC,EAAE,MAAM,0DAA0D,CAAC;AAChH,OAAO,EACL,+BAA+B,GAEhC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"LegendDataCubeNewQueryState.js","sourceRoot":"","sources":["../../../src/stores/query-builder/LegendDataCubeNewQueryState.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos/legend-application-data-cube",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Legend DataCube application core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"legend",
|
|
@@ -42,12 +42,13 @@
|
|
|
42
42
|
"test:watch": "jest --watch"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"@duckdb/duckdb-wasm": "1.29.0",
|
|
45
46
|
"@finos/legend-application": "16.0.22",
|
|
46
47
|
"@finos/legend-art": "7.1.79",
|
|
47
|
-
"@finos/legend-code-editor": "2.0.
|
|
48
|
-
"@finos/legend-data-cube": "0.0
|
|
49
|
-
"@finos/legend-graph": "32.0
|
|
50
|
-
"@finos/legend-query-builder": "4.
|
|
48
|
+
"@finos/legend-code-editor": "2.0.42",
|
|
49
|
+
"@finos/legend-data-cube": "0.1.0",
|
|
50
|
+
"@finos/legend-graph": "32.1.0",
|
|
51
|
+
"@finos/legend-query-builder": "4.16.0",
|
|
51
52
|
"@finos/legend-server-depot": "6.0.78",
|
|
52
53
|
"@finos/legend-shared": "11.0.1",
|
|
53
54
|
"@finos/legend-storage": "3.0.120",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"serializr": "3.0.3"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
|
-
"@finos/legend-dev-utils": "2.
|
|
64
|
+
"@finos/legend-dev-utils": "2.2.0",
|
|
64
65
|
"@jest/globals": "29.7.0",
|
|
65
66
|
"cross-env": "7.0.3",
|
|
66
67
|
"eslint": "9.19.0",
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from '../../__lib__/LegendDataCubeNavigation.js';
|
|
34
34
|
import { useEffect } from 'react';
|
|
35
35
|
import { LegendDataCubeSettingStorageKey } from '../../__lib__/LegendDataCubeSetting.js';
|
|
36
|
-
import type
|
|
36
|
+
import { assertErrorThrown, type PlainObject } from '@finos/legend-shared';
|
|
37
37
|
|
|
38
38
|
const LegendDataCubeQueryBuilderHeader = observer(() => {
|
|
39
39
|
const store = useLegendDataCubeQueryBuilderStore();
|
|
@@ -76,8 +76,16 @@ export const LegendDataCubeQueryBuilder = withLegendDataCubeQueryBuilderStore(
|
|
|
76
76
|
|
|
77
77
|
useEffect(() => {
|
|
78
78
|
if (sourceData) {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
try {
|
|
80
|
+
const sourceDataJson = JSON.parse(
|
|
81
|
+
decodeURIComponent(atob(sourceData)),
|
|
82
|
+
) as PlainObject;
|
|
83
|
+
store.newQueryState
|
|
84
|
+
.finalize(sourceDataJson)
|
|
85
|
+
.catch((error) => store.alertService.alertUnhandledError(error));
|
|
86
|
+
} catch (error) {
|
|
87
|
+
assertErrorThrown(error);
|
|
88
|
+
}
|
|
81
89
|
} else if (queryId !== store.builder?.persistentQuery?.id) {
|
|
82
90
|
store
|
|
83
91
|
.loadQuery(queryId)
|
|
@@ -149,6 +157,7 @@ export const LegendDataCubeQueryBuilder = withLegendDataCubeQueryBuilderStore(
|
|
|
149
157
|
);
|
|
150
158
|
},
|
|
151
159
|
documentationUrl: application.documentationService.url,
|
|
160
|
+
enableCache: true,
|
|
152
161
|
}}
|
|
153
162
|
/>
|
|
154
163
|
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-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
|
+
|
|
17
|
+
declare module '*.wasm' {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
const value: any;
|
|
20
|
+
// eslint-disable-next-line import/no-default-export
|
|
21
|
+
export default value;
|
|
22
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-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
|
+
|
|
17
|
+
import * as duckdb from '@duckdb/duckdb-wasm';
|
|
18
|
+
import duckdb_wasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm';
|
|
19
|
+
import duckdb_wasm_next from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm';
|
|
20
|
+
import {
|
|
21
|
+
TDSExecutionResult,
|
|
22
|
+
TDSRow,
|
|
23
|
+
TabularDataSet,
|
|
24
|
+
} from '@finos/legend-graph';
|
|
25
|
+
import type { AsyncDuckDBConnection } from '@duckdb/duckdb-wasm';
|
|
26
|
+
import { assertNonNullable } from '@finos/legend-shared';
|
|
27
|
+
|
|
28
|
+
export class LegendDataCubeDataCubeCacheEngine {
|
|
29
|
+
private _database?: duckdb.AsyncDuckDB | undefined;
|
|
30
|
+
private _connection?: AsyncDuckDBConnection | undefined;
|
|
31
|
+
|
|
32
|
+
// Documentation: https://duckdb.org/docs/api/wasm/instantiation.html
|
|
33
|
+
async initializeDuckDb(result: TDSExecutionResult) {
|
|
34
|
+
const MANUAL_BUNDLES: duckdb.DuckDBBundles = {
|
|
35
|
+
mvp: {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
37
|
+
mainModule: duckdb_wasm,
|
|
38
|
+
mainWorker: new URL(
|
|
39
|
+
'@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js',
|
|
40
|
+
import.meta.url,
|
|
41
|
+
).toString(),
|
|
42
|
+
},
|
|
43
|
+
eh: {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
45
|
+
mainModule: duckdb_wasm_next,
|
|
46
|
+
mainWorker: new URL(
|
|
47
|
+
'@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js',
|
|
48
|
+
import.meta.url,
|
|
49
|
+
).toString(),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
// Select a bundle based on browser checks
|
|
53
|
+
const bundle = await duckdb.selectBundle(MANUAL_BUNDLES);
|
|
54
|
+
// Instantiate the asynchronus version of DuckDB-wasm
|
|
55
|
+
assertNonNullable(bundle.mainWorker, `Can't initialize duck db`);
|
|
56
|
+
const worker = new Worker(bundle.mainWorker);
|
|
57
|
+
const logger = new duckdb.ConsoleLogger();
|
|
58
|
+
this._database = new duckdb.AsyncDuckDB(logger, worker);
|
|
59
|
+
await this._database.instantiate(bundle.mainModule, bundle.pthreadWorker);
|
|
60
|
+
this._connection = await this._database.connect();
|
|
61
|
+
|
|
62
|
+
const columns: string[] = [];
|
|
63
|
+
result.builder.columns.forEach((col) =>
|
|
64
|
+
columns.push(`"${col.name}" ${this.getDuckDbType(col.type)}`),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const CREATE_TABLE_SQL = `CREATE TABLE cached_tbl (${columns.join(',')})`;
|
|
68
|
+
await this._connection.query(CREATE_TABLE_SQL);
|
|
69
|
+
|
|
70
|
+
const rowString: string[] = [];
|
|
71
|
+
|
|
72
|
+
result.result.rows.forEach((row) => {
|
|
73
|
+
const updatedRows = row.values.map((val) => {
|
|
74
|
+
if (val !== null && typeof val === 'string') {
|
|
75
|
+
return `'${val.replaceAll(`'`, `''`)}'`;
|
|
76
|
+
} else if (val === null) {
|
|
77
|
+
return `NULL`;
|
|
78
|
+
}
|
|
79
|
+
return val;
|
|
80
|
+
});
|
|
81
|
+
rowString.push(`(${updatedRows.join(',')})`);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const INSERT_TABLE_SQL = `INSERT INTO cached_tbl VALUES ${rowString.join(',')}`;
|
|
85
|
+
|
|
86
|
+
await this._connection.query(INSERT_TABLE_SQL);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async runQuery(sql: string) {
|
|
90
|
+
const result = await this._connection?.query(sql);
|
|
91
|
+
const columnNames = Object.keys(result?.toArray().at(0));
|
|
92
|
+
const rows = result?.toArray().map((row) => {
|
|
93
|
+
const values = new TDSRow();
|
|
94
|
+
values.values = columnNames.map(
|
|
95
|
+
(column) => row[column] as string | number | boolean | null,
|
|
96
|
+
);
|
|
97
|
+
return values;
|
|
98
|
+
});
|
|
99
|
+
const tdsExecutionResult = new TDSExecutionResult();
|
|
100
|
+
const tds = new TabularDataSet();
|
|
101
|
+
tds.columns = columnNames;
|
|
102
|
+
tds.rows = rows !== undefined ? rows : [new TDSRow()];
|
|
103
|
+
tdsExecutionResult.result = tds;
|
|
104
|
+
return tdsExecutionResult;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async clearDuckDb() {
|
|
108
|
+
await this._connection?.close();
|
|
109
|
+
await this._database?.flushFiles();
|
|
110
|
+
await this._database?.terminate();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private getDuckDbType(type: string | undefined): string {
|
|
114
|
+
switch (type?.toLowerCase()) {
|
|
115
|
+
//TODO: mapping from tds build to duckdb data types
|
|
116
|
+
case 'string':
|
|
117
|
+
return 'VARCHAR';
|
|
118
|
+
case 'boolean':
|
|
119
|
+
return 'BOOLEAN';
|
|
120
|
+
case 'bigint':
|
|
121
|
+
return 'BIGINT';
|
|
122
|
+
case 'number':
|
|
123
|
+
return 'DOUBLE';
|
|
124
|
+
case 'integer':
|
|
125
|
+
return 'INTEGER';
|
|
126
|
+
case 'date':
|
|
127
|
+
return 'TIMESTAMP';
|
|
128
|
+
default:
|
|
129
|
+
return 'VARCHAR';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
V1_Lambda,
|
|
19
|
+
V1_ValueSpecification,
|
|
20
20
|
type V1_EngineServerClient,
|
|
21
21
|
V1_PureGraphManager,
|
|
22
22
|
type V1_PureModelContext,
|
|
@@ -44,6 +44,33 @@ import {
|
|
|
44
44
|
V1_PackageableType,
|
|
45
45
|
V1_deserializeRawValueSpecificationType,
|
|
46
46
|
V1_Protocol,
|
|
47
|
+
type V1_ExecutionPlan,
|
|
48
|
+
V1_deserializeExecutionPlan,
|
|
49
|
+
V1_SQLExecutionNode,
|
|
50
|
+
V1_SimpleExecutionPlan,
|
|
51
|
+
V1_Binary,
|
|
52
|
+
V1_ClassInstance,
|
|
53
|
+
V1_ClassInstanceType,
|
|
54
|
+
V1_Column,
|
|
55
|
+
V1_Database,
|
|
56
|
+
V1_Date,
|
|
57
|
+
V1_Double,
|
|
58
|
+
V1_DuckDBDatasourceSpecification,
|
|
59
|
+
V1_EngineRuntime,
|
|
60
|
+
V1_IdentifiedConnection,
|
|
61
|
+
V1_Integer,
|
|
62
|
+
V1_PackageableElementPointer,
|
|
63
|
+
V1_PackageableRuntime,
|
|
64
|
+
V1_PureModelContextData,
|
|
65
|
+
V1_RelationStoreAccessor,
|
|
66
|
+
type V1_RelationalDataType,
|
|
67
|
+
V1_RelationalDatabaseConnection,
|
|
68
|
+
V1_Schema,
|
|
69
|
+
V1_StoreConnections,
|
|
70
|
+
V1_Table,
|
|
71
|
+
V1_TestAuthenticationStrategy,
|
|
72
|
+
V1_VarChar,
|
|
73
|
+
type TDSBuilder,
|
|
47
74
|
} from '@finos/legend-graph';
|
|
48
75
|
import {
|
|
49
76
|
_elementPtr,
|
|
@@ -61,6 +88,8 @@ import {
|
|
|
61
88
|
_deserializeValueSpecification,
|
|
62
89
|
_defaultPrimitiveTypeValue,
|
|
63
90
|
type DataCubeExecutionOptions,
|
|
91
|
+
CachedDataCubeSource,
|
|
92
|
+
type DataCubeQuerySnapshot,
|
|
64
93
|
} from '@finos/legend-data-cube';
|
|
65
94
|
import {
|
|
66
95
|
isNonNullable,
|
|
@@ -72,8 +101,11 @@ import {
|
|
|
72
101
|
HttpStatus,
|
|
73
102
|
at,
|
|
74
103
|
assertType,
|
|
104
|
+
guaranteeType,
|
|
105
|
+
assertNonNullable,
|
|
75
106
|
} from '@finos/legend-shared';
|
|
76
107
|
import type { LegendDataCubeApplicationStore } from './LegendDataCubeBaseStore.js';
|
|
108
|
+
import { LegendDataCubeDataCubeCacheEngine } from './LegendDataCubeCacheManager.js';
|
|
77
109
|
import { APPLICATION_EVENT } from '@finos/legend-application';
|
|
78
110
|
import {
|
|
79
111
|
LEGEND_QUERY_DATA_CUBE_SOURCE_TYPE,
|
|
@@ -91,6 +123,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
91
123
|
private readonly _depotServerClient: DepotServerClient;
|
|
92
124
|
private readonly _engineServerClient: V1_EngineServerClient;
|
|
93
125
|
private readonly _graphManager: V1_PureGraphManager;
|
|
126
|
+
private readonly _cacheManager: LegendDataCubeDataCubeCacheEngine;
|
|
94
127
|
|
|
95
128
|
constructor(
|
|
96
129
|
application: LegendDataCubeApplicationStore,
|
|
@@ -104,6 +137,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
104
137
|
this._depotServerClient = depotServerClient;
|
|
105
138
|
this._engineServerClient = engineServerClient;
|
|
106
139
|
this._graphManager = graphManager;
|
|
140
|
+
this._cacheManager = new LegendDataCubeDataCubeCacheEngine();
|
|
107
141
|
}
|
|
108
142
|
|
|
109
143
|
// ---------------------------------- IMPLEMENTATION ----------------------------------
|
|
@@ -369,6 +403,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
369
403
|
) {
|
|
370
404
|
const queryCodePromise = this.getValueSpecificationCode(query);
|
|
371
405
|
let result: ExecutionResult;
|
|
406
|
+
const startTime = performance.now();
|
|
372
407
|
if (source instanceof AdhocQueryDataCubeSource) {
|
|
373
408
|
result = await this._runQuery(query, source.model, undefined, options);
|
|
374
409
|
} else if (source instanceof LegendQueryDataCubeSource) {
|
|
@@ -379,6 +414,35 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
379
414
|
source.parameterValues,
|
|
380
415
|
options,
|
|
381
416
|
);
|
|
417
|
+
} else if (source instanceof CachedDataCubeSource) {
|
|
418
|
+
//execute plan
|
|
419
|
+
const executionPlan = await this.generateExecutionPlan(
|
|
420
|
+
query,
|
|
421
|
+
source.model,
|
|
422
|
+
[],
|
|
423
|
+
options,
|
|
424
|
+
);
|
|
425
|
+
let sql;
|
|
426
|
+
if (executionPlan instanceof V1_SimpleExecutionPlan) {
|
|
427
|
+
sql = executionPlan.rootExecutionNode.executionNodes
|
|
428
|
+
.filter((node) => node instanceof V1_SQLExecutionNode)
|
|
429
|
+
.map((x) =>
|
|
430
|
+
guaranteeType(
|
|
431
|
+
x,
|
|
432
|
+
V1_SQLExecutionNode,
|
|
433
|
+
`Can't generate sql for the query`,
|
|
434
|
+
),
|
|
435
|
+
)
|
|
436
|
+
.at(-1)?.sqlQuery;
|
|
437
|
+
}
|
|
438
|
+
assertNonNullable(sql, `Can't generate sql for the query`);
|
|
439
|
+
const endTime = performance.now();
|
|
440
|
+
return {
|
|
441
|
+
executedQuery: await queryCodePromise,
|
|
442
|
+
executedSQL: sql,
|
|
443
|
+
result: await this._cacheManager.runQuery(sql),
|
|
444
|
+
executionTime: endTime - startTime,
|
|
445
|
+
};
|
|
382
446
|
} else {
|
|
383
447
|
throw new UnsupportedOperationError(
|
|
384
448
|
`Can't execute query with unsupported source`,
|
|
@@ -389,6 +453,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
389
453
|
TDSExecutionResult,
|
|
390
454
|
`Can't extract execution result: expected tabular data set format`,
|
|
391
455
|
);
|
|
456
|
+
const endTime = performance.now();
|
|
392
457
|
const queryCode = await queryCodePromise;
|
|
393
458
|
const sql =
|
|
394
459
|
result.activities?.[0] instanceof RelationalExecutionActivities
|
|
@@ -401,6 +466,7 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
401
466
|
result: result,
|
|
402
467
|
executedQuery: queryCode,
|
|
403
468
|
executedSQL: sql,
|
|
469
|
+
executionTime: endTime - startTime,
|
|
404
470
|
};
|
|
405
471
|
}
|
|
406
472
|
|
|
@@ -418,10 +484,25 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
418
484
|
_elementPtr(source.runtime),
|
|
419
485
|
].filter(isNonNullable),
|
|
420
486
|
);
|
|
487
|
+
} else if (source instanceof CachedDataCubeSource) {
|
|
488
|
+
return _function(
|
|
489
|
+
DataCubeFunction.FROM,
|
|
490
|
+
[_elementPtr(source.runtime)].filter(isNonNullable),
|
|
491
|
+
);
|
|
421
492
|
}
|
|
422
493
|
return undefined;
|
|
423
494
|
}
|
|
424
495
|
|
|
496
|
+
override processInitialSnapshot(
|
|
497
|
+
source: DataCubeSource,
|
|
498
|
+
snapshot: DataCubeQuerySnapshot,
|
|
499
|
+
): DataCubeQuerySnapshot {
|
|
500
|
+
if (source instanceof LegendQueryDataCubeSource) {
|
|
501
|
+
snapshot.data.configuration.name = source.info.name;
|
|
502
|
+
}
|
|
503
|
+
return snapshot;
|
|
504
|
+
}
|
|
505
|
+
|
|
425
506
|
// ---------------------------------- UTILITIES ----------------------------------
|
|
426
507
|
|
|
427
508
|
private async _getQueryRelationType(
|
|
@@ -432,6 +513,8 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
432
513
|
return this._getLambdaRelationType(query, source.model);
|
|
433
514
|
} else if (source instanceof LegendQueryDataCubeSource) {
|
|
434
515
|
return this._getLambdaRelationType(query, source.model);
|
|
516
|
+
} else if (source instanceof CachedDataCubeSource) {
|
|
517
|
+
return this._getLambdaRelationType(query, serialize(source.model));
|
|
435
518
|
}
|
|
436
519
|
throw new UnsupportedOperationError(
|
|
437
520
|
`Can't get relation type for lambda with unsupported source`,
|
|
@@ -486,6 +569,165 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
|
|
|
486
569
|
);
|
|
487
570
|
}
|
|
488
571
|
|
|
572
|
+
private async generateExecutionPlan(
|
|
573
|
+
query: V1_Lambda,
|
|
574
|
+
model: V1_PureModelContext,
|
|
575
|
+
parameterValues?: V1_ParameterValue[] | undefined,
|
|
576
|
+
options?: DataCubeExecutionOptions | undefined,
|
|
577
|
+
): Promise<V1_ExecutionPlan> {
|
|
578
|
+
return V1_deserializeExecutionPlan(
|
|
579
|
+
await this._engineServerClient.generatePlan({
|
|
580
|
+
clientVersion:
|
|
581
|
+
options?.clientVersion ??
|
|
582
|
+
// eslint-disable-next-line no-process-env
|
|
583
|
+
(process.env.NODE_ENV === 'development'
|
|
584
|
+
? PureClientVersion.VX_X_X
|
|
585
|
+
: undefined),
|
|
586
|
+
function: _serializeValueSpecification(query),
|
|
587
|
+
model: serialize(model),
|
|
588
|
+
context: serialize(
|
|
589
|
+
V1_rawBaseExecutionContextModelSchema,
|
|
590
|
+
new V1_RawBaseExecutionContext(),
|
|
591
|
+
),
|
|
592
|
+
parameterValues: (parameterValues ?? []).map((parameterValue) =>
|
|
593
|
+
serialize(V1_parameterValueModelSchema, parameterValue),
|
|
594
|
+
),
|
|
595
|
+
}),
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ---------------------------------- CACHING --------------------------------------
|
|
600
|
+
|
|
601
|
+
override async initializeCache(
|
|
602
|
+
source: DataCubeSource,
|
|
603
|
+
): Promise<CachedDataCubeSource | undefined> {
|
|
604
|
+
if (source instanceof LegendQueryDataCubeSource) {
|
|
605
|
+
const fromQuery = this.buildExecutionContext(source);
|
|
606
|
+
fromQuery?.parameters.unshift(source.query);
|
|
607
|
+
const valSpec = guaranteeType(
|
|
608
|
+
fromQuery,
|
|
609
|
+
V1_ValueSpecification,
|
|
610
|
+
'Query is not a valueSpecification',
|
|
611
|
+
);
|
|
612
|
+
const queryLambda = new V1_Lambda();
|
|
613
|
+
queryLambda.body = [valSpec];
|
|
614
|
+
const resultQuery = await this.executeQuery(
|
|
615
|
+
queryLambda,
|
|
616
|
+
source,
|
|
617
|
+
undefined,
|
|
618
|
+
);
|
|
619
|
+
const result = resultQuery.result;
|
|
620
|
+
await this._cacheManager.initializeDuckDb(result);
|
|
621
|
+
return this._synthesizeCachedSource(source, result.builder);
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
override async clearCache() {
|
|
627
|
+
await this._cacheManager.clearDuckDb();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// --------------------------------- CACHING UTILITY -------------------------------
|
|
631
|
+
|
|
632
|
+
private _synthesizeCachedSource(
|
|
633
|
+
source: LegendQueryDataCubeSource,
|
|
634
|
+
builder: TDSBuilder,
|
|
635
|
+
) {
|
|
636
|
+
const cachedSource = new CachedDataCubeSource();
|
|
637
|
+
cachedSource.columns = source.columns;
|
|
638
|
+
cachedSource.query = this._synthesizeQuery([
|
|
639
|
+
'local::duckdb::cachedStore',
|
|
640
|
+
'main',
|
|
641
|
+
'cached_tbl',
|
|
642
|
+
]);
|
|
643
|
+
cachedSource.model = this._synthesizeModel(builder);
|
|
644
|
+
cachedSource.runtime = 'local::duckdb::runtime';
|
|
645
|
+
return cachedSource;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private _synthesizeQuery(databaseAccessor: string[]) {
|
|
649
|
+
const classInstance = new V1_ClassInstance();
|
|
650
|
+
classInstance.type = V1_ClassInstanceType.RELATION_STORE_ACCESSOR;
|
|
651
|
+
const storeAccessor = new V1_RelationStoreAccessor();
|
|
652
|
+
storeAccessor.path = databaseAccessor;
|
|
653
|
+
classInstance.value = storeAccessor;
|
|
654
|
+
return classInstance;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private _synthesizeModel(builder: TDSBuilder) {
|
|
658
|
+
// synthesize table
|
|
659
|
+
const table = new V1_Table();
|
|
660
|
+
table.name = 'cached_tbl';
|
|
661
|
+
table.columns = builder.columns.map((col) => {
|
|
662
|
+
const column = new V1_Column();
|
|
663
|
+
column.name = col.name;
|
|
664
|
+
column.type = this._getColumnType(col.type);
|
|
665
|
+
return column;
|
|
666
|
+
});
|
|
667
|
+
// synthesize schema
|
|
668
|
+
const schema = new V1_Schema();
|
|
669
|
+
schema.name = 'main';
|
|
670
|
+
schema.tables = [table];
|
|
671
|
+
// synthesize database
|
|
672
|
+
const database = new V1_Database();
|
|
673
|
+
database.name = 'cachedStore';
|
|
674
|
+
database.package = 'local::duckdb';
|
|
675
|
+
database.schemas = [schema];
|
|
676
|
+
|
|
677
|
+
// build connection
|
|
678
|
+
const connection = new V1_RelationalDatabaseConnection();
|
|
679
|
+
connection.databaseType = 'DuckDB';
|
|
680
|
+
connection.type = 'DuckDB';
|
|
681
|
+
const dataSourceSpec = new V1_DuckDBDatasourceSpecification();
|
|
682
|
+
dataSourceSpec.path = '/temp/path';
|
|
683
|
+
connection.store = 'local::duckdb::cachedStore';
|
|
684
|
+
connection.datasourceSpecification = dataSourceSpec;
|
|
685
|
+
connection.authenticationStrategy = new V1_TestAuthenticationStrategy();
|
|
686
|
+
|
|
687
|
+
// build runtime
|
|
688
|
+
const runtime = new V1_EngineRuntime();
|
|
689
|
+
const storeConnections = new V1_StoreConnections();
|
|
690
|
+
storeConnections.store = new V1_PackageableElementPointer(
|
|
691
|
+
'STORE',
|
|
692
|
+
`${database.package}::${database.name}`,
|
|
693
|
+
);
|
|
694
|
+
const idConnection = new V1_IdentifiedConnection();
|
|
695
|
+
idConnection.connection = connection;
|
|
696
|
+
idConnection.id = 'local_duckdb_connection';
|
|
697
|
+
storeConnections.storeConnections = [idConnection];
|
|
698
|
+
runtime.connections = [storeConnections];
|
|
699
|
+
|
|
700
|
+
const packageableRuntime = new V1_PackageableRuntime();
|
|
701
|
+
packageableRuntime.runtimeValue = runtime;
|
|
702
|
+
packageableRuntime.package = 'local::duckdb';
|
|
703
|
+
packageableRuntime.name = 'runtime';
|
|
704
|
+
|
|
705
|
+
const pmcd = new V1_PureModelContextData();
|
|
706
|
+
pmcd.elements = [database, packageableRuntime];
|
|
707
|
+
return pmcd;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// TODO: need a better way to infer datatype from tds builder
|
|
711
|
+
private _getColumnType(type: string | undefined): V1_RelationalDataType {
|
|
712
|
+
if (type === undefined) {
|
|
713
|
+
throw Error('Unsupported data type');
|
|
714
|
+
}
|
|
715
|
+
switch (type) {
|
|
716
|
+
case 'string':
|
|
717
|
+
return new V1_VarChar();
|
|
718
|
+
case 'integer':
|
|
719
|
+
return new V1_Integer();
|
|
720
|
+
case 'date':
|
|
721
|
+
return new V1_Date();
|
|
722
|
+
case 'boolean':
|
|
723
|
+
return new V1_Binary();
|
|
724
|
+
case 'number':
|
|
725
|
+
return new V1_Double();
|
|
726
|
+
default:
|
|
727
|
+
return new V1_VarChar();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
489
731
|
// ---------------------------------- APPLICATION ----------------------------------
|
|
490
732
|
|
|
491
733
|
override logDebug(message: string, ...data: unknown[]) {
|