@deepagents/text2sql 0.14.0 → 0.16.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 (76) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2065 -3298
  4. package/dist/index.js.map +4 -4
  5. package/dist/lib/adapters/adapter.d.ts +4 -0
  6. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  7. package/dist/lib/adapters/bigquery/bigquery.d.ts +42 -0
  8. package/dist/lib/adapters/bigquery/bigquery.d.ts.map +1 -0
  9. package/dist/lib/adapters/bigquery/constraint.bigquery.grounding.d.ts +11 -0
  10. package/dist/lib/adapters/bigquery/constraint.bigquery.grounding.d.ts.map +1 -0
  11. package/dist/lib/adapters/bigquery/index.d.ts +35 -0
  12. package/dist/lib/adapters/bigquery/index.d.ts.map +1 -0
  13. package/dist/lib/adapters/bigquery/index.js +1332 -0
  14. package/dist/lib/adapters/bigquery/index.js.map +7 -0
  15. package/dist/lib/adapters/bigquery/indexes.bigquery.grounding.d.ts +14 -0
  16. package/dist/lib/adapters/bigquery/indexes.bigquery.grounding.d.ts.map +1 -0
  17. package/dist/lib/adapters/bigquery/info.bigquery.grounding.d.ts +9 -0
  18. package/dist/lib/adapters/bigquery/info.bigquery.grounding.d.ts.map +1 -0
  19. package/dist/lib/adapters/bigquery/row-count.bigquery.grounding.d.ts +14 -0
  20. package/dist/lib/adapters/bigquery/row-count.bigquery.grounding.d.ts.map +1 -0
  21. package/dist/lib/adapters/bigquery/table.bigquery.grounding.d.ts +15 -0
  22. package/dist/lib/adapters/bigquery/table.bigquery.grounding.d.ts.map +1 -0
  23. package/dist/lib/adapters/bigquery/view.bigquery.grounding.d.ts +12 -0
  24. package/dist/lib/adapters/bigquery/view.bigquery.grounding.d.ts.map +1 -0
  25. package/dist/lib/adapters/groundings/index.js +11 -2058
  26. package/dist/lib/adapters/groundings/index.js.map +4 -4
  27. package/dist/lib/adapters/mysql/index.js +22 -2058
  28. package/dist/lib/adapters/mysql/index.js.map +4 -4
  29. package/dist/lib/adapters/mysql/mysql.d.ts +1 -0
  30. package/dist/lib/adapters/mysql/mysql.d.ts.map +1 -1
  31. package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts +2 -4
  32. package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts.map +1 -1
  33. package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts +0 -8
  34. package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts.map +1 -1
  35. package/dist/lib/adapters/postgres/index.js +266 -2184
  36. package/dist/lib/adapters/postgres/index.js.map +4 -4
  37. package/dist/lib/adapters/postgres/postgres.d.ts +1 -0
  38. package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
  39. package/dist/lib/adapters/postgres/row-count.postgres.grounding.d.ts +0 -3
  40. package/dist/lib/adapters/postgres/row-count.postgres.grounding.d.ts.map +1 -1
  41. package/dist/lib/adapters/spreadsheet/index.js +22 -56
  42. package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
  43. package/dist/lib/adapters/sqlite/index.js +22 -2058
  44. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  45. package/dist/lib/adapters/sqlite/sqlite.d.ts +1 -0
  46. package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -1
  47. package/dist/lib/adapters/sqlserver/index.js +22 -2058
  48. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  49. package/dist/lib/adapters/sqlserver/sqlserver.d.ts +1 -0
  50. package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
  51. package/dist/lib/agents/result-tools.d.ts +1 -3
  52. package/dist/lib/agents/result-tools.d.ts.map +1 -1
  53. package/dist/lib/agents/sql.agent.d.ts.map +1 -1
  54. package/dist/lib/fragments/schema.d.ts +2 -0
  55. package/dist/lib/fragments/schema.d.ts.map +1 -1
  56. package/dist/lib/fs/index.d.ts +2 -1
  57. package/dist/lib/fs/index.d.ts.map +1 -1
  58. package/dist/lib/fs/mssql/ddl.mssql-fs.d.ts +2 -0
  59. package/dist/lib/fs/mssql/ddl.mssql-fs.d.ts.map +1 -0
  60. package/dist/lib/fs/mssql/mssql-fs.d.ts +57 -0
  61. package/dist/lib/fs/mssql/mssql-fs.d.ts.map +1 -0
  62. package/dist/lib/fs/scoped-fs.d.ts +2 -0
  63. package/dist/lib/fs/scoped-fs.d.ts.map +1 -1
  64. package/dist/lib/fs/{sqlite-fs.d.ts → sqlite/sqlite-fs.d.ts} +2 -0
  65. package/dist/lib/fs/sqlite/sqlite-fs.d.ts.map +1 -0
  66. package/dist/lib/fs/tracked-fs.d.ts +2 -0
  67. package/dist/lib/fs/tracked-fs.d.ts.map +1 -1
  68. package/dist/lib/instructions.d.ts.map +1 -1
  69. package/dist/lib/sql.d.ts +3 -3
  70. package/dist/lib/sql.d.ts.map +1 -1
  71. package/dist/lib/synthesis/index.js +255 -2120
  72. package/dist/lib/synthesis/index.js.map +4 -4
  73. package/package.json +21 -13
  74. package/dist/lib/agents/text2sql.agent.d.ts +0 -3
  75. package/dist/lib/agents/text2sql.agent.d.ts.map +0 -1
  76. package/dist/lib/fs/sqlite-fs.d.ts.map +0 -1
@@ -0,0 +1,1332 @@
1
+ // packages/text2sql/src/lib/adapters/adapter.ts
2
+ import { format as formatSql } from "sql-formatter";
3
+
4
+ // packages/text2sql/src/lib/fragments/schema.ts
5
+ function dialectInfo(input) {
6
+ return {
7
+ name: "dialectInfo",
8
+ data: {
9
+ dialect: input.dialect,
10
+ ...input.version && { version: input.version },
11
+ ...input.database && { database: input.database }
12
+ }
13
+ };
14
+ }
15
+ function table(input) {
16
+ return {
17
+ name: "table",
18
+ data: {
19
+ name: input.name,
20
+ ...input.schema && { schema: input.schema },
21
+ ...input.rowCount != null && { rowCount: input.rowCount },
22
+ ...input.sizeHint && { sizeHint: input.sizeHint },
23
+ columns: input.columns,
24
+ ...input.indexes?.length && { indexes: input.indexes },
25
+ ...input.constraints?.length && { constraints: input.constraints }
26
+ }
27
+ };
28
+ }
29
+ function column(input) {
30
+ return {
31
+ name: "column",
32
+ data: {
33
+ name: input.name,
34
+ type: input.type,
35
+ ...input.pk && { pk: true },
36
+ ...input.fk && { fk: input.fk },
37
+ ...input.unique && { unique: true },
38
+ ...input.notNull && { notNull: true },
39
+ ...input.default && { default: input.default },
40
+ ...input.indexed && { indexed: true },
41
+ ...input.values?.length && { values: input.values },
42
+ ...input.stats && { stats: input.stats }
43
+ }
44
+ };
45
+ }
46
+ function index(input) {
47
+ return {
48
+ name: "index",
49
+ data: {
50
+ name: input.name,
51
+ columns: input.columns,
52
+ ...input.unique && { unique: true },
53
+ ...input.type && { type: input.type }
54
+ }
55
+ };
56
+ }
57
+ function constraint(input) {
58
+ return {
59
+ name: "constraint",
60
+ data: {
61
+ name: input.name,
62
+ type: input.type,
63
+ ...input.columns?.length && { columns: input.columns },
64
+ ...input.definition && { definition: input.definition },
65
+ ...input.defaultValue && { defaultValue: input.defaultValue },
66
+ ...input.referencedTable && { referencedTable: input.referencedTable },
67
+ ...input.referencedColumns?.length && {
68
+ referencedColumns: input.referencedColumns
69
+ }
70
+ }
71
+ };
72
+ }
73
+ function view(input) {
74
+ return {
75
+ name: "view",
76
+ data: {
77
+ name: input.name,
78
+ ...input.schema && { schema: input.schema },
79
+ columns: input.columns,
80
+ ...input.definition && { definition: input.definition }
81
+ }
82
+ };
83
+ }
84
+ function relationship(input) {
85
+ return {
86
+ name: "relationship",
87
+ data: {
88
+ from: input.from,
89
+ to: input.to,
90
+ ...input.cardinality && { cardinality: input.cardinality }
91
+ }
92
+ };
93
+ }
94
+
95
+ // packages/text2sql/src/lib/adapters/groundings/context.ts
96
+ function createGroundingContext() {
97
+ return {
98
+ tables: [],
99
+ views: [],
100
+ relationships: [],
101
+ info: void 0
102
+ };
103
+ }
104
+
105
+ // packages/text2sql/src/lib/adapters/adapter.ts
106
+ var Adapter = class {
107
+ /**
108
+ * Introspect the database schema and return context fragments.
109
+ *
110
+ * Executes all configured groundings to populate the context, then
111
+ * generates fragments from the complete context data.
112
+ *
113
+ * @param ctx - Optional grounding context for sharing state between groundings
114
+ * @returns Array of context fragments representing the database schema
115
+ */
116
+ async introspect(ctx = createGroundingContext()) {
117
+ for (const fn of this.grounding) {
118
+ const grounding = fn(this);
119
+ await grounding.execute(ctx);
120
+ }
121
+ return this.#toSchemaFragments(ctx);
122
+ }
123
+ /**
124
+ * Convert complete grounding context to schema fragments.
125
+ * Called after all groundings have populated ctx with data.
126
+ */
127
+ #toSchemaFragments(ctx) {
128
+ const fragments = [];
129
+ if (ctx.info) {
130
+ fragments.push(
131
+ dialectInfo({
132
+ dialect: ctx.info.dialect,
133
+ version: ctx.info.version,
134
+ database: ctx.info.database
135
+ })
136
+ );
137
+ }
138
+ for (const t of ctx.tables) {
139
+ fragments.push(this.#tableToFragment(t));
140
+ }
141
+ for (const v of ctx.views) {
142
+ fragments.push(this.#viewToFragment(v));
143
+ }
144
+ const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
145
+ for (const rel of ctx.relationships) {
146
+ const sourceTable = tableMap.get(rel.table);
147
+ const targetTable = tableMap.get(rel.referenced_table);
148
+ fragments.push(
149
+ this.#relationshipToFragment(rel, sourceTable, targetTable)
150
+ );
151
+ }
152
+ if (ctx.report) {
153
+ fragments.push({ name: "businessContext", data: ctx.report });
154
+ }
155
+ return fragments;
156
+ }
157
+ /**
158
+ * Convert a Table to a table fragment with nested column, index, and constraint fragments.
159
+ */
160
+ #tableToFragment(t) {
161
+ const pkConstraint = t.constraints?.find((c) => c.type === "PRIMARY_KEY");
162
+ const pkColumns = new Set(pkConstraint?.columns ?? []);
163
+ const notNullColumns = new Set(
164
+ t.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
165
+ );
166
+ const defaultByColumn = /* @__PURE__ */ new Map();
167
+ for (const c of t.constraints?.filter((c2) => c2.type === "DEFAULT") ?? []) {
168
+ for (const col of c.columns ?? []) {
169
+ if (c.defaultValue != null) {
170
+ defaultByColumn.set(col, c.defaultValue);
171
+ }
172
+ }
173
+ }
174
+ const uniqueColumns = new Set(
175
+ t.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
176
+ );
177
+ const fkByColumn = /* @__PURE__ */ new Map();
178
+ for (const c of t.constraints?.filter((c2) => c2.type === "FOREIGN_KEY") ?? []) {
179
+ const cols = c.columns ?? [];
180
+ const refCols = c.referencedColumns ?? [];
181
+ for (let i = 0; i < cols.length; i++) {
182
+ const refCol = refCols[i] ?? refCols[0] ?? cols[i];
183
+ fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
184
+ }
185
+ }
186
+ const columnFragments = t.columns.map(
187
+ (col) => column({
188
+ name: col.name,
189
+ type: col.type,
190
+ pk: pkColumns.has(col.name) || void 0,
191
+ fk: fkByColumn.get(col.name),
192
+ unique: uniqueColumns.has(col.name) || void 0,
193
+ notNull: notNullColumns.has(col.name) || void 0,
194
+ default: defaultByColumn.get(col.name),
195
+ indexed: col.isIndexed || void 0,
196
+ values: col.values,
197
+ stats: col.stats
198
+ })
199
+ );
200
+ const indexFragments = (t.indexes ?? []).map(
201
+ (idx) => index({
202
+ name: idx.name,
203
+ columns: idx.columns,
204
+ unique: idx.unique,
205
+ type: idx.type
206
+ })
207
+ );
208
+ const constraintFragments = (t.constraints ?? []).filter(
209
+ (c) => c.type === "CHECK" || c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
210
+ ).map(
211
+ (c) => constraint({
212
+ name: c.name,
213
+ type: c.type,
214
+ columns: c.columns,
215
+ definition: c.definition
216
+ })
217
+ );
218
+ return table({
219
+ name: t.name,
220
+ schema: t.schema,
221
+ rowCount: t.rowCount,
222
+ sizeHint: t.sizeHint,
223
+ columns: columnFragments,
224
+ indexes: indexFragments.length > 0 ? indexFragments : void 0,
225
+ constraints: constraintFragments.length > 0 ? constraintFragments : void 0
226
+ });
227
+ }
228
+ /**
229
+ * Convert a View to a view fragment with nested column fragments.
230
+ */
231
+ #viewToFragment(v) {
232
+ const columnFragments = v.columns.map(
233
+ (col) => column({
234
+ name: col.name,
235
+ type: col.type,
236
+ values: col.values,
237
+ stats: col.stats
238
+ })
239
+ );
240
+ return view({
241
+ name: v.name,
242
+ schema: v.schema,
243
+ columns: columnFragments,
244
+ definition: v.definition
245
+ });
246
+ }
247
+ /**
248
+ * Convert a Relationship to a relationship fragment.
249
+ * Infers cardinality from row counts if available.
250
+ */
251
+ #relationshipToFragment(rel, sourceTable, targetTable) {
252
+ const sourceCount = sourceTable?.rowCount;
253
+ const targetCount = targetTable?.rowCount;
254
+ let cardinality;
255
+ if (sourceCount != null && targetCount != null && targetCount > 0) {
256
+ const ratio = sourceCount / targetCount;
257
+ if (ratio > 5) {
258
+ cardinality = "many-to-one";
259
+ } else if (ratio < 1.2 && ratio > 0.8) {
260
+ cardinality = "one-to-one";
261
+ } else if (ratio < 0.2) {
262
+ cardinality = "one-to-many";
263
+ }
264
+ }
265
+ return relationship({
266
+ from: { table: rel.table, columns: rel.from },
267
+ to: { table: rel.referenced_table, columns: rel.to },
268
+ cardinality
269
+ });
270
+ }
271
+ format(sql) {
272
+ try {
273
+ return formatSql(sql, { language: this.formatterLanguage });
274
+ } catch {
275
+ return sql;
276
+ }
277
+ }
278
+ /**
279
+ * Convert unknown database value to number.
280
+ * Handles number, bigint, and string types.
281
+ */
282
+ toNumber(value) {
283
+ if (typeof value === "number" && Number.isFinite(value)) {
284
+ return value;
285
+ }
286
+ if (typeof value === "bigint") {
287
+ return Number(value);
288
+ }
289
+ if (typeof value === "string" && value.trim() !== "") {
290
+ const parsed = Number(value);
291
+ return Number.isFinite(parsed) ? parsed : void 0;
292
+ }
293
+ return void 0;
294
+ }
295
+ /**
296
+ * Parse a potentially qualified table name into schema and table parts.
297
+ */
298
+ parseTableName(name) {
299
+ if (name.includes(".")) {
300
+ const [schema, ...rest] = name.split(".");
301
+ return { schema, table: rest.join(".") };
302
+ }
303
+ return { schema: this.defaultSchema ?? "", table: name };
304
+ }
305
+ /**
306
+ * Escape a string value for use in SQL string literals (single quotes).
307
+ * Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
308
+ */
309
+ escapeString(value) {
310
+ return value.replace(/'/g, "''");
311
+ }
312
+ /**
313
+ * Build a SQL filter clause to include/exclude schemas.
314
+ * @param columnName - The schema column name (e.g., 'TABLE_SCHEMA')
315
+ * @param allowedSchemas - If provided, filter to these schemas only
316
+ */
317
+ buildSchemaFilter(columnName, allowedSchemas) {
318
+ if (allowedSchemas && allowedSchemas.length > 0) {
319
+ const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
320
+ return `AND ${columnName} IN (${values})`;
321
+ }
322
+ if (this.systemSchemas.length > 0) {
323
+ const values = this.systemSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
324
+ return `AND ${columnName} NOT IN (${values})`;
325
+ }
326
+ return "";
327
+ }
328
+ };
329
+
330
+ // packages/text2sql/src/lib/adapters/groundings/abstract.grounding.ts
331
+ var AbstractGrounding = class {
332
+ /**
333
+ * Grounding identifier for debugging/logging.
334
+ */
335
+ name;
336
+ constructor(name) {
337
+ this.name = name;
338
+ }
339
+ };
340
+
341
+ // packages/text2sql/src/lib/adapters/groundings/constraint.grounding.ts
342
+ var ConstraintGrounding = class extends AbstractGrounding {
343
+ constructor(config = {}) {
344
+ super("constraint");
345
+ }
346
+ /**
347
+ * Execute the grounding process.
348
+ * Annotates tables in ctx.tables with their constraints.
349
+ */
350
+ async execute(ctx) {
351
+ for (const table2 of ctx.tables) {
352
+ try {
353
+ table2.constraints = await this.getConstraints(table2.name);
354
+ } catch (error) {
355
+ console.warn("Error collecting constraints for", table2.name, error);
356
+ }
357
+ }
358
+ }
359
+ };
360
+
361
+ // packages/text2sql/src/lib/adapters/groundings/indexes.grounding.ts
362
+ var IndexesGrounding = class extends AbstractGrounding {
363
+ constructor(config = {}) {
364
+ super("index");
365
+ }
366
+ /**
367
+ * Execute the grounding process.
368
+ * Annotates tables in ctx.tables with their indexes and marks indexed columns.
369
+ */
370
+ async execute(ctx) {
371
+ for (const table2 of ctx.tables) {
372
+ table2.indexes = await this.getIndexes(table2.name);
373
+ for (const index2 of table2.indexes ?? []) {
374
+ for (const colName of index2.columns) {
375
+ const column2 = table2.columns.find((c) => c.name === colName);
376
+ if (column2) {
377
+ column2.isIndexed = true;
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
383
+ };
384
+
385
+ // packages/text2sql/src/lib/adapters/groundings/info.grounding.ts
386
+ var InfoGrounding = class extends AbstractGrounding {
387
+ constructor(config = {}) {
388
+ super("dialectInfo");
389
+ }
390
+ /**
391
+ * Execute the grounding process.
392
+ * Writes database info to ctx.info.
393
+ */
394
+ async execute(ctx) {
395
+ ctx.info = await this.collectInfo();
396
+ }
397
+ };
398
+
399
+ // packages/text2sql/src/lib/adapters/groundings/report.grounding.ts
400
+ import { groq } from "@ai-sdk/groq";
401
+ import { tool } from "ai";
402
+ import dedent from "dedent";
403
+ import z from "zod";
404
+ import "@deepagents/agent";
405
+ import {
406
+ ContextEngine,
407
+ InMemoryContextStore,
408
+ agent,
409
+ fragment,
410
+ persona,
411
+ user
412
+ } from "@deepagents/context";
413
+ var ReportGrounding = class extends AbstractGrounding {
414
+ #adapter;
415
+ #model;
416
+ #cache;
417
+ #forceRefresh;
418
+ constructor(adapter, config = {}) {
419
+ super("business_context");
420
+ this.#adapter = adapter;
421
+ this.#model = config.model ?? groq("openai/gpt-oss-20b");
422
+ this.#cache = config.cache;
423
+ this.#forceRefresh = config.forceRefresh ?? false;
424
+ }
425
+ async execute(ctx) {
426
+ if (!this.#forceRefresh && this.#cache) {
427
+ const cached = await this.#cache.get();
428
+ if (cached) {
429
+ ctx.report = cached;
430
+ return;
431
+ }
432
+ }
433
+ const report2 = await this.#generateReport();
434
+ ctx.report = report2;
435
+ if (this.#cache) {
436
+ await this.#cache.set(report2);
437
+ }
438
+ }
439
+ async #generateReport() {
440
+ const context = new ContextEngine({
441
+ store: new InMemoryContextStore(),
442
+ chatId: `report-gen-${crypto.randomUUID()}`,
443
+ userId: "system"
444
+ });
445
+ context.set(
446
+ persona({
447
+ name: "db-report-agent",
448
+ role: "Database analyst",
449
+ objective: "Analyze the database and write a contextual report about what it represents"
450
+ }),
451
+ fragment(
452
+ "instructions",
453
+ dedent`
454
+ Write a business context that helps another agent answer questions accurately.
455
+
456
+ For EACH table, do queries ONE AT A TIME:
457
+ 1. SELECT COUNT(*) to get row count
458
+ 2. SELECT * LIMIT 3 to see sample data
459
+
460
+ Then write a report with:
461
+ - What business this database is for
462
+ - For each table: purpose, row count, and example of what the data looks like
463
+
464
+ Include concrete examples like "Track prices are $0.99",
465
+ "Customer names like 'Luís Gonçalves'", etc.
466
+
467
+ Keep it 400-600 words, conversational style.
468
+ `
469
+ ),
470
+ user(
471
+ "Please analyze the database and write a contextual report about what this database represents."
472
+ )
473
+ );
474
+ const adapter = this.#adapter;
475
+ const reportAgent = agent({
476
+ name: "db-report-agent",
477
+ model: this.#model,
478
+ context,
479
+ tools: {
480
+ query_database: tool({
481
+ description: "Execute a SELECT query to explore the database and gather insights.",
482
+ inputSchema: z.object({
483
+ sql: z.string().describe("The SELECT query to execute"),
484
+ purpose: z.string().describe(
485
+ "What insight you are trying to gather with this query"
486
+ )
487
+ }),
488
+ execute: ({ sql }) => {
489
+ return adapter.execute(sql);
490
+ }
491
+ })
492
+ }
493
+ });
494
+ const result = await reportAgent.generate({});
495
+ return result.text;
496
+ }
497
+ };
498
+
499
+ // packages/text2sql/src/lib/adapters/groundings/row-count.grounding.ts
500
+ var RowCountGrounding = class extends AbstractGrounding {
501
+ constructor(config = {}) {
502
+ super("rowCount");
503
+ }
504
+ /**
505
+ * Execute the grounding process.
506
+ * Annotates tables in ctx.tables with row counts and size hints.
507
+ */
508
+ async execute(ctx) {
509
+ for (const table2 of ctx.tables) {
510
+ const count = await this.getRowCount(table2.name);
511
+ if (count != null) {
512
+ table2.rowCount = count;
513
+ table2.sizeHint = this.#classifyRowCount(count);
514
+ }
515
+ }
516
+ }
517
+ /**
518
+ * Classify row count into a size hint category.
519
+ */
520
+ #classifyRowCount(count) {
521
+ if (count < 100) return "tiny";
522
+ if (count < 1e3) return "small";
523
+ if (count < 1e4) return "medium";
524
+ if (count < 1e5) return "large";
525
+ return "huge";
526
+ }
527
+ };
528
+
529
+ // packages/text2sql/src/lib/adapters/groundings/table.grounding.ts
530
+ var TableGrounding = class extends AbstractGrounding {
531
+ #filter;
532
+ #forward;
533
+ #backward;
534
+ constructor(config = {}) {
535
+ super("table");
536
+ this.#filter = config.filter;
537
+ this.#forward = config.forward;
538
+ this.#backward = config.backward;
539
+ }
540
+ /**
541
+ * Execute the grounding process.
542
+ * Writes discovered tables and relationships to the context.
543
+ */
544
+ async execute(ctx) {
545
+ const seedTables = await this.applyFilter();
546
+ const forward = this.#forward;
547
+ const backward = this.#backward;
548
+ if (!forward && !backward) {
549
+ const tables3 = await Promise.all(
550
+ seedTables.map((name) => this.getTable(name))
551
+ );
552
+ ctx.tables.push(...tables3);
553
+ return;
554
+ }
555
+ const tables2 = {};
556
+ const allRelationships = [];
557
+ const seenRelationships = /* @__PURE__ */ new Set();
558
+ const forwardQueue = [];
559
+ const backwardQueue = [];
560
+ const forwardVisited = /* @__PURE__ */ new Set();
561
+ const backwardVisited = /* @__PURE__ */ new Set();
562
+ for (const name of seedTables) {
563
+ if (forward) forwardQueue.push({ name, depth: 0 });
564
+ if (backward) backwardQueue.push({ name, depth: 0 });
565
+ }
566
+ const forwardLimit = forward === true ? Infinity : forward || 0;
567
+ while (forwardQueue.length > 0) {
568
+ const item = forwardQueue.shift();
569
+ if (!item) break;
570
+ const { name, depth } = item;
571
+ if (forwardVisited.has(name)) continue;
572
+ forwardVisited.add(name);
573
+ if (!tables2[name]) {
574
+ tables2[name] = await this.getTable(name);
575
+ }
576
+ if (depth < forwardLimit) {
577
+ const rels = await this.findOutgoingRelations(name);
578
+ for (const rel of rels) {
579
+ this.addRelationship(rel, allRelationships, seenRelationships);
580
+ if (!forwardVisited.has(rel.referenced_table)) {
581
+ forwardQueue.push({ name: rel.referenced_table, depth: depth + 1 });
582
+ }
583
+ }
584
+ }
585
+ }
586
+ const backwardLimit = backward === true ? Infinity : backward || 0;
587
+ while (backwardQueue.length > 0) {
588
+ const item = backwardQueue.shift();
589
+ if (!item) break;
590
+ const { name, depth } = item;
591
+ if (backwardVisited.has(name)) continue;
592
+ backwardVisited.add(name);
593
+ if (!tables2[name]) {
594
+ tables2[name] = await this.getTable(name);
595
+ }
596
+ if (depth < backwardLimit) {
597
+ const rels = await this.findIncomingRelations(name);
598
+ for (const rel of rels) {
599
+ this.addRelationship(rel, allRelationships, seenRelationships);
600
+ if (!backwardVisited.has(rel.table)) {
601
+ backwardQueue.push({ name: rel.table, depth: depth + 1 });
602
+ }
603
+ }
604
+ }
605
+ }
606
+ const tablesList = Object.values(tables2);
607
+ ctx.tables.push(...tablesList);
608
+ ctx.relationships.push(...allRelationships);
609
+ }
610
+ /**
611
+ * Apply the filter to get seed table names.
612
+ * If filter is an explicit array, skip querying all table names.
613
+ */
614
+ async applyFilter() {
615
+ const filter = this.#filter;
616
+ if (Array.isArray(filter)) {
617
+ return filter;
618
+ }
619
+ const names = await this.getAllTableNames();
620
+ if (!filter) {
621
+ return names;
622
+ }
623
+ if (filter instanceof RegExp) {
624
+ return names.filter((name) => filter.test(name));
625
+ }
626
+ return names.filter(filter);
627
+ }
628
+ /**
629
+ * Add a relationship to the collection, deduplicating by key.
630
+ */
631
+ addRelationship(rel, all, seen) {
632
+ const key = `${rel.table}:${rel.from.join(",")}:${rel.referenced_table}:${rel.to.join(",")}`;
633
+ if (!seen.has(key)) {
634
+ seen.add(key);
635
+ all.push(rel);
636
+ }
637
+ }
638
+ };
639
+
640
+ // packages/text2sql/src/lib/adapters/bigquery/bigquery.ts
641
+ function formatBigQueryError(sql, error) {
642
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : typeof error === "object" && error !== null ? error.message ?? JSON.stringify(error) : "Unknown error occurred";
643
+ return {
644
+ error: errorMessage,
645
+ error_type: "BIGQUERY_ERROR",
646
+ suggestion: "Validate the query (dry-run) and review table/dataset names, nested field paths, and parameter bindings.",
647
+ sql_attempted: sql
648
+ };
649
+ }
650
+ var BigQuery = class extends Adapter {
651
+ #options;
652
+ #datasetSet;
653
+ grounding;
654
+ defaultSchema;
655
+ systemSchemas = [];
656
+ formatterLanguage = "bigquery";
657
+ constructor(options) {
658
+ super();
659
+ if (!options || typeof options.execute !== "function") {
660
+ throw new Error("BigQuery adapter requires an execute(sql) function.");
661
+ }
662
+ if (typeof options.validate !== "function") {
663
+ throw new Error(
664
+ "BigQuery adapter requires a validate(sql) function. Provide a BigQuery dry-run validator (recommended) so generated SQL can be validated before execution."
665
+ );
666
+ }
667
+ const datasets = (options.datasets ?? []).map((d) => d.trim()).filter(Boolean);
668
+ if (datasets.length === 0) {
669
+ throw new Error(
670
+ "BigQuery adapter requires a non-empty datasets list (e.g. datasets: ['analytics']). This scopes all introspection."
671
+ );
672
+ }
673
+ this.#options = { ...options, datasets };
674
+ this.#datasetSet = new Set(datasets);
675
+ this.grounding = options.grounding;
676
+ this.defaultSchema = datasets.length === 1 ? datasets[0] : void 0;
677
+ }
678
+ get datasets() {
679
+ return this.#options.datasets;
680
+ }
681
+ get projectId() {
682
+ return this.#options.projectId;
683
+ }
684
+ isDatasetAllowed(dataset) {
685
+ return this.#datasetSet.has(dataset);
686
+ }
687
+ /**
688
+ * Build a fully-qualified BigQuery INFORMATION_SCHEMA view reference.
689
+ * Uses standard BigQuery backtick quoting on the full path.
690
+ */
691
+ infoSchemaView(dataset, viewName) {
692
+ const projectPrefix = this.projectId ? `${this.projectId}.` : "";
693
+ return `\`${projectPrefix}${dataset}.INFORMATION_SCHEMA.${viewName}\``;
694
+ }
695
+ async execute(sql) {
696
+ return this.#options.execute(sql);
697
+ }
698
+ async validate(sql) {
699
+ try {
700
+ return await this.#options.validate(sql);
701
+ } catch (error) {
702
+ return JSON.stringify(formatBigQueryError(sql, error));
703
+ }
704
+ }
705
+ async runQuery(sql) {
706
+ const result = await this.#options.execute(sql);
707
+ if (Array.isArray(result)) {
708
+ return result;
709
+ }
710
+ if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
711
+ return result.rows;
712
+ }
713
+ throw new Error(
714
+ "BigQuery adapter execute() must return an array of rows or an object with a rows array when introspecting."
715
+ );
716
+ }
717
+ quoteIdentifier(name) {
718
+ return name.split(".").map((part) => `\`${part.replace(/`/g, "``")}\``).join(".");
719
+ }
720
+ escape(value) {
721
+ return value.replace(/`/g, "``");
722
+ }
723
+ buildSampleRowsQuery(tableName, columns, limit) {
724
+ const qualifiedTableName = this.#qualifyTableName(tableName);
725
+ const tableIdentifier = this.quoteIdentifier(qualifiedTableName);
726
+ const columnList = columns?.length ? columns.map((c) => c === "*" ? "*" : this.quoteIdentifier(c)).join(", ") : "*";
727
+ return `SELECT ${columnList} FROM ${tableIdentifier} LIMIT ${limit}`;
728
+ }
729
+ #qualifyTableName(tableName) {
730
+ if (!this.projectId) {
731
+ return tableName;
732
+ }
733
+ if (tableName.split(".").length >= 3) {
734
+ return tableName;
735
+ }
736
+ return `${this.projectId}.${tableName}`;
737
+ }
738
+ };
739
+
740
+ // packages/text2sql/src/lib/adapters/bigquery/constraint.bigquery.grounding.ts
741
+ var BigQueryConstraintGrounding = class extends ConstraintGrounding {
742
+ #adapter;
743
+ constructor(adapter, config = {}) {
744
+ super(config);
745
+ this.#adapter = adapter;
746
+ }
747
+ async getConstraints(tableName) {
748
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(tableName);
749
+ const constraints2 = [];
750
+ const columnRows = await this.#adapter.runQuery(`
751
+ SELECT column_name, is_nullable, column_default
752
+ FROM ${this.#adapter.infoSchemaView(dataset, "COLUMNS")}
753
+ WHERE table_name = '${this.#adapter.escapeString(table2)}'
754
+ ORDER BY ordinal_position
755
+ `);
756
+ for (const row of columnRows) {
757
+ const col = row.column_name;
758
+ if (!col) continue;
759
+ if ((row.is_nullable ?? "").toUpperCase() === "NO") {
760
+ constraints2.push({
761
+ name: `${tableName}.${col}.NOT_NULL`,
762
+ type: "NOT_NULL",
763
+ columns: [col]
764
+ });
765
+ }
766
+ if (row.column_default != null && row.column_default !== "") {
767
+ constraints2.push({
768
+ name: `${tableName}.${col}.DEFAULT`,
769
+ type: "DEFAULT",
770
+ columns: [col],
771
+ defaultValue: row.column_default
772
+ });
773
+ }
774
+ }
775
+ const keyRows = await this.#adapter.runQuery(`
776
+ SELECT
777
+ tc.constraint_name,
778
+ tc.constraint_type,
779
+ kcu.column_name,
780
+ kcu.ordinal_position,
781
+ kcu.position_in_unique_constraint
782
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLE_CONSTRAINTS")} AS tc
783
+ JOIN ${this.#adapter.infoSchemaView(dataset, "KEY_COLUMN_USAGE")} AS kcu
784
+ ON tc.constraint_name = kcu.constraint_name
785
+ AND tc.constraint_schema = kcu.constraint_schema
786
+ WHERE tc.table_name = '${this.#adapter.escapeString(table2)}'
787
+ AND tc.constraint_type IN ('PRIMARY KEY', 'FOREIGN KEY')
788
+ ORDER BY tc.constraint_name, kcu.ordinal_position
789
+ `);
790
+ const pkByName = /* @__PURE__ */ new Map();
791
+ const fkByName = /* @__PURE__ */ new Map();
792
+ for (const row of keyRows) {
793
+ if (!row.constraint_name || !row.column_name) continue;
794
+ const type = (row.constraint_type ?? "").toUpperCase();
795
+ if (type === "PRIMARY KEY") {
796
+ const cols = pkByName.get(row.constraint_name) ?? [];
797
+ cols.push(row.column_name);
798
+ pkByName.set(row.constraint_name, cols);
799
+ continue;
800
+ }
801
+ if (type === "FOREIGN KEY") {
802
+ const cols = fkByName.get(row.constraint_name) ?? [];
803
+ cols.push({
804
+ column: row.column_name,
805
+ ordinal: row.ordinal_position ?? 0,
806
+ pkOrdinal: row.position_in_unique_constraint
807
+ });
808
+ fkByName.set(row.constraint_name, cols);
809
+ }
810
+ }
811
+ for (const [name, cols] of pkByName.entries()) {
812
+ constraints2.push({
813
+ name,
814
+ type: "PRIMARY_KEY",
815
+ columns: cols
816
+ });
817
+ }
818
+ for (const [constraintName, cols] of fkByName.entries()) {
819
+ const fk = await this.#buildForeignKeyConstraint({
820
+ constraintDataset: dataset,
821
+ constraintName,
822
+ childTableName: `${dataset}.${table2}`,
823
+ childColumns: cols
824
+ });
825
+ if (fk) constraints2.push(fk);
826
+ }
827
+ return constraints2;
828
+ }
829
+ async #buildForeignKeyConstraint(args) {
830
+ const refRows = await this.#adapter.runQuery(`
831
+ SELECT DISTINCT table_schema, table_name
832
+ FROM ${this.#adapter.infoSchemaView(args.constraintDataset, "CONSTRAINT_COLUMN_USAGE")}
833
+ WHERE constraint_name = '${this.#adapter.escapeString(args.constraintName)}'
834
+ `);
835
+ const referenced = refRows.find((r) => r.table_schema && r.table_name);
836
+ if (!referenced?.table_schema || !referenced.table_name) {
837
+ return void 0;
838
+ }
839
+ const referencedDataset = referenced.table_schema;
840
+ const referencedTable = referenced.table_name;
841
+ if (!this.#adapter.isDatasetAllowed(referencedDataset)) {
842
+ return void 0;
843
+ }
844
+ const pkConstraintRows = await this.#adapter.runQuery(`
845
+ SELECT constraint_name
846
+ FROM ${this.#adapter.infoSchemaView(referencedDataset, "TABLE_CONSTRAINTS")}
847
+ WHERE constraint_type = 'PRIMARY KEY'
848
+ AND table_name = '${this.#adapter.escapeString(referencedTable)}'
849
+ LIMIT 1
850
+ `);
851
+ const pkConstraintName = pkConstraintRows[0]?.constraint_name;
852
+ if (!pkConstraintName) return void 0;
853
+ const pkColumns = await this.#adapter.runQuery(`
854
+ SELECT column_name, ordinal_position
855
+ FROM ${this.#adapter.infoSchemaView(referencedDataset, "KEY_COLUMN_USAGE")}
856
+ WHERE constraint_name = '${this.#adapter.escapeString(pkConstraintName)}'
857
+ AND table_name = '${this.#adapter.escapeString(referencedTable)}'
858
+ ORDER BY ordinal_position
859
+ `);
860
+ const pkByOrdinal = /* @__PURE__ */ new Map();
861
+ for (const row of pkColumns) {
862
+ if (!row.column_name || row.ordinal_position == null) continue;
863
+ pkByOrdinal.set(row.ordinal_position, row.column_name);
864
+ }
865
+ const orderedChild = [...args.childColumns].sort(
866
+ (a, b) => a.ordinal - b.ordinal
867
+ );
868
+ const columns = orderedChild.map((c) => c.column);
869
+ const referencedColumns = orderedChild.map((c) => {
870
+ const pkOrdinal = c.pkOrdinal ?? c.ordinal;
871
+ return pkByOrdinal.get(pkOrdinal) ?? "unknown";
872
+ });
873
+ return {
874
+ name: args.constraintName,
875
+ type: "FOREIGN_KEY",
876
+ columns,
877
+ referencedTable: `${referencedDataset}.${referencedTable}`,
878
+ referencedColumns
879
+ };
880
+ }
881
+ };
882
+
883
+ // packages/text2sql/src/lib/adapters/bigquery/indexes.bigquery.grounding.ts
884
+ var BigQueryIndexesGrounding = class extends IndexesGrounding {
885
+ #adapter;
886
+ constructor(adapter, config = {}) {
887
+ super(config);
888
+ this.#adapter = adapter;
889
+ }
890
+ async getIndexes(tableName) {
891
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(tableName);
892
+ const rows = await this.#adapter.runQuery(`
893
+ SELECT column_name, is_partitioning_column, clustering_ordinal_position
894
+ FROM ${this.#adapter.infoSchemaView(dataset, "COLUMNS")}
895
+ WHERE table_name = '${this.#adapter.escapeString(table2)}'
896
+ AND (is_partitioning_column = 'YES' OR clustering_ordinal_position IS NOT NULL)
897
+ ORDER BY clustering_ordinal_position
898
+ `);
899
+ const partitionColumns = [];
900
+ const clusteringColumns = [];
901
+ for (const row of rows) {
902
+ if (!row.column_name) continue;
903
+ if ((row.is_partitioning_column ?? "").toUpperCase() === "YES") {
904
+ partitionColumns.push(row.column_name);
905
+ }
906
+ if (row.clustering_ordinal_position != null) {
907
+ clusteringColumns.push({
908
+ name: row.column_name,
909
+ pos: row.clustering_ordinal_position
910
+ });
911
+ }
912
+ }
913
+ const indexes2 = [];
914
+ if (partitionColumns.length > 0) {
915
+ indexes2.push({
916
+ name: `${table2}_partition`,
917
+ columns: partitionColumns,
918
+ type: "PARTITION"
919
+ });
920
+ }
921
+ if (clusteringColumns.length > 0) {
922
+ clusteringColumns.sort((a, b) => a.pos - b.pos);
923
+ indexes2.push({
924
+ name: `${table2}_clustering`,
925
+ columns: clusteringColumns.map((c) => c.name),
926
+ type: "CLUSTERING"
927
+ });
928
+ }
929
+ return indexes2;
930
+ }
931
+ };
932
+
933
+ // packages/text2sql/src/lib/adapters/bigquery/info.bigquery.grounding.ts
934
+ var BigQueryInfoGrounding = class extends InfoGrounding {
935
+ #adapter;
936
+ constructor(adapter) {
937
+ super();
938
+ this.#adapter = adapter;
939
+ }
940
+ async collectInfo() {
941
+ return {
942
+ dialect: "bigquery",
943
+ database: this.#adapter.projectId,
944
+ details: {
945
+ identifierQuote: "`",
946
+ identifiers: {
947
+ qualifiedTable: "dataset.table",
948
+ nestedFieldPath: "col.path.to.field"
949
+ },
950
+ parameters: {
951
+ positional: "?",
952
+ named: "@name"
953
+ }
954
+ }
955
+ };
956
+ }
957
+ };
958
+
959
+ // packages/text2sql/src/lib/adapters/bigquery/row-count.bigquery.grounding.ts
960
+ var BigQueryRowCountGrounding = class extends RowCountGrounding {
961
+ #adapter;
962
+ constructor(adapter, config = {}) {
963
+ super(config);
964
+ this.#adapter = adapter;
965
+ }
966
+ async getRowCount(tableName) {
967
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(tableName);
968
+ const rows = await this.#adapter.runQuery(`
969
+ SELECT total_rows
970
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLE_STORAGE")}
971
+ WHERE table_name = '${this.#adapter.escapeString(table2)}'
972
+ LIMIT 1
973
+ `);
974
+ const value = rows[0]?.total_rows;
975
+ return this.#adapter.toNumber(value);
976
+ }
977
+ };
978
+
979
+ // packages/text2sql/src/lib/adapters/bigquery/table.bigquery.grounding.ts
980
+ var BigQueryTableGrounding = class extends TableGrounding {
981
+ #adapter;
982
+ constructor(adapter, config = {}) {
983
+ super(config);
984
+ this.#adapter = adapter;
985
+ }
986
+ async applyFilter() {
987
+ const names = await super.applyFilter();
988
+ return names.filter((name) => this.#isTableInScope(name));
989
+ }
990
+ async getAllTableNames() {
991
+ const names = [];
992
+ for (const dataset of this.#adapter.datasets) {
993
+ const rows = await this.#adapter.runQuery(`
994
+ SELECT table_name
995
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLES")}
996
+ WHERE table_type = 'BASE TABLE'
997
+ ORDER BY table_name
998
+ `);
999
+ for (const row of rows) {
1000
+ if (!row.table_name) continue;
1001
+ names.push(`${dataset}.${row.table_name}`);
1002
+ }
1003
+ }
1004
+ return names;
1005
+ }
1006
+ async getTable(tableName) {
1007
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(tableName);
1008
+ const rows = await this.#adapter.runQuery(`
1009
+ SELECT
1010
+ f.field_path,
1011
+ f.data_type,
1012
+ c.ordinal_position
1013
+ FROM ${this.#adapter.infoSchemaView(dataset, "COLUMN_FIELD_PATHS")} AS f
1014
+ JOIN ${this.#adapter.infoSchemaView(dataset, "COLUMNS")} AS c
1015
+ ON f.table_name = c.table_name
1016
+ AND f.column_name = c.column_name
1017
+ WHERE f.table_name = '${this.#adapter.escapeString(table2)}'
1018
+ ORDER BY c.ordinal_position, f.field_path
1019
+ `);
1020
+ const seen = /* @__PURE__ */ new Set();
1021
+ const columns = rows.map((r) => ({
1022
+ name: r.field_path ?? "unknown",
1023
+ type: r.data_type ?? "unknown",
1024
+ ordinal: r.ordinal_position ?? 0
1025
+ })).filter((c) => {
1026
+ if (!c.name) return false;
1027
+ if (seen.has(c.name)) return false;
1028
+ seen.add(c.name);
1029
+ return true;
1030
+ }).map((c) => ({ name: c.name, type: c.type }));
1031
+ return {
1032
+ name: `${dataset}.${table2}`,
1033
+ schema: dataset,
1034
+ rawName: table2,
1035
+ columns
1036
+ };
1037
+ }
1038
+ async findOutgoingRelations(tableName) {
1039
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(tableName);
1040
+ const rows = await this.#adapter.runQuery(`
1041
+ SELECT
1042
+ kcu.constraint_name,
1043
+ kcu.column_name,
1044
+ kcu.ordinal_position,
1045
+ kcu.position_in_unique_constraint
1046
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLE_CONSTRAINTS")} AS tc
1047
+ JOIN ${this.#adapter.infoSchemaView(dataset, "KEY_COLUMN_USAGE")} AS kcu
1048
+ ON tc.constraint_name = kcu.constraint_name
1049
+ AND tc.constraint_schema = kcu.constraint_schema
1050
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1051
+ AND tc.table_name = '${this.#adapter.escapeString(table2)}'
1052
+ ORDER BY kcu.constraint_name, kcu.ordinal_position
1053
+ `);
1054
+ const byConstraint = /* @__PURE__ */ new Map();
1055
+ for (const row of rows) {
1056
+ if (!row.constraint_name || !row.column_name) continue;
1057
+ const list = byConstraint.get(row.constraint_name) ?? [];
1058
+ list.push({
1059
+ column: row.column_name,
1060
+ ordinal: row.ordinal_position ?? 0,
1061
+ pkOrdinal: row.position_in_unique_constraint
1062
+ });
1063
+ byConstraint.set(row.constraint_name, list);
1064
+ }
1065
+ const rels = [];
1066
+ for (const [constraintName, columns] of byConstraint.entries()) {
1067
+ const rel = await this.#buildForeignKeyRelationship({
1068
+ constraintDataset: dataset,
1069
+ childDataset: dataset,
1070
+ childTable: table2,
1071
+ constraintName,
1072
+ childColumns: columns
1073
+ });
1074
+ if (rel) rels.push(rel);
1075
+ }
1076
+ return rels;
1077
+ }
1078
+ async findIncomingRelations(tableName) {
1079
+ const { schema: referencedDataset, table: referencedTable } = this.#adapter.parseTableName(tableName);
1080
+ const rels = [];
1081
+ for (const constraintDataset of this.#adapter.datasets) {
1082
+ const rows = await this.#adapter.runQuery(`
1083
+ SELECT DISTINCT constraint_name
1084
+ FROM ${this.#adapter.infoSchemaView(constraintDataset, "CONSTRAINT_COLUMN_USAGE")}
1085
+ WHERE table_schema = '${this.#adapter.escapeString(referencedDataset)}'
1086
+ AND table_name = '${this.#adapter.escapeString(referencedTable)}'
1087
+ `);
1088
+ for (const row of rows) {
1089
+ if (!row.constraint_name) continue;
1090
+ const rel = await this.#buildForeignKeyRelationshipFromConstraintName(
1091
+ constraintDataset,
1092
+ row.constraint_name
1093
+ );
1094
+ if (rel && rel.referenced_table === `${referencedDataset}.${referencedTable}`) {
1095
+ rels.push(rel);
1096
+ }
1097
+ }
1098
+ }
1099
+ return rels;
1100
+ }
1101
+ #isTableInScope(tableName) {
1102
+ const { schema } = this.#adapter.parseTableName(tableName);
1103
+ return this.#adapter.isDatasetAllowed(schema);
1104
+ }
1105
+ async #buildForeignKeyRelationshipFromConstraintName(constraintDataset, constraintName) {
1106
+ const keyRows = await this.#adapter.runQuery(`
1107
+ SELECT
1108
+ kcu.constraint_name,
1109
+ tc.table_name AS child_table_name,
1110
+ kcu.column_name,
1111
+ kcu.ordinal_position,
1112
+ kcu.position_in_unique_constraint
1113
+ FROM ${this.#adapter.infoSchemaView(constraintDataset, "TABLE_CONSTRAINTS")} AS tc
1114
+ JOIN ${this.#adapter.infoSchemaView(constraintDataset, "KEY_COLUMN_USAGE")} AS kcu
1115
+ ON tc.constraint_name = kcu.constraint_name
1116
+ AND tc.constraint_schema = kcu.constraint_schema
1117
+ WHERE tc.constraint_type = 'FOREIGN KEY'
1118
+ AND tc.constraint_name = '${this.#adapter.escapeString(constraintName)}'
1119
+ ORDER BY kcu.ordinal_position
1120
+ `);
1121
+ if (keyRows.length === 0) return void 0;
1122
+ const childTable = keyRows[0]?.child_table_name;
1123
+ if (!childTable) return void 0;
1124
+ const childColumns = keyRows.filter((r) => r.column_name).map((r) => ({
1125
+ column: r.column_name ?? "unknown",
1126
+ ordinal: r.ordinal_position ?? 0,
1127
+ pkOrdinal: r.position_in_unique_constraint
1128
+ }));
1129
+ return this.#buildForeignKeyRelationship({
1130
+ constraintDataset,
1131
+ childDataset: constraintDataset,
1132
+ childTable,
1133
+ constraintName,
1134
+ childColumns
1135
+ });
1136
+ }
1137
+ async #buildForeignKeyRelationship(args) {
1138
+ const refTableRows = await this.#adapter.runQuery(`
1139
+ SELECT DISTINCT table_schema, table_name
1140
+ FROM ${this.#adapter.infoSchemaView(args.constraintDataset, "CONSTRAINT_COLUMN_USAGE")}
1141
+ WHERE constraint_name = '${this.#adapter.escapeString(args.constraintName)}'
1142
+ `);
1143
+ const referenced = refTableRows.find((r) => r.table_schema && r.table_name);
1144
+ if (!referenced?.table_schema || !referenced.table_name) {
1145
+ return void 0;
1146
+ }
1147
+ const referencedDataset = referenced.table_schema;
1148
+ const referencedTable = referenced.table_name;
1149
+ if (!this.#adapter.isDatasetAllowed(referencedDataset)) {
1150
+ return void 0;
1151
+ }
1152
+ const pkConstraintRows = await this.#adapter.runQuery(`
1153
+ SELECT constraint_name
1154
+ FROM ${this.#adapter.infoSchemaView(referencedDataset, "TABLE_CONSTRAINTS")}
1155
+ WHERE constraint_type = 'PRIMARY KEY'
1156
+ AND table_name = '${this.#adapter.escapeString(referencedTable)}'
1157
+ LIMIT 1
1158
+ `);
1159
+ const pkConstraintName = pkConstraintRows[0]?.constraint_name;
1160
+ if (!pkConstraintName) {
1161
+ return void 0;
1162
+ }
1163
+ const pkColumnRows = await this.#adapter.runQuery(`
1164
+ SELECT column_name, ordinal_position
1165
+ FROM ${this.#adapter.infoSchemaView(referencedDataset, "KEY_COLUMN_USAGE")}
1166
+ WHERE constraint_name = '${this.#adapter.escapeString(pkConstraintName)}'
1167
+ AND table_name = '${this.#adapter.escapeString(referencedTable)}'
1168
+ ORDER BY ordinal_position
1169
+ `);
1170
+ const pkByOrdinal = /* @__PURE__ */ new Map();
1171
+ for (const row of pkColumnRows) {
1172
+ if (!row.column_name || row.ordinal_position == null) continue;
1173
+ pkByOrdinal.set(row.ordinal_position, row.column_name);
1174
+ }
1175
+ const orderedChild = [...args.childColumns].sort(
1176
+ (a, b) => a.ordinal - b.ordinal
1177
+ );
1178
+ const from = orderedChild.map((c) => c.column);
1179
+ const to = orderedChild.map((c) => {
1180
+ const pkOrdinal = c.pkOrdinal ?? c.ordinal;
1181
+ return pkByOrdinal.get(pkOrdinal) ?? "unknown";
1182
+ });
1183
+ return {
1184
+ table: `${args.childDataset}.${args.childTable}`,
1185
+ from,
1186
+ referenced_table: `${referencedDataset}.${referencedTable}`,
1187
+ to
1188
+ };
1189
+ }
1190
+ };
1191
+
1192
+ // packages/text2sql/src/lib/adapters/groundings/view.grounding.ts
1193
+ var ViewGrounding = class extends AbstractGrounding {
1194
+ #filter;
1195
+ constructor(config = {}) {
1196
+ super("view");
1197
+ this.#filter = config.filter;
1198
+ }
1199
+ /**
1200
+ * Execute the grounding process.
1201
+ * Writes discovered views to the context.
1202
+ */
1203
+ async execute(ctx) {
1204
+ const viewNames = await this.applyFilter();
1205
+ const views2 = await Promise.all(
1206
+ viewNames.map((name) => this.getView(name))
1207
+ );
1208
+ ctx.views.push(...views2);
1209
+ }
1210
+ /**
1211
+ * Apply the filter to get view names.
1212
+ * If filter is an explicit array, skip querying all view names.
1213
+ */
1214
+ async applyFilter() {
1215
+ const filter = this.#filter;
1216
+ if (Array.isArray(filter)) {
1217
+ return filter;
1218
+ }
1219
+ const names = await this.getAllViewNames();
1220
+ if (!filter) {
1221
+ return names;
1222
+ }
1223
+ if (filter instanceof RegExp) {
1224
+ return names.filter((name) => filter.test(name));
1225
+ }
1226
+ return names.filter(filter);
1227
+ }
1228
+ };
1229
+
1230
+ // packages/text2sql/src/lib/adapters/bigquery/view.bigquery.grounding.ts
1231
+ var BigQueryViewGrounding = class extends ViewGrounding {
1232
+ #adapter;
1233
+ constructor(adapter, config = {}) {
1234
+ super(config);
1235
+ this.#adapter = adapter;
1236
+ }
1237
+ async applyFilter() {
1238
+ const names = await super.applyFilter();
1239
+ return names.filter((name) => this.#isViewInScope(name));
1240
+ }
1241
+ async getAllViewNames() {
1242
+ const names = [];
1243
+ for (const dataset of this.#adapter.datasets) {
1244
+ const rows = await this.#adapter.runQuery(`
1245
+ SELECT table_name
1246
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLES")}
1247
+ WHERE table_type IN ('VIEW', 'MATERIALIZED VIEW')
1248
+ ORDER BY table_name
1249
+ `);
1250
+ for (const row of rows) {
1251
+ if (!row.table_name) continue;
1252
+ names.push(`${dataset}.${row.table_name}`);
1253
+ }
1254
+ }
1255
+ return names;
1256
+ }
1257
+ async getView(viewName) {
1258
+ const { schema: dataset, table: table2 } = this.#adapter.parseTableName(viewName);
1259
+ const defRows = await this.#adapter.runQuery(`
1260
+ SELECT ddl
1261
+ FROM ${this.#adapter.infoSchemaView(dataset, "TABLES")}
1262
+ WHERE table_name = '${this.#adapter.escapeString(table2)}'
1263
+ AND table_type IN ('VIEW', 'MATERIALIZED VIEW')
1264
+ LIMIT 1
1265
+ `);
1266
+ const columns = await this.#adapter.runQuery(`
1267
+ SELECT column_name, data_type
1268
+ FROM ${this.#adapter.infoSchemaView(dataset, "COLUMNS")}
1269
+ WHERE table_name = '${this.#adapter.escapeString(table2)}'
1270
+ ORDER BY ordinal_position
1271
+ `);
1272
+ return {
1273
+ name: `${dataset}.${table2}`,
1274
+ schema: dataset,
1275
+ rawName: table2,
1276
+ definition: defRows[0]?.ddl ?? void 0,
1277
+ columns: columns.map((c) => ({
1278
+ name: c.column_name ?? "unknown",
1279
+ type: c.data_type ?? "unknown"
1280
+ }))
1281
+ };
1282
+ }
1283
+ #isViewInScope(viewName) {
1284
+ const { schema } = this.#adapter.parseTableName(viewName);
1285
+ return this.#adapter.isDatasetAllowed(schema);
1286
+ }
1287
+ };
1288
+
1289
+ // packages/text2sql/src/lib/adapters/bigquery/index.ts
1290
+ function tables(config = {}) {
1291
+ return (adapter) => new BigQueryTableGrounding(adapter, config);
1292
+ }
1293
+ function info(config = {}) {
1294
+ return (adapter) => new BigQueryInfoGrounding(adapter);
1295
+ }
1296
+ function views(config = {}) {
1297
+ return (adapter) => new BigQueryViewGrounding(adapter, config);
1298
+ }
1299
+ function indexes(config = {}) {
1300
+ return (adapter) => new BigQueryIndexesGrounding(adapter, config);
1301
+ }
1302
+ function rowCount(config = {}) {
1303
+ return (adapter) => new BigQueryRowCountGrounding(adapter, config);
1304
+ }
1305
+ function constraints(config = {}) {
1306
+ return (adapter) => new BigQueryConstraintGrounding(adapter, config);
1307
+ }
1308
+ function report(config = {}) {
1309
+ return (adapter) => new ReportGrounding(adapter, config);
1310
+ }
1311
+ var bigquery_default = {
1312
+ tables,
1313
+ info,
1314
+ views,
1315
+ indexes,
1316
+ rowCount,
1317
+ constraints,
1318
+ report,
1319
+ BigQuery
1320
+ };
1321
+ export {
1322
+ BigQuery,
1323
+ constraints,
1324
+ bigquery_default as default,
1325
+ indexes,
1326
+ info,
1327
+ report,
1328
+ rowCount,
1329
+ tables,
1330
+ views
1331
+ };
1332
+ //# sourceMappingURL=index.js.map