@finos/legend-application-data-cube 0.3.2 → 0.3.4

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 (103) hide show
  1. package/lib/__lib__/LegendDataCubeNavigation.d.ts +2 -0
  2. package/lib/__lib__/LegendDataCubeNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendDataCubeNavigation.js +2 -0
  4. package/lib/__lib__/LegendDataCubeNavigation.js.map +1 -1
  5. package/lib/application/LegendDataCubeApplicationConfig.d.ts +4 -0
  6. package/lib/application/LegendDataCubeApplicationConfig.d.ts.map +1 -1
  7. package/lib/application/LegendDataCubeApplicationConfig.js +5 -0
  8. package/lib/application/LegendDataCubeApplicationConfig.js.map +1 -1
  9. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts +18 -0
  10. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.d.ts.map +1 -0
  11. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js +48 -0
  12. package/lib/application/__test-utils__/LegendDataCubeApplicationTestUtils.js.map +1 -0
  13. package/lib/components/LegendDataCubeBlockingWindow.d.ts +2 -1
  14. package/lib/components/LegendDataCubeBlockingWindow.d.ts.map +1 -1
  15. package/lib/components/LegendDataCubeBlockingWindow.js +9 -3
  16. package/lib/components/LegendDataCubeBlockingWindow.js.map +1 -1
  17. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts +42 -0
  18. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.d.ts.map +1 -0
  19. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js +104 -0
  20. package/lib/components/__test-utils__/LegendDataCubeStoreTestUtils.js.map +1 -0
  21. package/lib/components/builder/LegendDataCubeBuilder.d.ts +5 -0
  22. package/lib/components/builder/LegendDataCubeBuilder.d.ts.map +1 -1
  23. package/lib/components/builder/LegendDataCubeBuilder.js +26 -5
  24. package/lib/components/builder/LegendDataCubeBuilder.js.map +1 -1
  25. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.d.ts.map +1 -1
  26. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js +2 -1
  27. package/lib/components/builder/LegendDataCubeBuilderStoreProvider.js.map +1 -1
  28. package/lib/components/builder/LegendDataCubeSourceViewer.d.ts.map +1 -1
  29. package/lib/components/builder/LegendDataCubeSourceViewer.js +61 -1
  30. package/lib/components/builder/LegendDataCubeSourceViewer.js.map +1 -1
  31. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts +25 -0
  32. package/lib/components/builder/source/LegendDataCubeSourceLoader.d.ts.map +1 -0
  33. package/lib/components/builder/source/LegendDataCubeSourceLoader.js +47 -0
  34. package/lib/components/builder/source/LegendDataCubeSourceLoader.js.map +1 -0
  35. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.d.ts.map +1 -1
  36. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js +1 -2
  37. package/lib/components/builder/source/LocalFileDataCubeSourceBuilder.js.map +1 -1
  38. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts +22 -0
  39. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.d.ts.map +1 -0
  40. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js +28 -0
  41. package/lib/components/builder/source/LocalFileDataCubeSourceLoader.js.map +1 -0
  42. package/lib/components/builder/source/UserDefinedFunctionDataCubeSourceBuilder.d.ts.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/package.json +7 -5
  46. package/lib/stores/LegendDataCubeBaseStore.d.ts +2 -1
  47. package/lib/stores/LegendDataCubeBaseStore.d.ts.map +1 -1
  48. package/lib/stores/LegendDataCubeBaseStore.js +6 -3
  49. package/lib/stores/LegendDataCubeBaseStore.js.map +1 -1
  50. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts +8 -5
  51. package/lib/stores/LegendDataCubeDataCubeEngine.d.ts.map +1 -1
  52. package/lib/stores/LegendDataCubeDataCubeEngine.js +132 -114
  53. package/lib/stores/LegendDataCubeDataCubeEngine.js.map +1 -1
  54. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts +11 -4
  55. package/lib/stores/LegendDataCubeDuckDBEngine.d.ts.map +1 -1
  56. package/lib/stores/LegendDataCubeDuckDBEngine.js +92 -20
  57. package/lib/stores/LegendDataCubeDuckDBEngine.js.map +1 -1
  58. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts +7 -0
  59. package/lib/stores/builder/LegendDataCubeBuilderStore.d.ts.map +1 -1
  60. package/lib/stores/builder/LegendDataCubeBuilderStore.js +68 -17
  61. package/lib/stores/builder/LegendDataCubeBuilderStore.js.map +1 -1
  62. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts +39 -0
  63. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.d.ts.map +1 -0
  64. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js +66 -0
  65. package/lib/stores/builder/source/LegendDataCubeSourceLoaderState.js.map +1 -0
  66. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts +3 -2
  67. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.d.ts.map +1 -1
  68. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js +7 -10
  69. package/lib/stores/builder/source/LocalFileDataCubeSourceBuilderState.js.map +1 -1
  70. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts +45 -0
  71. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.d.ts.map +1 -0
  72. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js +142 -0
  73. package/lib/stores/builder/source/LocalFileDataCubeSourceLoaderState.js.map +1 -0
  74. package/lib/stores/model/LegendQueryDataCubeSource.d.ts +2 -1
  75. package/lib/stores/model/LegendQueryDataCubeSource.d.ts.map +1 -1
  76. package/lib/stores/model/LegendQueryDataCubeSource.js +1 -0
  77. package/lib/stores/model/LegendQueryDataCubeSource.js.map +1 -1
  78. package/lib/stores/model/LocalFileDataCubeSource.d.ts +3 -8
  79. package/lib/stores/model/LocalFileDataCubeSource.d.ts.map +1 -1
  80. package/lib/stores/model/LocalFileDataCubeSource.js +5 -15
  81. package/lib/stores/model/LocalFileDataCubeSource.js.map +1 -1
  82. package/package.json +17 -15
  83. package/src/__lib__/LegendDataCubeNavigation.ts +21 -0
  84. package/src/application/LegendDataCubeApplicationConfig.ts +10 -0
  85. package/src/application/__test-utils__/LegendDataCubeApplicationTestUtils.ts +52 -0
  86. package/src/components/LegendDataCubeBlockingWindow.tsx +9 -2
  87. package/src/components/__test-utils__/LegendDataCubeStoreTestUtils.tsx +231 -0
  88. package/src/components/builder/LegendDataCubeBuilder.tsx +51 -6
  89. package/src/components/builder/LegendDataCubeBuilderStoreProvider.tsx +2 -0
  90. package/src/components/builder/LegendDataCubeSourceViewer.tsx +171 -1
  91. package/src/components/builder/source/LegendDataCubeSourceLoader.tsx +111 -0
  92. package/src/components/builder/source/LocalFileDataCubeSourceBuilder.tsx +1 -2
  93. package/src/components/builder/source/LocalFileDataCubeSourceLoader.tsx +58 -0
  94. package/src/stores/LegendDataCubeBaseStore.ts +13 -6
  95. package/src/stores/LegendDataCubeDataCubeEngine.ts +167 -131
  96. package/src/stores/LegendDataCubeDuckDBEngine.ts +110 -20
  97. package/src/stores/builder/LegendDataCubeBuilderStore.tsx +114 -23
  98. package/src/stores/builder/source/LegendDataCubeSourceLoaderState.tsx +104 -0
  99. package/src/stores/builder/source/LocalFileDataCubeSourceBuilderState.ts +9 -14
  100. package/src/stores/builder/source/LocalFileDataCubeSourceLoaderState.ts +232 -0
  101. package/src/stores/model/LegendQueryDataCubeSource.ts +2 -0
  102. package/src/stores/model/LocalFileDataCubeSource.ts +6 -15
  103. package/tsconfig.json +7 -1
@@ -0,0 +1,111 @@
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 { observer } from 'mobx-react-lite';
18
+ import { FormButton } from '@finos/legend-data-cube';
19
+ import { useLegendDataCubeBuilderStore } from '../LegendDataCubeBuilderStoreProvider.js';
20
+ import { LocalFileDataCubeSourceLoaderState } from '../../../stores/builder/source/LocalFileDataCubeSourceLoaderState.js';
21
+ import { LocalFileDataCubePartialSourceLoader } from './LocalFileDataCubeSourceLoader.js';
22
+ import { DataCubeIcon } from '@finos/legend-art';
23
+ import { formatDistanceToNow } from '@finos/legend-shared';
24
+ import type { LegendDataCubeSourceLoaderState } from '../../../stores/builder/source/LegendDataCubeSourceLoaderState.js';
25
+ import { LegendDataCubeBlockingWindow } from '../../LegendDataCubeBlockingWindow.js';
26
+
27
+ export const LegendDataCubeSourceLoader = observer(
28
+ (props: { state: LegendDataCubeSourceLoaderState }) => {
29
+ const { state } = props;
30
+ const store = useLegendDataCubeBuilderStore();
31
+ const persistentDataCube = state.persistentDataCube;
32
+
33
+ return (
34
+ <>
35
+ <div className="h-[calc(100%_-_40px)] w-full px-2 pt-2">
36
+ <div className="h-full w-full border border-neutral-300 bg-white">
37
+ <div className="h-full w-full select-none p-2">
38
+ <div className="relative mb-0.5 flex h-[42px] w-full border border-neutral-200 bg-neutral-100">
39
+ <div className="w-full">
40
+ <div className="h-6 w-4/5 overflow-hidden text-ellipsis whitespace-nowrap px-1.5 leading-6">
41
+ {persistentDataCube.name}
42
+ </div>
43
+ <div className="flex h-[18px] items-start justify-between px-1.5">
44
+ <div className="flex">
45
+ <DataCubeIcon.ClockEdit className="text-sm text-neutral-500" />
46
+ <div className="ml-1 text-sm text-neutral-500">
47
+ {persistentDataCube.lastUpdatedAt
48
+ ? formatDistanceToNow(
49
+ new Date(persistentDataCube.lastUpdatedAt),
50
+ {
51
+ includeSeconds: true,
52
+ addSuffix: true,
53
+ },
54
+ )
55
+ : '(unknown)'}
56
+ </div>
57
+ </div>
58
+ <div className="flex">
59
+ <DataCubeIcon.User className="text-sm text-neutral-500" />
60
+ <div className="ml-1 text-sm text-neutral-500">
61
+ {persistentDataCube.owner}
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div className="flex h-10 w-full items-center">
68
+ <div className="flex h-full w-32 flex-shrink-0 items-center text-sm">
69
+ Source Type:
70
+ <div className="pl-3">{state.label}</div>
71
+ </div>
72
+ </div>
73
+ <div className="h-[calc(100%_-_41px)] w-full overflow-auto">
74
+ {state instanceof LocalFileDataCubeSourceLoaderState && (
75
+ <LocalFileDataCubePartialSourceLoader
76
+ partialSourceLoader={state}
77
+ />
78
+ )}
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ <div className="flex h-10 items-center justify-end px-2">
84
+ <FormButton onClick={state.display.onClose}>Cancel</FormButton>
85
+ <FormButton
86
+ className="ml-2"
87
+ disabled={!state.isValid || state.finalizeState.isInProgress}
88
+ onClick={() => {
89
+ state.finalize().catch((error) => {
90
+ store.alertService.alertUnhandledError(error);
91
+ });
92
+ }}
93
+ >
94
+ OK
95
+ </FormButton>
96
+ </div>
97
+ </>
98
+ );
99
+ },
100
+ );
101
+
102
+ export const LegendDataCubeSourceLoaderBlockingWindow = observer(() => {
103
+ const store = useLegendDataCubeBuilderStore();
104
+
105
+ if (!store.sourceLoader) {
106
+ return null;
107
+ }
108
+ return (
109
+ <LegendDataCubeBlockingWindow windowState={store.sourceLoader.display} />
110
+ );
111
+ });
@@ -30,8 +30,7 @@ export const LocalFileDataCubeSourceBuilder = observer(
30
30
  type={AlertType.WARNING}
31
31
  text={`Currently, support for local file comes with the following limitations:
32
32
  - Only CSV files are supported, but not all variants of CSV files are supported (required header row, comma delimiter, single escape quote).
33
- - Data from uploaded file will not be stored nor shared.
34
- - DataCube created with local file source cannot be saved.`}
33
+ - Data from uploaded file will not be stored nor shared.`}
35
34
  />
36
35
  <div className="mt-2 flex h-6 w-full items-center text-neutral-500">
37
36
  <input
@@ -0,0 +1,58 @@
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 { observer } from 'mobx-react-lite';
18
+ import { AlertType, FormAlert, FormCodeEditor } from '@finos/legend-data-cube';
19
+ import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
20
+ import type { LocalFileDataCubeSourceLoaderState } from '../../../stores/builder/source/LocalFileDataCubeSourceLoaderState.js';
21
+
22
+ export const LocalFileDataCubePartialSourceLoader = observer(
23
+ (props: { partialSourceLoader: LocalFileDataCubeSourceLoaderState }) => {
24
+ const { partialSourceLoader } = props;
25
+
26
+ return (
27
+ <div className="h-full w-full">
28
+ <FormAlert
29
+ message="Local file support is experimental"
30
+ type={AlertType.WARNING}
31
+ text={`Currently, support for local file comes with the following limitations:
32
+ - Only CSV files are supported, but not all variants of CSV files are supported (required header row, comma delimiter, single escape quote).
33
+ - Data from uploaded file will not be stored nor shared.`}
34
+ />
35
+ <div className="mt-2 flex h-6 w-full items-center text-neutral-500">
36
+ <input
37
+ type="file"
38
+ onChange={(event) => {
39
+ partialSourceLoader.processFile(event.target.files?.[0]);
40
+ }}
41
+ className="w-full"
42
+ />
43
+ </div>
44
+ {partialSourceLoader.previewText !== undefined && (
45
+ <div className="mt-2 h-40">
46
+ <FormCodeEditor
47
+ value={partialSourceLoader.previewText}
48
+ language={CODE_EDITOR_LANGUAGE.TEXT}
49
+ isReadOnly={true}
50
+ hidePadding={true}
51
+ title="Data Preview"
52
+ />
53
+ </div>
54
+ )}
55
+ </div>
56
+ );
57
+ },
58
+ );
@@ -23,8 +23,9 @@ import type { LegendDataCubePluginManager } from '../application/LegendDataCubeP
23
23
  import { DepotServerClient } from '@finos/legend-server-depot';
24
24
  import type { LegendDataCubeApplicationConfig } from '../application/LegendDataCubeApplicationConfig.js';
25
25
  import {
26
- V1_EngineServerClient,
26
+ type V1_EngineServerClient,
27
27
  V1_PureGraphManager,
28
+ V1_RemoteEngine,
28
29
  } from '@finos/legend-graph';
29
30
  import {
30
31
  ActionState,
@@ -61,6 +62,7 @@ export class LegendDataCubeBaseStore {
61
62
  readonly pluginManager: LegendDataCubePluginManager;
62
63
  readonly depotServerClient: DepotServerClient;
63
64
  readonly graphManager: V1_PureGraphManager;
65
+ readonly remoteEngine: V1_RemoteEngine;
64
66
  readonly engineServerClient: V1_EngineServerClient;
65
67
 
66
68
  readonly engine: LegendDataCubeDataCubeEngine;
@@ -116,11 +118,15 @@ export class LegendDataCubeBaseStore {
116
118
  } satisfies DataCubeSetting<string>,
117
119
  ];
118
120
 
119
- this.engineServerClient = new V1_EngineServerClient({
120
- baseUrl: this.getEngineServerBaseUrlSettingValue(),
121
- queryBaseUrl: this.application.config.engineQueryServerUrl,
122
- enableCompression: this.getEngineEnableCompressionSettingValue(),
123
- });
121
+ this.remoteEngine = new V1_RemoteEngine(
122
+ {
123
+ baseUrl: this.getEngineServerBaseUrlSettingValue(),
124
+ queryBaseUrl: this.application.config.engineQueryServerUrl,
125
+ enableCompression: this.getEngineEnableCompressionSettingValue(),
126
+ },
127
+ application.logService,
128
+ );
129
+ this.engineServerClient = this.remoteEngine.getEngineServerClient();
124
130
  this.engineServerClient.setTracerService(application.tracerService);
125
131
 
126
132
  this.engine = new LegendDataCubeDataCubeEngine(
@@ -200,6 +206,7 @@ export class LegendDataCubeBaseStore {
200
206
  },
201
207
  },
202
208
  {
209
+ engine: this.remoteEngine,
203
210
  tracerService: this.application.tracerService,
204
211
  },
205
212
  );
@@ -82,6 +82,8 @@ import {
82
82
  V1_deserializePureModelContext,
83
83
  type V1_ConcreteFunctionDefinition,
84
84
  V1_deserializeValueSpecification,
85
+ LET_TOKEN,
86
+ V1_AppliedFunction,
85
87
  } from '@finos/legend-graph';
86
88
  import {
87
89
  _elementPtr,
@@ -101,6 +103,8 @@ import {
101
103
  DataCubeExecutionError,
102
104
  RawUserDefinedFunctionDataCubeSource,
103
105
  ADHOC_FUNCTION_DATA_CUBE_SOURCE_TYPE,
106
+ UserDefinedFunctionDataCubeSource,
107
+ DataCubeQueryFilterOperator,
104
108
  } from '@finos/legend-data-cube';
105
109
  import {
106
110
  isNonNullable,
@@ -199,12 +203,87 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
199
203
  RawLocalFileQueryDataCubeSource.serialization.fromJson(value);
200
204
  const source = new LocalFileDataCubeSource();
201
205
  source.fileName = rawSource.fileName;
202
- source.count = rawSource.count;
203
- source.db = rawSource.db;
204
- source.model = rawSource.model;
205
- source.runtime = rawSource.runtime;
206
- source.schema = rawSource.schema;
207
- source.table = rawSource.table;
206
+ source.fileFormat = rawSource.fileFormat;
207
+
208
+ const tableCatalog = this._duckDBEngine.retrieveCatalogTable(
209
+ rawSource._ref,
210
+ );
211
+
212
+ const { model, database, schema, table, runtime } =
213
+ this._synthesizeMinimalModelContext({
214
+ schemaName: tableCatalog.schemaName,
215
+ tableName: tableCatalog.tableName,
216
+ tableColumns: tableCatalog.columns.map((col) => {
217
+ const column = new V1_Column();
218
+ column.name = col[0] as string;
219
+ // TODO: confirm this is in accordance to engine
220
+ // check if we have a duckdb enum mapping
221
+ // See https://duckdb.org/docs/sql/data_types/overview.html
222
+ switch (col[1] as string) {
223
+ case 'BIT': {
224
+ column.type = new V1_Bit();
225
+ break;
226
+ }
227
+ case 'BOOLEAN': {
228
+ // TODO: understand why boolean is not present in relationalDataType
229
+ column.type = new V1_VarChar();
230
+ break;
231
+ }
232
+ case 'DATE': {
233
+ column.type = new V1_Date();
234
+ break;
235
+ }
236
+ case 'DECIMAL': {
237
+ column.type = new V1_Decimal();
238
+ break;
239
+ }
240
+ case 'DOUBLE': {
241
+ column.type = new V1_Double();
242
+ break;
243
+ }
244
+ case 'FLOAT': {
245
+ column.type = new V1_Float();
246
+ break;
247
+ }
248
+ case 'INTEGER': {
249
+ column.type = new V1_Integer();
250
+ break;
251
+ }
252
+ case 'TININT': {
253
+ column.type = new V1_TinyInt();
254
+ break;
255
+ }
256
+ case 'SMALLINT': {
257
+ column.type = new V1_SmallInt();
258
+ break;
259
+ }
260
+ case 'BIGINT': {
261
+ column.type = new V1_BigInt();
262
+ break;
263
+ }
264
+ case 'TIMESTAMP': {
265
+ column.type = new V1_Timestamp();
266
+ break;
267
+ }
268
+ case 'VARCHAR': {
269
+ column.type = new V1_VarChar();
270
+ break;
271
+ }
272
+ default: {
273
+ throw new UnsupportedOperationError(
274
+ `Can't ingest local file data: failed to find matching relational data type for DuckDB type '${col[1]}' when synthesizing table definition`,
275
+ );
276
+ }
277
+ }
278
+ return column;
279
+ }),
280
+ });
281
+
282
+ source.db = database.path;
283
+ source.model = model;
284
+ source.table = table.name;
285
+ source.schema = schema.name;
286
+ source.runtime = runtime.path;
208
287
 
209
288
  const query = new V1_ClassInstance();
210
289
  query.type = V1_ClassInstanceType.RELATION_STORE_ACCESSOR;
@@ -244,7 +323,8 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
244
323
  );
245
324
  }
246
325
 
247
- const source = new AdhocQueryDataCubeSource();
326
+ const source = new UserDefinedFunctionDataCubeSource();
327
+ source.functionPath = rawSource.functionPath;
248
328
  source.runtime = rawSource.runtime;
249
329
  source.model = rawSource.model;
250
330
  if (deserializedModel.sdlcInfo instanceof V1_LegendSDLC) {
@@ -327,32 +407,6 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
327
407
  ),
328
408
  );
329
409
  source.query = at(source.lambda.body, 0);
330
- // use the default parameter values from the query
331
- //
332
- // TODO?: we should probably allow configuring the parameters?
333
- // this would mean we need to create first-class support for parameters in DataCube component
334
- const parameterValues = await Promise.all(
335
- source.lambda.parameters.map(async (parameter) => {
336
- if (parameter.genericType?.rawType instanceof V1_PackageableType) {
337
- const paramValue = new V1_ParameterValue();
338
- paramValue.name = parameter.name;
339
- const type = parameter.genericType.rawType.fullPath;
340
- const defaultValue = queryInfo.defaultParameterValues?.find(
341
- (val) => val.name === parameter.name,
342
- )?.content;
343
- paramValue.value =
344
- defaultValue !== undefined
345
- ? await this.parseValueSpecification(defaultValue)
346
- : {
347
- _type: V1_deserializeRawValueSpecificationType(type),
348
- value: _defaultPrimitiveTypeValue(type),
349
- };
350
- return paramValue;
351
- }
352
- return undefined;
353
- }),
354
- );
355
- source.parameterValues = parameterValues.filter(isNonNullable);
356
410
  try {
357
411
  source.columns = (
358
412
  await this._getLambdaRelationType(
@@ -366,6 +420,59 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
366
420
  `Can't get query result columns. Make sure the saved query return a relation (i.e. typed TDS). Error: ${error.message}`,
367
421
  );
368
422
  }
423
+ // To handle parameter value with function calls we
424
+ // 1. Separate the parameters with function calls from regular parameters
425
+ // 2. Add let statements for function parameter values and store them in the source's letParameterValueSpec
426
+ // 3. Prepend the let statements to the lambda body when we execute the query
427
+ const letFuncs: V1_ValueSpecification[] = [];
428
+ const parameterValues = (
429
+ await Promise.all(
430
+ source.lambda.parameters.map(async (parameter) => {
431
+ if (
432
+ parameter.genericType?.rawType instanceof V1_PackageableType
433
+ ) {
434
+ const type = parameter.genericType.rawType.fullPath;
435
+ const defaultValueString =
436
+ queryInfo.defaultParameterValues?.find(
437
+ (val) => val.name === parameter.name,
438
+ )?.content;
439
+ const defaultValueSpec =
440
+ defaultValueString !== undefined
441
+ ? await this.parseValueSpecification(defaultValueString)
442
+ : {
443
+ _type: V1_deserializeRawValueSpecificationType(type),
444
+ value: _defaultPrimitiveTypeValue(type),
445
+ };
446
+ if (defaultValueSpec instanceof V1_AppliedFunction) {
447
+ const letFunc = guaranteeType(
448
+ this.deserializeValueSpecification(
449
+ await this._engineServerClient.grammarToJSON_lambda(
450
+ `${LET_TOKEN} ${parameter.name} ${DataCubeQueryFilterOperator.EQUAL} ${defaultValueString}`,
451
+ '',
452
+ undefined,
453
+ undefined,
454
+ false,
455
+ ),
456
+ ),
457
+ V1_Lambda,
458
+ );
459
+ letFuncs.push(...letFunc.body);
460
+ } else {
461
+ const paramValue = new V1_ParameterValue();
462
+ paramValue.name = parameter.name;
463
+ paramValue.value = defaultValueSpec;
464
+ return paramValue;
465
+ }
466
+ }
467
+ return undefined;
468
+ }),
469
+ )
470
+ ).filter(isNonNullable);
471
+ source.letParameterValueSpec = letFuncs;
472
+ source.parameterValues = parameterValues;
473
+ source.lambda.parameters = source.lambda.parameters.filter((param) =>
474
+ parameterValues.find((p) => p.name === param.name),
475
+ );
369
476
  return source;
370
477
  }
371
478
  default:
@@ -434,6 +541,13 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
434
541
  model: source.model,
435
542
  })
436
543
  ).completions as CompletionItem[];
544
+ } else if (source instanceof UserDefinedFunctionDataCubeSource) {
545
+ return (
546
+ await this._engineServerClient.completeCode({
547
+ codeBlock,
548
+ model: source.model,
549
+ })
550
+ ).completions as CompletionItem[];
437
551
  } else if (source instanceof LegendQueryDataCubeSource) {
438
552
  return (
439
553
  await this._engineServerClient.completeCode({
@@ -524,8 +638,11 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
524
638
  try {
525
639
  if (source instanceof AdhocQueryDataCubeSource) {
526
640
  result = await this._runQuery(query, source.model, undefined, options);
641
+ } else if (source instanceof UserDefinedFunctionDataCubeSource) {
642
+ result = await this._runQuery(query, source.model, undefined, options);
527
643
  } else if (source instanceof LegendQueryDataCubeSource) {
528
644
  query.parameters = source.lambda.parameters;
645
+ query.body = [...source.letParameterValueSpec, ...query.body];
529
646
  result = await this._runQuery(
530
647
  query,
531
648
  source.model,
@@ -598,19 +715,14 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
598
715
  TDSExecutionResult,
599
716
  `Can't process execution result: expected tabular data set format`,
600
717
  );
601
- const endTime = performance.now();
602
- const queryCode = await queryCodePromise;
603
- const sql = guaranteeNonNullable(
604
- result.activities?.[0] instanceof RelationalExecutionActivities
605
- ? result.activities[0].sql
606
- : undefined,
607
- `Can't process execution result: failed to extract generated SQL`,
608
- );
609
718
  return {
610
719
  result: result,
611
- executedQuery: queryCode,
612
- executedSQL: sql,
613
- executionTime: endTime - startTime,
720
+ executedQuery: await queryCodePromise,
721
+ executedSQL:
722
+ result.activities?.at(-1) instanceof RelationalExecutionActivities
723
+ ? (result.activities.at(-1) as RelationalExecutionActivities).sql
724
+ : undefined,
725
+ executionTime: performance.now() - startTime,
614
726
  };
615
727
  } catch (error) {
616
728
  assertErrorThrown(error);
@@ -631,6 +743,11 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
631
743
  DataCubeFunction.FROM,
632
744
  [_elementPtr(source.runtime)].filter(isNonNullable),
633
745
  );
746
+ } else if (source instanceof UserDefinedFunctionDataCubeSource) {
747
+ return _function(
748
+ DataCubeFunction.FROM,
749
+ [_elementPtr(source.runtime)].filter(isNonNullable),
750
+ );
634
751
  } else if (source instanceof LegendQueryDataCubeSource) {
635
752
  return _function(
636
753
  DataCubeFunction.FROM,
@@ -748,6 +865,8 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
748
865
  ) {
749
866
  if (source instanceof AdhocQueryDataCubeSource) {
750
867
  return this._getLambdaRelationType(query, source.model);
868
+ } else if (source instanceof UserDefinedFunctionDataCubeSource) {
869
+ return this._getLambdaRelationType(query, source.model);
751
870
  } else if (source instanceof LegendQueryDataCubeSource) {
752
871
  return this._getLambdaRelationType(query, source.model);
753
872
  } else if (source instanceof CachedDataCubeSource) {
@@ -853,93 +972,10 @@ export class LegendDataCubeDataCubeEngine extends DataCubeEngine {
853
972
  }
854
973
  }
855
974
 
856
- async ingestLocalFileData(
857
- data: string,
858
- format: string,
859
- ): Promise<DataCubeSource | undefined> {
860
- const {
861
- schema: schemaName,
862
- table: tableName,
863
- tableSpec,
864
- } = await this._duckDBEngine.ingestLocalFileData(data, format);
865
-
866
- const { model, database, schema, table, runtime } =
867
- this._synthesizeMinimalModelContext({
868
- schemaName,
869
- tableName,
870
- tableColumns: tableSpec.map((col) => {
871
- const column = new V1_Column();
872
- column.name = col[0] as string;
873
- // TODO: confirm this is in accordance to engine
874
- // check if we have a duckdb enum mapping
875
- // See https://duckdb.org/docs/sql/data_types/overview.html
876
- switch (col[1] as string) {
877
- case 'BIT': {
878
- column.type = new V1_Bit();
879
- break;
880
- }
881
- case 'BOOLEAN': {
882
- // TODO: understand why boolean is not present in relationalDataType
883
- column.type = new V1_VarChar();
884
- break;
885
- }
886
- case 'DATE': {
887
- column.type = new V1_Date();
888
- break;
889
- }
890
- case 'DECIMAL': {
891
- column.type = new V1_Decimal();
892
- break;
893
- }
894
- case 'DOUBLE': {
895
- column.type = new V1_Double();
896
- break;
897
- }
898
- case 'FLOAT': {
899
- column.type = new V1_Float();
900
- break;
901
- }
902
- case 'INTEGER': {
903
- column.type = new V1_Integer();
904
- break;
905
- }
906
- case 'TININT': {
907
- column.type = new V1_TinyInt();
908
- break;
909
- }
910
- case 'SMALLINT': {
911
- column.type = new V1_SmallInt();
912
- break;
913
- }
914
- case 'BIGINT': {
915
- column.type = new V1_BigInt();
916
- break;
917
- }
918
- case 'TIMESTAMP': {
919
- column.type = new V1_Timestamp();
920
- break;
921
- }
922
- case 'VARCHAR': {
923
- column.type = new V1_VarChar();
924
- break;
925
- }
926
- default: {
927
- throw new UnsupportedOperationError(
928
- `Can't ingest local file data: failed to find matching relational data type for DuckDB type '${col[1]}' when synthesizing table definition`,
929
- );
930
- }
931
- }
932
- return column;
933
- }),
934
- });
935
-
936
- const source = new LocalFileDataCubeSource();
937
- source.model = model;
938
- source.runtime = runtime.path;
939
- source.db = database.path;
940
- source.schema = schema.name;
941
- source.table = table.name;
942
- return source;
975
+ async ingestLocalFileData(data: string, format: string, refId?: string) {
976
+ const { dbReference, columnNames } =
977
+ await this._duckDBEngine.ingestLocalFileData(data, format, refId);
978
+ return { dbReference, columnNames };
943
979
  }
944
980
 
945
981
  private _synthesizeMinimalModelContext(data: {