@deepagents/text2sql 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CLICKHOUSE-DEPLOYMENT.md +181 -0
  2. package/README.md +32 -4
  3. package/dist/bin/sql.js +34 -51
  4. package/dist/index.js +58 -289
  5. package/dist/index.js.map +4 -4
  6. package/dist/lib/adapters/adapter.d.ts +9 -2
  7. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  8. package/dist/lib/adapters/bigquery/bigquery.d.ts.map +1 -1
  9. package/dist/lib/adapters/bigquery/bigquery.sql-policy.d.ts +9 -0
  10. package/dist/lib/adapters/bigquery/bigquery.sql-policy.d.ts.map +1 -0
  11. package/dist/lib/adapters/bigquery/index.d.ts +1 -0
  12. package/dist/lib/adapters/bigquery/index.d.ts.map +1 -1
  13. package/dist/lib/adapters/bigquery/index.js +305 -259
  14. package/dist/lib/adapters/bigquery/index.js.map +4 -4
  15. package/dist/lib/adapters/clickhouse/clickhouse.d.ts +22 -0
  16. package/dist/lib/adapters/clickhouse/clickhouse.d.ts.map +1 -0
  17. package/dist/lib/adapters/clickhouse/clickhouse.sql-policy.d.ts +8 -0
  18. package/dist/lib/adapters/clickhouse/clickhouse.sql-policy.d.ts.map +1 -0
  19. package/dist/lib/adapters/clickhouse/constraint.clickhouse.grounding.d.ts +8 -0
  20. package/dist/lib/adapters/clickhouse/constraint.clickhouse.grounding.d.ts.map +1 -0
  21. package/dist/lib/adapters/clickhouse/index.d.ts +33 -0
  22. package/dist/lib/adapters/clickhouse/index.d.ts.map +1 -0
  23. package/dist/lib/adapters/clickhouse/index.js +1546 -0
  24. package/dist/lib/adapters/clickhouse/index.js.map +7 -0
  25. package/dist/lib/adapters/clickhouse/indexes.clickhouse.grounding.d.ts +8 -0
  26. package/dist/lib/adapters/clickhouse/indexes.clickhouse.grounding.d.ts.map +1 -0
  27. package/dist/lib/adapters/clickhouse/info.clickhouse.grounding.d.ts +8 -0
  28. package/dist/lib/adapters/clickhouse/info.clickhouse.grounding.d.ts.map +1 -0
  29. package/dist/lib/adapters/clickhouse/row-count.clickhouse.grounding.d.ts +8 -0
  30. package/dist/lib/adapters/clickhouse/row-count.clickhouse.grounding.d.ts.map +1 -0
  31. package/dist/lib/adapters/clickhouse/table.clickhouse.grounding.d.ts +11 -0
  32. package/dist/lib/adapters/clickhouse/table.clickhouse.grounding.d.ts.map +1 -0
  33. package/dist/lib/adapters/clickhouse/view.clickhouse.grounding.d.ts +9 -0
  34. package/dist/lib/adapters/clickhouse/view.clickhouse.grounding.d.ts.map +1 -0
  35. package/dist/lib/adapters/groundings/abstract.grounding.d.ts +11 -16
  36. package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -1
  37. package/dist/lib/adapters/groundings/context.d.ts +3 -0
  38. package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
  39. package/dist/lib/adapters/groundings/index.js +17 -0
  40. package/dist/lib/adapters/groundings/index.js.map +2 -2
  41. package/dist/lib/adapters/groundings/table.grounding.d.ts +1 -0
  42. package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -1
  43. package/dist/lib/adapters/groundings/view.grounding.d.ts +1 -0
  44. package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -1
  45. package/dist/lib/adapters/mysql/index.d.ts +1 -0
  46. package/dist/lib/adapters/mysql/index.d.ts.map +1 -1
  47. package/dist/lib/adapters/mysql/index.js +319 -259
  48. package/dist/lib/adapters/mysql/index.js.map +4 -4
  49. package/dist/lib/adapters/mysql/mysql.d.ts.map +1 -1
  50. package/dist/lib/adapters/mysql/mysql.sql-policy.d.ts +11 -0
  51. package/dist/lib/adapters/mysql/mysql.sql-policy.d.ts.map +1 -0
  52. package/dist/lib/adapters/parser-sql-policy.d.ts +23 -0
  53. package/dist/lib/adapters/parser-sql-policy.d.ts.map +1 -0
  54. package/dist/lib/adapters/postgres/index.d.ts +1 -0
  55. package/dist/lib/adapters/postgres/index.d.ts.map +1 -1
  56. package/dist/lib/adapters/postgres/index.js +325 -259
  57. package/dist/lib/adapters/postgres/index.js.map +4 -4
  58. package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
  59. package/dist/lib/adapters/postgres/postgres.sql-policy.d.ts +10 -0
  60. package/dist/lib/adapters/postgres/postgres.sql-policy.d.ts.map +1 -0
  61. package/dist/lib/adapters/posthog/definitions.posthog.grounding.d.ts +19 -0
  62. package/dist/lib/adapters/posthog/definitions.posthog.grounding.d.ts.map +1 -0
  63. package/dist/lib/adapters/posthog/index.d.ts +22 -0
  64. package/dist/lib/adapters/posthog/index.d.ts.map +1 -0
  65. package/dist/lib/adapters/posthog/index.js +1303 -0
  66. package/dist/lib/adapters/posthog/index.js.map +7 -0
  67. package/dist/lib/adapters/posthog/info.posthog.grounding.d.ts +7 -0
  68. package/dist/lib/adapters/posthog/info.posthog.grounding.d.ts.map +1 -0
  69. package/dist/lib/adapters/posthog/posthog.d.ts +23 -0
  70. package/dist/lib/adapters/posthog/posthog.d.ts.map +1 -0
  71. package/dist/lib/adapters/posthog/posthog.sql-policy.d.ts +8 -0
  72. package/dist/lib/adapters/posthog/posthog.sql-policy.d.ts.map +1 -0
  73. package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts +16 -0
  74. package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts.map +1 -0
  75. package/dist/lib/adapters/posthog/transport.d.ts +16 -0
  76. package/dist/lib/adapters/posthog/transport.d.ts.map +1 -0
  77. package/dist/lib/adapters/posthog/types.d.ts +95 -0
  78. package/dist/lib/adapters/posthog/types.d.ts.map +1 -0
  79. package/dist/lib/adapters/spreadsheet/index.js +303 -259
  80. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  81. package/dist/lib/adapters/spreadsheet/parser.d.ts.map +1 -1
  82. package/dist/lib/adapters/sql-policy.d.ts +19 -0
  83. package/dist/lib/adapters/sql-policy.d.ts.map +1 -0
  84. package/dist/lib/adapters/sqlite/index.d.ts +1 -0
  85. package/dist/lib/adapters/sqlite/index.d.ts.map +1 -1
  86. package/dist/lib/adapters/sqlite/index.js +305 -259
  87. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  88. package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -1
  89. package/dist/lib/adapters/sqlite/sqlite.sql-policy.d.ts +8 -0
  90. package/dist/lib/adapters/sqlite/sqlite.sql-policy.d.ts.map +1 -0
  91. package/dist/lib/adapters/sqlserver/index.d.ts +1 -0
  92. package/dist/lib/adapters/sqlserver/index.d.ts.map +1 -1
  93. package/dist/lib/adapters/sqlserver/index.js +312 -259
  94. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  95. package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
  96. package/dist/lib/adapters/sqlserver/sqlserver.sql-policy.d.ts +11 -0
  97. package/dist/lib/adapters/sqlserver/sqlserver.sql-policy.d.ts.map +1 -0
  98. package/dist/lib/agents/exceptions.d.ts +1 -1
  99. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  100. package/dist/lib/sql-scope-error.d.ts +3 -0
  101. package/dist/lib/sql-scope-error.d.ts.map +1 -0
  102. package/dist/lib/synthesis/index.js +32 -38
  103. package/dist/lib/synthesis/index.js.map +4 -4
  104. package/package.json +35 -23
  105. package/dist/lib/adapters/runtime-scope.d.ts +0 -16
  106. package/dist/lib/adapters/runtime-scope.d.ts.map +0 -1
@@ -2,10 +2,10 @@
2
2
  import { format as formatSql } from "sql-formatter";
3
3
 
4
4
  // packages/text2sql/src/lib/agents/exceptions.ts
5
- var sqlValidationMarker = Symbol("SQLValidationError");
6
- var unanswerableSqlMarker = Symbol("UnanswerableSQLError");
7
- var sqlScopeMarker = Symbol("SQLScopeError");
8
- var sqlReadOnlyMarker = Symbol("SQLReadOnlyError");
5
+ var sqlValidationMarker = /* @__PURE__ */ Symbol("SQLValidationError");
6
+ var unanswerableSqlMarker = /* @__PURE__ */ Symbol("UnanswerableSQLError");
7
+ var sqlScopeMarker = /* @__PURE__ */ Symbol("SQLScopeError");
8
+ var sqlReadOnlyMarker = /* @__PURE__ */ Symbol("SQLReadOnlyError");
9
9
  var SQLValidationError = class _SQLValidationError extends Error {
10
10
  [sqlValidationMarker];
11
11
  constructor(message) {
@@ -165,164 +165,21 @@ function createGroundingContext(options = {}) {
165
165
  views: [],
166
166
  relationships: [],
167
167
  info: void 0,
168
+ fragments: [],
168
169
  cache: /* @__PURE__ */ new Map(),
169
170
  onProgress: timestampProgress(options.onProgress)
170
171
  };
171
172
  }
172
173
 
173
- // packages/text2sql/src/lib/adapters/runtime-scope.ts
174
- import nodeSqlParser from "node-sql-parser";
175
- var { Parser } = nodeSqlParser;
176
- var parser = new Parser();
177
- function parseStatementTypes(sql, dialect) {
178
- const ast = parser.astify(sql, { database: dialect });
179
- const statements = Array.isArray(ast) ? ast : [ast];
180
- return statements.flatMap(
181
- (statement) => isAstLike(statement) && typeof statement.type === "string" ? [statement.type] : []
182
- );
183
- }
184
- function extractBaseEntityReferences(sql, dialect) {
185
- const ast = parser.astify(sql, { database: dialect });
186
- const state = {
187
- cteNames: /* @__PURE__ */ new Set(),
188
- references: /* @__PURE__ */ new Map()
189
- };
190
- visitNode(ast, state);
191
- return Array.from(state.references.values());
192
- }
193
- function buildOutOfScopePayload(sql, referencedEntities, allowedEntities) {
194
- return {
195
- error: `Query references entities outside grounded scope: ${referencedEntities.join(", ")}`,
196
- error_type: "OUT_OF_SCOPE",
197
- suggestion: "Restrict the query to grounded tables/views or expand grounding to include the referenced entities.",
198
- sql_attempted: sql,
199
- referenced_entities: referencedEntities,
200
- allowed_entities: allowedEntities
201
- };
202
- }
203
- function buildScopeParseErrorPayload(sql, dialect, error) {
204
- const parserError = error instanceof Error ? error.message : String(error ?? "Unknown error");
205
- return {
206
- error: `SQL scope analysis failed before validation/execution: ${parserError}`,
207
- error_type: "SQL_SCOPE_PARSE_ERROR",
208
- suggestion: "Rewrite the query into simpler SQL that can be analyzed safely, or extend parser coverage for this dialect feature.",
209
- sql_attempted: sql,
210
- parser_dialect: dialect,
211
- parser_error: parserError
212
- };
213
- }
214
- function visitNode(node, state) {
215
- if (Array.isArray(node)) {
216
- for (const item of node) {
217
- visitNode(item, state);
218
- }
219
- return;
220
- }
221
- if (!isAstLike(node)) {
222
- return;
223
- }
224
- if (isStatementNode(node)) {
225
- visitStatement(node, state);
226
- return;
227
- }
228
- if (isTableReferenceNode(node)) {
229
- addReference(node, state);
230
- }
231
- for (const value of Object.values(node)) {
232
- visitNode(value, state);
233
- }
234
- }
235
- function visitStatement(node, parentState) {
236
- const localState = {
237
- cteNames: new Set(parentState.cteNames),
238
- references: parentState.references
239
- };
240
- const withItems = Array.isArray(node.with) ? node.with : [];
241
- for (const item of withItems) {
242
- if (!isAstLike(item)) {
243
- continue;
244
- }
245
- const cteName = readCteName(item);
246
- if (cteName) {
247
- localState.cteNames.add(caseFold(cteName));
248
- }
249
- }
250
- for (const item of withItems) {
251
- if (!isAstLike(item)) {
252
- continue;
253
- }
254
- visitNode(item.stmt, localState);
255
- }
256
- for (const [key, value] of Object.entries(node)) {
257
- if (key === "with") {
258
- continue;
259
- }
260
- visitNode(value, localState);
261
- }
262
- }
263
- function addReference(node, state) {
264
- const table2 = typeof node.table === "string" ? node.table : null;
265
- if (!table2) {
266
- return;
267
- }
268
- if (state.cteNames.has(caseFold(table2))) {
269
- return;
270
- }
271
- const db = typeof node.db === "string" ? node.db : null;
272
- const key = db ? `${db}.${table2}` : table2;
273
- if (!state.references.has(key)) {
274
- state.references.set(key, { db, table: table2 });
275
- }
276
- }
277
- function readCteName(node) {
278
- const name = node.name;
279
- if (typeof name === "string") {
280
- return name;
281
- }
282
- if (!isAstLike(name)) {
283
- return void 0;
284
- }
285
- const value = name.value;
286
- return typeof value === "string" ? value : void 0;
287
- }
288
- function isStatementNode(node) {
289
- const type = node.type;
290
- return typeof type === "string" && ["delete", "insert", "replace", "select", "update"].includes(type);
291
- }
292
- function isTableReferenceNode(node) {
293
- if (node.type === "column_ref") {
294
- return false;
295
- }
296
- if (typeof node.table !== "string") {
297
- return false;
298
- }
299
- return "addition" in node || "as" in node || "db" in node || "join" in node || "operator" in node || "surround" in node || "table_hint" in node || "temporal_table" in node;
300
- }
301
- function isAstLike(value) {
302
- return typeof value === "object" && value !== null;
303
- }
304
- function caseFold(value) {
305
- return value.toLowerCase();
306
- }
307
-
308
174
  // packages/text2sql/src/lib/adapters/adapter.ts
309
- var PARSER_DIALECTS_BY_FORMATTER_LANGUAGE = {
310
- sqlite: ["sqlite", "mysql"],
311
- postgresql: ["postgresql"],
312
- bigquery: ["bigquery"],
313
- transactsql: ["transactsql"],
314
- mysql: ["mysql"]
315
- };
316
- function parserDialectsFor(formatterLanguage) {
317
- const candidates = PARSER_DIALECTS_BY_FORMATTER_LANGUAGE[formatterLanguage];
318
- if (!candidates) {
319
- throw new TypeError(
320
- `No scope dialect mapping for formatter language "${formatterLanguage}". Add it to PARSER_DIALECTS_BY_FORMATTER_LANGUAGE in adapter.ts.`
321
- );
322
- }
323
- return candidates;
324
- }
325
175
  var Adapter = class {
176
+ #policyAnalyzer;
177
+ /**
178
+ * Injects the strategy that guards validate() and execute().
179
+ */
180
+ constructor(policyAnalyzer) {
181
+ this.#policyAnalyzer = policyAnalyzer;
182
+ }
326
183
  /**
327
184
  * Introspect the database schema and return context fragments.
328
185
  *
@@ -358,14 +215,14 @@ var Adapter = class {
358
215
  }
359
216
  /**
360
217
  * Resolve the allowed entity names (tables + views) from grounding config.
361
- * Runs all configured groundings and returns the resolved set of names.
218
+ * Asks each grounding to contribute its entities; annotators contribute
219
+ * nothing, so their metadata scans are skipped.
362
220
  * Results are NOT cached — call once and store the result.
363
221
  */
364
222
  async resolveAllowedEntities() {
365
223
  const ctx = createGroundingContext();
366
224
  for (const fn of this.grounding) {
367
- const grounding = fn(this);
368
- await grounding.execute(ctx);
225
+ await fn(this).contributeEntities(ctx);
369
226
  }
370
227
  return [...ctx.tables.map((t) => t.name), ...ctx.views.map((v) => v.name)];
371
228
  }
@@ -410,6 +267,7 @@ var Adapter = class {
410
267
  this.#relationshipToFragment(rel, sourceTable, targetTable)
411
268
  );
412
269
  }
270
+ fragments.push(...ctx.fragments ?? []);
413
271
  return fragments;
414
272
  }
415
273
  /**
@@ -542,120 +400,33 @@ var Adapter = class {
542
400
  #decodeShellEscapes(sql) {
543
401
  return sql.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\([().*])/g, "$1").replace(/\\(?=$)/gm, "");
544
402
  }
545
- #checkReadOnly(sql) {
546
- const statementTypes = this.#readParsedStatementTypes(sql);
547
- if (statementTypes) {
548
- if (statementTypes.length === 1 && statementTypes[0] === "select") {
549
- return null;
550
- }
551
- return "only SELECT or WITH queries allowed";
552
- }
553
- const keyword = this.#firstStatementKeyword(sql);
554
- if (keyword === "SELECT" || keyword === "WITH") return null;
555
- return "only SELECT or WITH queries allowed";
556
- }
557
- #readParsedStatementTypes(sql) {
558
- for (const dialect of parserDialectsFor(this.formatterLanguage)) {
559
- try {
560
- return parseStatementTypes(sql, dialect);
561
- } catch {
562
- }
563
- }
564
- return null;
565
- }
566
- #firstStatementKeyword(sql) {
567
- let offset = 0;
568
- while (offset < sql.length) {
569
- if (/\s/.test(sql[offset])) {
570
- offset++;
571
- continue;
572
- }
573
- if (sql.startsWith("--", offset)) {
574
- offset += 2;
575
- while (offset < sql.length && sql[offset] !== "\n" && sql[offset] !== "\r") {
576
- offset++;
577
- }
578
- continue;
579
- }
580
- if (sql.startsWith("/*", offset)) {
581
- const commentEnd = sql.indexOf("*/", offset + 2);
582
- if (commentEnd === -1) return null;
583
- offset = commentEnd + 2;
584
- continue;
585
- }
586
- const keyword = /^[A-Za-z]+/.exec(sql.slice(offset));
587
- return keyword ? keyword[0].toUpperCase() : null;
588
- }
589
- return null;
590
- }
591
403
  #cachedAllowedEntities = null;
592
404
  async #resolveScope() {
593
405
  if (this.#cachedAllowedEntities) return this.#cachedAllowedEntities;
594
406
  this.#cachedAllowedEntities = await this.resolveAllowedEntities();
595
407
  return this.#cachedAllowedEntities;
596
408
  }
597
- async #checkScope(sql, allowedEntities) {
598
- const candidates = parserDialectsFor(this.formatterLanguage);
599
- let references = null;
600
- let lastError;
601
- let lastDialect = candidates[0];
602
- for (const candidate of candidates) {
603
- try {
604
- references = extractBaseEntityReferences(sql, candidate);
605
- break;
606
- } catch (error) {
607
- lastDialect = candidate;
608
- lastError = error;
609
- }
610
- }
611
- if (references === null) {
612
- return buildScopeParseErrorPayload(sql, lastDialect, lastError);
613
- }
614
- if (references.length === 0) return null;
615
- const allowedQualified = new Set(
616
- allowedEntities.map((e) => e.toLowerCase())
617
- );
618
- const allowedUnqualified = /* @__PURE__ */ new Set();
619
- for (const entity of allowedEntities) {
620
- const dot = entity.lastIndexOf(".");
621
- if (dot !== -1) {
622
- allowedUnqualified.add(entity.slice(dot + 1).toLowerCase());
623
- } else {
624
- allowedUnqualified.add(entity.toLowerCase());
625
- }
626
- }
627
- const outOfScope = references.map((ref) => ref.db ? `${ref.db}.${ref.table}` : ref.table).filter((name) => {
628
- const lower = name.toLowerCase();
629
- if (name.includes(".")) {
630
- if (allowedQualified.has(lower)) return false;
631
- const parts = lower.split(".");
632
- if (parts.length >= 3) {
633
- const datasetTable = parts.slice(-2).join(".");
634
- if (allowedQualified.has(datasetTable)) return false;
635
- }
636
- return true;
637
- }
638
- return !allowedQualified.has(lower) && !allowedUnqualified.has(lower);
409
+ async #analyzePolicy(sql) {
410
+ return this.#policyAnalyzer.analyze(sql, {
411
+ resolveAllowedEntities: () => this.#resolveScope()
639
412
  });
640
- if (outOfScope.length === 0) return null;
641
- return buildOutOfScopePayload(sql, outOfScope, allowedEntities);
642
413
  }
643
414
  async validate(sql) {
644
415
  const decoded = this.#decodeShellEscapes(sql);
645
- const readOnlyError = this.#checkReadOnly(decoded);
646
- if (readOnlyError) return readOnlyError;
647
- const allowed = await this.#resolveScope();
648
- const scopeError = await this.#checkScope(decoded, allowed);
649
- if (scopeError) return JSON.stringify(scopeError);
416
+ const violation = await this.#analyzePolicy(decoded);
417
+ if (violation?.kind === "read-only") return violation.message;
418
+ if (violation?.kind === "scope") return JSON.stringify(violation.payload);
650
419
  return this.validateImpl(decoded);
651
420
  }
652
421
  async execute(sql) {
653
422
  const decoded = this.#decodeShellEscapes(sql);
654
- const readOnlyError = this.#checkReadOnly(decoded);
655
- if (readOnlyError) throw new SQLReadOnlyError(readOnlyError);
656
- const allowed = await this.#resolveScope();
657
- const scopeError = await this.#checkScope(decoded, allowed);
658
- if (scopeError) throw new SQLScopeError(scopeError);
423
+ const violation = await this.#analyzePolicy(decoded);
424
+ if (violation?.kind === "read-only") {
425
+ throw new SQLReadOnlyError(violation.message);
426
+ }
427
+ if (violation?.kind === "scope") {
428
+ throw new SQLScopeError(violation.payload);
429
+ }
659
430
  return this.executeImpl(decoded);
660
431
  }
661
432
  /**
@@ -736,6 +507,16 @@ var AbstractGrounding = class {
736
507
  this.name = name;
737
508
  this.phase = phase;
738
509
  }
510
+ /**
511
+ * Populate ctx with the tables/views this grounding makes available for scope
512
+ * resolution. Annotators decorate entities other groundings produced and add
513
+ * no membership, so the default is a no-op; entity-producing groundings
514
+ * override it. This lets scope resolution skip the annotators' metadata scans.
515
+ *
516
+ * @param _ctx - Shared context for accumulating entity membership
517
+ */
518
+ async contributeEntities(_ctx) {
519
+ }
739
520
  };
740
521
 
741
522
  // packages/text2sql/src/lib/adapters/groundings/column-stats.grounding.ts
@@ -995,6 +776,9 @@ var TableGrounding = class extends AbstractGrounding {
995
776
  this.#forward = config.forward;
996
777
  this.#backward = config.backward;
997
778
  }
779
+ async contributeEntities(ctx) {
780
+ await this.execute(ctx);
781
+ }
998
782
  /**
999
783
  * Execute the grounding process.
1000
784
  * Writes discovered tables and relationships to the context.
@@ -1298,6 +1082,264 @@ var SqliteRowCountGrounding = class extends RowCountGrounding {
1298
1082
  }
1299
1083
  };
1300
1084
 
1085
+ // packages/text2sql/src/lib/adapters/parser-sql-policy.ts
1086
+ import nodeSqlParser from "node-sql-parser";
1087
+
1088
+ // packages/text2sql/src/lib/sql-scope-error.ts
1089
+ function buildScopeParseErrorPayload(sql, parserDialect, error) {
1090
+ const parserError = error instanceof Error ? error.message : String(error ?? "Unknown error");
1091
+ return {
1092
+ error: `SQL scope analysis failed before validation/execution: ${parserError}`,
1093
+ error_type: "SQL_SCOPE_PARSE_ERROR",
1094
+ suggestion: "Rewrite the query into simpler SQL that can be analyzed safely, or extend parser coverage for this dialect feature.",
1095
+ sql_attempted: sql,
1096
+ parser_dialect: parserDialect,
1097
+ parser_error: parserError
1098
+ };
1099
+ }
1100
+
1101
+ // packages/text2sql/src/lib/adapters/parser-sql-policy.ts
1102
+ var READ_ONLY_MESSAGE = "only SELECT or WITH queries allowed";
1103
+ var { Parser } = nodeSqlParser;
1104
+ var parser = new Parser();
1105
+ var ParserSqlPolicyAnalyzer = class {
1106
+ readOnlyPolicy = {};
1107
+ async analyze(sql, context) {
1108
+ if (!this.#isReadOnly(sql)) {
1109
+ return { kind: "read-only", message: READ_ONLY_MESSAGE };
1110
+ }
1111
+ const scopeError = await this.#checkScope(sql, context);
1112
+ return scopeError ? { kind: "scope", payload: scopeError } : null;
1113
+ }
1114
+ #isReadOnly(sql) {
1115
+ const statements = this.#readParsedStatements(sql);
1116
+ if (statements) {
1117
+ return statements.length === 1 && statements[0]?.type === "select" && !containsBlockedOperation(statements[0], this.readOnlyPolicy);
1118
+ }
1119
+ const keyword = firstStatementKeyword(sql);
1120
+ return keyword === "SELECT" || keyword === "WITH";
1121
+ }
1122
+ #readParsedStatements(sql) {
1123
+ for (const dialect of this.dialects) {
1124
+ try {
1125
+ return parseStatements(sql, dialect);
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ return null;
1130
+ }
1131
+ async #checkScope(sql, context) {
1132
+ let references = null;
1133
+ let lastError;
1134
+ let lastDialect = this.dialects[0];
1135
+ for (const dialect of this.dialects) {
1136
+ try {
1137
+ references = extractBaseEntityReferences(sql, dialect);
1138
+ break;
1139
+ } catch (error) {
1140
+ lastDialect = dialect;
1141
+ lastError = error;
1142
+ }
1143
+ }
1144
+ if (references === null) {
1145
+ return buildScopeParseErrorPayload(sql, lastDialect, lastError);
1146
+ }
1147
+ if (references.length === 0) return null;
1148
+ const allowedEntities = await context.resolveAllowedEntities();
1149
+ const allowedQualified = new Set(
1150
+ allowedEntities.map((entity) => entity.toLowerCase())
1151
+ );
1152
+ const allowedUnqualified = /* @__PURE__ */ new Set();
1153
+ for (const entity of allowedEntities) {
1154
+ const dot = entity.lastIndexOf(".");
1155
+ allowedUnqualified.add(
1156
+ (dot === -1 ? entity : entity.slice(dot + 1)).toLowerCase()
1157
+ );
1158
+ }
1159
+ const outOfScope = references.map(
1160
+ (reference) => reference.db ? `${reference.db}.${reference.table}` : reference.table
1161
+ ).filter((name) => {
1162
+ const lower = name.toLowerCase();
1163
+ if (!name.includes(".")) {
1164
+ return !allowedQualified.has(lower) && !allowedUnqualified.has(lower);
1165
+ }
1166
+ if (allowedQualified.has(lower)) return false;
1167
+ const parts = lower.split(".");
1168
+ return parts.length < 3 || !allowedQualified.has(parts.slice(-2).join("."));
1169
+ });
1170
+ return outOfScope.length === 0 ? null : buildOutOfScopePayload(sql, outOfScope, [...allowedEntities]);
1171
+ }
1172
+ };
1173
+ function parseStatements(sql, dialect) {
1174
+ const ast = parser.astify(sql, { database: dialect });
1175
+ const statements = Array.isArray(ast) ? ast : [ast];
1176
+ return statements.filter(isAstLike);
1177
+ }
1178
+ function containsBlockedOperation(node, policy) {
1179
+ if (Array.isArray(node)) {
1180
+ return node.some((item) => containsBlockedOperation(item, policy));
1181
+ }
1182
+ if (!isAstLike(node)) return false;
1183
+ if (policy.blockSelectInto && node.type === "select" && isAstLike(node.into) && node.into.type === "into") {
1184
+ return true;
1185
+ }
1186
+ if (policy.blockLockingReads && node.locking_read != null) return true;
1187
+ if (policy.blockAssignments && node.type === "assign") return true;
1188
+ if (policy.blockServerQualifiedRelations && isTableReferenceNode(node) && typeof node.server === "string") {
1189
+ return true;
1190
+ }
1191
+ if (policy.blockTableHints && node.table_hint != null) return true;
1192
+ if (node.type === "function") {
1193
+ const functionIdentifier = readFunctionIdentifier(node);
1194
+ if (functionIdentifier && policy.blockQualifiedFunctions && functionIdentifier.parts.length > 1) {
1195
+ return true;
1196
+ }
1197
+ if (functionIdentifier && policy.blockedFunctions?.some(
1198
+ (blocked) => blocked.toLowerCase() === functionIdentifier.parts.at(-1)
1199
+ )) {
1200
+ return true;
1201
+ }
1202
+ if (functionIdentifier && policy.blockedFunctionPrefixes?.some(
1203
+ (prefix) => functionIdentifier.parts.at(-1)?.startsWith(prefix.toLowerCase())
1204
+ )) {
1205
+ return true;
1206
+ }
1207
+ }
1208
+ return Object.values(node).some(
1209
+ (value) => containsBlockedOperation(value, policy)
1210
+ );
1211
+ }
1212
+ function readFunctionIdentifier(node) {
1213
+ if (!isAstLike(node.name)) return null;
1214
+ const parts = [];
1215
+ if (isAstLike(node.name.schema) && typeof node.name.schema.value === "string") {
1216
+ appendIdentifierParts(parts, node.name.schema.value);
1217
+ }
1218
+ const names = Array.isArray(node.name.name) ? node.name.name : [];
1219
+ for (const name of names) {
1220
+ if (isAstLike(name) && typeof name.value === "string") {
1221
+ appendIdentifierParts(parts, name.value);
1222
+ }
1223
+ }
1224
+ return parts.length > 0 ? { parts } : null;
1225
+ }
1226
+ function appendIdentifierParts(parts, value) {
1227
+ parts.push(
1228
+ ...value.toLowerCase().split(".").filter((part) => part.length > 0)
1229
+ );
1230
+ }
1231
+ function extractBaseEntityReferences(sql, dialect) {
1232
+ const ast = parser.astify(sql, { database: dialect });
1233
+ const state = {
1234
+ cteNames: /* @__PURE__ */ new Set(),
1235
+ references: /* @__PURE__ */ new Map()
1236
+ };
1237
+ visitNode(ast, state);
1238
+ return Array.from(state.references.values());
1239
+ }
1240
+ function firstStatementKeyword(sql) {
1241
+ let offset = 0;
1242
+ while (offset < sql.length) {
1243
+ if (/\s/.test(sql[offset])) {
1244
+ offset++;
1245
+ continue;
1246
+ }
1247
+ if (sql.startsWith("--", offset)) {
1248
+ offset += 2;
1249
+ while (offset < sql.length && sql[offset] !== "\n" && sql[offset] !== "\r") {
1250
+ offset++;
1251
+ }
1252
+ continue;
1253
+ }
1254
+ if (sql.startsWith("/*", offset)) {
1255
+ const commentEnd = sql.indexOf("*/", offset + 2);
1256
+ if (commentEnd === -1) return null;
1257
+ offset = commentEnd + 2;
1258
+ continue;
1259
+ }
1260
+ const keyword = /^[A-Za-z]+/.exec(sql.slice(offset));
1261
+ return keyword ? keyword[0].toUpperCase() : null;
1262
+ }
1263
+ return null;
1264
+ }
1265
+ function buildOutOfScopePayload(sql, referencedEntities, allowedEntities) {
1266
+ return {
1267
+ error: `Query references entities outside grounded scope: ${referencedEntities.join(", ")}`,
1268
+ error_type: "OUT_OF_SCOPE",
1269
+ suggestion: "Restrict the query to grounded tables/views or expand grounding to include the referenced entities.",
1270
+ sql_attempted: sql,
1271
+ referenced_entities: referencedEntities,
1272
+ allowed_entities: allowedEntities
1273
+ };
1274
+ }
1275
+ function visitNode(node, state) {
1276
+ if (Array.isArray(node)) {
1277
+ for (const item of node) visitNode(item, state);
1278
+ return;
1279
+ }
1280
+ if (!isAstLike(node)) return;
1281
+ if (isStatementNode(node)) {
1282
+ visitStatement(node, state);
1283
+ return;
1284
+ }
1285
+ if (isTableReferenceNode(node)) addReference(node, state);
1286
+ for (const value of Object.values(node)) visitNode(value, state);
1287
+ }
1288
+ function visitStatement(node, parentState) {
1289
+ const localState = {
1290
+ cteNames: new Set(parentState.cteNames),
1291
+ references: parentState.references
1292
+ };
1293
+ const withItems = Array.isArray(node.with) ? node.with : [];
1294
+ for (const item of withItems) {
1295
+ if (!isAstLike(item)) continue;
1296
+ const cteName = readCteName(item);
1297
+ if (cteName) localState.cteNames.add(caseFold(cteName));
1298
+ }
1299
+ for (const item of withItems) {
1300
+ if (isAstLike(item)) visitNode(item.stmt, localState);
1301
+ }
1302
+ for (const [key, value] of Object.entries(node)) {
1303
+ if (key !== "with") visitNode(value, localState);
1304
+ }
1305
+ }
1306
+ function addReference(node, state) {
1307
+ const table2 = typeof node.table === "string" ? node.table : null;
1308
+ if (!table2 || state.cteNames.has(caseFold(table2))) return;
1309
+ const db = typeof node.db === "string" ? node.db : null;
1310
+ const key = db ? `${db}.${table2}` : table2;
1311
+ if (!state.references.has(key)) state.references.set(key, { db, table: table2 });
1312
+ }
1313
+ function readCteName(node) {
1314
+ const name = node.name;
1315
+ if (typeof name === "string") return name;
1316
+ if (!isAstLike(name)) return void 0;
1317
+ return typeof name.value === "string" ? name.value : void 0;
1318
+ }
1319
+ function isStatementNode(node) {
1320
+ return typeof node.type === "string" && ["delete", "insert", "replace", "select", "update"].includes(node.type);
1321
+ }
1322
+ function isTableReferenceNode(node) {
1323
+ if (node.type === "column_ref" || typeof node.table !== "string")
1324
+ return false;
1325
+ return "addition" in node || "as" in node || "db" in node || "join" in node || "operator" in node || "surround" in node || "table_hint" in node || "temporal_table" in node;
1326
+ }
1327
+ function isAstLike(value) {
1328
+ return typeof value === "object" && value !== null;
1329
+ }
1330
+ function caseFold(value) {
1331
+ return value.toLowerCase();
1332
+ }
1333
+
1334
+ // packages/text2sql/src/lib/adapters/sqlite/sqlite.sql-policy.ts
1335
+ var SqliteSqlPolicyAnalyzer = class extends ParserSqlPolicyAnalyzer {
1336
+ // MySQL covers SQLite syntax that node-sql-parser's SQLite grammar rejects.
1337
+ dialects = ["sqlite", "mysql"];
1338
+ readOnlyPolicy = {
1339
+ blockedFunctions: ["EDIT", "LOAD_EXTENSION", "READFILE", "WRITEFILE"]
1340
+ };
1341
+ };
1342
+
1301
1343
  // packages/text2sql/src/lib/adapters/sqlite/sqlite.ts
1302
1344
  var SQL_ERROR_MAP = [
1303
1345
  {
@@ -1356,7 +1398,7 @@ var Sqlite = class extends Adapter {
1356
1398
  systemSchemas = [];
1357
1399
  formatterLanguage = "sqlite";
1358
1400
  constructor(options) {
1359
- super();
1401
+ super(new SqliteSqlPolicyAnalyzer());
1360
1402
  if (!options || typeof options.execute !== "function") {
1361
1403
  throw new Error("Sqlite adapter requires an execute function.");
1362
1404
  }
@@ -1503,8 +1545,10 @@ function rowCount(config = {}) {
1503
1545
  import { DatabaseSync } from "node:sqlite";
1504
1546
 
1505
1547
  // packages/text2sql/src/lib/adapters/spreadsheet/parser.ts
1548
+ import fs from "node:fs";
1506
1549
  import * as path from "node:path";
1507
1550
  import XLSX from "xlsx";
1551
+ XLSX.set_fs(fs);
1508
1552
  function parseFile(filePath) {
1509
1553
  const ext = path.extname(filePath).toLowerCase();
1510
1554
  let workbook;