@malloydata/malloy 0.0.334 → 0.0.336

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 (91) hide show
  1. package/CONTEXT.md +4 -3
  2. package/MALLOY_API.md +129 -0
  3. package/dist/annotation.d.ts +0 -2
  4. package/dist/annotation.js +29 -23
  5. package/dist/api/asynchronous.d.ts +1 -1
  6. package/dist/api/foundation/cache.d.ts +32 -0
  7. package/dist/api/foundation/cache.js +92 -0
  8. package/dist/api/foundation/compile.d.ts +175 -0
  9. package/dist/api/foundation/compile.js +391 -0
  10. package/dist/api/foundation/core.d.ts +493 -0
  11. package/dist/api/foundation/core.js +1247 -0
  12. package/dist/api/foundation/document.d.ts +167 -0
  13. package/dist/api/foundation/document.js +206 -0
  14. package/dist/api/foundation/index.d.ts +10 -0
  15. package/dist/api/foundation/index.js +77 -0
  16. package/dist/api/foundation/readers.d.ts +53 -0
  17. package/dist/api/foundation/readers.js +134 -0
  18. package/dist/api/foundation/result.d.ts +185 -0
  19. package/dist/api/foundation/result.js +704 -0
  20. package/dist/api/foundation/runtime.d.ts +361 -0
  21. package/dist/api/foundation/runtime.js +733 -0
  22. package/dist/api/foundation/types.d.ts +54 -0
  23. package/dist/api/foundation/types.js +7 -0
  24. package/dist/api/foundation/writers.d.ts +42 -0
  25. package/dist/api/foundation/writers.js +230 -0
  26. package/dist/api/util.d.ts +1 -1
  27. package/dist/connection/base_connection.d.ts +5 -0
  28. package/dist/connection/types.d.ts +5 -0
  29. package/dist/dialect/duckdb/duckdb.js +2 -1
  30. package/dist/dialect/snowflake/snowflake.js +7 -1
  31. package/dist/dialect/trino/trino.js +7 -2
  32. package/dist/index.d.ts +6 -3
  33. package/dist/index.js +30 -26
  34. package/dist/lang/ast/error-factory.js +3 -5
  35. package/dist/lang/ast/expressions/expr-count-distinct.js +7 -1
  36. package/dist/lang/ast/expressions/expr-granular-time.js +1 -1
  37. package/dist/lang/ast/expressions/expr-max.js +7 -1
  38. package/dist/lang/ast/expressions/expr-min.js +7 -1
  39. package/dist/lang/ast/expressions/for-range.js +1 -1
  40. package/dist/lang/ast/source-elements/query-source.js +2 -7
  41. package/dist/lang/ast/source-elements/refined-source.js +11 -1
  42. package/dist/lang/ast/source-elements/sql-source.d.ts +1 -1
  43. package/dist/lang/ast/source-elements/sql-source.js +18 -3
  44. package/dist/lang/ast/sql-elements/sql-string.d.ts +2 -2
  45. package/dist/lang/ast/sql-elements/sql-string.js +18 -1
  46. package/dist/lang/ast/statements/define-source.js +7 -2
  47. package/dist/lang/ast/statements/import-statement.js +53 -21
  48. package/dist/lang/ast/types/document-compile-result.d.ts +1 -0
  49. package/dist/lang/ast/types/expression-def.js +1 -1
  50. package/dist/lang/ast/types/malloy-element.d.ts +3 -1
  51. package/dist/lang/ast/types/malloy-element.js +23 -7
  52. package/dist/lang/malloy-to-ast.d.ts +1 -1
  53. package/dist/lang/malloy-to-ast.js +1 -1
  54. package/dist/lang/parse-malloy.d.ts +3 -2
  55. package/dist/lang/parse-malloy.js +14 -25
  56. package/dist/lang/test/test-translator.d.ts +9 -2
  57. package/dist/lang/test/test-translator.js +103 -77
  58. package/dist/lang/translate-response.d.ts +1 -0
  59. package/dist/model/constant_expression_compiler.js +6 -7
  60. package/dist/model/index.d.ts +3 -1
  61. package/dist/model/index.js +15 -9
  62. package/dist/model/malloy_types.d.ts +89 -15
  63. package/dist/model/malloy_types.js +12 -0
  64. package/dist/model/persist_utils.d.ts +47 -0
  65. package/dist/model/persist_utils.js +257 -0
  66. package/dist/model/query_model_impl.d.ts +2 -4
  67. package/dist/model/query_model_impl.js +5 -13
  68. package/dist/model/query_node.d.ts +1 -2
  69. package/dist/model/query_node.js +3 -13
  70. package/dist/model/query_query.d.ts +17 -1
  71. package/dist/model/query_query.js +81 -36
  72. package/dist/model/source_def_utils.d.ts +50 -0
  73. package/dist/model/source_def_utils.js +154 -0
  74. package/dist/model/sql_block.d.ts +5 -1
  75. package/dist/model/sql_block.js +29 -4
  76. package/dist/model/sql_compiled.d.ts +29 -0
  77. package/dist/model/sql_compiled.js +102 -0
  78. package/dist/model/stage_writer.d.ts +1 -3
  79. package/dist/model/stage_writer.js +7 -25
  80. package/dist/model/utils.d.ts +20 -1
  81. package/dist/model/utils.js +40 -0
  82. package/dist/run_sql_options.d.ts +0 -1
  83. package/dist/taggable.d.ts +10 -0
  84. package/dist/taggable.js +7 -0
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/package.json +6 -4
  88. package/dist/malloy.d.ts +0 -1365
  89. package/dist/malloy.js +0 -3421
  90. package/dist/model/materialization/utils.d.ts +0 -3
  91. package/dist/model/materialization/utils.js +0 -41
@@ -0,0 +1,391 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Malloy = exports.MalloyError = void 0;
8
+ const lang_1 = require("../../lang");
9
+ const model_1 = require("../../model");
10
+ const sql_block_1 = require("../../model/sql_block");
11
+ const version_1 = require("../../version");
12
+ const readers_1 = require("./readers");
13
+ const document_1 = require("./document");
14
+ const core_1 = require("./core");
15
+ const result_1 = require("./result");
16
+ const MALLOY_INTERNAL_URL = 'internal://internal.malloy';
17
+ // =============================================================================
18
+ // Helper Functions
19
+ // =============================================================================
20
+ function flatDeps(tree) {
21
+ return [...Object.keys(tree), ...Object.values(tree).map(flatDeps).flat()];
22
+ }
23
+ // =============================================================================
24
+ // MalloyError
25
+ // =============================================================================
26
+ /**
27
+ * A Malloy error, which may contain log messages produced during compilation.
28
+ */
29
+ class MalloyError extends Error {
30
+ /**
31
+ * An array of log messages produced during compilation.
32
+ */
33
+ constructor(message, problems = []) {
34
+ super(message);
35
+ this.problems = problems;
36
+ }
37
+ }
38
+ exports.MalloyError = MalloyError;
39
+ // =============================================================================
40
+ // Malloy Static Class
41
+ // =============================================================================
42
+ class Malloy {
43
+ static get version() {
44
+ return version_1.MALLOY_VERSION;
45
+ }
46
+ static _parse(source, url, eventStream, options, invalidationKey) {
47
+ if (url === undefined) {
48
+ url = new URL(MALLOY_INTERNAL_URL);
49
+ }
50
+ let importBaseURL = url;
51
+ if (options === null || options === void 0 ? void 0 : options.importBaseURL) {
52
+ importBaseURL = options === null || options === void 0 ? void 0 : options.importBaseURL;
53
+ }
54
+ const translator = new lang_1.MalloyTranslator(url.toString(), importBaseURL.toString(), {
55
+ urls: { [url.toString()]: source },
56
+ }, eventStream);
57
+ if (options === null || options === void 0 ? void 0 : options.testEnvironment) {
58
+ translator.allDialectsEnabled = true;
59
+ }
60
+ return new document_1.Parse(translator, invalidationKey);
61
+ }
62
+ static parse({ url, urlReader, source, eventStream, options, }) {
63
+ if (source !== undefined) {
64
+ return Malloy._parse(source, url, eventStream, options);
65
+ }
66
+ else {
67
+ if (urlReader === undefined) {
68
+ throw new Error('Internal Error: urlReader is required.');
69
+ }
70
+ if (url === undefined) {
71
+ throw new Error('Internal Error: url is required if source not present.');
72
+ }
73
+ return (0, readers_1.readURL)(urlReader, url).then(({ contents, invalidationKey }) => {
74
+ return Malloy._parse(contents, url, eventStream, options, invalidationKey);
75
+ });
76
+ }
77
+ }
78
+ /**
79
+ * Compile a parsed Malloy document.
80
+ *
81
+ * @param urlReader Object capable of reading contents of a URL.
82
+ * @param connections Mapping of connection names to objects capable of reading Malloy schemas.
83
+ * @param parse The parsed Malloy document.
84
+ * @param model A compiled model to build upon (optional).
85
+ * @return A (promise of a) compiled `Model`.
86
+ */
87
+ static async compile({ url, source, parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, importBaseURL, cacheManager, }) {
88
+ var _a, _b, _c, _d, _e;
89
+ let refreshTimestamp;
90
+ if (refreshSchemaCache) {
91
+ refreshTimestamp =
92
+ typeof refreshSchemaCache === 'number'
93
+ ? refreshSchemaCache
94
+ : Date.now();
95
+ }
96
+ if (url === undefined && source === undefined && parse === undefined) {
97
+ throw new Error('Internal Error: url, source, or parse required.');
98
+ }
99
+ if (url === undefined) {
100
+ if (parse !== undefined) {
101
+ url = new URL(parse._translator.sourceURL);
102
+ }
103
+ else {
104
+ url = new URL(MALLOY_INTERNAL_URL);
105
+ }
106
+ }
107
+ const invalidationKeys = {};
108
+ // Before anything, if we have a URL and not source code, we check if that URL
109
+ // is cached.
110
+ if (source === undefined && cacheManager !== undefined) {
111
+ const cached = await cacheManager.getCachedModelDef(urlReader, url.toString());
112
+ if (cached) {
113
+ return new core_1.Model(cached.modelDef, [], // TODO when using a model from cache, should we also store the problems??
114
+ [url.toString(), ...flatDeps(cached.modelDef.dependencies)]);
115
+ }
116
+ }
117
+ importBaseURL !== null && importBaseURL !== void 0 ? importBaseURL : (importBaseURL = url);
118
+ let translator;
119
+ // It's not cached, so we may need to get the actual source
120
+ const _url = url.toString();
121
+ if (parse !== undefined) {
122
+ translator = parse._translator;
123
+ const invalidationKey = (_a = parse._invalidationKey) !== null && _a !== void 0 ? _a : (await (0, readers_1.getInvalidationKey)(urlReader, url));
124
+ invalidationKeys[_url] = invalidationKey;
125
+ }
126
+ else {
127
+ if (source === undefined) {
128
+ const { contents, invalidationKey } = await (0, readers_1.readURL)(urlReader, url);
129
+ invalidationKeys[_url] = invalidationKey;
130
+ source = contents;
131
+ }
132
+ else {
133
+ const invalidationKey = await (0, readers_1.getInvalidationKey)(urlReader, url);
134
+ invalidationKeys[_url] = invalidationKey;
135
+ }
136
+ translator = new lang_1.MalloyTranslator(_url, importBaseURL.toString(), {
137
+ urls: { [_url]: source },
138
+ }, eventStream);
139
+ }
140
+ for (;;) {
141
+ const result = translator.translate(model === null || model === void 0 ? void 0 : model._modelDef);
142
+ if (result.final) {
143
+ if (result.modelDef) {
144
+ await (cacheManager === null || cacheManager === void 0 ? void 0 : cacheManager.setCachedModelDef(url.toString(), {
145
+ modelDef: result.modelDef,
146
+ invalidationKeys,
147
+ }));
148
+ for (const m of translator.newlyTranslatedDependencies()) {
149
+ await (cacheManager === null || cacheManager === void 0 ? void 0 : cacheManager.setCachedModelDef(m.url, {
150
+ modelDef: m.modelDef,
151
+ invalidationKeys,
152
+ }));
153
+ }
154
+ // If the model wasn't modified, create new Model with result's modelDef
155
+ // (which has the queryList) but share the cached QueryModel from input model
156
+ const existingQueryModel = !result.modelWasModified && model
157
+ ? model.getExistingQueryModel()
158
+ : undefined;
159
+ return new core_1.Model(result.modelDef, result.problems || [], [...((_b = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _b !== void 0 ? _b : []), ...((_c = result.fromSources) !== null && _c !== void 0 ? _c : [])], existingQueryModel);
160
+ }
161
+ else if (noThrowOnError) {
162
+ const emptyModel = (0, model_1.mkModelDef)('modelDidNotCompile');
163
+ const modelFromCompile = (model === null || model === void 0 ? void 0 : model._modelDef) || emptyModel;
164
+ return new core_1.Model(modelFromCompile, result.problems || [], [
165
+ ...((_d = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _d !== void 0 ? _d : []),
166
+ ...((_e = result.fromSources) !== null && _e !== void 0 ? _e : []),
167
+ ]);
168
+ }
169
+ else {
170
+ const errors = result.problems || [];
171
+ const errText = translator.prettyErrors();
172
+ throw new MalloyError(`Error(s) compiling model:\n${errText}`, errors);
173
+ }
174
+ }
175
+ else {
176
+ // Parse incomplete because some external information is required,
177
+ // there might be more than one of these in a single reply ...
178
+ if (result.urls) {
179
+ for (const neededUrl of result.urls) {
180
+ try {
181
+ if ((0, readers_1.isInternalURL)(neededUrl)) {
182
+ throw new Error('In order to use relative imports, you must compile a file via a URL.');
183
+ }
184
+ // First, check the cache
185
+ if (cacheManager !== undefined) {
186
+ const cached = await cacheManager.getCachedModelDef(urlReader, neededUrl);
187
+ if (cached) {
188
+ for (const dependency in cached.invalidationKeys) {
189
+ invalidationKeys[dependency] =
190
+ cached.invalidationKeys[dependency];
191
+ }
192
+ translator.update({
193
+ translations: { [neededUrl]: cached.modelDef },
194
+ });
195
+ continue;
196
+ }
197
+ }
198
+ // Otherwise, fetch the URL contents
199
+ const { contents, invalidationKey } = await (0, readers_1.readURL)(urlReader, new URL(neededUrl));
200
+ const urls = { [neededUrl]: contents };
201
+ invalidationKeys[neededUrl] = invalidationKey;
202
+ translator.update({ urls });
203
+ }
204
+ catch (error) {
205
+ translator.update({
206
+ errors: { urls: { [neededUrl]: error.message } },
207
+ });
208
+ }
209
+ }
210
+ }
211
+ const { modelAnnotation } = translator.modelAnnotation(model === null || model === void 0 ? void 0 : model._modelDef);
212
+ if (result.tables) {
213
+ // collect tables by connection name since there may be multiple connections
214
+ const tablesByConnection = new Map();
215
+ for (const tableKey in result.tables) {
216
+ const { connectionName, tablePath } = result.tables[tableKey];
217
+ const connectionToTablesMap = tablesByConnection.get(connectionName);
218
+ if (connectionToTablesMap === undefined) {
219
+ tablesByConnection.set(connectionName, { [tableKey]: tablePath });
220
+ }
221
+ else {
222
+ connectionToTablesMap[tableKey] = tablePath;
223
+ }
224
+ }
225
+ // iterate over connections, fetching schema for all missing tables
226
+ for (const [connectionName, tablePathByKey] of tablesByConnection) {
227
+ try {
228
+ const connection = await connections.lookupConnection(connectionName);
229
+ // TODO detect if the union of `Object.keys(tables)` and `Object.keys(errors)` is not the same
230
+ // as `Object.keys(tablePathByKey)`, i.e. that all tables are accounted for. Otherwise
231
+ // the translator runs into an infinite loop fetching tables.
232
+ const { schemas: tables, errors } = await Malloy.safelyFetchTableSchema(connection, tablePathByKey, {
233
+ refreshTimestamp,
234
+ modelAnnotation,
235
+ });
236
+ translator.update({ tables, errors: { tables: errors } });
237
+ }
238
+ catch (error) {
239
+ // There was an exception getting the connection, associate that error
240
+ // with all its tables
241
+ const tables = {};
242
+ const errors = {};
243
+ for (const tableKey in tablePathByKey) {
244
+ errors[tableKey] = error.toString();
245
+ }
246
+ translator.update({ tables, errors: { tables: errors } });
247
+ }
248
+ }
249
+ }
250
+ if (result.compileSQL) {
251
+ // Unlike other requests, these do not come in batches
252
+ const toCompile = result.compileSQL;
253
+ const connectionName = toCompile.connection;
254
+ const key = (0, sql_block_1.sqlKey)(toCompile.connection, toCompile.selectStr);
255
+ try {
256
+ const conn = await connections.lookupConnection(connectionName);
257
+ const resolved = await conn.fetchSchemaForSQLStruct(toCompile, {
258
+ refreshTimestamp,
259
+ modelAnnotation,
260
+ });
261
+ if (resolved.error) {
262
+ translator.update({
263
+ errors: {
264
+ compileSQL: {
265
+ [key]: resolved.error,
266
+ },
267
+ },
268
+ });
269
+ }
270
+ if (resolved.structDef) {
271
+ translator.update({
272
+ compileSQL: { [key]: resolved.structDef },
273
+ });
274
+ }
275
+ }
276
+ catch (error) {
277
+ const errors = {};
278
+ errors[key] = error.toString();
279
+ translator.update({ errors: { compileSQL: errors } });
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ /**
286
+ * A dialect must provide a response for every table, or the translator loop
287
+ * will never exit. Because there was a time when this happened, we throw
288
+ * instead of looping forever, but the fix is to correct the dialect.
289
+ */
290
+ static async safelyFetchTableSchema(connection, toFetch, opts) {
291
+ const ret = await connection.fetchSchemaForTables(toFetch, opts);
292
+ for (const req of Object.keys(toFetch)) {
293
+ if (ret.schemas[req] === undefined && ret.errors[req] === undefined) {
294
+ throw new Error(`Schema fetch error for ${connection.name}, no response for ${req} from ${connection.dialectName}`);
295
+ }
296
+ }
297
+ return ret;
298
+ }
299
+ static async run({ connections, preparedResult, sqlStruct, connection, options, }) {
300
+ if (!connection) {
301
+ if (!connections) {
302
+ throw new Error('Internal Error: Connection or LookupConnection<Connection> must be provided.');
303
+ }
304
+ const connectionName = (sqlStruct === null || sqlStruct === void 0 ? void 0 : sqlStruct.connection) || (preparedResult === null || preparedResult === void 0 ? void 0 : preparedResult.connectionName);
305
+ connection = await connections.lookupConnection(connectionName);
306
+ }
307
+ if (sqlStruct) {
308
+ const data = await connection.runSQL(sqlStruct.selectStr);
309
+ return new result_1.Result({
310
+ structs: [sqlStruct],
311
+ sql: sqlStruct.selectStr,
312
+ result: data.rows,
313
+ totalRows: data.totalRows,
314
+ runStats: data.runStats,
315
+ lastStageName: sqlStruct.name,
316
+ // TODO feature-sql-block There is no malloy code...
317
+ malloy: '',
318
+ connectionName: sqlStruct.connection,
319
+ // TODO feature-sql-block There is no source explore...
320
+ sourceExplore: '',
321
+ sourceFilters: [],
322
+ profilingUrl: data.profilingUrl,
323
+ }, (0, model_1.mkModelDef)('empty_model'));
324
+ }
325
+ else if (preparedResult) {
326
+ const result = await connection.runSQL(preparedResult.sql, options);
327
+ return new result_1.Result({
328
+ ...preparedResult._rawQuery,
329
+ result: result.rows,
330
+ totalRows: result.totalRows,
331
+ runStats: result.runStats,
332
+ profilingUrl: result.profilingUrl,
333
+ }, preparedResult._modelDef);
334
+ }
335
+ else {
336
+ throw new Error('Internal error: sqlStruct or preparedResult must be provided.');
337
+ }
338
+ }
339
+ static async *runStream({ connections, preparedResult, sqlStruct, connection, options, }) {
340
+ if (sqlStruct === undefined && preparedResult === undefined) {
341
+ throw new Error('Internal error: sqlBlock or preparedResult must be provided.');
342
+ }
343
+ const connectionName = (sqlStruct === null || sqlStruct === void 0 ? void 0 : sqlStruct.connection) || (preparedResult === null || preparedResult === void 0 ? void 0 : preparedResult.connectionName);
344
+ if (connection === undefined) {
345
+ if (connections === undefined) {
346
+ throw new Error('Internal Error: Connection or LookupConnection<Connection> must be provided.');
347
+ }
348
+ connection = await connections.lookupConnection(connectionName);
349
+ }
350
+ // TODO is there a better way to handle this case? Just require StreamingConnections?
351
+ if (!connection.canStream()) {
352
+ throw new Error(`Connection '${connectionName}' cannot stream results.`);
353
+ }
354
+ let sql;
355
+ let resultExplore;
356
+ if (sqlStruct) {
357
+ resultExplore = new core_1.Explore(sqlStruct);
358
+ sql = sqlStruct.selectStr;
359
+ }
360
+ else if (preparedResult !== undefined) {
361
+ resultExplore = preparedResult.resultExplore;
362
+ sql = preparedResult.sql;
363
+ }
364
+ else {
365
+ throw new Error('Internal error: sqlStruct or preparedResult must be provided.');
366
+ }
367
+ let index = 0;
368
+ for await (const row of connection.runSQLStream(sql, options)) {
369
+ yield new result_1.DataRecord(row, index, resultExplore, undefined, undefined);
370
+ index += 1;
371
+ }
372
+ }
373
+ static async estimateQueryCost({ connections, preparedResult, sqlStruct, }) {
374
+ if (!connections) {
375
+ throw new Error('Internal Error: Connection or LookupConnection<Connection> must be provided.');
376
+ }
377
+ const connectionName = (sqlStruct === null || sqlStruct === void 0 ? void 0 : sqlStruct.connection) || (preparedResult === null || preparedResult === void 0 ? void 0 : preparedResult.connectionName);
378
+ const connection = await connections.lookupConnection(connectionName);
379
+ if (sqlStruct) {
380
+ return await connection.estimateQueryCost(sqlStruct.selectStr);
381
+ }
382
+ else if (preparedResult) {
383
+ return await connection.estimateQueryCost(preparedResult.sql);
384
+ }
385
+ else {
386
+ throw new Error('Internal error: sqlStruct or preparedResult must be provided.');
387
+ }
388
+ }
389
+ }
390
+ exports.Malloy = Malloy;
391
+ //# sourceMappingURL=compile.js.map