@graphenedata/cli 0.0.5 → 0.0.7

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