@deepagents/text2sql 1.0.0 → 2.2.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.
@@ -174,6 +174,13 @@ function createGroundingContext(options = {}) {
174
174
  import nodeSqlParser from "node-sql-parser";
175
175
  var { Parser } = nodeSqlParser;
176
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
+ }
177
184
  function extractBaseEntityReferences(sql, dialect) {
178
185
  const ast = parser.astify(sql, { database: dialect });
179
186
  const state = {
@@ -299,6 +306,22 @@ function caseFold(value) {
299
306
  }
300
307
 
301
308
  // 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
+ }
302
325
  var Adapter = class {
303
326
  /**
304
327
  * Introspect the database schema and return context fragments.
@@ -520,10 +543,49 @@ var Adapter = class {
520
543
  return sql.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\([().*])/g, "$1").replace(/\\(?=$)/gm, "");
521
544
  }
522
545
  #checkReadOnly(sql) {
523
- const upper = sql.trimStart().toUpperCase();
524
- if (!upper.startsWith("SELECT") && !upper.startsWith("WITH")) {
546
+ const statementTypes = this.#readParsedStatementTypes(sql);
547
+ if (statementTypes) {
548
+ if (statementTypes.length === 1 && statementTypes[0] === "select") {
549
+ return null;
550
+ }
525
551
  return "only SELECT or WITH queries allowed";
526
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
+ }
527
589
  return null;
528
590
  }
529
591
  #cachedAllowedEntities = null;
@@ -533,36 +595,21 @@ var Adapter = class {
533
595
  return this.#cachedAllowedEntities;
534
596
  }
535
597
  async #checkScope(sql, allowedEntities) {
536
- const dialect = this.formatterLanguage;
537
- const scopeDialects = {
538
- sqlite: ["sqlite", "mysql"],
539
- postgresql: ["postgresql"],
540
- bigquery: ["bigquery"],
541
- transactsql: ["transactsql"],
542
- mysql: ["mysql"]
543
- };
544
- const candidates = scopeDialects[dialect];
545
- if (!candidates) {
546
- throw new TypeError(
547
- `No scope dialect mapping for formatter language "${dialect}". Add it to the scopeDialects map in Adapter.#checkScope.`
548
- );
549
- }
598
+ const candidates = parserDialectsFor(this.formatterLanguage);
550
599
  let references = null;
551
600
  let lastError;
601
+ let lastDialect = candidates[0];
552
602
  for (const candidate of candidates) {
553
603
  try {
554
604
  references = extractBaseEntityReferences(sql, candidate);
555
605
  break;
556
606
  } catch (error) {
607
+ lastDialect = candidate;
557
608
  lastError = error;
558
609
  }
559
610
  }
560
611
  if (references === null) {
561
- return buildScopeParseErrorPayload(
562
- sql,
563
- dialect,
564
- lastError
565
- );
612
+ return buildScopeParseErrorPayload(sql, lastDialect, lastError);
566
613
  }
567
614
  if (references.length === 0) return null;
568
615
  const allowedQualified = new Set(