@graphenedata/cli 0.0.5 → 0.0.7
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/cli.ts +56 -19
- package/dist/cli/cli.js +1197 -484
- package/dist/docs/graphene.md +184 -171
- package/dist/ui/component-utilities/inputUtils.ts +11 -0
- package/dist/ui/components/Area.svelte +6 -3
- package/dist/ui/components/AreaChart.svelte +2 -1
- package/dist/ui/components/Bar.svelte +14 -8
- package/dist/ui/components/BarChart.svelte +3 -2
- package/dist/ui/components/Chart.svelte +48 -101
- package/dist/ui/components/Column.svelte +2 -0
- package/dist/ui/components/Line.svelte +8 -5
- package/dist/ui/components/LineChart.svelte +3 -3
- package/dist/ui/components/QueryLoad.svelte +1 -1
- package/dist/ui/internal/queryEngine.ts +29 -8
- package/dist/ui/web.js +3 -2
- package/package.json +3 -2
package/dist/cli/cli.js
CHANGED
|
@@ -69,6 +69,7 @@ async function pollFor(fn, timeoutMs, interval) {
|
|
|
69
69
|
}
|
|
70
70
|
var init_util = __esm({
|
|
71
71
|
"../lang/util.ts"() {
|
|
72
|
+
"use strict";
|
|
72
73
|
}
|
|
73
74
|
});
|
|
74
75
|
|
|
@@ -135,6 +136,7 @@ function extractLeadingMetadata(node) {
|
|
|
135
136
|
}
|
|
136
137
|
var init_metadata = __esm({
|
|
137
138
|
"../lang/metadata.ts"() {
|
|
139
|
+
"use strict";
|
|
138
140
|
init_util();
|
|
139
141
|
}
|
|
140
142
|
});
|
|
@@ -143,8 +145,12 @@ var init_metadata = __esm({
|
|
|
143
145
|
import * as fs from "fs";
|
|
144
146
|
import path from "path";
|
|
145
147
|
function setConfig(cfg) {
|
|
148
|
+
let dialect = cfg.dialect || "duckdb";
|
|
149
|
+
if (cfg.bigquery) dialect = "bigquery";
|
|
150
|
+
else if (cfg.snowflake) dialect = "snowflake";
|
|
151
|
+
else if (cfg.duckdb) dialect = "duckdb";
|
|
146
152
|
Object.keys(config).forEach((key) => delete config[key]);
|
|
147
|
-
Object.assign(config, cfg);
|
|
153
|
+
Object.assign(config, cfg, { dialect });
|
|
148
154
|
}
|
|
149
155
|
function loadConfig(dir) {
|
|
150
156
|
if (config.root) return;
|
|
@@ -156,19 +162,17 @@ function loadConfig(dir) {
|
|
|
156
162
|
} catch {
|
|
157
163
|
console.warn("No package.json found in current directory");
|
|
158
164
|
}
|
|
159
|
-
|
|
160
|
-
if (packageJsonObject.bigquery) dialect = "bigquery";
|
|
161
|
-
else if (packageJsonObject.snowflake) dialect = "snowflake";
|
|
162
|
-
setConfig({ ...packageJsonObject, dialect, root: packageJsonObject.root || process.cwd() });
|
|
165
|
+
setConfig({ ...packageJsonObject, root: packageJsonObject.root || process.cwd() });
|
|
163
166
|
}
|
|
164
167
|
var config;
|
|
165
168
|
var init_config = __esm({
|
|
166
169
|
"../lang/config.ts"() {
|
|
170
|
+
"use strict";
|
|
167
171
|
config = { dialect: "duckdb", root: "" };
|
|
168
172
|
}
|
|
169
173
|
});
|
|
170
174
|
|
|
171
|
-
// ../lang/
|
|
175
|
+
// ../lang/functionDefs.ts
|
|
172
176
|
import { DUCKDB_DIALECT_FUNCTIONS, GlobalNameSpace, DialectNameSpace, getDialect } from "@graphenedata/malloy";
|
|
173
177
|
function findOverloads(name, dialect) {
|
|
174
178
|
if (!dialectNamespaces.has(dialect)) {
|
|
@@ -179,8 +183,9 @@ function findOverloads(name, dialect) {
|
|
|
179
183
|
return res?.entry ? res.entry.overloads : [];
|
|
180
184
|
}
|
|
181
185
|
var globalNamespace, dialectNamespaces, BIGQUERY_DIALECT_FUNCTIONS;
|
|
182
|
-
var
|
|
183
|
-
"../lang/
|
|
186
|
+
var init_functionDefs = __esm({
|
|
187
|
+
"../lang/functionDefs.ts"() {
|
|
188
|
+
"use strict";
|
|
184
189
|
globalNamespace = new GlobalNameSpace();
|
|
185
190
|
dialectNamespaces = /* @__PURE__ */ new Map();
|
|
186
191
|
Object.assign(DUCKDB_DIALECT_FUNCTIONS, {
|
|
@@ -249,8 +254,9 @@ var init_functions = __esm({
|
|
|
249
254
|
impl: { function: "TIMESTAMP_DIFF" }
|
|
250
255
|
},
|
|
251
256
|
"date_trunc": {
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
generic: { "T": ["date", "timestamp"] },
|
|
258
|
+
takes: { "date": { generic: "T" }, "unit": { sql_native: "kw" } },
|
|
259
|
+
returns: { generic: "T" },
|
|
254
260
|
impl: { sql: "DATE_TRUNC(${date}, ${unit})" }
|
|
255
261
|
},
|
|
256
262
|
"current_date": {
|
|
@@ -310,6 +316,244 @@ var init_functions = __esm({
|
|
|
310
316
|
}
|
|
311
317
|
});
|
|
312
318
|
|
|
319
|
+
// ../lang/functions.ts
|
|
320
|
+
function analyzeFunctionCall(expr, scope) {
|
|
321
|
+
let rawName = txt(expr.getChild("Identifier")).toLowerCase();
|
|
322
|
+
let argNodes = expr.getChildren("Expression");
|
|
323
|
+
let name = rawName;
|
|
324
|
+
let overload = findOverloads(name, config.dialect).find((o) => {
|
|
325
|
+
return o.params.length == argNodes.length || !!o.params.find((p) => p.isVariadic);
|
|
326
|
+
});
|
|
327
|
+
let args = argNodes.map((node, idx) => {
|
|
328
|
+
let firstType = overload?.params[idx]?.allowedTypes[0];
|
|
329
|
+
if (firstType?.type === "sql native" && firstType?.rawType === "kw") {
|
|
330
|
+
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
331
|
+
} else {
|
|
332
|
+
let argExpr = analyzeExpression(node, scope);
|
|
333
|
+
let allowed = overload?.params[idx]?.allowedTypes.map((at) => at.type);
|
|
334
|
+
if (allowed) checkTypes(argExpr, allowed, node);
|
|
335
|
+
return argExpr;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
let type = overload?.returnType.type;
|
|
339
|
+
if (type == "generic") type = args[0]?.type || "string";
|
|
340
|
+
if (type && !isSupportedType(type)) {
|
|
341
|
+
return diag(expr, `Unsupported function return type ${type} from function ${name}`, errExpr);
|
|
342
|
+
}
|
|
343
|
+
let structPaths = /* @__PURE__ */ new Set();
|
|
344
|
+
args.forEach((a) => walkExpression(a, (e) => {
|
|
345
|
+
if (e.node != "field") return;
|
|
346
|
+
structPaths.add(e.path.slice(0, -1).join(".") || scope.table.name);
|
|
347
|
+
}));
|
|
348
|
+
let ret;
|
|
349
|
+
let percentileMatch = /^p(\d+)$/.exec(rawName);
|
|
350
|
+
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
351
|
+
let type2 = "number", typeDef;
|
|
352
|
+
if (["min", "max", "avg"].includes(name.toLowerCase())) {
|
|
353
|
+
type2 = args[0].type;
|
|
354
|
+
typeDef = args[0].typeDef;
|
|
355
|
+
}
|
|
356
|
+
ret = { node: "aggregate", function: name, e: args[0], type: type2, typeDef, isAgg: true };
|
|
357
|
+
} else if (percentileMatch) {
|
|
358
|
+
ret = analyzePercentile(expr, scope, percentileMatch[1], argNodes);
|
|
359
|
+
} else if (overload && type) {
|
|
360
|
+
ret = {
|
|
361
|
+
node: "function_call",
|
|
362
|
+
type,
|
|
363
|
+
name,
|
|
364
|
+
overload,
|
|
365
|
+
expressionType: overload.returnType.expressionType || "scalar",
|
|
366
|
+
kids: { args },
|
|
367
|
+
isAgg: overload.returnType.expressionType == "aggregate" || args.some((a) => a.isAgg)
|
|
368
|
+
};
|
|
369
|
+
} else {
|
|
370
|
+
return diag(expr, `Unknown function: ${name}`, errExpr);
|
|
371
|
+
}
|
|
372
|
+
if (structPaths.size > 1 && (ret.node == "aggregate" || ret.expressionType == "aggregate")) {
|
|
373
|
+
return diag(expr, "Graphene only supports a single table within aggregates. This one has: " + Array.from(structPaths).join(", "), errExpr);
|
|
374
|
+
}
|
|
375
|
+
let foriegnPaths = Array.from(structPaths).filter((p) => p != scope.table.name);
|
|
376
|
+
if (foriegnPaths.length > 0) ret.structPath = foriegnPaths[0].split(".");
|
|
377
|
+
return ret;
|
|
378
|
+
}
|
|
379
|
+
function isSupportedType(value) {
|
|
380
|
+
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic", "interval"];
|
|
381
|
+
return supported.includes(value);
|
|
382
|
+
}
|
|
383
|
+
function analyzePercentile(callNode, scope, digits, argNodes) {
|
|
384
|
+
let frac = Number(`0.${digits}`);
|
|
385
|
+
if (Number(digits) == 100) return diag(callNode, "p100 is not allowed", errExpr);
|
|
386
|
+
if (Number(digits) == 0) return diag(callNode, "p0 is not allowed", errExpr);
|
|
387
|
+
if (config.dialect == "bigquery" && frac > 0.99) return diag(callNode, "BigQuery only supports up to p99", errExpr);
|
|
388
|
+
let valueExpr = analyzeExpression(argNodes[0], scope);
|
|
389
|
+
checkTypes(valueExpr, ["number"], argNodes[0]);
|
|
390
|
+
let src;
|
|
391
|
+
switch (config.dialect) {
|
|
392
|
+
case "duckdb":
|
|
393
|
+
src = ["quantile_cont(", `, ${frac})`];
|
|
394
|
+
break;
|
|
395
|
+
case "bigquery": {
|
|
396
|
+
src = ["approx_quantiles(", `, 100)[OFFSET(${Math.round(frac * 10)})]`];
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case "snowflake":
|
|
400
|
+
src = [`PERCENTILE_CONT(${frac}) WITHIN GROUP (ORDER BY `, ")"];
|
|
401
|
+
break;
|
|
402
|
+
default:
|
|
403
|
+
return diag(callNode, `Percentile functions are not supported for dialect ${config.dialect}`, errExpr);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
node: "genericSQLExpr",
|
|
407
|
+
kids: { args: [valueExpr] },
|
|
408
|
+
src,
|
|
409
|
+
type: "number",
|
|
410
|
+
isAgg: true
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
var errExpr;
|
|
414
|
+
var init_functions = __esm({
|
|
415
|
+
"../lang/functions.ts"() {
|
|
416
|
+
"use strict";
|
|
417
|
+
init_config();
|
|
418
|
+
init_functionDefs();
|
|
419
|
+
init_util();
|
|
420
|
+
init_analyze();
|
|
421
|
+
errExpr = { node: "error", type: "error" };
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// ../lang/temporalLiterals.ts
|
|
426
|
+
function parseTemporalLiteral(value, expected) {
|
|
427
|
+
let raw = (value ?? "").trim();
|
|
428
|
+
if (!raw) return null;
|
|
429
|
+
let yearMatch = raw.match(/^([0-9]{4})$/);
|
|
430
|
+
if (yearMatch) {
|
|
431
|
+
let year = Number(yearMatch[1]);
|
|
432
|
+
return buildResult(year, 1, 1, 0, 0, 0, "year", expected);
|
|
433
|
+
}
|
|
434
|
+
let yearMonthMatch = raw.match(/^([0-9]{4})-([0-9]{2})$/);
|
|
435
|
+
if (yearMonthMatch) {
|
|
436
|
+
let year = Number(yearMonthMatch[1]);
|
|
437
|
+
let month = Number(yearMonthMatch[2]);
|
|
438
|
+
if (!inRange(month, 1, 12)) return null;
|
|
439
|
+
return buildResult(year, month, 1, 0, 0, 0, "month", expected);
|
|
440
|
+
}
|
|
441
|
+
let dateMatch = raw.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
|
|
442
|
+
if (dateMatch) {
|
|
443
|
+
let year = Number(dateMatch[1]);
|
|
444
|
+
let month = Number(dateMatch[2]);
|
|
445
|
+
let day = Number(dateMatch[3]);
|
|
446
|
+
if (!isValidDate(year, month, day)) return null;
|
|
447
|
+
return buildResult(year, month, day, 0, 0, 0, "day", expected);
|
|
448
|
+
}
|
|
449
|
+
if (expected === "timestamp") {
|
|
450
|
+
let dateTimeMatch = raw.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt\s]([0-9]{1,2})(?::([0-9]{2})(?::([0-9]{2}))?)?$/);
|
|
451
|
+
if (!dateTimeMatch) return null;
|
|
452
|
+
let year = Number(dateTimeMatch[1]);
|
|
453
|
+
let month = Number(dateTimeMatch[2]);
|
|
454
|
+
let day = Number(dateTimeMatch[3]);
|
|
455
|
+
if (!isValidDate(year, month, day)) return null;
|
|
456
|
+
let hour = Number(dateTimeMatch[4]);
|
|
457
|
+
let minute = dateTimeMatch[5] ? Number(dateTimeMatch[5]) : 0;
|
|
458
|
+
let second = dateTimeMatch[6] ? Number(dateTimeMatch[6]) : 0;
|
|
459
|
+
if (!inRange(hour, 0, 23) || !inRange(minute, 0, 59) || !inRange(second, 0, 59)) return null;
|
|
460
|
+
let timeframe = "hour";
|
|
461
|
+
if (dateTimeMatch[6]) {
|
|
462
|
+
timeframe = "second";
|
|
463
|
+
} else if (dateTimeMatch[5]) {
|
|
464
|
+
timeframe = "minute";
|
|
465
|
+
}
|
|
466
|
+
return buildResult(year, month, day, hour, minute, second, timeframe, expected);
|
|
467
|
+
}
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
function parseTemporal(node) {
|
|
471
|
+
if (node.node !== "stringLiteral") return null;
|
|
472
|
+
let rawValue = typeof node.literal === "string" ? node.literal.trim() : "";
|
|
473
|
+
if (!rawValue) return null;
|
|
474
|
+
let parsedDate = parseTemporalLiteral(rawValue, "date");
|
|
475
|
+
if (parsedDate) {
|
|
476
|
+
let typeDef = { type: parsedDate.type, timeframe: parsedDate.timeframe };
|
|
477
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedDate.literal, type: parsedDate.type, typeDef });
|
|
478
|
+
return parsedDate.timeframe;
|
|
479
|
+
}
|
|
480
|
+
let parsedTimestamp = parseTemporalLiteral(rawValue, "timestamp");
|
|
481
|
+
if (parsedTimestamp) {
|
|
482
|
+
let typeDef = { type: parsedTimestamp.type, timeframe: parsedTimestamp.timeframe };
|
|
483
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedTimestamp.literal, type: parsedTimestamp.type, typeDef });
|
|
484
|
+
return parsedTimestamp.timeframe;
|
|
485
|
+
}
|
|
486
|
+
let interval = parseIntervalLiteral(rawValue);
|
|
487
|
+
if (interval) {
|
|
488
|
+
Object.assign(node, { node: "numberLiteral", literal: interval.quantity.toString(), type: "interval", intervalUnit: interval.unit });
|
|
489
|
+
return interval.unit;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
function parseIntervalLiteral(value) {
|
|
494
|
+
let raw = (value ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
495
|
+
if (!raw) return null;
|
|
496
|
+
let match = raw.match(/^(-?\d+(?:\.\d+)?)\s+([a-z]+)$/);
|
|
497
|
+
if (!match) return null;
|
|
498
|
+
let quantity = Number(match[1]);
|
|
499
|
+
if (!Number.isFinite(quantity)) return null;
|
|
500
|
+
let unit = INTERVAL_UNITS[match[2]];
|
|
501
|
+
if (!unit) return null;
|
|
502
|
+
return { quantity, unit };
|
|
503
|
+
}
|
|
504
|
+
function buildResult(year, month, day, hour, minute, second, timeframe, expected) {
|
|
505
|
+
if (expected === "date") {
|
|
506
|
+
return { literal: `${pad(year)}-${pad(month)}-${pad(day)}`, timeframe, type: "date" };
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
literal: `${pad(year)}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`,
|
|
510
|
+
timeframe,
|
|
511
|
+
type: "timestamp"
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function inRange(value, min, max) {
|
|
515
|
+
return Number.isInteger(value) && value >= min && value <= max;
|
|
516
|
+
}
|
|
517
|
+
function isValidDate(year, month, day) {
|
|
518
|
+
if (!inRange(month, 1, 12)) return false;
|
|
519
|
+
if (!inRange(day, 1, 31)) return false;
|
|
520
|
+
let date = new Date(Date.UTC(year, month - 1, day));
|
|
521
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
522
|
+
}
|
|
523
|
+
function pad(value) {
|
|
524
|
+
return value.toString().padStart(2, "0");
|
|
525
|
+
}
|
|
526
|
+
var INTERVAL_UNITS;
|
|
527
|
+
var init_temporalLiterals = __esm({
|
|
528
|
+
"../lang/temporalLiterals.ts"() {
|
|
529
|
+
"use strict";
|
|
530
|
+
INTERVAL_UNITS = {
|
|
531
|
+
second: "second",
|
|
532
|
+
seconds: "second",
|
|
533
|
+
sec: "second",
|
|
534
|
+
secs: "second",
|
|
535
|
+
minute: "minute",
|
|
536
|
+
minutes: "minute",
|
|
537
|
+
min: "minute",
|
|
538
|
+
mins: "minute",
|
|
539
|
+
hour: "hour",
|
|
540
|
+
hours: "hour",
|
|
541
|
+
hr: "hour",
|
|
542
|
+
hrs: "hour",
|
|
543
|
+
day: "day",
|
|
544
|
+
days: "day",
|
|
545
|
+
week: "week",
|
|
546
|
+
weeks: "week",
|
|
547
|
+
month: "month",
|
|
548
|
+
months: "month",
|
|
549
|
+
quarter: "quarter",
|
|
550
|
+
quarters: "quarter",
|
|
551
|
+
year: "year",
|
|
552
|
+
years: "year"
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
313
557
|
// ../lang/params.ts
|
|
314
558
|
function inferParamTypes(query) {
|
|
315
559
|
let parentMap = /* @__PURE__ */ new WeakMap();
|
|
@@ -416,8 +660,7 @@ function sanitizeType(value) {
|
|
|
416
660
|
return value;
|
|
417
661
|
}
|
|
418
662
|
function fillInParams(query, params) {
|
|
419
|
-
let
|
|
420
|
-
let filters = q.pipeline[0].filterList || [];
|
|
663
|
+
let filters = query.pipeline[0].filterList || [];
|
|
421
664
|
for (let filter of filters) {
|
|
422
665
|
walkExpression(filter.e, (e) => {
|
|
423
666
|
if (e.node !== "parameter") return;
|
|
@@ -426,20 +669,31 @@ function fillInParams(query, params) {
|
|
|
426
669
|
else if (value == null) Object.assign(e, { node: "null", type: "string" });
|
|
427
670
|
else if (e.type == "string") Object.assign(e, { node: "stringLiteral", literal: value });
|
|
428
671
|
else if (e.type == "number") Object.assign(e, { node: "numberLiteral", literal: value.toString() });
|
|
429
|
-
else if (e.type == "
|
|
672
|
+
else if (e.type == "date" || e.type == "timestamp") {
|
|
673
|
+
if (typeof value !== "string") throw new Error(`Parameters of type ${e.type} must be provided as strings`);
|
|
674
|
+
let parsed = parseTemporalLiteral(value, e.type);
|
|
675
|
+
if (!parsed) throw new Error(`Could not parse ${e.type} literal for param $${e.path[0]}`);
|
|
676
|
+
Object.assign(e, { node: "timeLiteral", literal: parsed.literal, type: parsed.type, typeDef: { type: parsed.type, timeframe: parsed.timeframe } });
|
|
677
|
+
} else if (e.type == "interval") {
|
|
678
|
+
if (typeof value !== "string") throw new Error("Parameters of type interval must be provided as strings");
|
|
679
|
+
let parsed = parseIntervalLiteral(value);
|
|
680
|
+
if (!parsed) throw new Error(`Could not parse interval literal for param $${e.path[0]}`);
|
|
681
|
+
Object.assign(e, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
682
|
+
} else if (e.type == "boolean") Object.assign(e, { node: value ? "true" : "false" });
|
|
430
683
|
else throw new Error(`Unsupported param type ${e.type}`);
|
|
431
684
|
});
|
|
432
685
|
}
|
|
433
|
-
return q;
|
|
434
686
|
}
|
|
435
687
|
var COMPARISON_OPS, BOOLEAN_OPS, NUMERIC_UNARY_OPS, FIELD_TYPES;
|
|
436
688
|
var init_params = __esm({
|
|
437
689
|
"../lang/params.ts"() {
|
|
690
|
+
"use strict";
|
|
438
691
|
init_util();
|
|
692
|
+
init_temporalLiterals();
|
|
439
693
|
COMPARISON_OPS = /* @__PURE__ */ new Set(["=", "!=", "<>", ">", ">=", "<", "<=", "like", "ilike"]);
|
|
440
694
|
BOOLEAN_OPS = /* @__PURE__ */ new Set(["and", "or"]);
|
|
441
695
|
NUMERIC_UNARY_OPS = /* @__PURE__ */ new Set(["unary-"]);
|
|
442
|
-
FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "fieldref", "array", "record", "null"]);
|
|
696
|
+
FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "fieldref", "array", "record", "null", "interval"]);
|
|
443
697
|
}
|
|
444
698
|
});
|
|
445
699
|
|
|
@@ -451,40 +705,15 @@ function findTables(fi) {
|
|
|
451
705
|
fi.tables = [];
|
|
452
706
|
let nodes = tn.getChildren("TableStatement").concat(tn.getChildren("ViewStatement"));
|
|
453
707
|
for (let syntaxNode of nodes) {
|
|
454
|
-
let name = txt(syntaxNode.getChild("
|
|
708
|
+
let name = txt(syntaxNode.getChild("Ref"));
|
|
455
709
|
if (Object.values(FILE_MAP).find((f) => f.tables.find((t) => t.name == name))) {
|
|
456
|
-
diag(syntaxNode.getChild("
|
|
710
|
+
diag(syntaxNode.getChild("Ref"), `Table "${name}" is already defined`);
|
|
457
711
|
}
|
|
458
712
|
let table2 = makeTable(name, syntaxNode.getChild("QueryStatement") ? "query_source" : "table");
|
|
459
713
|
table2.metadata = extractLeadingMetadata(syntaxNode);
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (table2.primaryKey) diag(cn, `Table ${table2.name} has multiple primary keys`);
|
|
464
|
-
table2.primaryKey = name2;
|
|
465
|
-
}
|
|
466
|
-
let type = convertDataType(txt(cn.getChild("DataType")));
|
|
467
|
-
if (!type) diag(cn, `Unsupported data type: ${txt(cn.getChild("DataType"))}`);
|
|
468
|
-
let field = { name: name2, type, metadata: extractLeadingMetadata(cn) };
|
|
469
|
-
table2.fields.push(field);
|
|
470
|
-
FIELD_NODE_MAP.set(field, cn);
|
|
471
|
-
}
|
|
472
|
-
for (let jn of syntaxNode.getChildren("JoinDef")) {
|
|
473
|
-
let nameNode = jn.getChild("Alias") || jn.getChild("Identifier");
|
|
474
|
-
let field = { name: txt(nameNode) };
|
|
475
|
-
table2.fields.push(field);
|
|
476
|
-
FIELD_NODE_MAP.set(field, jn);
|
|
477
|
-
}
|
|
478
|
-
for (let cn of syntaxNode.getChildren("ComputedDef")) {
|
|
479
|
-
let field = { name: txt(cn.getChild("Alias")), metadata: extractLeadingMetadata(cn) };
|
|
480
|
-
table2.fields.push(field);
|
|
481
|
-
FIELD_NODE_MAP.set(field, cn);
|
|
482
|
-
}
|
|
483
|
-
table2.fields.reduce((set, f) => {
|
|
484
|
-
if (!set[f.name]) set[f.name] = true;
|
|
485
|
-
else diag(FIELD_NODE_MAP.get(f), `Table already has a field called "${f.name}"`);
|
|
486
|
-
return set;
|
|
487
|
-
}, {});
|
|
714
|
+
syntaxNode.getChildren("ColumnDef").forEach((cn) => addColumnField(table2, cn));
|
|
715
|
+
syntaxNode.getChildren("JoinDef").forEach((jn) => addJoinField(table2, jn));
|
|
716
|
+
syntaxNode.getChildren("ComputedDef").forEach((cn) => addComputedField(table2, cn));
|
|
488
717
|
TABLE_NODE_MAP.set(table2, syntaxNode);
|
|
489
718
|
fi.tables.push(table2);
|
|
490
719
|
}
|
|
@@ -493,12 +722,53 @@ function makeTable(name, type) {
|
|
|
493
722
|
let tablePath = config.namespace ? `${config.namespace}.${name}` : name;
|
|
494
723
|
return { name, type, fields: [], connection: config.dialect, dialect: config.dialect, tableName: name, tablePath, metadata: {} };
|
|
495
724
|
}
|
|
725
|
+
function addColumnField(table2, node) {
|
|
726
|
+
let name = txt(node.getChild("Identifier"));
|
|
727
|
+
if (node.getChild("PrimaryKey")) {
|
|
728
|
+
if (table2.primaryKey) diag(node, `Table ${table2.name} has multiple primary keys`);
|
|
729
|
+
table2.primaryKey = name;
|
|
730
|
+
}
|
|
731
|
+
let type = convertDataType(txt(node.getChild("DataType")));
|
|
732
|
+
if (!type) diag(node, `Unsupported data type: ${txt(node.getChild("DataType"))}`);
|
|
733
|
+
addFieldToTable(table2, { name, type, metadata: extractLeadingMetadata(node) }, node);
|
|
734
|
+
}
|
|
735
|
+
function addJoinField(table2, node) {
|
|
736
|
+
let nameNode = node.getChild("Alias") || node.getChild("Ref").getChildren("Identifier").pop();
|
|
737
|
+
return addFieldToTable(table2, { name: txt(nameNode) }, node);
|
|
738
|
+
}
|
|
739
|
+
function addComputedField(table2, node) {
|
|
740
|
+
let name = txt(node.getChild("Alias"));
|
|
741
|
+
addFieldToTable(table2, { name, metadata: extractLeadingMetadata(node) }, node);
|
|
742
|
+
}
|
|
743
|
+
function addFieldToTable(table2, field, node) {
|
|
744
|
+
if (table2.fields.find((f) => f.name == field.name)) {
|
|
745
|
+
return diag(node, `Table already has a field called "${field.name}"`);
|
|
746
|
+
}
|
|
747
|
+
table2.fields.push(field);
|
|
748
|
+
FIELD_NODE_MAP.set(field, node);
|
|
749
|
+
}
|
|
750
|
+
function applyExtends(fi) {
|
|
751
|
+
fi.tree.topNode.getChildren("ExtendStatement").forEach((node) => {
|
|
752
|
+
let tableName = txt(node.getChild("Ref"));
|
|
753
|
+
let target = lookupTable(tableName, node);
|
|
754
|
+
if (!target) {
|
|
755
|
+
return diag(node.getChild("Ref") || node, `Cannot extend unknown table "${tableName}"`);
|
|
756
|
+
}
|
|
757
|
+
node.getChildren("JoinDef").forEach((jn) => addJoinField(target, jn));
|
|
758
|
+
node.getChildren("ComputedDef").forEach((cn) => addComputedField(target, cn));
|
|
759
|
+
});
|
|
760
|
+
}
|
|
496
761
|
function analyzeTable(table2) {
|
|
497
|
-
if (table2.type == "query_source")
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
762
|
+
if (table2.type == "query_source") {
|
|
763
|
+
if (table2.query) return;
|
|
764
|
+
let node = TABLE_NODE_MAP.get(table2);
|
|
765
|
+
let query = analyzeQuery(node.getChild("QueryStatement"));
|
|
766
|
+
if (!query) return;
|
|
767
|
+
let queryFields = query.fields.map((f) => ({ type: f.type, name: f.name, metadata: f.metadata }));
|
|
768
|
+
table2.fields.push(...queryFields);
|
|
769
|
+
table2.query = query;
|
|
501
770
|
}
|
|
771
|
+
table2.fields.map((f) => analyzeField(f, table2));
|
|
502
772
|
}
|
|
503
773
|
function analyzeField(field, table2) {
|
|
504
774
|
if (field.type) return;
|
|
@@ -509,13 +779,14 @@ function analyzeField(field, table2) {
|
|
|
509
779
|
analysisQueue.add(field);
|
|
510
780
|
if (node.name == "JoinDef") {
|
|
511
781
|
field = field;
|
|
512
|
-
let target = lookupTable(txt(node.getChild("
|
|
782
|
+
let target = lookupTable(txt(node.getChild("Ref")), node);
|
|
513
783
|
if (!target) return diag(node, "Unknown table to join");
|
|
514
|
-
if (target.type == "query_source")
|
|
515
|
-
let
|
|
784
|
+
if (target.type == "query_source") analyzeTable(target);
|
|
785
|
+
let joinTypeStr = txt(node.getChild("JoinType")).replace(/\s+/g, " ");
|
|
786
|
+
let jt = { "join many": "many", "join one": "one" }[joinTypeStr];
|
|
516
787
|
if (!jt) return diag(node, "Unknown join type");
|
|
517
788
|
Object.assign(field, target, { name: field.name, join: jt });
|
|
518
|
-
field.onExpression = analyzeExpression(node.getChild("
|
|
789
|
+
field.onExpression = analyzeExpression(node.getChild("BinaryExpression"), { table: table2, outputFields: [] });
|
|
519
790
|
}
|
|
520
791
|
if (node.name == "ComputedDef") {
|
|
521
792
|
let e = analyzeExpression(node.getChild("Expression"), { table: table2, outputFields: [] });
|
|
@@ -524,38 +795,29 @@ function analyzeField(field, table2) {
|
|
|
524
795
|
}
|
|
525
796
|
analysisQueue.delete(field);
|
|
526
797
|
}
|
|
527
|
-
function analyzeQueryTable(table2) {
|
|
528
|
-
if (table2.query) return;
|
|
529
|
-
let node = TABLE_NODE_MAP.get(table2);
|
|
530
|
-
let query = analyzeQuery(node.getChild("QueryStatement"));
|
|
531
|
-
if (!query) return;
|
|
532
|
-
table2.fields = query.fields.map((f) => ({ type: f.type, name: f.name, metadata: f.metadata }));
|
|
533
|
-
table2.query = query.malloyQuery;
|
|
534
|
-
if (table2.query && typeof table2.query.structRef == "string") {
|
|
535
|
-
table2.query.structRef = lookupTable(table2.query.structRef, node);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
798
|
function analyzeQuery(queryNode) {
|
|
539
|
-
let
|
|
799
|
+
let baseTableName;
|
|
540
800
|
let scope = { table: null, outputFields: [] };
|
|
541
801
|
let isAgg = false;
|
|
542
|
-
let subQuerySources = [];
|
|
543
802
|
if (!txt(queryNode)) return;
|
|
544
|
-
if (txt(queryNode).trim().toLowerCase() == "select 1")
|
|
803
|
+
if (txt(queryNode).trim().toLowerCase() == "select 1") {
|
|
804
|
+
let fields = [{ name: "col_0", type: "number", metadata: {}, e: { node: "numberLiteral", literal: "1", type: "number" } }];
|
|
805
|
+
return { fields, baseTableName: "", rawSql: "select 1", structRef: {}, pipeline: [] };
|
|
806
|
+
}
|
|
545
807
|
let froms = queryNode.getChild("FromClause")?.getChildren("TablePrimary") || [];
|
|
546
808
|
if (froms.find((f) => f.name == "JoinClause")) diag(froms[0], "Query joins not yet supported");
|
|
547
809
|
if (froms.length == 0) return diag(queryNode, "No tables in FROM clause");
|
|
548
810
|
if (froms.length > 1) diag(froms[0], "Multiple tables/joins in FROM clause not yet supported");
|
|
549
811
|
if (froms[0].name == "Subquery") {
|
|
550
|
-
|
|
551
|
-
|
|
812
|
+
diag(froms[0], "Graphene doesn't yet support subqueries. Try chaining queries instead.");
|
|
813
|
+
baseTableName = txt(froms[0].getChild("Alias")) || "subquery";
|
|
814
|
+
scope.table = makeTable(baseTableName, "query_source");
|
|
552
815
|
TABLE_NODE_MAP.set(scope.table, froms[0].getChild("SubqueryExpression"));
|
|
553
|
-
|
|
554
|
-
subQuerySources.push(scope.table);
|
|
816
|
+
analyzeTable(scope.table);
|
|
555
817
|
} else {
|
|
556
|
-
|
|
557
|
-
scope.table = lookupTable(
|
|
558
|
-
if (!scope.table) return diag(froms[0], `could not find table "${
|
|
818
|
+
baseTableName = txt(froms[0].getChild("Ref"));
|
|
819
|
+
scope.table = lookupTable(baseTableName, froms[0]);
|
|
820
|
+
if (!scope.table) return diag(froms[0], `could not find table "${baseTableName}"`);
|
|
559
821
|
NODE_ENTITY_MAP.set(froms[0], { entityType: "table", table: scope.table });
|
|
560
822
|
}
|
|
561
823
|
let selects = queryNode.getChild("SelectClause")?.getChildren("SelectItem") || [];
|
|
@@ -563,11 +825,12 @@ function analyzeQuery(queryNode) {
|
|
|
563
825
|
isAgg ||= !!isSelectDistinct;
|
|
564
826
|
selects.forEach((s) => {
|
|
565
827
|
if (s.getChild("Wildcard")) {
|
|
566
|
-
let
|
|
567
|
-
let pathStrings =
|
|
568
|
-
let target = followJoins(
|
|
828
|
+
let path10 = s.getChild("Wildcard").getChildren("Identifier");
|
|
829
|
+
let pathStrings = path10.map((p) => txt(p));
|
|
830
|
+
let target = followJoins(path10, scope.table);
|
|
569
831
|
if (!target) return;
|
|
570
832
|
target.fields.forEach((f) => {
|
|
833
|
+
analyzeField(f, target);
|
|
571
834
|
if (isJoin(f) || f.isAgg) return;
|
|
572
835
|
scope.outputFields.push({ ...f, e: { node: "field", path: [...pathStrings, f.name], type: f.type } });
|
|
573
836
|
});
|
|
@@ -621,27 +884,26 @@ function analyzeQuery(queryNode) {
|
|
|
621
884
|
}
|
|
622
885
|
let q = {
|
|
623
886
|
fields: scope.outputFields,
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
887
|
+
baseTableName,
|
|
888
|
+
type: "query",
|
|
889
|
+
structRef: null,
|
|
890
|
+
// We fill this in as part of `toSql`
|
|
891
|
+
pipeline: [{
|
|
892
|
+
type: isAgg ? "reduce" : "project",
|
|
893
|
+
queryFields: scope.outputFields,
|
|
894
|
+
filterList,
|
|
895
|
+
outputStruct: null,
|
|
896
|
+
isRepeated: false,
|
|
897
|
+
orderBy: orderByList.length ? orderByList : void 0,
|
|
898
|
+
limit: queryLimit
|
|
899
|
+
}]
|
|
638
900
|
};
|
|
639
|
-
inferParamTypes(q
|
|
901
|
+
inferParamTypes(q);
|
|
640
902
|
return q;
|
|
641
903
|
}
|
|
642
904
|
function analyzeExpression(expr, scope) {
|
|
643
905
|
if (expr.type.isError) {
|
|
644
|
-
return diag(expr, "Invalid expression",
|
|
906
|
+
return diag(expr, "Invalid expression", errExpr2);
|
|
645
907
|
}
|
|
646
908
|
switch (expr.name) {
|
|
647
909
|
case "Number":
|
|
@@ -656,21 +918,23 @@ function analyzeExpression(expr, scope) {
|
|
|
656
918
|
return { node: "parameter", path: [txt(expr).slice(1)], type: "string" };
|
|
657
919
|
case "Ref": {
|
|
658
920
|
let field = lookupField(expr, scope);
|
|
659
|
-
if (!field) return
|
|
921
|
+
if (!field) return errExpr2;
|
|
660
922
|
let type = field.type || "unknown";
|
|
661
923
|
let typeInfo = { type };
|
|
662
924
|
if (type === "date" || type === "timestamp") typeInfo.typeDef = { type };
|
|
663
925
|
if (scope.outputFields.includes(field) && field.isAgg) {
|
|
664
926
|
return { node: "outputField", name: field.name, ...typeInfo, isAgg: field.isAgg };
|
|
665
927
|
}
|
|
666
|
-
let
|
|
667
|
-
return { node: "field", path:
|
|
928
|
+
let path10 = expr.getChildren("Identifier").map((i) => txt(i));
|
|
929
|
+
return { node: "field", path: path10, ...typeInfo, isAgg: field.isAgg };
|
|
668
930
|
}
|
|
669
931
|
case "ExtractExpression": {
|
|
670
|
-
let
|
|
671
|
-
|
|
932
|
+
let extractExprNode = expr.getChild("Expression");
|
|
933
|
+
let e = analyzeExpression(extractExprNode, scope);
|
|
934
|
+
checkTypes(e, ["date", "timestamp"], extractExprNode);
|
|
935
|
+
if (!isTemporalType(e.type) || !e.typeDef) return diag(expr, "Expression must be a date or timestamp", errExpr2);
|
|
672
936
|
let units = txt(expr.getChild("ExtractUnit")).replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
673
|
-
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract",
|
|
937
|
+
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract", errExpr2);
|
|
674
938
|
return { node: "extract", type: "number", units, e, isAgg: false };
|
|
675
939
|
}
|
|
676
940
|
case "FunctionCall":
|
|
@@ -690,7 +954,29 @@ function analyzeExpression(expr, scope) {
|
|
|
690
954
|
let left2 = analyzeExpression(expr.firstChild, scope);
|
|
691
955
|
let right2 = analyzeExpression(expr.lastChild, scope);
|
|
692
956
|
let op = txt(expr.firstChild?.nextSibling).toLowerCase();
|
|
693
|
-
|
|
957
|
+
let type = left2.type;
|
|
958
|
+
if (op == "or" || op == "and") type = "boolean";
|
|
959
|
+
if (op == "+" || op == "-") {
|
|
960
|
+
if (["date", "timestamp", "interval"].find((t) => left2.type == t || right2.type == t)) {
|
|
961
|
+
return analyzeTimeExpression(op, left2, right2, expr);
|
|
962
|
+
}
|
|
963
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
964
|
+
}
|
|
965
|
+
if (op == "*" || op == "/" || op == "%") {
|
|
966
|
+
checkTypes(left2, ["number"], expr.firstChild);
|
|
967
|
+
checkTypes(right2, ["number"], expr.lastChild);
|
|
968
|
+
}
|
|
969
|
+
if (op == "<" || op == "<=" || op == ">" || op == ">=" || op == "=" || op == "!=" || op == "<>") {
|
|
970
|
+
if (op == "<>") op = "!=";
|
|
971
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
972
|
+
type = "boolean";
|
|
973
|
+
}
|
|
974
|
+
if (op == "like" || op == "ilike") {
|
|
975
|
+
checkTypes(left2, ["string"], expr.firstChild);
|
|
976
|
+
checkTypes(right2, ["string"], expr.lastChild);
|
|
977
|
+
type = "boolean";
|
|
978
|
+
}
|
|
979
|
+
return { node: op, kids: { left: left2, right: right2 }, type, isAgg: left2.isAgg || right2.isAgg };
|
|
694
980
|
}
|
|
695
981
|
case "NullTestExpression": {
|
|
696
982
|
let node = expr.getChildren("Kw").find((n) => txt(n).toLowerCase() == "not") ? "is-not-null" : "is-null";
|
|
@@ -703,7 +989,7 @@ function analyzeExpression(expr, scope) {
|
|
|
703
989
|
if (opTxt === "not") return { node: "not", e: child, type: "boolean", isAgg: child.isAgg };
|
|
704
990
|
if (opTxt === "-") return { node: "unary-", e: child, type: child.type, isAgg: child.isAgg };
|
|
705
991
|
if (opTxt === "+") return { node: "()", e: child, type: child.type, isAgg: child.isAgg };
|
|
706
|
-
return diag(expr, `Unknown unary operator: ${opTxt}`,
|
|
992
|
+
return diag(expr, `Unknown unary operator: ${opTxt}`, errExpr2);
|
|
707
993
|
}
|
|
708
994
|
case "CaseExpression": {
|
|
709
995
|
let caseValue = expr.getChild("Expression");
|
|
@@ -724,8 +1010,13 @@ function analyzeExpression(expr, scope) {
|
|
|
724
1010
|
let oneOf = [];
|
|
725
1011
|
let valueList = expr.getChild("InValueList");
|
|
726
1012
|
if (valueList) {
|
|
727
|
-
oneOf = valueList.getChildren("Expression").map((v) =>
|
|
1013
|
+
oneOf = valueList.getChildren("Expression").map((v) => {
|
|
1014
|
+
let e = analyzeExpression(v, scope);
|
|
1015
|
+
checkTypes(e, [eNode.type], v);
|
|
1016
|
+
return e;
|
|
1017
|
+
});
|
|
728
1018
|
} else {
|
|
1019
|
+
diag(expr, "IN (<subquery>) is not yet supported");
|
|
729
1020
|
oneOf = [{ node: "genericSQLExpr", kids: { args: [] }, type: "array" }];
|
|
730
1021
|
}
|
|
731
1022
|
let isAgg = eNode.isAgg || oneOf.some((v) => v.isAgg);
|
|
@@ -733,59 +1024,52 @@ function analyzeExpression(expr, scope) {
|
|
|
733
1024
|
}
|
|
734
1025
|
case "SubqueryExpression":
|
|
735
1026
|
default:
|
|
736
|
-
return diag(expr, `Unsupported expression "${expr.name}": ${txt(expr)}`,
|
|
1027
|
+
return diag(expr, `Unsupported expression "${expr.name}": ${txt(expr)}`, errExpr2);
|
|
737
1028
|
}
|
|
738
1029
|
}
|
|
739
|
-
function
|
|
740
|
-
|
|
741
|
-
let
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
let type2 = overload?.params[idx]?.allowedTypes[0];
|
|
747
|
-
if (type2?.type === "sql native" && type2?.rawType === "kw") {
|
|
748
|
-
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
749
|
-
} else {
|
|
750
|
-
return analyzeExpression(node, scope);
|
|
1030
|
+
function analyzeTimeExpression(op, left2, right2, node) {
|
|
1031
|
+
if (left2.type !== "date" && left2.type !== "timestamp") return diag(node, "Expected left side to be a date or timestamp", errExpr2);
|
|
1032
|
+
let units = left2.type === "timestamp" ? "second" : "day";
|
|
1033
|
+
if (right2.node == "stringLiteral") {
|
|
1034
|
+
units = parseTemporal(right2);
|
|
1035
|
+
if (right2.node == "stringLiteral") {
|
|
1036
|
+
return diag(node, "Could not parse interval", errExpr2);
|
|
751
1037
|
}
|
|
752
|
-
});
|
|
753
|
-
let type = overload?.returnType.type;
|
|
754
|
-
if (type == "generic") type = args[0]?.type || "string";
|
|
755
|
-
if (type && !isSupportedType(type)) {
|
|
756
|
-
return diag(expr, `Unsupported function return type ${type} from function ${name}`, errExpr);
|
|
757
|
-
}
|
|
758
|
-
let structPaths = /* @__PURE__ */ new Set();
|
|
759
|
-
args.forEach((a) => walkExpression(a, (e) => {
|
|
760
|
-
if (e.node != "field") return;
|
|
761
|
-
structPaths.add(e.path.slice(0, -1).join(".") || scope.table.name);
|
|
762
|
-
}));
|
|
763
|
-
let ret;
|
|
764
|
-
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
765
|
-
ret = { node: "aggregate", function: name, e: args[0], type: "number", isAgg: true };
|
|
766
|
-
} else if (overload && type) {
|
|
767
|
-
ret = {
|
|
768
|
-
node: "function_call",
|
|
769
|
-
type,
|
|
770
|
-
name,
|
|
771
|
-
overload,
|
|
772
|
-
expressionType: overload.returnType.expressionType || "scalar",
|
|
773
|
-
kids: { args },
|
|
774
|
-
isAgg: overload.returnType.expressionType == "aggregate" || args.some((a) => a.isAgg)
|
|
775
|
-
};
|
|
776
|
-
} else {
|
|
777
|
-
return diag(expr, `Unknown function: ${name}`, errExpr);
|
|
778
|
-
}
|
|
779
|
-
if (structPaths.size > 1 && (ret.node == "aggregate" || ret.expressionType == "aggregate")) {
|
|
780
|
-
return diag(expr, "Graphene only supports a single table within aggregates. This one has: " + Array.from(structPaths).join(", "), errExpr);
|
|
781
1038
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1039
|
+
if (right2.type == "date" || right2.type == "timestamp") {
|
|
1040
|
+
if (op !== "-") return diag(node, "Only subtraction between dates is supported", errExpr2);
|
|
1041
|
+
if (right2.type !== left2.type) return diag(node, `Expected right side to be a ${left2.type}`, errExpr2);
|
|
1042
|
+
return { node: "timeDiff", kids: { left: left2, right: right2 }, units, type: "interval", isAgg: false };
|
|
1043
|
+
}
|
|
1044
|
+
if (right2.type == "interval") {
|
|
1045
|
+
let typeDef = { type: left2.type };
|
|
1046
|
+
return { node: "delta", kids: { base: left2, delta: right2 }, op, units, type: left2.type, typeDef, isAgg: false };
|
|
1047
|
+
}
|
|
1048
|
+
return diag(node, "Expected right side to be a date or interval", errExpr2);
|
|
1049
|
+
}
|
|
1050
|
+
function ensureSameType(left2, leftNode, right2, rightNode) {
|
|
1051
|
+
if (left2.type === "error" || right2.type === "error") return;
|
|
1052
|
+
if (left2.node === "parameter" || right2.node === "parameter") return;
|
|
1053
|
+
if (isTemporalType(left2.type)) checkTypes(right2, [left2.type], rightNode);
|
|
1054
|
+
if (isTemporalType(right2.type)) checkTypes(left2, [right2.type], leftNode);
|
|
1055
|
+
if (left2.type !== right2.type) diag(rightNode, `Expected ${left2.type}, got ${right2.type}`);
|
|
1056
|
+
}
|
|
1057
|
+
function checkTypes(expr, expected, node) {
|
|
1058
|
+
if (expr.type === "error") return;
|
|
1059
|
+
if (expr.node === "parameter") return;
|
|
1060
|
+
if (expected.includes(expr.type)) return;
|
|
1061
|
+
if (expected.includes("generic")) return;
|
|
1062
|
+
let dt = expected.find((t) => t == "date") || expected.find((t) => t == "timestamp");
|
|
1063
|
+
if (expr.node == "stringLiteral" && dt) {
|
|
1064
|
+
let parsed = parseTemporalLiteral(expr.literal, dt);
|
|
1065
|
+
if (!parsed) return diag(node, `Could not parse ${dt} literal: "${expr.literal}"`, void 0);
|
|
1066
|
+
let typeDef = { type: parsed.type, timeframe: parsed.timeframe };
|
|
1067
|
+
Object.assign(expr, { node: "timeLiteral", literal: parsed?.literal, type: parsed?.type, typeDef });
|
|
1068
|
+
} else if (expr.node == "stringLiteral" && expected.includes("interval")) {
|
|
1069
|
+
let parsed = parseIntervalLiteral(expr.literal);
|
|
1070
|
+
if (!parsed) return diag(node, `Could not parse interval literal: "${expr.literal}"`, void 0);
|
|
1071
|
+
return Object.assign(expr, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
1072
|
+
} else diag(node, `Expected types: ${expected.join(", ")}`);
|
|
789
1073
|
}
|
|
790
1074
|
function lookupField(expr, scope) {
|
|
791
1075
|
let pathNodes = expr.getChildren("Identifier");
|
|
@@ -886,6 +1170,8 @@ function convertDataType(dataType) {
|
|
|
886
1170
|
return "number";
|
|
887
1171
|
case "FLOAT64":
|
|
888
1172
|
return "number";
|
|
1173
|
+
case "BOOL":
|
|
1174
|
+
return "boolean";
|
|
889
1175
|
case "BOOLEAN":
|
|
890
1176
|
return "boolean";
|
|
891
1177
|
case "DATE":
|
|
@@ -918,58 +1204,61 @@ function convertDataType(dataType) {
|
|
|
918
1204
|
return null;
|
|
919
1205
|
}
|
|
920
1206
|
}
|
|
921
|
-
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue,
|
|
1207
|
+
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue, errExpr2;
|
|
922
1208
|
var init_analyze = __esm({
|
|
923
1209
|
"../lang/analyze.ts"() {
|
|
1210
|
+
"use strict";
|
|
924
1211
|
init_util();
|
|
925
1212
|
init_metadata();
|
|
926
1213
|
init_config();
|
|
927
1214
|
init_functions();
|
|
928
1215
|
init_params();
|
|
1216
|
+
init_temporalLiterals();
|
|
929
1217
|
FILE_MAP = {};
|
|
930
1218
|
diagnostics = [];
|
|
931
1219
|
TABLE_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
932
1220
|
FIELD_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
933
1221
|
NODE_ENTITY_MAP = new NodeWeakMap();
|
|
934
1222
|
analysisQueue = /* @__PURE__ */ new Set();
|
|
935
|
-
|
|
1223
|
+
errExpr2 = { node: "error", type: "error" };
|
|
936
1224
|
}
|
|
937
1225
|
});
|
|
938
1226
|
|
|
939
1227
|
// ../lang/parser.terms.js
|
|
940
|
-
var table, primary_key, as, on, not,
|
|
1228
|
+
var table, primary_key, join, as, on, or, and, like, not, _in, from, inner, left, right, full, cross, select, where, group, by, order, asc, desc, limit, offset, is, _null, exists, _true, _false;
|
|
941
1229
|
var init_parser_terms = __esm({
|
|
942
1230
|
"../lang/parser.terms.js"() {
|
|
1231
|
+
"use strict";
|
|
943
1232
|
table = 6;
|
|
944
|
-
primary_key =
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1233
|
+
primary_key = 12;
|
|
1234
|
+
join = 16;
|
|
1235
|
+
as = 22;
|
|
1236
|
+
on = 25;
|
|
1237
|
+
or = 31;
|
|
1238
|
+
and = 34;
|
|
1239
|
+
like = 43;
|
|
1240
|
+
not = 45;
|
|
1241
|
+
_in = 55;
|
|
1242
|
+
from = 60;
|
|
1243
|
+
inner = 66;
|
|
1244
|
+
left = 68;
|
|
1245
|
+
right = 70;
|
|
1246
|
+
full = 72;
|
|
1247
|
+
cross = 74;
|
|
1248
|
+
select = 77;
|
|
1249
|
+
where = 84;
|
|
1250
|
+
group = 87;
|
|
1251
|
+
by = 89;
|
|
1252
|
+
order = 95;
|
|
1253
|
+
asc = 99;
|
|
1254
|
+
desc = 101;
|
|
1255
|
+
limit = 104;
|
|
1256
|
+
offset = 106;
|
|
966
1257
|
is = 109;
|
|
967
1258
|
_null = 111;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
and = 132;
|
|
972
|
-
like = 141;
|
|
1259
|
+
exists = 129;
|
|
1260
|
+
_true = 133;
|
|
1261
|
+
_false = 135;
|
|
973
1262
|
}
|
|
974
1263
|
});
|
|
975
1264
|
|
|
@@ -981,6 +1270,7 @@ function specializeIdentifier(value) {
|
|
|
981
1270
|
var keywords;
|
|
982
1271
|
var init_tokens = __esm({
|
|
983
1272
|
"../lang/tokens.js"() {
|
|
1273
|
+
"use strict";
|
|
984
1274
|
init_parser_terms();
|
|
985
1275
|
keywords = {
|
|
986
1276
|
select,
|
|
@@ -1022,25 +1312,26 @@ import { LRParser } from "@lezer/lr";
|
|
|
1022
1312
|
var spec_Identifier, parser;
|
|
1023
1313
|
var init_parser = __esm({
|
|
1024
1314
|
"../lang/parser.js"() {
|
|
1315
|
+
"use strict";
|
|
1025
1316
|
init_tokens();
|
|
1026
|
-
spec_Identifier = { __proto__: null, table: 12, primary_key:
|
|
1317
|
+
spec_Identifier = { __proto__: null, table: 12, primary_key: 24, join: 32, one: 36, many: 40, as: 44, on: 50, or: 62, and: 68, like: 86, not: 90, in: 110, from: 120, inner: 132, left: 136, right: 140, full: 144, cross: 148, select: 154, distinct: 158, where: 168, group: 174, by: 178, having: 184, order: 190, asc: 198, desc: 202, limit: 208, offset: 212, is: 218, null: 222, case: 232, when: 238, then: 242, else: 248, end: 252, exists: 258, true: 266, false: 270, count: 280, extract: 286, extend: 306 };
|
|
1027
1318
|
parser = LRParser.deserialize({
|
|
1028
1319
|
version: 14,
|
|
1029
|
-
states: "
|
|
1030
|
-
stateData: "
|
|
1031
|
-
goto: "
|
|
1032
|
-
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw
|
|
1033
|
-
maxTerm:
|
|
1320
|
+
states: "G`QYQPOOOOQO'#C`'#C`OOQO'#Di'#DiOwQPO'#DhOOQO'#Dz'#DzO#TQPO'#DyOOQO'#ER'#EROOQO'#EU'#EUO#[QPO'#ETOOQO'#EZ'#EZOOQO'#E^'#E^O#[QPO'#E]OOQO'#Eg'#EgO#aQPO'#EfOOQO'#Fp'#FpO#}QPO'#DgO$[QPO'#C_OOQO'#Fj'#FjO$[QPO'#FiO$aQPO'#FlQYQPOOQ%UQPO'#FlO%ZQPO'#EQO%ZQPO'#EYO(hQPO'#CcO(rQPO'#CcO(wQPO'#DkO#fQPO'#DmO*UQPO'#DlOOQO'#GP'#GPO+wQPO,5:SO,kQPO'#CcO-zQPO'#CcOOQO'#DY'#DYO.SQPO'#DmOOQO'#D|'#D|OOQO'#EP'#EPOOQO'#EO'#EOO.mQPO,5:eO!PQPO,5:eO0oQPO'#EOOOQO'#En'#EnOOQO'#Eq'#EqOOQO'#Es'#EsO1iQPO'#ErOOQO'#FQ'#FQO1pQPO'#FPOOQO'#FU'#FUOOQO'#FW'#FWOOQO'#FT'#FTOOQO'#FY'#FYOOQO'#GT'#GTOOQO'#F]'#F]O1uQPO'#F[OOQO'#GS'#GSOOQO'#F`'#F`OOQO'#GR'#GROOQO'#GU'#GUOOQO'#F}'#F}O%ZQPO'#EpO1zQPO'#F_OOQO'#EW'#EWO!PQPO,5:oO2PQPO,5:wO2XQPO,5;QOOQO-E9n-E9nO2|QPO,58yO3UQPO,5<TOOQO,5<W,5<WOOQO-E9j-E9jO3ZQPO,5:lO3}QPO,5:tOOQO,5<X,5<XO4qQPO,58}OOQO-E9k-E9kOOQO'#Cq'#CqOOQO'#Cs'#CsOOQO,5:V,5:VO8]QPO,5:VO8bQPO,5:XOOQO,5:W,5:WO8]QPO,5:WOOQO'#Ck'#CkOOQO'#Do'#DoOOQO'#Dq'#DqOOQO'#Ds'#DsOOQO'#Du'#DuOOQO'#Dw'#DwOwQPO'#DnO8gQPO'#DnOOQO'#Fq'#FqO8lQPO1G/nO9`QPO,5;uOOQO,5:k,5:kO9gQPO,5<OO9nQPO1G0PO:bQPO1G0PO:bQPO1G0POOQO'#Cz'#CzOOQO'#C}'#C}OOQO'#DW'#DWOOQO'#DP'#DPO;VQPO,59}OOQO'#D['#D[OOQO'#D_'#D_OOQO'#Dd'#DdO;_QPO,59}OOQO'#El'#ElO;dQPO,5;VO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hOOQO,5:j,5:jO8]QPO,5:jO;lQPO,5;^OOQO'#Ev'#EvOOQO'#Ft'#FtO;sQPO,5;^O%ZQPO'#EuO#fQPO,5;kO<OQPO,5;vOOQO,5;[,5;[O<]QPO,5;yO<eQPO1G0ZO=YQPO'#E`O>TQPO1G0cOOQO'#Ei'#EiO>xQPO1G0lO>}QPO1G.eO@OQPO1G1nO@TQPO1G1oPAUQPO'#FmOOQO1G/q1G/qOOQO1G/s1G/sOOQO1G/r1G/rOAZQPO,5:YOwQPO,5:YOOQO-E9o-E9oOBbQPO1G1aOOQO1G1a1G1aOOQO1G1j1G1jOOQO,5<^,5<^OBlQPO7+%kOOQO-E9p-E9pOC`QPO7+%kOOQO,59k,59kODTQPO1G/iO.SQPO1G/iOOQO1G0q1G0qO;gQPO1G0qOGhQPO1G/SOGoQPO1G/SOKRQPO1G/SOK]QPO1G/SOOQO1G/S1G/SOOQO1G0U1G0UO;sQPO1G0xOOQO-E9r-E9rOOQO'#E{'#E{OOQO'#E}'#E}OOQO1G0x1G0xO;yQPO1G0xO%ZQPO'#EzOKgQPO,5;aOKnQPO1G1VOKsQPO1G1bOOQO1G1b1G1bOKzQPO1G1bO%ZQPO1G1bOOQO'#Fb'#FbOLPQPO1G1eOLUQPO7+%uOLxQPO7+%uOOQO'#Eb'#EbOOQO'#Ed'#EdOOQO,5:z,5:zON_QPO7+%}ONiQPO7+%}OOQO7+&W7+&WO!!WQPO'#CcO!!_QPO'#CjO$[QPO'#CiO/bQPO'#CxOOQO'#F|'#F|OOQO'#F{'#F{O!!gQPO'#FnO!#nQPO7+$PO!#uQPO'#CxO#fQPO7+'YONpQPO'#CcOOQO'#GW'#GWO!#zQPO'#FuO!%RQPO7+'ZOOQO'#Ct'#CtO%ZQPO1G/tO!%YQPO1G/tO!&aQPO7+&{O!&iQPO7+&{OOQO7+&{7+&{P!PQPO'#FrO!&pQPO<<IVO.SQPO7+%TO!'dQPO'#DfO!'nQPO7+%TOOQO7+&]7+&]OOQO7+&d7+&dO;yQPO7+&dO!'sQPO,5;fOOQO'#Ex'#ExO%ZQPO1G0{OOQO7+&q7+&qOOQO7+&|7+&|O!'zQPO7+&|O%ZQPO7+'PO!(RQPO<<IaOOQO,5<_,5<_O!(uQPO<<IiOOQO-E9q-E9qOOQO'#Ce'#CeO!)mQPO,59OOOQO'#Cm'#CmOOQO'#Co'#CoOOQO,59U,59UO!*wQPO,59TO;VQPO,5<PO!+PQPO,5<PO;dQPO,5<QO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO8]QPO,59dOOQO,5<Y,5<YOOQO-E9l-E9lOOQO<<Gk<<GkO%ZQPO,59dO!+UQPO<<JtOOQO,5<a,5<aOOQO-E9s-E9sOOQO<<Ju<<JuO!+ZQPO7+%`O%ZQPO7+%`OOQO-E9m-E9mO!,aQPO<<JgOOQO<<Jg<<JgO!,hQPO,5<ZO!,rQPO<<HoO!,wQPO,5:QO!-PQPO,5:QOOQO<<Ho<<HoOOQO<<JO<<JOO!-WQPO7+&gOOQO<<Jh<<JhO!-eQPO<<JkP2PQPO'#FsOOQO'#Cg'#CgOOQO'#Cf'#CfOOQO1G.j1G.jO$[QPO1G.oO8]QPO1G.oO!-lQPO1G1kO.SQPO1G1kOOQO1G1l1G1lO;gQPO1G1lO!.{QPO1G/PO!/SQPO1G/PO!0bQPO1G/PO!0lQPO1G/POOQO1G/P1G/POOQO1G/O1G/OO!0vQPO1G/OOOQOAN@`AN@`O!1vQPO<<HzP%ZQPO'#FoOOQOAN@RAN@ROOQOAN>ZAN>ZO!2|QPO1G/lOOQOAN@VAN@VO!3TQPO'#CvOOQO7+$Z7+$ZO!*zQPO7+$ZO.SQPO7+'VO!3YQPO7+'VOOQO7+'W7+'WO$[QPO,59bO$[QPO<<GuO!3_QPO<<JqOOQO<<Jq<<JqOOQO1G.|1G.|OOQOAN=aAN=aOOQOAN@]AN@]",
|
|
1321
|
+
stateData: "!3i~O$lOSPOS~OUPO!^QO!oSO!vUO!yVO#OXO#RYO#[[O$_aO~OThO$nkO~OToO}qO!PzO!QzO!StO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO$nrO~O!qsO~P!PO!{!_O~O#T!bO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~O$j!ZX$y!ZX$t!ZX~P#fOThO~O$y!fOU$`X!^$`X!o$`X!v$`X!y$`X#O$`X#R$`X#[$`X$_$`X$j$`X~O$y!fO~OToO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO$nrO~O$m!jOTVX`VXfVX!^VX!dVX!fVX!hVX!jVX!lVX!oVX!vVX!yVX#OVX#RVX#[VX$jVX$yVX$tVXkVX}VX!PVX!QVX#TVX#cVX#hVX#uVX#vVX#yVX#{VX$QVX$TVX$VVX$rVX~O$nVXiVX~P&[OT!kO~OT!nOf!mO`!_X!^!_X!d!_X!f!_X!h!_X!j!_X!l!_X!o!_X!v!_X!y!_X#O!_X#R!_X#[!_X$j!_X$y!_X$t!_Xi!_X~OT!nOf!mO`!`X!^!`X!d!`X!f!`X!h!`X!j!`X!l!`X!o!`X!v!`X!y!`X#O!`X#R!`X#[!`X$j!`X$y!`X$t!`Xi!`X~O`!tO!d!uO!f!vO!h!wO!j!xO!l!yO~O!^![a!o![a!v![a!y![a#O![a#R![a#[![a$j![a$y![a$t![a~P+cO$n#OOoVXrVXtVXuVXvVXwVXxVXyVX{VX!SVX!TVX!UVX!XVX#aVX#kVX#mVX#rVX#pVX~P&[OT!kO!S#PO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~P%ZO$r#RO!^!ma!o!ma!v!ma!y!ma#O!ma#R!ma#[!ma$j!ma$y!ma$t!ma~Of!mOk#XOo#UOr#VOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_O~OT!nO!^!rX!o!rX!v!rX!y!rX#O!rX#R!rX#[!rX$j!rX$r!rX$y!rX$t!rX~P/bO#k#iO~P%ZO$n#mO~O$n#nO~O$n#pO~OT#rO#T#rO~O#^#tO!^#Ya!o#Ya!v#Ya!y#Ya#O#Ya#R#Ya#[#Ya$j#Ya$y#Ya$t#Ya~Of!mO$n#vO~O$n#xO~O!^!ta!o!ta!v!ta!y!ta#O!ta#R!ta#[!ta$j!ta$y!ta$t!ta~P/eO!^!|a!o!|a!v!|a!y!|a#O!|a#R!|a#[!|a$j!|a$y!|a$t!|a~P/eO$m!jOTVa`VafVa!^Va!dVa!fVa!hVa!jVa!lVa!oVa!vVa!yVa#OVa#RVa#[Va$jVa$yVakVaoVarVatVauVavVawVaxVayVa{Va}Va!PVa!QVa!SVa!TVa!UVa!XVa#aVa$rVa$nVa$tVa#kVaiVa#mVa#rVa#pVa#TVa#cVa#hVa#uVa#vVa#yVa#{Va$QVa$TVa$VVa~OT!nO~O$t#{O~O`!tO~O!^![i!o![i!v![i!y![i#O![i#R![i#[![i$j![i$y![i$t![i~P+cO$t$RO~P%ZO$t$SO~P/eO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~P!PO$r$UO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~O{#WO!X#]O~O$n$ZO~O}qO#cyO~O#k#iO~P/eO#k#iO#p$fO#r$gO~O!S$oO!qsO$t$nO~P%ZOT$qO#v$qO~O$r$sO!^!wi!o!wi!v!wi!y!wi#O!wi#R!wi#[!wi$j!wi$y!wi$t!wi~O#V$uO#X$vO!^#SX!o#SX!v#SX!y#SX#O#SX#R#SX#[#SX$j#SX$r#SX$y#SX$t#SX~O$r$xO!^#Pi!o#Pi!v#Pi!y#Pi#O#Pi#R#Pi#[#Pi$j#Pi$y#Pi$t#Pi~O#T$zO~OT${O`!tO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO~O$n%UO~OT%VO`!tO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO~O$m!jO~Oi%ZO`!ba!^!ba!d!ba!f!ba!h!ba!j!ba!l!ba!o!ba!v!ba!y!ba#O!ba#R!ba#[!ba$j!ba$y!ba$t!ba~O$r%_O$t%`O~P/eO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~P!PO$r%bO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~O$n%cO~Ok#XOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_OTpifpiopi!^pi!opi!vpi!ypi#Opi#Rpi#[pi$jpi$rpi$ypi$tpi#kpi#mpi#rpi`pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~Or#VO~PDYOrpi~PDYO!S#[O!T#[O!U#[OTpifpikpiopirpitpiupivpiwpixpiypi{pi}pi!Xpi!^pi!opi!vpi!ypi#Opi#Rpi#[pi#api$jpi$rpi$ypi$tpi#kpi#mpi#rpi`pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~O!P#ZO!Q#ZO~PGvO!Ppi!Qpi~PGvO#m%jO~P/eO$t%lO~O$t%mO~P/eO$t%mO~O!^QO~O!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~P!PO$r%pO!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~O!^#Pq!o#Pq!v#Pq!y#Pq#O#Pq#R#Pq#[#Pq$j#Pq$y#Pq$t#Pq~OT#rO#T#rO~PMmO$r%rO~PMmO$m!jO$n#OOfVXkVXoVXrVXtVXuVXvVXwVXxVXyVX{VX}VX!PVX!QVX!SVX!TVX!UVX!XVX#aVX$ZgX~OT%tO~PNpOb%vOd%wO~O$r&TOT$bX`$bX}$bX!P$bX!Q$bX#T$bX#c$bX#h$bX#u$bX#v$bX#y$bX#{$bX$Q$bX$T$bX$V$bX$t$bX~O$t&VO~P>}O$Z&WO~O$r&YOT$iX`$iX}$iX!P$iX!Q$iX#T$iX#c$iX#h$iX#u$iX#v$iX#y$iX#{$iX$Q$iX$T$iX$V$iX$t$iX~O$t&[O~P@TOi%ZO`!bi!^!bi!d!bi!f!bi!h!bi!j!bi!l!bi!o!bi!v!bi!y!bi#O!bi#R!bi#[!bi$j!bi$y!bi$t!bi~O$r&`O$t&aO~O$t&aO~P%ZO!^!my!o!my!v!my!y!my#O!my#R!my#[!my$j!my$y!my$t!my~P!PO$r&eO$t!YX~P/eO$t&fO~O#r#na~P/eO$t&iO~P/eO!^!wy!o!wy!v!wy!y!wy#O!wy#R!wy#[!wy$j!wy$y!wy$t!wy~P!POT#rO#T#rO!^#Py!o#Py!v#Py!y#Py#O#Py#R#Py#[#Py$j#Py$y#Py$t#Py~O[&lOTWa`Wa}Wa!PWa!QWa#TWa#cWa#hWa#uWa#vWa#yWa#{Wa$QWa$TWa$VWa$rWa$tWa~Of!mOi%ZO~O$n&rO~O$t&|O~O`!bq!^!bq!d!bq!f!bq!h!bq!j!bq!l!bq!o!bq!v!bq!y!bq#O!bq#R!bq#[!bq$j!bq$y!bq$t!bq~P/eO$t'PO~P%ZO$r$ca$t$ca~P/eO$t'QO~O$r'RO$t!Ya~O$t!Ya~P%ZO#k#iq#p#iq#r#iq~P/eO$t'SO~P/eO$n'WO~Ok#XOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_Ofmiomi~Or#VO~P!-qOrmi~P!-qO!S#[O!T#[O!U#[Ofmikmiomirmitmiumivmiwmixmiymi{mi}mi!Xmi#ami~O!P#ZO!Q#ZO~P!/ZO!Pmi!Qmi~P!/ZOTli`li#Tli#cli#hli#uli#vli#yli#{li$Qli$Tli$Vli$rli$tli~P/eO`!by!^!by!d!by!f!by!h!by!j!by!l!by!o!by!v!by!y!by#O!by#R!by#[!by$j!by$y!by$t!by~P/eO$t!Yi~P%ZOk'ZO~O$t'^O~O$t'aO~OP!T!S!Q~",
|
|
1322
|
+
goto: "H}${PPP$|%QPP%U&n&r&u&xP&{'T'ZP'hP'hP'kP'}(mP(yP&{)P)VP)m*lP+UPPPPPP+pP,^P.RPP.oPPP)m/_P0P0]0w1UP1f1f1k2o2sP2sP2sP2sP2sP0w2wP3UP3[3m0w3xP0w4VP4dP0w4jP0w4wP5UP5^P5^P0w5aP5nP)m5qP6]P7l8o7l9rP:u:{P;RP;U;[P;`P7l;jPP<m=pP=pP<m>s>s?vP7l@yPA|P1p)P)PP$|$|BPPBTBZCmCsC}D^DdDrDxESPPPPPEYE^EdPGkPGt7l>s)mPHyTcOdT`OdUjR!z$O#Q!WTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WQ!d`Q!ebQ%y$}S'T&o'[R'_'ZT%Q#v%SR%u${R&n%uR&m%uS%Q#v%ST%W#x%YX$}#v#x%S%YS!zn!}Q$O!{X$|#v#x%S%YR%x$|Q!pjQ!slQ#gxQ#w!dQ&S%OR&p%yQ!ojQ!rlQ#fxQ#z!pQ#|!sQ$c#gW%T#v#x%S%YQ&z&SR'V&pQ%[#}Q&^%]Q&o%yR'['VQ'U&oR'`'[X%P#v#x%S%Yr#ax!h!i#Q#h$Q$k$m%d%i%n&]&b&h&j&{&}R%}%O!y![Tfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'Wv#bx!h!i#Q#h$Q$^$k$m%d%i%n&]&b&h&j&u&{&}R&O%Oz#cx!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}R&P%O|#Xx!h!i#Q#h$Q$^$_$k$m%O%d%i%n&]&b&h&j&u&v&{&}T$X#Y%z#QzTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'Wz#Yx!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}Q$]#`Q%z%OR&t%|!O#dx!h!i#Q#h$Q$^$_$`$k$m%d%i%n&]&b&h&j&u&v&w&{&}R&Q%O!S#ex!h!i#Q#h$Q$^$_$`$a$k$m%d%i%n&]&b&h&j&u&v&w&x&{&}R&R%Oz#^x!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}Q$Y#YQ%{%OR&q%zQ%e$ZQ&c%cQ'X&rR']'WSeOdS!qkrQ$l#mQ%e$ZQ&X%UQ&c%cQ'X&rR']'Wg^O_dkr#m$Z%U%c&r'WfRO_dkr#m$Z%U%c&r'WR%o$rVmR!z$OUlR!z$O!y!ZTfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WT!|n!}T!{n!}gTO_dkr#m$Z%U%c&r'WQwTR$p#nQvTQ#TwQ#q!`]$T#R$U$s%a%b%pcuTw!`#R$U$s%a%b%pgfO_dkr#m$Z%U%c&r'WgWO_dkr#m$Z%U%c&r'WQ!`WR!aZggO_dkr#m$Z%U%c&r'WgZO_dkr#m$Z%U%c&r'WQ#s!aV%q$x%r&kR$w#rg]O_dkr#m$Z%U%c&r'WR#u!bz#`x!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}R%|%O#Q!STfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WQ$[#`Q%f$]Q&s%|R'Y&t#R!YTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!]Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R|Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WX#j|#h#k$dX#l|#h#k$dR%k$kQ$i#kR%h$dT$j#k$dQ$h#kS%g$d$iR&g%h#R!OTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!TTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!RTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!WTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!VTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!^Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WR$r#pTbOdQdOR!gd#QiR`bfgr|!]!z#O#a#b#c#d#e#l#n#v#x$O$Z$j$p$}%S%Y%[%_%c%k%o%}&O&P&Q&R&W&^&`&e&o&r'O'R'W'Z'[bpTw!`#R$U$s%a%b%pT!lipQ%S#vR&U%SQ%^$QS&_%^&dR&d%dd_Odkr#m$Z%U%c&r'WR!c_Q!}nR$P!}Q#SvU$V#S$W$tQ$W#TR$t#qQ$y#sR%s$yQ#k|Q$d#hT$e#k$dQ%Y#xR&Z%YT%R#v%SX%O#v#x%S%YbxTw!`#R$U$s%a%b%pQ!hfQ!igQ#QrQ#h|Q#o!]Q$Q#OQ$^#aQ$_#bQ$`#cQ$a#dQ$b#eQ$k#lQ$m#nW%d$Z%c&r'WQ%i$jQ%n$pQ&]%[Y&b%_&`&e'O'RQ&h%kQ&j%oQ&u%}Q&v&OQ&w&PQ&x&QQ&y&RQ&{&WR&}&^QnRQ#}!zR%]$O!x![Tfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WX%P#v#x%S%YT%X#x%Y",
|
|
1323
|
+
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table Ref ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw join Kw one Kw many Kw as Alias Kw on BinaryExpression = ComputedDef BinaryExpression Kw or BinaryExpression Kw and ComparisonOp != <> < > <= >= Kw like Kw not AddOp + - MulOp * / % InExpression Kw in InValueList QueryStatement FromClause Kw from TableName Subquery SubqueryExpression JoinClause Kw inner Kw left Kw right Kw full Kw cross SelectClause Kw select Kw distinct SelectItem Wildcard WhereClause Kw where GroupByClause Kw group Kw by HavingClause Kw having OrderByClause Kw order OrderItem Number Kw asc Kw desc LimitClause Kw limit Kw offset NullTestExpression Kw is Kw null UnaryExpression UnaryOperator CaseExpression Kw case WhenClause Kw when Kw then ElseClause Kw else Kw end ExistsExpression Kw exists String Boolean Kw true Kw false Null FunctionCall Count Kw count ExtractExpression Kw extract ExtractUnit Param Parenthetical InExpression NullTestExpression : ViewStatement ExtendStatement Kw extend",
|
|
1324
|
+
maxTerm: 180,
|
|
1034
1325
|
nodeProps: [
|
|
1035
|
-
["group", -
|
|
1326
|
+
["group", -12, 7, 97, 112, 114, 127, 130, 131, 136, 137, 138, 141, 145, "Expression Expression", -9, 26, 29, 32, 53, 63, 107, 146, 147, 148, "Expression", -2, 61, 62, "TablePrimary"]
|
|
1036
1327
|
],
|
|
1037
1328
|
skippedNodes: [0, 1],
|
|
1038
|
-
repeatNodeCount:
|
|
1039
|
-
tokenData: "*
|
|
1329
|
+
repeatNodeCount: 10,
|
|
1330
|
+
tokenData: "*o~RoX^#Spq#Sqr#wrs$Stu$quv%cwx%hxy&Qyz&Vz{&[{|&a|}&f}!O&k!O!P'[!P!Q'a!Q![(p![!])Z!]!^)`!^!_)e!_!`)z!`!a*P!c!}*^#T#o*^#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#XY$l~X^#Spq#S#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#zP!_!`#}~$SOt~~$VTOr$Srs$fs;'S$S;'S;=`$k<%lO$S~$kO#v~~$nP;=`<%l$S~$tS!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%VS$V~!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%hO!U~~%kTOw%hwx$fx;'S%h;'S;=`%z<%lO%h~%}P;=`<%l%h~&VO$n~~&[O$t~~&aO!S~~&fO!P~~&kO$r~~&pP!Q~}!O&s~&xSP~OY&sZ;'S&s;'S;=`'U<%lO&s~'XP;=`<%l&s~'aO$m~~'fP!T~z{'i~'lTOz'iz{'{{;'S'i;'S;=`(j<%lO'i~(OVOz'iz{'{{!P'i!P!Q(e!Q;'S'i;'S;=`(j<%lO'i~(jOP~~(mP;=`<%l'i~(uQ#T~!O!P({!Q![(p~)OP!Q![)R~)WP#T~!Q![)R~)`O$Z~~)eO$y~~)jQv~!_!`)p!`!a)u~)uOx~~)zOu~~*POk~~*UPw~!_!`*X~*^Oy~~*cST~!Q![*^!c!}*^#R#S*^#T#o*^",
|
|
1040
1331
|
tokenizers: [0],
|
|
1041
1332
|
topRules: { "Program": [0, 2] },
|
|
1042
1333
|
specialized: [{ term: 5, get: (value, stack) => specializeIdentifier(value, stack) << 1, external: specializeIdentifier }, { term: 5, get: (value) => spec_Identifier[value] || -1 }],
|
|
1043
|
-
tokenPrec:
|
|
1334
|
+
tokenPrec: 2964
|
|
1044
1335
|
});
|
|
1045
1336
|
}
|
|
1046
1337
|
});
|
|
@@ -1203,6 +1494,7 @@ function isFence(event) {
|
|
|
1203
1494
|
var COMPONENT_ATTRIBUTE_KEYS, GSQL_FENCE, COMPONENT_TAG, ATTRIBUTE;
|
|
1204
1495
|
var init_markdown = __esm({
|
|
1205
1496
|
"../lang/markdown.ts"() {
|
|
1497
|
+
"use strict";
|
|
1206
1498
|
init_parser();
|
|
1207
1499
|
COMPONENT_ATTRIBUTE_KEYS = ["x", "y", "y2", "series", "value", "category"];
|
|
1208
1500
|
GSQL_FENCE = /^([ \t]*)(`{3,})g?sql[^\n]*\n([\s\S]*?)^\1\2[ \t]*$/gim;
|
|
@@ -1211,6 +1503,74 @@ var init_markdown = __esm({
|
|
|
1211
1503
|
}
|
|
1212
1504
|
});
|
|
1213
1505
|
|
|
1506
|
+
// ../lang/snowflake.ts
|
|
1507
|
+
function uppercaseMalloyQuery(query) {
|
|
1508
|
+
query.baseTableName = uppercaseIdentifier(query.baseTableName);
|
|
1509
|
+
for (let stage of query.pipeline || []) {
|
|
1510
|
+
let fields = stage.queryFields || [];
|
|
1511
|
+
fields.forEach((field) => uppercaseColumnField(field));
|
|
1512
|
+
let filters = stage.filterList || [];
|
|
1513
|
+
filters.forEach((filter) => uppercaseExpression(filter?.e));
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function uppercaseTable(table2) {
|
|
1517
|
+
if (table2.upperCased) return;
|
|
1518
|
+
table2.upperCased = true;
|
|
1519
|
+
table2.name = uppercaseIdentifier(table2.name);
|
|
1520
|
+
if (table2.primaryKey) table2.primaryKey = uppercaseIdentifier(table2.primaryKey);
|
|
1521
|
+
if (table2.tableName) table2.tableName = uppercaseQualified(table2.tableName);
|
|
1522
|
+
if (table2.tablePath) table2.tablePath = uppercaseQualified(table2.tablePath);
|
|
1523
|
+
table2.fields?.forEach((field) => uppercaseField(field));
|
|
1524
|
+
if (table2.query) uppercaseMalloyQuery(table2.query);
|
|
1525
|
+
}
|
|
1526
|
+
function uppercaseField(field) {
|
|
1527
|
+
if (!field) return;
|
|
1528
|
+
if (isJoinField(field)) {
|
|
1529
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1530
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1531
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1532
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1533
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1534
|
+
if (field.onExpression) uppercaseExpression(field.onExpression);
|
|
1535
|
+
uppercaseTable(field);
|
|
1536
|
+
} else {
|
|
1537
|
+
uppercaseColumnField(field);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function uppercaseColumnField(field) {
|
|
1541
|
+
if (!field) return;
|
|
1542
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1543
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1544
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1545
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1546
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1547
|
+
if (field.e) uppercaseExpression(field.e);
|
|
1548
|
+
}
|
|
1549
|
+
function uppercaseExpression(expr) {
|
|
1550
|
+
if (!expr) return;
|
|
1551
|
+
walkExpression(expr, (node) => {
|
|
1552
|
+
if (Array.isArray(node.path)) node.path = node.path.map(uppercaseIdentifier);
|
|
1553
|
+
if (Array.isArray(node.structPath)) node.structPath = node.structPath.map(uppercaseIdentifier);
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
function uppercaseIdentifier(value) {
|
|
1557
|
+
if (!value) return value || "";
|
|
1558
|
+
return value.toString().toUpperCase();
|
|
1559
|
+
}
|
|
1560
|
+
function uppercaseQualified(value) {
|
|
1561
|
+
if (!value) return value;
|
|
1562
|
+
return value.split(".").map((part) => part.startsWith('"') && part.endsWith('"') ? part : uppercaseIdentifier(part)).join(".");
|
|
1563
|
+
}
|
|
1564
|
+
function isJoinField(field) {
|
|
1565
|
+
return !!field?.join;
|
|
1566
|
+
}
|
|
1567
|
+
var init_snowflake = __esm({
|
|
1568
|
+
"../lang/snowflake.ts"() {
|
|
1569
|
+
"use strict";
|
|
1570
|
+
init_util();
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1214
1574
|
// ../lang/core.ts
|
|
1215
1575
|
import { registerDialect, StandardSQLDialect, QueryModel, expandBlueprintMap } from "@graphenedata/malloy";
|
|
1216
1576
|
import { readFile } from "node:fs/promises";
|
|
@@ -1225,15 +1585,19 @@ function getDiagnostics() {
|
|
|
1225
1585
|
async function loadWorkspace(dir, includeMd) {
|
|
1226
1586
|
let files = await glob(includeMd ? "**/*.{gsql,md}" : "**/*.gsql", { cwd: dir, ignore: ["node_modules/**"] });
|
|
1227
1587
|
for await (let file of files) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1588
|
+
try {
|
|
1589
|
+
let contents = await readFile(path2.join(dir, file), "utf-8");
|
|
1590
|
+
updateFile(contents, file);
|
|
1591
|
+
} catch (e) {
|
|
1592
|
+
console.error("Failed to read file", file, e.message);
|
|
1593
|
+
}
|
|
1230
1594
|
}
|
|
1231
1595
|
}
|
|
1232
|
-
function updateFile(contents,
|
|
1233
|
-
FILE_MAP[
|
|
1234
|
-
FILE_MAP[
|
|
1235
|
-
FILE_MAP[
|
|
1236
|
-
return FILE_MAP[
|
|
1596
|
+
function updateFile(contents, path10) {
|
|
1597
|
+
FILE_MAP[path10] ||= { path: path10, contents, tree: null, tables: [], queries: [] };
|
|
1598
|
+
FILE_MAP[path10].contents = contents;
|
|
1599
|
+
FILE_MAP[path10].tree = null;
|
|
1600
|
+
return FILE_MAP[path10];
|
|
1237
1601
|
}
|
|
1238
1602
|
function analyze(contents, type) {
|
|
1239
1603
|
clearDiagnostics();
|
|
@@ -1246,6 +1610,7 @@ function analyze(contents, type) {
|
|
|
1246
1610
|
recordSyntaxErrors(fi);
|
|
1247
1611
|
findTables(fi);
|
|
1248
1612
|
});
|
|
1613
|
+
Object.values(FILE_MAP).forEach(applyExtends);
|
|
1249
1614
|
Object.values(FILE_MAP).flatMap((f) => f.tables).forEach(analyzeTable);
|
|
1250
1615
|
if (contents) {
|
|
1251
1616
|
let fi = FILE_MAP["input"];
|
|
@@ -1258,13 +1623,21 @@ function analyze(contents, type) {
|
|
|
1258
1623
|
}
|
|
1259
1624
|
function toSql(query, params = {}) {
|
|
1260
1625
|
if (query.rawSql) return query.rawSql;
|
|
1261
|
-
let contents = {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1626
|
+
let contents = Object.fromEntries(Object.values(FILE_MAP).flatMap((fi) => {
|
|
1627
|
+
return fi.tables.map((t) => {
|
|
1628
|
+
t = structuredClone(t);
|
|
1629
|
+
if (fi.path == "input" && t.query) fillInParams(t.query, params);
|
|
1630
|
+
if (config.dialect == "snowflake") uppercaseTable(t);
|
|
1631
|
+
return [t.name, t];
|
|
1632
|
+
});
|
|
1633
|
+
}));
|
|
1634
|
+
query = structuredClone(query);
|
|
1635
|
+
fillInParams(query, params);
|
|
1636
|
+
if (config.dialect == "snowflake") uppercaseMalloyQuery(query);
|
|
1637
|
+
let tableQueries = Object.values(contents).map((t) => t.query);
|
|
1638
|
+
let joinQueries = Object.values(contents).flatMap((t) => t.fields.map((f) => f.query));
|
|
1639
|
+
let allQueries = [...tableQueries, ...joinQueries, query].filter((q) => !!q);
|
|
1640
|
+
allQueries.forEach((q) => q.structRef = contents[q.baseTableName]);
|
|
1268
1641
|
let qm = new QueryModel({
|
|
1269
1642
|
name: "generated_model",
|
|
1270
1643
|
contents,
|
|
@@ -1272,18 +1645,20 @@ function toSql(query, params = {}) {
|
|
|
1272
1645
|
dependencies: {},
|
|
1273
1646
|
exports: []
|
|
1274
1647
|
});
|
|
1275
|
-
return qm.compileQuery(
|
|
1648
|
+
return qm.compileQuery(query).sql;
|
|
1276
1649
|
}
|
|
1277
1650
|
var BigQueryDialect;
|
|
1278
1651
|
var init_core = __esm({
|
|
1279
1652
|
"../lang/core.ts"() {
|
|
1653
|
+
"use strict";
|
|
1280
1654
|
init_analyze();
|
|
1281
1655
|
init_params();
|
|
1282
1656
|
init_util();
|
|
1283
1657
|
init_config();
|
|
1284
|
-
|
|
1658
|
+
init_functionDefs();
|
|
1285
1659
|
init_parser();
|
|
1286
1660
|
init_markdown();
|
|
1661
|
+
init_snowflake();
|
|
1287
1662
|
BigQueryDialect = class extends StandardSQLDialect {
|
|
1288
1663
|
constructor() {
|
|
1289
1664
|
super();
|
|
@@ -1349,6 +1724,7 @@ function printTable(rows) {
|
|
|
1349
1724
|
var styleText;
|
|
1350
1725
|
var init_printer = __esm({
|
|
1351
1726
|
"printer.ts"() {
|
|
1727
|
+
"use strict";
|
|
1352
1728
|
init_core();
|
|
1353
1729
|
styleText = (style, text) => {
|
|
1354
1730
|
try {
|
|
@@ -1361,21 +1737,20 @@ var init_printer = __esm({
|
|
|
1361
1737
|
});
|
|
1362
1738
|
|
|
1363
1739
|
// background.ts
|
|
1364
|
-
import { spawn } from "child_process";
|
|
1740
|
+
import { spawn, exec } from "child_process";
|
|
1741
|
+
import { promisify } from "util";
|
|
1365
1742
|
import { fileURLToPath } from "url";
|
|
1366
1743
|
import fs2 from "fs-extra";
|
|
1367
1744
|
import path3 from "path";
|
|
1368
1745
|
async function runServeInBackground() {
|
|
1369
|
-
let
|
|
1370
|
-
let grapheneCache = getGrapheneCache(root);
|
|
1746
|
+
let grapheneCache = getGrapheneCache(config.root);
|
|
1371
1747
|
let logFile = path3.join(grapheneCache, "serve.log");
|
|
1372
1748
|
await fs2.ensureDir(grapheneCache);
|
|
1373
|
-
await stopGrapheneIfRunning(root);
|
|
1374
1749
|
let log = fs2.openSync(logFile, "w");
|
|
1375
1750
|
let entryPoint = process.argv[1] || fileURLToPath(import.meta.url);
|
|
1376
|
-
let childArgs = [...process.execArgv, entryPoint, "serve"
|
|
1751
|
+
let childArgs = [...process.execArgv, entryPoint, "serve"];
|
|
1377
1752
|
let child = spawn(process.execPath, childArgs, {
|
|
1378
|
-
cwd: root,
|
|
1753
|
+
cwd: config.root,
|
|
1379
1754
|
detached: true,
|
|
1380
1755
|
env: { ...process.env },
|
|
1381
1756
|
stdio: ["ignore", log, log]
|
|
@@ -1394,7 +1769,6 @@ async function runServeInBackground() {
|
|
|
1394
1769
|
}
|
|
1395
1770
|
});
|
|
1396
1771
|
child.once("exit", () => {
|
|
1397
|
-
process.stdout.write(fs2.readFileSync(logFile));
|
|
1398
1772
|
reject(new Error("Exited before server started"));
|
|
1399
1773
|
});
|
|
1400
1774
|
child.once("error", (e) => reject(e));
|
|
@@ -1403,15 +1777,9 @@ async function runServeInBackground() {
|
|
|
1403
1777
|
function getGrapheneCache(root) {
|
|
1404
1778
|
return path3.join(root, "node_modules", ".graphene");
|
|
1405
1779
|
}
|
|
1406
|
-
function getPidFilePath(root) {
|
|
1407
|
-
return path3.join(getGrapheneCache(root), process.env.NODE_ENV == "test" ? "test.pid" : "serve.pid");
|
|
1408
|
-
}
|
|
1409
|
-
function targetPids(pid) {
|
|
1410
|
-
if (process.platform === "win32") return [pid];
|
|
1411
|
-
return [pid, -pid];
|
|
1412
|
-
}
|
|
1413
1780
|
function sendSignal(pid, signal) {
|
|
1414
|
-
|
|
1781
|
+
let pids = process.platform === "win32" ? [pid] : [pid, -pid];
|
|
1782
|
+
for (let target of pids) {
|
|
1415
1783
|
try {
|
|
1416
1784
|
process.kill(target, signal);
|
|
1417
1785
|
} catch (err) {
|
|
@@ -1422,244 +1790,83 @@ function sendSignal(pid, signal) {
|
|
|
1422
1790
|
}
|
|
1423
1791
|
return true;
|
|
1424
1792
|
}
|
|
1425
|
-
async function stopGrapheneIfRunning(
|
|
1426
|
-
|
|
1427
|
-
let
|
|
1428
|
-
let pid = await readPid(pidFile);
|
|
1793
|
+
async function stopGrapheneIfRunning() {
|
|
1794
|
+
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1795
|
+
let pid = await getPidOnPort(port);
|
|
1429
1796
|
if (!pid) return;
|
|
1430
1797
|
console.log(`Stopping server (${pid})`);
|
|
1431
1798
|
sendSignal(pid, "SIGTERM");
|
|
1432
1799
|
let end = Date.now() + 5e3;
|
|
1433
|
-
while (Date.now() < end
|
|
1800
|
+
while (Date.now() < end) {
|
|
1801
|
+
if (!await getPidOnPort(port)) break;
|
|
1434
1802
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1435
1803
|
}
|
|
1436
|
-
if (
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
let
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
async function isServerRunning() {
|
|
1449
|
-
let pidFile = getPidFilePath(config.root);
|
|
1450
|
-
let pid = await readPid(pidFile);
|
|
1451
|
-
if (!pid) return false;
|
|
1804
|
+
if (await getPidOnPort(port)) {
|
|
1805
|
+
sendSignal(pid, "SIGKILL");
|
|
1806
|
+
}
|
|
1807
|
+
if (await getPidOnPort(port)) {
|
|
1808
|
+
console.error("Failed to stop previous Graphene server");
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
async function isServerRunning(portOverride) {
|
|
1812
|
+
let port = portOverride || Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1813
|
+
return !!await getPidOnPort(port);
|
|
1814
|
+
}
|
|
1815
|
+
async function getPidOnPort(port) {
|
|
1452
1816
|
try {
|
|
1453
|
-
process.
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1817
|
+
if (process.platform === "win32") {
|
|
1818
|
+
let { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
|
|
1819
|
+
let lines = stdout.trim().split("\n");
|
|
1820
|
+
for (let line of lines) {
|
|
1821
|
+
let parts = line.trim().split(/\s+/);
|
|
1822
|
+
if (parts.length < 5) continue;
|
|
1823
|
+
let localAddress = parts[1];
|
|
1824
|
+
let pid = parseInt(parts[parts.length - 1], 10);
|
|
1825
|
+
if (localAddress.endsWith(`:${port}`)) {
|
|
1826
|
+
return pid;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
} else {
|
|
1830
|
+
return new Promise((resolve) => {
|
|
1831
|
+
let child = spawn("lsof", ["-i", `:${port}`, "-t", "-sTCP:LISTEN"]);
|
|
1832
|
+
let stdout = "";
|
|
1833
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
1834
|
+
child.on("close", (code) => {
|
|
1835
|
+
if (code !== 0) return resolve(void 0);
|
|
1836
|
+
let pid = parseInt(stdout.trim(), 10);
|
|
1837
|
+
resolve(isNaN(pid) ? void 0 : pid);
|
|
1838
|
+
});
|
|
1839
|
+
child.on("error", () => resolve(void 0));
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
} catch (e) {
|
|
1843
|
+
console.warn("Failed to check for server:", e.message);
|
|
1844
|
+
return void 0;
|
|
1458
1845
|
}
|
|
1846
|
+
return void 0;
|
|
1459
1847
|
}
|
|
1848
|
+
var execAsync;
|
|
1460
1849
|
var init_background = __esm({
|
|
1461
1850
|
"background.ts"() {
|
|
1851
|
+
"use strict";
|
|
1462
1852
|
init_config();
|
|
1853
|
+
execAsync = promisify(exec);
|
|
1463
1854
|
}
|
|
1464
1855
|
});
|
|
1465
1856
|
|
|
1466
|
-
//
|
|
1467
|
-
var
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
var BigQueryConnection;
|
|
1473
|
-
var init_bigQuery = __esm({
|
|
1474
|
-
"connections/bigQuery.ts"() {
|
|
1475
|
-
init_config();
|
|
1476
|
-
BigQueryConnection = class {
|
|
1477
|
-
client;
|
|
1478
|
-
constructor(options = {}) {
|
|
1479
|
-
options.projectId ||= config.bigquery?.projectId;
|
|
1480
|
-
if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
|
|
1481
|
-
let parsed = JSON.parse(process.env.GOOGLE_CREDENTIALS_CONTENT);
|
|
1482
|
-
options.projectId = parsed.project_id;
|
|
1483
|
-
options.credentials = parsed;
|
|
1484
|
-
}
|
|
1485
|
-
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
1486
|
-
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
1487
|
-
}
|
|
1488
|
-
async runQuery(sql) {
|
|
1489
|
-
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false });
|
|
1490
|
-
let [rows] = await job.getQueryResults({ maxResults: 1e4 });
|
|
1491
|
-
let metadata = job.metadata || (await job.getMetadata())[0];
|
|
1492
|
-
let totalRows = Number(metadata?.statistics?.query?.totalRows ?? rows.length);
|
|
1493
|
-
rows.forEach((r) => {
|
|
1494
|
-
Object.entries(r).forEach(([k, v]) => {
|
|
1495
|
-
if (v instanceof BigQueryTimestamp) r[k] = v.value;
|
|
1496
|
-
if (v instanceof BigQueryDate) r[k] = v.value;
|
|
1497
|
-
});
|
|
1498
|
-
});
|
|
1499
|
-
return { rows, totalRows };
|
|
1500
|
-
}
|
|
1501
|
-
};
|
|
1857
|
+
// mockFiles.ts
|
|
1858
|
+
var mockFileMap;
|
|
1859
|
+
var init_mockFiles = __esm({
|
|
1860
|
+
"mockFiles.ts"() {
|
|
1861
|
+
"use strict";
|
|
1862
|
+
mockFileMap = {};
|
|
1502
1863
|
}
|
|
1503
1864
|
});
|
|
1504
1865
|
|
|
1505
|
-
//
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
DuckDBConnection: () => DuckDBConnection
|
|
1509
|
-
});
|
|
1510
|
-
import { promises as fs3 } from "fs";
|
|
1866
|
+
// check.ts
|
|
1867
|
+
import fs3 from "fs-extra";
|
|
1868
|
+
import os from "os";
|
|
1511
1869
|
import path4 from "path";
|
|
1512
|
-
import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue } from "@duckdb/node-api";
|
|
1513
|
-
var DuckDBConnection;
|
|
1514
|
-
var init_duckdb = __esm({
|
|
1515
|
-
"connections/duckdb.ts"() {
|
|
1516
|
-
init_config();
|
|
1517
|
-
DuckDBConnection = class {
|
|
1518
|
-
options;
|
|
1519
|
-
ready;
|
|
1520
|
-
connection = null;
|
|
1521
|
-
constructor(options) {
|
|
1522
|
-
this.options = options || {};
|
|
1523
|
-
this.ready = this.initialize();
|
|
1524
|
-
}
|
|
1525
|
-
async initialize() {
|
|
1526
|
-
let dbPath = this.options.path;
|
|
1527
|
-
if (!dbPath) {
|
|
1528
|
-
let files = await fs3.readdir(config.root);
|
|
1529
|
-
dbPath = files.find((f) => f.endsWith(".duckdb"));
|
|
1530
|
-
if (!dbPath) throw new Error("No .duckdb file found in current directory");
|
|
1531
|
-
dbPath = path4.resolve(config.root, dbPath);
|
|
1532
|
-
}
|
|
1533
|
-
let db = await DuckDBInstance.create(":memory:");
|
|
1534
|
-
this.connection = await db.connect();
|
|
1535
|
-
let escapedPath = dbPath.replace(/'/g, "''");
|
|
1536
|
-
await this.connection.run(`attach '${escapedPath}' as graphene_cli (READ_ONLY);`);
|
|
1537
|
-
await this.connection.run("use graphene_cli;");
|
|
1538
|
-
}
|
|
1539
|
-
async runQuery(sql) {
|
|
1540
|
-
await this.ready;
|
|
1541
|
-
let reader = await this.connection.runAndReadAll(sql);
|
|
1542
|
-
let rows = reader.getRowObjects().map((record) => {
|
|
1543
|
-
let out = {};
|
|
1544
|
-
for (let [k, v] of Object.entries(record)) {
|
|
1545
|
-
if (typeof v === "bigint") out[k] = Number(v);
|
|
1546
|
-
else if (v === null) out[k] = null;
|
|
1547
|
-
else if (v instanceof DuckDBTimestampValue) out[k] = new Date(Number(v.micros / 1000n)).toUTCString();
|
|
1548
|
-
else if (v instanceof DuckDBDateValue) out[k] = v.toString();
|
|
1549
|
-
else if (typeof v === "object") throw new Error(`Unsupported datatype ${v.constructor?.name}`);
|
|
1550
|
-
else out[k] = v;
|
|
1551
|
-
}
|
|
1552
|
-
return out;
|
|
1553
|
-
});
|
|
1554
|
-
return { rows };
|
|
1555
|
-
}
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1559
|
-
|
|
1560
|
-
// connections/snowflake.ts
|
|
1561
|
-
var snowflake_exports = {};
|
|
1562
|
-
__export(snowflake_exports, {
|
|
1563
|
-
SnowflakeConnection: () => SnowflakeConnection
|
|
1564
|
-
});
|
|
1565
|
-
import { createPrivateKey } from "node:crypto";
|
|
1566
|
-
import snowflake from "snowflake-sdk";
|
|
1567
|
-
var SnowflakeConnection;
|
|
1568
|
-
var init_snowflake = __esm({
|
|
1569
|
-
"connections/snowflake.ts"() {
|
|
1570
|
-
init_config();
|
|
1571
|
-
SnowflakeConnection = class {
|
|
1572
|
-
ready;
|
|
1573
|
-
connection;
|
|
1574
|
-
constructor(opts) {
|
|
1575
|
-
this.ready = this.initialize(opts || {});
|
|
1576
|
-
}
|
|
1577
|
-
async initialize(opts) {
|
|
1578
|
-
let privateKeyPath = process.env.SNOWFLAKE_PRI_KEY_PATH || config.snowflake?.privateKeyPath;
|
|
1579
|
-
let privateKeyPass = process.env.SNOWFLAKE_PRI_PASSPHRASE;
|
|
1580
|
-
let authOptions = {};
|
|
1581
|
-
if (privateKeyPath) {
|
|
1582
|
-
authOptions = { privateKeyPath, privateKeyPass };
|
|
1583
|
-
} else if (opts.privateKey) {
|
|
1584
|
-
let privateKey = createPrivateKey({ key: opts.privateKey, format: "pem", passphrase: privateKeyPass });
|
|
1585
|
-
authOptions = { privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) };
|
|
1586
|
-
}
|
|
1587
|
-
snowflake.configure({ logLevel: process.env.SNOWFLAKE_LOG_LEVEL || "WARN", logFilePath: "/dev/null" });
|
|
1588
|
-
this.connection = snowflake.createConnection({
|
|
1589
|
-
...opts,
|
|
1590
|
-
...config.snowflake || {},
|
|
1591
|
-
...authOptions,
|
|
1592
|
-
authenticator: "SNOWFLAKE_JWT",
|
|
1593
|
-
application: "Graphene"
|
|
1594
|
-
});
|
|
1595
|
-
await new Promise((resolve, reject) => {
|
|
1596
|
-
this.connection.connect((err, conn) => err ? reject(err) : resolve(conn));
|
|
1597
|
-
});
|
|
1598
|
-
}
|
|
1599
|
-
async runQuery(sql) {
|
|
1600
|
-
await this.ready;
|
|
1601
|
-
return await new Promise((resolve, reject) => {
|
|
1602
|
-
let rows = [];
|
|
1603
|
-
this.connection.execute({
|
|
1604
|
-
sqlText: sql,
|
|
1605
|
-
streamResult: true,
|
|
1606
|
-
complete: (error, statement) => {
|
|
1607
|
-
if (error) {
|
|
1608
|
-
reject(new Error(`Snowflake query failed: ${error.message || error}`));
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
let stream = statement.streamRows();
|
|
1612
|
-
stream.on("error", (err) => reject(err));
|
|
1613
|
-
stream.on("readable", function(row) {
|
|
1614
|
-
while ((row = this.read()) !== null) {
|
|
1615
|
-
rows.push(row);
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
stream.on("end", () => {
|
|
1619
|
-
let totalRows = Number(statement.getNumRows());
|
|
1620
|
-
resolve({ rows, totalRows });
|
|
1621
|
-
});
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1627
|
-
}
|
|
1628
|
-
});
|
|
1629
|
-
|
|
1630
|
-
// connections/index.ts
|
|
1631
|
-
async function getConnection() {
|
|
1632
|
-
if (config.dialect === "bigquery") {
|
|
1633
|
-
let mod = await Promise.resolve().then(() => (init_bigQuery(), bigQuery_exports));
|
|
1634
|
-
return new mod.BigQueryConnection();
|
|
1635
|
-
} else if (config.dialect === "duckdb") {
|
|
1636
|
-
let mod = await Promise.resolve().then(() => (init_duckdb(), duckdb_exports));
|
|
1637
|
-
return new mod.DuckDBConnection({});
|
|
1638
|
-
} else if (config.dialect === "snowflake") {
|
|
1639
|
-
let mod = await Promise.resolve().then(() => (init_snowflake(), snowflake_exports));
|
|
1640
|
-
return new mod.SnowflakeConnection({});
|
|
1641
|
-
} else {
|
|
1642
|
-
throw new Error(`Unsupported dialect: ${config.dialect}`);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
var init_connections = __esm({
|
|
1646
|
-
"connections/index.ts"() {
|
|
1647
|
-
init_config();
|
|
1648
|
-
}
|
|
1649
|
-
});
|
|
1650
|
-
|
|
1651
|
-
// mockFiles.ts
|
|
1652
|
-
var mockFileMap;
|
|
1653
|
-
var init_mockFiles = __esm({
|
|
1654
|
-
"mockFiles.ts"() {
|
|
1655
|
-
mockFileMap = {};
|
|
1656
|
-
}
|
|
1657
|
-
});
|
|
1658
|
-
|
|
1659
|
-
// check.ts
|
|
1660
|
-
import fs4 from "fs-extra";
|
|
1661
|
-
import os from "os";
|
|
1662
|
-
import path5 from "path";
|
|
1663
1870
|
import { spawn as spawn2 } from "child_process";
|
|
1664
1871
|
import { WebSocketServer } from "ws";
|
|
1665
1872
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -1676,7 +1883,7 @@ async function check(options) {
|
|
|
1676
1883
|
if (process.env.NODE_ENV == "test" && mockFileMap[mdFile]) {
|
|
1677
1884
|
updateFile(mockFileMap[mdFile], mdFile);
|
|
1678
1885
|
} else {
|
|
1679
|
-
let content = readFileSync2(
|
|
1886
|
+
let content = readFileSync2(path4.resolve(config.root, mdFile), "utf-8");
|
|
1680
1887
|
updateFile(content, mdFile);
|
|
1681
1888
|
}
|
|
1682
1889
|
}
|
|
@@ -1731,12 +1938,12 @@ async function check(options) {
|
|
|
1731
1938
|
}
|
|
1732
1939
|
if (resp?.screenshot) {
|
|
1733
1940
|
let filename = `graphene-screenshot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
|
|
1734
|
-
let screenshotPath =
|
|
1941
|
+
let screenshotPath = path4.join(os.tmpdir(), filename);
|
|
1735
1942
|
let base64Data = resp.screenshot.replace(/^data:image\/png;base64,/, "");
|
|
1736
|
-
await
|
|
1943
|
+
await fs3.writeFile(screenshotPath, base64Data, "base64");
|
|
1737
1944
|
log("Screenshot saved to", screenshotPath);
|
|
1738
1945
|
}
|
|
1739
|
-
return
|
|
1946
|
+
return errors.length == 0;
|
|
1740
1947
|
}
|
|
1741
1948
|
async function sendCheckRequest({ host, pageUrl, chart }) {
|
|
1742
1949
|
let abort = new AbortController();
|
|
@@ -1770,11 +1977,11 @@ function normalizeMdFile(mdFile) {
|
|
|
1770
1977
|
return clean;
|
|
1771
1978
|
}
|
|
1772
1979
|
let absolute = [
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
].find((p) =>
|
|
1980
|
+
path4.resolve(process.cwd(), clean),
|
|
1981
|
+
path4.resolve(config.root, clean)
|
|
1982
|
+
].find((p) => fs3.existsSync(p)) || null;
|
|
1776
1983
|
if (!absolute) return null;
|
|
1777
|
-
let relative =
|
|
1984
|
+
let relative = path4.relative(config.root, absolute);
|
|
1778
1985
|
return relative;
|
|
1779
1986
|
}
|
|
1780
1987
|
async function proxyCheckRequest(req, res) {
|
|
@@ -1833,6 +2040,7 @@ function checkVitePlugin() {
|
|
|
1833
2040
|
var browserConnections, pendingRequests;
|
|
1834
2041
|
var init_check = __esm({
|
|
1835
2042
|
"check.ts"() {
|
|
2043
|
+
"use strict";
|
|
1836
2044
|
init_core();
|
|
1837
2045
|
init_printer();
|
|
1838
2046
|
init_mockFiles();
|
|
@@ -1843,9 +2051,482 @@ var init_check = __esm({
|
|
|
1843
2051
|
}
|
|
1844
2052
|
});
|
|
1845
2053
|
|
|
1846
|
-
//
|
|
1847
|
-
import
|
|
2054
|
+
// auth.ts
|
|
2055
|
+
import fs4 from "node:fs/promises";
|
|
2056
|
+
import path5 from "path";
|
|
2057
|
+
import os2 from "os";
|
|
2058
|
+
import { spawn as spawn3 } from "child_process";
|
|
2059
|
+
import http from "http";
|
|
2060
|
+
async function readStore() {
|
|
2061
|
+
try {
|
|
2062
|
+
let txt2 = await fs4.readFile(credsPath, "utf8");
|
|
2063
|
+
return JSON.parse(txt2) || {};
|
|
2064
|
+
} catch {
|
|
2065
|
+
return {};
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
async function readEntry() {
|
|
2069
|
+
let store = await readStore();
|
|
2070
|
+
return store[config.root];
|
|
2071
|
+
}
|
|
2072
|
+
async function updateEntry(cred) {
|
|
2073
|
+
let store = await readStore();
|
|
2074
|
+
cred.refresh_token ||= store[config.root]?.refresh_token;
|
|
2075
|
+
cred.expires_at = Date.now() + cred.expires_in;
|
|
2076
|
+
store[config.root] = cred;
|
|
2077
|
+
await fs4.mkdir(path5.dirname(credsPath), { recursive: true, mode: 448 });
|
|
2078
|
+
await fs4.writeFile(credsPath, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
2079
|
+
if (process.platform !== "win32") {
|
|
2080
|
+
await fs4.chmod(credsPath, 384).catch(() => {
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function openInBrowser(url) {
|
|
2085
|
+
try {
|
|
2086
|
+
let plat = process.platform;
|
|
2087
|
+
let cmd = "xdg-open";
|
|
2088
|
+
if (plat == "darwin") cmd = "open";
|
|
2089
|
+
if (plat == "win32") cmd = "start";
|
|
2090
|
+
let p = spawn3(cmd, [url], { stdio: "ignore", shell: plat === "win32" });
|
|
2091
|
+
p.unref();
|
|
2092
|
+
} catch {
|
|
2093
|
+
console.log(`Open this URL to authenticate:
|
|
2094
|
+
${url}`);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
function base64url(buf) {
|
|
2098
|
+
let b64 = Buffer.from(buf).toString("base64");
|
|
2099
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
2100
|
+
}
|
|
2101
|
+
function randomBytes(len = 32) {
|
|
2102
|
+
return crypto.getRandomValues(new Uint8Array(len));
|
|
2103
|
+
}
|
|
2104
|
+
async function startLoopback() {
|
|
2105
|
+
let server = http.createServer();
|
|
2106
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
2107
|
+
let addr = server.address();
|
|
2108
|
+
if (!addr || typeof addr !== "object") throw new Error("Couldnt start oauth callback server");
|
|
2109
|
+
let redirectBase = `http://localhost:${addr.port}`;
|
|
2110
|
+
let waitForCode = new Promise((resolve) => {
|
|
2111
|
+
server.on("request", (req, res) => {
|
|
2112
|
+
let url = new URL(req.url || "/", redirectBase);
|
|
2113
|
+
if (url.pathname !== "/callback") {
|
|
2114
|
+
res.statusCode = 404;
|
|
2115
|
+
res.end("Not Found");
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
let code = url.searchParams.get("code") || "";
|
|
2119
|
+
let state = url.searchParams.get("state") || "";
|
|
2120
|
+
res.statusCode = 200;
|
|
2121
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2122
|
+
res.end("<html><body>Login complete. You may close this window.</body></html>");
|
|
2123
|
+
resolve({ code, state });
|
|
2124
|
+
});
|
|
2125
|
+
});
|
|
2126
|
+
return { url: redirectBase, waitForCode, close: () => server.close() };
|
|
2127
|
+
}
|
|
2128
|
+
async function loginPkce(opener) {
|
|
2129
|
+
let verifier = base64url(randomBytes(48));
|
|
2130
|
+
let data = new TextEncoder().encode(verifier);
|
|
2131
|
+
let digest = await crypto.subtle.digest("SHA-256", data);
|
|
2132
|
+
let code_challenge = base64url(digest);
|
|
2133
|
+
let state = base64url(randomBytes(16));
|
|
2134
|
+
let loop = await startLoopback();
|
|
2135
|
+
let redirect_uri = `${loop.url}/callback`;
|
|
2136
|
+
let authorizeUrl = new URL(`${config.host}/authenticate`);
|
|
2137
|
+
authorizeUrl.search = new URLSearchParams({
|
|
2138
|
+
redirect_uri,
|
|
2139
|
+
code_challenge,
|
|
2140
|
+
state,
|
|
2141
|
+
client_id: AUTH_CLIENT_ID,
|
|
2142
|
+
response_type: "code",
|
|
2143
|
+
code_challenge_method: "S256",
|
|
2144
|
+
scope: AUTH_SCOPES
|
|
2145
|
+
}).toString();
|
|
2146
|
+
if (opener) await opener(authorizeUrl.toString());
|
|
2147
|
+
else openInBrowser(authorizeUrl.toString());
|
|
2148
|
+
let result = await loop.waitForCode;
|
|
2149
|
+
if (!result.code) throw new Error("No authorization code received");
|
|
2150
|
+
if (result.state !== state) throw new Error("State mismatch");
|
|
2151
|
+
let res = await fetch(`${config.host}/_api/oauth2/token`, {
|
|
2152
|
+
method: "POST",
|
|
2153
|
+
headers: { "content-type": "application/json" },
|
|
2154
|
+
body: JSON.stringify({
|
|
2155
|
+
grant_type: "authorization_code",
|
|
2156
|
+
code: result.code,
|
|
2157
|
+
redirect_uri,
|
|
2158
|
+
client_id: AUTH_CLIENT_ID,
|
|
2159
|
+
code_verifier: verifier
|
|
2160
|
+
})
|
|
2161
|
+
});
|
|
2162
|
+
if (!res.ok) throw new Error(`token exchange failed: ${res.status}`);
|
|
2163
|
+
let tokenResp = await res.json();
|
|
2164
|
+
await updateEntry(tokenResp);
|
|
2165
|
+
}
|
|
2166
|
+
async function refreshAccessToken() {
|
|
2167
|
+
let refresh_token = (await readEntry())?.refresh_token;
|
|
2168
|
+
let res = await fetch(new URL("/_api/oauth2/token", config.host).toString(), {
|
|
2169
|
+
method: "POST",
|
|
2170
|
+
headers: { "content-type": "application/json" },
|
|
2171
|
+
body: JSON.stringify({ grant_type: "refresh_token", refresh_token, client_id: AUTH_CLIENT_ID })
|
|
2172
|
+
});
|
|
2173
|
+
if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
|
|
2174
|
+
let json = await res.json();
|
|
2175
|
+
await updateEntry(json);
|
|
2176
|
+
}
|
|
2177
|
+
async function authenticatedFetch(pathOrUrl, init = {}) {
|
|
2178
|
+
let entry = await readEntry();
|
|
2179
|
+
if (!entry) throw new Error("Not logged in; run `graphene login`");
|
|
2180
|
+
if (!entry.access_token || entry.expires_at < Date.now()) {
|
|
2181
|
+
await refreshAccessToken();
|
|
2182
|
+
entry = await readEntry();
|
|
2183
|
+
}
|
|
2184
|
+
let token = entry?.access_token;
|
|
2185
|
+
if (!token) throw new Error("Failed to obtain access token");
|
|
2186
|
+
let url = new URL(pathOrUrl, config.host);
|
|
2187
|
+
let headers = new Headers(init.headers || {});
|
|
2188
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
2189
|
+
let res = await fetch(url.toString(), { ...init, headers });
|
|
2190
|
+
if (res.status === 401 || res.status === 403) {
|
|
2191
|
+
await refreshAccessToken();
|
|
2192
|
+
token = (await readEntry())?.access_token;
|
|
2193
|
+
if (token) {
|
|
2194
|
+
headers.set("cookie", `access_token=${token}`);
|
|
2195
|
+
res = await fetch(url.toString(), { ...init, headers });
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
return res;
|
|
2199
|
+
}
|
|
2200
|
+
var AUTH_CLIENT_ID, AUTH_SCOPES, cfgDir, credsPath;
|
|
2201
|
+
var init_auth = __esm({
|
|
2202
|
+
"auth.ts"() {
|
|
2203
|
+
"use strict";
|
|
2204
|
+
init_config();
|
|
2205
|
+
AUTH_CLIENT_ID = "connected-app-test-4685a2a0-1cb9-4a81-a6bf-01a3efe7b981";
|
|
2206
|
+
AUTH_SCOPES = "offline_access";
|
|
2207
|
+
cfgDir = process.env.XDG_CONFIG_HOME || path5.join(os2.homedir(), ".config");
|
|
2208
|
+
credsPath = path5.join(cfgDir, "graphene", "credentials.json");
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
|
|
2212
|
+
// connections/bigQuery.ts
|
|
2213
|
+
var bigQuery_exports = {};
|
|
2214
|
+
__export(bigQuery_exports, {
|
|
2215
|
+
BigQueryConnection: () => BigQueryConnection
|
|
2216
|
+
});
|
|
2217
|
+
import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquery";
|
|
2218
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2219
|
+
function sqlStringLiteral(value) {
|
|
2220
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2221
|
+
}
|
|
2222
|
+
var BigQueryConnection;
|
|
2223
|
+
var init_bigQuery = __esm({
|
|
2224
|
+
"connections/bigQuery.ts"() {
|
|
2225
|
+
"use strict";
|
|
2226
|
+
init_config();
|
|
2227
|
+
BigQueryConnection = class {
|
|
2228
|
+
client;
|
|
2229
|
+
projectId;
|
|
2230
|
+
defaultNamespace;
|
|
2231
|
+
constructor(options = {}) {
|
|
2232
|
+
options.projectId ||= config.bigquery?.projectId;
|
|
2233
|
+
if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
|
|
2234
|
+
let parsed = JSON.parse(process.env.GOOGLE_CREDENTIALS_CONTENT);
|
|
2235
|
+
options.projectId = parsed.project_id;
|
|
2236
|
+
options.credentials = parsed;
|
|
2237
|
+
}
|
|
2238
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
2239
|
+
let tmp = JSON.parse(readFileSync3(process.env.GOOGLE_APPLICATION_CREDENTIALS, { encoding: "utf-8" }));
|
|
2240
|
+
options.projectId = tmp.project_id;
|
|
2241
|
+
}
|
|
2242
|
+
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
2243
|
+
this.projectId = options.projectId;
|
|
2244
|
+
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
2245
|
+
this.defaultNamespace = config.namespace;
|
|
2246
|
+
}
|
|
2247
|
+
async runQuery(sql) {
|
|
2248
|
+
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false });
|
|
2249
|
+
let [rows] = await job.getQueryResults({ maxResults: 1e4 });
|
|
2250
|
+
let metadata = job.metadata || (await job.getMetadata())[0];
|
|
2251
|
+
let totalRows = Number(metadata?.statistics?.query?.totalRows ?? rows.length);
|
|
2252
|
+
rows.forEach((r) => {
|
|
2253
|
+
Object.entries(r).forEach(([k, v]) => {
|
|
2254
|
+
if (v instanceof BigQueryTimestamp) r[k] = v.value;
|
|
2255
|
+
if (v instanceof BigQueryDate) r[k] = v.value;
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2258
|
+
return { rows, totalRows };
|
|
2259
|
+
}
|
|
2260
|
+
async listDatasets() {
|
|
2261
|
+
let [datasets] = await this.client.getDatasets();
|
|
2262
|
+
return datasets.map((d) => d.id || d.metadata.datasetReference?.datasetId);
|
|
2263
|
+
}
|
|
2264
|
+
async listTables(dataset) {
|
|
2265
|
+
if (!dataset) throw new Error("BigQuery requires a dataset");
|
|
2266
|
+
let res = await this.runQuery(`select table_schema as table_schema, table_name as table_name
|
|
2267
|
+
from \`${dataset}.INFORMATION_SCHEMA.TABLES\`
|
|
2268
|
+
where table_type in ('BASE TABLE', 'VIEW') order by table_name`);
|
|
2269
|
+
return res.rows.map((r) => `${r["table_schema"]}.${r["table_name"]}`);
|
|
2270
|
+
}
|
|
2271
|
+
async describeTable(target) {
|
|
2272
|
+
let parts = target.split(".");
|
|
2273
|
+
let table2 = parts.pop() || "";
|
|
2274
|
+
let dataset = parts.join(".");
|
|
2275
|
+
let sql = `
|
|
2276
|
+
select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position
|
|
2277
|
+
from \`${dataset}.INFORMATION_SCHEMA.COLUMNS\`
|
|
2278
|
+
where lower(table_name) = lower(${sqlStringLiteral(table2)})
|
|
2279
|
+
order by ordinal_position
|
|
2280
|
+
`.trim();
|
|
2281
|
+
let res = await this.runQuery(sql);
|
|
2282
|
+
return res.rows.map((row) => {
|
|
2283
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// connections/duckdb.ts
|
|
2291
|
+
var duckdb_exports = {};
|
|
2292
|
+
__export(duckdb_exports, {
|
|
2293
|
+
DuckDBConnection: () => DuckDBConnection
|
|
2294
|
+
});
|
|
2295
|
+
import { promises as fs5 } from "fs";
|
|
1848
2296
|
import path6 from "path";
|
|
2297
|
+
import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue } from "@duckdb/node-api";
|
|
2298
|
+
function sqlStringLiteral2(value) {
|
|
2299
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2300
|
+
}
|
|
2301
|
+
var DuckDBConnection;
|
|
2302
|
+
var init_duckdb = __esm({
|
|
2303
|
+
"connections/duckdb.ts"() {
|
|
2304
|
+
"use strict";
|
|
2305
|
+
init_config();
|
|
2306
|
+
DuckDBConnection = class {
|
|
2307
|
+
options;
|
|
2308
|
+
ready;
|
|
2309
|
+
connection = null;
|
|
2310
|
+
constructor(options) {
|
|
2311
|
+
this.options = options || {};
|
|
2312
|
+
this.ready = this.initialize();
|
|
2313
|
+
}
|
|
2314
|
+
async initialize() {
|
|
2315
|
+
let dbPath = this.options.path;
|
|
2316
|
+
if (!dbPath) {
|
|
2317
|
+
let files = await fs5.readdir(config.root);
|
|
2318
|
+
dbPath = files.find((f) => f.endsWith(".duckdb"));
|
|
2319
|
+
if (!dbPath) throw new Error("No .duckdb file found in current directory");
|
|
2320
|
+
dbPath = path6.resolve(config.root, dbPath);
|
|
2321
|
+
}
|
|
2322
|
+
let db = await DuckDBInstance.create(":memory:");
|
|
2323
|
+
this.connection = await db.connect();
|
|
2324
|
+
let escapedPath = dbPath.replace(/'/g, "''");
|
|
2325
|
+
await this.connection.run(`attach '${escapedPath}' as graphene_cli (READ_ONLY);`);
|
|
2326
|
+
await this.connection.run("use graphene_cli;");
|
|
2327
|
+
}
|
|
2328
|
+
async runQuery(sql) {
|
|
2329
|
+
await this.ready;
|
|
2330
|
+
let reader = await this.connection.runAndReadAll(sql);
|
|
2331
|
+
let rows = reader.getRowObjects().map((record) => {
|
|
2332
|
+
let out = {};
|
|
2333
|
+
for (let [k, v] of Object.entries(record)) {
|
|
2334
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
2335
|
+
else if (v === null) out[k] = null;
|
|
2336
|
+
else if (v instanceof DuckDBTimestampValue) out[k] = new Date(Number(v.micros / 1000n)).toUTCString();
|
|
2337
|
+
else if (v instanceof DuckDBDateValue) out[k] = v.toString();
|
|
2338
|
+
else if (typeof v === "object") throw new Error(`Unsupported datatype ${v.constructor?.name}`);
|
|
2339
|
+
else out[k] = v;
|
|
2340
|
+
}
|
|
2341
|
+
return out;
|
|
2342
|
+
});
|
|
2343
|
+
return { rows };
|
|
2344
|
+
}
|
|
2345
|
+
async listDatasets() {
|
|
2346
|
+
return await Promise.resolve([]);
|
|
2347
|
+
}
|
|
2348
|
+
async listTables() {
|
|
2349
|
+
let sql = `
|
|
2350
|
+
select table_schema as table_schema, table_name as table_name
|
|
2351
|
+
from information_schema.tables
|
|
2352
|
+
where table_type in ('BASE TABLE', 'VIEW') and table_schema not in ('information_schema', 'pg_catalog')
|
|
2353
|
+
order by table_schema, table_name
|
|
2354
|
+
`.trim();
|
|
2355
|
+
let res = await this.runQuery(sql);
|
|
2356
|
+
return res.rows.map((row) => String(row["table_name"]));
|
|
2357
|
+
}
|
|
2358
|
+
async describeTable(target) {
|
|
2359
|
+
let parts = target.split(".");
|
|
2360
|
+
let table2 = parts.pop() || "";
|
|
2361
|
+
let schema = parts[0];
|
|
2362
|
+
let schemaFilter = schema ? `lower(table_schema) = lower(${sqlStringLiteral2(schema)})` : "table_schema not in ('information_schema', 'pg_catalog')";
|
|
2363
|
+
let sql = `
|
|
2364
|
+
select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position
|
|
2365
|
+
from information_schema.columns
|
|
2366
|
+
where lower(table_name) = lower(${sqlStringLiteral2(table2)}) and ${schemaFilter}
|
|
2367
|
+
order by ordinal_position
|
|
2368
|
+
`.trim();
|
|
2369
|
+
let res = await this.runQuery(sql);
|
|
2370
|
+
return res.rows.map((row) => {
|
|
2371
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
// connections/snowflake.ts
|
|
2379
|
+
var snowflake_exports = {};
|
|
2380
|
+
__export(snowflake_exports, {
|
|
2381
|
+
SnowflakeConnection: () => SnowflakeConnection
|
|
2382
|
+
});
|
|
2383
|
+
import { createPrivateKey } from "node:crypto";
|
|
2384
|
+
import snowflake from "snowflake-sdk";
|
|
2385
|
+
function getSnowflakeNamespace() {
|
|
2386
|
+
throw new Error("Not yet implemented");
|
|
2387
|
+
}
|
|
2388
|
+
function snowflakeIdent(value) {
|
|
2389
|
+
if (!value) throw new Error("Snowflake identifiers cannot be empty");
|
|
2390
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
2391
|
+
}
|
|
2392
|
+
function sqlStringLiteral3(value) {
|
|
2393
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2394
|
+
}
|
|
2395
|
+
var SnowflakeConnection;
|
|
2396
|
+
var init_snowflake2 = __esm({
|
|
2397
|
+
"connections/snowflake.ts"() {
|
|
2398
|
+
"use strict";
|
|
2399
|
+
init_config();
|
|
2400
|
+
SnowflakeConnection = class {
|
|
2401
|
+
ready;
|
|
2402
|
+
connection;
|
|
2403
|
+
constructor(opts) {
|
|
2404
|
+
this.ready = this.initialize(opts || {});
|
|
2405
|
+
}
|
|
2406
|
+
async initialize(opts) {
|
|
2407
|
+
let privateKeyPath = process.env.SNOWFLAKE_PRI_KEY_PATH || config.snowflake?.privateKeyPath;
|
|
2408
|
+
let privateKeyPass = process.env.SNOWFLAKE_PRI_PASSPHRASE;
|
|
2409
|
+
let authOptions = {};
|
|
2410
|
+
if (privateKeyPath) {
|
|
2411
|
+
authOptions = { privateKeyPath, privateKeyPass };
|
|
2412
|
+
} else if (opts.privateKey) {
|
|
2413
|
+
let privateKey = createPrivateKey({ key: opts.privateKey, format: "pem", passphrase: privateKeyPass });
|
|
2414
|
+
authOptions = { privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) };
|
|
2415
|
+
}
|
|
2416
|
+
snowflake.configure({ logLevel: process.env.SNOWFLAKE_LOG_LEVEL || "WARN", logFilePath: "/dev/null" });
|
|
2417
|
+
this.connection = snowflake.createConnection({
|
|
2418
|
+
...opts,
|
|
2419
|
+
...config.snowflake || {},
|
|
2420
|
+
...authOptions,
|
|
2421
|
+
authenticator: "SNOWFLAKE_JWT",
|
|
2422
|
+
application: "Graphene"
|
|
2423
|
+
});
|
|
2424
|
+
await new Promise((resolve, reject) => {
|
|
2425
|
+
this.connection.connect((err, conn) => err ? reject(err) : resolve(conn));
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
async runQuery(sql) {
|
|
2429
|
+
await this.ready;
|
|
2430
|
+
return await new Promise((resolve, reject) => {
|
|
2431
|
+
let rows = [];
|
|
2432
|
+
this.connection.execute({
|
|
2433
|
+
sqlText: sql,
|
|
2434
|
+
streamResult: true,
|
|
2435
|
+
complete: (error, statement) => {
|
|
2436
|
+
if (error) {
|
|
2437
|
+
reject(new Error(`Snowflake query failed: ${error.message || error}`));
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
let stream = statement.streamRows();
|
|
2441
|
+
stream.on("error", (err) => reject(err));
|
|
2442
|
+
stream.on("readable", function(row) {
|
|
2443
|
+
while ((row = this.read()) !== null) {
|
|
2444
|
+
rows.push(row);
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
stream.on("end", () => {
|
|
2448
|
+
let totalRows = Number(statement.getNumRows());
|
|
2449
|
+
resolve({ rows, totalRows });
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
async listDatasets() {
|
|
2456
|
+
await Promise.resolve();
|
|
2457
|
+
throw new Error("Not yet implemented");
|
|
2458
|
+
}
|
|
2459
|
+
async listTables() {
|
|
2460
|
+
let { database } = getSnowflakeNamespace();
|
|
2461
|
+
let tablesRef = `${snowflakeIdent(database)}.${snowflakeIdent("INFORMATION_SCHEMA")}.${snowflakeIdent("TABLES")}`;
|
|
2462
|
+
let sql = `
|
|
2463
|
+
select table_schema as "table_schema", table_name as "table_name"
|
|
2464
|
+
from ${tablesRef}
|
|
2465
|
+
where table_type in ('BASE TABLE', 'VIEW')
|
|
2466
|
+
order by table_schema, table_name
|
|
2467
|
+
`.trim();
|
|
2468
|
+
let res = await this.runQuery(sql);
|
|
2469
|
+
return res.rows.map((row) => String(row["table_name"] || ""));
|
|
2470
|
+
}
|
|
2471
|
+
async describeTable(target) {
|
|
2472
|
+
let parts = target.split(".");
|
|
2473
|
+
let table2 = parts.pop() || "";
|
|
2474
|
+
let database = parts.shift() || "";
|
|
2475
|
+
let schema = parts.join(".");
|
|
2476
|
+
let columnsRef = `${snowflakeIdent(database)}.${snowflakeIdent("INFORMATION_SCHEMA")}.${snowflakeIdent("COLUMNS")}`;
|
|
2477
|
+
let sql = `
|
|
2478
|
+
select column_name as "column_name", data_type as "data_type", ordinal_position as ordinal_position
|
|
2479
|
+
from ${columnsRef}
|
|
2480
|
+
where upper(table_schema) = upper(${sqlStringLiteral3(schema)}) and upper(table_name) = upper(${sqlStringLiteral3(table2)})
|
|
2481
|
+
order by ordinal_position
|
|
2482
|
+
`.trim();
|
|
2483
|
+
let res = await this.runQuery(sql);
|
|
2484
|
+
return res.rows.map((row) => {
|
|
2485
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
|
|
2492
|
+
// connections/index.ts
|
|
2493
|
+
async function getConnection() {
|
|
2494
|
+
if (config.dialect === "bigquery") {
|
|
2495
|
+
let mod = await Promise.resolve().then(() => (init_bigQuery(), bigQuery_exports));
|
|
2496
|
+
return new mod.BigQueryConnection();
|
|
2497
|
+
} else if (config.dialect === "duckdb") {
|
|
2498
|
+
let mod = await Promise.resolve().then(() => (init_duckdb(), duckdb_exports));
|
|
2499
|
+
return new mod.DuckDBConnection({});
|
|
2500
|
+
} else if (config.dialect === "snowflake") {
|
|
2501
|
+
let mod = await Promise.resolve().then(() => (init_snowflake2(), snowflake_exports));
|
|
2502
|
+
return new mod.SnowflakeConnection({});
|
|
2503
|
+
} else {
|
|
2504
|
+
throw new Error(`Unsupported dialect: ${config.dialect}`);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
async function runQuery(sql) {
|
|
2508
|
+
if (config.host) {
|
|
2509
|
+
let resp = await authenticatedFetch("/_api/query", {
|
|
2510
|
+
method: "POST",
|
|
2511
|
+
headers: { "Content-Type": "application/json" },
|
|
2512
|
+
body: JSON.stringify({ sql })
|
|
2513
|
+
});
|
|
2514
|
+
return await resp.json();
|
|
2515
|
+
}
|
|
2516
|
+
let conn = await getConnection();
|
|
2517
|
+
return await conn.runQuery(sql);
|
|
2518
|
+
}
|
|
2519
|
+
var init_connections = __esm({
|
|
2520
|
+
"connections/index.ts"() {
|
|
2521
|
+
"use strict";
|
|
2522
|
+
init_config();
|
|
2523
|
+
init_auth();
|
|
2524
|
+
}
|
|
2525
|
+
});
|
|
2526
|
+
|
|
2527
|
+
// mdCompile.ts
|
|
2528
|
+
import fs6 from "fs";
|
|
2529
|
+
import path7 from "path";
|
|
1849
2530
|
import { visit } from "unist-util-visit";
|
|
1850
2531
|
import sanitizeHtml from "sanitize-html";
|
|
1851
2532
|
function extractQueries() {
|
|
@@ -1922,13 +2603,14 @@ ${content}`;
|
|
|
1922
2603
|
}
|
|
1923
2604
|
function componentNames() {
|
|
1924
2605
|
if (cachedComponentNames) return cachedComponentNames;
|
|
1925
|
-
let files =
|
|
1926
|
-
cachedComponentNames = files.map((f) =>
|
|
2606
|
+
let files = fs6.readdirSync(path7.join(import.meta.dirname, "../ui/components"));
|
|
2607
|
+
cachedComponentNames = files.map((f) => path7.basename(f, ".svelte")).filter((f) => !f.startsWith("_"));
|
|
1927
2608
|
return cachedComponentNames || [];
|
|
1928
2609
|
}
|
|
1929
2610
|
var cachedComponentNames;
|
|
1930
2611
|
var init_mdCompile = __esm({
|
|
1931
2612
|
"mdCompile.ts"() {
|
|
2613
|
+
"use strict";
|
|
1932
2614
|
cachedComponentNames = null;
|
|
1933
2615
|
}
|
|
1934
2616
|
});
|
|
@@ -1940,16 +2622,15 @@ __export(serve2_exports, {
|
|
|
1940
2622
|
});
|
|
1941
2623
|
import { createServer, optimizeDeps } from "vite";
|
|
1942
2624
|
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
1943
|
-
import
|
|
1944
|
-
import
|
|
2625
|
+
import fs7 from "fs-extra";
|
|
2626
|
+
import crypto2 from "crypto";
|
|
1945
2627
|
import { mdsvex } from "mdsvex";
|
|
1946
|
-
import
|
|
2628
|
+
import path8 from "path";
|
|
1947
2629
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1948
2630
|
async function serve2() {
|
|
1949
|
-
uiRoot =
|
|
2631
|
+
uiRoot = path8.join(fileURLToPath2(import.meta.url), "../../ui");
|
|
1950
2632
|
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1951
|
-
await
|
|
1952
|
-
await fs6.writeFile(path7.resolve(config.root, `node_modules/.graphene/${process.env.NODE_ENV == "test" ? "test" : "serve"}.pid`), String(process.pid));
|
|
2633
|
+
await fs7.ensureDir(path8.resolve(config.root, "node_modules/.graphene"));
|
|
1953
2634
|
let server = await createServer({
|
|
1954
2635
|
root: config.root,
|
|
1955
2636
|
plugins: [
|
|
@@ -1970,7 +2651,6 @@ async function serve2() {
|
|
|
1970
2651
|
updateWorkspacePlugin,
|
|
1971
2652
|
mockFilesForTests()
|
|
1972
2653
|
],
|
|
1973
|
-
publicDir: path7.resolve(uiRoot),
|
|
1974
2654
|
server: {
|
|
1975
2655
|
port,
|
|
1976
2656
|
fs: { strict: false },
|
|
@@ -1978,7 +2658,7 @@ async function serve2() {
|
|
|
1978
2658
|
},
|
|
1979
2659
|
resolve: {
|
|
1980
2660
|
alias: {
|
|
1981
|
-
graphene:
|
|
2661
|
+
graphene: path8.resolve(uiRoot, "web.js")
|
|
1982
2662
|
}
|
|
1983
2663
|
}
|
|
1984
2664
|
// optimizeDeps: { // this seems prudent in tests, but currently breaks because ssf needs to be optimized, even in tests
|
|
@@ -1988,9 +2668,7 @@ async function serve2() {
|
|
|
1988
2668
|
});
|
|
1989
2669
|
await optimizeDeps(server.config);
|
|
1990
2670
|
await server.listen();
|
|
1991
|
-
|
|
1992
|
-
console.log(`Server running at http://localhost:${port}`);
|
|
1993
|
-
}
|
|
2671
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
1994
2672
|
return server;
|
|
1995
2673
|
}
|
|
1996
2674
|
async function handleQuery(req, res) {
|
|
@@ -2007,14 +2685,13 @@ async function handleQuery(req, res) {
|
|
|
2007
2685
|
}
|
|
2008
2686
|
if (queries.length > 1) throw new Error("Found multiple queries, which could be a parsing error");
|
|
2009
2687
|
let sql = toSql(queries[0], params);
|
|
2010
|
-
let hash =
|
|
2688
|
+
let hash = crypto2.createHash("SHA1").update(sql).digest("hex");
|
|
2011
2689
|
res.setHeader("ETag", hash);
|
|
2012
2690
|
if (hashes.includes(hash) && req.headers["cache-control"] != "no-cache") {
|
|
2013
2691
|
res.statusCode = 304;
|
|
2014
2692
|
return res.end();
|
|
2015
2693
|
}
|
|
2016
|
-
let
|
|
2017
|
-
let queryResults = await connection.runQuery(sql);
|
|
2694
|
+
let queryResults = await runQuery(sql);
|
|
2018
2695
|
let totalRows = queryResults.totalRows ?? queryResults.rows.length;
|
|
2019
2696
|
if (totalRows > queryResults.rows.length) throw new Error("Query returns too many rows");
|
|
2020
2697
|
let fields = queries[0].fields.map((f) => ({ name: f.name, type: f.type }));
|
|
@@ -2032,7 +2709,7 @@ async function handlePage(server, res, filePath, mount) {
|
|
|
2032
2709
|
<meta charset="UTF-8" />
|
|
2033
2710
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2034
2711
|
<title>Graphene</title>
|
|
2035
|
-
<link rel="icon" href="/
|
|
2712
|
+
<link rel="icon" href="/favicon.ico" />
|
|
2036
2713
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
2037
2714
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2038
2715
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
|
@@ -2066,6 +2743,7 @@ function mockFilesForTests() {
|
|
|
2066
2743
|
var uiRoot, workspaceLoadPromise, updateWorkspacePlugin, handleRequestPlugin;
|
|
2067
2744
|
var init_serve2 = __esm({
|
|
2068
2745
|
"serve2.ts"() {
|
|
2746
|
+
"use strict";
|
|
2069
2747
|
init_core();
|
|
2070
2748
|
init_connections();
|
|
2071
2749
|
init_mdCompile();
|
|
@@ -2090,9 +2768,13 @@ var init_serve2 = __esm({
|
|
|
2090
2768
|
let [pathName] = (req.url || "").split("?");
|
|
2091
2769
|
if (pathName == "/_api/query") return await handleQuery(req, res);
|
|
2092
2770
|
if (pathName == "/__ct") return await handlePage(s, res, "__ct", false);
|
|
2771
|
+
if (pathName == "/favicon.ico") {
|
|
2772
|
+
res.setHeader("Content-Type", "image/x-icon");
|
|
2773
|
+
return res.end(await fs7.readFile(path8.resolve(uiRoot, "assets/favicon.ico")));
|
|
2774
|
+
}
|
|
2093
2775
|
if (!pathName || pathName == "/") pathName = "index";
|
|
2094
|
-
let mdPath =
|
|
2095
|
-
if (await
|
|
2776
|
+
let mdPath = path8.join(config.root, pathName + ".md");
|
|
2777
|
+
if (await fs7.exists(mdPath)) {
|
|
2096
2778
|
await handlePage(s, res, mdPath, true);
|
|
2097
2779
|
} else {
|
|
2098
2780
|
next();
|
|
@@ -2113,11 +2795,14 @@ init_printer();
|
|
|
2113
2795
|
init_core();
|
|
2114
2796
|
init_config();
|
|
2115
2797
|
init_background();
|
|
2116
|
-
init_connections();
|
|
2117
2798
|
init_check();
|
|
2799
|
+
init_connections();
|
|
2800
|
+
init_auth();
|
|
2118
2801
|
import { Command } from "commander";
|
|
2119
|
-
import
|
|
2120
|
-
import
|
|
2802
|
+
import fs8 from "fs-extra";
|
|
2803
|
+
import path9 from "path";
|
|
2804
|
+
import dotenv from "dotenv";
|
|
2805
|
+
dotenv.config({ quiet: true });
|
|
2121
2806
|
var program = new Command();
|
|
2122
2807
|
program.name("graphene").description("Graphene CLI").version("1.0.0");
|
|
2123
2808
|
program.hook("preAction", async () => {
|
|
@@ -2139,26 +2824,54 @@ program.command("run").description("Run a query against your database").argument
|
|
|
2139
2824
|
let queries = analyze(gsql);
|
|
2140
2825
|
if (!validQuery(queries)) return;
|
|
2141
2826
|
let sql = toSql(queries[0]);
|
|
2142
|
-
let
|
|
2143
|
-
let res = await connection.runQuery(sql);
|
|
2827
|
+
let res = await runQuery(sql);
|
|
2144
2828
|
printTable(res.rows);
|
|
2145
2829
|
});
|
|
2146
|
-
program.command("
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2830
|
+
program.command("schema").description("Inspect database tables or describe a table").argument("[schema | table]", "Optional schema or table name to describe").action(async (tableArg) => {
|
|
2831
|
+
let connection = await getConnection();
|
|
2832
|
+
let datasets = await connection.listDatasets();
|
|
2833
|
+
if (!tableArg && datasets.length > 1) {
|
|
2834
|
+
return console.log(`Datasets available:
|
|
2835
|
+
${datasets.join("\n")}`);
|
|
2836
|
+
}
|
|
2837
|
+
let dsToList = null;
|
|
2838
|
+
if (datasets.includes(tableArg)) dsToList = tableArg;
|
|
2839
|
+
else if (!tableArg && datasets.length == 1) dsToList = datasets[0];
|
|
2840
|
+
else if (!tableArg && config.namespace) dsToList = config.namespace;
|
|
2841
|
+
else if (!tableArg && config.dialect == "duckdb") dsToList = "<default>";
|
|
2842
|
+
if (dsToList) {
|
|
2843
|
+
let tables = await connection.listTables(dsToList);
|
|
2844
|
+
return console.log(`Tables${dsToList ? ` in ${dsToList}` : ""}:
|
|
2845
|
+
${tables.join("\n")}`);
|
|
2846
|
+
}
|
|
2847
|
+
let cols = await connection.describeTable(tableArg);
|
|
2848
|
+
if (!cols.length) return console.log(`Table ${tableArg} not found`);
|
|
2849
|
+
console.log(`table ${tableArg} (`);
|
|
2850
|
+
cols.forEach((col) => console.log(` ${col.name} ${col.dataType}`));
|
|
2851
|
+
console.log(")");
|
|
2852
|
+
});
|
|
2853
|
+
program.command("serve").description("Run the local server").option("--bg", "Run the server in the background").action(async (options) => {
|
|
2854
|
+
await stopGrapheneIfRunning();
|
|
2855
|
+
if (options.bg) {
|
|
2151
2856
|
await runServeInBackground();
|
|
2152
2857
|
process.exit(0);
|
|
2858
|
+
} else {
|
|
2859
|
+
let mod = await Promise.resolve().then(() => (init_serve2(), serve2_exports));
|
|
2860
|
+
await mod.serve2();
|
|
2153
2861
|
}
|
|
2154
2862
|
});
|
|
2155
2863
|
program.command("stop").description("Stop the local server").action(async () => {
|
|
2156
|
-
await stopGrapheneIfRunning(
|
|
2864
|
+
await stopGrapheneIfRunning();
|
|
2157
2865
|
});
|
|
2158
2866
|
program.command("check").description("Check the project for errors, optionally capturing a page screenshot").argument("[mdFile]", "Markdown file to check (e.g., index.md)").option("-c, --chart <chartTitle>", "Title of a specific chart to capture").action(async (mdArg, options) => {
|
|
2159
2867
|
let res = await check({ mdArg, chart: options.chart });
|
|
2160
2868
|
process.exit(res ? 0 : 1);
|
|
2161
2869
|
});
|
|
2870
|
+
program.command("login").description("Log in to Graphene Cloud").action(async () => {
|
|
2871
|
+
await loginPkce();
|
|
2872
|
+
console.log("Successfully logged in");
|
|
2873
|
+
process.exit(0);
|
|
2874
|
+
});
|
|
2162
2875
|
program.parse(process.argv);
|
|
2163
2876
|
async function readInput(arg) {
|
|
2164
2877
|
if (!arg || arg === "-") {
|
|
@@ -2170,9 +2883,9 @@ async function readInput(arg) {
|
|
|
2170
2883
|
process.stdin.resume();
|
|
2171
2884
|
});
|
|
2172
2885
|
}
|
|
2173
|
-
let absolutePath =
|
|
2174
|
-
if (
|
|
2175
|
-
return await
|
|
2886
|
+
let absolutePath = path9.resolve(arg);
|
|
2887
|
+
if (fs8.existsSync(absolutePath)) {
|
|
2888
|
+
return await fs8.promises.readFile(absolutePath, "utf-8");
|
|
2176
2889
|
}
|
|
2177
2890
|
return arg;
|
|
2178
2891
|
}
|