@databricks/appkit 0.39.0 → 0.40.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/dist/appkit/package.js +1 -1
- package/dist/cli/commands/generate-types.js +114 -5
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/spawn-lock.js +116 -0
- package/dist/cli/commands/spawn-lock.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +2 -2
- package/dist/type-generator/index.js +115 -12
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/preflight.js +24 -0
- package/dist/type-generator/preflight.js.map +1 -0
- package/dist/type-generator/query-registry.js +322 -90
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/type-generator/types.js.map +1 -1
- package/dist/type-generator/vite-plugin.d.ts.map +1 -1
- package/dist/type-generator/vite-plugin.js +141 -7
- package/dist/type-generator/vite-plugin.js.map +1 -1
- package/dist/type-generator/warehouse-status.js +116 -0
- package/dist/type-generator/warehouse-status.js.map +1 -0
- package/docs/development/type-generation.md +13 -0
- package/docs/plugins/analytics.md +156 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "../logging/logger.js";
|
|
2
2
|
import { CACHE_VERSION, hashSQL, loadCache, saveCache } from "./cache.js";
|
|
3
|
+
import { decidePreflight } from "./preflight.js";
|
|
3
4
|
import { Spinner } from "./spinner.js";
|
|
4
5
|
import { sqlTypeToHelper, sqlTypeToMarker } from "./types.js";
|
|
6
|
+
import { getWarehouseState, startWarehouse, waitUntilRunning } from "./warehouse-status.js";
|
|
5
7
|
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
6
8
|
import pc from "picocolors";
|
|
7
9
|
import fs from "node:fs/promises";
|
|
@@ -10,6 +12,12 @@ import path from "node:path";
|
|
|
10
12
|
//#region src/type-generator/query-registry.ts
|
|
11
13
|
const logger = createLogger("type-generator:query-registry");
|
|
12
14
|
/**
|
|
15
|
+
* Upper bound on how long a `blocking`-mode preflight will wait for a starting
|
|
16
|
+
* warehouse to reach RUNNING before giving up (~5 min). Generous enough to ride
|
|
17
|
+
* out a cold start without hanging an interactive CLI invocation indefinitely.
|
|
18
|
+
*/
|
|
19
|
+
const PREFLIGHT_WAIT_MAX_MS = 3e5;
|
|
20
|
+
/**
|
|
13
21
|
* Regex breakdown:
|
|
14
22
|
* '(?:[^']|'')*' — matches a SQL string literal, including escaped '' pairs
|
|
15
23
|
* | — alternation: whichever branch matches first at a position wins
|
|
@@ -58,6 +66,83 @@ function parseError(raw) {
|
|
|
58
66
|
} catch {}
|
|
59
67
|
return { message: raw };
|
|
60
68
|
}
|
|
69
|
+
function isObject(value) {
|
|
70
|
+
return typeof value === "object" && value !== null;
|
|
71
|
+
}
|
|
72
|
+
function getErrorMessage(error) {
|
|
73
|
+
if (error instanceof Error) return error.message;
|
|
74
|
+
if (isObject(error) && typeof error.message === "string") return error.message;
|
|
75
|
+
return String(error);
|
|
76
|
+
}
|
|
77
|
+
function getErrorDiagnostic(error) {
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
const messages = [];
|
|
80
|
+
const stack = [error];
|
|
81
|
+
while (stack.length > 0) {
|
|
82
|
+
const current = stack.pop();
|
|
83
|
+
if (current === void 0 || seen.has(current)) continue;
|
|
84
|
+
seen.add(current);
|
|
85
|
+
const message = getErrorMessage(current);
|
|
86
|
+
if (message && message !== "[object Object]" && !messages.includes(message)) messages.push(message);
|
|
87
|
+
const code = getErrorCode(current);
|
|
88
|
+
if (code && !messages.includes(code)) messages.push(code);
|
|
89
|
+
stack.push(...getErrorChildren(current));
|
|
90
|
+
}
|
|
91
|
+
return messages.length > 0 ? messages.join(": ") : getErrorMessage(error);
|
|
92
|
+
}
|
|
93
|
+
function getErrorCode(error) {
|
|
94
|
+
if (!isObject(error)) return void 0;
|
|
95
|
+
const code = error.code ?? error.errno;
|
|
96
|
+
return typeof code === "string" ? code : void 0;
|
|
97
|
+
}
|
|
98
|
+
function getErrorStatus(error) {
|
|
99
|
+
if (!isObject(error)) return void 0;
|
|
100
|
+
const direct = error.status ?? error.statusCode;
|
|
101
|
+
if (typeof direct === "number") return direct;
|
|
102
|
+
if (isObject(error.response) && typeof error.response.status === "number") return error.response.status;
|
|
103
|
+
}
|
|
104
|
+
function getErrorChildren(error) {
|
|
105
|
+
if (!isObject(error)) return [];
|
|
106
|
+
const children = [];
|
|
107
|
+
if ("cause" in error) children.push(error.cause);
|
|
108
|
+
if (error instanceof AggregateError) children.push(...error.errors);
|
|
109
|
+
return children;
|
|
110
|
+
}
|
|
111
|
+
const CONNECTIVITY_ERROR_CODES = new Set([
|
|
112
|
+
"ECONNREFUSED",
|
|
113
|
+
"ECONNRESET",
|
|
114
|
+
"ENOTFOUND",
|
|
115
|
+
"ETIMEDOUT",
|
|
116
|
+
"EAI_AGAIN",
|
|
117
|
+
"EAI_NODATA",
|
|
118
|
+
"EAI_NONAME",
|
|
119
|
+
"EHOSTUNREACH",
|
|
120
|
+
"ENETUNREACH",
|
|
121
|
+
"CERT_HAS_EXPIRED",
|
|
122
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
123
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
124
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
125
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE"
|
|
126
|
+
]);
|
|
127
|
+
function isConnectivityMessage(message) {
|
|
128
|
+
return /\bconnection (?:refused|reset|timed out)\b/i.test(message) || /\bsocket hang up\b/i.test(message) || /\bnetwork error\b/i.test(message) || /\bcan'?t connect to\b/i.test(message) || /\bcertificate has expired\b/i.test(message) || /\bunable to verify the first certificate\b/i.test(message) || /\bupstream connect error or disconnect\/reset before headers\b/i.test(message);
|
|
129
|
+
}
|
|
130
|
+
function isConnectivityError(error) {
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
const stack = [error];
|
|
133
|
+
while (stack.length > 0) {
|
|
134
|
+
const current = stack.pop();
|
|
135
|
+
if (current === void 0 || seen.has(current)) continue;
|
|
136
|
+
seen.add(current);
|
|
137
|
+
const code = getErrorCode(current);
|
|
138
|
+
if (code && (CONNECTIVITY_ERROR_CODES.has(code) || code.startsWith("UND_ERR_"))) return true;
|
|
139
|
+
const status = getErrorStatus(current);
|
|
140
|
+
if (status === 502 || status === 503 || status === 504) return true;
|
|
141
|
+
if (isConnectivityMessage(getErrorMessage(current))) return true;
|
|
142
|
+
stack.push(...getErrorChildren(current));
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
61
146
|
/**
|
|
62
147
|
* Extract parameters from a SQL query
|
|
63
148
|
* @param sql - the SQL query to extract parameters from
|
|
@@ -120,6 +205,16 @@ function generateUnknownResultQuery(sql, queryName) {
|
|
|
120
205
|
result: unknown;
|
|
121
206
|
}`;
|
|
122
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Degrade gracefully when DESCRIBE can't produce a fresh schema (transient
|
|
210
|
+
* connectivity outage, or a warehouse that's reachable but not ready). Reuse
|
|
211
|
+
* the last-good cached type when the SQL hash is unchanged, otherwise emit
|
|
212
|
+
* `unknown` from SQL alone. Never persists `result: unknown`.
|
|
213
|
+
*/
|
|
214
|
+
function degradedType(cache, queryName, sql, sqlHash) {
|
|
215
|
+
const prior = cache.queries[queryName];
|
|
216
|
+
return prior?.hash === sqlHash && !prior.retry ? prior.type : generateUnknownResultQuery(sql, queryName);
|
|
217
|
+
}
|
|
123
218
|
function extractParameterTypes(sql) {
|
|
124
219
|
const paramTypes = {};
|
|
125
220
|
const matches = sql.matchAll(/--\s*@param\s+(\w+)\s+(STRING|NUMERIC|DECIMAL|BIGINT|TINYINT|SMALLINT|INT|FLOAT|DOUBLE|BOOLEAN|DATE|TIMESTAMP_NTZ|TIMESTAMP|BINARY)\b/gi);
|
|
@@ -167,10 +262,15 @@ function inferParameterTypes(sql, ranges) {
|
|
|
167
262
|
* @param warehouseId - the warehouse id to use for schema analysis
|
|
168
263
|
* @param options - options for the query generation
|
|
169
264
|
* @param options.noCache - if true, skip the cache and regenerate all types
|
|
265
|
+
* @param options.mode - preflight policy: "non-blocking" never probes the
|
|
266
|
+
* warehouse and never describes (emits cached/`unknown` types and returns
|
|
267
|
+
* immediately), "blocking" waits for a starting warehouse and starts (then
|
|
268
|
+
* waits for) a stopped one, treating only a deleted/deleting warehouse as
|
|
269
|
+
* fatal. Defaults to "non-blocking".
|
|
170
270
|
* @returns an array of query schemas
|
|
171
271
|
*/
|
|
172
272
|
async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {}) {
|
|
173
|
-
const { noCache = false, concurrency: rawConcurrency = 10 } = options;
|
|
273
|
+
const { noCache = false, concurrency: rawConcurrency = 10, mode = "non-blocking" } = options;
|
|
174
274
|
const concurrency = typeof rawConcurrency === "number" && Number.isFinite(rawConcurrency) ? Math.max(1, Math.floor(rawConcurrency)) : 10;
|
|
175
275
|
const [allFiles, cache] = await Promise.all([fs.readdir(queryFolder), noCache ? {
|
|
176
276
|
version: CACHE_VERSION,
|
|
@@ -231,105 +331,212 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
|
|
|
231
331
|
}
|
|
232
332
|
}
|
|
233
333
|
const freshResults = [];
|
|
334
|
+
const syntaxErrors = [];
|
|
335
|
+
const fatalErrors = [];
|
|
234
336
|
if (uncachedQueries.length > 0) {
|
|
235
|
-
let
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
337
|
+
let decision = "proceed";
|
|
338
|
+
let fatalMessage = "";
|
|
339
|
+
if (mode === "non-blocking") decision = "degradeAll";
|
|
340
|
+
else try {
|
|
341
|
+
const state = await getWarehouseState(client, warehouseId);
|
|
342
|
+
decision = decidePreflight(state, mode);
|
|
343
|
+
if (decision === "fatal") fatalMessage = `warehouse ${warehouseId} is ${state}`;
|
|
344
|
+
if (decision === "startWaitProceed") {
|
|
345
|
+
await startWarehouse(client, warehouseId);
|
|
346
|
+
const final = await waitUntilRunning(client, warehouseId, {
|
|
347
|
+
maxMs: PREFLIGHT_WAIT_MAX_MS,
|
|
348
|
+
treatStoppedAsTransient: true
|
|
349
|
+
});
|
|
350
|
+
if (final === "RUNNING") decision = "proceed";
|
|
351
|
+
else {
|
|
352
|
+
decision = "fatal";
|
|
353
|
+
fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (decision === "waitThenProceed") {
|
|
357
|
+
const final = await waitUntilRunning(client, warehouseId, { maxMs: PREFLIGHT_WAIT_MAX_MS });
|
|
358
|
+
if (final === "RUNNING") decision = "proceed";
|
|
359
|
+
else {
|
|
360
|
+
decision = "fatal";
|
|
361
|
+
fatalMessage = `warehouse ${warehouseId} did not reach RUNNING (now ${final})`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (err) {
|
|
365
|
+
if (isConnectivityError(err)) decision = "degradeAll";
|
|
366
|
+
else {
|
|
367
|
+
decision = "fatal";
|
|
368
|
+
fatalMessage = `warehouse ${warehouseId}: ${getErrorDiagnostic(err)}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (decision !== "proceed") {
|
|
372
|
+
const kind = decision === "fatal" ? "fatal" : "connectivity";
|
|
373
|
+
for (const { index, queryName, sql, sqlHash } of uncachedQueries) {
|
|
374
|
+
freshResults.push({
|
|
252
375
|
index,
|
|
253
376
|
schema: {
|
|
254
377
|
name: queryName,
|
|
255
|
-
type
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
error: parseError(sqlError)
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
const { type, hasResults } = convertToQueryType(result, sql, queryName);
|
|
266
|
-
return {
|
|
267
|
-
status: "ok",
|
|
268
|
-
index,
|
|
269
|
-
schema: {
|
|
270
|
-
name: queryName,
|
|
271
|
-
type
|
|
272
|
-
},
|
|
273
|
-
cacheEntry: {
|
|
274
|
-
hash: sqlHash,
|
|
275
|
-
type,
|
|
276
|
-
retry: !hasResults
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
const processBatchResults = (settled, batchOffset) => {
|
|
281
|
-
for (let i = 0; i < settled.length; i++) {
|
|
282
|
-
const entry = settled[i];
|
|
283
|
-
const { queryName } = uncachedQueries[batchOffset + i];
|
|
284
|
-
if (entry.status === "fulfilled") {
|
|
285
|
-
const res = entry.value;
|
|
286
|
-
freshResults.push({
|
|
287
|
-
index: res.index,
|
|
288
|
-
schema: res.schema
|
|
378
|
+
type: degradedType(cache, queryName, sql, sqlHash)
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
if (decision === "fatal") {
|
|
382
|
+
fatalErrors.push({
|
|
383
|
+
name: queryName,
|
|
384
|
+
message: fatalMessage
|
|
289
385
|
});
|
|
290
|
-
cache.queries[queryName] = res.cacheEntry;
|
|
291
386
|
logEntries.push({
|
|
292
387
|
queryName,
|
|
293
388
|
status: "MISS",
|
|
294
|
-
|
|
295
|
-
error:
|
|
389
|
+
kind,
|
|
390
|
+
error: { message: fatalMessage }
|
|
296
391
|
});
|
|
297
|
-
} else {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
392
|
+
} else logEntries.push({
|
|
393
|
+
queryName,
|
|
394
|
+
status: "MISS",
|
|
395
|
+
kind
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
let completed = 0;
|
|
400
|
+
const total = uncachedQueries.length;
|
|
401
|
+
spinner.start(`Describing ${total} ${total === 1 ? "query" : "queries"} (0/${total})`);
|
|
402
|
+
const describeOne = async ({ index, queryName, sql, sqlHash, cleanedSql }) => {
|
|
403
|
+
const result = await client.statementExecution.executeStatement({
|
|
404
|
+
statement: `DESCRIBE QUERY ${cleanedSql}`,
|
|
405
|
+
warehouse_id: warehouseId
|
|
406
|
+
});
|
|
407
|
+
completed++;
|
|
408
|
+
spinner.update(`Describing ${total} ${total === 1 ? "query" : "queries"} (${completed}/${total})`);
|
|
409
|
+
logger.debug("DESCRIBE result for %s: state=%s, rows=%d", queryName, result.status.state, result.result?.data_array?.length ?? 0);
|
|
410
|
+
if (result.status.state === "FAILED") {
|
|
411
|
+
const sqlError = result.status.error?.message || "Query execution failed";
|
|
412
|
+
return {
|
|
413
|
+
status: "syntax",
|
|
303
414
|
index,
|
|
304
415
|
schema: {
|
|
305
416
|
name: queryName,
|
|
306
|
-
type
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
417
|
+
type: generateUnknownResultQuery(sql, queryName)
|
|
418
|
+
},
|
|
419
|
+
error: parseError(sqlError)
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (result.status.state !== "SUCCEEDED") return {
|
|
423
|
+
status: "unavailable",
|
|
424
|
+
index,
|
|
425
|
+
schema: {
|
|
426
|
+
name: queryName,
|
|
427
|
+
type: degradedType(cache, queryName, sql, sqlHash)
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const { type, hasResults } = convertToQueryType(result, sql, queryName);
|
|
431
|
+
if (!hasResults) return {
|
|
432
|
+
status: "empty",
|
|
433
|
+
index,
|
|
434
|
+
schema: {
|
|
435
|
+
name: queryName,
|
|
436
|
+
type
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
return {
|
|
440
|
+
status: "ok",
|
|
441
|
+
index,
|
|
442
|
+
schema: {
|
|
443
|
+
name: queryName,
|
|
444
|
+
type
|
|
445
|
+
},
|
|
446
|
+
cacheEntry: {
|
|
310
447
|
hash: sqlHash,
|
|
311
448
|
type,
|
|
312
|
-
retry:
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
449
|
+
retry: false
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
const processBatchResults = (settled, batchOffset) => {
|
|
454
|
+
for (let i = 0; i < settled.length; i++) {
|
|
455
|
+
const entry = settled[i];
|
|
456
|
+
const { queryName } = uncachedQueries[batchOffset + i];
|
|
457
|
+
if (entry.status === "fulfilled") {
|
|
458
|
+
const res = entry.value;
|
|
459
|
+
freshResults.push({
|
|
460
|
+
index: res.index,
|
|
461
|
+
schema: res.schema
|
|
462
|
+
});
|
|
463
|
+
if (res.status === "ok") {
|
|
464
|
+
cache.queries[queryName] = res.cacheEntry;
|
|
465
|
+
logEntries.push({
|
|
466
|
+
queryName,
|
|
467
|
+
status: "MISS"
|
|
468
|
+
});
|
|
469
|
+
} else if (res.status === "syntax") {
|
|
470
|
+
syntaxErrors.push({
|
|
471
|
+
name: queryName,
|
|
472
|
+
message: res.error.message
|
|
473
|
+
});
|
|
474
|
+
logEntries.push({
|
|
475
|
+
queryName,
|
|
476
|
+
status: "MISS",
|
|
477
|
+
kind: "syntax",
|
|
478
|
+
error: res.error
|
|
479
|
+
});
|
|
480
|
+
} else if (res.status === "empty") logEntries.push({
|
|
481
|
+
queryName,
|
|
482
|
+
status: "MISS",
|
|
483
|
+
kind: "empty"
|
|
484
|
+
});
|
|
485
|
+
else logEntries.push({
|
|
486
|
+
queryName,
|
|
487
|
+
status: "MISS",
|
|
488
|
+
kind: "connectivity"
|
|
489
|
+
});
|
|
490
|
+
} else {
|
|
491
|
+
completed++;
|
|
492
|
+
spinner.update(`Describing ${total} ${total === 1 ? "query" : "queries"} (${completed}/${total})`);
|
|
493
|
+
const { sql, sqlHash, index } = uncachedQueries[batchOffset + i];
|
|
494
|
+
const reason = getErrorDiagnostic(entry.reason);
|
|
495
|
+
const error = parseError(reason);
|
|
496
|
+
const priorEntry = cache.queries[queryName];
|
|
497
|
+
const canReusePrior = priorEntry?.hash === sqlHash && !priorEntry.retry;
|
|
498
|
+
const type = degradedType(cache, queryName, sql, sqlHash);
|
|
499
|
+
freshResults.push({
|
|
500
|
+
index,
|
|
501
|
+
schema: {
|
|
502
|
+
name: queryName,
|
|
503
|
+
type
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
if (!isConnectivityError(entry.reason)) {
|
|
507
|
+
fatalErrors.push({
|
|
508
|
+
name: queryName,
|
|
509
|
+
message: error.message
|
|
510
|
+
});
|
|
511
|
+
logEntries.push({
|
|
512
|
+
queryName,
|
|
513
|
+
status: "MISS",
|
|
514
|
+
kind: "fatal",
|
|
515
|
+
error
|
|
516
|
+
});
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
logger.warn("DESCRIBE unreachable for %s: %s — %s", queryName, reason, canReusePrior ? "reusing last cached type" : "emitting unknown (no matching cache)");
|
|
520
|
+
logEntries.push({
|
|
521
|
+
queryName,
|
|
522
|
+
status: "MISS",
|
|
523
|
+
kind: "connectivity",
|
|
524
|
+
error
|
|
525
|
+
});
|
|
526
|
+
}
|
|
320
527
|
}
|
|
528
|
+
};
|
|
529
|
+
if (uncachedQueries.length > concurrency) for (let b = 0; b < uncachedQueries.length; b += concurrency) {
|
|
530
|
+
const batch = uncachedQueries.slice(b, b + concurrency);
|
|
531
|
+
processBatchResults(await Promise.allSettled(batch.map(describeOne)), b);
|
|
532
|
+
await saveCache(cache);
|
|
321
533
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
processBatchResults(await Promise.allSettled(uncachedQueries.map(describeOne)), 0);
|
|
330
|
-
await saveCache(cache);
|
|
534
|
+
else {
|
|
535
|
+
processBatchResults(await Promise.allSettled(uncachedQueries.map(describeOne)), 0);
|
|
536
|
+
await saveCache(cache);
|
|
537
|
+
}
|
|
538
|
+
spinner.stop("");
|
|
331
539
|
}
|
|
332
|
-
spinner.stop("");
|
|
333
540
|
}
|
|
334
541
|
const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
335
542
|
if (logEntries.length > 0) {
|
|
@@ -339,23 +546,48 @@ async function generateQueriesFromDescribe(queryFolder, warehouseId, options = {
|
|
|
339
546
|
console.log(` ${pc.bold("Typegen Queries")} ${pc.dim(`(${logEntries.length})`)}`);
|
|
340
547
|
console.log(` ${separator}`);
|
|
341
548
|
for (const entry of logEntries) {
|
|
342
|
-
|
|
549
|
+
let tag;
|
|
550
|
+
switch (entry.kind) {
|
|
551
|
+
case "syntax":
|
|
552
|
+
tag = pc.bold(pc.red("SQL ERR"));
|
|
553
|
+
break;
|
|
554
|
+
case "connectivity":
|
|
555
|
+
tag = pc.bold(pc.yellow("OFFLINE"));
|
|
556
|
+
break;
|
|
557
|
+
case "empty":
|
|
558
|
+
tag = pc.dim("EMPTY ");
|
|
559
|
+
break;
|
|
560
|
+
case "fatal":
|
|
561
|
+
tag = pc.bold(pc.red("FATAL "));
|
|
562
|
+
break;
|
|
563
|
+
default: tag = entry.status === "HIT" ? `cache ${pc.bold(pc.green("HIT "))}` : `cache ${pc.bold(pc.yellow("MISS "))}`;
|
|
564
|
+
}
|
|
343
565
|
const rawName = entry.queryName.padEnd(maxNameLen);
|
|
344
|
-
const name = entry.
|
|
566
|
+
const name = entry.kind === "syntax" || entry.kind === "fatal" ? pc.dim(pc.strikethrough(rawName)) : rawName;
|
|
345
567
|
const errorCode = entry.error?.message.match(/\[([^\]]+)\]/)?.[1];
|
|
346
568
|
const reason = errorCode ? ` ${pc.dim(errorCode)}` : "";
|
|
347
569
|
console.log(` ${tag} ${name}${reason}`);
|
|
348
570
|
}
|
|
349
|
-
const newCount = logEntries.filter((e) => e.status === "MISS" && !e.
|
|
350
|
-
const cacheCount = logEntries.filter((e) => e.status === "HIT"
|
|
351
|
-
const
|
|
571
|
+
const newCount = logEntries.filter((e) => e.status === "MISS" && !e.kind).length;
|
|
572
|
+
const cacheCount = logEntries.filter((e) => e.status === "HIT").length;
|
|
573
|
+
const syntaxCount = logEntries.filter((e) => e.kind === "syntax").length;
|
|
574
|
+
const offlineCount = logEntries.filter((e) => e.kind === "connectivity").length;
|
|
575
|
+
const emptyCount = logEntries.filter((e) => e.kind === "empty").length;
|
|
576
|
+
const fatalCount = logEntries.filter((e) => e.kind === "fatal").length;
|
|
352
577
|
console.log(` ${separator}`);
|
|
353
578
|
const parts = [`${newCount} new`, `${cacheCount} from cache`];
|
|
354
|
-
if (
|
|
579
|
+
if (syntaxCount > 0) parts.push(`${syntaxCount} SQL ${syntaxCount === 1 ? "error" : "errors"}`);
|
|
580
|
+
if (offlineCount > 0) parts.push(`${offlineCount} degraded`);
|
|
581
|
+
if (emptyCount > 0) parts.push(`${emptyCount} empty`);
|
|
582
|
+
if (fatalCount > 0) parts.push(`${fatalCount} fatal ${fatalCount === 1 ? "error" : "errors"}`);
|
|
355
583
|
console.log(` ${parts.join(", ")}. ${pc.dim(`${elapsed}s`)}`);
|
|
356
584
|
console.log("");
|
|
357
585
|
}
|
|
358
|
-
return
|
|
586
|
+
return {
|
|
587
|
+
schemas: [...cachedResults, ...freshResults].sort((a, b) => a.index - b.index).map((r) => r.schema),
|
|
588
|
+
syntaxErrors,
|
|
589
|
+
fatalErrors
|
|
590
|
+
};
|
|
359
591
|
}
|
|
360
592
|
/**
|
|
361
593
|
* Normalize query name by removing the .obo extension
|