@deepagents/text2sql 0.2.2 → 0.3.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.
- package/dist/index.d.ts +7 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +929 -2746
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/adapter.d.ts +67 -10
- package/dist/lib/adapters/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/grounding.ticket.d.ts +21 -0
- package/dist/lib/adapters/grounding.ticket.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +32 -0
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/constraint.grounding.d.ts +31 -0
- package/dist/lib/adapters/groundings/constraint.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/context.d.ts +41 -0
- package/dist/lib/adapters/groundings/context.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/grounding.d.ts +8 -0
- package/dist/lib/adapters/groundings/grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/grounding.js +507 -0
- package/dist/lib/adapters/groundings/grounding.js.map +7 -0
- package/dist/lib/adapters/groundings/indexes.grounding.d.ts +30 -0
- package/dist/lib/adapters/groundings/indexes.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/info.grounding.d.ts +29 -0
- package/dist/lib/adapters/groundings/info.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts +35 -0
- package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/report.grounding.d.ts +38 -0
- package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/row-count.grounding.d.ts +30 -0
- package/dist/lib/adapters/groundings/row-count.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/table.grounding.d.ts +61 -0
- package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/view.grounding.d.ts +57 -0
- package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts +12 -0
- package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/constraint.postgres.grounding.d.ts +11 -0
- package/dist/lib/adapters/postgres/constraint.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/index.d.ts +43 -0
- package/dist/lib/adapters/postgres/index.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/index.js +1640 -0
- package/dist/lib/adapters/postgres/index.js.map +7 -0
- package/dist/lib/adapters/postgres/indexes.postgres.grounding.d.ts +15 -0
- package/dist/lib/adapters/postgres/indexes.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/info.postgres.grounding.d.ts +11 -0
- package/dist/lib/adapters/postgres/info.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts +14 -0
- package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/postgres.d.ts +26 -0
- package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/row-count.postgres.grounding.d.ts +11 -0
- package/dist/lib/adapters/postgres/row-count.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/table.postgres.grounding.d.ts +21 -0
- package/dist/lib/adapters/postgres/table.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/view.postgres.grounding.d.ts +16 -0
- package/dist/lib/adapters/postgres/view.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/column-stats.sqlite.grounding.d.ts +12 -0
- package/dist/lib/adapters/sqlite/column-stats.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/constraint.sqlite.grounding.d.ts +15 -0
- package/dist/lib/adapters/sqlite/constraint.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/index.d.ts +43 -0
- package/dist/lib/adapters/sqlite/index.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/index.js +1215 -0
- package/dist/lib/adapters/sqlite/index.js.map +7 -0
- package/dist/lib/adapters/sqlite/indexes.sqlite.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlite/indexes.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/info.sqlite.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlite/info.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts +14 -0
- package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/row-count.sqlite.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlite/row-count.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/sqlite.d.ts +25 -0
- package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/table.sqlite.grounding.d.ts +17 -0
- package/dist/lib/adapters/sqlite/table.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/view.sqlite.grounding.d.ts +12 -0
- package/dist/lib/adapters/sqlite/view.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/column-stats.sqlserver.grounding.d.ts +12 -0
- package/dist/lib/adapters/sqlserver/column-stats.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/constraint.sqlserver.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlserver/constraint.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/index.d.ts +43 -0
- package/dist/lib/adapters/sqlserver/index.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/index.js +1693 -0
- package/dist/lib/adapters/sqlserver/index.js.map +7 -0
- package/dist/lib/adapters/sqlserver/indexes.sqlserver.grounding.d.ts +15 -0
- package/dist/lib/adapters/sqlserver/indexes.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/info.sqlserver.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlserver/info.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.d.ts +14 -0
- package/dist/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/row-count.sqlserver.grounding.d.ts +11 -0
- package/dist/lib/adapters/sqlserver/row-count.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts +26 -0
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts +21 -0
- package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/view.sqlserver.grounding.d.ts +16 -0
- package/dist/lib/adapters/sqlserver/view.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/agents/suggestions.agents.d.ts +0 -2
- package/dist/lib/agents/suggestions.agents.d.ts.map +1 -1
- package/dist/lib/agents/teachables.agent.d.ts +0 -3
- package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
- package/dist/lib/agents/text2sql.agent.d.ts +69 -29
- package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
- package/dist/lib/file-cache.d.ts +12 -0
- package/dist/lib/file-cache.d.ts.map +1 -0
- package/dist/lib/instructions.d.ts +3 -0
- package/dist/lib/instructions.d.ts.map +1 -0
- package/dist/lib/instructions.js +386 -0
- package/dist/lib/instructions.js.map +7 -0
- package/dist/lib/memory/memory.prompt.d.ts +3 -0
- package/dist/lib/memory/memory.prompt.d.ts.map +1 -0
- package/dist/lib/memory/memory.store.d.ts +5 -0
- package/dist/lib/memory/memory.store.d.ts.map +1 -0
- package/dist/lib/memory/sqlite.store.d.ts +14 -0
- package/dist/lib/memory/sqlite.store.d.ts.map +1 -0
- package/dist/lib/memory/store.d.ts +40 -0
- package/dist/lib/memory/store.d.ts.map +1 -0
- package/dist/lib/prompt.d.ts +1 -6
- package/dist/lib/prompt.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +27 -34
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/teach/teachables.d.ts +184 -13
- package/dist/lib/teach/teachables.d.ts.map +1 -1
- package/dist/lib/teach/teachings.d.ts.map +1 -1
- package/dist/lib/teach/xml.d.ts.map +1 -1
- package/package.json +38 -4
- package/dist/lib/adapters/postgres.d.ts +0 -31
- package/dist/lib/adapters/postgres.d.ts.map +0 -1
- package/dist/lib/adapters/resolveTables.spec.d.ts +0 -2
- package/dist/lib/adapters/resolveTables.spec.d.ts.map +0 -1
- package/dist/lib/adapters/sqlite.d.ts +0 -30
- package/dist/lib/adapters/sqlite.d.ts.map +0 -1
- package/dist/lib/adapters/sqlserver.d.ts +0 -31
- package/dist/lib/adapters/sqlserver.d.ts.map +0 -1
- package/dist/lib/agents/brief.agent.d.ts +0 -16
- package/dist/lib/agents/brief.agent.d.ts.map +0 -1
- package/dist/lib/memory/user-profile.d.ts +0 -39
- package/dist/lib/memory/user-profile.d.ts.map +0 -1
|
@@ -0,0 +1,1693 @@
|
|
|
1
|
+
// packages/text2sql/src/lib/adapters/groundings/context.ts
|
|
2
|
+
function createGroundingContext() {
|
|
3
|
+
return {
|
|
4
|
+
tables: [],
|
|
5
|
+
views: [],
|
|
6
|
+
relationships: [],
|
|
7
|
+
info: void 0
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
12
|
+
var Adapter = class {
|
|
13
|
+
async introspect() {
|
|
14
|
+
const lines = [];
|
|
15
|
+
const ctx = createGroundingContext();
|
|
16
|
+
for (const fn of this.grounding) {
|
|
17
|
+
const grounding = fn(this);
|
|
18
|
+
lines.push({
|
|
19
|
+
tag: grounding.tag,
|
|
20
|
+
fn: await grounding.execute(ctx)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return lines.map(({ fn, tag }) => {
|
|
24
|
+
const description = fn();
|
|
25
|
+
if (description === null) {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
return `<${tag}>
|
|
29
|
+
${description}
|
|
30
|
+
</${tag}>`;
|
|
31
|
+
}).join("\n");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convert unknown database value to number.
|
|
35
|
+
* Handles number, bigint, and string types.
|
|
36
|
+
*/
|
|
37
|
+
toNumber(value) {
|
|
38
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "bigint") {
|
|
42
|
+
return Number(value);
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse a potentially qualified table name into schema and table parts.
|
|
52
|
+
*/
|
|
53
|
+
parseTableName(name) {
|
|
54
|
+
if (name.includes(".")) {
|
|
55
|
+
const [schema, ...rest] = name.split(".");
|
|
56
|
+
return { schema, table: rest.join(".") };
|
|
57
|
+
}
|
|
58
|
+
return { schema: this.defaultSchema ?? "", table: name };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Escape a string value for use in SQL string literals (single quotes).
|
|
62
|
+
* Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
|
|
63
|
+
*/
|
|
64
|
+
escapeString(value) {
|
|
65
|
+
return value.replace(/'/g, "''");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a SQL filter clause to include/exclude schemas.
|
|
69
|
+
* @param columnName - The schema column name (e.g., 'TABLE_SCHEMA')
|
|
70
|
+
* @param allowedSchemas - If provided, filter to these schemas only
|
|
71
|
+
*/
|
|
72
|
+
buildSchemaFilter(columnName, allowedSchemas) {
|
|
73
|
+
if (allowedSchemas && allowedSchemas.length > 0) {
|
|
74
|
+
const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
|
|
75
|
+
return `AND ${columnName} IN (${values})`;
|
|
76
|
+
}
|
|
77
|
+
if (this.systemSchemas.length > 0) {
|
|
78
|
+
const values = this.systemSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
|
|
79
|
+
return `AND ${columnName} NOT IN (${values})`;
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// packages/text2sql/src/lib/adapters/grounding.ticket.ts
|
|
86
|
+
var AbstractGrounding = class {
|
|
87
|
+
tag;
|
|
88
|
+
constructor(tag) {
|
|
89
|
+
this.tag = tag;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// packages/text2sql/src/lib/adapters/groundings/column-stats.grounding.ts
|
|
94
|
+
var ColumnStatsGrounding = class extends AbstractGrounding {
|
|
95
|
+
constructor(config = {}) {
|
|
96
|
+
super("column_stats");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Execute the grounding process.
|
|
100
|
+
* Annotates columns in ctx.tables and ctx.views with statistics.
|
|
101
|
+
*/
|
|
102
|
+
async execute(ctx) {
|
|
103
|
+
const allContainers = [...ctx.tables, ...ctx.views];
|
|
104
|
+
for (const container of allContainers) {
|
|
105
|
+
for (const column of container.columns) {
|
|
106
|
+
try {
|
|
107
|
+
const stats = await this.collectStats(container.name, column);
|
|
108
|
+
if (stats) {
|
|
109
|
+
column.stats = stats;
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.warn(
|
|
113
|
+
"Error collecting stats for",
|
|
114
|
+
container.name,
|
|
115
|
+
column.name,
|
|
116
|
+
error
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return () => this.#describe();
|
|
122
|
+
}
|
|
123
|
+
#describe() {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// packages/text2sql/src/lib/adapters/groundings/constraint.grounding.ts
|
|
129
|
+
var ConstraintGrounding = class extends AbstractGrounding {
|
|
130
|
+
constructor(config = {}) {
|
|
131
|
+
super("constraints");
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Execute the grounding process.
|
|
135
|
+
* Annotates tables in ctx.tables with their constraints.
|
|
136
|
+
*/
|
|
137
|
+
async execute(ctx) {
|
|
138
|
+
for (const table of ctx.tables) {
|
|
139
|
+
try {
|
|
140
|
+
table.constraints = await this.getConstraints(table.name);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn("Error collecting constraints for", table.name, error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return () => null;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// packages/text2sql/src/lib/adapters/groundings/indexes.grounding.ts
|
|
150
|
+
var IndexesGrounding = class extends AbstractGrounding {
|
|
151
|
+
constructor(config = {}) {
|
|
152
|
+
super("indexes");
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Execute the grounding process.
|
|
156
|
+
* Annotates tables in ctx.tables with their indexes and marks indexed columns.
|
|
157
|
+
*/
|
|
158
|
+
async execute(ctx) {
|
|
159
|
+
for (const table of ctx.tables) {
|
|
160
|
+
table.indexes = await this.getIndexes(table.name);
|
|
161
|
+
for (const index of table.indexes ?? []) {
|
|
162
|
+
for (const colName of index.columns) {
|
|
163
|
+
const column = table.columns.find((c) => c.name === colName);
|
|
164
|
+
if (column) {
|
|
165
|
+
column.isIndexed = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return () => null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// packages/text2sql/src/lib/adapters/groundings/info.grounding.ts
|
|
175
|
+
var InfoGrounding = class extends AbstractGrounding {
|
|
176
|
+
constructor(config = {}) {
|
|
177
|
+
super("dialect_info");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Execute the grounding process.
|
|
181
|
+
* Writes database info to ctx.info.
|
|
182
|
+
*/
|
|
183
|
+
async execute(ctx) {
|
|
184
|
+
ctx.info = await this.collectInfo();
|
|
185
|
+
const lines = [`Dialect: ${ctx.info.dialect ?? "unknown"}`];
|
|
186
|
+
if (ctx.info.version) {
|
|
187
|
+
lines.push(`Version: ${ctx.info.version}`);
|
|
188
|
+
}
|
|
189
|
+
if (ctx.info.database) {
|
|
190
|
+
lines.push(`Database: ${ctx.info.database}`);
|
|
191
|
+
}
|
|
192
|
+
if (ctx.info.details && Object.keys(ctx.info.details).length) {
|
|
193
|
+
lines.push(`Details: ${JSON.stringify(ctx.info.details)}`);
|
|
194
|
+
}
|
|
195
|
+
return () => lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// packages/text2sql/src/lib/adapters/groundings/low-cardinality.grounding.ts
|
|
200
|
+
var LowCardinalityGrounding = class extends AbstractGrounding {
|
|
201
|
+
constructor(config = {}) {
|
|
202
|
+
super("low_cardinality");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Execute the grounding process.
|
|
206
|
+
* Annotates columns in ctx.tables and ctx.views with low cardinality values.
|
|
207
|
+
*/
|
|
208
|
+
async execute(ctx) {
|
|
209
|
+
const allContainers = [...ctx.tables, ...ctx.views];
|
|
210
|
+
for (const container of allContainers) {
|
|
211
|
+
for (const column of container.columns) {
|
|
212
|
+
try {
|
|
213
|
+
const lowCard = await this.collectLowCardinality(
|
|
214
|
+
container.name,
|
|
215
|
+
column
|
|
216
|
+
);
|
|
217
|
+
if (lowCard) {
|
|
218
|
+
column.kind = lowCard.kind;
|
|
219
|
+
column.values = lowCard.values;
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.warn(
|
|
223
|
+
"Error collecting low cardinality values for",
|
|
224
|
+
container.name,
|
|
225
|
+
column.name,
|
|
226
|
+
error
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return () => this.#describe();
|
|
232
|
+
}
|
|
233
|
+
#describe() {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// packages/text2sql/src/lib/adapters/groundings/report.grounding.ts
|
|
239
|
+
import { groq } from "@ai-sdk/groq";
|
|
240
|
+
import { tool } from "ai";
|
|
241
|
+
import dedent from "dedent";
|
|
242
|
+
import z from "zod";
|
|
243
|
+
import {
|
|
244
|
+
agent,
|
|
245
|
+
generate,
|
|
246
|
+
toState,
|
|
247
|
+
user
|
|
248
|
+
} from "@deepagents/agent";
|
|
249
|
+
var ReportGrounding = class extends AbstractGrounding {
|
|
250
|
+
#adapter;
|
|
251
|
+
#model;
|
|
252
|
+
#cache;
|
|
253
|
+
#forceRefresh;
|
|
254
|
+
constructor(adapter, config = {}) {
|
|
255
|
+
super("business_context");
|
|
256
|
+
this.#adapter = adapter;
|
|
257
|
+
this.#model = config.model ?? groq("openai/gpt-oss-20b");
|
|
258
|
+
this.#cache = config.cache;
|
|
259
|
+
this.#forceRefresh = config.forceRefresh ?? false;
|
|
260
|
+
}
|
|
261
|
+
async execute(ctx) {
|
|
262
|
+
if (!this.#forceRefresh && this.#cache) {
|
|
263
|
+
const cached = await this.#cache.get();
|
|
264
|
+
if (cached) {
|
|
265
|
+
ctx.report = cached;
|
|
266
|
+
return () => cached;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const report2 = await this.#generateReport();
|
|
270
|
+
ctx.report = report2;
|
|
271
|
+
if (this.#cache) {
|
|
272
|
+
await this.#cache.set(report2);
|
|
273
|
+
}
|
|
274
|
+
return () => report2;
|
|
275
|
+
}
|
|
276
|
+
async #generateReport() {
|
|
277
|
+
const reportAgent = agent({
|
|
278
|
+
name: "db-report-agent",
|
|
279
|
+
model: this.#model,
|
|
280
|
+
prompt: () => dedent`
|
|
281
|
+
<identity>
|
|
282
|
+
You are a database analyst expert. Your job is to understand what
|
|
283
|
+
a database represents and provide business context about it.
|
|
284
|
+
You have READ-ONLY access to the database.
|
|
285
|
+
</identity>
|
|
286
|
+
|
|
287
|
+
<instructions>
|
|
288
|
+
Write a business context that helps another agent answer questions accurately.
|
|
289
|
+
|
|
290
|
+
For EACH table, do queries ONE AT A TIME:
|
|
291
|
+
1. SELECT COUNT(*) to get row count
|
|
292
|
+
2. SELECT * LIMIT 3 to see sample data
|
|
293
|
+
|
|
294
|
+
Then write a report with:
|
|
295
|
+
- What business this database is for
|
|
296
|
+
- For each table: purpose, row count, and example of what the data looks like
|
|
297
|
+
|
|
298
|
+
Include concrete examples like "Track prices are $0.99",
|
|
299
|
+
"Customer names like 'Luís Gonçalves'", etc.
|
|
300
|
+
|
|
301
|
+
Keep it 400-600 words, conversational style.
|
|
302
|
+
</instructions>
|
|
303
|
+
`,
|
|
304
|
+
tools: {
|
|
305
|
+
query_database: tool({
|
|
306
|
+
description: "Execute a SELECT query to explore the database and gather insights.",
|
|
307
|
+
inputSchema: z.object({
|
|
308
|
+
sql: z.string().describe("The SELECT query to execute"),
|
|
309
|
+
purpose: z.string().describe(
|
|
310
|
+
"What insight you are trying to gather with this query"
|
|
311
|
+
)
|
|
312
|
+
}),
|
|
313
|
+
execute: ({ sql }, options) => {
|
|
314
|
+
const state = toState(options);
|
|
315
|
+
return state.adapter.execute(sql);
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
const { text } = await generate(
|
|
321
|
+
reportAgent,
|
|
322
|
+
[
|
|
323
|
+
user(
|
|
324
|
+
"Please analyze the database and write a contextual report about what this database represents."
|
|
325
|
+
)
|
|
326
|
+
],
|
|
327
|
+
{ adapter: this.#adapter }
|
|
328
|
+
);
|
|
329
|
+
return text;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// packages/text2sql/src/lib/adapters/groundings/row-count.grounding.ts
|
|
334
|
+
var RowCountGrounding = class extends AbstractGrounding {
|
|
335
|
+
constructor(config = {}) {
|
|
336
|
+
super("row_counts");
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Execute the grounding process.
|
|
340
|
+
* Annotates tables in ctx.tables with row counts and size hints.
|
|
341
|
+
*/
|
|
342
|
+
async execute(ctx) {
|
|
343
|
+
for (const table of ctx.tables) {
|
|
344
|
+
const count = await this.getRowCount(table.name);
|
|
345
|
+
if (count != null) {
|
|
346
|
+
table.rowCount = count;
|
|
347
|
+
table.sizeHint = this.#classifyRowCount(count);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return () => null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Classify row count into a size hint category.
|
|
354
|
+
*/
|
|
355
|
+
#classifyRowCount(count) {
|
|
356
|
+
if (count < 100) return "tiny";
|
|
357
|
+
if (count < 1e3) return "small";
|
|
358
|
+
if (count < 1e4) return "medium";
|
|
359
|
+
if (count < 1e5) return "large";
|
|
360
|
+
return "huge";
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// packages/text2sql/src/lib/adapters/groundings/table.grounding.ts
|
|
365
|
+
import pluralize from "pluralize";
|
|
366
|
+
var TableGrounding = class extends AbstractGrounding {
|
|
367
|
+
#filter;
|
|
368
|
+
#forward;
|
|
369
|
+
#backward;
|
|
370
|
+
constructor(config = {}) {
|
|
371
|
+
super("tables");
|
|
372
|
+
this.#filter = config.filter;
|
|
373
|
+
this.#forward = config.forward;
|
|
374
|
+
this.#backward = config.backward;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Execute the grounding process.
|
|
378
|
+
* Writes discovered tables and relationships to the context.
|
|
379
|
+
*/
|
|
380
|
+
async execute(ctx) {
|
|
381
|
+
const seedTables = await this.applyFilter();
|
|
382
|
+
const forward = this.#forward;
|
|
383
|
+
const backward = this.#backward;
|
|
384
|
+
if (!forward && !backward) {
|
|
385
|
+
const tables3 = await Promise.all(
|
|
386
|
+
seedTables.map((name) => this.getTable(name))
|
|
387
|
+
);
|
|
388
|
+
ctx.tables.push(...tables3);
|
|
389
|
+
return () => this.#describeTables(tables3);
|
|
390
|
+
}
|
|
391
|
+
const tables2 = {};
|
|
392
|
+
const allRelationships = [];
|
|
393
|
+
const seenRelationships = /* @__PURE__ */ new Set();
|
|
394
|
+
const forwardQueue = [];
|
|
395
|
+
const backwardQueue = [];
|
|
396
|
+
const forwardVisited = /* @__PURE__ */ new Set();
|
|
397
|
+
const backwardVisited = /* @__PURE__ */ new Set();
|
|
398
|
+
for (const name of seedTables) {
|
|
399
|
+
if (forward) forwardQueue.push({ name, depth: 0 });
|
|
400
|
+
if (backward) backwardQueue.push({ name, depth: 0 });
|
|
401
|
+
}
|
|
402
|
+
const forwardLimit = forward === true ? Infinity : forward || 0;
|
|
403
|
+
while (forwardQueue.length > 0) {
|
|
404
|
+
const item = forwardQueue.shift();
|
|
405
|
+
if (!item) break;
|
|
406
|
+
const { name, depth } = item;
|
|
407
|
+
if (forwardVisited.has(name)) continue;
|
|
408
|
+
forwardVisited.add(name);
|
|
409
|
+
if (!tables2[name]) {
|
|
410
|
+
tables2[name] = await this.getTable(name);
|
|
411
|
+
}
|
|
412
|
+
if (depth < forwardLimit) {
|
|
413
|
+
const rels = await this.findOutgoingRelations(name);
|
|
414
|
+
for (const rel of rels) {
|
|
415
|
+
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
416
|
+
if (!forwardVisited.has(rel.referenced_table)) {
|
|
417
|
+
forwardQueue.push({ name: rel.referenced_table, depth: depth + 1 });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const backwardLimit = backward === true ? Infinity : backward || 0;
|
|
423
|
+
while (backwardQueue.length > 0) {
|
|
424
|
+
const item = backwardQueue.shift();
|
|
425
|
+
if (!item) break;
|
|
426
|
+
const { name, depth } = item;
|
|
427
|
+
if (backwardVisited.has(name)) continue;
|
|
428
|
+
backwardVisited.add(name);
|
|
429
|
+
if (!tables2[name]) {
|
|
430
|
+
tables2[name] = await this.getTable(name);
|
|
431
|
+
}
|
|
432
|
+
if (depth < backwardLimit) {
|
|
433
|
+
const rels = await this.findIncomingRelations(name);
|
|
434
|
+
for (const rel of rels) {
|
|
435
|
+
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
436
|
+
if (!backwardVisited.has(rel.table)) {
|
|
437
|
+
backwardQueue.push({ name: rel.table, depth: depth + 1 });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const tablesList = Object.values(tables2);
|
|
443
|
+
ctx.tables.push(...tablesList);
|
|
444
|
+
ctx.relationships.push(...allRelationships);
|
|
445
|
+
return () => this.#describeTables(tablesList);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Apply the filter to get seed table names.
|
|
449
|
+
* If filter is an explicit array, skip querying all table names.
|
|
450
|
+
*/
|
|
451
|
+
async applyFilter() {
|
|
452
|
+
const filter = this.#filter;
|
|
453
|
+
if (Array.isArray(filter)) {
|
|
454
|
+
return filter;
|
|
455
|
+
}
|
|
456
|
+
const names = await this.getAllTableNames();
|
|
457
|
+
if (!filter) {
|
|
458
|
+
return names;
|
|
459
|
+
}
|
|
460
|
+
if (filter instanceof RegExp) {
|
|
461
|
+
return names.filter((name) => filter.test(name));
|
|
462
|
+
}
|
|
463
|
+
return names.filter(filter);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Add a relationship to the collection, deduplicating by key.
|
|
467
|
+
*/
|
|
468
|
+
addRelationship(rel, all, seen) {
|
|
469
|
+
const key = `${rel.table}:${rel.from.join(",")}:${rel.referenced_table}:${rel.to.join(",")}`;
|
|
470
|
+
if (!seen.has(key)) {
|
|
471
|
+
seen.add(key);
|
|
472
|
+
all.push(rel);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
#describeTables(tables2) {
|
|
476
|
+
if (!tables2.length) {
|
|
477
|
+
return "Schema unavailable.";
|
|
478
|
+
}
|
|
479
|
+
return tables2.map((table) => {
|
|
480
|
+
const rowCountInfo = table.rowCount != null ? ` [rows: ${table.rowCount}${table.sizeHint ? `, size: ${table.sizeHint}` : ""}]` : "";
|
|
481
|
+
const pkConstraint = table.constraints?.find((c) => c.type === "PRIMARY_KEY");
|
|
482
|
+
const pkColumns = new Set(pkConstraint?.columns ?? []);
|
|
483
|
+
const columns = table.columns.map((column) => {
|
|
484
|
+
const annotations = [];
|
|
485
|
+
const isPrimaryKey = pkColumns.has(column.name);
|
|
486
|
+
if (isPrimaryKey) {
|
|
487
|
+
annotations.push("PK");
|
|
488
|
+
}
|
|
489
|
+
if (column.isIndexed && !isPrimaryKey) {
|
|
490
|
+
annotations.push("Indexed");
|
|
491
|
+
}
|
|
492
|
+
if (column.kind === "LowCardinality" && column.values?.length) {
|
|
493
|
+
annotations.push(`LowCardinality: ${column.values.join(", ")}`);
|
|
494
|
+
}
|
|
495
|
+
if (column.stats) {
|
|
496
|
+
const statParts = [];
|
|
497
|
+
if (column.stats.min != null || column.stats.max != null) {
|
|
498
|
+
const minText = column.stats.min ?? "n/a";
|
|
499
|
+
const maxText = column.stats.max ?? "n/a";
|
|
500
|
+
statParts.push(`range ${minText} \u2192 ${maxText}`);
|
|
501
|
+
}
|
|
502
|
+
if (column.stats.nullFraction != null && Number.isFinite(column.stats.nullFraction)) {
|
|
503
|
+
const percent = Math.round(column.stats.nullFraction * 1e3) / 10;
|
|
504
|
+
statParts.push(`null\u2248${percent}%`);
|
|
505
|
+
}
|
|
506
|
+
if (statParts.length) {
|
|
507
|
+
annotations.push(statParts.join(", "));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const annotationText = annotations.length ? ` [${annotations.join(", ")}]` : "";
|
|
511
|
+
return ` - ${column.name} (${column.type})${annotationText}`;
|
|
512
|
+
}).join("\n");
|
|
513
|
+
const indexes2 = table.indexes?.length ? `
|
|
514
|
+
Indexes:
|
|
515
|
+
${table.indexes.map((index) => {
|
|
516
|
+
const props = [];
|
|
517
|
+
if (index.unique) {
|
|
518
|
+
props.push("UNIQUE");
|
|
519
|
+
}
|
|
520
|
+
if (index.type) {
|
|
521
|
+
props.push(index.type);
|
|
522
|
+
}
|
|
523
|
+
const propsText = props.length ? ` (${props.join(", ")})` : "";
|
|
524
|
+
const columnsText = index.columns?.length ? index.columns.join(", ") : "expression";
|
|
525
|
+
return ` - ${index.name}${propsText}: ${columnsText}`;
|
|
526
|
+
}).join("\n")}` : "";
|
|
527
|
+
return `- Table: ${table.name}${rowCountInfo}
|
|
528
|
+
Columns:
|
|
529
|
+
${columns}${indexes2}`;
|
|
530
|
+
}).join("\n\n");
|
|
531
|
+
}
|
|
532
|
+
#formatTableLabel = (tableName) => {
|
|
533
|
+
const base = tableName.split(".").pop() ?? tableName;
|
|
534
|
+
return base.replace(/_/g, " ");
|
|
535
|
+
};
|
|
536
|
+
#describeRelationships = (tables2, relationships) => {
|
|
537
|
+
if (!relationships.length) {
|
|
538
|
+
return "None detected";
|
|
539
|
+
}
|
|
540
|
+
const tableMap = new Map(tables2.map((table) => [table.name, table]));
|
|
541
|
+
return relationships.map((relationship) => {
|
|
542
|
+
const sourceLabel = this.#formatTableLabel(relationship.table);
|
|
543
|
+
const targetLabel = this.#formatTableLabel(
|
|
544
|
+
relationship.referenced_table
|
|
545
|
+
);
|
|
546
|
+
const singularSource = pluralize.singular(sourceLabel);
|
|
547
|
+
const pluralSource = pluralize.plural(sourceLabel);
|
|
548
|
+
const singularTarget = pluralize.singular(targetLabel);
|
|
549
|
+
const pluralTarget = pluralize.plural(targetLabel);
|
|
550
|
+
const sourceTable = tableMap.get(relationship.table);
|
|
551
|
+
const targetTable = tableMap.get(relationship.referenced_table);
|
|
552
|
+
const sourceCount = sourceTable?.rowCount;
|
|
553
|
+
const targetCount = targetTable?.rowCount;
|
|
554
|
+
const ratio = sourceCount != null && targetCount != null && targetCount > 0 ? sourceCount / targetCount : null;
|
|
555
|
+
let cardinality = "each";
|
|
556
|
+
if (ratio != null) {
|
|
557
|
+
if (ratio > 5) {
|
|
558
|
+
cardinality = `many-to-one (\u2248${sourceCount} vs ${targetCount})`;
|
|
559
|
+
} else if (ratio < 1.2 && ratio > 0.8) {
|
|
560
|
+
cardinality = `roughly 1:1 (${sourceCount} vs ${targetCount})`;
|
|
561
|
+
} else if (ratio < 0.2) {
|
|
562
|
+
cardinality = `one-to-many (${sourceCount} vs ${targetCount})`;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const mappings = relationship.from.map((fromCol, idx) => {
|
|
566
|
+
const targetCol = relationship.to[idx] ?? relationship.to[0] ?? fromCol;
|
|
567
|
+
return `${relationship.table}.${fromCol} -> ${relationship.referenced_table}.${targetCol}`;
|
|
568
|
+
}).join(", ");
|
|
569
|
+
return `- ${relationship.table} (${relationship.from.join(", ")}) -> ${relationship.referenced_table} (${relationship.to.join(", ")}) [${cardinality}]`;
|
|
570
|
+
}).join("\n");
|
|
571
|
+
};
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// packages/text2sql/src/lib/adapters/sqlserver/column-stats.sqlserver.grounding.ts
|
|
575
|
+
var SqlServerColumnStatsGrounding = class extends ColumnStatsGrounding {
|
|
576
|
+
#adapter;
|
|
577
|
+
constructor(adapter, config = {}) {
|
|
578
|
+
super(config);
|
|
579
|
+
this.#adapter = adapter;
|
|
580
|
+
}
|
|
581
|
+
async collectStats(tableName, column) {
|
|
582
|
+
if (!this.#shouldCollectStats(column.type)) {
|
|
583
|
+
return void 0;
|
|
584
|
+
}
|
|
585
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
586
|
+
const tableIdentifier = `[${this.#adapter.escape(schema)}].[${this.#adapter.escape(table)}]`;
|
|
587
|
+
const columnIdentifier = `[${this.#adapter.escape(column.name)}]`;
|
|
588
|
+
const sql = `
|
|
589
|
+
SELECT
|
|
590
|
+
CAST(MIN(${columnIdentifier}) AS NVARCHAR(MAX)) AS min_value,
|
|
591
|
+
CAST(MAX(${columnIdentifier}) AS NVARCHAR(MAX)) AS max_value,
|
|
592
|
+
AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
|
|
593
|
+
FROM ${tableIdentifier}
|
|
594
|
+
`;
|
|
595
|
+
const rows = await this.#adapter.runQuery(sql);
|
|
596
|
+
if (!rows.length) {
|
|
597
|
+
return void 0;
|
|
598
|
+
}
|
|
599
|
+
const min = rows[0]?.min_value;
|
|
600
|
+
const max = rows[0]?.max_value;
|
|
601
|
+
const nullFraction = this.#adapter.toNumber(rows[0]?.null_fraction);
|
|
602
|
+
if (min == null && max == null && nullFraction == null) {
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
min: min ?? void 0,
|
|
607
|
+
max: max ?? void 0,
|
|
608
|
+
nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
#shouldCollectStats(type) {
|
|
612
|
+
if (!type) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
const normalized = type.toLowerCase();
|
|
616
|
+
return /int|real|numeric|float|decimal|date|time|bit|money/.test(
|
|
617
|
+
normalized
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// packages/text2sql/src/lib/adapters/sqlserver/constraint.sqlserver.grounding.ts
|
|
623
|
+
var SqlServerConstraintGrounding = class extends ConstraintGrounding {
|
|
624
|
+
#adapter;
|
|
625
|
+
constructor(adapter, config = {}) {
|
|
626
|
+
super(config);
|
|
627
|
+
this.#adapter = adapter;
|
|
628
|
+
}
|
|
629
|
+
async getConstraints(tableName) {
|
|
630
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
631
|
+
const constraints2 = [];
|
|
632
|
+
const pkRows = await this.#adapter.runQuery(`
|
|
633
|
+
SELECT
|
|
634
|
+
kc.name AS constraint_name,
|
|
635
|
+
COL_NAME(ic.object_id, ic.column_id) AS column_name
|
|
636
|
+
FROM sys.key_constraints kc
|
|
637
|
+
JOIN sys.index_columns ic ON kc.parent_object_id = ic.object_id AND kc.unique_index_id = ic.index_id
|
|
638
|
+
JOIN sys.tables t ON kc.parent_object_id = t.object_id
|
|
639
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
640
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
641
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
642
|
+
AND kc.type = 'PK'
|
|
643
|
+
ORDER BY ic.key_ordinal
|
|
644
|
+
`);
|
|
645
|
+
if (pkRows.length > 0) {
|
|
646
|
+
constraints2.push({
|
|
647
|
+
name: pkRows[0].constraint_name,
|
|
648
|
+
type: "PRIMARY_KEY",
|
|
649
|
+
columns: pkRows.map((r) => r.column_name)
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
const fkRows = await this.#adapter.runQuery(`
|
|
653
|
+
SELECT
|
|
654
|
+
fk.name AS constraint_name,
|
|
655
|
+
COL_NAME(fkc.parent_object_id, fkc.parent_column_id) AS column_name,
|
|
656
|
+
ref_s.name AS ref_schema,
|
|
657
|
+
ref_t.name AS ref_table,
|
|
658
|
+
COL_NAME(fkc.referenced_object_id, fkc.referenced_column_id) AS ref_column
|
|
659
|
+
FROM sys.foreign_keys fk
|
|
660
|
+
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
|
|
661
|
+
JOIN sys.tables t ON fk.parent_object_id = t.object_id
|
|
662
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
663
|
+
JOIN sys.tables ref_t ON fk.referenced_object_id = ref_t.object_id
|
|
664
|
+
JOIN sys.schemas ref_s ON ref_t.schema_id = ref_s.schema_id
|
|
665
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
666
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
667
|
+
ORDER BY fk.name, fkc.constraint_column_id
|
|
668
|
+
`);
|
|
669
|
+
const fkMap = /* @__PURE__ */ new Map();
|
|
670
|
+
for (const row of fkRows) {
|
|
671
|
+
const existing = fkMap.get(row.constraint_name);
|
|
672
|
+
if (existing) {
|
|
673
|
+
existing.columns.push(row.column_name);
|
|
674
|
+
existing.refColumns.push(row.ref_column);
|
|
675
|
+
} else {
|
|
676
|
+
fkMap.set(row.constraint_name, {
|
|
677
|
+
columns: [row.column_name],
|
|
678
|
+
refSchema: row.ref_schema,
|
|
679
|
+
refTable: row.ref_table,
|
|
680
|
+
refColumns: [row.ref_column]
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const [name, data] of fkMap) {
|
|
685
|
+
constraints2.push({
|
|
686
|
+
name,
|
|
687
|
+
type: "FOREIGN_KEY",
|
|
688
|
+
columns: data.columns,
|
|
689
|
+
referencedTable: `${data.refSchema}.${data.refTable}`,
|
|
690
|
+
referencedColumns: data.refColumns
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
const checkRows = await this.#adapter.runQuery(`
|
|
694
|
+
SELECT
|
|
695
|
+
cc.name AS constraint_name,
|
|
696
|
+
cc.definition
|
|
697
|
+
FROM sys.check_constraints cc
|
|
698
|
+
JOIN sys.tables t ON cc.parent_object_id = t.object_id
|
|
699
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
700
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
701
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
702
|
+
`);
|
|
703
|
+
for (const row of checkRows) {
|
|
704
|
+
constraints2.push({
|
|
705
|
+
name: row.constraint_name,
|
|
706
|
+
type: "CHECK",
|
|
707
|
+
definition: row.definition?.replace(/^\(/, "").replace(/\)$/, "")
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
const uniqueRows = await this.#adapter.runQuery(`
|
|
711
|
+
SELECT
|
|
712
|
+
i.name AS constraint_name,
|
|
713
|
+
COL_NAME(ic.object_id, ic.column_id) AS column_name
|
|
714
|
+
FROM sys.indexes i
|
|
715
|
+
JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
|
716
|
+
JOIN sys.tables t ON i.object_id = t.object_id
|
|
717
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
718
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
719
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
720
|
+
AND i.is_unique_constraint = 1
|
|
721
|
+
ORDER BY i.name, ic.key_ordinal
|
|
722
|
+
`);
|
|
723
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const row of uniqueRows) {
|
|
725
|
+
const existing = uniqueMap.get(row.constraint_name);
|
|
726
|
+
if (existing) {
|
|
727
|
+
existing.push(row.column_name);
|
|
728
|
+
} else {
|
|
729
|
+
uniqueMap.set(row.constraint_name, [row.column_name]);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
for (const [name, columns] of uniqueMap) {
|
|
733
|
+
constraints2.push({
|
|
734
|
+
name,
|
|
735
|
+
type: "UNIQUE",
|
|
736
|
+
columns
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const columnRows = await this.#adapter.runQuery(`
|
|
740
|
+
SELECT
|
|
741
|
+
c.name AS column_name,
|
|
742
|
+
dc.definition AS default_definition,
|
|
743
|
+
c.is_nullable
|
|
744
|
+
FROM sys.columns c
|
|
745
|
+
JOIN sys.tables t ON c.object_id = t.object_id
|
|
746
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
747
|
+
LEFT JOIN sys.default_constraints dc ON c.default_object_id = dc.object_id
|
|
748
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
749
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
750
|
+
`);
|
|
751
|
+
const pkConstraint = constraints2.find((c) => c.type === "PRIMARY_KEY");
|
|
752
|
+
const pkColumns = new Set(pkConstraint?.columns ?? []);
|
|
753
|
+
for (const col of columnRows) {
|
|
754
|
+
if (col.is_nullable === 0 && !pkColumns.has(col.column_name)) {
|
|
755
|
+
constraints2.push({
|
|
756
|
+
name: `${table}_${col.column_name}_notnull`,
|
|
757
|
+
type: "NOT_NULL",
|
|
758
|
+
columns: [col.column_name]
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
if (col.default_definition != null) {
|
|
762
|
+
constraints2.push({
|
|
763
|
+
name: `${table}_${col.column_name}_default`,
|
|
764
|
+
type: "DEFAULT",
|
|
765
|
+
columns: [col.column_name],
|
|
766
|
+
defaultValue: col.default_definition.replace(/^\(/, "").replace(/\)$/, "")
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return constraints2;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
// packages/text2sql/src/lib/adapters/sqlserver/indexes.sqlserver.grounding.ts
|
|
775
|
+
var SqlServerIndexesGrounding = class extends IndexesGrounding {
|
|
776
|
+
#adapter;
|
|
777
|
+
#schemas;
|
|
778
|
+
constructor(adapter, config = {}) {
|
|
779
|
+
super(config);
|
|
780
|
+
this.#adapter = adapter;
|
|
781
|
+
this.#schemas = config.schemas;
|
|
782
|
+
}
|
|
783
|
+
async getIndexes(tableName) {
|
|
784
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
785
|
+
const rows = await this.#adapter.runQuery(`
|
|
786
|
+
SELECT
|
|
787
|
+
i.name AS index_name,
|
|
788
|
+
COL_NAME(ic.object_id, ic.column_id) AS column_name,
|
|
789
|
+
i.is_unique,
|
|
790
|
+
i.type_desc AS index_type,
|
|
791
|
+
ic.key_ordinal
|
|
792
|
+
FROM sys.indexes i
|
|
793
|
+
JOIN sys.index_columns ic
|
|
794
|
+
ON i.object_id = ic.object_id
|
|
795
|
+
AND i.index_id = ic.index_id
|
|
796
|
+
JOIN sys.tables t
|
|
797
|
+
ON i.object_id = t.object_id
|
|
798
|
+
JOIN sys.schemas s
|
|
799
|
+
ON t.schema_id = s.schema_id
|
|
800
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
801
|
+
AND t.name = '${this.#adapter.escapeString(table)}'
|
|
802
|
+
AND i.name IS NOT NULL
|
|
803
|
+
AND ic.is_included_column = 0
|
|
804
|
+
ORDER BY i.name, ic.key_ordinal
|
|
805
|
+
`);
|
|
806
|
+
return this.#groupIndexes(rows);
|
|
807
|
+
}
|
|
808
|
+
#groupIndexes(rows) {
|
|
809
|
+
const indexes2 = /* @__PURE__ */ new Map();
|
|
810
|
+
for (const row of rows) {
|
|
811
|
+
if (!row.index_name || !row.column_name) continue;
|
|
812
|
+
const existing = indexes2.get(row.index_name);
|
|
813
|
+
if (existing) {
|
|
814
|
+
existing.columns.push(row.column_name);
|
|
815
|
+
} else {
|
|
816
|
+
indexes2.set(row.index_name, {
|
|
817
|
+
name: row.index_name,
|
|
818
|
+
columns: [row.column_name],
|
|
819
|
+
unique: row.is_unique,
|
|
820
|
+
type: row.index_type
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return Array.from(indexes2.values());
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// packages/text2sql/src/lib/adapters/sqlserver/info.sqlserver.grounding.ts
|
|
829
|
+
var SqlServerInfoGrounding = class extends InfoGrounding {
|
|
830
|
+
#adapter;
|
|
831
|
+
constructor(adapter, config = {}) {
|
|
832
|
+
super(config);
|
|
833
|
+
this.#adapter = adapter;
|
|
834
|
+
}
|
|
835
|
+
async collectInfo() {
|
|
836
|
+
const [versionRows, dbRows] = await Promise.all([
|
|
837
|
+
this.#adapter.runQuery(
|
|
838
|
+
"SELECT @@VERSION AS version"
|
|
839
|
+
),
|
|
840
|
+
this.#adapter.runQuery("SELECT DB_NAME() AS db")
|
|
841
|
+
]);
|
|
842
|
+
return {
|
|
843
|
+
dialect: "sqlserver",
|
|
844
|
+
version: versionRows[0]?.version,
|
|
845
|
+
database: dbRows[0]?.db
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
// packages/text2sql/src/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.ts
|
|
851
|
+
var LOW_CARDINALITY_LIMIT = 20;
|
|
852
|
+
var SqlServerLowCardinalityGrounding = class extends LowCardinalityGrounding {
|
|
853
|
+
#adapter;
|
|
854
|
+
constructor(adapter, config = {}) {
|
|
855
|
+
super(config);
|
|
856
|
+
this.#adapter = adapter;
|
|
857
|
+
}
|
|
858
|
+
async collectLowCardinality(tableName, column) {
|
|
859
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
860
|
+
const tableIdentifier = `[${this.#adapter.escape(schema)}].[${this.#adapter.escape(table)}]`;
|
|
861
|
+
const columnIdentifier = `[${this.#adapter.escape(column.name)}]`;
|
|
862
|
+
const limit = LOW_CARDINALITY_LIMIT + 1;
|
|
863
|
+
const sql = `
|
|
864
|
+
SELECT DISTINCT TOP ${limit} CAST(${columnIdentifier} AS NVARCHAR(MAX)) AS value
|
|
865
|
+
FROM ${tableIdentifier}
|
|
866
|
+
WHERE ${columnIdentifier} IS NOT NULL
|
|
867
|
+
`;
|
|
868
|
+
const rows = await this.#adapter.runQuery(sql);
|
|
869
|
+
if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
|
|
870
|
+
return void 0;
|
|
871
|
+
}
|
|
872
|
+
const values = [];
|
|
873
|
+
for (const row of rows) {
|
|
874
|
+
if (row.value == null) {
|
|
875
|
+
return void 0;
|
|
876
|
+
}
|
|
877
|
+
values.push(row.value);
|
|
878
|
+
}
|
|
879
|
+
if (!values.length) {
|
|
880
|
+
return void 0;
|
|
881
|
+
}
|
|
882
|
+
return { kind: "LowCardinality", values };
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
// packages/text2sql/src/lib/adapters/sqlserver/row-count.sqlserver.grounding.ts
|
|
887
|
+
var SqlServerRowCountGrounding = class extends RowCountGrounding {
|
|
888
|
+
#adapter;
|
|
889
|
+
constructor(adapter, config = {}) {
|
|
890
|
+
super(config);
|
|
891
|
+
this.#adapter = adapter;
|
|
892
|
+
}
|
|
893
|
+
async getRowCount(tableName) {
|
|
894
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
895
|
+
const tableIdentifier = `[${this.#adapter.escape(schema)}].[${this.#adapter.escape(table)}]`;
|
|
896
|
+
const rows = await this.#adapter.runQuery(
|
|
897
|
+
`SELECT COUNT(*) as count FROM ${tableIdentifier}`
|
|
898
|
+
);
|
|
899
|
+
return this.#adapter.toNumber(rows[0]?.count);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// packages/text2sql/src/lib/adapters/sqlserver/sqlserver.ts
|
|
904
|
+
var SQL_SERVER_ERROR_MAP = {
|
|
905
|
+
"208": {
|
|
906
|
+
type: "MISSING_TABLE",
|
|
907
|
+
hint: "Check that the table exists and include the schema prefix (e.g., dbo.TableName)."
|
|
908
|
+
},
|
|
909
|
+
"207": {
|
|
910
|
+
type: "INVALID_COLUMN",
|
|
911
|
+
hint: "Verify the column exists on the table and that any aliases are referenced correctly."
|
|
912
|
+
},
|
|
913
|
+
"156": {
|
|
914
|
+
type: "SYNTAX_ERROR",
|
|
915
|
+
hint: "There is a SQL syntax error. Review keywords, punctuation, and clauses such as GROUP BY."
|
|
916
|
+
},
|
|
917
|
+
"4104": {
|
|
918
|
+
type: "INVALID_COLUMN",
|
|
919
|
+
hint: "Columns must be qualified with table aliases when ambiguous. Double-check join aliases."
|
|
920
|
+
},
|
|
921
|
+
"1934": {
|
|
922
|
+
type: "CONSTRAINT_ERROR",
|
|
923
|
+
hint: "The query violates a constraint. Re-check join logic and filtering."
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
var LOW_CARDINALITY_LIMIT2 = 20;
|
|
927
|
+
function getErrorCode(error) {
|
|
928
|
+
if (typeof error === "object" && error !== null && "number" in error && typeof error.number === "number") {
|
|
929
|
+
return String(error.number);
|
|
930
|
+
}
|
|
931
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
|
|
932
|
+
return error.code;
|
|
933
|
+
}
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
function formatSqlServerError(sql, error) {
|
|
937
|
+
const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
|
|
938
|
+
const code = getErrorCode(error);
|
|
939
|
+
const metadata = code ? SQL_SERVER_ERROR_MAP[code] : void 0;
|
|
940
|
+
if (metadata) {
|
|
941
|
+
return {
|
|
942
|
+
error: errorMessage,
|
|
943
|
+
error_type: metadata.type,
|
|
944
|
+
suggestion: metadata.hint,
|
|
945
|
+
sql_attempted: sql
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
error: errorMessage,
|
|
950
|
+
error_type: "UNKNOWN_ERROR",
|
|
951
|
+
suggestion: "Review the query and try again",
|
|
952
|
+
sql_attempted: sql
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
var SqlServer = class extends Adapter {
|
|
956
|
+
#options;
|
|
957
|
+
grounding;
|
|
958
|
+
defaultSchema = "dbo";
|
|
959
|
+
systemSchemas = ["INFORMATION_SCHEMA", "sys"];
|
|
960
|
+
constructor(options) {
|
|
961
|
+
super();
|
|
962
|
+
if (!options || typeof options.execute !== "function") {
|
|
963
|
+
throw new Error("SqlServer adapter requires an execute function.");
|
|
964
|
+
}
|
|
965
|
+
this.#options = {
|
|
966
|
+
...options,
|
|
967
|
+
schemas: options.schemas?.length ? options.schemas : void 0
|
|
968
|
+
};
|
|
969
|
+
this.grounding = options.grounding;
|
|
970
|
+
}
|
|
971
|
+
async execute(sql) {
|
|
972
|
+
return this.#options.execute(sql);
|
|
973
|
+
}
|
|
974
|
+
async validate(sql) {
|
|
975
|
+
const validator = this.#options.validate ?? (async (text) => {
|
|
976
|
+
await this.#options.execute(
|
|
977
|
+
`SET PARSEONLY ON; ${text}; SET PARSEONLY OFF;`
|
|
978
|
+
);
|
|
979
|
+
});
|
|
980
|
+
try {
|
|
981
|
+
return await validator(sql);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
return JSON.stringify(formatSqlServerError(sql, error));
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
async runQuery(sql) {
|
|
987
|
+
return this.#runIntrospectionQuery(sql);
|
|
988
|
+
}
|
|
989
|
+
quoteIdentifier(name) {
|
|
990
|
+
return `[${name.replace(/]/g, "]]")}]`;
|
|
991
|
+
}
|
|
992
|
+
escape(value) {
|
|
993
|
+
return value.replace(/]/g, "]]");
|
|
994
|
+
}
|
|
995
|
+
async #loadTables() {
|
|
996
|
+
const rows = await this.#runIntrospectionQuery(`
|
|
997
|
+
SELECT
|
|
998
|
+
c.TABLE_SCHEMA AS table_schema,
|
|
999
|
+
c.TABLE_NAME AS table_name,
|
|
1000
|
+
c.COLUMN_NAME AS column_name,
|
|
1001
|
+
c.DATA_TYPE AS data_type
|
|
1002
|
+
FROM INFORMATION_SCHEMA.COLUMNS AS c
|
|
1003
|
+
JOIN INFORMATION_SCHEMA.TABLES AS t
|
|
1004
|
+
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
1005
|
+
AND c.TABLE_NAME = t.TABLE_NAME
|
|
1006
|
+
WHERE t.TABLE_TYPE = 'BASE TABLE'
|
|
1007
|
+
${this.#buildSchemaFilter("c.TABLE_SCHEMA")}
|
|
1008
|
+
ORDER BY c.TABLE_SCHEMA, c.TABLE_NAME, c.ORDINAL_POSITION
|
|
1009
|
+
`);
|
|
1010
|
+
const tables2 = /* @__PURE__ */ new Map();
|
|
1011
|
+
for (const row of rows) {
|
|
1012
|
+
if (!row.table_name) {
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
const schema = row.table_schema ?? "dbo";
|
|
1016
|
+
const tableName = row.table_name;
|
|
1017
|
+
const qualifiedName = `${schema}.${tableName}`;
|
|
1018
|
+
const table = tables2.get(qualifiedName) ?? {
|
|
1019
|
+
name: qualifiedName,
|
|
1020
|
+
schema,
|
|
1021
|
+
rawName: tableName,
|
|
1022
|
+
columns: []
|
|
1023
|
+
};
|
|
1024
|
+
table.columns.push({
|
|
1025
|
+
name: row.column_name ?? "unknown",
|
|
1026
|
+
type: row.data_type ?? "unknown"
|
|
1027
|
+
});
|
|
1028
|
+
tables2.set(qualifiedName, table);
|
|
1029
|
+
}
|
|
1030
|
+
return Array.from(tables2.values());
|
|
1031
|
+
}
|
|
1032
|
+
async #loadRelationships(filterTableNames) {
|
|
1033
|
+
const tableFilter = this.#buildTableNamesFilter(filterTableNames);
|
|
1034
|
+
const rows = await this.#runIntrospectionQuery(`
|
|
1035
|
+
SELECT
|
|
1036
|
+
fk.CONSTRAINT_NAME AS constraint_name,
|
|
1037
|
+
fk.TABLE_SCHEMA AS table_schema,
|
|
1038
|
+
fk.TABLE_NAME AS table_name,
|
|
1039
|
+
fk.COLUMN_NAME AS column_name,
|
|
1040
|
+
pk.TABLE_SCHEMA AS referenced_table_schema,
|
|
1041
|
+
pk.TABLE_NAME AS referenced_table_name,
|
|
1042
|
+
pk.COLUMN_NAME AS referenced_column_name
|
|
1043
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
|
1044
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS fk
|
|
1045
|
+
ON fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
1046
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS pk
|
|
1047
|
+
ON pk.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
|
1048
|
+
AND pk.ORDINAL_POSITION = fk.ORDINAL_POSITION
|
|
1049
|
+
WHERE 1 = 1
|
|
1050
|
+
${this.#buildSchemaFilter("fk.TABLE_SCHEMA")}
|
|
1051
|
+
${tableFilter}
|
|
1052
|
+
ORDER BY fk.TABLE_SCHEMA, fk.TABLE_NAME, fk.CONSTRAINT_NAME, fk.ORDINAL_POSITION
|
|
1053
|
+
`);
|
|
1054
|
+
const relationships = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const row of rows) {
|
|
1056
|
+
if (!row.constraint_name || !row.table_name || !row.referenced_table_name) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
const schema = row.table_schema ?? "dbo";
|
|
1060
|
+
const referencedSchema = row.referenced_table_schema ?? "dbo";
|
|
1061
|
+
const key = `${schema}.${row.table_name}:${row.constraint_name}`;
|
|
1062
|
+
const relationship = relationships.get(key) ?? {
|
|
1063
|
+
table: `${schema}.${row.table_name}`,
|
|
1064
|
+
from: [],
|
|
1065
|
+
referenced_table: `${referencedSchema}.${row.referenced_table_name}`,
|
|
1066
|
+
to: []
|
|
1067
|
+
};
|
|
1068
|
+
relationship.from.push(row.column_name ?? "unknown");
|
|
1069
|
+
relationship.to.push(row.referenced_column_name ?? "unknown");
|
|
1070
|
+
relationships.set(key, relationship);
|
|
1071
|
+
}
|
|
1072
|
+
return Array.from(relationships.values());
|
|
1073
|
+
}
|
|
1074
|
+
async #annotateRowCounts(tables2, onProgress) {
|
|
1075
|
+
const total = tables2.length;
|
|
1076
|
+
for (let i = 0; i < tables2.length; i++) {
|
|
1077
|
+
const table = tables2[i];
|
|
1078
|
+
const tableIdentifier = this.#formatQualifiedTableName(table);
|
|
1079
|
+
onProgress?.({
|
|
1080
|
+
phase: "row_counts",
|
|
1081
|
+
message: `Counting rows in ${table.name}...`,
|
|
1082
|
+
current: i + 1,
|
|
1083
|
+
total
|
|
1084
|
+
});
|
|
1085
|
+
try {
|
|
1086
|
+
const rows = await this.#runIntrospectionQuery(`SELECT COUNT(*) as count FROM ${tableIdentifier}`);
|
|
1087
|
+
const rowCount2 = this.#toNumber(rows[0]?.count);
|
|
1088
|
+
if (rowCount2 != null) {
|
|
1089
|
+
table.rowCount = rowCount2;
|
|
1090
|
+
table.sizeHint = this.#classifyRowCount(rowCount2);
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* @deprecated Primary keys are now handled via constraints grounding.
|
|
1099
|
+
* This method is kept for backward compatibility but does nothing.
|
|
1100
|
+
*/
|
|
1101
|
+
async #annotatePrimaryKeys(_tables) {
|
|
1102
|
+
}
|
|
1103
|
+
async #annotateIndexes(tables2) {
|
|
1104
|
+
if (!tables2.length) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const tableMap = new Map(tables2.map((table) => [table.name, table]));
|
|
1108
|
+
const rows = await this.#runIntrospectionQuery(`
|
|
1109
|
+
SELECT
|
|
1110
|
+
sch.name AS schema_name,
|
|
1111
|
+
t.name AS table_name,
|
|
1112
|
+
ind.name AS index_name,
|
|
1113
|
+
col.name AS column_name,
|
|
1114
|
+
ind.is_unique,
|
|
1115
|
+
ind.is_primary_key,
|
|
1116
|
+
ind.type_desc,
|
|
1117
|
+
ic.is_included_column
|
|
1118
|
+
FROM sys.indexes AS ind
|
|
1119
|
+
JOIN sys.tables AS t ON ind.object_id = t.object_id
|
|
1120
|
+
JOIN sys.schemas AS sch ON t.schema_id = sch.schema_id
|
|
1121
|
+
JOIN sys.index_columns AS ic
|
|
1122
|
+
ON ind.object_id = ic.object_id
|
|
1123
|
+
AND ind.index_id = ic.index_id
|
|
1124
|
+
JOIN sys.columns AS col
|
|
1125
|
+
ON ic.object_id = col.object_id
|
|
1126
|
+
AND ic.column_id = col.column_id
|
|
1127
|
+
WHERE ind.is_hypothetical = 0
|
|
1128
|
+
AND ind.name IS NOT NULL
|
|
1129
|
+
${this.#buildSchemaFilter("sch.name")}
|
|
1130
|
+
ORDER BY sch.name, t.name, ind.name, ic.key_ordinal
|
|
1131
|
+
`);
|
|
1132
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const row of rows) {
|
|
1134
|
+
if (!row.table_name || !row.index_name) {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
const schema = row.schema_name ?? "dbo";
|
|
1138
|
+
const tableKey = `${schema}.${row.table_name}`;
|
|
1139
|
+
const table = tableMap.get(tableKey);
|
|
1140
|
+
if (!table) {
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const indexKey = `${tableKey}:${row.index_name}`;
|
|
1144
|
+
let index = indexMap.get(indexKey);
|
|
1145
|
+
if (!index) {
|
|
1146
|
+
index = {
|
|
1147
|
+
name: row.index_name,
|
|
1148
|
+
columns: [],
|
|
1149
|
+
unique: Boolean(row.is_unique),
|
|
1150
|
+
type: row.type_desc ?? void 0
|
|
1151
|
+
};
|
|
1152
|
+
indexMap.set(indexKey, index);
|
|
1153
|
+
if (!table.indexes) {
|
|
1154
|
+
table.indexes = [];
|
|
1155
|
+
}
|
|
1156
|
+
table.indexes.push(index);
|
|
1157
|
+
}
|
|
1158
|
+
if (row.is_included_column) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (row.column_name) {
|
|
1162
|
+
index.columns.push(row.column_name);
|
|
1163
|
+
const column = table.columns.find(
|
|
1164
|
+
(col) => col.name === row.column_name
|
|
1165
|
+
);
|
|
1166
|
+
if (column) {
|
|
1167
|
+
column.isIndexed = true;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async #annotateColumnStats(tables2, onProgress) {
|
|
1173
|
+
if (!tables2.length) {
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const total = tables2.length;
|
|
1177
|
+
for (let i = 0; i < tables2.length; i++) {
|
|
1178
|
+
const table = tables2[i];
|
|
1179
|
+
const tableIdentifier = this.#formatQualifiedTableName(table);
|
|
1180
|
+
onProgress?.({
|
|
1181
|
+
phase: "column_stats",
|
|
1182
|
+
message: `Collecting stats for ${table.name}...`,
|
|
1183
|
+
current: i + 1,
|
|
1184
|
+
total
|
|
1185
|
+
});
|
|
1186
|
+
for (const column of table.columns) {
|
|
1187
|
+
if (!this.#shouldCollectStats(column.type)) {
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
const columnIdentifier = this.#quoteIdentifier(column.name);
|
|
1191
|
+
const sql = `
|
|
1192
|
+
SELECT
|
|
1193
|
+
CONVERT(NVARCHAR(4000), MIN(${columnIdentifier})) AS min_value,
|
|
1194
|
+
CONVERT(NVARCHAR(4000), MAX(${columnIdentifier})) AS max_value,
|
|
1195
|
+
AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
|
|
1196
|
+
FROM ${tableIdentifier}
|
|
1197
|
+
`;
|
|
1198
|
+
try {
|
|
1199
|
+
const rows = await this.#runIntrospectionQuery(sql);
|
|
1200
|
+
if (!rows.length) {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const nullFraction = this.#toNumber(rows[0]?.null_fraction);
|
|
1204
|
+
if (rows[0]?.min_value != null || rows[0]?.max_value != null || nullFraction != null) {
|
|
1205
|
+
column.stats = {
|
|
1206
|
+
min: rows[0]?.min_value ?? void 0,
|
|
1207
|
+
max: rows[0]?.max_value ?? void 0,
|
|
1208
|
+
nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async #annotateLowCardinalityColumns(tables2, onProgress) {
|
|
1218
|
+
const total = tables2.length;
|
|
1219
|
+
for (let i = 0; i < tables2.length; i++) {
|
|
1220
|
+
const table = tables2[i];
|
|
1221
|
+
const tableIdentifier = this.#formatQualifiedTableName(table);
|
|
1222
|
+
onProgress?.({
|
|
1223
|
+
phase: "low_cardinality",
|
|
1224
|
+
message: `Analyzing cardinality in ${table.name}...`,
|
|
1225
|
+
current: i + 1,
|
|
1226
|
+
total
|
|
1227
|
+
});
|
|
1228
|
+
for (const column of table.columns) {
|
|
1229
|
+
const columnIdentifier = this.#quoteIdentifier(column.name);
|
|
1230
|
+
const limit = LOW_CARDINALITY_LIMIT2 + 1;
|
|
1231
|
+
const sql = `
|
|
1232
|
+
SELECT DISTINCT TOP (${limit}) ${columnIdentifier} AS value
|
|
1233
|
+
FROM ${tableIdentifier}
|
|
1234
|
+
WHERE ${columnIdentifier} IS NOT NULL
|
|
1235
|
+
`;
|
|
1236
|
+
let rows = [];
|
|
1237
|
+
try {
|
|
1238
|
+
rows = await this.#runIntrospectionQuery(sql);
|
|
1239
|
+
} catch {
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT2) {
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
const values = [];
|
|
1246
|
+
let shouldSkip = false;
|
|
1247
|
+
for (const row of rows) {
|
|
1248
|
+
const formatted = this.#normalizeValue(row.value);
|
|
1249
|
+
if (formatted == null) {
|
|
1250
|
+
shouldSkip = true;
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
values.push(formatted);
|
|
1254
|
+
}
|
|
1255
|
+
if (shouldSkip || !values.length) {
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
column.kind = "LowCardinality";
|
|
1259
|
+
column.values = values;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
#buildSchemaFilter(columnName) {
|
|
1264
|
+
if (this.#options.schemas && this.#options.schemas.length > 0) {
|
|
1265
|
+
const values = this.#options.schemas.map((schema) => `'${schema.replace(/'/g, "''")}'`).join(", ");
|
|
1266
|
+
return `AND ${columnName} IN (${values})`;
|
|
1267
|
+
}
|
|
1268
|
+
return `AND ${columnName} NOT IN ('INFORMATION_SCHEMA', 'sys')`;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Build a filter for table names (qualified as schema.table).
|
|
1272
|
+
* Matches if either the source table or referenced table is in the list.
|
|
1273
|
+
*/
|
|
1274
|
+
#buildTableNamesFilter(tableNames) {
|
|
1275
|
+
if (!tableNames || tableNames.length === 0) {
|
|
1276
|
+
return "";
|
|
1277
|
+
}
|
|
1278
|
+
const conditions = [];
|
|
1279
|
+
for (const name of tableNames) {
|
|
1280
|
+
const escaped = name.replace(/'/g, "''");
|
|
1281
|
+
if (name.includes(".")) {
|
|
1282
|
+
const [schema, ...rest] = name.split(".");
|
|
1283
|
+
const tableName = rest.join(".");
|
|
1284
|
+
const escapedSchema = schema.replace(/'/g, "''");
|
|
1285
|
+
const escapedTable = tableName.replace(/'/g, "''");
|
|
1286
|
+
conditions.push(
|
|
1287
|
+
`(fk.TABLE_SCHEMA = '${escapedSchema}' AND fk.TABLE_NAME = '${escapedTable}')`
|
|
1288
|
+
);
|
|
1289
|
+
conditions.push(
|
|
1290
|
+
`(pk.TABLE_SCHEMA = '${escapedSchema}' AND pk.TABLE_NAME = '${escapedTable}')`
|
|
1291
|
+
);
|
|
1292
|
+
} else {
|
|
1293
|
+
conditions.push(`fk.TABLE_NAME = '${escaped}'`);
|
|
1294
|
+
conditions.push(`pk.TABLE_NAME = '${escaped}'`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return `AND (${conditions.join(" OR ")})`;
|
|
1298
|
+
}
|
|
1299
|
+
async #runIntrospectionQuery(sql) {
|
|
1300
|
+
const result = await this.#options.execute(sql);
|
|
1301
|
+
if (Array.isArray(result)) {
|
|
1302
|
+
return result;
|
|
1303
|
+
}
|
|
1304
|
+
if (result && typeof result === "object") {
|
|
1305
|
+
if ("rows" in result && Array.isArray(result.rows)) {
|
|
1306
|
+
return result.rows;
|
|
1307
|
+
}
|
|
1308
|
+
if ("recordset" in result && Array.isArray(result.recordset)) {
|
|
1309
|
+
return result.recordset;
|
|
1310
|
+
}
|
|
1311
|
+
if ("recordsets" in result && Array.isArray(result.recordsets) && Array.isArray(result.recordsets[0])) {
|
|
1312
|
+
return result.recordsets[0];
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
throw new Error(
|
|
1316
|
+
"SqlServer adapter execute() must return an array of rows or an object with rows/recordset properties when introspecting."
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
#quoteIdentifier(name) {
|
|
1320
|
+
return `[${name.replace(/]/g, "]]")}]`;
|
|
1321
|
+
}
|
|
1322
|
+
#formatQualifiedTableName(table) {
|
|
1323
|
+
if (table.schema && table.rawName) {
|
|
1324
|
+
return `${this.#quoteIdentifier(table.schema)}.${this.#quoteIdentifier(table.rawName)}`;
|
|
1325
|
+
}
|
|
1326
|
+
if (table.name.includes(".")) {
|
|
1327
|
+
const [schemaPart, ...rest] = table.name.split(".");
|
|
1328
|
+
const tablePart = rest.join(".") || schemaPart;
|
|
1329
|
+
if (rest.length === 0) {
|
|
1330
|
+
return this.#quoteIdentifier(schemaPart);
|
|
1331
|
+
}
|
|
1332
|
+
return `${this.#quoteIdentifier(schemaPart)}.${this.#quoteIdentifier(tablePart)}`;
|
|
1333
|
+
}
|
|
1334
|
+
return this.#quoteIdentifier(table.name);
|
|
1335
|
+
}
|
|
1336
|
+
#toNumber(value) {
|
|
1337
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1338
|
+
return value;
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof value === "bigint") {
|
|
1341
|
+
return Number(value);
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
1344
|
+
const parsed = Number(value);
|
|
1345
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1346
|
+
}
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
#classifyRowCount(count) {
|
|
1350
|
+
if (count < 100) {
|
|
1351
|
+
return "tiny";
|
|
1352
|
+
}
|
|
1353
|
+
if (count < 1e3) {
|
|
1354
|
+
return "small";
|
|
1355
|
+
}
|
|
1356
|
+
if (count < 1e4) {
|
|
1357
|
+
return "medium";
|
|
1358
|
+
}
|
|
1359
|
+
if (count < 1e5) {
|
|
1360
|
+
return "large";
|
|
1361
|
+
}
|
|
1362
|
+
return "huge";
|
|
1363
|
+
}
|
|
1364
|
+
#shouldCollectStats(type) {
|
|
1365
|
+
if (!type) {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
const normalized = type.toLowerCase();
|
|
1369
|
+
return /int|numeric|decimal|float|real|money|date|time|timestamp|bool/.test(
|
|
1370
|
+
normalized
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
#normalizeValue(value) {
|
|
1374
|
+
if (value === null || value === void 0) {
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
if (typeof value === "string") {
|
|
1378
|
+
return value;
|
|
1379
|
+
}
|
|
1380
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
1381
|
+
return String(value);
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof value === "boolean") {
|
|
1384
|
+
return value ? "true" : "false";
|
|
1385
|
+
}
|
|
1386
|
+
if (value instanceof Date) {
|
|
1387
|
+
return value.toISOString();
|
|
1388
|
+
}
|
|
1389
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1390
|
+
return value.toString("utf-8");
|
|
1391
|
+
}
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// packages/text2sql/src/lib/adapters/sqlserver/table.sqlserver.grounding.ts
|
|
1397
|
+
var SqlServerTableGrounding = class extends TableGrounding {
|
|
1398
|
+
#adapter;
|
|
1399
|
+
#schemas;
|
|
1400
|
+
constructor(adapter, config = {}) {
|
|
1401
|
+
super(config);
|
|
1402
|
+
this.#adapter = adapter;
|
|
1403
|
+
this.#schemas = config.schemas;
|
|
1404
|
+
}
|
|
1405
|
+
async getAllTableNames() {
|
|
1406
|
+
const rows = await this.#adapter.runQuery(`
|
|
1407
|
+
SELECT TABLE_SCHEMA + '.' + TABLE_NAME AS name
|
|
1408
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
1409
|
+
WHERE TABLE_TYPE = 'BASE TABLE'
|
|
1410
|
+
${this.#adapter.buildSchemaFilter("TABLE_SCHEMA", this.#schemas)}
|
|
1411
|
+
ORDER BY name
|
|
1412
|
+
`);
|
|
1413
|
+
return rows.map((r) => r.name);
|
|
1414
|
+
}
|
|
1415
|
+
async getTable(tableName) {
|
|
1416
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
1417
|
+
const columns = await this.#adapter.runQuery(`
|
|
1418
|
+
SELECT COLUMN_NAME AS column_name, DATA_TYPE AS data_type
|
|
1419
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1420
|
+
WHERE TABLE_SCHEMA = '${this.#adapter.escapeString(schema)}'
|
|
1421
|
+
AND TABLE_NAME = '${this.#adapter.escapeString(table)}'
|
|
1422
|
+
ORDER BY ORDINAL_POSITION
|
|
1423
|
+
`);
|
|
1424
|
+
return {
|
|
1425
|
+
name: tableName,
|
|
1426
|
+
schema,
|
|
1427
|
+
rawName: table,
|
|
1428
|
+
columns: columns.map((col) => ({
|
|
1429
|
+
name: col.column_name ?? "unknown",
|
|
1430
|
+
type: col.data_type ?? "unknown"
|
|
1431
|
+
}))
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
async findOutgoingRelations(tableName) {
|
|
1435
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
1436
|
+
const rows = await this.#adapter.runQuery(`
|
|
1437
|
+
SELECT
|
|
1438
|
+
fk.CONSTRAINT_NAME AS constraint_name,
|
|
1439
|
+
fk.TABLE_SCHEMA AS table_schema,
|
|
1440
|
+
fk.TABLE_NAME AS table_name,
|
|
1441
|
+
fk.COLUMN_NAME AS column_name,
|
|
1442
|
+
pk.TABLE_SCHEMA AS referenced_table_schema,
|
|
1443
|
+
pk.TABLE_NAME AS referenced_table_name,
|
|
1444
|
+
pk.COLUMN_NAME AS referenced_column_name
|
|
1445
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
|
1446
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS fk
|
|
1447
|
+
ON fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
1448
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS pk
|
|
1449
|
+
ON pk.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
|
1450
|
+
AND pk.ORDINAL_POSITION = fk.ORDINAL_POSITION
|
|
1451
|
+
WHERE fk.TABLE_SCHEMA = '${this.#adapter.escapeString(schema)}'
|
|
1452
|
+
AND fk.TABLE_NAME = '${this.#adapter.escapeString(table)}'
|
|
1453
|
+
ORDER BY fk.CONSTRAINT_NAME, fk.ORDINAL_POSITION
|
|
1454
|
+
`);
|
|
1455
|
+
return this.#groupRelationships(rows);
|
|
1456
|
+
}
|
|
1457
|
+
async findIncomingRelations(tableName) {
|
|
1458
|
+
const { schema, table } = this.#adapter.parseTableName(tableName);
|
|
1459
|
+
const rows = await this.#adapter.runQuery(`
|
|
1460
|
+
SELECT
|
|
1461
|
+
fk.CONSTRAINT_NAME AS constraint_name,
|
|
1462
|
+
fk.TABLE_SCHEMA AS table_schema,
|
|
1463
|
+
fk.TABLE_NAME AS table_name,
|
|
1464
|
+
fk.COLUMN_NAME AS column_name,
|
|
1465
|
+
pk.TABLE_SCHEMA AS referenced_table_schema,
|
|
1466
|
+
pk.TABLE_NAME AS referenced_table_name,
|
|
1467
|
+
pk.COLUMN_NAME AS referenced_column_name
|
|
1468
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
|
1469
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS fk
|
|
1470
|
+
ON fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
1471
|
+
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS pk
|
|
1472
|
+
ON pk.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
|
|
1473
|
+
AND pk.ORDINAL_POSITION = fk.ORDINAL_POSITION
|
|
1474
|
+
WHERE pk.TABLE_SCHEMA = '${this.#adapter.escapeString(schema)}'
|
|
1475
|
+
AND pk.TABLE_NAME = '${this.#adapter.escapeString(table)}'
|
|
1476
|
+
ORDER BY fk.CONSTRAINT_NAME, fk.ORDINAL_POSITION
|
|
1477
|
+
`);
|
|
1478
|
+
return this.#groupRelationships(rows);
|
|
1479
|
+
}
|
|
1480
|
+
#groupRelationships(rows) {
|
|
1481
|
+
const relationships = /* @__PURE__ */ new Map();
|
|
1482
|
+
const defaultSchema = this.#adapter.defaultSchema ?? "dbo";
|
|
1483
|
+
for (const row of rows) {
|
|
1484
|
+
if (!row.constraint_name || !row.table_name || !row.referenced_table_name) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const schema = row.table_schema ?? defaultSchema;
|
|
1488
|
+
const referencedSchema = row.referenced_table_schema ?? defaultSchema;
|
|
1489
|
+
const key = `${schema}.${row.table_name}:${row.constraint_name}`;
|
|
1490
|
+
const relationship = relationships.get(key) ?? {
|
|
1491
|
+
table: `${schema}.${row.table_name}`,
|
|
1492
|
+
from: [],
|
|
1493
|
+
referenced_table: `${referencedSchema}.${row.referenced_table_name}`,
|
|
1494
|
+
to: []
|
|
1495
|
+
};
|
|
1496
|
+
relationship.from.push(row.column_name ?? "unknown");
|
|
1497
|
+
relationship.to.push(row.referenced_column_name ?? "unknown");
|
|
1498
|
+
relationships.set(key, relationship);
|
|
1499
|
+
}
|
|
1500
|
+
return Array.from(relationships.values());
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
// packages/text2sql/src/lib/adapters/groundings/view.grounding.ts
|
|
1505
|
+
var ViewGrounding = class extends AbstractGrounding {
|
|
1506
|
+
#filter;
|
|
1507
|
+
constructor(config = {}) {
|
|
1508
|
+
super("views");
|
|
1509
|
+
this.#filter = config.filter;
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Execute the grounding process.
|
|
1513
|
+
* Writes discovered views to the context.
|
|
1514
|
+
*/
|
|
1515
|
+
async execute(ctx) {
|
|
1516
|
+
const viewNames = await this.applyFilter();
|
|
1517
|
+
const views2 = await Promise.all(
|
|
1518
|
+
viewNames.map((name) => this.getView(name))
|
|
1519
|
+
);
|
|
1520
|
+
ctx.views.push(...views2);
|
|
1521
|
+
return () => this.#describe(views2);
|
|
1522
|
+
}
|
|
1523
|
+
#describe(views2) {
|
|
1524
|
+
if (!views2.length) {
|
|
1525
|
+
return "No views available.";
|
|
1526
|
+
}
|
|
1527
|
+
return views2.map((view) => {
|
|
1528
|
+
const columns = view.columns.map((column) => {
|
|
1529
|
+
const annotations = [];
|
|
1530
|
+
if (column.kind === "LowCardinality" && column.values?.length) {
|
|
1531
|
+
annotations.push(`LowCardinality: ${column.values.join(", ")}`);
|
|
1532
|
+
}
|
|
1533
|
+
if (column.stats) {
|
|
1534
|
+
const statParts = [];
|
|
1535
|
+
if (column.stats.min != null || column.stats.max != null) {
|
|
1536
|
+
const minText = column.stats.min ?? "n/a";
|
|
1537
|
+
const maxText = column.stats.max ?? "n/a";
|
|
1538
|
+
statParts.push(`range ${minText} \u2192 ${maxText}`);
|
|
1539
|
+
}
|
|
1540
|
+
if (column.stats.nullFraction != null && Number.isFinite(column.stats.nullFraction)) {
|
|
1541
|
+
const percent = Math.round(column.stats.nullFraction * 1e3) / 10;
|
|
1542
|
+
statParts.push(`null\u2248${percent}%`);
|
|
1543
|
+
}
|
|
1544
|
+
if (statParts.length) {
|
|
1545
|
+
annotations.push(statParts.join(", "));
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
const annotationText = annotations.length ? ` [${annotations.join(", ")}]` : "";
|
|
1549
|
+
return ` - ${column.name} (${column.type})${annotationText}`;
|
|
1550
|
+
}).join("\n");
|
|
1551
|
+
const definition = view.definition ? `
|
|
1552
|
+
Definition: ${view.definition.length > 200 ? view.definition.slice(0, 200) + "..." : view.definition}` : "";
|
|
1553
|
+
return `- View: ${view.name}${definition}
|
|
1554
|
+
Columns:
|
|
1555
|
+
${columns}`;
|
|
1556
|
+
}).join("\n\n");
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Apply the filter to get view names.
|
|
1560
|
+
* If filter is an explicit array, skip querying all view names.
|
|
1561
|
+
*/
|
|
1562
|
+
async applyFilter() {
|
|
1563
|
+
const filter = this.#filter;
|
|
1564
|
+
if (Array.isArray(filter)) {
|
|
1565
|
+
return filter;
|
|
1566
|
+
}
|
|
1567
|
+
const names = await this.getAllViewNames();
|
|
1568
|
+
if (!filter) {
|
|
1569
|
+
return names;
|
|
1570
|
+
}
|
|
1571
|
+
if (filter instanceof RegExp) {
|
|
1572
|
+
return names.filter((name) => filter.test(name));
|
|
1573
|
+
}
|
|
1574
|
+
return names.filter(filter);
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// packages/text2sql/src/lib/adapters/sqlserver/view.sqlserver.grounding.ts
|
|
1579
|
+
var SqlServerViewGrounding = class extends ViewGrounding {
|
|
1580
|
+
#adapter;
|
|
1581
|
+
#schemas;
|
|
1582
|
+
constructor(adapter, config = {}) {
|
|
1583
|
+
super(config);
|
|
1584
|
+
this.#adapter = adapter;
|
|
1585
|
+
this.#schemas = config.schemas;
|
|
1586
|
+
}
|
|
1587
|
+
async getAllViewNames() {
|
|
1588
|
+
const rows = await this.#adapter.runQuery(`
|
|
1589
|
+
SELECT TABLE_SCHEMA + '.' + TABLE_NAME AS name
|
|
1590
|
+
FROM INFORMATION_SCHEMA.VIEWS
|
|
1591
|
+
WHERE 1=1
|
|
1592
|
+
${this.#adapter.buildSchemaFilter("TABLE_SCHEMA", this.#schemas)}
|
|
1593
|
+
ORDER BY name
|
|
1594
|
+
`);
|
|
1595
|
+
return rows.map((r) => r.name);
|
|
1596
|
+
}
|
|
1597
|
+
async getView(viewName) {
|
|
1598
|
+
const { schema, table: view } = this.#adapter.parseTableName(viewName);
|
|
1599
|
+
const defRows = await this.#adapter.runQuery(`
|
|
1600
|
+
SELECT m.definition
|
|
1601
|
+
FROM sys.views v
|
|
1602
|
+
JOIN sys.schemas s ON v.schema_id = s.schema_id
|
|
1603
|
+
JOIN sys.sql_modules m ON v.object_id = m.object_id
|
|
1604
|
+
WHERE s.name = '${this.#adapter.escapeString(schema)}'
|
|
1605
|
+
AND v.name = '${this.#adapter.escapeString(view)}'
|
|
1606
|
+
`);
|
|
1607
|
+
const columns = await this.#adapter.runQuery(`
|
|
1608
|
+
SELECT COLUMN_NAME AS column_name, DATA_TYPE AS data_type
|
|
1609
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
1610
|
+
WHERE TABLE_SCHEMA = '${this.#adapter.escapeString(schema)}'
|
|
1611
|
+
AND TABLE_NAME = '${this.#adapter.escapeString(view)}'
|
|
1612
|
+
ORDER BY ORDINAL_POSITION
|
|
1613
|
+
`);
|
|
1614
|
+
return {
|
|
1615
|
+
name: viewName,
|
|
1616
|
+
schema,
|
|
1617
|
+
rawName: view,
|
|
1618
|
+
definition: defRows[0]?.definition ?? void 0,
|
|
1619
|
+
columns: columns.map((col) => ({
|
|
1620
|
+
name: col.column_name ?? "unknown",
|
|
1621
|
+
type: col.data_type ?? "unknown"
|
|
1622
|
+
}))
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
// packages/text2sql/src/lib/adapters/sqlserver/index.ts
|
|
1628
|
+
function tables(config = {}) {
|
|
1629
|
+
return (adapter) => new SqlServerTableGrounding(adapter, config);
|
|
1630
|
+
}
|
|
1631
|
+
function info(config = {}) {
|
|
1632
|
+
return (adapter) => new SqlServerInfoGrounding(adapter, config);
|
|
1633
|
+
}
|
|
1634
|
+
function views(config = {}) {
|
|
1635
|
+
return (adapter) => {
|
|
1636
|
+
return new SqlServerViewGrounding(adapter, config);
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function columnStats(config = {}) {
|
|
1640
|
+
return (adapter) => {
|
|
1641
|
+
return new SqlServerColumnStatsGrounding(adapter, config);
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
function lowCardinality(config = {}) {
|
|
1645
|
+
return (adapter) => {
|
|
1646
|
+
return new SqlServerLowCardinalityGrounding(adapter, config);
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function indexes(config = {}) {
|
|
1650
|
+
return (adapter) => {
|
|
1651
|
+
return new SqlServerIndexesGrounding(adapter, config);
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
function rowCount(config = {}) {
|
|
1655
|
+
return (adapter) => {
|
|
1656
|
+
return new SqlServerRowCountGrounding(adapter, config);
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function constraints(config = {}) {
|
|
1660
|
+
return (adapter) => {
|
|
1661
|
+
return new SqlServerConstraintGrounding(adapter, config);
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
function report(config = {}) {
|
|
1665
|
+
return (adapter) => new ReportGrounding(adapter, config);
|
|
1666
|
+
}
|
|
1667
|
+
var sqlserver_default = {
|
|
1668
|
+
tables,
|
|
1669
|
+
info,
|
|
1670
|
+
views,
|
|
1671
|
+
columnStats,
|
|
1672
|
+
lowCardinality,
|
|
1673
|
+
indexes,
|
|
1674
|
+
rowCount,
|
|
1675
|
+
constraints,
|
|
1676
|
+
report,
|
|
1677
|
+
SqlServer
|
|
1678
|
+
};
|
|
1679
|
+
export {
|
|
1680
|
+
SqlServer,
|
|
1681
|
+
columnStats,
|
|
1682
|
+
constraints,
|
|
1683
|
+
sqlserver_default as default,
|
|
1684
|
+
formatSqlServerError,
|
|
1685
|
+
indexes,
|
|
1686
|
+
info,
|
|
1687
|
+
lowCardinality,
|
|
1688
|
+
report,
|
|
1689
|
+
rowCount,
|
|
1690
|
+
tables,
|
|
1691
|
+
views
|
|
1692
|
+
};
|
|
1693
|
+
//# sourceMappingURL=index.js.map
|