@graphenedata/cli 0.0.6 → 0.0.8
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 +39 -6
- package/dist/cli/cli.js +407 -157
- package/dist/docs/graphene.md +4 -38
- package/dist/ui/app.css +33 -2
- package/dist/ui/component-utilities/echarts.js +10 -1
- package/dist/ui/internal/NavSidebar.svelte +383 -0
- package/dist/ui/internal/telemetry.ts +2 -1
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
- package/dist/ui/web.js +15 -16
- package/package.json +7 -6
- package/dist/ui/playwright.config.ts +0 -30
- /package/dist/ui/{assets → public}/favicon.ico +0 -0
package/dist/cli/cli.js
CHANGED
|
@@ -150,15 +150,17 @@ function setConfig(cfg) {
|
|
|
150
150
|
else if (cfg.snowflake) dialect = "snowflake";
|
|
151
151
|
else if (cfg.duckdb) dialect = "duckdb";
|
|
152
152
|
Object.keys(config).forEach((key) => delete config[key]);
|
|
153
|
-
Object.assign(config, cfg
|
|
153
|
+
Object.assign(config, cfg);
|
|
154
|
+
config.dialect = dialect;
|
|
155
|
+
config.root ||= process.cwd();
|
|
156
|
+
config.ignoredFiles ||= ["agents.md", "claude.md"];
|
|
154
157
|
}
|
|
155
158
|
function loadConfig(dir) {
|
|
156
159
|
if (config.root) return;
|
|
157
160
|
let packageJsonObject = {};
|
|
158
161
|
try {
|
|
159
162
|
let txt2 = fs.readFileSync(path.join(dir, "package.json"), "utf8");
|
|
160
|
-
|
|
161
|
-
packageJsonObject = all.graphene || {};
|
|
163
|
+
packageJsonObject = JSON.parse(txt2).graphene || {};
|
|
162
164
|
} catch {
|
|
163
165
|
console.warn("No package.json found in current directory");
|
|
164
166
|
}
|
|
@@ -172,7 +174,7 @@ var init_config = __esm({
|
|
|
172
174
|
}
|
|
173
175
|
});
|
|
174
176
|
|
|
175
|
-
// ../lang/
|
|
177
|
+
// ../lang/functionDefs.ts
|
|
176
178
|
import { DUCKDB_DIALECT_FUNCTIONS, GlobalNameSpace, DialectNameSpace, getDialect } from "@graphenedata/malloy";
|
|
177
179
|
function findOverloads(name, dialect) {
|
|
178
180
|
if (!dialectNamespaces.has(dialect)) {
|
|
@@ -183,8 +185,8 @@ function findOverloads(name, dialect) {
|
|
|
183
185
|
return res?.entry ? res.entry.overloads : [];
|
|
184
186
|
}
|
|
185
187
|
var globalNamespace, dialectNamespaces, BIGQUERY_DIALECT_FUNCTIONS;
|
|
186
|
-
var
|
|
187
|
-
"../lang/
|
|
188
|
+
var init_functionDefs = __esm({
|
|
189
|
+
"../lang/functionDefs.ts"() {
|
|
188
190
|
"use strict";
|
|
189
191
|
globalNamespace = new GlobalNameSpace();
|
|
190
192
|
dialectNamespaces = /* @__PURE__ */ new Map();
|
|
@@ -254,8 +256,9 @@ var init_functions = __esm({
|
|
|
254
256
|
impl: { function: "TIMESTAMP_DIFF" }
|
|
255
257
|
},
|
|
256
258
|
"date_trunc": {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
+
generic: { "T": ["date", "timestamp"] },
|
|
260
|
+
takes: { "date": { generic: "T" }, "unit": { sql_native: "kw" } },
|
|
261
|
+
returns: { generic: "T" },
|
|
259
262
|
impl: { sql: "DATE_TRUNC(${date}, ${unit})" }
|
|
260
263
|
},
|
|
261
264
|
"current_date": {
|
|
@@ -315,6 +318,112 @@ var init_functions = __esm({
|
|
|
315
318
|
}
|
|
316
319
|
});
|
|
317
320
|
|
|
321
|
+
// ../lang/functions.ts
|
|
322
|
+
function analyzeFunctionCall(expr, scope) {
|
|
323
|
+
let rawName = txt(expr.getChild("Identifier")).toLowerCase();
|
|
324
|
+
let argNodes = expr.getChildren("Expression");
|
|
325
|
+
let name = rawName;
|
|
326
|
+
let overload = findOverloads(name, config.dialect).find((o) => {
|
|
327
|
+
return o.params.length == argNodes.length || !!o.params.find((p) => p.isVariadic);
|
|
328
|
+
});
|
|
329
|
+
let args = argNodes.map((node, idx) => {
|
|
330
|
+
let firstType = overload?.params[idx]?.allowedTypes[0];
|
|
331
|
+
if (firstType?.type === "sql native" && firstType?.rawType === "kw") {
|
|
332
|
+
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
333
|
+
} else {
|
|
334
|
+
let argExpr = analyzeExpression(node, scope);
|
|
335
|
+
let allowed = overload?.params[idx]?.allowedTypes.map((at) => at.type);
|
|
336
|
+
if (allowed) checkTypes(argExpr, allowed, node);
|
|
337
|
+
return argExpr;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
let type = overload?.returnType.type;
|
|
341
|
+
if (type == "generic") type = args[0]?.type || "string";
|
|
342
|
+
if (type && !isSupportedType(type)) {
|
|
343
|
+
return diag(expr, `Unsupported function return type ${type} from function ${name}`, errExpr);
|
|
344
|
+
}
|
|
345
|
+
let structPaths = /* @__PURE__ */ new Set();
|
|
346
|
+
args.forEach((a) => walkExpression(a, (e) => {
|
|
347
|
+
if (e.node != "field") return;
|
|
348
|
+
structPaths.add(e.path.slice(0, -1).join(".") || scope.table.name);
|
|
349
|
+
}));
|
|
350
|
+
let ret;
|
|
351
|
+
let percentileMatch = /^p(\d+)$/.exec(rawName);
|
|
352
|
+
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
353
|
+
let type2 = "number", typeDef;
|
|
354
|
+
if (["min", "max", "avg"].includes(name.toLowerCase())) {
|
|
355
|
+
type2 = args[0].type;
|
|
356
|
+
typeDef = args[0].typeDef;
|
|
357
|
+
}
|
|
358
|
+
ret = { node: "aggregate", function: name, e: args[0], type: type2, typeDef, isAgg: true };
|
|
359
|
+
} else if (percentileMatch) {
|
|
360
|
+
ret = analyzePercentile(expr, scope, percentileMatch[1], argNodes);
|
|
361
|
+
} else if (overload && type) {
|
|
362
|
+
ret = {
|
|
363
|
+
node: "function_call",
|
|
364
|
+
type,
|
|
365
|
+
name,
|
|
366
|
+
overload,
|
|
367
|
+
expressionType: overload.returnType.expressionType || "scalar",
|
|
368
|
+
kids: { args },
|
|
369
|
+
isAgg: overload.returnType.expressionType == "aggregate" || args.some((a) => a.isAgg)
|
|
370
|
+
};
|
|
371
|
+
} else {
|
|
372
|
+
return diag(expr, `Unknown function: ${name}`, errExpr);
|
|
373
|
+
}
|
|
374
|
+
if (structPaths.size > 1 && (ret.node == "aggregate" || ret.expressionType == "aggregate")) {
|
|
375
|
+
return diag(expr, "Graphene only supports a single table within aggregates. This one has: " + Array.from(structPaths).join(", "), errExpr);
|
|
376
|
+
}
|
|
377
|
+
let foriegnPaths = Array.from(structPaths).filter((p) => p != scope.table.name);
|
|
378
|
+
if (foriegnPaths.length > 0) ret.structPath = foriegnPaths[0].split(".");
|
|
379
|
+
return ret;
|
|
380
|
+
}
|
|
381
|
+
function isSupportedType(value) {
|
|
382
|
+
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic", "interval"];
|
|
383
|
+
return supported.includes(value);
|
|
384
|
+
}
|
|
385
|
+
function analyzePercentile(callNode, scope, digits, argNodes) {
|
|
386
|
+
let frac = Number(`0.${digits}`);
|
|
387
|
+
if (Number(digits) == 100) return diag(callNode, "p100 is not allowed", errExpr);
|
|
388
|
+
if (Number(digits) == 0) return diag(callNode, "p0 is not allowed", errExpr);
|
|
389
|
+
if (config.dialect == "bigquery" && frac > 0.99) return diag(callNode, "BigQuery only supports up to p99", errExpr);
|
|
390
|
+
let valueExpr = analyzeExpression(argNodes[0], scope);
|
|
391
|
+
checkTypes(valueExpr, ["number"], argNodes[0]);
|
|
392
|
+
let src;
|
|
393
|
+
switch (config.dialect) {
|
|
394
|
+
case "duckdb":
|
|
395
|
+
src = ["quantile_cont(", `, ${frac})`];
|
|
396
|
+
break;
|
|
397
|
+
case "bigquery": {
|
|
398
|
+
src = ["approx_quantiles(", `, 100)[OFFSET(${Math.round(frac * 10)})]`];
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
case "snowflake":
|
|
402
|
+
src = [`PERCENTILE_CONT(${frac}) WITHIN GROUP (ORDER BY `, ")"];
|
|
403
|
+
break;
|
|
404
|
+
default:
|
|
405
|
+
return diag(callNode, `Percentile functions are not supported for dialect ${config.dialect}`, errExpr);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
node: "genericSQLExpr",
|
|
409
|
+
kids: { args: [valueExpr] },
|
|
410
|
+
src,
|
|
411
|
+
type: "number",
|
|
412
|
+
isAgg: true
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
var errExpr;
|
|
416
|
+
var init_functions = __esm({
|
|
417
|
+
"../lang/functions.ts"() {
|
|
418
|
+
"use strict";
|
|
419
|
+
init_config();
|
|
420
|
+
init_functionDefs();
|
|
421
|
+
init_util();
|
|
422
|
+
init_analyze();
|
|
423
|
+
errExpr = { node: "error", type: "error" };
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
318
427
|
// ../lang/temporalLiterals.ts
|
|
319
428
|
function parseTemporalLiteral(value, expected) {
|
|
320
429
|
let raw = (value ?? "").trim();
|
|
@@ -598,9 +707,9 @@ function findTables(fi) {
|
|
|
598
707
|
fi.tables = [];
|
|
599
708
|
let nodes = tn.getChildren("TableStatement").concat(tn.getChildren("ViewStatement"));
|
|
600
709
|
for (let syntaxNode of nodes) {
|
|
601
|
-
let name = txt(syntaxNode.getChild("
|
|
710
|
+
let name = txt(syntaxNode.getChild("Ref"));
|
|
602
711
|
if (Object.values(FILE_MAP).find((f) => f.tables.find((t) => t.name == name))) {
|
|
603
|
-
diag(syntaxNode.getChild("
|
|
712
|
+
diag(syntaxNode.getChild("Ref"), `Table "${name}" is already defined`);
|
|
604
713
|
}
|
|
605
714
|
let table2 = makeTable(name, syntaxNode.getChild("QueryStatement") ? "query_source" : "table");
|
|
606
715
|
table2.metadata = extractLeadingMetadata(syntaxNode);
|
|
@@ -622,12 +731,11 @@ function addColumnField(table2, node) {
|
|
|
622
731
|
table2.primaryKey = name;
|
|
623
732
|
}
|
|
624
733
|
let type = convertDataType(txt(node.getChild("DataType")));
|
|
625
|
-
if (!type) diag(node, `Unsupported data type: ${txt(node.getChild("DataType"))}`);
|
|
626
|
-
|
|
627
|
-
return addFieldToTable(table2, field, node);
|
|
734
|
+
if (!type) return diag(node, `Unsupported data type: ${txt(node.getChild("DataType"))}`);
|
|
735
|
+
addFieldToTable(table2, { name, type, metadata: extractLeadingMetadata(node) }, node);
|
|
628
736
|
}
|
|
629
737
|
function addJoinField(table2, node) {
|
|
630
|
-
let nameNode = node.getChild("Alias") || node.getChild("Identifier");
|
|
738
|
+
let nameNode = node.getChild("Alias") || node.getChild("Ref").getChildren("Identifier").pop();
|
|
631
739
|
return addFieldToTable(table2, { name: txt(nameNode) }, node);
|
|
632
740
|
}
|
|
633
741
|
function addComputedField(table2, node) {
|
|
@@ -636,19 +744,17 @@ function addComputedField(table2, node) {
|
|
|
636
744
|
}
|
|
637
745
|
function addFieldToTable(table2, field, node) {
|
|
638
746
|
if (table2.fields.find((f) => f.name == field.name)) {
|
|
639
|
-
diag(node, `Table already has a field called "${field.name}"`);
|
|
640
|
-
return null;
|
|
747
|
+
return diag(node, `Table already has a field called "${field.name}"`);
|
|
641
748
|
}
|
|
642
749
|
table2.fields.push(field);
|
|
643
750
|
FIELD_NODE_MAP.set(field, node);
|
|
644
|
-
return field;
|
|
645
751
|
}
|
|
646
752
|
function applyExtends(fi) {
|
|
647
753
|
fi.tree.topNode.getChildren("ExtendStatement").forEach((node) => {
|
|
648
|
-
let tableName = txt(node.getChild("
|
|
754
|
+
let tableName = txt(node.getChild("Ref"));
|
|
649
755
|
let target = lookupTable(tableName, node);
|
|
650
756
|
if (!target) {
|
|
651
|
-
return diag(node.getChild("
|
|
757
|
+
return diag(node.getChild("Ref") || node, `Cannot extend unknown table "${tableName}"`);
|
|
652
758
|
}
|
|
653
759
|
node.getChildren("JoinDef").forEach((jn) => addJoinField(target, jn));
|
|
654
760
|
node.getChildren("ComputedDef").forEach((cn) => addComputedField(target, cn));
|
|
@@ -675,14 +781,14 @@ function analyzeField(field, table2) {
|
|
|
675
781
|
analysisQueue.add(field);
|
|
676
782
|
if (node.name == "JoinDef") {
|
|
677
783
|
field = field;
|
|
678
|
-
let target = lookupTable(txt(node.getChild("
|
|
784
|
+
let target = lookupTable(txt(node.getChild("Ref")), node);
|
|
679
785
|
if (!target) return diag(node, "Unknown table to join");
|
|
680
786
|
if (target.type == "query_source") analyzeTable(target);
|
|
681
787
|
let joinTypeStr = txt(node.getChild("JoinType")).replace(/\s+/g, " ");
|
|
682
788
|
let jt = { "join many": "many", "join one": "one" }[joinTypeStr];
|
|
683
789
|
if (!jt) return diag(node, "Unknown join type");
|
|
684
790
|
Object.assign(field, target, { name: field.name, join: jt });
|
|
685
|
-
field.onExpression = analyzeExpression(node.getChild("
|
|
791
|
+
field.onExpression = analyzeExpression(node.getChild("BinaryExpression"), { table: table2, outputFields: [] });
|
|
686
792
|
}
|
|
687
793
|
if (node.name == "ComputedDef") {
|
|
688
794
|
let e = analyzeExpression(node.getChild("Expression"), { table: table2, outputFields: [] });
|
|
@@ -711,7 +817,7 @@ function analyzeQuery(queryNode) {
|
|
|
711
817
|
TABLE_NODE_MAP.set(scope.table, froms[0].getChild("SubqueryExpression"));
|
|
712
818
|
analyzeTable(scope.table);
|
|
713
819
|
} else {
|
|
714
|
-
baseTableName = txt(froms[0].getChild("
|
|
820
|
+
baseTableName = txt(froms[0].getChild("Ref"));
|
|
715
821
|
scope.table = lookupTable(baseTableName, froms[0]);
|
|
716
822
|
if (!scope.table) return diag(froms[0], `could not find table "${baseTableName}"`);
|
|
717
823
|
NODE_ENTITY_MAP.set(froms[0], { entityType: "table", table: scope.table });
|
|
@@ -799,7 +905,7 @@ function analyzeQuery(queryNode) {
|
|
|
799
905
|
}
|
|
800
906
|
function analyzeExpression(expr, scope) {
|
|
801
907
|
if (expr.type.isError) {
|
|
802
|
-
return diag(expr, "Invalid expression",
|
|
908
|
+
return diag(expr, "Invalid expression", errExpr2);
|
|
803
909
|
}
|
|
804
910
|
switch (expr.name) {
|
|
805
911
|
case "Number":
|
|
@@ -814,7 +920,7 @@ function analyzeExpression(expr, scope) {
|
|
|
814
920
|
return { node: "parameter", path: [txt(expr).slice(1)], type: "string" };
|
|
815
921
|
case "Ref": {
|
|
816
922
|
let field = lookupField(expr, scope);
|
|
817
|
-
if (!field) return
|
|
923
|
+
if (!field) return errExpr2;
|
|
818
924
|
let type = field.type || "unknown";
|
|
819
925
|
let typeInfo = { type };
|
|
820
926
|
if (type === "date" || type === "timestamp") typeInfo.typeDef = { type };
|
|
@@ -828,9 +934,9 @@ function analyzeExpression(expr, scope) {
|
|
|
828
934
|
let extractExprNode = expr.getChild("Expression");
|
|
829
935
|
let e = analyzeExpression(extractExprNode, scope);
|
|
830
936
|
checkTypes(e, ["date", "timestamp"], extractExprNode);
|
|
831
|
-
if (!isTemporalType(e.type) || !e.typeDef) return diag(expr, "Expression must be a date or timestamp",
|
|
937
|
+
if (!isTemporalType(e.type) || !e.typeDef) return diag(expr, "Expression must be a date or timestamp", errExpr2);
|
|
832
938
|
let units = txt(expr.getChild("ExtractUnit")).replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
833
|
-
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract",
|
|
939
|
+
if (!isExtractUnit(units)) return diag(expr, "Not a valid unit to extract", errExpr2);
|
|
834
940
|
return { node: "extract", type: "number", units, e, isAgg: false };
|
|
835
941
|
}
|
|
836
942
|
case "FunctionCall":
|
|
@@ -863,6 +969,7 @@ function analyzeExpression(expr, scope) {
|
|
|
863
969
|
checkTypes(right2, ["number"], expr.lastChild);
|
|
864
970
|
}
|
|
865
971
|
if (op == "<" || op == "<=" || op == ">" || op == ">=" || op == "=" || op == "!=" || op == "<>") {
|
|
972
|
+
if (op == "<>") op = "!=";
|
|
866
973
|
ensureSameType(left2, expr.firstChild, right2, expr.lastChild);
|
|
867
974
|
type = "boolean";
|
|
868
975
|
}
|
|
@@ -884,7 +991,7 @@ function analyzeExpression(expr, scope) {
|
|
|
884
991
|
if (opTxt === "not") return { node: "not", e: child, type: "boolean", isAgg: child.isAgg };
|
|
885
992
|
if (opTxt === "-") return { node: "unary-", e: child, type: child.type, isAgg: child.isAgg };
|
|
886
993
|
if (opTxt === "+") return { node: "()", e: child, type: child.type, isAgg: child.isAgg };
|
|
887
|
-
return diag(expr, `Unknown unary operator: ${opTxt}`,
|
|
994
|
+
return diag(expr, `Unknown unary operator: ${opTxt}`, errExpr2);
|
|
888
995
|
}
|
|
889
996
|
case "CaseExpression": {
|
|
890
997
|
let caseValue = expr.getChild("Expression");
|
|
@@ -919,83 +1026,28 @@ function analyzeExpression(expr, scope) {
|
|
|
919
1026
|
}
|
|
920
1027
|
case "SubqueryExpression":
|
|
921
1028
|
default:
|
|
922
|
-
return diag(expr, `Unsupported expression "${expr.name}": ${txt(expr)}`,
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function analyzeFunctionCall(expr, scope) {
|
|
926
|
-
let name = txt(expr.getChild("Identifier")).toLowerCase();
|
|
927
|
-
let argNodes = expr.getChildren("Expression");
|
|
928
|
-
let overload = findOverloads(name, config.dialect).find((o) => {
|
|
929
|
-
return o.params.length == argNodes.length || !!o.params.find((p) => p.isVariadic);
|
|
930
|
-
});
|
|
931
|
-
let args = argNodes.map((node, idx) => {
|
|
932
|
-
let firstType = overload?.params[idx]?.allowedTypes[0];
|
|
933
|
-
if (firstType?.type === "sql native" && firstType?.rawType === "kw") {
|
|
934
|
-
return { node: "genericSQLExpr", kids: { args: [] }, type: "sql native", src: [txt(node)], isAgg: false };
|
|
935
|
-
} else {
|
|
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;
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
let type = overload?.returnType.type;
|
|
943
|
-
if (type == "generic") type = args[0]?.type || "string";
|
|
944
|
-
if (type && !isSupportedType(type)) {
|
|
945
|
-
return diag(expr, `Unsupported function return type ${type} from function ${name}`, errExpr);
|
|
946
|
-
}
|
|
947
|
-
let structPaths = /* @__PURE__ */ new Set();
|
|
948
|
-
args.forEach((a) => walkExpression(a, (e) => {
|
|
949
|
-
if (e.node != "field") return;
|
|
950
|
-
structPaths.add(e.path.slice(0, -1).join(".") || scope.table.name);
|
|
951
|
-
}));
|
|
952
|
-
let ret;
|
|
953
|
-
if (["count", "min", "max", "avg", "sum"].includes(name.toLowerCase())) {
|
|
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 };
|
|
960
|
-
} else if (overload && type) {
|
|
961
|
-
ret = {
|
|
962
|
-
node: "function_call",
|
|
963
|
-
type,
|
|
964
|
-
name,
|
|
965
|
-
overload,
|
|
966
|
-
expressionType: overload.returnType.expressionType || "scalar",
|
|
967
|
-
kids: { args },
|
|
968
|
-
isAgg: overload.returnType.expressionType == "aggregate" || args.some((a) => a.isAgg)
|
|
969
|
-
};
|
|
970
|
-
} else {
|
|
971
|
-
return diag(expr, `Unknown function: ${name}`, errExpr);
|
|
972
|
-
}
|
|
973
|
-
if (structPaths.size > 1 && (ret.node == "aggregate" || ret.expressionType == "aggregate")) {
|
|
974
|
-
return diag(expr, "Graphene only supports a single table within aggregates. This one has: " + Array.from(structPaths).join(", "), errExpr);
|
|
1029
|
+
return diag(expr, `Unsupported expression "${expr.name}": ${txt(expr)}`, errExpr2);
|
|
975
1030
|
}
|
|
976
|
-
let foriegnPaths = Array.from(structPaths).filter((p) => p != scope.table.name);
|
|
977
|
-
if (foriegnPaths.length > 0) ret.structPath = foriegnPaths[0].split(".");
|
|
978
|
-
return ret;
|
|
979
1031
|
}
|
|
980
1032
|
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",
|
|
1033
|
+
if (left2.type !== "date" && left2.type !== "timestamp") return diag(node, "Expected left side to be a date or timestamp", errExpr2);
|
|
982
1034
|
let units = left2.type === "timestamp" ? "second" : "day";
|
|
983
1035
|
if (right2.node == "stringLiteral") {
|
|
984
1036
|
units = parseTemporal(right2);
|
|
985
1037
|
if (right2.node == "stringLiteral") {
|
|
986
|
-
return diag(node, "Could not parse interval",
|
|
1038
|
+
return diag(node, "Could not parse interval", errExpr2);
|
|
987
1039
|
}
|
|
988
1040
|
}
|
|
989
1041
|
if (right2.type == "date" || right2.type == "timestamp") {
|
|
990
|
-
if (op !== "-") return diag(node, "Only subtraction between dates is supported",
|
|
991
|
-
if (right2.type !== left2.type) return diag(node, `Expected right side to be a ${left2.type}`,
|
|
1042
|
+
if (op !== "-") return diag(node, "Only subtraction between dates is supported", errExpr2);
|
|
1043
|
+
if (right2.type !== left2.type) return diag(node, `Expected right side to be a ${left2.type}`, errExpr2);
|
|
992
1044
|
return { node: "timeDiff", kids: { left: left2, right: right2 }, units, type: "interval", isAgg: false };
|
|
993
1045
|
}
|
|
994
1046
|
if (right2.type == "interval") {
|
|
995
1047
|
let typeDef = { type: left2.type };
|
|
996
1048
|
return { node: "delta", kids: { base: left2, delta: right2 }, op, units, type: left2.type, typeDef, isAgg: false };
|
|
997
1049
|
}
|
|
998
|
-
return diag(node, "Expected right side to be a date or interval",
|
|
1050
|
+
return diag(node, "Expected right side to be a date or interval", errExpr2);
|
|
999
1051
|
}
|
|
1000
1052
|
function ensureSameType(left2, leftNode, right2, rightNode) {
|
|
1001
1053
|
if (left2.type === "error" || right2.type === "error") return;
|
|
@@ -1021,10 +1073,6 @@ function checkTypes(expr, expected, node) {
|
|
|
1021
1073
|
return Object.assign(expr, { node: "numberLiteral", literal: parsed.quantity.toString(), type: "interval", intervalUnit: parsed.unit });
|
|
1022
1074
|
} else diag(node, `Expected types: ${expected.join(", ")}`);
|
|
1023
1075
|
}
|
|
1024
|
-
function isSupportedType(value) {
|
|
1025
|
-
let supported = ["string", "number", "boolean", "date", "timestamp", "json", "sql native", "error", "array", "record", "null", "generic", "interval"];
|
|
1026
|
-
return supported.includes(value);
|
|
1027
|
-
}
|
|
1028
1076
|
function lookupField(expr, scope) {
|
|
1029
1077
|
let pathNodes = expr.getChildren("Identifier");
|
|
1030
1078
|
let fieldNode = pathNodes.pop();
|
|
@@ -1069,7 +1117,7 @@ function lookupTable(name, node) {
|
|
|
1069
1117
|
}
|
|
1070
1118
|
}
|
|
1071
1119
|
function clearWorkspace() {
|
|
1072
|
-
FILE_MAP
|
|
1120
|
+
Object.keys(FILE_MAP).forEach((k) => delete FILE_MAP[k]);
|
|
1073
1121
|
TABLE_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
1074
1122
|
diagnostics = [];
|
|
1075
1123
|
}
|
|
@@ -1124,6 +1172,8 @@ function convertDataType(dataType) {
|
|
|
1124
1172
|
return "number";
|
|
1125
1173
|
case "FLOAT64":
|
|
1126
1174
|
return "number";
|
|
1175
|
+
case "BOOL":
|
|
1176
|
+
return "boolean";
|
|
1127
1177
|
case "BOOLEAN":
|
|
1128
1178
|
return "boolean";
|
|
1129
1179
|
case "DATE":
|
|
@@ -1156,7 +1206,7 @@ function convertDataType(dataType) {
|
|
|
1156
1206
|
return null;
|
|
1157
1207
|
}
|
|
1158
1208
|
}
|
|
1159
|
-
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue,
|
|
1209
|
+
var FILE_MAP, diagnostics, TABLE_NODE_MAP, FIELD_NODE_MAP, NODE_ENTITY_MAP, analysisQueue, errExpr2;
|
|
1160
1210
|
var init_analyze = __esm({
|
|
1161
1211
|
"../lang/analyze.ts"() {
|
|
1162
1212
|
"use strict";
|
|
@@ -1172,7 +1222,7 @@ var init_analyze = __esm({
|
|
|
1172
1222
|
FIELD_NODE_MAP = /* @__PURE__ */ new WeakMap();
|
|
1173
1223
|
NODE_ENTITY_MAP = new NodeWeakMap();
|
|
1174
1224
|
analysisQueue = /* @__PURE__ */ new Set();
|
|
1175
|
-
|
|
1225
|
+
errExpr2 = { node: "error", type: "error" };
|
|
1176
1226
|
}
|
|
1177
1227
|
});
|
|
1178
1228
|
|
|
@@ -1182,10 +1232,10 @@ var init_parser_terms = __esm({
|
|
|
1182
1232
|
"../lang/parser.terms.js"() {
|
|
1183
1233
|
"use strict";
|
|
1184
1234
|
table = 6;
|
|
1185
|
-
primary_key =
|
|
1186
|
-
join =
|
|
1187
|
-
as =
|
|
1188
|
-
on =
|
|
1235
|
+
primary_key = 12;
|
|
1236
|
+
join = 16;
|
|
1237
|
+
as = 22;
|
|
1238
|
+
on = 25;
|
|
1189
1239
|
or = 31;
|
|
1190
1240
|
and = 34;
|
|
1191
1241
|
like = 43;
|
|
@@ -1266,24 +1316,24 @@ var init_parser = __esm({
|
|
|
1266
1316
|
"../lang/parser.js"() {
|
|
1267
1317
|
"use strict";
|
|
1268
1318
|
init_tokens();
|
|
1269
|
-
spec_Identifier = { __proto__: null, table: 12, primary_key:
|
|
1319
|
+
spec_Identifier = { __proto__: null, table: 12, primary_key: 24, join: 32, one: 36, many: 40, as: 44, on: 50, or: 62, and: 68, like: 86, not: 90, in: 110, from: 120, inner: 132, left: 136, right: 140, full: 144, cross: 148, select: 154, distinct: 158, where: 168, group: 174, by: 178, having: 184, order: 190, asc: 198, desc: 202, limit: 208, offset: 212, is: 218, null: 222, case: 232, when: 238, then: 242, else: 248, end: 252, exists: 258, true: 266, false: 270, count: 280, extract: 286, extend: 306 };
|
|
1270
1320
|
parser = LRParser.deserialize({
|
|
1271
1321
|
version: 14,
|
|
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$
|
|
1273
|
-
stateData: "!
|
|
1274
|
-
goto: "
|
|
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
|
|
1322
|
+
states: "G`QYQPOOOOQO'#C`'#C`OOQO'#Di'#DiOwQPO'#DhOOQO'#Dz'#DzO#TQPO'#DyOOQO'#ER'#EROOQO'#EU'#EUO#[QPO'#ETOOQO'#EZ'#EZOOQO'#E^'#E^O#[QPO'#E]OOQO'#Eg'#EgO#aQPO'#EfOOQO'#Fp'#FpO#}QPO'#DgO$[QPO'#C_OOQO'#Fj'#FjO$[QPO'#FiO$aQPO'#FlQYQPOOQ%UQPO'#FlO%ZQPO'#EQO%ZQPO'#EYO(hQPO'#CcO(rQPO'#CcO(wQPO'#DkO#fQPO'#DmO*UQPO'#DlOOQO'#GP'#GPO+wQPO,5:SO,kQPO'#CcO-zQPO'#CcOOQO'#DY'#DYO.SQPO'#DmOOQO'#D|'#D|OOQO'#EP'#EPOOQO'#EO'#EOO.mQPO,5:eO!PQPO,5:eO0oQPO'#EOOOQO'#En'#EnOOQO'#Eq'#EqOOQO'#Es'#EsO1iQPO'#ErOOQO'#FQ'#FQO1pQPO'#FPOOQO'#FU'#FUOOQO'#FW'#FWOOQO'#FT'#FTOOQO'#FY'#FYOOQO'#GT'#GTOOQO'#F]'#F]O1uQPO'#F[OOQO'#GS'#GSOOQO'#F`'#F`OOQO'#GR'#GROOQO'#GU'#GUOOQO'#F}'#F}O%ZQPO'#EpO1zQPO'#F_OOQO'#EW'#EWO!PQPO,5:oO2PQPO,5:wO2XQPO,5;QOOQO-E9n-E9nO2|QPO,58yO3UQPO,5<TOOQO,5<W,5<WOOQO-E9j-E9jO3ZQPO,5:lO3}QPO,5:tOOQO,5<X,5<XO4qQPO,58}OOQO-E9k-E9kOOQO'#Cq'#CqOOQO'#Cs'#CsOOQO,5:V,5:VO8]QPO,5:VO8bQPO,5:XOOQO,5:W,5:WO8]QPO,5:WOOQO'#Ck'#CkOOQO'#Do'#DoOOQO'#Dq'#DqOOQO'#Ds'#DsOOQO'#Du'#DuOOQO'#Dw'#DwOwQPO'#DnO8gQPO'#DnOOQO'#Fq'#FqO8lQPO1G/nO9`QPO,5;uOOQO,5:k,5:kO9gQPO,5<OO9nQPO1G0PO:bQPO1G0PO:bQPO1G0POOQO'#Cz'#CzOOQO'#C}'#C}OOQO'#DW'#DWOOQO'#DP'#DPO;VQPO,59}OOQO'#D['#D[OOQO'#D_'#D_OOQO'#Dd'#DdO;_QPO,59}OOQO'#El'#ElO;dQPO,5;VO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hO%ZQPO,59hOOQO,5:j,5:jO8]QPO,5:jO;lQPO,5;^OOQO'#Ev'#EvOOQO'#Ft'#FtO;sQPO,5;^O%ZQPO'#EuO#fQPO,5;kO<OQPO,5;vOOQO,5;[,5;[O<]QPO,5;yO<eQPO1G0ZO=YQPO'#E`O>TQPO1G0cOOQO'#Ei'#EiO>xQPO1G0lO>}QPO1G.eO@OQPO1G1nO@TQPO1G1oPAUQPO'#FmOOQO1G/q1G/qOOQO1G/s1G/sOOQO1G/r1G/rOAZQPO,5:YOwQPO,5:YOOQO-E9o-E9oOBbQPO1G1aOOQO1G1a1G1aOOQO1G1j1G1jOOQO,5<^,5<^OBlQPO7+%kOOQO-E9p-E9pOC`QPO7+%kOOQO,59k,59kODTQPO1G/iO.SQPO1G/iOOQO1G0q1G0qO;gQPO1G0qOGhQPO1G/SOGoQPO1G/SOKRQPO1G/SOK]QPO1G/SOOQO1G/S1G/SOOQO1G0U1G0UO;sQPO1G0xOOQO-E9r-E9rOOQO'#E{'#E{OOQO'#E}'#E}OOQO1G0x1G0xO;yQPO1G0xO%ZQPO'#EzOKgQPO,5;aOKnQPO1G1VOKsQPO1G1bOOQO1G1b1G1bOKzQPO1G1bO%ZQPO1G1bOOQO'#Fb'#FbOLPQPO1G1eOLUQPO7+%uOLxQPO7+%uOOQO'#Eb'#EbOOQO'#Ed'#EdOOQO,5:z,5:zON_QPO7+%}ONiQPO7+%}OOQO7+&W7+&WO!!WQPO'#CcO!!_QPO'#CjO$[QPO'#CiO/bQPO'#CxOOQO'#F|'#F|OOQO'#F{'#F{O!!gQPO'#FnO!#nQPO7+$PO!#uQPO'#CxO#fQPO7+'YONpQPO'#CcOOQO'#GW'#GWO!#zQPO'#FuO!%RQPO7+'ZOOQO'#Ct'#CtO%ZQPO1G/tO!%YQPO1G/tO!&aQPO7+&{O!&iQPO7+&{OOQO7+&{7+&{P!PQPO'#FrO!&pQPO<<IVO.SQPO7+%TO!'dQPO'#DfO!'nQPO7+%TOOQO7+&]7+&]OOQO7+&d7+&dO;yQPO7+&dO!'sQPO,5;fOOQO'#Ex'#ExO%ZQPO1G0{OOQO7+&q7+&qOOQO7+&|7+&|O!'zQPO7+&|O%ZQPO7+'PO!(RQPO<<IaOOQO,5<_,5<_O!(uQPO<<IiOOQO-E9q-E9qOOQO'#Ce'#CeO!)mQPO,59OOOQO'#Cm'#CmOOQO'#Co'#CoOOQO,59U,59UO!*wQPO,59TO;VQPO,5<PO!+PQPO,5<PO;dQPO,5<QO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO%ZQPO,59eO8]QPO,59dOOQO,5<Y,5<YOOQO-E9l-E9lOOQO<<Gk<<GkO%ZQPO,59dO!+UQPO<<JtOOQO,5<a,5<aOOQO-E9s-E9sOOQO<<Ju<<JuO!+ZQPO7+%`O%ZQPO7+%`OOQO-E9m-E9mO!,aQPO<<JgOOQO<<Jg<<JgO!,hQPO,5<ZO!,rQPO<<HoO!,wQPO,5:QO!-PQPO,5:QOOQO<<Ho<<HoOOQO<<JO<<JOO!-WQPO7+&gOOQO<<Jh<<JhO!-eQPO<<JkP2PQPO'#FsOOQO'#Cg'#CgOOQO'#Cf'#CfOOQO1G.j1G.jO$[QPO1G.oO8]QPO1G.oO!-lQPO1G1kO.SQPO1G1kOOQO1G1l1G1lO;gQPO1G1lO!.{QPO1G/PO!/SQPO1G/PO!0bQPO1G/PO!0lQPO1G/POOQO1G/P1G/POOQO1G/O1G/OO!0vQPO1G/OOOQOAN@`AN@`O!1vQPO<<HzP%ZQPO'#FoOOQOAN@RAN@ROOQOAN>ZAN>ZO!2|QPO1G/lOOQOAN@VAN@VO!3TQPO'#CvOOQO7+$Z7+$ZO!*zQPO7+$ZO.SQPO7+'VO!3YQPO7+'VOOQO7+'W7+'WO$[QPO,59bO$[QPO<<GuO!3_QPO<<JqOOQO<<Jq<<JqOOQO1G.|1G.|OOQOAN=aAN=aOOQOAN@]AN@]",
|
|
1323
|
+
stateData: "!3i~O$lOSPOS~OUPO!^QO!oSO!vUO!yVO#OXO#RYO#[[O$_aO~OThO$nkO~OToO}qO!PzO!QzO!StO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO$nrO~O!qsO~P!PO!{!_O~O#T!bO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~O$j!ZX$y!ZX$t!ZX~P#fOThO~O$y!fOU$`X!^$`X!o$`X!v$`X!y$`X#O$`X#R$`X#[$`X$_$`X$j$`X~O$y!fO~OToO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO$nrO~O$m!jOTVX`VXfVX!^VX!dVX!fVX!hVX!jVX!lVX!oVX!vVX!yVX#OVX#RVX#[VX$jVX$yVX$tVXkVX}VX!PVX!QVX#TVX#cVX#hVX#uVX#vVX#yVX#{VX$QVX$TVX$VVX$rVX~O$nVXiVX~P&[OT!kO~OT!nOf!mO`!_X!^!_X!d!_X!f!_X!h!_X!j!_X!l!_X!o!_X!v!_X!y!_X#O!_X#R!_X#[!_X$j!_X$y!_X$t!_Xi!_X~OT!nOf!mO`!`X!^!`X!d!`X!f!`X!h!`X!j!`X!l!`X!o!`X!v!`X!y!`X#O!`X#R!`X#[!`X$j!`X$y!`X$t!`Xi!`X~O`!tO!d!uO!f!vO!h!wO!j!xO!l!yO~O!^![a!o![a!v![a!y![a#O![a#R![a#[![a$j![a$y![a$t![a~P+cO$n#OOoVXrVXtVXuVXvVXwVXxVXyVX{VX!SVX!TVX!UVX!XVX#aVX#kVX#mVX#rVX#pVX~P&[OT!kO!S#PO~O!^QO!oSO!vUO!yVO#OXO#RYO#[[O~P%ZO$r#RO!^!ma!o!ma!v!ma!y!ma#O!ma#R!ma#[!ma$j!ma$y!ma$t!ma~Of!mOk#XOo#UOr#VOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_O~OT!nO!^!rX!o!rX!v!rX!y!rX#O!rX#R!rX#[!rX$j!rX$r!rX$y!rX$t!rX~P/bO#k#iO~P%ZO$n#mO~O$n#nO~O$n#pO~OT#rO#T#rO~O#^#tO!^#Ya!o#Ya!v#Ya!y#Ya#O#Ya#R#Ya#[#Ya$j#Ya$y#Ya$t#Ya~Of!mO$n#vO~O$n#xO~O!^!ta!o!ta!v!ta!y!ta#O!ta#R!ta#[!ta$j!ta$y!ta$t!ta~P/eO!^!|a!o!|a!v!|a!y!|a#O!|a#R!|a#[!|a$j!|a$y!|a$t!|a~P/eO$m!jOTVa`VafVa!^Va!dVa!fVa!hVa!jVa!lVa!oVa!vVa!yVa#OVa#RVa#[Va$jVa$yVakVaoVarVatVauVavVawVaxVayVa{Va}Va!PVa!QVa!SVa!TVa!UVa!XVa#aVa$rVa$nVa$tVa#kVaiVa#mVa#rVa#pVa#TVa#cVa#hVa#uVa#vVa#yVa#{Va$QVa$TVa$VVa~OT!nO~O$t#{O~O`!tO~O!^![i!o![i!v![i!y![i#O![i#R![i#[![i$j![i$y![i$t![i~P+cO$t$RO~P%ZO$t$SO~P/eO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~P!PO$r$UO!^!mi!o!mi!v!mi!y!mi#O!mi#R!mi#[!mi$j!mi$y!mi$t!mi~O{#WO!X#]O~O$n$ZO~O}qO#cyO~O#k#iO~P/eO#k#iO#p$fO#r$gO~O!S$oO!qsO$t$nO~P%ZOT$qO#v$qO~O$r$sO!^!wi!o!wi!v!wi!y!wi#O!wi#R!wi#[!wi$j!wi$y!wi$t!wi~O#V$uO#X$vO!^#SX!o#SX!v#SX!y#SX#O#SX#R#SX#[#SX$j#SX$r#SX$y#SX$t#SX~O$r$xO!^#Pi!o#Pi!v#Pi!y#Pi#O#Pi#R#Pi#[#Pi$j#Pi$y#Pi$t#Pi~O#T$zO~OT${O`!tO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO~O$n%UO~OT%VO`!tO}qO!PzO!QzO#T!TO#cyO#h{O#u}O#v!TO#y!PO#{!QO$Q!UO$T!XO$V!YO~O$m!jO~Oi%ZO`!ba!^!ba!d!ba!f!ba!h!ba!j!ba!l!ba!o!ba!v!ba!y!ba#O!ba#R!ba#[!ba$j!ba$y!ba$t!ba~O$r%_O$t%`O~P/eO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~P!PO$r%bO!^!mq!o!mq!v!mq!y!mq#O!mq#R!mq#[!mq$j!mq$y!mq$t!mq~O$n%cO~Ok#XOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_OTpifpiopi!^pi!opi!vpi!ypi#Opi#Rpi#[pi$jpi$rpi$ypi$tpi#kpi#mpi#rpi`pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~Or#VO~PDYOrpi~PDYO!S#[O!T#[O!U#[OTpifpikpiopirpitpiupivpiwpixpiypi{pi}pi!Xpi!^pi!opi!vpi!ypi#Opi#Rpi#[pi#api$jpi$rpi$ypi$tpi#kpi#mpi#rpi`pi!dpi!fpi!hpi!jpi!lpi#ppi#Tpi#cpi#hpi#upi#vpi#ypi#{pi$Qpi$Tpi$Vpi~O!P#ZO!Q#ZO~PGvO!Ppi!Qpi~PGvO#m%jO~P/eO$t%lO~O$t%mO~P/eO$t%mO~O!^QO~O!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~P!PO$r%pO!^!wq!o!wq!v!wq!y!wq#O!wq#R!wq#[!wq$j!wq$y!wq$t!wq~O!^#Pq!o#Pq!v#Pq!y#Pq#O#Pq#R#Pq#[#Pq$j#Pq$y#Pq$t#Pq~OT#rO#T#rO~PMmO$r%rO~PMmO$m!jO$n#OOfVXkVXoVXrVXtVXuVXvVXwVXxVXyVX{VX}VX!PVX!QVX!SVX!TVX!UVX!XVX#aVX$ZgX~OT%tO~PNpOb%vOd%wO~O$r&TOT$bX`$bX}$bX!P$bX!Q$bX#T$bX#c$bX#h$bX#u$bX#v$bX#y$bX#{$bX$Q$bX$T$bX$V$bX$t$bX~O$t&VO~P>}O$Z&WO~O$r&YOT$iX`$iX}$iX!P$iX!Q$iX#T$iX#c$iX#h$iX#u$iX#v$iX#y$iX#{$iX$Q$iX$T$iX$V$iX$t$iX~O$t&[O~P@TOi%ZO`!bi!^!bi!d!bi!f!bi!h!bi!j!bi!l!bi!o!bi!v!bi!y!bi#O!bi#R!bi#[!bi$j!bi$y!bi$t!bi~O$r&`O$t&aO~O$t&aO~P%ZO!^!my!o!my!v!my!y!my#O!my#R!my#[!my$j!my$y!my$t!my~P!PO$r&eO$t!YX~P/eO$t&fO~O#r#na~P/eO$t&iO~P/eO!^!wy!o!wy!v!wy!y!wy#O!wy#R!wy#[!wy$j!wy$y!wy$t!wy~P!POT#rO#T#rO!^#Py!o#Py!v#Py!y#Py#O#Py#R#Py#[#Py$j#Py$y#Py$t#Py~O[&lOTWa`Wa}Wa!PWa!QWa#TWa#cWa#hWa#uWa#vWa#yWa#{Wa$QWa$TWa$VWa$rWa$tWa~Of!mOi%ZO~O$n&rO~O$t&|O~O`!bq!^!bq!d!bq!f!bq!h!bq!j!bq!l!bq!o!bq!v!bq!y!bq#O!bq#R!bq#[!bq$j!bq$y!bq$t!bq~P/eO$t'PO~P%ZO$r$ca$t$ca~P/eO$t'QO~O$r'RO$t!Ya~O$t!Ya~P%ZO#k#iq#p#iq#r#iq~P/eO$t'SO~P/eO$n'WO~Ok#XOt#XOu#XOv#XOw#XOx#XOy#XO{#WO}qO!P#ZO!Q#ZO!S#[O!T#[O!U#[O!X#]O#a#_Ofmiomi~Or#VO~P!-qOrmi~P!-qO!S#[O!T#[O!U#[Ofmikmiomirmitmiumivmiwmixmiymi{mi}mi!Xmi#ami~O!P#ZO!Q#ZO~P!/ZO!Pmi!Qmi~P!/ZOTli`li#Tli#cli#hli#uli#vli#yli#{li$Qli$Tli$Vli$rli$tli~P/eO`!by!^!by!d!by!f!by!h!by!j!by!l!by!o!by!v!by!y!by#O!by#R!by#[!by$j!by$y!by$t!by~P/eO$t!Yi~P%ZOk'ZO~O$t'^O~O$t'aO~OP!T!S!Q~",
|
|
1324
|
+
goto: "H}${PPP$|%QPP%U&n&r&u&xP&{'T'ZP'hP'hP'kP'}(mP(yP&{)P)VP)m*lP+UPPPPPP+pP,^P.RPP.oPPP)m/_P0P0]0w1UP1f1f1k2o2sP2sP2sP2sP2sP0w2wP3UP3[3m0w3xP0w4VP4dP0w4jP0w4wP5UP5^P5^P0w5aP5nP)m5qP6]P7l8o7l9rP:u:{P;RP;U;[P;`P7l;jPP<m=pP=pP<m>s>s?vP7l@yPA|P1p)P)PP$|$|BPPBTBZCmCsC}D^DdDrDxESPPPPPEYE^EdPGkPGt7l>s)mPHyTcOdT`OdUjR!z$O#Q!WTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WQ!d`Q!ebQ%y$}S'T&o'[R'_'ZT%Q#v%SR%u${R&n%uR&m%uS%Q#v%ST%W#x%YX$}#v#x%S%YS!zn!}Q$O!{X$|#v#x%S%YR%x$|Q!pjQ!slQ#gxQ#w!dQ&S%OR&p%yQ!ojQ!rlQ#fxQ#z!pQ#|!sQ$c#gW%T#v#x%S%YQ&z&SR'V&pQ%[#}Q&^%]Q&o%yR'['VQ'U&oR'`'[X%P#v#x%S%Yr#ax!h!i#Q#h$Q$k$m%d%i%n&]&b&h&j&{&}R%}%O!y![Tfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'Wv#bx!h!i#Q#h$Q$^$k$m%d%i%n&]&b&h&j&u&{&}R&O%Oz#cx!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}R&P%O|#Xx!h!i#Q#h$Q$^$_$k$m%O%d%i%n&]&b&h&j&u&v&{&}T$X#Y%z#QzTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'Wz#Yx!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}Q$]#`Q%z%OR&t%|!O#dx!h!i#Q#h$Q$^$_$`$k$m%d%i%n&]&b&h&j&u&v&w&{&}R&Q%O!S#ex!h!i#Q#h$Q$^$_$`$a$k$m%d%i%n&]&b&h&j&u&v&w&x&{&}R&R%Oz#^x!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}Q$Y#YQ%{%OR&q%zQ%e$ZQ&c%cQ'X&rR']'WSeOdS!qkrQ$l#mQ%e$ZQ&X%UQ&c%cQ'X&rR']'Wg^O_dkr#m$Z%U%c&r'WfRO_dkr#m$Z%U%c&r'WR%o$rVmR!z$OUlR!z$O!y!ZTfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WT!|n!}T!{n!}gTO_dkr#m$Z%U%c&r'WQwTR$p#nQvTQ#TwQ#q!`]$T#R$U$s%a%b%pcuTw!`#R$U$s%a%b%pgfO_dkr#m$Z%U%c&r'WgWO_dkr#m$Z%U%c&r'WQ!`WR!aZggO_dkr#m$Z%U%c&r'WgZO_dkr#m$Z%U%c&r'WQ#s!aV%q$x%r&kR$w#rg]O_dkr#m$Z%U%c&r'WR#u!bz#`x!h!i#Q#h$Q$^$_$k$m%d%i%n&]&b&h&j&u&v&{&}R%|%O#Q!STfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WQ$[#`Q%f$]Q&s%|R'Y&t#R!YTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!]Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R|Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WX#j|#h#k$dX#l|#h#k$dR%k$kQ$i#kR%h$dT$j#k$dQ$h#kS%g$d$iR&g%h#R!OTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!TTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!RTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!WTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!VTfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'W#R!^Tfgrw|!]!`#O#R#a#b#c#d#e#l#n#v#x$U$Z$j$p$s%S%Y%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WR$r#pTbOdQdOR!gd#QiR`bfgr|!]!z#O#a#b#c#d#e#l#n#v#x$O$Z$j$p$}%S%Y%[%_%c%k%o%}&O&P&Q&R&W&^&`&e&o&r'O'R'W'Z'[bpTw!`#R$U$s%a%b%pT!lipQ%S#vR&U%SQ%^$QS&_%^&dR&d%dd_Odkr#m$Z%U%c&r'WR!c_Q!}nR$P!}Q#SvU$V#S$W$tQ$W#TR$t#qQ$y#sR%s$yQ#k|Q$d#hT$e#k$dQ%Y#xR&Z%YT%R#v%SX%O#v#x%S%YbxTw!`#R$U$s%a%b%pQ!hfQ!igQ#QrQ#h|Q#o!]Q$Q#OQ$^#aQ$_#bQ$`#cQ$a#dQ$b#eQ$k#lQ$m#nW%d$Z%c&r'WQ%i$jQ%n$pQ&]%[Y&b%_&`&e'O'RQ&h%kQ&j%oQ&u%}Q&v&OQ&w&PQ&x&QQ&y&RQ&{&WR&}&^QnRQ#}!zR%]$O!x![Tfgrw|!]!`#O#R#a#b#c#d#e#l#n$U$Z$j$p$s%[%_%a%b%c%k%o%p%}&O&P&Q&R&W&^&`&e&r'O'R'WX%P#v#x%S%YT%X#x%Y",
|
|
1325
|
+
nodeNames: "\u26A0 Comment Program TableStatement Kw Identifier table Ref ColumnDef DataType PrimaryKey Kw primary_key JoinDef JoinType Kw join Kw one Kw many Kw as Alias Kw on BinaryExpression = ComputedDef BinaryExpression Kw or BinaryExpression Kw and ComparisonOp != <> < > <= >= Kw like Kw not AddOp + - MulOp * / % InExpression Kw in InValueList QueryStatement FromClause Kw from TableName Subquery SubqueryExpression JoinClause Kw inner Kw left Kw right Kw full Kw cross SelectClause Kw select Kw distinct SelectItem Wildcard WhereClause Kw where GroupByClause Kw group Kw by HavingClause Kw having OrderByClause Kw order OrderItem Number Kw asc Kw desc LimitClause Kw limit Kw offset NullTestExpression Kw is Kw null UnaryExpression UnaryOperator CaseExpression Kw case WhenClause Kw when Kw then ElseClause Kw else Kw end ExistsExpression Kw exists String Boolean Kw true Kw false Null FunctionCall Count Kw count ExtractExpression Kw extract ExtractUnit Param Parenthetical InExpression NullTestExpression : ViewStatement ExtendStatement Kw extend",
|
|
1276
1326
|
maxTerm: 180,
|
|
1277
1327
|
nodeProps: [
|
|
1278
|
-
["group", -
|
|
1328
|
+
["group", -12, 7, 97, 112, 114, 127, 130, 131, 136, 137, 138, 141, 145, "Expression Expression", -9, 26, 29, 32, 53, 63, 107, 146, 147, 148, "Expression", -2, 61, 62, "TablePrimary"]
|
|
1279
1329
|
],
|
|
1280
1330
|
skippedNodes: [0, 1],
|
|
1281
1331
|
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$
|
|
1332
|
+
tokenData: "*o~RoX^#Spq#Sqr#wrs$Stu$quv%cwx%hxy&Qyz&Vz{&[{|&a|}&f}!O&k!O!P'[!P!Q'a!Q![(p![!])Z!]!^)`!^!_)e!_!`)z!`!a*P!c!}*^#T#o*^#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#XY$l~X^#Spq#S#y#z#S$f$g#S#BY#BZ#S$IS$I_#S$I|$JO#S$JT$JU#S$KV$KW#S&FU&FV#S~#zP!_!`#}~$SOt~~$VTOr$Srs$fs;'S$S;'S;=`$k<%lO$S~$kO#v~~$nP;=`<%l$S~$tS!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%VS$V~!Q![%Q!c!}%Q#R#S%Q#T#o%Q~%hO!U~~%kTOw%hwx$fx;'S%h;'S;=`%z<%lO%h~%}P;=`<%l%h~&VO$n~~&[O$t~~&aO!S~~&fO!P~~&kO$r~~&pP!Q~}!O&s~&xSP~OY&sZ;'S&s;'S;=`'U<%lO&s~'XP;=`<%l&s~'aO$m~~'fP!T~z{'i~'lTOz'iz{'{{;'S'i;'S;=`(j<%lO'i~(OVOz'iz{'{{!P'i!P!Q(e!Q;'S'i;'S;=`(j<%lO'i~(jOP~~(mP;=`<%l'i~(uQ#T~!O!P({!Q![(p~)OP!Q![)R~)WP#T~!Q![)R~)`O$Z~~)eO$y~~)jQv~!_!`)p!`!a)u~)uOx~~)zOu~~*POk~~*UPw~!_!`*X~*^Oy~~*cST~!Q![*^!c!}*^#R#S*^#T#o*^",
|
|
1283
1333
|
tokenizers: [0],
|
|
1284
1334
|
topRules: { "Program": [0, 2] },
|
|
1285
1335
|
specialized: [{ term: 5, get: (value, stack) => specializeIdentifier(value, stack) << 1, external: specializeIdentifier }, { term: 5, get: (value) => spec_Identifier[value] || -1 }],
|
|
1286
|
-
tokenPrec:
|
|
1336
|
+
tokenPrec: 2964
|
|
1287
1337
|
});
|
|
1288
1338
|
}
|
|
1289
1339
|
});
|
|
@@ -1535,7 +1585,7 @@ function getDiagnostics() {
|
|
|
1535
1585
|
return diagnostics;
|
|
1536
1586
|
}
|
|
1537
1587
|
async function loadWorkspace(dir, includeMd) {
|
|
1538
|
-
let files = await glob(includeMd ? "**/*.{gsql,md}" : "**/*.gsql", { cwd: dir, ignore: ["node_modules/**"] });
|
|
1588
|
+
let files = await glob(includeMd ? "**/*.{gsql,md}" : "**/*.gsql", { cwd: dir, ignore: ["node_modules/**"], follow: false });
|
|
1539
1589
|
for await (let file of files) {
|
|
1540
1590
|
try {
|
|
1541
1591
|
let contents = await readFile(path2.join(dir, file), "utf-8");
|
|
@@ -1586,10 +1636,16 @@ function toSql(query, params = {}) {
|
|
|
1586
1636
|
query = structuredClone(query);
|
|
1587
1637
|
fillInParams(query, params);
|
|
1588
1638
|
if (config.dialect == "snowflake") uppercaseMalloyQuery(query);
|
|
1589
|
-
let
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1639
|
+
let visited = /* @__PURE__ */ new Set();
|
|
1640
|
+
function setStructRefs(obj) {
|
|
1641
|
+
if (!obj || typeof obj !== "object" || visited.has(obj)) return;
|
|
1642
|
+
visited.add(obj);
|
|
1643
|
+
if (obj.baseTableName && obj.pipeline) obj.structRef = contents[obj.baseTableName];
|
|
1644
|
+
if (obj.query) setStructRefs(obj.query);
|
|
1645
|
+
if (obj.fields) for (let f of obj.fields) setStructRefs(f);
|
|
1646
|
+
}
|
|
1647
|
+
Object.values(contents).forEach(setStructRefs);
|
|
1648
|
+
setStructRefs(query);
|
|
1593
1649
|
let qm = new QueryModel({
|
|
1594
1650
|
name: "generated_model",
|
|
1595
1651
|
contents,
|
|
@@ -1607,7 +1663,7 @@ var init_core = __esm({
|
|
|
1607
1663
|
init_params();
|
|
1608
1664
|
init_util();
|
|
1609
1665
|
init_config();
|
|
1610
|
-
|
|
1666
|
+
init_functionDefs();
|
|
1611
1667
|
init_parser();
|
|
1612
1668
|
init_markdown();
|
|
1613
1669
|
init_snowflake();
|
|
@@ -1848,6 +1904,9 @@ async function check(options) {
|
|
|
1848
1904
|
log("No errors found \u{1F48E}");
|
|
1849
1905
|
return true;
|
|
1850
1906
|
}
|
|
1907
|
+
if (process.env.NODE_ENV == "test" && mdFile) {
|
|
1908
|
+
delete FILE_MAP[mdFile];
|
|
1909
|
+
}
|
|
1851
1910
|
let host = `http://localhost:${config.port || Number(process.env.GRAPHENE_PORT) || 4e3}`;
|
|
1852
1911
|
let pageUrl = "/" + mdFile.replace(/\.md$/, "").replace(/^\//, "").replace(/\\/g, "/");
|
|
1853
1912
|
if (pageUrl === "/index") pageUrl = "/";
|
|
@@ -1998,6 +2057,7 @@ var init_check = __esm({
|
|
|
1998
2057
|
init_mockFiles();
|
|
1999
2058
|
init_background();
|
|
2000
2059
|
init_util();
|
|
2060
|
+
init_analyze();
|
|
2001
2061
|
browserConnections = [];
|
|
2002
2062
|
pendingRequests = {};
|
|
2003
2063
|
}
|
|
@@ -2167,6 +2227,10 @@ __export(bigQuery_exports, {
|
|
|
2167
2227
|
BigQueryConnection: () => BigQueryConnection
|
|
2168
2228
|
});
|
|
2169
2229
|
import { BigQuery, BigQueryDate, BigQueryTimestamp } from "@google-cloud/bigquery";
|
|
2230
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2231
|
+
function sqlStringLiteral(value) {
|
|
2232
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2233
|
+
}
|
|
2170
2234
|
var BigQueryConnection;
|
|
2171
2235
|
var init_bigQuery = __esm({
|
|
2172
2236
|
"connections/bigQuery.ts"() {
|
|
@@ -2174,6 +2238,8 @@ var init_bigQuery = __esm({
|
|
|
2174
2238
|
init_config();
|
|
2175
2239
|
BigQueryConnection = class {
|
|
2176
2240
|
client;
|
|
2241
|
+
projectId;
|
|
2242
|
+
defaultNamespace;
|
|
2177
2243
|
constructor(options = {}) {
|
|
2178
2244
|
options.projectId ||= config.bigquery?.projectId;
|
|
2179
2245
|
if (process.env.GOOGLE_CREDENTIALS_CONTENT) {
|
|
@@ -2181,8 +2247,14 @@ var init_bigQuery = __esm({
|
|
|
2181
2247
|
options.projectId = parsed.project_id;
|
|
2182
2248
|
options.credentials = parsed;
|
|
2183
2249
|
}
|
|
2250
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
2251
|
+
let tmp = JSON.parse(readFileSync3(process.env.GOOGLE_APPLICATION_CREDENTIALS, { encoding: "utf-8" }));
|
|
2252
|
+
options.projectId = tmp.project_id;
|
|
2253
|
+
}
|
|
2184
2254
|
if (!options.projectId) throw new Error("projectId must be set in config or provided in service account credentials");
|
|
2255
|
+
this.projectId = options.projectId;
|
|
2185
2256
|
this.client = new BigQuery({ ...options, userAgent: "Graphene" });
|
|
2257
|
+
this.defaultNamespace = config.namespace;
|
|
2186
2258
|
}
|
|
2187
2259
|
async runQuery(sql) {
|
|
2188
2260
|
let [job] = await this.client.createQueryJob({ query: sql, useLegacySql: false });
|
|
@@ -2197,6 +2269,32 @@ var init_bigQuery = __esm({
|
|
|
2197
2269
|
});
|
|
2198
2270
|
return { rows, totalRows };
|
|
2199
2271
|
}
|
|
2272
|
+
async listDatasets() {
|
|
2273
|
+
let [datasets] = await this.client.getDatasets();
|
|
2274
|
+
return datasets.map((d) => d.id || d.metadata.datasetReference?.datasetId);
|
|
2275
|
+
}
|
|
2276
|
+
async listTables(dataset) {
|
|
2277
|
+
if (!dataset) throw new Error("BigQuery requires a dataset");
|
|
2278
|
+
let res = await this.runQuery(`select table_schema as table_schema, table_name as table_name
|
|
2279
|
+
from \`${dataset}.INFORMATION_SCHEMA.TABLES\`
|
|
2280
|
+
where table_type in ('BASE TABLE', 'VIEW') order by table_name`);
|
|
2281
|
+
return res.rows.map((r) => `${r["table_schema"]}.${r["table_name"]}`);
|
|
2282
|
+
}
|
|
2283
|
+
async describeTable(target) {
|
|
2284
|
+
let parts = target.split(".");
|
|
2285
|
+
let table2 = parts.pop() || "";
|
|
2286
|
+
let dataset = parts.join(".");
|
|
2287
|
+
let sql = `
|
|
2288
|
+
select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position
|
|
2289
|
+
from \`${dataset}.INFORMATION_SCHEMA.COLUMNS\`
|
|
2290
|
+
where lower(table_name) = lower(${sqlStringLiteral(table2)})
|
|
2291
|
+
order by ordinal_position
|
|
2292
|
+
`.trim();
|
|
2293
|
+
let res = await this.runQuery(sql);
|
|
2294
|
+
return res.rows.map((row) => {
|
|
2295
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2200
2298
|
};
|
|
2201
2299
|
}
|
|
2202
2300
|
});
|
|
@@ -2209,6 +2307,9 @@ __export(duckdb_exports, {
|
|
|
2209
2307
|
import { promises as fs5 } from "fs";
|
|
2210
2308
|
import path6 from "path";
|
|
2211
2309
|
import { DuckDBTimestampValue, DuckDBInstance, DuckDBDateValue } from "@duckdb/node-api";
|
|
2310
|
+
function sqlStringLiteral2(value) {
|
|
2311
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2312
|
+
}
|
|
2212
2313
|
var DuckDBConnection;
|
|
2213
2314
|
var init_duckdb = __esm({
|
|
2214
2315
|
"connections/duckdb.ts"() {
|
|
@@ -2253,6 +2354,35 @@ var init_duckdb = __esm({
|
|
|
2253
2354
|
});
|
|
2254
2355
|
return { rows };
|
|
2255
2356
|
}
|
|
2357
|
+
async listDatasets() {
|
|
2358
|
+
return await Promise.resolve([]);
|
|
2359
|
+
}
|
|
2360
|
+
async listTables() {
|
|
2361
|
+
let sql = `
|
|
2362
|
+
select table_schema as table_schema, table_name as table_name
|
|
2363
|
+
from information_schema.tables
|
|
2364
|
+
where table_type in ('BASE TABLE', 'VIEW') and table_schema not in ('information_schema', 'pg_catalog')
|
|
2365
|
+
order by table_schema, table_name
|
|
2366
|
+
`.trim();
|
|
2367
|
+
let res = await this.runQuery(sql);
|
|
2368
|
+
return res.rows.map((row) => String(row["table_name"]));
|
|
2369
|
+
}
|
|
2370
|
+
async describeTable(target) {
|
|
2371
|
+
let parts = target.split(".");
|
|
2372
|
+
let table2 = parts.pop() || "";
|
|
2373
|
+
let schema = parts[0];
|
|
2374
|
+
let schemaFilter = schema ? `lower(table_schema) = lower(${sqlStringLiteral2(schema)})` : "table_schema not in ('information_schema', 'pg_catalog')";
|
|
2375
|
+
let sql = `
|
|
2376
|
+
select column_name as column_name, data_type as data_type, ordinal_position as ordinal_position
|
|
2377
|
+
from information_schema.columns
|
|
2378
|
+
where lower(table_name) = lower(${sqlStringLiteral2(table2)}) and ${schemaFilter}
|
|
2379
|
+
order by ordinal_position
|
|
2380
|
+
`.trim();
|
|
2381
|
+
let res = await this.runQuery(sql);
|
|
2382
|
+
return res.rows.map((row) => {
|
|
2383
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2256
2386
|
};
|
|
2257
2387
|
}
|
|
2258
2388
|
});
|
|
@@ -2264,6 +2394,13 @@ __export(snowflake_exports, {
|
|
|
2264
2394
|
});
|
|
2265
2395
|
import { createPrivateKey } from "node:crypto";
|
|
2266
2396
|
import snowflake from "snowflake-sdk";
|
|
2397
|
+
function snowflakeIdent(value) {
|
|
2398
|
+
if (!value) throw new Error("Snowflake identifiers cannot be empty");
|
|
2399
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
2400
|
+
}
|
|
2401
|
+
function sqlStringLiteral3(value) {
|
|
2402
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2403
|
+
}
|
|
2267
2404
|
var SnowflakeConnection;
|
|
2268
2405
|
var init_snowflake2 = __esm({
|
|
2269
2406
|
"connections/snowflake.ts"() {
|
|
@@ -2324,36 +2461,68 @@ var init_snowflake2 = __esm({
|
|
|
2324
2461
|
});
|
|
2325
2462
|
});
|
|
2326
2463
|
}
|
|
2464
|
+
async listDatasets() {
|
|
2465
|
+
let res = await this.runQuery("show databases");
|
|
2466
|
+
return res.rows.map((row) => String(row["name"] || ""));
|
|
2467
|
+
}
|
|
2468
|
+
async listTables(dataset) {
|
|
2469
|
+
let parts = dataset.split(".");
|
|
2470
|
+
let database = parts.shift() || "";
|
|
2471
|
+
let schema = parts.join(".");
|
|
2472
|
+
let res = await this.runQuery(`
|
|
2473
|
+
select table_schema as "table_schema", table_name as "table_name"
|
|
2474
|
+
from ${snowflakeIdent(database)}.INFORMATION_SCHEMA.TABLES
|
|
2475
|
+
where table_type in ('BASE TABLE', 'VIEW') and table_schema = ${sqlStringLiteral3(schema)}
|
|
2476
|
+
order by table_name
|
|
2477
|
+
`);
|
|
2478
|
+
return res.rows.map((row) => `${row["table_schema"]}.${row["table_name"]}`);
|
|
2479
|
+
}
|
|
2480
|
+
async describeTable(target) {
|
|
2481
|
+
let parts = target.split(".");
|
|
2482
|
+
let database = parts.shift() || "";
|
|
2483
|
+
let table2 = parts.pop() || "";
|
|
2484
|
+
let schema = parts.join(".");
|
|
2485
|
+
let res = await this.runQuery(`
|
|
2486
|
+
select column_name as "column_name", data_type as "data_type", ordinal_position as ordinal_position
|
|
2487
|
+
from ${snowflakeIdent(database)}.INFORMATION_SCHEMA.COLUMNS
|
|
2488
|
+
where upper(table_schema) = upper(${sqlStringLiteral3(schema)}) and upper(table_name) = upper(${sqlStringLiteral3(table2)})
|
|
2489
|
+
order by ordinal_position
|
|
2490
|
+
`);
|
|
2491
|
+
return res.rows.map((row) => {
|
|
2492
|
+
return { name: String(row["column_name"]), dataType: String(row["data_type"]) };
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2327
2495
|
};
|
|
2328
2496
|
}
|
|
2329
2497
|
});
|
|
2330
2498
|
|
|
2331
2499
|
// connections/index.ts
|
|
2332
|
-
async function
|
|
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
|
-
}
|
|
2500
|
+
async function getConnection() {
|
|
2341
2501
|
if (config.dialect === "bigquery") {
|
|
2342
2502
|
let mod = await Promise.resolve().then(() => (init_bigQuery(), bigQuery_exports));
|
|
2343
|
-
|
|
2344
|
-
return await conn.runQuery(sql);
|
|
2503
|
+
return new mod.BigQueryConnection();
|
|
2345
2504
|
} else if (config.dialect === "duckdb") {
|
|
2346
2505
|
let mod = await Promise.resolve().then(() => (init_duckdb(), duckdb_exports));
|
|
2347
|
-
|
|
2348
|
-
return await conn.runQuery(sql);
|
|
2506
|
+
return new mod.DuckDBConnection({});
|
|
2349
2507
|
} else if (config.dialect === "snowflake") {
|
|
2350
2508
|
let mod = await Promise.resolve().then(() => (init_snowflake2(), snowflake_exports));
|
|
2351
|
-
|
|
2352
|
-
return await conn.runQuery(sql);
|
|
2509
|
+
return new mod.SnowflakeConnection({});
|
|
2353
2510
|
} else {
|
|
2354
2511
|
throw new Error(`Unsupported dialect: ${config.dialect}`);
|
|
2355
2512
|
}
|
|
2356
2513
|
}
|
|
2514
|
+
async function runQuery(sql) {
|
|
2515
|
+
if (config.host) {
|
|
2516
|
+
let resp = await authenticatedFetch("/_api/query", {
|
|
2517
|
+
method: "POST",
|
|
2518
|
+
headers: { "Content-Type": "application/json" },
|
|
2519
|
+
body: JSON.stringify({ sql })
|
|
2520
|
+
});
|
|
2521
|
+
return await resp.json();
|
|
2522
|
+
}
|
|
2523
|
+
let conn = await getConnection();
|
|
2524
|
+
return await conn.runQuery(sql);
|
|
2525
|
+
}
|
|
2357
2526
|
var init_connections = __esm({
|
|
2358
2527
|
"connections/index.ts"() {
|
|
2359
2528
|
"use strict";
|
|
@@ -2456,20 +2625,28 @@ var init_mdCompile = __esm({
|
|
|
2456
2625
|
// serve2.ts
|
|
2457
2626
|
var serve2_exports = {};
|
|
2458
2627
|
__export(serve2_exports, {
|
|
2628
|
+
prepareDeps: () => prepareDeps,
|
|
2459
2629
|
serve2: () => serve2
|
|
2460
2630
|
});
|
|
2461
|
-
import { createServer, optimizeDeps } from "vite";
|
|
2631
|
+
import { createServer, optimizeDeps, resolveConfig } from "vite";
|
|
2462
2632
|
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
2463
2633
|
import fs7 from "fs-extra";
|
|
2634
|
+
import { glob as glob2 } from "glob";
|
|
2464
2635
|
import crypto2 from "crypto";
|
|
2465
2636
|
import { mdsvex } from "mdsvex";
|
|
2466
2637
|
import path8 from "path";
|
|
2467
2638
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2468
2639
|
async function serve2() {
|
|
2640
|
+
let server = await createServer(await createConfig());
|
|
2641
|
+
await server.listen();
|
|
2642
|
+
console.log(`Server running at http://localhost:${server.config.server.port}`);
|
|
2643
|
+
return server;
|
|
2644
|
+
}
|
|
2645
|
+
async function createConfig() {
|
|
2469
2646
|
uiRoot = path8.join(fileURLToPath2(import.meta.url), "../../ui");
|
|
2470
2647
|
let port = Number(process.env.GRAPHENE_PORT) || 4e3;
|
|
2471
2648
|
await fs7.ensureDir(path8.resolve(config.root, "node_modules/.graphene"));
|
|
2472
|
-
|
|
2649
|
+
return {
|
|
2473
2650
|
root: config.root,
|
|
2474
2651
|
plugins: [
|
|
2475
2652
|
svelte({
|
|
@@ -2484,12 +2661,15 @@ async function serve2() {
|
|
|
2484
2661
|
injectComponentImports()
|
|
2485
2662
|
]
|
|
2486
2663
|
}),
|
|
2664
|
+
fixSvelteDepsInTests(),
|
|
2487
2665
|
checkVitePlugin(),
|
|
2488
2666
|
handleRequestPlugin,
|
|
2489
2667
|
updateWorkspacePlugin,
|
|
2490
2668
|
mockFilesForTests()
|
|
2491
2669
|
],
|
|
2492
|
-
publicDir: path8.resolve(uiRoot),
|
|
2670
|
+
publicDir: path8.resolve(uiRoot, "public"),
|
|
2671
|
+
// on the fence about this one. This would make it less likely we need to optimize when alternating between dev and tests.
|
|
2672
|
+
// cacheDir: process.env.NODE_ENV == 'test' ? 'node_modules/.vite-tests' : 'node_modules/.vite',
|
|
2493
2673
|
server: {
|
|
2494
2674
|
port,
|
|
2495
2675
|
fs: { strict: false },
|
|
@@ -2499,16 +2679,38 @@ async function serve2() {
|
|
|
2499
2679
|
alias: {
|
|
2500
2680
|
graphene: path8.resolve(uiRoot, "web.js")
|
|
2501
2681
|
}
|
|
2682
|
+
},
|
|
2683
|
+
// vite's pre-bundling won't naturally discover these dependencies since they're transitive.
|
|
2684
|
+
// Instead, we need to list them out here so vite knows where they are.
|
|
2685
|
+
optimizeDeps: {
|
|
2686
|
+
noDiscovery: process.env.NODE_ENV == "test",
|
|
2687
|
+
// tests manually optimize before starting test workers
|
|
2688
|
+
include: [
|
|
2689
|
+
"@graphenedata/cli > svelte",
|
|
2690
|
+
"@graphenedata/cli > ssf",
|
|
2691
|
+
"@graphenedata/cli > @tidyjs/tidy",
|
|
2692
|
+
"@graphenedata/cli > chroma-js",
|
|
2693
|
+
"@graphenedata/cli > echarts/dist/echarts.esm.js",
|
|
2694
|
+
"@graphenedata/cli > @graphenedata/html2canvas"
|
|
2695
|
+
]
|
|
2502
2696
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
async function prepareDeps() {
|
|
2700
|
+
let cfg = await resolveConfig(await createConfig(), "serve");
|
|
2701
|
+
await optimizeDeps(cfg, true);
|
|
2702
|
+
}
|
|
2703
|
+
function fixSvelteDepsInTests() {
|
|
2704
|
+
let viteConfig;
|
|
2705
|
+
function configResolved(cfg) {
|
|
2706
|
+
viteConfig = cfg;
|
|
2707
|
+
}
|
|
2708
|
+
function buildStart() {
|
|
2709
|
+
if (process.env.NODE_ENV != "test") return;
|
|
2710
|
+
viteConfig.optimizeDeps.force = false;
|
|
2711
|
+
}
|
|
2712
|
+
buildStart.sequential = true;
|
|
2713
|
+
return { name: "fix-svelte-deps", enforce: "pre", sequential: true, configResolved, buildStart };
|
|
2512
2714
|
}
|
|
2513
2715
|
async function handleQuery(req, res) {
|
|
2514
2716
|
let chunks = [];
|
|
@@ -2539,7 +2741,7 @@ async function handleQuery(req, res) {
|
|
|
2539
2741
|
async function handlePage(server, res, filePath, mount) {
|
|
2540
2742
|
res.setHeader("Content-Type", "text/html");
|
|
2541
2743
|
let mdMount = mount ? `
|
|
2542
|
-
import Page from ${JSON.stringify(filePath)}
|
|
2744
|
+
import Page from ${JSON.stringify(filePath)}
|
|
2543
2745
|
new Page({ target: document.getElementById('content'), props: {} })
|
|
2544
2746
|
` : "";
|
|
2545
2747
|
let html = await server.transformIndexHtml(filePath, `<!doctype html>
|
|
@@ -2548,15 +2750,11 @@ async function handlePage(server, res, filePath, mount) {
|
|
|
2548
2750
|
<meta charset="UTF-8" />
|
|
2549
2751
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2550
2752
|
<title>Graphene</title>
|
|
2551
|
-
<link rel="icon" href="/
|
|
2552
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
2553
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2554
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
|
2753
|
+
<link rel="icon" href="/favicon.ico" />
|
|
2555
2754
|
</head>
|
|
2556
2755
|
<body>
|
|
2557
|
-
<
|
|
2558
|
-
|
|
2559
|
-
</main>
|
|
2756
|
+
<nav id="nav"></nav>
|
|
2757
|
+
<main id="content"></main>
|
|
2560
2758
|
<script type="module">
|
|
2561
2759
|
import 'graphene' // do this first so we can track errors caused by importing the md file
|
|
2562
2760
|
${mdMount}
|
|
@@ -2575,11 +2773,12 @@ function mockFilesForTests() {
|
|
|
2575
2773
|
},
|
|
2576
2774
|
load(id) {
|
|
2577
2775
|
if (!id.endsWith("?mock")) return null;
|
|
2578
|
-
|
|
2776
|
+
let content = mockFileMap[id.replace(config.root + "/", "").replace(/\?mock$/, "")];
|
|
2777
|
+
return content;
|
|
2579
2778
|
}
|
|
2580
2779
|
};
|
|
2581
2780
|
}
|
|
2582
|
-
var uiRoot, workspaceLoadPromise, updateWorkspacePlugin, handleRequestPlugin;
|
|
2781
|
+
var uiRoot, workspaceLoadPromise, mdFiles, updateWorkspacePlugin, handleRequestPlugin;
|
|
2583
2782
|
var init_serve2 = __esm({
|
|
2584
2783
|
"serve2.ts"() {
|
|
2585
2784
|
"use strict";
|
|
@@ -2588,15 +2787,38 @@ var init_serve2 = __esm({
|
|
|
2588
2787
|
init_mdCompile();
|
|
2589
2788
|
init_check();
|
|
2590
2789
|
init_mockFiles();
|
|
2790
|
+
mdFiles = [];
|
|
2591
2791
|
updateWorkspacePlugin = {
|
|
2592
2792
|
name: "updateWorkspace",
|
|
2793
|
+
resolveId(id) {
|
|
2794
|
+
if (id == "virtual:nav") return "\0virtual:nav";
|
|
2795
|
+
},
|
|
2796
|
+
load(id) {
|
|
2797
|
+
if (id != "\0virtual:nav") return;
|
|
2798
|
+
let allFiles = [...mdFiles];
|
|
2799
|
+
if (process.env.NODE_ENV == "test") {
|
|
2800
|
+
for (let key of Object.keys(mockFileMap)) {
|
|
2801
|
+
if (!allFiles.includes(key)) allFiles.push(key);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
return `export default ${JSON.stringify(allFiles)}`;
|
|
2805
|
+
},
|
|
2593
2806
|
configureServer: (s) => {
|
|
2594
|
-
|
|
2595
|
-
s.watcher.on("change", () => {
|
|
2807
|
+
let refresh = async () => {
|
|
2596
2808
|
clearWorkspace();
|
|
2597
2809
|
workspaceLoadPromise = loadWorkspace(config.root, false);
|
|
2598
|
-
|
|
2599
|
-
|
|
2810
|
+
mdFiles = await glob2("**/*.md", { cwd: config.root, ignore: ["node_modules/**"] });
|
|
2811
|
+
mdFiles = mdFiles.filter((f) => !config.ignoredFiles?.includes(path8.basename(f).toLowerCase()));
|
|
2812
|
+
if (process.env.NODE_ENV == "test") {
|
|
2813
|
+
mdFiles.push(...Object.keys(mockFileMap));
|
|
2814
|
+
}
|
|
2815
|
+
let mod = s.moduleGraph.getModuleById("\0virtual:nav");
|
|
2816
|
+
if (!mod) return;
|
|
2817
|
+
s.reloadModule(mod);
|
|
2818
|
+
};
|
|
2819
|
+
s.watcher.add(["**/*.gsql", "**/*.md"]);
|
|
2820
|
+
s.watcher.on("all", refresh);
|
|
2821
|
+
refresh();
|
|
2600
2822
|
}
|
|
2601
2823
|
};
|
|
2602
2824
|
handleRequestPlugin = {
|
|
@@ -2608,14 +2830,15 @@ var init_serve2 = __esm({
|
|
|
2608
2830
|
if (pathName == "/_api/query") return await handleQuery(req, res);
|
|
2609
2831
|
if (pathName == "/__ct") return await handlePage(s, res, "__ct", false);
|
|
2610
2832
|
if (!pathName || pathName == "/") pathName = "index";
|
|
2611
|
-
let
|
|
2612
|
-
|
|
2833
|
+
let relativeMdPath = pathName.replace(/^\//, "") + ".md";
|
|
2834
|
+
let mdPath = path8.join(config.root, relativeMdPath);
|
|
2835
|
+
if (mockFileMap[relativeMdPath] || await fs7.exists(mdPath)) {
|
|
2613
2836
|
await handlePage(s, res, mdPath, true);
|
|
2614
2837
|
} else {
|
|
2615
2838
|
next();
|
|
2616
2839
|
}
|
|
2617
2840
|
} catch (err) {
|
|
2618
|
-
console.error(err);
|
|
2841
|
+
if (process.env.NODE_ENV != "test") console.error(err);
|
|
2619
2842
|
res.statusCode = 500;
|
|
2620
2843
|
res.end(JSON.stringify([{ message: err.message, stack: err.stack }]));
|
|
2621
2844
|
}
|
|
@@ -2636,6 +2859,8 @@ init_auth();
|
|
|
2636
2859
|
import { Command } from "commander";
|
|
2637
2860
|
import fs8 from "fs-extra";
|
|
2638
2861
|
import path9 from "path";
|
|
2862
|
+
import dotenv from "dotenv";
|
|
2863
|
+
dotenv.config({ quiet: true });
|
|
2639
2864
|
var program = new Command();
|
|
2640
2865
|
program.name("graphene").description("Graphene CLI").version("1.0.0");
|
|
2641
2866
|
program.hook("preAction", async () => {
|
|
@@ -2660,6 +2885,31 @@ program.command("run").description("Run a query against your database").argument
|
|
|
2660
2885
|
let res = await runQuery(sql);
|
|
2661
2886
|
printTable(res.rows);
|
|
2662
2887
|
});
|
|
2888
|
+
program.command("schema").description("Inspect database tables or describe a table").argument("[schema | table]", "Optional schema or table name to describe").action(async (tableArg) => {
|
|
2889
|
+
let connection = await getConnection();
|
|
2890
|
+
let datasets = await connection.listDatasets();
|
|
2891
|
+
if (!tableArg && datasets.length > 1) {
|
|
2892
|
+
return console.log(`Datasets available:
|
|
2893
|
+
${datasets.join("\n")}`);
|
|
2894
|
+
}
|
|
2895
|
+
let dsToList = null;
|
|
2896
|
+
let parts = tableArg ? tableArg.split(".") : [];
|
|
2897
|
+
if (datasets.includes(tableArg)) dsToList = tableArg;
|
|
2898
|
+
else if (!tableArg && datasets.length == 1) dsToList = datasets[0];
|
|
2899
|
+
else if (!tableArg && config.namespace) dsToList = config.namespace;
|
|
2900
|
+
else if (!tableArg && config.dialect == "duckdb") dsToList = "<default>";
|
|
2901
|
+
else if (tableArg && config.dialect == "snowflake" && parts.length == 2) dsToList = tableArg;
|
|
2902
|
+
if (dsToList) {
|
|
2903
|
+
let tables = await connection.listTables(dsToList);
|
|
2904
|
+
return console.log(`Tables${dsToList ? ` in ${dsToList}` : ""}:
|
|
2905
|
+
${tables.join("\n")}`);
|
|
2906
|
+
}
|
|
2907
|
+
let cols = await connection.describeTable(tableArg);
|
|
2908
|
+
if (!cols.length) return console.log(`Table ${tableArg} not found`);
|
|
2909
|
+
console.log(`table ${tableArg} (`);
|
|
2910
|
+
cols.forEach((col) => console.log(` ${col.name} ${col.dataType}`));
|
|
2911
|
+
console.log(")");
|
|
2912
|
+
});
|
|
2663
2913
|
program.command("serve").description("Run the local server").option("--bg", "Run the server in the background").action(async (options) => {
|
|
2664
2914
|
await stopGrapheneIfRunning();
|
|
2665
2915
|
if (options.bg) {
|