@graphenedata/cli 0.0.5 → 0.0.6
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 +20 -14
- package/dist/cli/cli.js +936 -413
- package/dist/docs/graphene.md +180 -133
- 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 +2 -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,14 +162,12 @@ 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
|
});
|
|
@@ -181,6 +185,7 @@ function findOverloads(name, dialect) {
|
|
|
181
185
|
var globalNamespace, dialectNamespaces, BIGQUERY_DIALECT_FUNCTIONS;
|
|
182
186
|
var init_functions = __esm({
|
|
183
187
|
"../lang/functions.ts"() {
|
|
188
|
+
"use strict";
|
|
184
189
|
globalNamespace = new GlobalNameSpace();
|
|
185
190
|
dialectNamespaces = /* @__PURE__ */ new Map();
|
|
186
191
|
Object.assign(DUCKDB_DIALECT_FUNCTIONS, {
|
|
@@ -310,6 +315,138 @@ var init_functions = __esm({
|
|
|
310
315
|
}
|
|
311
316
|
});
|
|
312
317
|
|
|
318
|
+
// ../lang/temporalLiterals.ts
|
|
319
|
+
function parseTemporalLiteral(value, expected) {
|
|
320
|
+
let raw = (value ?? "").trim();
|
|
321
|
+
if (!raw) return null;
|
|
322
|
+
let yearMatch = raw.match(/^([0-9]{4})$/);
|
|
323
|
+
if (yearMatch) {
|
|
324
|
+
let year = Number(yearMatch[1]);
|
|
325
|
+
return buildResult(year, 1, 1, 0, 0, 0, "year", expected);
|
|
326
|
+
}
|
|
327
|
+
let yearMonthMatch = raw.match(/^([0-9]{4})-([0-9]{2})$/);
|
|
328
|
+
if (yearMonthMatch) {
|
|
329
|
+
let year = Number(yearMonthMatch[1]);
|
|
330
|
+
let month = Number(yearMonthMatch[2]);
|
|
331
|
+
if (!inRange(month, 1, 12)) return null;
|
|
332
|
+
return buildResult(year, month, 1, 0, 0, 0, "month", expected);
|
|
333
|
+
}
|
|
334
|
+
let dateMatch = raw.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
|
|
335
|
+
if (dateMatch) {
|
|
336
|
+
let year = Number(dateMatch[1]);
|
|
337
|
+
let month = Number(dateMatch[2]);
|
|
338
|
+
let day = Number(dateMatch[3]);
|
|
339
|
+
if (!isValidDate(year, month, day)) return null;
|
|
340
|
+
return buildResult(year, month, day, 0, 0, 0, "day", expected);
|
|
341
|
+
}
|
|
342
|
+
if (expected === "timestamp") {
|
|
343
|
+
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}))?)?$/);
|
|
344
|
+
if (!dateTimeMatch) return null;
|
|
345
|
+
let year = Number(dateTimeMatch[1]);
|
|
346
|
+
let month = Number(dateTimeMatch[2]);
|
|
347
|
+
let day = Number(dateTimeMatch[3]);
|
|
348
|
+
if (!isValidDate(year, month, day)) return null;
|
|
349
|
+
let hour = Number(dateTimeMatch[4]);
|
|
350
|
+
let minute = dateTimeMatch[5] ? Number(dateTimeMatch[5]) : 0;
|
|
351
|
+
let second = dateTimeMatch[6] ? Number(dateTimeMatch[6]) : 0;
|
|
352
|
+
if (!inRange(hour, 0, 23) || !inRange(minute, 0, 59) || !inRange(second, 0, 59)) return null;
|
|
353
|
+
let timeframe = "hour";
|
|
354
|
+
if (dateTimeMatch[6]) {
|
|
355
|
+
timeframe = "second";
|
|
356
|
+
} else if (dateTimeMatch[5]) {
|
|
357
|
+
timeframe = "minute";
|
|
358
|
+
}
|
|
359
|
+
return buildResult(year, month, day, hour, minute, second, timeframe, expected);
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function parseTemporal(node) {
|
|
364
|
+
if (node.node !== "stringLiteral") return null;
|
|
365
|
+
let rawValue = typeof node.literal === "string" ? node.literal.trim() : "";
|
|
366
|
+
if (!rawValue) return null;
|
|
367
|
+
let parsedDate = parseTemporalLiteral(rawValue, "date");
|
|
368
|
+
if (parsedDate) {
|
|
369
|
+
let typeDef = { type: parsedDate.type, timeframe: parsedDate.timeframe };
|
|
370
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedDate.literal, type: parsedDate.type, typeDef });
|
|
371
|
+
return parsedDate.timeframe;
|
|
372
|
+
}
|
|
373
|
+
let parsedTimestamp = parseTemporalLiteral(rawValue, "timestamp");
|
|
374
|
+
if (parsedTimestamp) {
|
|
375
|
+
let typeDef = { type: parsedTimestamp.type, timeframe: parsedTimestamp.timeframe };
|
|
376
|
+
Object.assign(node, { node: "timeLiteral", literal: parsedTimestamp.literal, type: parsedTimestamp.type, typeDef });
|
|
377
|
+
return parsedTimestamp.timeframe;
|
|
378
|
+
}
|
|
379
|
+
let interval = parseIntervalLiteral(rawValue);
|
|
380
|
+
if (interval) {
|
|
381
|
+
Object.assign(node, { node: "numberLiteral", literal: interval.quantity.toString(), type: "interval", intervalUnit: interval.unit });
|
|
382
|
+
return interval.unit;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
function parseIntervalLiteral(value) {
|
|
387
|
+
let raw = (value ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
388
|
+
if (!raw) return null;
|
|
389
|
+
let match = raw.match(/^(-?\d+(?:\.\d+)?)\s+([a-z]+)$/);
|
|
390
|
+
if (!match) return null;
|
|
391
|
+
let quantity = Number(match[1]);
|
|
392
|
+
if (!Number.isFinite(quantity)) return null;
|
|
393
|
+
let unit = INTERVAL_UNITS[match[2]];
|
|
394
|
+
if (!unit) return null;
|
|
395
|
+
return { quantity, unit };
|
|
396
|
+
}
|
|
397
|
+
function buildResult(year, month, day, hour, minute, second, timeframe, expected) {
|
|
398
|
+
if (expected === "date") {
|
|
399
|
+
return { literal: `${pad(year)}-${pad(month)}-${pad(day)}`, timeframe, type: "date" };
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
literal: `${pad(year)}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`,
|
|
403
|
+
timeframe,
|
|
404
|
+
type: "timestamp"
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function inRange(value, min, max) {
|
|
408
|
+
return Number.isInteger(value) && value >= min && value <= max;
|
|
409
|
+
}
|
|
410
|
+
function isValidDate(year, month, day) {
|
|
411
|
+
if (!inRange(month, 1, 12)) return false;
|
|
412
|
+
if (!inRange(day, 1, 31)) return false;
|
|
413
|
+
let date = new Date(Date.UTC(year, month - 1, day));
|
|
414
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
415
|
+
}
|
|
416
|
+
function pad(value) {
|
|
417
|
+
return value.toString().padStart(2, "0");
|
|
418
|
+
}
|
|
419
|
+
var INTERVAL_UNITS;
|
|
420
|
+
var init_temporalLiterals = __esm({
|
|
421
|
+
"../lang/temporalLiterals.ts"() {
|
|
422
|
+
"use strict";
|
|
423
|
+
INTERVAL_UNITS = {
|
|
424
|
+
second: "second",
|
|
425
|
+
seconds: "second",
|
|
426
|
+
sec: "second",
|
|
427
|
+
secs: "second",
|
|
428
|
+
minute: "minute",
|
|
429
|
+
minutes: "minute",
|
|
430
|
+
min: "minute",
|
|
431
|
+
mins: "minute",
|
|
432
|
+
hour: "hour",
|
|
433
|
+
hours: "hour",
|
|
434
|
+
hr: "hour",
|
|
435
|
+
hrs: "hour",
|
|
436
|
+
day: "day",
|
|
437
|
+
days: "day",
|
|
438
|
+
week: "week",
|
|
439
|
+
weeks: "week",
|
|
440
|
+
month: "month",
|
|
441
|
+
months: "month",
|
|
442
|
+
quarter: "quarter",
|
|
443
|
+
quarters: "quarter",
|
|
444
|
+
year: "year",
|
|
445
|
+
years: "year"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
313
450
|
// ../lang/params.ts
|
|
314
451
|
function inferParamTypes(query) {
|
|
315
452
|
let parentMap = /* @__PURE__ */ new WeakMap();
|
|
@@ -416,8 +553,7 @@ function sanitizeType(value) {
|
|
|
416
553
|
return value;
|
|
417
554
|
}
|
|
418
555
|
function fillInParams(query, params) {
|
|
419
|
-
let
|
|
420
|
-
let filters = q.pipeline[0].filterList || [];
|
|
556
|
+
let filters = query.pipeline[0].filterList || [];
|
|
421
557
|
for (let filter of filters) {
|
|
422
558
|
walkExpression(filter.e, (e) => {
|
|
423
559
|
if (e.node !== "parameter") return;
|
|
@@ -426,20 +562,31 @@ function fillInParams(query, params) {
|
|
|
426
562
|
else if (value == null) Object.assign(e, { node: "null", type: "string" });
|
|
427
563
|
else if (e.type == "string") Object.assign(e, { node: "stringLiteral", literal: value });
|
|
428
564
|
else if (e.type == "number") Object.assign(e, { node: "numberLiteral", literal: value.toString() });
|
|
429
|
-
else if (e.type == "
|
|
565
|
+
else if (e.type == "date" || e.type == "timestamp") {
|
|
566
|
+
if (typeof value !== "string") throw new Error(`Parameters of type ${e.type} must be provided as strings`);
|
|
567
|
+
let parsed = parseTemporalLiteral(value, e.type);
|
|
568
|
+
if (!parsed) throw new Error(`Could not parse ${e.type} literal for param $${e.path[0]}`);
|
|
569
|
+
Object.assign(e, { node: "timeLiteral", literal: parsed.literal, type: parsed.type, typeDef: { type: parsed.type, timeframe: parsed.timeframe } });
|
|
570
|
+
} else if (e.type == "interval") {
|
|
571
|
+
if (typeof value !== "string") throw new Error("Parameters of type interval must be provided as strings");
|
|
572
|
+
let parsed = parseIntervalLiteral(value);
|
|
573
|
+
if (!parsed) throw new Error(`Could not parse interval literal for param $${e.path[0]}`);
|
|
574
|
+
Object.assign(e, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
575
|
+
} else if (e.type == "boolean") Object.assign(e, { node: value ? "true" : "false" });
|
|
430
576
|
else throw new Error(`Unsupported param type ${e.type}`);
|
|
431
577
|
});
|
|
432
578
|
}
|
|
433
|
-
return q;
|
|
434
579
|
}
|
|
435
580
|
var COMPARISON_OPS, BOOLEAN_OPS, NUMERIC_UNARY_OPS, FIELD_TYPES;
|
|
436
581
|
var init_params = __esm({
|
|
437
582
|
"../lang/params.ts"() {
|
|
583
|
+
"use strict";
|
|
438
584
|
init_util();
|
|
585
|
+
init_temporalLiterals();
|
|
439
586
|
COMPARISON_OPS = /* @__PURE__ */ new Set(["=", "!=", "<>", ">", ">=", "<", "<=", "like", "ilike"]);
|
|
440
587
|
BOOLEAN_OPS = /* @__PURE__ */ new Set(["and", "or"]);
|
|
441
588
|
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"]);
|
|
589
|
+
FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "fieldref", "array", "record", "null", "interval"]);
|
|
443
590
|
}
|
|
444
591
|
});
|
|
445
592
|
|
|
@@ -457,34 +604,9 @@ function findTables(fi) {
|
|
|
457
604
|
}
|
|
458
605
|
let table2 = makeTable(name, syntaxNode.getChild("QueryStatement") ? "query_source" : "table");
|
|
459
606
|
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
|
-
}, {});
|
|
607
|
+
syntaxNode.getChildren("ColumnDef").forEach((cn) => addColumnField(table2, cn));
|
|
608
|
+
syntaxNode.getChildren("JoinDef").forEach((jn) => addJoinField(table2, jn));
|
|
609
|
+
syntaxNode.getChildren("ComputedDef").forEach((cn) => addComputedField(table2, cn));
|
|
488
610
|
TABLE_NODE_MAP.set(table2, syntaxNode);
|
|
489
611
|
fi.tables.push(table2);
|
|
490
612
|
}
|
|
@@ -493,12 +615,56 @@ function makeTable(name, type) {
|
|
|
493
615
|
let tablePath = config.namespace ? `${config.namespace}.${name}` : name;
|
|
494
616
|
return { name, type, fields: [], connection: config.dialect, dialect: config.dialect, tableName: name, tablePath, metadata: {} };
|
|
495
617
|
}
|
|
618
|
+
function addColumnField(table2, node) {
|
|
619
|
+
let name = txt(node.getChild("Identifier"));
|
|
620
|
+
if (node.getChild("PrimaryKey")) {
|
|
621
|
+
if (table2.primaryKey) diag(node, `Table ${table2.name} has multiple primary keys`);
|
|
622
|
+
table2.primaryKey = name;
|
|
623
|
+
}
|
|
624
|
+
let type = convertDataType(txt(node.getChild("DataType")));
|
|
625
|
+
if (!type) diag(node, `Unsupported data type: ${txt(node.getChild("DataType"))}`);
|
|
626
|
+
let field = { name, type, metadata: extractLeadingMetadata(node) };
|
|
627
|
+
return addFieldToTable(table2, field, node);
|
|
628
|
+
}
|
|
629
|
+
function addJoinField(table2, node) {
|
|
630
|
+
let nameNode = node.getChild("Alias") || node.getChild("Identifier");
|
|
631
|
+
return addFieldToTable(table2, { name: txt(nameNode) }, node);
|
|
632
|
+
}
|
|
633
|
+
function addComputedField(table2, node) {
|
|
634
|
+
let name = txt(node.getChild("Alias"));
|
|
635
|
+
addFieldToTable(table2, { name, metadata: extractLeadingMetadata(node) }, node);
|
|
636
|
+
}
|
|
637
|
+
function addFieldToTable(table2, field, node) {
|
|
638
|
+
if (table2.fields.find((f) => f.name == field.name)) {
|
|
639
|
+
diag(node, `Table already has a field called "${field.name}"`);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
table2.fields.push(field);
|
|
643
|
+
FIELD_NODE_MAP.set(field, node);
|
|
644
|
+
return field;
|
|
645
|
+
}
|
|
646
|
+
function applyExtends(fi) {
|
|
647
|
+
fi.tree.topNode.getChildren("ExtendStatement").forEach((node) => {
|
|
648
|
+
let tableName = txt(node.getChild("Identifier"));
|
|
649
|
+
let target = lookupTable(tableName, node);
|
|
650
|
+
if (!target) {
|
|
651
|
+
return diag(node.getChild("Identifier") || node, `Cannot extend unknown table "${tableName}"`);
|
|
652
|
+
}
|
|
653
|
+
node.getChildren("JoinDef").forEach((jn) => addJoinField(target, jn));
|
|
654
|
+
node.getChildren("ComputedDef").forEach((cn) => addComputedField(target, cn));
|
|
655
|
+
});
|
|
656
|
+
}
|
|
496
657
|
function analyzeTable(table2) {
|
|
497
|
-
if (table2.type == "query_source")
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
658
|
+
if (table2.type == "query_source") {
|
|
659
|
+
if (table2.query) return;
|
|
660
|
+
let node = TABLE_NODE_MAP.get(table2);
|
|
661
|
+
let query = analyzeQuery(node.getChild("QueryStatement"));
|
|
662
|
+
if (!query) return;
|
|
663
|
+
let queryFields = query.fields.map((f) => ({ type: f.type, name: f.name, metadata: f.metadata }));
|
|
664
|
+
table2.fields.push(...queryFields);
|
|
665
|
+
table2.query = query;
|
|
501
666
|
}
|
|
667
|
+
table2.fields.map((f) => analyzeField(f, table2));
|
|
502
668
|
}
|
|
503
669
|
function analyzeField(field, table2) {
|
|
504
670
|
if (field.type) return;
|
|
@@ -511,8 +677,9 @@ function analyzeField(field, table2) {
|
|
|
511
677
|
field = field;
|
|
512
678
|
let target = lookupTable(txt(node.getChild("Identifier")), node);
|
|
513
679
|
if (!target) return diag(node, "Unknown table to join");
|
|
514
|
-
if (target.type == "query_source")
|
|
515
|
-
let
|
|
680
|
+
if (target.type == "query_source") analyzeTable(target);
|
|
681
|
+
let joinTypeStr = txt(node.getChild("JoinType")).replace(/\s+/g, " ");
|
|
682
|
+
let jt = { "join many": "many", "join one": "one" }[joinTypeStr];
|
|
516
683
|
if (!jt) return diag(node, "Unknown join type");
|
|
517
684
|
Object.assign(field, target, { name: field.name, join: jt });
|
|
518
685
|
field.onExpression = analyzeExpression(node.getChild("Expression"), { table: table2, outputFields: [] });
|
|
@@ -524,38 +691,29 @@ function analyzeField(field, table2) {
|
|
|
524
691
|
}
|
|
525
692
|
analysisQueue.delete(field);
|
|
526
693
|
}
|
|
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
694
|
function analyzeQuery(queryNode) {
|
|
539
|
-
let
|
|
695
|
+
let baseTableName;
|
|
540
696
|
let scope = { table: null, outputFields: [] };
|
|
541
697
|
let isAgg = false;
|
|
542
|
-
let subQuerySources = [];
|
|
543
698
|
if (!txt(queryNode)) return;
|
|
544
|
-
if (txt(queryNode).trim().toLowerCase() == "select 1")
|
|
699
|
+
if (txt(queryNode).trim().toLowerCase() == "select 1") {
|
|
700
|
+
let fields = [{ name: "col_0", type: "number", metadata: {}, e: { node: "numberLiteral", literal: "1", type: "number" } }];
|
|
701
|
+
return { fields, baseTableName: "", rawSql: "select 1", structRef: {}, pipeline: [] };
|
|
702
|
+
}
|
|
545
703
|
let froms = queryNode.getChild("FromClause")?.getChildren("TablePrimary") || [];
|
|
546
704
|
if (froms.find((f) => f.name == "JoinClause")) diag(froms[0], "Query joins not yet supported");
|
|
547
705
|
if (froms.length == 0) return diag(queryNode, "No tables in FROM clause");
|
|
548
706
|
if (froms.length > 1) diag(froms[0], "Multiple tables/joins in FROM clause not yet supported");
|
|
549
707
|
if (froms[0].name == "Subquery") {
|
|
550
|
-
|
|
551
|
-
|
|
708
|
+
diag(froms[0], "Graphene doesn't yet support subqueries. Try chaining queries instead.");
|
|
709
|
+
baseTableName = txt(froms[0].getChild("Alias")) || "subquery";
|
|
710
|
+
scope.table = makeTable(baseTableName, "query_source");
|
|
552
711
|
TABLE_NODE_MAP.set(scope.table, froms[0].getChild("SubqueryExpression"));
|
|
553
|
-
|
|
554
|
-
subQuerySources.push(scope.table);
|
|
712
|
+
analyzeTable(scope.table);
|
|
555
713
|
} else {
|
|
556
|
-
|
|
557
|
-
scope.table = lookupTable(
|
|
558
|
-
if (!scope.table) return diag(froms[0], `could not find table "${
|
|
714
|
+
baseTableName = txt(froms[0].getChild("Identifier"));
|
|
715
|
+
scope.table = lookupTable(baseTableName, froms[0]);
|
|
716
|
+
if (!scope.table) return diag(froms[0], `could not find table "${baseTableName}"`);
|
|
559
717
|
NODE_ENTITY_MAP.set(froms[0], { entityType: "table", table: scope.table });
|
|
560
718
|
}
|
|
561
719
|
let selects = queryNode.getChild("SelectClause")?.getChildren("SelectItem") || [];
|
|
@@ -563,11 +721,12 @@ function analyzeQuery(queryNode) {
|
|
|
563
721
|
isAgg ||= !!isSelectDistinct;
|
|
564
722
|
selects.forEach((s) => {
|
|
565
723
|
if (s.getChild("Wildcard")) {
|
|
566
|
-
let
|
|
567
|
-
let pathStrings =
|
|
568
|
-
let target = followJoins(
|
|
724
|
+
let path10 = s.getChild("Wildcard").getChildren("Identifier");
|
|
725
|
+
let pathStrings = path10.map((p) => txt(p));
|
|
726
|
+
let target = followJoins(path10, scope.table);
|
|
569
727
|
if (!target) return;
|
|
570
728
|
target.fields.forEach((f) => {
|
|
729
|
+
analyzeField(f, target);
|
|
571
730
|
if (isJoin(f) || f.isAgg) return;
|
|
572
731
|
scope.outputFields.push({ ...f, e: { node: "field", path: [...pathStrings, f.name], type: f.type } });
|
|
573
732
|
});
|
|
@@ -621,22 +780,21 @@ function analyzeQuery(queryNode) {
|
|
|
621
780
|
}
|
|
622
781
|
let q = {
|
|
623
782
|
fields: scope.outputFields,
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
783
|
+
baseTableName,
|
|
784
|
+
type: "query",
|
|
785
|
+
structRef: null,
|
|
786
|
+
// We fill this in as part of `toSql`
|
|
787
|
+
pipeline: [{
|
|
788
|
+
type: isAgg ? "reduce" : "project",
|
|
789
|
+
queryFields: scope.outputFields,
|
|
790
|
+
filterList,
|
|
791
|
+
outputStruct: null,
|
|
792
|
+
isRepeated: false,
|
|
793
|
+
orderBy: orderByList.length ? orderByList : void 0,
|
|
794
|
+
limit: queryLimit
|
|
795
|
+
}]
|
|
638
796
|
};
|
|
639
|
-
inferParamTypes(q
|
|
797
|
+
inferParamTypes(q);
|
|
640
798
|
return q;
|
|
641
799
|
}
|
|
642
800
|
function analyzeExpression(expr, scope) {
|
|
@@ -663,11 +821,13 @@ function analyzeExpression(expr, scope) {
|
|
|
663
821
|
if (scope.outputFields.includes(field) && field.isAgg) {
|
|
664
822
|
return { node: "outputField", name: field.name, ...typeInfo, isAgg: field.isAgg };
|
|
665
823
|
}
|
|
666
|
-
let
|
|
667
|
-
return { node: "field", path:
|
|
824
|
+
let path10 = expr.getChildren("Identifier").map((i) => txt(i));
|
|
825
|
+
return { node: "field", path: path10, ...typeInfo, isAgg: field.isAgg };
|
|
668
826
|
}
|
|
669
827
|
case "ExtractExpression": {
|
|
670
|
-
let
|
|
828
|
+
let extractExprNode = expr.getChild("Expression");
|
|
829
|
+
let e = analyzeExpression(extractExprNode, scope);
|
|
830
|
+
checkTypes(e, ["date", "timestamp"], extractExprNode);
|
|
671
831
|
if (!isTemporalType(e.type) || !e.typeDef) return diag(expr, "Expression must be a date or timestamp", errExpr);
|
|
672
832
|
let units = txt(expr.getChild("ExtractUnit")).replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
673
833
|
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract", errExpr);
|
|
@@ -690,7 +850,28 @@ function analyzeExpression(expr, scope) {
|
|
|
690
850
|
let left2 = analyzeExpression(expr.firstChild, scope);
|
|
691
851
|
let right2 = analyzeExpression(expr.lastChild, scope);
|
|
692
852
|
let op = txt(expr.firstChild?.nextSibling).toLowerCase();
|
|
693
|
-
|
|
853
|
+
let type = left2.type;
|
|
854
|
+
if (op == "or" || op == "and") type = "boolean";
|
|
855
|
+
if (op == "+" || op == "-") {
|
|
856
|
+
if (["date", "timestamp", "interval"].find((t) => left2.type == t || right2.type == t)) {
|
|
857
|
+
return analyzeTimeExpression(op, left2, right2, expr);
|
|
858
|
+
}
|
|
859
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
860
|
+
}
|
|
861
|
+
if (op == "*" || op == "/" || op == "%") {
|
|
862
|
+
checkTypes(left2, ["number"], expr.firstChild);
|
|
863
|
+
checkTypes(right2, ["number"], expr.lastChild);
|
|
864
|
+
}
|
|
865
|
+
if (op == "<" || op == "<=" || op == ">" || op == ">=" || op == "=" || op == "!=" || op == "<>") {
|
|
866
|
+
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
867
|
+
type = "boolean";
|
|
868
|
+
}
|
|
869
|
+
if (op == "like" || op == "ilike") {
|
|
870
|
+
checkTypes(left2, ["string"], expr.firstChild);
|
|
871
|
+
checkTypes(right2, ["string"], expr.lastChild);
|
|
872
|
+
type = "boolean";
|
|
873
|
+
}
|
|
874
|
+
return { node: op, kids: { left: left2, right: right2 }, type, isAgg: left2.isAgg || right2.isAgg };
|
|
694
875
|
}
|
|
695
876
|
case "NullTestExpression": {
|
|
696
877
|
let node = expr.getChildren("Kw").find((n) => txt(n).toLowerCase() == "not") ? "is-not-null" : "is-null";
|
|
@@ -724,8 +905,13 @@ function analyzeExpression(expr, scope) {
|
|
|
724
905
|
let oneOf = [];
|
|
725
906
|
let valueList = expr.getChild("InValueList");
|
|
726
907
|
if (valueList) {
|
|
727
|
-
oneOf = valueList.getChildren("Expression").map((v) =>
|
|
908
|
+
oneOf = valueList.getChildren("Expression").map((v) => {
|
|
909
|
+
let e = analyzeExpression(v, scope);
|
|
910
|
+
checkTypes(e, [eNode.type], v);
|
|
911
|
+
return e;
|
|
912
|
+
});
|
|
728
913
|
} else {
|
|
914
|
+
diag(expr, "IN (<subquery>) is not yet supported");
|
|
729
915
|
oneOf = [{ node: "genericSQLExpr", kids: { args: [] }, type: "array" }];
|
|
730
916
|
}
|
|
731
917
|
let isAgg = eNode.isAgg || oneOf.some((v) => v.isAgg);
|
|
@@ -743,11 +929,14 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
743
929
|
return o.params.length == argNodes.length || !!o.params.find((p) => p.isVariadic);
|
|
744
930
|
});
|
|
745
931
|
let args = argNodes.map((node, idx) => {
|
|
746
|
-
let
|
|
747
|
-
if (
|
|
932
|
+
let firstType = overload?.params[idx]?.allowedTypes[0];
|
|
933
|
+
if (firstType?.type === "sql native" && firstType?.rawType === "kw") {
|
|
748
934
|
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
749
935
|
} else {
|
|
750
|
-
|
|
936
|
+
let argExpr = analyzeExpression(node, scope);
|
|
937
|
+
let allowed = overload?.params[idx]?.allowedTypes.map((at) => at.type);
|
|
938
|
+
if (allowed) checkTypes(argExpr, allowed, node);
|
|
939
|
+
return argExpr;
|
|
751
940
|
}
|
|
752
941
|
});
|
|
753
942
|
let type = overload?.returnType.type;
|
|
@@ -762,7 +951,12 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
762
951
|
}));
|
|
763
952
|
let ret;
|
|
764
953
|
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
765
|
-
|
|
954
|
+
let type2 = "number", typeDef;
|
|
955
|
+
if (["min", "max", "avg"].includes(name.toLowerCase())) {
|
|
956
|
+
type2 = args[0].type;
|
|
957
|
+
typeDef = args[0].typeDef;
|
|
958
|
+
}
|
|
959
|
+
ret = { node: "aggregate", function: name, e: args[0], type: type2, typeDef, isAgg: true };
|
|
766
960
|
} else if (overload && type) {
|
|
767
961
|
ret = {
|
|
768
962
|
node: "function_call",
|
|
@@ -783,8 +977,52 @@ function analyzeFunctionCall(expr, scope) {
|
|
|
783
977
|
if (foriegnPaths.length > 0) ret.structPath = foriegnPaths[0].split(".");
|
|
784
978
|
return ret;
|
|
785
979
|
}
|
|
980
|
+
function analyzeTimeExpression(op, left2, right2, node) {
|
|
981
|
+
if (left2.type !== "date" && left2.type !== "timestamp") return diag(node, "Expected left side to be a date or timestamp", errExpr);
|
|
982
|
+
let units = left2.type === "timestamp" ? "second" : "day";
|
|
983
|
+
if (right2.node == "stringLiteral") {
|
|
984
|
+
units = parseTemporal(right2);
|
|
985
|
+
if (right2.node == "stringLiteral") {
|
|
986
|
+
return diag(node, "Could not parse interval", errExpr);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (right2.type == "date" || right2.type == "timestamp") {
|
|
990
|
+
if (op !== "-") return diag(node, "Only subtraction between dates is supported", errExpr);
|
|
991
|
+
if (right2.type !== left2.type) return diag(node, `Expected right side to be a ${left2.type}`, errExpr);
|
|
992
|
+
return { node: "timeDiff", kids: { left: left2, right: right2 }, units, type: "interval", isAgg: false };
|
|
993
|
+
}
|
|
994
|
+
if (right2.type == "interval") {
|
|
995
|
+
let typeDef = { type: left2.type };
|
|
996
|
+
return { node: "delta", kids: { base: left2, delta: right2 }, op, units, type: left2.type, typeDef, isAgg: false };
|
|
997
|
+
}
|
|
998
|
+
return diag(node, "Expected right side to be a date or interval", errExpr);
|
|
999
|
+
}
|
|
1000
|
+
function ensureSameType(left2, leftNode, right2, rightNode) {
|
|
1001
|
+
if (left2.type === "error" || right2.type === "error") return;
|
|
1002
|
+
if (left2.node === "parameter" || right2.node === "parameter") return;
|
|
1003
|
+
if (isTemporalType(left2.type)) checkTypes(right2, [left2.type], rightNode);
|
|
1004
|
+
if (isTemporalType(right2.type)) checkTypes(left2, [right2.type], leftNode);
|
|
1005
|
+
if (left2.type !== right2.type) diag(rightNode, `Expected ${left2.type}, got ${right2.type}`);
|
|
1006
|
+
}
|
|
1007
|
+
function checkTypes(expr, expected, node) {
|
|
1008
|
+
if (expr.type === "error") return;
|
|
1009
|
+
if (expr.node === "parameter") return;
|
|
1010
|
+
if (expected.includes(expr.type)) return;
|
|
1011
|
+
if (expected.includes("generic")) return;
|
|
1012
|
+
let dt = expected.find((t) => t == "date") || expected.find((t) => t == "timestamp");
|
|
1013
|
+
if (expr.node == "stringLiteral" && dt) {
|
|
1014
|
+
let parsed = parseTemporalLiteral(expr.literal, dt);
|
|
1015
|
+
if (!parsed) return diag(node, `Could not parse ${dt} literal: "${expr.literal}"`, void 0);
|
|
1016
|
+
let typeDef = { type: parsed.type, timeframe: parsed.timeframe };
|
|
1017
|
+
Object.assign(expr, { node: "timeLiteral", literal: parsed?.literal, type: parsed?.type, typeDef });
|
|
1018
|
+
} else if (expr.node == "stringLiteral" && expected.includes("interval")) {
|
|
1019
|
+
let parsed = parseIntervalLiteral(expr.literal);
|
|
1020
|
+
if (!parsed) return diag(node, `Could not parse interval literal: "${expr.literal}"`, void 0);
|
|
1021
|
+
return Object.assign(expr, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
1022
|
+
} else diag(node, `Expected types: ${expected.join(", ")}`);
|
|
1023
|
+
}
|
|
786
1024
|
function isSupportedType(value) {
|
|
787
|
-
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic"];
|
|
1025
|
+
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic", "interval"];
|
|
788
1026
|
return supported.includes(value);
|
|
789
1027
|
}
|
|
790
1028
|
function lookupField(expr, scope) {
|
|
@@ -921,11 +1159,13 @@ function convertDataType(dataType) {
|
|
|
921
1159
|
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue, errExpr;
|
|
922
1160
|
var init_analyze = __esm({
|
|
923
1161
|
"../lang/analyze.ts"() {
|
|
1162
|
+
"use strict";
|
|
924
1163
|
init_util();
|
|
925
1164
|
init_metadata();
|
|
926
1165
|
init_config();
|
|
927
1166
|
init_functions();
|
|
928
1167
|
init_params();
|
|
1168
|
+
init_temporalLiterals();
|
|
929
1169
|
FILE_MAP = {};
|
|
930
1170
|
diagnostics = [];
|
|
931
1171
|
TABLE_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
@@ -937,39 +1177,40 @@ var init_analyze = __esm({
|
|
|
937
1177
|
});
|
|
938
1178
|
|
|
939
1179
|
// ../lang/parser.terms.js
|
|
940
|
-
var table, primary_key, as, on, not,
|
|
1180
|
+
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
1181
|
var init_parser_terms = __esm({
|
|
942
1182
|
"../lang/parser.terms.js"() {
|
|
1183
|
+
"use strict";
|
|
943
1184
|
table = 6;
|
|
944
1185
|
primary_key = 11;
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1186
|
+
join = 15;
|
|
1187
|
+
as = 21;
|
|
1188
|
+
on = 24;
|
|
1189
|
+
or = 31;
|
|
1190
|
+
and = 34;
|
|
1191
|
+
like = 43;
|
|
1192
|
+
not = 45;
|
|
1193
|
+
_in = 55;
|
|
1194
|
+
from = 60;
|
|
1195
|
+
inner = 66;
|
|
1196
|
+
left = 68;
|
|
1197
|
+
right = 70;
|
|
1198
|
+
full = 72;
|
|
1199
|
+
cross = 74;
|
|
1200
|
+
select = 77;
|
|
1201
|
+
where = 84;
|
|
1202
|
+
group = 87;
|
|
1203
|
+
by = 89;
|
|
1204
|
+
order = 95;
|
|
1205
|
+
asc = 99;
|
|
1206
|
+
desc = 101;
|
|
1207
|
+
limit = 104;
|
|
1208
|
+
offset = 106;
|
|
966
1209
|
is = 109;
|
|
967
1210
|
_null = 111;
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
and = 132;
|
|
972
|
-
like = 141;
|
|
1211
|
+
exists = 129;
|
|
1212
|
+
_true = 133;
|
|
1213
|
+
_false = 135;
|
|
973
1214
|
}
|
|
974
1215
|
});
|
|
975
1216
|
|
|
@@ -981,6 +1222,7 @@ function specializeIdentifier(value) {
|
|
|
981
1222
|
var keywords;
|
|
982
1223
|
var init_tokens = __esm({
|
|
983
1224
|
"../lang/tokens.js"() {
|
|
1225
|
+
"use strict";
|
|
984
1226
|
init_parser_terms();
|
|
985
1227
|
keywords = {
|
|
986
1228
|
select,
|
|
@@ -1022,25 +1264,26 @@ import { LRParser } from "@lezer/lr";
|
|
|
1022
1264
|
var spec_Identifier, parser;
|
|
1023
1265
|
var init_parser = __esm({
|
|
1024
1266
|
"../lang/parser.js"() {
|
|
1267
|
+
"use strict";
|
|
1025
1268
|
init_tokens();
|
|
1026
|
-
spec_Identifier = { __proto__: null, table: 12, primary_key: 22,
|
|
1269
|
+
spec_Identifier = { __proto__: null, table: 12, primary_key: 22, join: 30, one: 34, many: 38, as: 42, on: 48, 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
1270
|
parser = LRParser.deserialize({
|
|
1028
1271
|
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:
|
|
1272
|
+
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$aQPO'#FiO$fQPO'#FlQYQPOOQ%ZQPO'#FlO%`QPO'#EQO%`QPO'#EYO&aQPO'#DkO#fQPO'#DmO'nQPO'#DlOOQO'#GP'#GPO)aQPO,5:SO+hQPO'#CvO-pQPO'#CvOOQO'#DY'#DYO-xQPO'#DmOOQO'#D|'#D|OOQO'#EP'#EPOOQO'#EO'#EOO.cQPO,5:eO!PQPO,5:eO0eQPO'#EOOOQO'#En'#EnOOQO'#Eq'#EqOOQO'#Es'#EsO1_QPO'#ErOOQO'#FQ'#FQO1fQPO'#FPOOQO'#FU'#FUOOQO'#FW'#FWOOQO'#FT'#FTOOQO'#FY'#FYOOQO'#GT'#GTOOQO'#F]'#F]O1kQPO'#F[OOQO'#GS'#GSOOQO'#F`'#F`OOQO'#GR'#GROOQO'#GU'#GUOOQO'#F}'#F}O%`QPO'#EpO1pQPO'#F_OOQO'#EW'#EWO!PQPO,5:oO1uQPO,5:wO1}QPO,5;QOOQO-E9n-E9nO2rQPO,58yO2zQPO,5<TOOQO,5<W,5<WOOQO-E9j-E9jO3PQPO'#CvO3UQPO,5:lO3xQPO,5:tOOQO'#Cp'#CpOOQO'#Cr'#CrOOQO,5:V,5:VO4lQPO,5:VO4qQPO,5:XOOQO,5:W,5:WO4lQPO,5:WOOQO'#Cj'#CjOOQO'#Do'#DoOOQO'#Dq'#DqOOQO'#Ds'#DsOOQO'#Du'#DuOOQO'#Dw'#DwOwQPO'#DnO4vQPO'#DnOOQO'#Fq'#FqO4{QPO1G/nOOQO,5<Y,5<YO5oQPO,5;uO5vQPO,59bOOQO-E9l-E9lOOQO,5:k,5:kO9[QPO,5<OO9cQPO1G0PO:VQPO1G0PO:VQPO1G0POOQO'#Cz'#CzOOQO'#C}'#C}OOQO'#DW'#DWOOQO'#DP'#DPO:zQPO,59}OOQO'#D['#D[OOQO'#D_'#D_OOQO'#Dd'#DdO;SQPO,59}OOQO'#El'#ElO;XQPO,5;VO%`QPO,59hO%`QPO,59hO%`QPO,59hO%`QPO,59hO%`QPO,59hOOQO,5:j,5:jO4lQPO,5:jO;aQPO,5;^OOQO'#Ev'#EvOOQO'#Ft'#FtO;hQPO,5;^O%`QPO'#EuO#fQPO,5;kO;sQPO,5;vOOQO,5;[,5;[O<QQPO,5;yO<YQPO1G0ZO<}QPO'#E`O=xQPO1G0cOOQO'#Ei'#EiO>mQPO1G0lO>rQPO1G.eO?sQPO1G1nO?xQPO1G1oOOQO1G/q1G/qOOQO1G/s1G/sOOQO1G/r1G/rO@yQPO,5:YOwQPO,5:YOOQO-E9o-E9oOBQQPO1G1aOOQO1G1a1G1aPB[QPO'#FnOOQO1G1j1G1jOOQO,5<^,5<^OBaQPO7+%kOOQO-E9p-E9pOCTQPO7+%kOOQO,59k,59kOCxQPO1G/iO-xQPO1G/iOOQO1G0q1G0qO;[QPO1G0qOG]QPO1G/SOGdQPO1G/SOJvQPO1G/SOKQQPO1G/SOOQO1G/S1G/SOOQO1G0U1G0UO;hQPO1G0xOOQO-E9r-E9rOOQO'#E{'#E{OOQO'#E}'#E}OOQO1G0x1G0xO;nQPO1G0xO%`QPO'#EzOK[QPO,5;aOKcQPO1G1VOKhQPO1G1bOOQO1G1b1G1bOKoQPO1G1bO%`QPO1G1bOOQO'#Fb'#FbOKtQPO1G1eOKyQPO7+%uOLmQPO7+%uOOQO'#Eb'#EbOOQO'#Ed'#EdOOQO,5:z,5:zONSQPO7+%}ON^QPO7+%}OOQO7+&W7+&WONeQPO'#CrONoQPO'#CiONwQPO'#ChO/WQPO'#CxOOQO'#F|'#F|OOQO'#Fz'#FzON|QPO'#FmO!!TQPO7+$PO!![QPO'#CxO#fQPO7+'YONhQPO'#CrOOQO'#GW'#GWO!!aQPO'#FuO!#hQPO7+'ZOOQO'#Cs'#CsO%`QPO1G/tO!#oQPO1G/tO!$vQPO7+&{O!%OQPO7+&{OOQO7+&{7+&{P!PQPO'#FrO!%VQPO<<IVO-xQPO7+%TO!%yQPO'#DfO!&TQPO7+%TOOQO7+&]7+&]OOQO7+&d7+&dO;nQPO7+&dO!&YQPO,5;fOOQO'#Ex'#ExO%`QPO1G0{OOQO7+&q7+&qOOQO7+&|7+&|O!&aQPO7+&|O%`QPO7+'PO!&hQPO<<IaOOQO,5<_,5<_O!'[QPO<<IiOOQO-E9q-E9qOOQO'#Cd'#CdO!(SQPO,58}OOQO'#Cl'#ClOOQO'#Cn'#CnOOQO,59T,59TO!)^QPO,59SO:zQPO,5<PO!)fQPO,5<PO;XQPO,5<QO%`QPO,59eO%`QPO,59eO%`QPO,59eO%`QPO,59eO%`QPO,59eO4lQPO,59dOOQO,5<X,5<XOOQO-E9k-E9kOOQO<<Gk<<GkO%`QPO,59dO!)kQPO<<JtOOQO,5<a,5<aOOQO-E9s-E9sOOQO<<Ju<<JuO!)pQPO7+%`O%`QPO7+%`OOQO-E9m-E9mO!*vQPO<<JgOOQO<<Jg<<JgO!*}QPO,5<ZO!+XQPO<<HoO!+^QPO,5:QO!+fQPO,5:QOOQO<<Ho<<HoOOQO<<JO<<JOO!+mQPO7+&gOOQO<<Jh<<JhO!+zQPO<<JkP1uQPO'#FsOOQO'#Cf'#CfOOQO'#Ce'#CeOOQO1G.i1G.iO!,RQPO1G.nO4lQPO1G.nO!,WQPO1G1kO-xQPO1G1kOOQO1G1l1G1lO;[QPO1G1lO!-gQPO1G/PO!-nQPO1G/PO!.|QPO1G/PO!/WQPO1G/POOQO1G/P1G/POOQO1G/O1G/OO!/bQPO1G/OOOQOAN@`AN@`O!0bQPO<<HzP%`QPO'#FoOOQOAN@RAN@ROOQOAN>ZAN>ZO!1hQPO1G/lOOQOAN@VAN@VO!1oQPO'#CvO!2|QPO'#CuOOQO7+$Y7+$YO!)aQPO7+$YO-xQPO7+'VO!3RQPO7+'VOOQO7+'W7+'WO!,RQPO,59aO!,RQPO<<GtO!3WQPO<<JqOOQO<<Jq<<JqOOQO1G.{1G.{OOQOAN=`AN=`OOQOAN@]AN@]",
|
|
1273
|
+
stateData: "!3b~O$lOSPOS~OUPO!^QO!oSO!vUO!yVO#OXO#RYO#[[O$_aO~OThO$miO~OTmO}oO!PxO!QxO!SrO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO$mpO~O!qqO~P!PO!{!]O~O#T!`O~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~O$j!ZX$y!ZX$t!ZX~P#fOT!bO~OT!cO~O$y!dOU$`X!^$`X!o$`X!v$`X!y$`X#O$`X#R$`X#[$`X$_$`X$j$`X~O$y!dO~OTmO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO$mpO~OT!jOe!iO_!_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!_Xh!_X~OT!jOe!iO_!`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!`Xh!`X~O_!pO!d!qO!f!rO!h!sO!j!tO!l!uO~O!^![a!o![a!v![a!y![a#O![a#R![a#[![a$j![a$y![a$t![a~P({O$m!{O$o!zOejXkjXojXrjXtjXujXvjXwjXxjXyjX{jX}jX!PjX!QjX!SjX!TjX!UjX!XjX#ajX~OTjX!^jX!ojX!vjX!yjX#OjX#RjX#[jX$jjX$rjX$yjX$tjX#kjX#mjX#rjX_jX!djX!fjX!hjX!jjX!ljX#pjX#TjX#cjX#hjX#ujX#vjX#yjX#{jX$QjX$TjX$VjX~P*TOT!|O!S#OO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~P%`O$r#QO!^!ma!o!ma!v!ma!y!ma#O!ma#R!ma#[!ma$j!ma$y!ma$t!ma~Oe!iOk#WOo#TOr#UOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^O~OT!jO!^!rX!o!rX!v!rX!y!rX#O!rX#R!rX#[!rX$j!rX$r!rX$y!rX$t!rX~P/WO#k#hO~P%`O$m#lO~O$m#mO~O$m#oO~OT#qO#T#qO~O#^#sO!^#Ya!o#Ya!v#Ya!y#Ya#O#Ya#R#Ya#[#Ya$j#Ya$y#Ya$t#Ya~Oe!iO$m#uO~O$m#wO~OT!|O~O!^!ta!o!ta!v!ta!y!ta#O!ta#R!ta#[!ta$j!ta$y!ta$t!ta~P/ZO!^!|a!o!|a!v!|a!y!|a#O!|a#R!|a#[!|a$j!|a$y!|a$t!|a~P/ZOT!jO~O$t#yO~O_!pO~O!^![i!o![i!v![i!y![i#O![i#R![i#[![i$j![i$y![i$t![i~P({O$t$PO~P%`O$o!zOTjaejakjaojarjatjaujavjawjaxjayja{ja}ja!Pja!Qja!Sja!Tja!Uja!Xja!^ja!oja!vja!yja#Oja#Rja#[ja#aja$jja$rja$yja$tja#kja#mja#rja_ja!dja!fja!hja!jja!lja#pja#Tja#cja#hja#uja#vja#yja#{ja$Qja$Tja$Vja~O$t$RO~P/ZO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~P!PO$r$TO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~O{#VO!X#[O~O$m$YO~O}oO#cwO~O#k#hO~P/ZO#k#hO#p$eO#r$fO~O!S$nO!qqO$t$mO~P%`OT$pO#v$pO~O$r$rO!^!wi!o!wi!v!wi!y!wi#O!wi#R!wi#[!wi$j!wi$y!wi$t!wi~O#V$tO#X$uO!^#SX!o#SX!v#SX!y#SX#O#SX#R#SX#[#SX$j#SX$r#SX$y#SX$t#SX~O$r$wO!^#Pi!o#Pi!v#Pi!y#Pi#O#Pi#R#Pi#[#Pi$j#Pi$y#Pi$t#Pi~O#T$yO~OT$zO_!pO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO~O$m%TO~OT%UO_!pO}oO!PxO!QxO#T!RO#cwO#hyO#u{O#v!RO#y}O#{!OO$Q!SO$T!VO$V!WO~Oh%YO_!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/ZO$o!zO~O!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~P!PO$r%aO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~O$m%bO~Ok#WOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^OTpiepiopi!^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#UO~PC}Orpi~PC}O!S#ZO!T#ZO!U#ZOTpiepikpiopirpitpiupivpiwpixpiypi{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#YO!Q#YO~PGkO!Ppi!Qpi~PGkO#m%iO~P/ZO$t%kO~O$t%lO~P/ZO$t%lO~O!^QO~O!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~P!PO$r%oO!^!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#qO#T#qO~PMbO$r%qO~PMbOT%sO$ZfX~P*TOa%uOc%vO~OT%xO~O$r&SOT$aX_$aX}$aX!P$aX!Q$aX#T$aX#c$aX#h$aX#u$aX#v$aX#y$aX#{$aX$Q$aX$T$aX$V$aX$t$aX~O$t&UO~P>rO$Z&VO~O$r&XOT$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&ZO~P?xOh%YO_!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&`O~O$t&`O~P%`O!^!my!o!my!v!my!y!my#O!my#R!my#[!my$j!my$y!my$t!my~P!PO$r&dO$t!YX~P/ZO$t&eO~O#r#na~P/ZO$t&hO~P/ZO!^!wy!o!wy!v!wy!y!wy#O!wy#R!wy#[!wy$j!wy$y!wy$t!wy~P!POT#qO#T#qO!^#Py!o#Py!v#Py!y#Py#O#Py#R#Py#[#Py$j#Py$y#Py$t#Py~OZ&kOTVa_Va}Va!PVa!QVa#TVa#cVa#hVa#uVa#vVa#yVa#{Va$QVa$TVa$VVa$rVa$tVa~Oe!iOh%YO~O$m&qO~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/ZO$t'OO~P%`O$r$ca$t$ca~P/ZO$t'PO~O$r'QO$t!Ya~O$t!Ya~P%`O#k#iq#p#iq#r#iq~P/ZO$t'RO~P/ZOT'SO~O$m'WO~Ok#WOt#WOu#WOv#WOw#WOx#WOy#WO{#VO}oO!P#YO!Q#YO!S#ZO!T#ZO!U#ZO!X#[O#a#^Oemiomi~Or#UO~P!,]Ormi~P!,]O!S#ZO!T#ZO!U#ZOemikmiomirmitmiumivmiwmixmiymi{mi}mi!Xmi#ami~O!P#YO!Q#YO~P!-uO!Pmi!Qmi~P!-uOTli_li#Tli#cli#hli#uli#vli#yli#{li$Qli$Tli$Vli$rli$tli~P/ZO_!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/ZO$t!Yi~P%`O$o!zOkjXTjX_jX}jX!PjX!QjX#TjX#cjX#hjX#ujX#vjX#yjX#{jX$QjX$TjX$VjX$rjX$tjX~Ok'ZO~O$t'^O~O$t'aO~OP!T!S!Q~",
|
|
1274
|
+
goto: "Hi${PPP$|%QPP%U%Y%]%`P%c%k%qP&OP&OP&RP&e'TP'a'gP%c(q(wP)_*^P*vPPPPPP+bP,OP-sPP.aPPP)_/PP/q/}0i0vP1W1W1]2a2eP2eP2eP2eP2eP0i2iP2vP2|3_0i3jP0i3wP4UP0i4[P0i4iP4vP5OP5OP0i5RP5`P)_5cP5}P7^8a7^9dP:g:mP:sP:v:|P;QP7^;[PP<_=bP=bP<_>e>e?hP7^@kPAnP1b(q(qP$|$|AqPAuA{BRC_CiCxDOD^DdDnPPPPDtPDxEOPGVPG`7^>e)_PHeTcOdT`OdT%P#u%RR%t$zR&m%tR&l%tS%P#u%RT%V#w%XX$|#u#w%R%XS!vl!yQ#|!wX${#u#w%R%XR%w${Q!lhQ!ojQ#fvQ#v!bQ&R$}R&o%xQ!khQ!njQ#evQ#x!lQ#z!oQ$b#fW%S#u#w%R%XQ&y&RR'V&oQ%Z#{Q&]%[Q&n%xR'['VQ'U&nR'`'[#Q!UTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WS'T&n'[R'_'ZX%O#u#w%R%Xr#`v!g!h#P#g$O$j$l%c%h%m&[&a&g&i&z&|R%|$}!y!YTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'Wv#av!g!h#P#g$O$]$j$l%c%h%m&[&a&g&i&t&z&|R%}$}z#bv!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|R&O$}|#Wv!g!h#P#g$O$]$^$j$l$}%c%h%m&[&a&g&i&t&u&z&|T$W#X%y#QxTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'Wz#Xv!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|Q$[#_Q%y$}R&s%{!O#cv!g!h#P#g$O$]$^$_$j$l%c%h%m&[&a&g&i&t&u&v&z&|R&P$}!S#dv!g!h#P#g$O$]$^$_$`$j$l%c%h%m&[&a&g&i&t&u&v&w&z&|R&Q$}z#]v!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|Q$X#XQ%z$}R&p%yQ%d$YQ&b%bQ'X&qR']'WSeOdS!mipQ$k#lQ%d$YQ&W%TQ&b%bQ'X&qR']'Wg^O_dip#l$Y%T%b&q'WfRO_dip#l$Y%T%b&q'WR%n$qVkR!v#|UjR!v#|!y!XTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WT!xl!yT!wl!ygTO_dip#l$Y%T%b&q'WQuTR$o#mQtTQ#SuQ#p!^]$S#Q$T$r%`%a%ocsTu!^#Q$T$r%`%a%ogfO_dip#l$Y%T%b&q'WgWO_dip#l$Y%T%b&q'WQ!^WR!_ZggO_dip#l$Y%T%b&q'WgZO_dip#l$Y%T%b&q'WQ#r!_V%p$w%q&jR$v#qg]O_dip#l$Y%T%b&q'WR#t!`z#_v!g!h#P#g$O$]$^$j$l%c%h%m&[&a&g&i&t&u&z&|R%{$}#Q!QTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WQ$Z#_Q%e$[Q&r%{R'Y&s#R!WTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!ZTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#RzTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WX#iz#g#j$cX#kz#g#j$cR%j$jQ$h#jR%g$cT$i#j$cQ$g#jS%f$c$hR&f%g#R|Tfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!RTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!PTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!UTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R!TTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'W#R![Tfgpuz!Z!^!{#Q#`#a#b#c#d#k#m#u#w$T$Y$i$o$r%R%X%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WR$q#oTbOdQdOR!edQ%R#uR&T%RbnTu!^#Q$T$r%`%a%o!t!ffgpz!Z!{#`#a#b#c#d#k#m#u#w$Y$i$o%R%X%Z%^%b%j%n%|%}&O&P&Q&V&]&_&d&n&q&}'Q'W'Z'[T!}n!fQ%]$OS&^%]&cR&c%cd_Odip#l$Y%T%b&q'WR!a_Q!ylR#}!yQ#RtU$U#R$V$sQ$V#SR$s#pQ$x#rR%r$xQ#jzQ$c#gT$d#j$cQ%X#wR&Y%XT%Q#u%RX$}#u#w%R%XbvTu!^#Q$T$r%`%a%oQ!gfQ!hgQ#PpQ#gzQ#n!ZQ$O!{Q$]#`Q$^#aQ$_#bQ$`#cQ$a#dQ$j#kQ$l#mW%c$Y%b&q'WQ%h$iQ%m$oQ&[%ZY&a%^&_&d&}'QQ&g%jQ&i%nQ&t%|Q&u%}Q&v&OQ&w&PQ&x&QQ&z&VR&|&]QlRQ#{!vR%[#|!x!YTfgpuz!Z!^!{#Q#`#a#b#c#d#k#m$T$Y$i$o$r%Z%^%`%a%b%j%n%o%|%}&O&P&Q&V&]&_&d&q&}'Q'WX%O#u#w%R%XT%W#w%X",
|
|
1275
|
+
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw join Kw one Kw many Kw as Alias Kw on BinaryExpression Ref = 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",
|
|
1276
|
+
maxTerm: 180,
|
|
1034
1277
|
nodeProps: [
|
|
1035
|
-
["group", -
|
|
1278
|
+
["group", -9, 25, 29, 32, 53, 63, 107, 146, 147, 148, "Expression", -12, 26, 97, 112, 114, 127, 130, 131, 136, 137, 138, 141, 145, "Expression Expression", -2, 61, 62, "TablePrimary"]
|
|
1036
1279
|
],
|
|
1037
1280
|
skippedNodes: [0, 1],
|
|
1038
|
-
repeatNodeCount:
|
|
1039
|
-
tokenData: "*
|
|
1281
|
+
repeatNodeCount: 10,
|
|
1282
|
+
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$m~~&[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$o~~'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
1283
|
tokenizers: [0],
|
|
1041
1284
|
topRules: { "Program": [0, 2] },
|
|
1042
1285
|
specialized: [{ term: 5, get: (value, stack) => specializeIdentifier(value, stack) << 1, external: specializeIdentifier }, { term: 5, get: (value) => spec_Identifier[value] || -1 }],
|
|
1043
|
-
tokenPrec:
|
|
1286
|
+
tokenPrec: 2957
|
|
1044
1287
|
});
|
|
1045
1288
|
}
|
|
1046
1289
|
});
|
|
@@ -1203,6 +1446,7 @@ function isFence(event) {
|
|
|
1203
1446
|
var COMPONENT_ATTRIBUTE_KEYS, GSQL_FENCE, COMPONENT_TAG, ATTRIBUTE;
|
|
1204
1447
|
var init_markdown = __esm({
|
|
1205
1448
|
"../lang/markdown.ts"() {
|
|
1449
|
+
"use strict";
|
|
1206
1450
|
init_parser();
|
|
1207
1451
|
COMPONENT_ATTRIBUTE_KEYS = ["x", "y", "y2", "series", "value", "category"];
|
|
1208
1452
|
GSQL_FENCE = /^([ \t]*)(`{3,})g?sql[^\n]*\n([\s\S]*?)^\1\2[ \t]*$/gim;
|
|
@@ -1211,6 +1455,74 @@ var init_markdown = __esm({
|
|
|
1211
1455
|
}
|
|
1212
1456
|
});
|
|
1213
1457
|
|
|
1458
|
+
// ../lang/snowflake.ts
|
|
1459
|
+
function uppercaseMalloyQuery(query) {
|
|
1460
|
+
query.baseTableName = uppercaseIdentifier(query.baseTableName);
|
|
1461
|
+
for (let stage of query.pipeline || []) {
|
|
1462
|
+
let fields = stage.queryFields || [];
|
|
1463
|
+
fields.forEach((field) => uppercaseColumnField(field));
|
|
1464
|
+
let filters = stage.filterList || [];
|
|
1465
|
+
filters.forEach((filter) => uppercaseExpression(filter?.e));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function uppercaseTable(table2) {
|
|
1469
|
+
if (table2.upperCased) return;
|
|
1470
|
+
table2.upperCased = true;
|
|
1471
|
+
table2.name = uppercaseIdentifier(table2.name);
|
|
1472
|
+
if (table2.primaryKey) table2.primaryKey = uppercaseIdentifier(table2.primaryKey);
|
|
1473
|
+
if (table2.tableName) table2.tableName = uppercaseQualified(table2.tableName);
|
|
1474
|
+
if (table2.tablePath) table2.tablePath = uppercaseQualified(table2.tablePath);
|
|
1475
|
+
table2.fields?.forEach((field) => uppercaseField(field));
|
|
1476
|
+
if (table2.query) uppercaseMalloyQuery(table2.query);
|
|
1477
|
+
}
|
|
1478
|
+
function uppercaseField(field) {
|
|
1479
|
+
if (!field) return;
|
|
1480
|
+
if (isJoinField(field)) {
|
|
1481
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1482
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1483
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1484
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1485
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1486
|
+
if (field.onExpression) uppercaseExpression(field.onExpression);
|
|
1487
|
+
uppercaseTable(field);
|
|
1488
|
+
} else {
|
|
1489
|
+
uppercaseColumnField(field);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
function uppercaseColumnField(field) {
|
|
1493
|
+
if (!field) return;
|
|
1494
|
+
field.name = uppercaseIdentifier(field.name);
|
|
1495
|
+
if (field.path) field.path = field.path.map(uppercaseIdentifier);
|
|
1496
|
+
if (field.structPath) field.structPath = field.structPath.map(uppercaseIdentifier);
|
|
1497
|
+
if (field.tableName) field.tableName = uppercaseQualified(field.tableName);
|
|
1498
|
+
if (field.tablePath) field.tablePath = uppercaseQualified(field.tablePath);
|
|
1499
|
+
if (field.e) uppercaseExpression(field.e);
|
|
1500
|
+
}
|
|
1501
|
+
function uppercaseExpression(expr) {
|
|
1502
|
+
if (!expr) return;
|
|
1503
|
+
walkExpression(expr, (node) => {
|
|
1504
|
+
if (Array.isArray(node.path)) node.path = node.path.map(uppercaseIdentifier);
|
|
1505
|
+
if (Array.isArray(node.structPath)) node.structPath = node.structPath.map(uppercaseIdentifier);
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function uppercaseIdentifier(value) {
|
|
1509
|
+
if (!value) return value || "";
|
|
1510
|
+
return value.toString().toUpperCase();
|
|
1511
|
+
}
|
|
1512
|
+
function uppercaseQualified(value) {
|
|
1513
|
+
if (!value) return value;
|
|
1514
|
+
return value.split(".").map((part) => part.startsWith('"') && part.endsWith('"') ? part : uppercaseIdentifier(part)).join(".");
|
|
1515
|
+
}
|
|
1516
|
+
function isJoinField(field) {
|
|
1517
|
+
return !!field?.join;
|
|
1518
|
+
}
|
|
1519
|
+
var init_snowflake = __esm({
|
|
1520
|
+
"../lang/snowflake.ts"() {
|
|
1521
|
+
"use strict";
|
|
1522
|
+
init_util();
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1214
1526
|
// ../lang/core.ts
|
|
1215
1527
|
import { registerDialect, StandardSQLDialect, QueryModel, expandBlueprintMap } from "@graphenedata/malloy";
|
|
1216
1528
|
import { readFile } from "node:fs/promises";
|
|
@@ -1225,15 +1537,19 @@ function getDiagnostics() {
|
|
|
1225
1537
|
async function loadWorkspace(dir, includeMd) {
|
|
1226
1538
|
let files = await glob(includeMd ? "**/*.{gsql,md}" : "**/*.gsql", { cwd: dir, ignore: ["node_modules/**"] });
|
|
1227
1539
|
for await (let file of files) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1540
|
+
try {
|
|
1541
|
+
let contents = await readFile(path2.join(dir, file), "utf-8");
|
|
1542
|
+
updateFile(contents, file);
|
|
1543
|
+
} catch (e) {
|
|
1544
|
+
console.error("Failed to read file", file, e.message);
|
|
1545
|
+
}
|
|
1230
1546
|
}
|
|
1231
1547
|
}
|
|
1232
|
-
function updateFile(contents,
|
|
1233
|
-
FILE_MAP[
|
|
1234
|
-
FILE_MAP[
|
|
1235
|
-
FILE_MAP[
|
|
1236
|
-
return FILE_MAP[
|
|
1548
|
+
function updateFile(contents, path10) {
|
|
1549
|
+
FILE_MAP[path10] ||= { path: path10, contents, tree: null, tables: [], queries: [] };
|
|
1550
|
+
FILE_MAP[path10].contents = contents;
|
|
1551
|
+
FILE_MAP[path10].tree = null;
|
|
1552
|
+
return FILE_MAP[path10];
|
|
1237
1553
|
}
|
|
1238
1554
|
function analyze(contents, type) {
|
|
1239
1555
|
clearDiagnostics();
|
|
@@ -1246,6 +1562,7 @@ function analyze(contents, type) {
|
|
|
1246
1562
|
recordSyntaxErrors(fi);
|
|
1247
1563
|
findTables(fi);
|
|
1248
1564
|
});
|
|
1565
|
+
Object.values(FILE_MAP).forEach(applyExtends);
|
|
1249
1566
|
Object.values(FILE_MAP).flatMap((f) => f.tables).forEach(analyzeTable);
|
|
1250
1567
|
if (contents) {
|
|
1251
1568
|
let fi = FILE_MAP["input"];
|
|
@@ -1258,13 +1575,21 @@ function analyze(contents, type) {
|
|
|
1258
1575
|
}
|
|
1259
1576
|
function toSql(query, params = {}) {
|
|
1260
1577
|
if (query.rawSql) return query.rawSql;
|
|
1261
|
-
let contents = {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1578
|
+
let contents = Object.fromEntries(Object.values(FILE_MAP).flatMap((fi) => {
|
|
1579
|
+
return fi.tables.map((t) => {
|
|
1580
|
+
t = structuredClone(t);
|
|
1581
|
+
if (fi.path == "input" && t.query) fillInParams(t.query, params);
|
|
1582
|
+
if (config.dialect == "snowflake") uppercaseTable(t);
|
|
1583
|
+
return [t.name, t];
|
|
1584
|
+
});
|
|
1585
|
+
}));
|
|
1586
|
+
query = structuredClone(query);
|
|
1587
|
+
fillInParams(query, params);
|
|
1588
|
+
if (config.dialect == "snowflake") uppercaseMalloyQuery(query);
|
|
1589
|
+
let tableQueries = Object.values(contents).map((t) => t.query);
|
|
1590
|
+
let joinQueries = Object.values(contents).flatMap((t) => t.fields.map((f) => f.query));
|
|
1591
|
+
let allQueries = [...tableQueries, ...joinQueries, query].filter((q) => !!q);
|
|
1592
|
+
allQueries.forEach((q) => q.structRef = contents[q.baseTableName]);
|
|
1268
1593
|
let qm = new QueryModel({
|
|
1269
1594
|
name: "generated_model",
|
|
1270
1595
|
contents,
|
|
@@ -1272,11 +1597,12 @@ function toSql(query, params = {}) {
|
|
|
1272
1597
|
dependencies: {},
|
|
1273
1598
|
exports: []
|
|
1274
1599
|
});
|
|
1275
|
-
return qm.compileQuery(
|
|
1600
|
+
return qm.compileQuery(query).sql;
|
|
1276
1601
|
}
|
|
1277
1602
|
var BigQueryDialect;
|
|
1278
1603
|
var init_core = __esm({
|
|
1279
1604
|
"../lang/core.ts"() {
|
|
1605
|
+
"use strict";
|
|
1280
1606
|
init_analyze();
|
|
1281
1607
|
init_params();
|
|
1282
1608
|
init_util();
|
|
@@ -1284,6 +1610,7 @@ var init_core = __esm({
|
|
|
1284
1610
|
init_functions();
|
|
1285
1611
|
init_parser();
|
|
1286
1612
|
init_markdown();
|
|
1613
|
+
init_snowflake();
|
|
1287
1614
|
BigQueryDialect = class extends StandardSQLDialect {
|
|
1288
1615
|
constructor() {
|
|
1289
1616
|
super();
|
|
@@ -1349,6 +1676,7 @@ function printTable(rows) {
|
|
|
1349
1676
|
var styleText;
|
|
1350
1677
|
var init_printer = __esm({
|
|
1351
1678
|
"printer.ts"() {
|
|
1679
|
+
"use strict";
|
|
1352
1680
|
init_core();
|
|
1353
1681
|
styleText = (style, text) => {
|
|
1354
1682
|
try {
|
|
@@ -1361,21 +1689,20 @@ var init_printer = __esm({
|
|
|
1361
1689
|
});
|
|
1362
1690
|
|
|
1363
1691
|
// background.ts
|
|
1364
|
-
import { spawn } from "child_process";
|
|
1692
|
+
import { spawn, exec } from "child_process";
|
|
1693
|
+
import { promisify } from "util";
|
|
1365
1694
|
import { fileURLToPath } from "url";
|
|
1366
1695
|
import fs2 from "fs-extra";
|
|
1367
1696
|
import path3 from "path";
|
|
1368
1697
|
async function runServeInBackground() {
|
|
1369
|
-
let
|
|
1370
|
-
let grapheneCache = getGrapheneCache(root);
|
|
1698
|
+
let grapheneCache = getGrapheneCache(config.root);
|
|
1371
1699
|
let logFile = path3.join(grapheneCache, "serve.log");
|
|
1372
1700
|
await fs2.ensureDir(grapheneCache);
|
|
1373
|
-
await stopGrapheneIfRunning(root);
|
|
1374
1701
|
let log = fs2.openSync(logFile, "w");
|
|
1375
1702
|
let entryPoint = process.argv[1] || fileURLToPath(import.meta.url);
|
|
1376
|
-
let childArgs = [...process.execArgv, entryPoint, "serve"
|
|
1703
|
+
let childArgs = [...process.execArgv, entryPoint, "serve"];
|
|
1377
1704
|
let child = spawn(process.execPath, childArgs, {
|
|
1378
|
-
cwd: root,
|
|
1705
|
+
cwd: config.root,
|
|
1379
1706
|
detached: true,
|
|
1380
1707
|
env: { ...process.env },
|
|
1381
1708
|
stdio: ["ignore", log, log]
|
|
@@ -1394,7 +1721,6 @@ async function runServeInBackground() {
|
|
|
1394
1721
|
}
|
|
1395
1722
|
});
|
|
1396
1723
|
child.once("exit", () => {
|
|
1397
|
-
process.stdout.write(fs2.readFileSync(logFile));
|
|
1398
1724
|
reject(new Error("Exited before server started"));
|
|
1399
1725
|
});
|
|
1400
1726
|
child.once("error", (e) => reject(e));
|
|
@@ -1403,15 +1729,9 @@ async function runServeInBackground() {
|
|
|
1403
1729
|
function getGrapheneCache(root) {
|
|
1404
1730
|
return path3.join(root, "node_modules", ".graphene");
|
|
1405
1731
|
}
|
|
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
1732
|
function sendSignal(pid, signal) {
|
|
1414
|
-
|
|
1733
|
+
let pids = process.platform === "win32" ? [pid] : [pid, -pid];
|
|
1734
|
+
for (let target of pids) {
|
|
1415
1735
|
try {
|
|
1416
1736
|
process.kill(target, signal);
|
|
1417
1737
|
} catch (err) {
|
|
@@ -1422,229 +1742,67 @@ function sendSignal(pid, signal) {
|
|
|
1422
1742
|
}
|
|
1423
1743
|
return true;
|
|
1424
1744
|
}
|
|
1425
|
-
async function stopGrapheneIfRunning(
|
|
1426
|
-
|
|
1427
|
-
let
|
|
1428
|
-
let pid = await readPid(pidFile);
|
|
1745
|
+
async function stopGrapheneIfRunning() {
|
|
1746
|
+
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1747
|
+
let pid = await getPidOnPort(port);
|
|
1429
1748
|
if (!pid) return;
|
|
1430
1749
|
console.log(`Stopping server (${pid})`);
|
|
1431
1750
|
sendSignal(pid, "SIGTERM");
|
|
1432
1751
|
let end = Date.now() + 5e3;
|
|
1433
|
-
while (Date.now() < end
|
|
1752
|
+
while (Date.now() < end) {
|
|
1753
|
+
if (!await getPidOnPort(port)) break;
|
|
1434
1754
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1435
1755
|
}
|
|
1436
|
-
if (
|
|
1437
|
-
|
|
1438
|
-
await fs2.remove(pidFile);
|
|
1439
|
-
}
|
|
1440
|
-
async function readPid(pidFile) {
|
|
1441
|
-
if (!await fs2.pathExists(pidFile)) return void 0;
|
|
1442
|
-
let contents = (await fs2.readFile(pidFile, "utf8")).trim();
|
|
1443
|
-
if (!contents) return void 0;
|
|
1444
|
-
let pid = Number.parseInt(contents, 10);
|
|
1445
|
-
if (Number.isNaN(pid)) return void 0;
|
|
1446
|
-
return pid;
|
|
1447
|
-
}
|
|
1448
|
-
async function isServerRunning() {
|
|
1449
|
-
let pidFile = getPidFilePath(config.root);
|
|
1450
|
-
let pid = await readPid(pidFile);
|
|
1451
|
-
if (!pid) return false;
|
|
1452
|
-
try {
|
|
1453
|
-
process.kill(pid, 0);
|
|
1454
|
-
return true;
|
|
1455
|
-
} catch {
|
|
1456
|
-
fs2.removeSync(pidFile);
|
|
1457
|
-
return false;
|
|
1756
|
+
if (await getPidOnPort(port)) {
|
|
1757
|
+
sendSignal(pid, "SIGKILL");
|
|
1458
1758
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
"background.ts"() {
|
|
1462
|
-
init_config();
|
|
1759
|
+
if (await getPidOnPort(port)) {
|
|
1760
|
+
console.error("Failed to stop previous Graphene server");
|
|
1463
1761
|
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
});
|
|
1504
|
-
|
|
1505
|
-
// connections/duckdb.ts
|
|
1506
|
-
var duckdb_exports = {};
|
|
1507
|
-
__export(duckdb_exports, {
|
|
1508
|
-
DuckDBConnection: () => DuckDBConnection
|
|
1509
|
-
});
|
|
1510
|
-
import { promises as fs3 } from "fs";
|
|
1511
|
-
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" }) };
|
|
1762
|
+
}
|
|
1763
|
+
async function isServerRunning(portOverride) {
|
|
1764
|
+
let port = portOverride || Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
1765
|
+
return !!await getPidOnPort(port);
|
|
1766
|
+
}
|
|
1767
|
+
async function getPidOnPort(port) {
|
|
1768
|
+
try {
|
|
1769
|
+
if (process.platform === "win32") {
|
|
1770
|
+
let { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
|
|
1771
|
+
let lines = stdout.trim().split("\n");
|
|
1772
|
+
for (let line of lines) {
|
|
1773
|
+
let parts = line.trim().split(/\s+/);
|
|
1774
|
+
if (parts.length < 5) continue;
|
|
1775
|
+
let localAddress = parts[1];
|
|
1776
|
+
let pid = parseInt(parts[parts.length - 1], 10);
|
|
1777
|
+
if (localAddress.endsWith(`:${port}`)) {
|
|
1778
|
+
return pid;
|
|
1586
1779
|
}
|
|
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
1780
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
});
|
|
1781
|
+
} else {
|
|
1782
|
+
return new Promise((resolve) => {
|
|
1783
|
+
let child = spawn("lsof", ["-i", `:${port}`, "-t", "-sTCP:LISTEN"]);
|
|
1784
|
+
let stdout = "";
|
|
1785
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
1786
|
+
child.on("close", (code) => {
|
|
1787
|
+
if (code !== 0) return resolve(void 0);
|
|
1788
|
+
let pid = parseInt(stdout.trim(), 10);
|
|
1789
|
+
resolve(isNaN(pid) ? void 0 : pid);
|
|
1624
1790
|
});
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
})
|
|
1629
|
-
|
|
1630
|
-
|
|
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}`);
|
|
1791
|
+
child.on("error", () => resolve(void 0));
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
} catch (e) {
|
|
1795
|
+
console.warn("Failed to check for server:", e.message);
|
|
1796
|
+
return void 0;
|
|
1643
1797
|
}
|
|
1798
|
+
return void 0;
|
|
1644
1799
|
}
|
|
1645
|
-
var
|
|
1646
|
-
|
|
1800
|
+
var execAsync;
|
|
1801
|
+
var init_background = __esm({
|
|
1802
|
+
"background.ts"() {
|
|
1803
|
+
"use strict";
|
|
1647
1804
|
init_config();
|
|
1805
|
+
execAsync = promisify(exec);
|
|
1648
1806
|
}
|
|
1649
1807
|
});
|
|
1650
1808
|
|
|
@@ -1652,14 +1810,15 @@ var init_connections = __esm({
|
|
|
1652
1810
|
var mockFileMap;
|
|
1653
1811
|
var init_mockFiles = __esm({
|
|
1654
1812
|
"mockFiles.ts"() {
|
|
1813
|
+
"use strict";
|
|
1655
1814
|
mockFileMap = {};
|
|
1656
1815
|
}
|
|
1657
1816
|
});
|
|
1658
1817
|
|
|
1659
1818
|
// check.ts
|
|
1660
|
-
import
|
|
1819
|
+
import fs3 from "fs-extra";
|
|
1661
1820
|
import os from "os";
|
|
1662
|
-
import
|
|
1821
|
+
import path4 from "path";
|
|
1663
1822
|
import { spawn as spawn2 } from "child_process";
|
|
1664
1823
|
import { WebSocketServer } from "ws";
|
|
1665
1824
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -1676,7 +1835,7 @@ async function check(options) {
|
|
|
1676
1835
|
if (process.env.NODE_ENV == "test" && mockFileMap[mdFile]) {
|
|
1677
1836
|
updateFile(mockFileMap[mdFile], mdFile);
|
|
1678
1837
|
} else {
|
|
1679
|
-
let content = readFileSync2(
|
|
1838
|
+
let content = readFileSync2(path4.resolve(config.root, mdFile), "utf-8");
|
|
1680
1839
|
updateFile(content, mdFile);
|
|
1681
1840
|
}
|
|
1682
1841
|
}
|
|
@@ -1731,12 +1890,12 @@ async function check(options) {
|
|
|
1731
1890
|
}
|
|
1732
1891
|
if (resp?.screenshot) {
|
|
1733
1892
|
let filename = `graphene-screenshot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.png`;
|
|
1734
|
-
let screenshotPath =
|
|
1893
|
+
let screenshotPath = path4.join(os.tmpdir(), filename);
|
|
1735
1894
|
let base64Data = resp.screenshot.replace(/^data:image\/png;base64,/, "");
|
|
1736
|
-
await
|
|
1895
|
+
await fs3.writeFile(screenshotPath, base64Data, "base64");
|
|
1737
1896
|
log("Screenshot saved to", screenshotPath);
|
|
1738
1897
|
}
|
|
1739
|
-
return
|
|
1898
|
+
return errors.length == 0;
|
|
1740
1899
|
}
|
|
1741
1900
|
async function sendCheckRequest({ host, pageUrl, chart }) {
|
|
1742
1901
|
let abort = new AbortController();
|
|
@@ -1770,11 +1929,11 @@ function normalizeMdFile(mdFile) {
|
|
|
1770
1929
|
return clean;
|
|
1771
1930
|
}
|
|
1772
1931
|
let absolute = [
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
].find((p) =>
|
|
1932
|
+
path4.resolve(process.cwd(), clean),
|
|
1933
|
+
path4.resolve(config.root, clean)
|
|
1934
|
+
].find((p) => fs3.existsSync(p)) || null;
|
|
1776
1935
|
if (!absolute) return null;
|
|
1777
|
-
let relative =
|
|
1936
|
+
let relative = path4.relative(config.root, absolute);
|
|
1778
1937
|
return relative;
|
|
1779
1938
|
}
|
|
1780
1939
|
async function proxyCheckRequest(req, res) {
|
|
@@ -1833,6 +1992,7 @@ function checkVitePlugin() {
|
|
|
1833
1992
|
var browserConnections, pendingRequests;
|
|
1834
1993
|
var init_check = __esm({
|
|
1835
1994
|
"check.ts"() {
|
|
1995
|
+
"use strict";
|
|
1836
1996
|
init_core();
|
|
1837
1997
|
init_printer();
|
|
1838
1998
|
init_mockFiles();
|
|
@@ -1843,9 +2003,368 @@ var init_check = __esm({
|
|
|
1843
2003
|
}
|
|
1844
2004
|
});
|
|
1845
2005
|
|
|
1846
|
-
//
|
|
1847
|
-
import
|
|
2006
|
+
// auth.ts
|
|
2007
|
+
import fs4 from "node:fs/promises";
|
|
2008
|
+
import path5 from "path";
|
|
2009
|
+
import os2 from "os";
|
|
2010
|
+
import { spawn as spawn3 } from "child_process";
|
|
2011
|
+
import http from "http";
|
|
2012
|
+
async function readStore() {
|
|
2013
|
+
try {
|
|
2014
|
+
let txt2 = await fs4.readFile(credsPath, "utf8");
|
|
2015
|
+
return JSON.parse(txt2) || {};
|
|
2016
|
+
} catch {
|
|
2017
|
+
return {};
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
async function readEntry() {
|
|
2021
|
+
let store = await readStore();
|
|
2022
|
+
return store[config.root];
|
|
2023
|
+
}
|
|
2024
|
+
async function updateEntry(cred) {
|
|
2025
|
+
let store = await readStore();
|
|
2026
|
+
cred.refresh_token ||= store[config.root]?.refresh_token;
|
|
2027
|
+
cred.expires_at = Date.now() + cred.expires_in;
|
|
2028
|
+
store[config.root] = cred;
|
|
2029
|
+
await fs4.mkdir(path5.dirname(credsPath), { recursive: true, mode: 448 });
|
|
2030
|
+
await fs4.writeFile(credsPath, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
|
|
2031
|
+
if (process.platform !== "win32") {
|
|
2032
|
+
await fs4.chmod(credsPath, 384).catch(() => {
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function openInBrowser(url) {
|
|
2037
|
+
try {
|
|
2038
|
+
let plat = process.platform;
|
|
2039
|
+
let cmd = "xdg-open";
|
|
2040
|
+
if (plat == "darwin") cmd = "open";
|
|
2041
|
+
if (plat == "win32") cmd = "start";
|
|
2042
|
+
let p = spawn3(cmd, [url], { stdio: "ignore", shell: plat === "win32" });
|
|
2043
|
+
p.unref();
|
|
2044
|
+
} catch {
|
|
2045
|
+
console.log(`Open this URL to authenticate:
|
|
2046
|
+
${url}`);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function base64url(buf) {
|
|
2050
|
+
let b64 = Buffer.from(buf).toString("base64");
|
|
2051
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
2052
|
+
}
|
|
2053
|
+
function randomBytes(len = 32) {
|
|
2054
|
+
return crypto.getRandomValues(new Uint8Array(len));
|
|
2055
|
+
}
|
|
2056
|
+
async function startLoopback() {
|
|
2057
|
+
let server = http.createServer();
|
|
2058
|
+
await new Promise((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
2059
|
+
let addr = server.address();
|
|
2060
|
+
if (!addr || typeof addr !== "object") throw new Error("Couldnt start oauth callback server");
|
|
2061
|
+
let redirectBase = `http://localhost:${addr.port}`;
|
|
2062
|
+
let waitForCode = new Promise((resolve) => {
|
|
2063
|
+
server.on("request", (req, res) => {
|
|
2064
|
+
let url = new URL(req.url || "/", redirectBase);
|
|
2065
|
+
if (url.pathname !== "/callback") {
|
|
2066
|
+
res.statusCode = 404;
|
|
2067
|
+
res.end("Not Found");
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
let code = url.searchParams.get("code") || "";
|
|
2071
|
+
let state = url.searchParams.get("state") || "";
|
|
2072
|
+
res.statusCode = 200;
|
|
2073
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2074
|
+
res.end("<html><body>Login complete. You may close this window.</body></html>");
|
|
2075
|
+
resolve({ code, state });
|
|
2076
|
+
});
|
|
2077
|
+
});
|
|
2078
|
+
return { url: redirectBase, waitForCode, close: () => server.close() };
|
|
2079
|
+
}
|
|
2080
|
+
async function loginPkce(opener) {
|
|
2081
|
+
let verifier = base64url(randomBytes(48));
|
|
2082
|
+
let data = new TextEncoder().encode(verifier);
|
|
2083
|
+
let digest = await crypto.subtle.digest("SHA-256", data);
|
|
2084
|
+
let code_challenge = base64url(digest);
|
|
2085
|
+
let state = base64url(randomBytes(16));
|
|
2086
|
+
let loop = await startLoopback();
|
|
2087
|
+
let redirect_uri = `${loop.url}/callback`;
|
|
2088
|
+
let authorizeUrl = new URL(`${config.host}/authenticate`);
|
|
2089
|
+
authorizeUrl.search = new URLSearchParams({
|
|
2090
|
+
redirect_uri,
|
|
2091
|
+
code_challenge,
|
|
2092
|
+
state,
|
|
2093
|
+
client_id: AUTH_CLIENT_ID,
|
|
2094
|
+
response_type: "code",
|
|
2095
|
+
code_challenge_method: "S256",
|
|
2096
|
+
scope: AUTH_SCOPES
|
|
2097
|
+
}).toString();
|
|
2098
|
+
if (opener) await opener(authorizeUrl.toString());
|
|
2099
|
+
else openInBrowser(authorizeUrl.toString());
|
|
2100
|
+
let result = await loop.waitForCode;
|
|
2101
|
+
if (!result.code) throw new Error("No authorization code received");
|
|
2102
|
+
if (result.state !== state) throw new Error("State mismatch");
|
|
2103
|
+
let res = await fetch(`${config.host}/_api/oauth2/token`, {
|
|
2104
|
+
method: "POST",
|
|
2105
|
+
headers: { "content-type": "application/json" },
|
|
2106
|
+
body: JSON.stringify({
|
|
2107
|
+
grant_type: "authorization_code",
|
|
2108
|
+
code: result.code,
|
|
2109
|
+
redirect_uri,
|
|
2110
|
+
client_id: AUTH_CLIENT_ID,
|
|
2111
|
+
code_verifier: verifier
|
|
2112
|
+
})
|
|
2113
|
+
});
|
|
2114
|
+
if (!res.ok) throw new Error(`token exchange failed: ${res.status}`);
|
|
2115
|
+
let tokenResp = await res.json();
|
|
2116
|
+
await updateEntry(tokenResp);
|
|
2117
|
+
}
|
|
2118
|
+
async function refreshAccessToken() {
|
|
2119
|
+
let refresh_token = (await readEntry())?.refresh_token;
|
|
2120
|
+
let res = await fetch(new URL("/_api/oauth2/token", config.host).toString(), {
|
|
2121
|
+
method: "POST",
|
|
2122
|
+
headers: { "content-type": "application/json" },
|
|
2123
|
+
body: JSON.stringify({ grant_type: "refresh_token", refresh_token, client_id: AUTH_CLIENT_ID })
|
|
2124
|
+
});
|
|
2125
|
+
if (!res.ok) throw new Error(`refresh failed: ${res.status}`);
|
|
2126
|
+
let json = await res.json();
|
|
2127
|
+
await updateEntry(json);
|
|
2128
|
+
}
|
|
2129
|
+
async function authenticatedFetch(pathOrUrl, init = {}) {
|
|
2130
|
+
let entry = await readEntry();
|
|
2131
|
+
if (!entry) throw new Error("Not logged in; run `graphene login`");
|
|
2132
|
+
if (!entry.access_token || entry.expires_at < Date.now()) {
|
|
2133
|
+
await refreshAccessToken();
|
|
2134
|
+
entry = await readEntry();
|
|
2135
|
+
}
|
|
2136
|
+
let token = entry?.access_token;
|
|
2137
|
+
if (!token) throw new Error("Failed to obtain access token");
|
|
2138
|
+
let url = new URL(pathOrUrl, config.host);
|
|
2139
|
+
let headers = new Headers(init.headers || {});
|
|
2140
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
2141
|
+
let res = await fetch(url.toString(), { ...init, headers });
|
|
2142
|
+
if (res.status === 401 || res.status === 403) {
|
|
2143
|
+
await refreshAccessToken();
|
|
2144
|
+
token = (await readEntry())?.access_token;
|
|
2145
|
+
if (token) {
|
|
2146
|
+
headers.set("cookie", `access_token=${token}`);
|
|
2147
|
+
res = await fetch(url.toString(), { ...init, headers });
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
return res;
|
|
2151
|
+
}
|
|
2152
|
+
var AUTH_CLIENT_ID, AUTH_SCOPES, cfgDir, credsPath;
|
|
2153
|
+
var init_auth = __esm({
|
|
2154
|
+
"auth.ts"() {
|
|
2155
|
+
"use strict";
|
|
2156
|
+
init_config();
|
|
2157
|
+
AUTH_CLIENT_ID = "connected-app-test-4685a2a0-1cb9-4a81-a6bf-01a3efe7b981";
|
|
2158
|
+
AUTH_SCOPES = "offline_access";
|
|
2159
|
+
cfgDir = process.env.XDG_CONFIG_HOME || path5.join(os2.homedir(), ".config");
|
|
2160
|
+
credsPath = path5.join(cfgDir, "graphene", "credentials.json");
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
// connections/bigQuery.ts
|
|
2165
|
+
var bigQuery_exports = {};
|
|
2166
|
+
__export(bigQuery_exports, {
|
|
2167
|
+
BigQueryConnection: () => BigQueryConnection
|
|
2168
|
+
});
|
|
2169
|
+
import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquery";
|
|
2170
|
+
var BigQueryConnection;
|
|
2171
|
+
var init_bigQuery = __esm({
|
|
2172
|
+
"connections/bigQuery.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
init_config();
|
|
2175
|
+
BigQueryConnection = class {
|
|
2176
|
+
client;
|
|
2177
|
+
constructor(options = {}) {
|
|
2178
|
+
options.projectId ||= config.bigquery?.projectId;
|
|
2179
|
+
if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
|
|
2180
|
+
let parsed = JSON.parse(process.env.GOOGLE_CREDENTIALS_CONTENT);
|
|
2181
|
+
options.projectId = parsed.project_id;
|
|
2182
|
+
options.credentials = parsed;
|
|
2183
|
+
}
|
|
2184
|
+
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
2185
|
+
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
2186
|
+
}
|
|
2187
|
+
async runQuery(sql) {
|
|
2188
|
+
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false });
|
|
2189
|
+
let [rows] = await job.getQueryResults({ maxResults: 1e4 });
|
|
2190
|
+
let metadata = job.metadata || (await job.getMetadata())[0];
|
|
2191
|
+
let totalRows = Number(metadata?.statistics?.query?.totalRows ?? rows.length);
|
|
2192
|
+
rows.forEach((r) => {
|
|
2193
|
+
Object.entries(r).forEach(([k, v]) => {
|
|
2194
|
+
if (v instanceof BigQueryTimestamp) r[k] = v.value;
|
|
2195
|
+
if (v instanceof BigQueryDate) r[k] = v.value;
|
|
2196
|
+
});
|
|
2197
|
+
});
|
|
2198
|
+
return { rows, totalRows };
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
// connections/duckdb.ts
|
|
2205
|
+
var duckdb_exports = {};
|
|
2206
|
+
__export(duckdb_exports, {
|
|
2207
|
+
DuckDBConnection: () => DuckDBConnection
|
|
2208
|
+
});
|
|
2209
|
+
import { promises as fs5 } from "fs";
|
|
1848
2210
|
import path6 from "path";
|
|
2211
|
+
import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue } from "@duckdb/node-api";
|
|
2212
|
+
var DuckDBConnection;
|
|
2213
|
+
var init_duckdb = __esm({
|
|
2214
|
+
"connections/duckdb.ts"() {
|
|
2215
|
+
"use strict";
|
|
2216
|
+
init_config();
|
|
2217
|
+
DuckDBConnection = class {
|
|
2218
|
+
options;
|
|
2219
|
+
ready;
|
|
2220
|
+
connection = null;
|
|
2221
|
+
constructor(options) {
|
|
2222
|
+
this.options = options || {};
|
|
2223
|
+
this.ready = this.initialize();
|
|
2224
|
+
}
|
|
2225
|
+
async initialize() {
|
|
2226
|
+
let dbPath = this.options.path;
|
|
2227
|
+
if (!dbPath) {
|
|
2228
|
+
let files = await fs5.readdir(config.root);
|
|
2229
|
+
dbPath = files.find((f) => f.endsWith(".duckdb"));
|
|
2230
|
+
if (!dbPath) throw new Error("No .duckdb file found in current directory");
|
|
2231
|
+
dbPath = path6.resolve(config.root, dbPath);
|
|
2232
|
+
}
|
|
2233
|
+
let db = await DuckDBInstance.create(":memory:");
|
|
2234
|
+
this.connection = await db.connect();
|
|
2235
|
+
let escapedPath = dbPath.replace(/'/g, "''");
|
|
2236
|
+
await this.connection.run(`attach '${escapedPath}' as graphene_cli (READ_ONLY);`);
|
|
2237
|
+
await this.connection.run("use graphene_cli;");
|
|
2238
|
+
}
|
|
2239
|
+
async runQuery(sql) {
|
|
2240
|
+
await this.ready;
|
|
2241
|
+
let reader = await this.connection.runAndReadAll(sql);
|
|
2242
|
+
let rows = reader.getRowObjects().map((record) => {
|
|
2243
|
+
let out = {};
|
|
2244
|
+
for (let [k, v] of Object.entries(record)) {
|
|
2245
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
2246
|
+
else if (v === null) out[k] = null;
|
|
2247
|
+
else if (v instanceof DuckDBTimestampValue) out[k] = new Date(Number(v.micros / 1000n)).toUTCString();
|
|
2248
|
+
else if (v instanceof DuckDBDateValue) out[k] = v.toString();
|
|
2249
|
+
else if (typeof v === "object") throw new Error(`Unsupported datatype ${v.constructor?.name}`);
|
|
2250
|
+
else out[k] = v;
|
|
2251
|
+
}
|
|
2252
|
+
return out;
|
|
2253
|
+
});
|
|
2254
|
+
return { rows };
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
// connections/snowflake.ts
|
|
2261
|
+
var snowflake_exports = {};
|
|
2262
|
+
__export(snowflake_exports, {
|
|
2263
|
+
SnowflakeConnection: () => SnowflakeConnection
|
|
2264
|
+
});
|
|
2265
|
+
import { createPrivateKey } from "node:crypto";
|
|
2266
|
+
import snowflake from "snowflake-sdk";
|
|
2267
|
+
var SnowflakeConnection;
|
|
2268
|
+
var init_snowflake2 = __esm({
|
|
2269
|
+
"connections/snowflake.ts"() {
|
|
2270
|
+
"use strict";
|
|
2271
|
+
init_config();
|
|
2272
|
+
SnowflakeConnection = class {
|
|
2273
|
+
ready;
|
|
2274
|
+
connection;
|
|
2275
|
+
constructor(opts) {
|
|
2276
|
+
this.ready = this.initialize(opts || {});
|
|
2277
|
+
}
|
|
2278
|
+
async initialize(opts) {
|
|
2279
|
+
let privateKeyPath = process.env.SNOWFLAKE_PRI_KEY_PATH || config.snowflake?.privateKeyPath;
|
|
2280
|
+
let privateKeyPass = process.env.SNOWFLAKE_PRI_PASSPHRASE;
|
|
2281
|
+
let authOptions = {};
|
|
2282
|
+
if (privateKeyPath) {
|
|
2283
|
+
authOptions = { privateKeyPath, privateKeyPass };
|
|
2284
|
+
} else if (opts.privateKey) {
|
|
2285
|
+
let privateKey = createPrivateKey({ key: opts.privateKey, format: "pem", passphrase: privateKeyPass });
|
|
2286
|
+
authOptions = { privateKey: privateKey.export({ format: "pem", type: "pkcs8" }) };
|
|
2287
|
+
}
|
|
2288
|
+
snowflake.configure({ logLevel: process.env.SNOWFLAKE_LOG_LEVEL || "WARN", logFilePath: "/dev/null" });
|
|
2289
|
+
this.connection = snowflake.createConnection({
|
|
2290
|
+
...opts,
|
|
2291
|
+
...config.snowflake || {},
|
|
2292
|
+
...authOptions,
|
|
2293
|
+
authenticator: "SNOWFLAKE_JWT",
|
|
2294
|
+
application: "Graphene"
|
|
2295
|
+
});
|
|
2296
|
+
await new Promise((resolve, reject) => {
|
|
2297
|
+
this.connection.connect((err, conn) => err ? reject(err) : resolve(conn));
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
async runQuery(sql) {
|
|
2301
|
+
await this.ready;
|
|
2302
|
+
return await new Promise((resolve, reject) => {
|
|
2303
|
+
let rows = [];
|
|
2304
|
+
this.connection.execute({
|
|
2305
|
+
sqlText: sql,
|
|
2306
|
+
streamResult: true,
|
|
2307
|
+
complete: (error, statement) => {
|
|
2308
|
+
if (error) {
|
|
2309
|
+
reject(new Error(`Snowflake query failed: ${error.message || error}`));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
let stream = statement.streamRows();
|
|
2313
|
+
stream.on("error", (err) => reject(err));
|
|
2314
|
+
stream.on("readable", function(row) {
|
|
2315
|
+
while ((row = this.read()) !== null) {
|
|
2316
|
+
rows.push(row);
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
stream.on("end", () => {
|
|
2320
|
+
let totalRows = Number(statement.getNumRows());
|
|
2321
|
+
resolve({ rows, totalRows });
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
// connections/index.ts
|
|
2332
|
+
async function runQuery(sql) {
|
|
2333
|
+
if (config.host) {
|
|
2334
|
+
let resp = await authenticatedFetch("/_api/query", {
|
|
2335
|
+
method: "POST",
|
|
2336
|
+
headers: { "Content-Type": "application/json" },
|
|
2337
|
+
body: JSON.stringify({ sql })
|
|
2338
|
+
});
|
|
2339
|
+
return await resp.json();
|
|
2340
|
+
}
|
|
2341
|
+
if (config.dialect === "bigquery") {
|
|
2342
|
+
let mod = await Promise.resolve().then(() => (init_bigQuery(), bigQuery_exports));
|
|
2343
|
+
let conn = new mod.BigQueryConnection();
|
|
2344
|
+
return await conn.runQuery(sql);
|
|
2345
|
+
} else if (config.dialect === "duckdb") {
|
|
2346
|
+
let mod = await Promise.resolve().then(() => (init_duckdb(), duckdb_exports));
|
|
2347
|
+
let conn = new mod.DuckDBConnection({});
|
|
2348
|
+
return await conn.runQuery(sql);
|
|
2349
|
+
} else if (config.dialect === "snowflake") {
|
|
2350
|
+
let mod = await Promise.resolve().then(() => (init_snowflake2(), snowflake_exports));
|
|
2351
|
+
let conn = new mod.SnowflakeConnection({});
|
|
2352
|
+
return await conn.runQuery(sql);
|
|
2353
|
+
} else {
|
|
2354
|
+
throw new Error(`Unsupported dialect: ${config.dialect}`);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
var init_connections = __esm({
|
|
2358
|
+
"connections/index.ts"() {
|
|
2359
|
+
"use strict";
|
|
2360
|
+
init_config();
|
|
2361
|
+
init_auth();
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
// mdCompile.ts
|
|
2366
|
+
import fs6 from "fs";
|
|
2367
|
+
import path7 from "path";
|
|
1849
2368
|
import { visit } from "unist-util-visit";
|
|
1850
2369
|
import sanitizeHtml from "sanitize-html";
|
|
1851
2370
|
function extractQueries() {
|
|
@@ -1922,13 +2441,14 @@ ${content}`;
|
|
|
1922
2441
|
}
|
|
1923
2442
|
function componentNames() {
|
|
1924
2443
|
if (cachedComponentNames) return cachedComponentNames;
|
|
1925
|
-
let files =
|
|
1926
|
-
cachedComponentNames = files.map((f) =>
|
|
2444
|
+
let files = fs6.readdirSync(path7.join(import.meta.dirname, "../ui/components"));
|
|
2445
|
+
cachedComponentNames = files.map((f) => path7.basename(f, ".svelte")).filter((f) => !f.startsWith("_"));
|
|
1927
2446
|
return cachedComponentNames || [];
|
|
1928
2447
|
}
|
|
1929
2448
|
var cachedComponentNames;
|
|
1930
2449
|
var init_mdCompile = __esm({
|
|
1931
2450
|
"mdCompile.ts"() {
|
|
2451
|
+
"use strict";
|
|
1932
2452
|
cachedComponentNames = null;
|
|
1933
2453
|
}
|
|
1934
2454
|
});
|
|
@@ -1940,16 +2460,15 @@ __export(serve2_exports, {
|
|
|
1940
2460
|
});
|
|
1941
2461
|
import { createServer, optimizeDeps } from "vite";
|
|
1942
2462
|
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
1943
|
-
import
|
|
1944
|
-
import
|
|
2463
|
+
import fs7 from "fs-extra";
|
|
2464
|
+
import crypto2 from "crypto";
|
|
1945
2465
|
import { mdsvex } from "mdsvex";
|
|
1946
|
-
import
|
|
2466
|
+
import path8 from "path";
|
|
1947
2467
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1948
2468
|
async function serve2() {
|
|
1949
|
-
uiRoot =
|
|
2469
|
+
uiRoot = path8.join(fileURLToPath2(import.meta.url), "../../ui");
|
|
1950
2470
|
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));
|
|
2471
|
+
await fs7.ensureDir(path8.resolve(config.root, "node_modules/.graphene"));
|
|
1953
2472
|
let server = await createServer({
|
|
1954
2473
|
root: config.root,
|
|
1955
2474
|
plugins: [
|
|
@@ -1970,7 +2489,7 @@ async function serve2() {
|
|
|
1970
2489
|
updateWorkspacePlugin,
|
|
1971
2490
|
mockFilesForTests()
|
|
1972
2491
|
],
|
|
1973
|
-
publicDir:
|
|
2492
|
+
publicDir: path8.resolve(uiRoot),
|
|
1974
2493
|
server: {
|
|
1975
2494
|
port,
|
|
1976
2495
|
fs: { strict: false },
|
|
@@ -1978,7 +2497,7 @@ async function serve2() {
|
|
|
1978
2497
|
},
|
|
1979
2498
|
resolve: {
|
|
1980
2499
|
alias: {
|
|
1981
|
-
graphene:
|
|
2500
|
+
graphene: path8.resolve(uiRoot, "web.js")
|
|
1982
2501
|
}
|
|
1983
2502
|
}
|
|
1984
2503
|
// optimizeDeps: { // this seems prudent in tests, but currently breaks because ssf needs to be optimized, even in tests
|
|
@@ -1988,9 +2507,7 @@ async function serve2() {
|
|
|
1988
2507
|
});
|
|
1989
2508
|
await optimizeDeps(server.config);
|
|
1990
2509
|
await server.listen();
|
|
1991
|
-
|
|
1992
|
-
console.log(`Server running at http://localhost:${port}`);
|
|
1993
|
-
}
|
|
2510
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
1994
2511
|
return server;
|
|
1995
2512
|
}
|
|
1996
2513
|
async function handleQuery(req, res) {
|
|
@@ -2007,14 +2524,13 @@ async function handleQuery(req, res) {
|
|
|
2007
2524
|
}
|
|
2008
2525
|
if (queries.length > 1) throw new Error("Found multiple queries, which could be a parsing error");
|
|
2009
2526
|
let sql = toSql(queries[0], params);
|
|
2010
|
-
let hash =
|
|
2527
|
+
let hash = crypto2.createHash("SHA1").update(sql).digest("hex");
|
|
2011
2528
|
res.setHeader("ETag", hash);
|
|
2012
2529
|
if (hashes.includes(hash) && req.headers["cache-control"] != "no-cache") {
|
|
2013
2530
|
res.statusCode = 304;
|
|
2014
2531
|
return res.end();
|
|
2015
2532
|
}
|
|
2016
|
-
let
|
|
2017
|
-
let queryResults = await connection.runQuery(sql);
|
|
2533
|
+
let queryResults = await runQuery(sql);
|
|
2018
2534
|
let totalRows = queryResults.totalRows ?? queryResults.rows.length;
|
|
2019
2535
|
if (totalRows > queryResults.rows.length) throw new Error("Query returns too many rows");
|
|
2020
2536
|
let fields = queries[0].fields.map((f) => ({ name: f.name, type: f.type }));
|
|
@@ -2066,6 +2582,7 @@ function mockFilesForTests() {
|
|
|
2066
2582
|
var uiRoot, workspaceLoadPromise, updateWorkspacePlugin, handleRequestPlugin;
|
|
2067
2583
|
var init_serve2 = __esm({
|
|
2068
2584
|
"serve2.ts"() {
|
|
2585
|
+
"use strict";
|
|
2069
2586
|
init_core();
|
|
2070
2587
|
init_connections();
|
|
2071
2588
|
init_mdCompile();
|
|
@@ -2091,8 +2608,8 @@ var init_serve2 = __esm({
|
|
|
2091
2608
|
if (pathName == "/_api/query") return await handleQuery(req, res);
|
|
2092
2609
|
if (pathName == "/__ct") return await handlePage(s, res, "__ct", false);
|
|
2093
2610
|
if (!pathName || pathName == "/") pathName = "index";
|
|
2094
|
-
let mdPath =
|
|
2095
|
-
if (await
|
|
2611
|
+
let mdPath = path8.join(config.root, pathName + ".md");
|
|
2612
|
+
if (await fs7.exists(mdPath)) {
|
|
2096
2613
|
await handlePage(s, res, mdPath, true);
|
|
2097
2614
|
} else {
|
|
2098
2615
|
next();
|
|
@@ -2113,11 +2630,12 @@ init_printer();
|
|
|
2113
2630
|
init_core();
|
|
2114
2631
|
init_config();
|
|
2115
2632
|
init_background();
|
|
2116
|
-
init_connections();
|
|
2117
2633
|
init_check();
|
|
2634
|
+
init_connections();
|
|
2635
|
+
init_auth();
|
|
2118
2636
|
import { Command } from "commander";
|
|
2119
|
-
import
|
|
2120
|
-
import
|
|
2637
|
+
import fs8 from "fs-extra";
|
|
2638
|
+
import path9 from "path";
|
|
2121
2639
|
var program = new Command();
|
|
2122
2640
|
program.name("graphene").description("Graphene CLI").version("1.0.0");
|
|
2123
2641
|
program.hook("preAction", async () => {
|
|
@@ -2139,26 +2657,31 @@ program.command("run").description("Run a query against your database").argument
|
|
|
2139
2657
|
let queries = analyze(gsql);
|
|
2140
2658
|
if (!validQuery(queries)) return;
|
|
2141
2659
|
let sql = toSql(queries[0]);
|
|
2142
|
-
let
|
|
2143
|
-
let res = await connection.runQuery(sql);
|
|
2660
|
+
let res = await runQuery(sql);
|
|
2144
2661
|
printTable(res.rows);
|
|
2145
2662
|
});
|
|
2146
|
-
program.command("serve").description("Run the local server").option("--
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
await mod.serve2();
|
|
2150
|
-
} else {
|
|
2663
|
+
program.command("serve").description("Run the local server").option("--bg", "Run the server in the background").action(async (options) => {
|
|
2664
|
+
await stopGrapheneIfRunning();
|
|
2665
|
+
if (options.bg) {
|
|
2151
2666
|
await runServeInBackground();
|
|
2152
2667
|
process.exit(0);
|
|
2668
|
+
} else {
|
|
2669
|
+
let mod = await Promise.resolve().then(() => (init_serve2(), serve2_exports));
|
|
2670
|
+
await mod.serve2();
|
|
2153
2671
|
}
|
|
2154
2672
|
});
|
|
2155
2673
|
program.command("stop").description("Stop the local server").action(async () => {
|
|
2156
|
-
await stopGrapheneIfRunning(
|
|
2674
|
+
await stopGrapheneIfRunning();
|
|
2157
2675
|
});
|
|
2158
2676
|
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
2677
|
let res = await check({ mdArg, chart: options.chart });
|
|
2160
2678
|
process.exit(res ? 0 : 1);
|
|
2161
2679
|
});
|
|
2680
|
+
program.command("login").description("Log in to Graphene Cloud").action(async () => {
|
|
2681
|
+
await loginPkce();
|
|
2682
|
+
console.log("Successfully logged in");
|
|
2683
|
+
process.exit(0);
|
|
2684
|
+
});
|
|
2162
2685
|
program.parse(process.argv);
|
|
2163
2686
|
async function readInput(arg) {
|
|
2164
2687
|
if (!arg || arg === "-") {
|
|
@@ -2170,9 +2693,9 @@ async function readInput(arg) {
|
|
|
2170
2693
|
process.stdin.resume();
|
|
2171
2694
|
});
|
|
2172
2695
|
}
|
|
2173
|
-
let absolutePath =
|
|
2174
|
-
if (
|
|
2175
|
-
return await
|
|
2696
|
+
let absolutePath = path9.resolve(arg);
|
|
2697
|
+
if (fs8.existsSync(absolutePath)) {
|
|
2698
|
+
return await fs8.promises.readFile(absolutePath, "utf-8");
|
|
2176
2699
|
}
|
|
2177
2700
|
return arg;
|
|
2178
2701
|
}
|