@deepagents/text2sql 7.0.2 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bin/sql.js +57 -32
- package/dist/index.js +793 -498
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/adapter.d.ts +23 -1
- package/dist/lib/adapters/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/bigquery-fk.d.ts +0 -11
- package/dist/lib/adapters/bigquery/bigquery-fk.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/bigquery-rows.d.ts +22 -0
- package/dist/lib/adapters/bigquery/bigquery-rows.d.ts.map +1 -0
- package/dist/lib/adapters/bigquery/bigquery.d.ts +1 -1
- package/dist/lib/adapters/bigquery/bigquery.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/constraint.bigquery.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/index.d.ts +6 -13
- package/dist/lib/adapters/bigquery/index.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/index.js +244 -51
- package/dist/lib/adapters/bigquery/index.js.map +4 -4
- package/dist/lib/adapters/bigquery/indexes.bigquery.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/row-count.bigquery.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/table.bigquery.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/view.bigquery.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/clickhouse-rows.d.ts +7 -0
- package/dist/lib/adapters/clickhouse/clickhouse-rows.d.ts.map +1 -0
- package/dist/lib/adapters/clickhouse/clickhouse.d.ts +1 -1
- package/dist/lib/adapters/clickhouse/clickhouse.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/constraint.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/index.js +133 -44
- package/dist/lib/adapters/clickhouse/index.js.map +4 -4
- package/dist/lib/adapters/clickhouse/indexes.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/info.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/row-count.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/table.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/clickhouse/view.clickhouse.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/duckdb/duckdb.d.ts +1 -1
- package/dist/lib/adapters/duckdb/duckdb.d.ts.map +1 -1
- package/dist/lib/adapters/duckdb/duckdb.groundings.d.ts.map +1 -1
- package/dist/lib/adapters/duckdb/index.d.ts +8 -10
- package/dist/lib/adapters/duckdb/index.d.ts.map +1 -1
- package/dist/lib/adapters/duckdb/index.js +1549 -1412
- package/dist/lib/adapters/duckdb/index.js.map +4 -4
- package/dist/lib/adapters/groundings/index.js.map +1 -1
- package/dist/lib/adapters/groundings/rows.d.ts +32 -0
- package/dist/lib/adapters/groundings/rows.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/column-stats.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/column-values.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/constraint.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/index.d.ts +2 -4
- package/dist/lib/adapters/mysql/index.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/index.js +230 -76
- package/dist/lib/adapters/mysql/index.js.map +4 -4
- package/dist/lib/adapters/mysql/indexes.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/info.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/mysql-rows.d.ts +11 -0
- package/dist/lib/adapters/mysql/mysql-rows.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/mysql.d.ts +1 -1
- package/dist/lib/adapters/mysql/mysql.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/row-count.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/table.mysql.grounding.d.ts +3 -2
- package/dist/lib/adapters/mysql/table.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/view.mysql.grounding.d.ts +2 -2
- package/dist/lib/adapters/mysql/view.mysql.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/constraint.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/index.d.ts +2 -4
- package/dist/lib/adapters/postgres/index.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/index.js +266 -487
- package/dist/lib/adapters/postgres/index.js.map +4 -4
- package/dist/lib/adapters/postgres/indexes.postgres.grounding.d.ts +1 -5
- package/dist/lib/adapters/postgres/indexes.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/info.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/postgres-rows.d.ts +7 -0
- package/dist/lib/adapters/postgres/postgres-rows.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/postgres.d.ts +6 -1
- package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/row-count.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/table.postgres.grounding.d.ts +5 -4
- package/dist/lib/adapters/postgres/table.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/view.postgres.grounding.d.ts +3 -3
- package/dist/lib/adapters/postgres/view.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/definitions.posthog.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/index.d.ts +4 -5
- package/dist/lib/adapters/posthog/index.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/index.js +349 -287
- package/dist/lib/adapters/posthog/index.js.map +4 -4
- package/dist/lib/adapters/posthog/posthog.d.ts +2 -2
- package/dist/lib/adapters/posthog/posthog.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/posthog.sql-policy.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/transport.d.ts.map +1 -1
- package/dist/lib/adapters/posthog/types.d.ts +16 -6
- package/dist/lib/adapters/posthog/types.d.ts.map +1 -1
- package/dist/lib/adapters/spreadsheet/index.js +113 -34
- package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
- package/dist/lib/adapters/sqlite/column-stats.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/column-values.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/constraint.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/index.js +136 -49
- package/dist/lib/adapters/sqlite/index.js.map +4 -4
- package/dist/lib/adapters/sqlite/indexes.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/info.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/row-count.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/sqlite-rows.d.ts +41 -0
- package/dist/lib/adapters/sqlite/sqlite-rows.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/sqlite.d.ts +1 -1
- package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/table.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/view.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/column-stats.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/column-values.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/constraint.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/index.d.ts +2 -4
- package/dist/lib/adapters/sqlserver/index.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/index.js +223 -455
- package/dist/lib/adapters/sqlserver/index.js.map +4 -4
- package/dist/lib/adapters/sqlserver/indexes.sqlserver.grounding.d.ts +1 -5
- package/dist/lib/adapters/sqlserver/indexes.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/info.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/row-count.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/sqlserver-rows.d.ts +7 -0
- package/dist/lib/adapters/sqlserver/sqlserver-rows.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts +6 -1
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts +4 -3
- package/dist/lib/adapters/sqlserver/table.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/view.sqlserver.grounding.d.ts +3 -3
- package/dist/lib/adapters/sqlserver/view.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/agents/sql-transform-plugins.d.ts.map +1 -1
- package/dist/lib/checkpoint.d.ts +34 -21
- package/dist/lib/checkpoint.d.ts.map +1 -1
- package/dist/lib/file-cache.d.ts +6 -1
- package/dist/lib/file-cache.d.ts.map +1 -1
- package/dist/lib/fs/columns.d.ts +14 -0
- package/dist/lib/fs/columns.d.ts.map +1 -0
- package/dist/lib/fs/mssql/mssql-fs.d.ts +4 -4
- package/dist/lib/fs/mssql/mssql-fs.d.ts.map +1 -1
- package/dist/lib/fs/postgres/postgres-fs.d.ts +4 -4
- package/dist/lib/fs/postgres/postgres-fs.d.ts.map +1 -1
- package/dist/lib/fs/sqlite/sqlite-fs.d.ts +4 -4
- package/dist/lib/fs/sqlite/sqlite-fs.d.ts.map +1 -1
- package/dist/lib/index-cache.d.ts +4 -3
- package/dist/lib/index-cache.d.ts.map +1 -1
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts +1 -2
- package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts.map +1 -1
- package/dist/lib/synthesis/decorators/filtered-producer.d.ts +0 -2
- package/dist/lib/synthesis/decorators/filtered-producer.d.ts.map +1 -1
- package/dist/lib/synthesis/decorators/validated-producer.d.ts +1 -3
- package/dist/lib/synthesis/decorators/validated-producer.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/tool-input.d.ts +6 -0
- package/dist/lib/synthesis/extractors/tool-input.d.ts.map +1 -0
- package/dist/lib/synthesis/index.js +124 -114
- package/dist/lib/synthesis/index.js.map +4 -4
- package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts +0 -2
- package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts +0 -3
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +0 -2
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -1,1554 +1,1674 @@
|
|
|
1
|
-
// packages/text2sql/src/lib/adapters/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
2
|
+
import { format as formatSql } from "sql-formatter";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// packages/text2sql/src/lib/agents/exceptions.ts
|
|
6
|
+
var sqlValidationMarker = /* @__PURE__ */ Symbol("SQLValidationError");
|
|
7
|
+
var unanswerableSqlMarker = /* @__PURE__ */ Symbol("UnanswerableSQLError");
|
|
8
|
+
var sqlScopeMarker = /* @__PURE__ */ Symbol("SQLScopeError");
|
|
9
|
+
var sqlReadOnlyMarker = /* @__PURE__ */ Symbol("SQLReadOnlyError");
|
|
10
|
+
var SQLValidationError = class _SQLValidationError extends Error {
|
|
11
|
+
[sqlValidationMarker];
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "SQLValidationError";
|
|
15
|
+
this[sqlValidationMarker] = true;
|
|
5
16
|
}
|
|
6
|
-
|
|
7
|
-
return
|
|
17
|
+
static isInstance(error) {
|
|
18
|
+
return error instanceof _SQLValidationError && error[sqlValidationMarker] === true;
|
|
8
19
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
var AbstractGrounding = class {
|
|
18
|
-
/**
|
|
19
|
-
* Grounding identifier for debugging/logging.
|
|
20
|
-
*/
|
|
21
|
-
name;
|
|
22
|
-
phase;
|
|
23
|
-
constructor(name, phase) {
|
|
24
|
-
this.name = name;
|
|
25
|
-
this.phase = phase;
|
|
20
|
+
};
|
|
21
|
+
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
22
|
+
[unanswerableSqlMarker];
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "UnanswerableSQLError";
|
|
26
|
+
this[unanswerableSqlMarker] = true;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* resolution. Annotators decorate entities other groundings produced and add
|
|
30
|
-
* no membership, so the default is a no-op; entity-producing groundings
|
|
31
|
-
* override it. This lets scope resolution skip the annotators' metadata scans.
|
|
32
|
-
*
|
|
33
|
-
* @param _ctx - Shared context for accumulating entity membership
|
|
34
|
-
*/
|
|
35
|
-
async contributeEntities(_ctx) {
|
|
28
|
+
static isInstance(error) {
|
|
29
|
+
return error instanceof _UnanswerableSQLError && error[unanswerableSqlMarker] === true;
|
|
36
30
|
}
|
|
37
31
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
var SQLScopeError = class _SQLScopeError extends Error {
|
|
33
|
+
[sqlScopeMarker];
|
|
34
|
+
payload;
|
|
35
|
+
errorType;
|
|
36
|
+
constructor(payload) {
|
|
37
|
+
super(JSON.stringify(payload));
|
|
38
|
+
this.name = "SQLScopeError";
|
|
39
|
+
this.payload = payload;
|
|
40
|
+
this.errorType = payload.error_type;
|
|
41
|
+
this[sqlScopeMarker] = true;
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* Annotates columns in ctx.tables and ctx.views with statistics.
|
|
47
|
-
*/
|
|
48
|
-
async execute(ctx) {
|
|
49
|
-
const allContainers = [...ctx.tables, ...ctx.views];
|
|
50
|
-
const total = allContainers.length;
|
|
51
|
-
for (let i = 0; i < allContainers.length; i++) {
|
|
52
|
-
const container = allContainers[i];
|
|
53
|
-
ctx.onProgress({
|
|
54
|
-
type: "phase:progress",
|
|
55
|
-
phase: "column_stats",
|
|
56
|
-
table: container.name,
|
|
57
|
-
message: `Collecting stats for ${container.name}...`,
|
|
58
|
-
current: i + 1,
|
|
59
|
-
total
|
|
60
|
-
});
|
|
61
|
-
for (const column2 of container.columns) {
|
|
62
|
-
try {
|
|
63
|
-
const stats = await this.collectStats(container.name, column2);
|
|
64
|
-
if (stats) {
|
|
65
|
-
column2.stats = stats;
|
|
66
|
-
}
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.warn(
|
|
69
|
-
"Error collecting stats for",
|
|
70
|
-
container.name,
|
|
71
|
-
column2.name,
|
|
72
|
-
error
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
43
|
+
static isInstance(error) {
|
|
44
|
+
return error instanceof _SQLScopeError && error[sqlScopeMarker] === true;
|
|
77
45
|
}
|
|
78
46
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
super("columnValues", "column_values");
|
|
86
|
-
this.lowCardinalityLimit = config.lowCardinalityLimit ?? 20;
|
|
87
|
-
this.maxValueLength = config.maxValueLength ?? 100;
|
|
47
|
+
var SQLReadOnlyError = class _SQLReadOnlyError extends Error {
|
|
48
|
+
[sqlReadOnlyMarker];
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = "SQLReadOnlyError";
|
|
52
|
+
this[sqlReadOnlyMarker] = true;
|
|
88
53
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* Return undefined if column is not an ENUM type.
|
|
92
|
-
* Default implementation returns undefined (no native ENUM support).
|
|
93
|
-
*/
|
|
94
|
-
async collectEnumValues(_tableName, _column) {
|
|
95
|
-
return void 0;
|
|
54
|
+
static isInstance(error) {
|
|
55
|
+
return error instanceof _SQLReadOnlyError && error[sqlReadOnlyMarker] === true;
|
|
96
56
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// packages/text2sql/src/lib/fragments/schema.ts
|
|
60
|
+
function dialectInfo(input) {
|
|
61
|
+
return {
|
|
62
|
+
name: "dialectInfo",
|
|
63
|
+
data: {
|
|
64
|
+
dialect: input.dialect,
|
|
65
|
+
...input.version && { version: input.version },
|
|
66
|
+
...input.database && { database: input.database },
|
|
67
|
+
...input.details && { details: input.details }
|
|
107
68
|
}
|
|
108
|
-
|
|
109
|
-
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function table(input) {
|
|
72
|
+
return {
|
|
73
|
+
name: "table",
|
|
74
|
+
data: {
|
|
75
|
+
name: input.name,
|
|
76
|
+
...input.schema && { schema: input.schema },
|
|
77
|
+
...input.rowCount != null && { rowCount: input.rowCount },
|
|
78
|
+
...input.sizeHint && { sizeHint: input.sizeHint },
|
|
79
|
+
columns: input.columns,
|
|
80
|
+
...input.indexes?.length && { indexes: input.indexes },
|
|
81
|
+
...input.constraints?.length && { constraints: input.constraints }
|
|
110
82
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function column(input) {
|
|
86
|
+
return {
|
|
87
|
+
name: "column",
|
|
88
|
+
data: {
|
|
89
|
+
name: input.name,
|
|
90
|
+
type: input.type,
|
|
91
|
+
...input.pk && { pk: true },
|
|
92
|
+
...input.fk && { fk: input.fk },
|
|
93
|
+
...input.unique && { unique: true },
|
|
94
|
+
...input.notNull && { notNull: true },
|
|
95
|
+
...input.default && { default: input.default },
|
|
96
|
+
...input.indexed && { indexed: true },
|
|
97
|
+
...input.values?.length && { values: input.values },
|
|
98
|
+
...input.stats && { stats: input.stats }
|
|
119
99
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function index(input) {
|
|
103
|
+
return {
|
|
104
|
+
name: "index",
|
|
105
|
+
data: {
|
|
106
|
+
name: input.name,
|
|
107
|
+
columns: input.columns,
|
|
108
|
+
...input.unique && { unique: true },
|
|
109
|
+
...input.type && { type: input.type }
|
|
128
110
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function constraint(input) {
|
|
114
|
+
return {
|
|
115
|
+
name: "constraint",
|
|
116
|
+
data: {
|
|
117
|
+
name: input.name,
|
|
118
|
+
type: input.type,
|
|
119
|
+
...input.columns?.length && { columns: input.columns },
|
|
120
|
+
...input.definition && { definition: input.definition },
|
|
121
|
+
...input.defaultValue && { defaultValue: input.defaultValue },
|
|
122
|
+
...input.referencedTable && { referencedTable: input.referencedTable },
|
|
123
|
+
...input.referencedColumns?.length && {
|
|
124
|
+
referencedColumns: input.referencedColumns
|
|
125
|
+
}
|
|
136
126
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function view(input) {
|
|
130
|
+
return {
|
|
131
|
+
name: "view",
|
|
132
|
+
data: {
|
|
133
|
+
name: input.name,
|
|
134
|
+
...input.schema && { schema: input.schema },
|
|
135
|
+
columns: input.columns,
|
|
136
|
+
...input.definition && { definition: input.definition }
|
|
147
137
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function relationship(input) {
|
|
141
|
+
return {
|
|
142
|
+
name: "relationship",
|
|
143
|
+
data: {
|
|
144
|
+
from: input.from,
|
|
145
|
+
to: input.to,
|
|
146
|
+
...input.cardinality && { cardinality: input.cardinality }
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// packages/text2sql/src/lib/adapters/groundings/context.ts
|
|
152
|
+
var noopProgress = () => {
|
|
153
|
+
};
|
|
154
|
+
function timestampProgress(onProgress) {
|
|
155
|
+
if (!onProgress) return noopProgress;
|
|
156
|
+
return (progress) => {
|
|
157
|
+
onProgress({
|
|
158
|
+
...progress,
|
|
159
|
+
timestampMs: progress.timestampMs ?? Date.now()
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function createGroundingContext(options = {}) {
|
|
164
|
+
return {
|
|
165
|
+
tables: [],
|
|
166
|
+
views: [],
|
|
167
|
+
relationships: [],
|
|
168
|
+
info: void 0,
|
|
169
|
+
fragments: [],
|
|
170
|
+
cache: /* @__PURE__ */ new Map(),
|
|
171
|
+
onProgress: timestampProgress(options.onProgress)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
176
|
+
function groundingFor(adapterType, create) {
|
|
177
|
+
return (adapter) => {
|
|
178
|
+
if (!(adapter instanceof adapterType)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`A ${adapterType.name} grounding needs a ${adapterType.name} adapter, not ${adapter.constructor.name}.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return create(adapter);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
var Adapter = class {
|
|
187
|
+
#policyAnalyzer;
|
|
156
188
|
/**
|
|
157
|
-
*
|
|
189
|
+
* Injects the strategy that guards validate() and execute().
|
|
158
190
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
constructor(policyAnalyzer) {
|
|
192
|
+
this.#policyAnalyzer = policyAnalyzer;
|
|
161
193
|
}
|
|
162
194
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
195
|
+
* Introspect the database schema and return context fragments.
|
|
196
|
+
*
|
|
197
|
+
* Executes all configured groundings to populate the context, then
|
|
198
|
+
* generates fragments from the complete context data.
|
|
199
|
+
*
|
|
200
|
+
* @param ctx - Optional grounding context for sharing state between groundings
|
|
201
|
+
* @returns Array of context fragments representing the database schema
|
|
165
202
|
*/
|
|
166
|
-
async
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
table2?.constraints
|
|
186
|
-
);
|
|
187
|
-
if (result) {
|
|
188
|
-
column2.kind = result.kind;
|
|
189
|
-
column2.values = result.values;
|
|
190
|
-
}
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.warn(
|
|
193
|
-
"Error collecting column values for",
|
|
194
|
-
container.name,
|
|
195
|
-
column2.name,
|
|
196
|
-
error
|
|
197
|
-
);
|
|
203
|
+
async introspect(ctx = createGroundingContext()) {
|
|
204
|
+
for (const fn of this.grounding) {
|
|
205
|
+
const grounding = fn(this);
|
|
206
|
+
if (grounding.phase) {
|
|
207
|
+
ctx.onProgress({
|
|
208
|
+
type: "phase:start",
|
|
209
|
+
phase: grounding.phase,
|
|
210
|
+
message: `Starting ${grounding.name} introspection...`
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
await grounding.execute(ctx);
|
|
215
|
+
} finally {
|
|
216
|
+
if (grounding.phase) {
|
|
217
|
+
ctx.onProgress({
|
|
218
|
+
type: "phase:end",
|
|
219
|
+
phase: grounding.phase,
|
|
220
|
+
message: `Finished ${grounding.name} introspection.`
|
|
221
|
+
});
|
|
198
222
|
}
|
|
199
223
|
}
|
|
200
224
|
}
|
|
201
|
-
|
|
202
|
-
exceedsMaxValueLength(values) {
|
|
203
|
-
return values.some((v) => v.length > this.maxValueLength);
|
|
225
|
+
return this.#toSchemaFragments(ctx);
|
|
204
226
|
}
|
|
205
227
|
/**
|
|
206
|
-
* Resolve
|
|
228
|
+
* Resolve the allowed entity names (tables + views) from grounding config.
|
|
229
|
+
* Asks each grounding to contribute its entities; annotators contribute
|
|
230
|
+
* nothing, so their metadata scans are skipped.
|
|
231
|
+
* Results are NOT cached — call once and store the result.
|
|
207
232
|
*/
|
|
208
|
-
async
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
if (constraints2) {
|
|
214
|
-
for (const constraint2 of constraints2) {
|
|
215
|
-
const checkValues = this.parseCheckConstraint(constraint2, column2.name);
|
|
216
|
-
if (checkValues?.length && !this.exceedsMaxValueLength(checkValues)) {
|
|
217
|
-
return { kind: "Enum", values: checkValues };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
const lowCardValues = await this.collectLowCardinality(tableName, column2);
|
|
222
|
-
if (lowCardValues?.length && !this.exceedsMaxValueLength(lowCardValues)) {
|
|
223
|
-
return { kind: "LowCardinality", values: lowCardValues };
|
|
233
|
+
async resolveAllowedEntities() {
|
|
234
|
+
const ctx = createGroundingContext();
|
|
235
|
+
for (const fn of this.grounding) {
|
|
236
|
+
await fn(this).contributeEntities(ctx);
|
|
224
237
|
}
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// packages/text2sql/src/lib/adapters/groundings/constraint.grounding.ts
|
|
230
|
-
var ConstraintGrounding = class extends AbstractGrounding {
|
|
231
|
-
constructor(config = {}) {
|
|
232
|
-
super("constraint", "constraints");
|
|
238
|
+
return [...ctx.tables.map((t) => t.name), ...ctx.views.map((v) => v.name)];
|
|
233
239
|
}
|
|
234
240
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
241
|
+
* Convert complete grounding context to schema fragments.
|
|
242
|
+
* Called after all groundings have populated ctx with data.
|
|
237
243
|
*/
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
244
|
+
#toSchemaFragments(ctx) {
|
|
245
|
+
const fragments = [];
|
|
246
|
+
if (ctx.info) {
|
|
247
|
+
fragments.push(
|
|
248
|
+
dialectInfo({
|
|
249
|
+
dialect: ctx.info.dialect,
|
|
250
|
+
version: ctx.info.version,
|
|
251
|
+
database: ctx.info.database,
|
|
252
|
+
details: ctx.info.details
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
for (const t of ctx.tables) {
|
|
257
|
+
fragments.push(this.#tableToFragment(t));
|
|
258
|
+
}
|
|
259
|
+
for (const v of ctx.views) {
|
|
260
|
+
fragments.push(this.#viewToFragment(v));
|
|
261
|
+
}
|
|
262
|
+
const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
|
|
263
|
+
const tableColumnSets = new Map(
|
|
264
|
+
ctx.tables.map((t) => [t.name, new Set(t.columns.map((c) => c.name))])
|
|
265
|
+
);
|
|
266
|
+
for (const rel of ctx.relationships) {
|
|
267
|
+
const sourceColumns = tableColumnSets.get(rel.table);
|
|
268
|
+
const targetColumns = tableColumnSets.get(rel.referenced_table);
|
|
269
|
+
if (sourceColumns && rel.from.some((column2) => !sourceColumns.has(column2))) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (targetColumns && rel.to.some((column2) => !targetColumns.has(column2))) {
|
|
273
|
+
continue;
|
|
254
274
|
}
|
|
275
|
+
const sourceTable = tableMap.get(rel.table);
|
|
276
|
+
const targetTable = tableMap.get(rel.referenced_table);
|
|
277
|
+
fragments.push(
|
|
278
|
+
this.#relationshipToFragment(rel, sourceTable, targetTable)
|
|
279
|
+
);
|
|
255
280
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// packages/text2sql/src/lib/adapters/groundings/indexes.grounding.ts
|
|
260
|
-
var IndexesGrounding = class extends AbstractGrounding {
|
|
261
|
-
constructor(config = {}) {
|
|
262
|
-
super("index", "indexes");
|
|
281
|
+
fragments.push(...ctx.fragments ?? []);
|
|
282
|
+
return fragments;
|
|
263
283
|
}
|
|
264
284
|
/**
|
|
265
|
-
*
|
|
266
|
-
* Annotates tables in ctx.tables with their indexes and marks indexed columns.
|
|
285
|
+
* Convert a Table to a table fragment with nested column, index, and constraint fragments.
|
|
267
286
|
*/
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
table2.indexes = await this.getIndexes(table2.name);
|
|
281
|
-
for (const index2 of table2.indexes ?? []) {
|
|
282
|
-
for (const colName of index2.columns) {
|
|
283
|
-
const column2 = table2.columns.find((c) => c.name === colName);
|
|
284
|
-
if (column2) {
|
|
285
|
-
column2.isIndexed = true;
|
|
286
|
-
}
|
|
287
|
+
#tableToFragment(t) {
|
|
288
|
+
const pkConstraint = t.constraints?.find((c) => c.type === "PRIMARY_KEY");
|
|
289
|
+
const pkColumns = new Set(pkConstraint?.columns ?? []);
|
|
290
|
+
const notNullColumns = new Set(
|
|
291
|
+
t.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
|
|
292
|
+
);
|
|
293
|
+
const defaultByColumn = /* @__PURE__ */ new Map();
|
|
294
|
+
for (const c of t.constraints?.filter((c2) => c2.type === "DEFAULT") ?? []) {
|
|
295
|
+
for (const col of c.columns ?? []) {
|
|
296
|
+
if (c.defaultValue != null) {
|
|
297
|
+
defaultByColumn.set(col, c.defaultValue);
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
const uniqueColumns = new Set(
|
|
302
|
+
t.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
|
|
303
|
+
);
|
|
304
|
+
const fkByColumn = /* @__PURE__ */ new Map();
|
|
305
|
+
for (const c of t.constraints?.filter((c2) => c2.type === "FOREIGN_KEY") ?? []) {
|
|
306
|
+
const cols = c.columns ?? [];
|
|
307
|
+
const refCols = c.referencedColumns ?? [];
|
|
308
|
+
for (let i = 0; i < cols.length; i++) {
|
|
309
|
+
const refCol = refCols[i] ?? refCols[0] ?? cols[i];
|
|
310
|
+
fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const columnFragments = t.columns.map(
|
|
314
|
+
(col) => column({
|
|
315
|
+
name: col.name,
|
|
316
|
+
type: col.type,
|
|
317
|
+
pk: pkColumns.has(col.name) || void 0,
|
|
318
|
+
fk: fkByColumn.get(col.name),
|
|
319
|
+
unique: uniqueColumns.has(col.name) || void 0,
|
|
320
|
+
notNull: notNullColumns.has(col.name) || void 0,
|
|
321
|
+
default: defaultByColumn.get(col.name),
|
|
322
|
+
indexed: col.isIndexed || void 0,
|
|
323
|
+
values: col.values,
|
|
324
|
+
stats: col.stats
|
|
325
|
+
})
|
|
326
|
+
);
|
|
327
|
+
const presentColumns = new Set(t.columns.map((c) => c.name));
|
|
328
|
+
const indexFragments = (t.indexes ?? []).filter(
|
|
329
|
+
(idx) => idx.columns.every((column2) => presentColumns.has(column2))
|
|
330
|
+
).map(
|
|
331
|
+
(idx) => index({
|
|
332
|
+
name: idx.name,
|
|
333
|
+
columns: idx.columns,
|
|
334
|
+
unique: idx.unique,
|
|
335
|
+
type: idx.type
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
const constraintFragments = (t.constraints ?? []).filter(
|
|
339
|
+
(c) => c.type === "CHECK" || c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
|
|
340
|
+
).filter(
|
|
341
|
+
(c) => !c.columns?.length || c.columns.every((column2) => presentColumns.has(column2))
|
|
342
|
+
).map(
|
|
343
|
+
(c) => constraint({
|
|
344
|
+
name: c.name,
|
|
345
|
+
type: c.type,
|
|
346
|
+
columns: c.columns,
|
|
347
|
+
definition: c.definition
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
return table({
|
|
351
|
+
name: t.name,
|
|
352
|
+
schema: t.schema,
|
|
353
|
+
rowCount: t.rowCount,
|
|
354
|
+
sizeHint: t.sizeHint,
|
|
355
|
+
columns: columnFragments,
|
|
356
|
+
indexes: indexFragments.length > 0 ? indexFragments : void 0,
|
|
357
|
+
constraints: constraintFragments.length > 0 ? constraintFragments : void 0
|
|
358
|
+
});
|
|
297
359
|
}
|
|
298
360
|
/**
|
|
299
|
-
*
|
|
300
|
-
* Writes database info to ctx.info.
|
|
361
|
+
* Convert a View to a view fragment with nested column fragments.
|
|
301
362
|
*/
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
363
|
+
#viewToFragment(v) {
|
|
364
|
+
const columnFragments = v.columns.map(
|
|
365
|
+
(col) => column({
|
|
366
|
+
name: col.name,
|
|
367
|
+
type: col.type,
|
|
368
|
+
values: col.values,
|
|
369
|
+
stats: col.stats
|
|
370
|
+
})
|
|
371
|
+
);
|
|
372
|
+
return view({
|
|
373
|
+
name: v.name,
|
|
374
|
+
schema: v.schema,
|
|
375
|
+
columns: columnFragments,
|
|
376
|
+
definition: v.definition
|
|
377
|
+
});
|
|
311
378
|
}
|
|
312
379
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
380
|
+
* Convert a Relationship to a relationship fragment.
|
|
381
|
+
* Infers cardinality from row counts if available.
|
|
315
382
|
*/
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const count = await this.getRowCount(table2.name);
|
|
329
|
-
if (count != null) {
|
|
330
|
-
table2.rowCount = count;
|
|
331
|
-
table2.sizeHint = this.#classifyRowCount(count);
|
|
383
|
+
#relationshipToFragment(rel, sourceTable, targetTable) {
|
|
384
|
+
const sourceCount = sourceTable?.rowCount;
|
|
385
|
+
const targetCount = targetTable?.rowCount;
|
|
386
|
+
let cardinality;
|
|
387
|
+
if (sourceCount != null && targetCount != null && targetCount > 0) {
|
|
388
|
+
const ratio = sourceCount / targetCount;
|
|
389
|
+
if (ratio > 5) {
|
|
390
|
+
cardinality = "many-to-one";
|
|
391
|
+
} else if (ratio < 1.2 && ratio > 0.8) {
|
|
392
|
+
cardinality = "one-to-one";
|
|
393
|
+
} else if (ratio < 0.2) {
|
|
394
|
+
cardinality = "one-to-many";
|
|
332
395
|
}
|
|
333
396
|
}
|
|
397
|
+
return relationship({
|
|
398
|
+
from: { table: rel.table, columns: rel.from },
|
|
399
|
+
to: { table: rel.referenced_table, columns: rel.to },
|
|
400
|
+
cardinality
|
|
401
|
+
});
|
|
334
402
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (count < 1e5) return "large";
|
|
343
|
-
return "huge";
|
|
403
|
+
format(sql) {
|
|
404
|
+
const decoded = this.#decodeShellEscapes(sql);
|
|
405
|
+
try {
|
|
406
|
+
return formatSql(decoded, { language: this.formatterLanguage });
|
|
407
|
+
} catch {
|
|
408
|
+
return decoded;
|
|
409
|
+
}
|
|
344
410
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// packages/text2sql/src/lib/adapters/groundings/table.grounding.ts
|
|
348
|
-
var TableGrounding = class extends AbstractGrounding {
|
|
349
|
-
#filter;
|
|
350
|
-
#columns;
|
|
351
|
-
#forward;
|
|
352
|
-
#backward;
|
|
353
|
-
constructor(config = {}) {
|
|
354
|
-
super("table", "tables");
|
|
355
|
-
this.#filter = config.filter;
|
|
356
|
-
this.#columns = config.columns;
|
|
357
|
-
this.#forward = config.forward;
|
|
358
|
-
this.#backward = config.backward;
|
|
411
|
+
#decodeShellEscapes(sql) {
|
|
412
|
+
return sql.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\([().*$])/g, "$1").replace(/\\(?=$)/gm, "");
|
|
359
413
|
}
|
|
360
|
-
|
|
361
|
-
|
|
414
|
+
#cachedAllowedEntities = null;
|
|
415
|
+
async #resolveScope() {
|
|
416
|
+
if (this.#cachedAllowedEntities) return this.#cachedAllowedEntities;
|
|
417
|
+
this.#cachedAllowedEntities = await this.resolveAllowedEntities();
|
|
418
|
+
return this.#cachedAllowedEntities;
|
|
419
|
+
}
|
|
420
|
+
async #analyzePolicy(sql) {
|
|
421
|
+
return this.#policyAnalyzer.analyze(sql, {
|
|
422
|
+
resolveAllowedEntities: () => this.#resolveScope()
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
async validate(sql) {
|
|
426
|
+
const decoded = this.#decodeShellEscapes(sql);
|
|
427
|
+
const violation = await this.#analyzePolicy(decoded);
|
|
428
|
+
if (violation?.kind === "read-only") return violation.message;
|
|
429
|
+
if (violation?.kind === "scope") return JSON.stringify(violation.payload);
|
|
430
|
+
return this.validateImpl(decoded);
|
|
431
|
+
}
|
|
432
|
+
async execute(sql) {
|
|
433
|
+
return this.executeImpl(await this.enforceExecutionPolicy(sql));
|
|
434
|
+
}
|
|
435
|
+
async enforceExecutionPolicy(sql) {
|
|
436
|
+
const decoded = this.#decodeShellEscapes(sql);
|
|
437
|
+
const violation = await this.#analyzePolicy(decoded);
|
|
438
|
+
if (violation?.kind === "read-only") {
|
|
439
|
+
throw new SQLReadOnlyError(violation.message);
|
|
440
|
+
}
|
|
441
|
+
if (violation?.kind === "scope") {
|
|
442
|
+
throw new SQLScopeError(violation.payload);
|
|
443
|
+
}
|
|
444
|
+
return decoded;
|
|
362
445
|
}
|
|
363
446
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
447
|
+
* Run an introspection query and check every row against `row`, the shape
|
|
448
|
+
* the driver returns for that query. A row of any other shape throws.
|
|
366
449
|
*/
|
|
367
|
-
async
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
type: "phase:progress",
|
|
376
|
-
phase: "tables",
|
|
377
|
-
table: name,
|
|
378
|
-
message: `Loading table ${name}...`,
|
|
379
|
-
current: index2 + 1,
|
|
380
|
-
total: seedTables.length
|
|
381
|
-
});
|
|
382
|
-
return applyColumnFilter(await this.getTable(name), this.#columns);
|
|
383
|
-
})
|
|
450
|
+
async runQuery(sql, row) {
|
|
451
|
+
const rows = z.array(row).safeParse(await this.queryRows(sql));
|
|
452
|
+
if (!rows.success) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`${this.constructor.name} introspection query returned rows of an unexpected shape:
|
|
455
|
+
${z.prettifyError(rows.error)}
|
|
456
|
+
SQL: ${sql.trim()}`,
|
|
457
|
+
{ cause: rows.error }
|
|
384
458
|
);
|
|
385
|
-
ctx.tables.push(...tables3);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const tables2 = {};
|
|
389
|
-
const allRelationships = [];
|
|
390
|
-
const seenRelationships = /* @__PURE__ */ new Set();
|
|
391
|
-
const forwardQueue = [];
|
|
392
|
-
const backwardQueue = [];
|
|
393
|
-
const forwardVisited = /* @__PURE__ */ new Set();
|
|
394
|
-
const backwardVisited = /* @__PURE__ */ new Set();
|
|
395
|
-
let loadedCount = 0;
|
|
396
|
-
for (const name of seedTables) {
|
|
397
|
-
if (forward) forwardQueue.push({ name, depth: 0 });
|
|
398
|
-
if (backward) backwardQueue.push({ name, depth: 0 });
|
|
399
459
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
});
|
|
416
|
-
tables2[name] = applyColumnFilter(
|
|
417
|
-
await this.getTable(name),
|
|
418
|
-
this.#columns
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
if (depth < forwardLimit) {
|
|
422
|
-
const rels = await this.findOutgoingRelations(name);
|
|
423
|
-
for (const rel of rels) {
|
|
424
|
-
if (!this.isRelationshipVisible(rel)) continue;
|
|
425
|
-
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
426
|
-
if (!forwardVisited.has(rel.referenced_table)) {
|
|
427
|
-
forwardQueue.push({ name: rel.referenced_table, depth: depth + 1 });
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
460
|
+
return rows.data;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* The rows in a driver's execute() result: the result itself when it is an
|
|
464
|
+
* array, otherwise the first array found at one of `paths` (dot-separated,
|
|
465
|
+
* so `recordsets.0` reads `result.recordsets[0]`).
|
|
466
|
+
*/
|
|
467
|
+
rowsFrom(result, paths) {
|
|
468
|
+
if (Array.isArray(result)) return result;
|
|
469
|
+
for (const path of paths) {
|
|
470
|
+
const rows = path.split(".").reduce(
|
|
471
|
+
(value, key) => typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0,
|
|
472
|
+
result
|
|
473
|
+
);
|
|
474
|
+
if (Array.isArray(rows)) return rows;
|
|
431
475
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (!item) break;
|
|
436
|
-
const { name, depth } = item;
|
|
437
|
-
if (backwardVisited.has(name)) continue;
|
|
438
|
-
backwardVisited.add(name);
|
|
439
|
-
if (!tables2[name]) {
|
|
440
|
-
loadedCount++;
|
|
441
|
-
ctx.onProgress({
|
|
442
|
-
type: "phase:progress",
|
|
443
|
-
phase: "tables",
|
|
444
|
-
table: name,
|
|
445
|
-
message: `Loading table ${name}...`,
|
|
446
|
-
current: loadedCount
|
|
447
|
-
});
|
|
448
|
-
tables2[name] = applyColumnFilter(
|
|
449
|
-
await this.getTable(name),
|
|
450
|
-
this.#columns
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
if (depth < backwardLimit) {
|
|
454
|
-
const rels = await this.findIncomingRelations(name);
|
|
455
|
-
for (const rel of rels) {
|
|
456
|
-
if (!this.isRelationshipVisible(rel)) continue;
|
|
457
|
-
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
458
|
-
if (!backwardVisited.has(rel.table)) {
|
|
459
|
-
backwardQueue.push({ name: rel.table, depth: depth + 1 });
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const tablesList = Object.values(tables2);
|
|
465
|
-
ctx.tables.push(...tablesList);
|
|
466
|
-
ctx.relationships.push(...allRelationships);
|
|
476
|
+
throw new Error(
|
|
477
|
+
`${this.constructor.name} execute() must return an array of rows or an object holding one at ${paths.map((path) => `result.${path}`).join(", ")}.`
|
|
478
|
+
);
|
|
467
479
|
}
|
|
468
480
|
/**
|
|
469
|
-
*
|
|
470
|
-
*
|
|
481
|
+
* Convert unknown database value to number.
|
|
482
|
+
* Handles number, bigint, and string types.
|
|
471
483
|
*/
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return filter;
|
|
484
|
+
toNumber(value) {
|
|
485
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
486
|
+
return value;
|
|
476
487
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return names;
|
|
488
|
+
if (typeof value === "bigint") {
|
|
489
|
+
return Number(value);
|
|
480
490
|
}
|
|
481
|
-
if (
|
|
482
|
-
|
|
491
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
492
|
+
const parsed = Number(value);
|
|
493
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
483
494
|
}
|
|
484
|
-
return
|
|
495
|
+
return void 0;
|
|
485
496
|
}
|
|
486
497
|
/**
|
|
487
|
-
*
|
|
498
|
+
* Parse a potentially qualified table name into schema and table parts.
|
|
488
499
|
*/
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
all.push(rel);
|
|
500
|
+
parseTableName(name) {
|
|
501
|
+
if (name.includes(".")) {
|
|
502
|
+
const [schema, ...rest] = name.split(".");
|
|
503
|
+
return { schema, table: rest.join(".") };
|
|
494
504
|
}
|
|
505
|
+
return { schema: this.defaultSchema ?? "", table: name };
|
|
495
506
|
}
|
|
496
|
-
|
|
497
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Escape a string value for use in SQL string literals (single quotes).
|
|
509
|
+
* Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
|
|
510
|
+
*/
|
|
511
|
+
escapeString(value) {
|
|
512
|
+
return value.replace(/'/g, "''");
|
|
498
513
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
514
|
+
/**
|
|
515
|
+
* Build a SQL filter clause to include/exclude schemas.
|
|
516
|
+
* @param columnName - The schema column name (e.g., 'TABLE_SCHEMA')
|
|
517
|
+
* @param allowedSchemas - If provided, filter to these schemas only
|
|
518
|
+
*/
|
|
519
|
+
buildSchemaFilter(columnName, allowedSchemas) {
|
|
520
|
+
if (allowedSchemas && allowedSchemas.length > 0) {
|
|
521
|
+
const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
|
|
522
|
+
return `AND ${columnName} IN (${values})`;
|
|
523
|
+
}
|
|
524
|
+
if (this.systemSchemas.length > 0) {
|
|
525
|
+
const values = this.systemSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
|
|
526
|
+
return `AND ${columnName} NOT IN (${values})`;
|
|
527
|
+
}
|
|
528
|
+
return "";
|
|
505
529
|
}
|
|
506
530
|
};
|
|
507
531
|
|
|
508
|
-
// packages/text2sql/src/lib/adapters/
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
this.#filter = config.filter;
|
|
516
|
-
this.#columns = config.columns;
|
|
517
|
-
this.includeDefinition = config.includeDefinition ?? true;
|
|
532
|
+
// packages/text2sql/src/lib/adapters/duckdb/duckdb.groundings.ts
|
|
533
|
+
import { z as z3 } from "zod";
|
|
534
|
+
|
|
535
|
+
// packages/text2sql/src/lib/adapters/groundings/abstract.grounding.ts
|
|
536
|
+
function filterColumns(columns, filter) {
|
|
537
|
+
if (Array.isArray(filter)) {
|
|
538
|
+
return columns.filter((col) => filter.includes(col.name));
|
|
518
539
|
}
|
|
519
|
-
|
|
520
|
-
|
|
540
|
+
if (filter instanceof RegExp) {
|
|
541
|
+
return columns.filter((col) => filter.test(col.name));
|
|
521
542
|
}
|
|
543
|
+
return columns.filter((col) => filter(col.name));
|
|
544
|
+
}
|
|
545
|
+
function applyColumnFilter(entity, columnsConfig) {
|
|
546
|
+
if (!columnsConfig) return entity;
|
|
547
|
+
const filter = columnsConfig[entity.name];
|
|
548
|
+
if (!filter) return entity;
|
|
549
|
+
return { ...entity, columns: filterColumns(entity.columns, filter) };
|
|
550
|
+
}
|
|
551
|
+
var AbstractGrounding = class {
|
|
522
552
|
/**
|
|
523
|
-
*
|
|
524
|
-
* Writes discovered views to the context.
|
|
553
|
+
* Grounding identifier for debugging/logging.
|
|
525
554
|
*/
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
type: "phase:progress",
|
|
532
|
-
phase: "views",
|
|
533
|
-
table: name,
|
|
534
|
-
message: `Loading view ${name}...`,
|
|
535
|
-
current: index2 + 1,
|
|
536
|
-
total: viewNames.length
|
|
537
|
-
});
|
|
538
|
-
return applyColumnFilter(await this.getView(name), this.#columns);
|
|
539
|
-
})
|
|
540
|
-
);
|
|
541
|
-
ctx.views.push(...views2);
|
|
555
|
+
name;
|
|
556
|
+
phase;
|
|
557
|
+
constructor(name, phase) {
|
|
558
|
+
this.name = name;
|
|
559
|
+
this.phase = phase;
|
|
542
560
|
}
|
|
543
561
|
/**
|
|
544
|
-
*
|
|
545
|
-
*
|
|
562
|
+
* Populate ctx with the tables/views this grounding makes available for scope
|
|
563
|
+
* resolution. Annotators decorate entities other groundings produced and add
|
|
564
|
+
* no membership, so the default is a no-op; entity-producing groundings
|
|
565
|
+
* override it. This lets scope resolution skip the annotators' metadata scans.
|
|
566
|
+
*
|
|
567
|
+
* @param _ctx - Shared context for accumulating entity membership
|
|
546
568
|
*/
|
|
547
|
-
async
|
|
548
|
-
const filter = this.#filter;
|
|
549
|
-
if (Array.isArray(filter)) {
|
|
550
|
-
return filter;
|
|
551
|
-
}
|
|
552
|
-
const names = await this.getAllViewNames();
|
|
553
|
-
if (!filter) {
|
|
554
|
-
return names;
|
|
555
|
-
}
|
|
556
|
-
if (filter instanceof RegExp) {
|
|
557
|
-
return names.filter((name) => filter.test(name));
|
|
558
|
-
}
|
|
559
|
-
return names.filter(filter);
|
|
569
|
+
async contributeEntities(_ctx) {
|
|
560
570
|
}
|
|
561
571
|
};
|
|
562
572
|
|
|
563
|
-
// packages/text2sql/src/lib/adapters/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
function parseDuckDBRelationName(name) {
|
|
569
|
-
const parts = splitIdentifierPath(name);
|
|
570
|
-
if (parts.length === 1) return { table: parts[0] };
|
|
571
|
-
if (parts.length === 2) {
|
|
572
|
-
return { schema: parts[0], table: parts[1] };
|
|
573
|
-
}
|
|
574
|
-
if (parts.length === 3) {
|
|
575
|
-
return { catalog: parts[0], schema: parts[1], table: parts[2] };
|
|
573
|
+
// packages/text2sql/src/lib/adapters/groundings/column-stats.grounding.ts
|
|
574
|
+
var ColumnStatsGrounding = class extends AbstractGrounding {
|
|
575
|
+
constructor(config = {}) {
|
|
576
|
+
super("columnStats", "column_stats");
|
|
576
577
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
578
|
+
/**
|
|
579
|
+
* Execute the grounding process.
|
|
580
|
+
* Annotates columns in ctx.tables and ctx.views with statistics.
|
|
581
|
+
*/
|
|
582
|
+
async execute(ctx) {
|
|
583
|
+
const allContainers = [...ctx.tables, ...ctx.views];
|
|
584
|
+
const total = allContainers.length;
|
|
585
|
+
for (let i = 0; i < allContainers.length; i++) {
|
|
586
|
+
const container = allContainers[i];
|
|
587
|
+
ctx.onProgress({
|
|
588
|
+
type: "phase:progress",
|
|
589
|
+
phase: "column_stats",
|
|
590
|
+
table: container.name,
|
|
591
|
+
message: `Collecting stats for ${container.name}...`,
|
|
592
|
+
current: i + 1,
|
|
593
|
+
total
|
|
594
|
+
});
|
|
595
|
+
for (const column2 of container.columns) {
|
|
596
|
+
try {
|
|
597
|
+
const stats = await this.collectStats(container.name, column2);
|
|
598
|
+
if (stats) {
|
|
599
|
+
column2.stats = stats;
|
|
600
|
+
}
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.warn(
|
|
603
|
+
"Error collecting stats for",
|
|
604
|
+
container.name,
|
|
605
|
+
column2.name,
|
|
606
|
+
error
|
|
607
|
+
);
|
|
608
|
+
}
|
|
591
609
|
}
|
|
592
|
-
} else if (character === "." && !quoted) {
|
|
593
|
-
if (!part) throw new Error(`Invalid DuckDB relation: ${input}`);
|
|
594
|
-
parts.push(part);
|
|
595
|
-
part = "";
|
|
596
|
-
} else {
|
|
597
|
-
part += character;
|
|
598
610
|
}
|
|
599
611
|
}
|
|
600
|
-
|
|
601
|
-
parts.push(part);
|
|
602
|
-
return parts;
|
|
603
|
-
}
|
|
612
|
+
};
|
|
604
613
|
|
|
605
|
-
// packages/text2sql/src/lib/adapters/
|
|
606
|
-
var
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
var DuckDBTableGrounding = class extends TableGrounding {
|
|
614
|
-
#adapter;
|
|
615
|
-
constructor(adapter, config = {}) {
|
|
616
|
-
super(config);
|
|
617
|
-
this.#adapter = adapter;
|
|
614
|
+
// packages/text2sql/src/lib/adapters/groundings/column-values.grounding.ts
|
|
615
|
+
var ColumnValuesGrounding = class extends AbstractGrounding {
|
|
616
|
+
lowCardinalityLimit;
|
|
617
|
+
maxValueLength;
|
|
618
|
+
constructor(config = {}) {
|
|
619
|
+
super("columnValues", "column_values");
|
|
620
|
+
this.lowCardinalityLimit = config.lowCardinalityLimit ?? 20;
|
|
621
|
+
this.maxValueLength = config.maxValueLength ?? 100;
|
|
618
622
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
623
|
+
/**
|
|
624
|
+
* Get values for native ENUM type columns.
|
|
625
|
+
* Return undefined if column is not an ENUM type.
|
|
626
|
+
* Default implementation returns undefined (no native ENUM support).
|
|
627
|
+
*/
|
|
628
|
+
async collectEnumValues(_tableName, _column) {
|
|
629
|
+
return void 0;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Parse CHECK constraint for enum-like IN clause.
|
|
633
|
+
* Extracts values from patterns like:
|
|
634
|
+
* - CHECK (status IN ('active', 'inactive'))
|
|
635
|
+
* - CHECK ((status)::text = ANY (ARRAY['a'::text, 'b'::text]))
|
|
636
|
+
* - CHECK (status = 'active' OR status = 'inactive')
|
|
637
|
+
*/
|
|
638
|
+
parseCheckConstraint(constraint2, columnName) {
|
|
639
|
+
if (constraint2.type !== "CHECK" || !constraint2.definition) {
|
|
640
|
+
return void 0;
|
|
634
641
|
}
|
|
635
|
-
|
|
642
|
+
if (constraint2.columns && !constraint2.columns.includes(columnName)) {
|
|
643
|
+
return void 0;
|
|
644
|
+
}
|
|
645
|
+
const def = constraint2.definition;
|
|
646
|
+
const escapedCol = this.escapeRegex(columnName);
|
|
647
|
+
const colPattern = `(?:\\(?\\(?${escapedCol}\\)?(?:::(?:text|varchar|character varying))?\\)?)`;
|
|
648
|
+
const inMatch = def.match(
|
|
649
|
+
new RegExp(`${colPattern}\\s+IN\\s*\\(([^)]+)\\)`, "i")
|
|
650
|
+
);
|
|
651
|
+
if (inMatch) {
|
|
652
|
+
return this.extractStringValues(inMatch[1]);
|
|
653
|
+
}
|
|
654
|
+
const anyMatch = def.match(
|
|
655
|
+
new RegExp(
|
|
656
|
+
`${colPattern}\\s*=\\s*ANY\\s*\\(\\s*(?:ARRAY)?\\s*\\[([^\\]]+)\\]`,
|
|
657
|
+
"i"
|
|
658
|
+
)
|
|
659
|
+
);
|
|
660
|
+
if (anyMatch) {
|
|
661
|
+
return this.extractStringValues(anyMatch[1]);
|
|
662
|
+
}
|
|
663
|
+
const orPattern = new RegExp(
|
|
664
|
+
`\\b${this.escapeRegex(columnName)}\\b\\s*=\\s*'([^']*)'`,
|
|
665
|
+
"gi"
|
|
666
|
+
);
|
|
667
|
+
const orMatches = [...def.matchAll(orPattern)];
|
|
668
|
+
if (orMatches.length >= 2) {
|
|
669
|
+
return orMatches.map((m) => m[1]);
|
|
670
|
+
}
|
|
671
|
+
return void 0;
|
|
636
672
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
`);
|
|
648
|
-
return rows.map((row) => {
|
|
649
|
-
if (typeof row.database_name !== "string" || typeof row.schema_name !== "string" || typeof row.table_name !== "string") {
|
|
650
|
-
throw new Error("DuckDB table catalog returned an unknown row shape.");
|
|
651
|
-
}
|
|
652
|
-
return this.#adapter.formatRelationName(
|
|
653
|
-
row.database_name,
|
|
654
|
-
row.schema_name,
|
|
655
|
-
row.table_name
|
|
656
|
-
);
|
|
657
|
-
});
|
|
673
|
+
/**
|
|
674
|
+
* Extract string values from a comma-separated list.
|
|
675
|
+
*/
|
|
676
|
+
extractStringValues(input) {
|
|
677
|
+
const values = [];
|
|
678
|
+
const matches = input.matchAll(/'([^']*)'/g);
|
|
679
|
+
for (const match of matches) {
|
|
680
|
+
values.push(match[1]);
|
|
681
|
+
}
|
|
682
|
+
return values.length > 0 ? values : void 0;
|
|
658
683
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
684
|
+
/**
|
|
685
|
+
* Escape special regex characters in a string.
|
|
686
|
+
*/
|
|
687
|
+
escapeRegex(str) {
|
|
688
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Get the table from context by name.
|
|
692
|
+
*/
|
|
693
|
+
getTable(ctx, name) {
|
|
694
|
+
return ctx.tables.find((t) => t.name === name);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Execute the grounding process.
|
|
698
|
+
* Annotates columns in ctx.tables and ctx.views with values.
|
|
699
|
+
*/
|
|
700
|
+
async execute(ctx) {
|
|
701
|
+
const allContainers = [...ctx.tables, ...ctx.views];
|
|
702
|
+
const total = allContainers.length;
|
|
703
|
+
for (let i = 0; i < allContainers.length; i++) {
|
|
704
|
+
const container = allContainers[i];
|
|
705
|
+
ctx.onProgress({
|
|
706
|
+
type: "phase:progress",
|
|
707
|
+
phase: "column_values",
|
|
708
|
+
table: container.name,
|
|
709
|
+
message: `Collecting column values for ${container.name}...`,
|
|
710
|
+
current: i + 1,
|
|
711
|
+
total
|
|
712
|
+
});
|
|
713
|
+
const table2 = this.getTable(ctx, container.name);
|
|
714
|
+
for (const column2 of container.columns) {
|
|
715
|
+
try {
|
|
716
|
+
const result = await this.resolveColumnValues(
|
|
717
|
+
container.name,
|
|
718
|
+
column2,
|
|
719
|
+
table2?.constraints
|
|
720
|
+
);
|
|
721
|
+
if (result) {
|
|
722
|
+
column2.kind = result.kind;
|
|
723
|
+
column2.values = result.values;
|
|
724
|
+
}
|
|
725
|
+
} catch (error) {
|
|
726
|
+
console.warn(
|
|
727
|
+
"Error collecting column values for",
|
|
728
|
+
container.name,
|
|
729
|
+
column2.name,
|
|
730
|
+
error
|
|
677
731
|
);
|
|
678
732
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
682
735
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const rows = await foreignKeys(this.#adapter, relation, "outgoing");
|
|
686
|
-
return rows.map((row) => relationshipFromRow(this.#adapter, relation, row));
|
|
736
|
+
exceedsMaxValueLength(values) {
|
|
737
|
+
return values.some((v) => v.length > this.maxValueLength);
|
|
687
738
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
739
|
+
/**
|
|
740
|
+
* Resolve column values from all sources in priority order.
|
|
741
|
+
*/
|
|
742
|
+
async resolveColumnValues(tableName, column2, constraints2) {
|
|
743
|
+
const enumValues = await this.collectEnumValues(tableName, column2);
|
|
744
|
+
if (enumValues?.length && !this.exceedsMaxValueLength(enumValues)) {
|
|
745
|
+
return { kind: "Enum", values: enumValues };
|
|
746
|
+
}
|
|
747
|
+
if (constraints2) {
|
|
748
|
+
for (const constraint2 of constraints2) {
|
|
749
|
+
const checkValues = this.parseCheckConstraint(constraint2, column2.name);
|
|
750
|
+
if (checkValues?.length && !this.exceedsMaxValueLength(checkValues)) {
|
|
751
|
+
return { kind: "Enum", values: checkValues };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
const lowCardValues = await this.collectLowCardinality(tableName, column2);
|
|
756
|
+
if (lowCardValues?.length && !this.exceedsMaxValueLength(lowCardValues)) {
|
|
757
|
+
return { kind: "LowCardinality", values: lowCardValues };
|
|
758
|
+
}
|
|
759
|
+
return void 0;
|
|
699
760
|
}
|
|
700
761
|
};
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
762
|
+
|
|
763
|
+
// packages/text2sql/src/lib/adapters/groundings/constraint.grounding.ts
|
|
764
|
+
var ConstraintGrounding = class extends AbstractGrounding {
|
|
765
|
+
constructor(config = {}) {
|
|
766
|
+
super("constraint", "constraints");
|
|
706
767
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
768
|
+
/**
|
|
769
|
+
* Execute the grounding process.
|
|
770
|
+
* Annotates tables in ctx.tables with their constraints.
|
|
771
|
+
*/
|
|
772
|
+
async execute(ctx) {
|
|
773
|
+
const total = ctx.tables.length;
|
|
774
|
+
for (let i = 0; i < ctx.tables.length; i++) {
|
|
775
|
+
const table2 = ctx.tables[i];
|
|
776
|
+
ctx.onProgress({
|
|
777
|
+
type: "phase:progress",
|
|
778
|
+
phase: "constraints",
|
|
779
|
+
table: table2.name,
|
|
780
|
+
message: `Loading constraints for ${table2.name}...`,
|
|
781
|
+
current: i + 1,
|
|
782
|
+
total
|
|
783
|
+
});
|
|
784
|
+
try {
|
|
785
|
+
table2.constraints = await this.getConstraints(table2.name);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.warn("Error collecting constraints for", table2.name, error);
|
|
723
788
|
}
|
|
724
|
-
}
|
|
789
|
+
}
|
|
725
790
|
}
|
|
726
791
|
};
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
792
|
+
|
|
793
|
+
// packages/text2sql/src/lib/adapters/groundings/indexes.grounding.ts
|
|
794
|
+
var IndexesGrounding = class extends AbstractGrounding {
|
|
795
|
+
constructor(config = {}) {
|
|
796
|
+
super("index", "indexes");
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Execute the grounding process.
|
|
800
|
+
* Annotates tables in ctx.tables with their indexes and marks indexed columns.
|
|
801
|
+
*/
|
|
802
|
+
async execute(ctx) {
|
|
803
|
+
const total = ctx.tables.length;
|
|
804
|
+
for (let i = 0; i < ctx.tables.length; i++) {
|
|
805
|
+
const table2 = ctx.tables[i];
|
|
806
|
+
ctx.onProgress({
|
|
807
|
+
type: "phase:progress",
|
|
808
|
+
phase: "indexes",
|
|
809
|
+
table: table2.name,
|
|
810
|
+
message: `Loading indexes for ${table2.name}...`,
|
|
811
|
+
current: i + 1,
|
|
812
|
+
total
|
|
813
|
+
});
|
|
814
|
+
table2.indexes = await this.getIndexes(table2.name);
|
|
815
|
+
for (const index2 of table2.indexes ?? []) {
|
|
816
|
+
for (const colName of index2.columns) {
|
|
817
|
+
const column2 = table2.columns.find((c) => c.name === colName);
|
|
818
|
+
if (column2) {
|
|
819
|
+
column2.isIndexed = true;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
747
822
|
}
|
|
748
823
|
}
|
|
749
|
-
return result;
|
|
750
|
-
}
|
|
751
|
-
async getAllViewNames() {
|
|
752
|
-
const { catalogs, schemas } = await this.#adapter.scopedNamespaces();
|
|
753
|
-
const rows = await this.#adapter.runQuery(`
|
|
754
|
-
SELECT database_name, schema_name, view_name
|
|
755
|
-
FROM duckdb_views()
|
|
756
|
-
WHERE NOT internal
|
|
757
|
-
AND NOT temporary
|
|
758
|
-
AND database_name IN (${sqlLiterals(this.#adapter, catalogs)})
|
|
759
|
-
AND schema_name IN (${sqlLiterals(this.#adapter, schemas)})
|
|
760
|
-
ORDER BY database_name, schema_name, view_name
|
|
761
|
-
`);
|
|
762
|
-
return rows.map(
|
|
763
|
-
(row) => this.#adapter.formatRelationName(
|
|
764
|
-
requiredString(row.database_name, "database_name"),
|
|
765
|
-
requiredString(row.schema_name, "schema_name"),
|
|
766
|
-
requiredString(row.view_name, "view_name")
|
|
767
|
-
)
|
|
768
|
-
);
|
|
769
|
-
}
|
|
770
|
-
async getView(viewName) {
|
|
771
|
-
const relation = await this.#adapter.resolveRelationName(viewName);
|
|
772
|
-
const where = relationPredicate(this.#adapter, relation);
|
|
773
|
-
const [columns, definitions] = await Promise.all([
|
|
774
|
-
this.#adapter.runQuery(`
|
|
775
|
-
SELECT column_name, data_type
|
|
776
|
-
FROM duckdb_columns()
|
|
777
|
-
WHERE ${where}
|
|
778
|
-
ORDER BY column_index
|
|
779
|
-
`),
|
|
780
|
-
this.includeDefinition ? this.#adapter.runQuery(`
|
|
781
|
-
SELECT sql
|
|
782
|
-
FROM duckdb_views()
|
|
783
|
-
WHERE database_name = '${this.#adapter.escapeString(relation.catalog)}'
|
|
784
|
-
AND schema_name = '${this.#adapter.escapeString(relation.schema)}'
|
|
785
|
-
AND view_name = '${this.#adapter.escapeString(relation.table)}'
|
|
786
|
-
`) : Promise.resolve([])
|
|
787
|
-
]);
|
|
788
|
-
return {
|
|
789
|
-
name: this.#adapter.formatRelationName(
|
|
790
|
-
relation.catalog,
|
|
791
|
-
relation.schema,
|
|
792
|
-
relation.table
|
|
793
|
-
),
|
|
794
|
-
schema: formatDuckDBIdentifierPath([relation.catalog, relation.schema]),
|
|
795
|
-
rawName: relation.table,
|
|
796
|
-
definition: typeof definitions[0]?.sql === "string" ? definitions[0].sql : void 0,
|
|
797
|
-
columns: columns.map(columnFromRow)
|
|
798
|
-
};
|
|
799
824
|
}
|
|
800
825
|
};
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
826
|
+
|
|
827
|
+
// packages/text2sql/src/lib/adapters/groundings/info.grounding.ts
|
|
828
|
+
var InfoGrounding = class extends AbstractGrounding {
|
|
829
|
+
constructor(config = {}) {
|
|
830
|
+
super("dialectInfo", "info");
|
|
806
831
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
constraint_type,
|
|
814
|
-
expression,
|
|
815
|
-
constraint_column_names,
|
|
816
|
-
referenced_table,
|
|
817
|
-
referenced_column_names
|
|
818
|
-
FROM duckdb_constraints()
|
|
819
|
-
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
820
|
-
ORDER BY constraint_index
|
|
821
|
-
`),
|
|
822
|
-
this.#adapter.runQuery(`
|
|
823
|
-
SELECT column_name, column_default
|
|
824
|
-
FROM duckdb_columns()
|
|
825
|
-
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
826
|
-
AND column_default IS NOT NULL
|
|
827
|
-
ORDER BY column_index
|
|
828
|
-
`)
|
|
829
|
-
]);
|
|
830
|
-
const constraints2 = rows.map(
|
|
831
|
-
(row) => constraintFromRow(this.#adapter, relation, row)
|
|
832
|
-
);
|
|
833
|
-
for (const column2 of columns) {
|
|
834
|
-
if (typeof column2.column_name === "string" && typeof column2.column_default === "string") {
|
|
835
|
-
constraints2.push({
|
|
836
|
-
name: `${relation.table}_${column2.column_name}_default`,
|
|
837
|
-
type: "DEFAULT",
|
|
838
|
-
columns: [column2.column_name],
|
|
839
|
-
defaultValue: column2.column_default
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
return constraints2;
|
|
832
|
+
/**
|
|
833
|
+
* Execute the grounding process.
|
|
834
|
+
* Writes database info to ctx.info.
|
|
835
|
+
*/
|
|
836
|
+
async execute(ctx) {
|
|
837
|
+
ctx.info = await this.collectInfo();
|
|
844
838
|
}
|
|
845
839
|
};
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
840
|
+
|
|
841
|
+
// packages/text2sql/src/lib/adapters/groundings/row-count.grounding.ts
|
|
842
|
+
var RowCountGrounding = class extends AbstractGrounding {
|
|
843
|
+
constructor(config = {}) {
|
|
844
|
+
super("rowCount", "row_counts");
|
|
851
845
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const tableColumns = columnRows.map(
|
|
873
|
-
(row) => requiredString(row.column_name, "column_name")
|
|
874
|
-
);
|
|
875
|
-
return rows.flatMap((row) => {
|
|
876
|
-
if (typeof row.index_name !== "string") {
|
|
877
|
-
throw new Error("DuckDB index catalog returned an unknown row shape.");
|
|
878
|
-
}
|
|
879
|
-
if (!Array.isArray(row.expressions) || row.expressions.some((expression) => typeof expression !== "string")) {
|
|
880
|
-
return [];
|
|
846
|
+
/**
|
|
847
|
+
* Execute the grounding process.
|
|
848
|
+
* Annotates tables in ctx.tables with row counts and size hints.
|
|
849
|
+
*/
|
|
850
|
+
async execute(ctx) {
|
|
851
|
+
const total = ctx.tables.length;
|
|
852
|
+
for (let i = 0; i < ctx.tables.length; i++) {
|
|
853
|
+
const table2 = ctx.tables[i];
|
|
854
|
+
ctx.onProgress({
|
|
855
|
+
type: "phase:progress",
|
|
856
|
+
phase: "row_counts",
|
|
857
|
+
table: table2.name,
|
|
858
|
+
message: `Counting rows in ${table2.name}...`,
|
|
859
|
+
current: i + 1,
|
|
860
|
+
total
|
|
861
|
+
});
|
|
862
|
+
const count = await this.getRowCount(table2.name);
|
|
863
|
+
if (count != null) {
|
|
864
|
+
table2.rowCount = count;
|
|
865
|
+
table2.sizeHint = this.#classifyRowCount(count);
|
|
881
866
|
}
|
|
882
|
-
|
|
883
|
-
(expression) => tableColumns.find(
|
|
884
|
-
(column2) => expression === column2 || expression === this.#adapter.quoteIdentifier(column2)
|
|
885
|
-
)
|
|
886
|
-
);
|
|
887
|
-
return columns.length > 0 && columns.every((column2) => column2 !== void 0) ? [
|
|
888
|
-
{
|
|
889
|
-
name: row.index_name,
|
|
890
|
-
columns,
|
|
891
|
-
unique: row.is_unique === true,
|
|
892
|
-
type: row.is_primary === true ? "PRIMARY" : void 0
|
|
893
|
-
}
|
|
894
|
-
] : [];
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
var DuckDBRowCountGrounding = class extends RowCountGrounding {
|
|
899
|
-
#adapter;
|
|
900
|
-
constructor(adapter, config = {}) {
|
|
901
|
-
super(config);
|
|
902
|
-
this.#adapter = adapter;
|
|
867
|
+
}
|
|
903
868
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
869
|
+
/**
|
|
870
|
+
* Classify row count into a size hint category.
|
|
871
|
+
*/
|
|
872
|
+
#classifyRowCount(count) {
|
|
873
|
+
if (count < 100) return "tiny";
|
|
874
|
+
if (count < 1e3) return "small";
|
|
875
|
+
if (count < 1e4) return "medium";
|
|
876
|
+
if (count < 1e5) return "large";
|
|
877
|
+
return "huge";
|
|
912
878
|
}
|
|
913
879
|
};
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
880
|
+
|
|
881
|
+
// packages/text2sql/src/lib/adapters/groundings/rows.ts
|
|
882
|
+
import { z as z2 } from "zod";
|
|
883
|
+
var nameRow = z2.object({ name: z2.string() });
|
|
884
|
+
var versionRow = z2.object({ version: z2.string() });
|
|
885
|
+
var databaseRow = z2.object({ db: z2.string() });
|
|
886
|
+
var viewDefinitionRow = z2.object({
|
|
887
|
+
definition: z2.string().nullable()
|
|
888
|
+
});
|
|
889
|
+
var distinctValueRow = z2.object({ value: z2.unknown() });
|
|
890
|
+
var textValueRow = z2.object({ value: z2.string() });
|
|
891
|
+
var numericValue = z2.union([z2.number(), z2.bigint(), z2.string()]);
|
|
892
|
+
|
|
893
|
+
// packages/text2sql/src/lib/adapters/groundings/table.grounding.ts
|
|
894
|
+
var TableGrounding = class extends AbstractGrounding {
|
|
895
|
+
#filter;
|
|
896
|
+
#columns;
|
|
897
|
+
#forward;
|
|
898
|
+
#backward;
|
|
899
|
+
constructor(config = {}) {
|
|
900
|
+
super("table", "tables");
|
|
901
|
+
this.#filter = config.filter;
|
|
902
|
+
this.#columns = config.columns;
|
|
903
|
+
this.#forward = config.forward;
|
|
904
|
+
this.#backward = config.backward;
|
|
919
905
|
}
|
|
920
|
-
async
|
|
921
|
-
|
|
922
|
-
return void 0;
|
|
923
|
-
}
|
|
924
|
-
const relation = this.#adapter.quoteRelationName(tableName);
|
|
925
|
-
const identifier = this.#adapter.quoteIdentifier(column2.name);
|
|
926
|
-
const rows = await this.#adapter.runQuery(`
|
|
927
|
-
SELECT
|
|
928
|
-
CAST(MIN(${identifier}) AS VARCHAR) AS min_value,
|
|
929
|
-
CAST(MAX(${identifier}) AS VARCHAR) AS max_value,
|
|
930
|
-
CASE
|
|
931
|
-
WHEN COUNT(*) = 0 THEN NULL
|
|
932
|
-
ELSE COUNT(*) FILTER (WHERE ${identifier} IS NULL)::DOUBLE / COUNT(*)
|
|
933
|
-
END AS null_fraction,
|
|
934
|
-
COUNT(DISTINCT ${identifier}) AS n_distinct
|
|
935
|
-
FROM ${relation}
|
|
936
|
-
`);
|
|
937
|
-
const row = rows[0];
|
|
938
|
-
if (!row) return void 0;
|
|
939
|
-
return {
|
|
940
|
-
min: typeof row.min_value === "string" ? row.min_value : void 0,
|
|
941
|
-
max: typeof row.max_value === "string" ? row.max_value : void 0,
|
|
942
|
-
nullFraction: this.#adapter.toNumber(row.null_fraction),
|
|
943
|
-
nDistinct: this.#adapter.toNumber(row.n_distinct)
|
|
944
|
-
};
|
|
906
|
+
async contributeEntities(ctx) {
|
|
907
|
+
await this.execute(ctx);
|
|
945
908
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
return void 0;
|
|
971
|
-
}
|
|
972
|
-
if (rows.some((row) => typeof row.value !== "string")) return void 0;
|
|
973
|
-
return rows.map((row) => row.value);
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
function sqlLiterals(adapter, values) {
|
|
977
|
-
return values.map((value) => `'${adapter.escapeString(value)}'`).join(", ");
|
|
978
|
-
}
|
|
979
|
-
function relationPredicate(adapter, relation) {
|
|
980
|
-
return `database_name = '${adapter.escapeString(relation.catalog)}'
|
|
981
|
-
AND schema_name = '${adapter.escapeString(relation.schema)}'
|
|
982
|
-
AND table_name = '${adapter.escapeString(relation.table)}'`;
|
|
983
|
-
}
|
|
984
|
-
function columnFromRow(row) {
|
|
985
|
-
return {
|
|
986
|
-
name: requiredString(row.column_name, "column_name"),
|
|
987
|
-
type: requiredString(row.data_type, "data_type")
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
async function foreignKeys(adapter, relation, direction) {
|
|
991
|
-
const predicate = direction === "outgoing" ? `table_name = '${adapter.escapeString(relation.table)}'` : `referenced_table = '${adapter.escapeString(relation.table)}'`;
|
|
992
|
-
return adapter.runQuery(`
|
|
993
|
-
SELECT
|
|
994
|
-
table_name,
|
|
995
|
-
constraint_name,
|
|
996
|
-
constraint_type,
|
|
997
|
-
expression,
|
|
998
|
-
constraint_column_names,
|
|
999
|
-
referenced_table,
|
|
1000
|
-
referenced_column_names
|
|
1001
|
-
FROM duckdb_constraints()
|
|
1002
|
-
WHERE database_name = '${adapter.escapeString(relation.catalog)}'
|
|
1003
|
-
AND schema_name = '${adapter.escapeString(relation.schema)}'
|
|
1004
|
-
AND constraint_type = 'FOREIGN KEY'
|
|
1005
|
-
AND ${predicate}
|
|
1006
|
-
ORDER BY table_name, constraint_index
|
|
1007
|
-
`);
|
|
1008
|
-
}
|
|
1009
|
-
function relationshipFromRow(adapter, source, row) {
|
|
1010
|
-
return {
|
|
1011
|
-
table: adapter.formatRelationName(
|
|
1012
|
-
source.catalog,
|
|
1013
|
-
source.schema,
|
|
1014
|
-
source.table
|
|
1015
|
-
),
|
|
1016
|
-
from: stringArray(row.constraint_column_names, "constraint_column_names"),
|
|
1017
|
-
referenced_table: adapter.formatRelationName(
|
|
1018
|
-
source.catalog,
|
|
1019
|
-
source.schema,
|
|
1020
|
-
requiredString(row.referenced_table, "referenced_table")
|
|
1021
|
-
),
|
|
1022
|
-
to: stringArray(row.referenced_column_names, "referenced_column_names")
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
function constraintFromRow(adapter, relation, row) {
|
|
1026
|
-
const type = requiredString(row.constraint_type, "constraint_type");
|
|
1027
|
-
const mapped = CONSTRAINT_TYPES.get(type);
|
|
1028
|
-
if (!mapped) throw new Error(`Unknown DuckDB constraint type: ${type}`);
|
|
1029
|
-
const referenced = mapped === "FOREIGN_KEY" ? adapter.formatRelationName(
|
|
1030
|
-
relation.catalog,
|
|
1031
|
-
relation.schema,
|
|
1032
|
-
requiredString(row.referenced_table, "referenced_table")
|
|
1033
|
-
) : void 0;
|
|
1034
|
-
return {
|
|
1035
|
-
name: requiredString(row.constraint_name, "constraint_name"),
|
|
1036
|
-
type: mapped,
|
|
1037
|
-
columns: stringArray(
|
|
1038
|
-
row.constraint_column_names,
|
|
1039
|
-
"constraint_column_names"
|
|
1040
|
-
),
|
|
1041
|
-
definition: mapped === "CHECK" && typeof row.expression === "string" ? row.expression : void 0,
|
|
1042
|
-
referencedTable: referenced,
|
|
1043
|
-
referencedColumns: mapped === "FOREIGN_KEY" ? stringArray(row.referenced_column_names, "referenced_column_names") : void 0
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
function parseEnumLabels(type) {
|
|
1047
|
-
const body = /^ENUM\((.*)\)$/i.exec(type)?.[1];
|
|
1048
|
-
if (body === void 0) return void 0;
|
|
1049
|
-
const labels = [...body.matchAll(/'((?:''|[^'])*)'/g)].map(
|
|
1050
|
-
(match) => match[1].replaceAll("''", "'")
|
|
1051
|
-
);
|
|
1052
|
-
return labels.length > 0 ? labels : void 0;
|
|
1053
|
-
}
|
|
1054
|
-
function requiredString(value, field) {
|
|
1055
|
-
if (typeof value !== "string") {
|
|
1056
|
-
throw new Error(`DuckDB catalog field ${field} must be a string.`);
|
|
1057
|
-
}
|
|
1058
|
-
return value;
|
|
1059
|
-
}
|
|
1060
|
-
function stringArray(value, field) {
|
|
1061
|
-
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
1062
|
-
throw new Error(`DuckDB catalog field ${field} must be a string array.`);
|
|
1063
|
-
}
|
|
1064
|
-
return value;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
1068
|
-
import { format as formatSql } from "sql-formatter";
|
|
1069
|
-
|
|
1070
|
-
// packages/text2sql/src/lib/agents/exceptions.ts
|
|
1071
|
-
var sqlValidationMarker = /* @__PURE__ */ Symbol("SQLValidationError");
|
|
1072
|
-
var unanswerableSqlMarker = /* @__PURE__ */ Symbol("UnanswerableSQLError");
|
|
1073
|
-
var sqlScopeMarker = /* @__PURE__ */ Symbol("SQLScopeError");
|
|
1074
|
-
var sqlReadOnlyMarker = /* @__PURE__ */ Symbol("SQLReadOnlyError");
|
|
1075
|
-
var SQLValidationError = class _SQLValidationError extends Error {
|
|
1076
|
-
[sqlValidationMarker];
|
|
1077
|
-
constructor(message) {
|
|
1078
|
-
super(message);
|
|
1079
|
-
this.name = "SQLValidationError";
|
|
1080
|
-
this[sqlValidationMarker] = true;
|
|
1081
|
-
}
|
|
1082
|
-
static isInstance(error) {
|
|
1083
|
-
return error instanceof _SQLValidationError && error[sqlValidationMarker] === true;
|
|
1084
|
-
}
|
|
1085
|
-
};
|
|
1086
|
-
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
1087
|
-
[unanswerableSqlMarker];
|
|
1088
|
-
constructor(message) {
|
|
1089
|
-
super(message);
|
|
1090
|
-
this.name = "UnanswerableSQLError";
|
|
1091
|
-
this[unanswerableSqlMarker] = true;
|
|
1092
|
-
}
|
|
1093
|
-
static isInstance(error) {
|
|
1094
|
-
return error instanceof _UnanswerableSQLError && error[unanswerableSqlMarker] === true;
|
|
1095
|
-
}
|
|
1096
|
-
};
|
|
1097
|
-
var SQLScopeError = class _SQLScopeError extends Error {
|
|
1098
|
-
[sqlScopeMarker];
|
|
1099
|
-
payload;
|
|
1100
|
-
errorType;
|
|
1101
|
-
constructor(payload) {
|
|
1102
|
-
super(JSON.stringify(payload));
|
|
1103
|
-
this.name = "SQLScopeError";
|
|
1104
|
-
this.payload = payload;
|
|
1105
|
-
this.errorType = payload.error_type;
|
|
1106
|
-
this[sqlScopeMarker] = true;
|
|
1107
|
-
}
|
|
1108
|
-
static isInstance(error) {
|
|
1109
|
-
return error instanceof _SQLScopeError && error[sqlScopeMarker] === true;
|
|
1110
|
-
}
|
|
1111
|
-
};
|
|
1112
|
-
var SQLReadOnlyError = class _SQLReadOnlyError extends Error {
|
|
1113
|
-
[sqlReadOnlyMarker];
|
|
1114
|
-
constructor(message) {
|
|
1115
|
-
super(message);
|
|
1116
|
-
this.name = "SQLReadOnlyError";
|
|
1117
|
-
this[sqlReadOnlyMarker] = true;
|
|
1118
|
-
}
|
|
1119
|
-
static isInstance(error) {
|
|
1120
|
-
return error instanceof _SQLReadOnlyError && error[sqlReadOnlyMarker] === true;
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
// packages/text2sql/src/lib/fragments/schema.ts
|
|
1125
|
-
function dialectInfo(input) {
|
|
1126
|
-
return {
|
|
1127
|
-
name: "dialectInfo",
|
|
1128
|
-
data: {
|
|
1129
|
-
dialect: input.dialect,
|
|
1130
|
-
...input.version && { version: input.version },
|
|
1131
|
-
...input.database && { database: input.database },
|
|
1132
|
-
...input.details && { details: input.details }
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
function table(input) {
|
|
1137
|
-
return {
|
|
1138
|
-
name: "table",
|
|
1139
|
-
data: {
|
|
1140
|
-
name: input.name,
|
|
1141
|
-
...input.schema && { schema: input.schema },
|
|
1142
|
-
...input.rowCount != null && { rowCount: input.rowCount },
|
|
1143
|
-
...input.sizeHint && { sizeHint: input.sizeHint },
|
|
1144
|
-
columns: input.columns,
|
|
1145
|
-
...input.indexes?.length && { indexes: input.indexes },
|
|
1146
|
-
...input.constraints?.length && { constraints: input.constraints }
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
function column(input) {
|
|
1151
|
-
return {
|
|
1152
|
-
name: "column",
|
|
1153
|
-
data: {
|
|
1154
|
-
name: input.name,
|
|
1155
|
-
type: input.type,
|
|
1156
|
-
...input.pk && { pk: true },
|
|
1157
|
-
...input.fk && { fk: input.fk },
|
|
1158
|
-
...input.unique && { unique: true },
|
|
1159
|
-
...input.notNull && { notNull: true },
|
|
1160
|
-
...input.default && { default: input.default },
|
|
1161
|
-
...input.indexed && { indexed: true },
|
|
1162
|
-
...input.values?.length && { values: input.values },
|
|
1163
|
-
...input.stats && { stats: input.stats }
|
|
909
|
+
/**
|
|
910
|
+
* Execute the grounding process.
|
|
911
|
+
* Writes discovered tables and relationships to the context.
|
|
912
|
+
*/
|
|
913
|
+
async execute(ctx) {
|
|
914
|
+
const seedTables = await this.applyFilter();
|
|
915
|
+
const forward = this.#forward;
|
|
916
|
+
const backward = this.#backward;
|
|
917
|
+
if (!forward && !backward) {
|
|
918
|
+
const tables3 = await Promise.all(
|
|
919
|
+
seedTables.map(async (name, index2) => {
|
|
920
|
+
ctx.onProgress({
|
|
921
|
+
type: "phase:progress",
|
|
922
|
+
phase: "tables",
|
|
923
|
+
table: name,
|
|
924
|
+
message: `Loading table ${name}...`,
|
|
925
|
+
current: index2 + 1,
|
|
926
|
+
total: seedTables.length
|
|
927
|
+
});
|
|
928
|
+
return applyColumnFilter(await this.getTable(name), this.#columns);
|
|
929
|
+
})
|
|
930
|
+
);
|
|
931
|
+
ctx.tables.push(...tables3);
|
|
932
|
+
return;
|
|
1164
933
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
934
|
+
const tables2 = {};
|
|
935
|
+
const allRelationships = [];
|
|
936
|
+
const seenRelationships = /* @__PURE__ */ new Set();
|
|
937
|
+
const forwardQueue = [];
|
|
938
|
+
const backwardQueue = [];
|
|
939
|
+
const forwardVisited = /* @__PURE__ */ new Set();
|
|
940
|
+
const backwardVisited = /* @__PURE__ */ new Set();
|
|
941
|
+
let loadedCount = 0;
|
|
942
|
+
for (const name of seedTables) {
|
|
943
|
+
if (forward) forwardQueue.push({ name, depth: 0 });
|
|
944
|
+
if (backward) backwardQueue.push({ name, depth: 0 });
|
|
1175
945
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
946
|
+
const forwardLimit = forward === true ? Infinity : forward || 0;
|
|
947
|
+
while (forwardQueue.length > 0) {
|
|
948
|
+
const item = forwardQueue.shift();
|
|
949
|
+
if (!item) break;
|
|
950
|
+
const { name, depth } = item;
|
|
951
|
+
if (forwardVisited.has(name)) continue;
|
|
952
|
+
forwardVisited.add(name);
|
|
953
|
+
if (!tables2[name]) {
|
|
954
|
+
loadedCount++;
|
|
955
|
+
ctx.onProgress({
|
|
956
|
+
type: "phase:progress",
|
|
957
|
+
phase: "tables",
|
|
958
|
+
table: name,
|
|
959
|
+
message: `Loading table ${name}...`,
|
|
960
|
+
current: loadedCount
|
|
961
|
+
});
|
|
962
|
+
tables2[name] = applyColumnFilter(
|
|
963
|
+
await this.getTable(name),
|
|
964
|
+
this.#columns
|
|
965
|
+
);
|
|
1190
966
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
...input.cardinality && { cardinality: input.cardinality }
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// packages/text2sql/src/lib/adapters/groundings/context.ts
|
|
1217
|
-
var noopProgress = () => {
|
|
1218
|
-
};
|
|
1219
|
-
function timestampProgress(onProgress) {
|
|
1220
|
-
if (!onProgress) return noopProgress;
|
|
1221
|
-
return (progress) => {
|
|
1222
|
-
onProgress({
|
|
1223
|
-
...progress,
|
|
1224
|
-
timestampMs: progress.timestampMs ?? Date.now()
|
|
1225
|
-
});
|
|
1226
|
-
};
|
|
1227
|
-
}
|
|
1228
|
-
function createGroundingContext(options = {}) {
|
|
1229
|
-
return {
|
|
1230
|
-
tables: [],
|
|
1231
|
-
views: [],
|
|
1232
|
-
relationships: [],
|
|
1233
|
-
info: void 0,
|
|
1234
|
-
fragments: [],
|
|
1235
|
-
cache: /* @__PURE__ */ new Map(),
|
|
1236
|
-
onProgress: timestampProgress(options.onProgress)
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
1241
|
-
var Adapter = class {
|
|
1242
|
-
#policyAnalyzer;
|
|
1243
|
-
/**
|
|
1244
|
-
* Injects the strategy that guards validate() and execute().
|
|
1245
|
-
*/
|
|
1246
|
-
constructor(policyAnalyzer) {
|
|
1247
|
-
this.#policyAnalyzer = policyAnalyzer;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Introspect the database schema and return context fragments.
|
|
1251
|
-
*
|
|
1252
|
-
* Executes all configured groundings to populate the context, then
|
|
1253
|
-
* generates fragments from the complete context data.
|
|
1254
|
-
*
|
|
1255
|
-
* @param ctx - Optional grounding context for sharing state between groundings
|
|
1256
|
-
* @returns Array of context fragments representing the database schema
|
|
1257
|
-
*/
|
|
1258
|
-
async introspect(ctx = createGroundingContext()) {
|
|
1259
|
-
for (const fn of this.grounding) {
|
|
1260
|
-
const grounding = fn(this);
|
|
1261
|
-
if (grounding.phase) {
|
|
967
|
+
if (depth < forwardLimit) {
|
|
968
|
+
const rels = await this.findOutgoingRelations(name);
|
|
969
|
+
for (const rel of rels) {
|
|
970
|
+
if (!this.isRelationshipVisible(rel)) continue;
|
|
971
|
+
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
972
|
+
if (!forwardVisited.has(rel.referenced_table)) {
|
|
973
|
+
forwardQueue.push({ name: rel.referenced_table, depth: depth + 1 });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const backwardLimit = backward === true ? Infinity : backward || 0;
|
|
979
|
+
while (backwardQueue.length > 0) {
|
|
980
|
+
const item = backwardQueue.shift();
|
|
981
|
+
if (!item) break;
|
|
982
|
+
const { name, depth } = item;
|
|
983
|
+
if (backwardVisited.has(name)) continue;
|
|
984
|
+
backwardVisited.add(name);
|
|
985
|
+
if (!tables2[name]) {
|
|
986
|
+
loadedCount++;
|
|
1262
987
|
ctx.onProgress({
|
|
1263
|
-
type: "phase:
|
|
1264
|
-
phase:
|
|
1265
|
-
|
|
988
|
+
type: "phase:progress",
|
|
989
|
+
phase: "tables",
|
|
990
|
+
table: name,
|
|
991
|
+
message: `Loading table ${name}...`,
|
|
992
|
+
current: loadedCount
|
|
1266
993
|
});
|
|
994
|
+
tables2[name] = applyColumnFilter(
|
|
995
|
+
await this.getTable(name),
|
|
996
|
+
this.#columns
|
|
997
|
+
);
|
|
1267
998
|
}
|
|
1268
|
-
|
|
1269
|
-
await
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
});
|
|
999
|
+
if (depth < backwardLimit) {
|
|
1000
|
+
const rels = await this.findIncomingRelations(name);
|
|
1001
|
+
for (const rel of rels) {
|
|
1002
|
+
if (!this.isRelationshipVisible(rel)) continue;
|
|
1003
|
+
this.addRelationship(rel, allRelationships, seenRelationships);
|
|
1004
|
+
if (!backwardVisited.has(rel.table)) {
|
|
1005
|
+
backwardQueue.push({ name: rel.table, depth: depth + 1 });
|
|
1006
|
+
}
|
|
1277
1007
|
}
|
|
1278
1008
|
}
|
|
1279
1009
|
}
|
|
1280
|
-
|
|
1010
|
+
const tablesList = Object.values(tables2);
|
|
1011
|
+
ctx.tables.push(...tablesList);
|
|
1012
|
+
ctx.relationships.push(...allRelationships);
|
|
1281
1013
|
}
|
|
1282
1014
|
/**
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
* nothing, so their metadata scans are skipped.
|
|
1286
|
-
* Results are NOT cached — call once and store the result.
|
|
1015
|
+
* Apply the filter to get seed table names.
|
|
1016
|
+
* If filter is an explicit array, skip querying all table names.
|
|
1287
1017
|
*/
|
|
1288
|
-
async
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1018
|
+
async applyFilter() {
|
|
1019
|
+
const filter = this.#filter;
|
|
1020
|
+
if (Array.isArray(filter)) {
|
|
1021
|
+
return filter;
|
|
1292
1022
|
}
|
|
1293
|
-
|
|
1023
|
+
const names = await this.getAllTableNames();
|
|
1024
|
+
if (!filter) {
|
|
1025
|
+
return names;
|
|
1026
|
+
}
|
|
1027
|
+
if (filter instanceof RegExp) {
|
|
1028
|
+
return names.filter((name) => filter.test(name));
|
|
1029
|
+
}
|
|
1030
|
+
return names.filter(filter);
|
|
1294
1031
|
}
|
|
1295
1032
|
/**
|
|
1296
|
-
*
|
|
1297
|
-
* Called after all groundings have populated ctx with data.
|
|
1033
|
+
* Add a relationship to the collection, deduplicating by key.
|
|
1298
1034
|
*/
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
dialect: ctx.info.dialect,
|
|
1305
|
-
version: ctx.info.version,
|
|
1306
|
-
database: ctx.info.database,
|
|
1307
|
-
details: ctx.info.details
|
|
1308
|
-
})
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
for (const t of ctx.tables) {
|
|
1312
|
-
fragments.push(this.#tableToFragment(t));
|
|
1313
|
-
}
|
|
1314
|
-
for (const v of ctx.views) {
|
|
1315
|
-
fragments.push(this.#viewToFragment(v));
|
|
1316
|
-
}
|
|
1317
|
-
const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
|
|
1318
|
-
const tableColumnSets = new Map(
|
|
1319
|
-
ctx.tables.map((t) => [t.name, new Set(t.columns.map((c) => c.name))])
|
|
1320
|
-
);
|
|
1321
|
-
for (const rel of ctx.relationships) {
|
|
1322
|
-
const sourceColumns = tableColumnSets.get(rel.table);
|
|
1323
|
-
const targetColumns = tableColumnSets.get(rel.referenced_table);
|
|
1324
|
-
if (sourceColumns && rel.from.some((column2) => !sourceColumns.has(column2))) {
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
if (targetColumns && rel.to.some((column2) => !targetColumns.has(column2))) {
|
|
1328
|
-
continue;
|
|
1329
|
-
}
|
|
1330
|
-
const sourceTable = tableMap.get(rel.table);
|
|
1331
|
-
const targetTable = tableMap.get(rel.referenced_table);
|
|
1332
|
-
fragments.push(
|
|
1333
|
-
this.#relationshipToFragment(rel, sourceTable, targetTable)
|
|
1334
|
-
);
|
|
1035
|
+
addRelationship(rel, all, seen) {
|
|
1036
|
+
const key = `${rel.table}:${rel.from.join(",")}:${rel.referenced_table}:${rel.to.join(",")}`;
|
|
1037
|
+
if (!seen.has(key)) {
|
|
1038
|
+
seen.add(key);
|
|
1039
|
+
all.push(rel);
|
|
1335
1040
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1041
|
+
}
|
|
1042
|
+
isRelationshipVisible(rel) {
|
|
1043
|
+
return this.areColumnsVisible(rel.from, this.#columns?.[rel.table]) && this.areColumnsVisible(rel.to, this.#columns?.[rel.referenced_table]);
|
|
1044
|
+
}
|
|
1045
|
+
areColumnsVisible(names, filter) {
|
|
1046
|
+
if (!filter) return true;
|
|
1047
|
+
return filterColumns(
|
|
1048
|
+
names.map((name) => ({ name })),
|
|
1049
|
+
filter
|
|
1050
|
+
).length === names.length;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// packages/text2sql/src/lib/adapters/groundings/view.grounding.ts
|
|
1055
|
+
var ViewGrounding = class extends AbstractGrounding {
|
|
1056
|
+
#filter;
|
|
1057
|
+
#columns;
|
|
1058
|
+
includeDefinition;
|
|
1059
|
+
constructor(config = {}) {
|
|
1060
|
+
super("view", "views");
|
|
1061
|
+
this.#filter = config.filter;
|
|
1062
|
+
this.#columns = config.columns;
|
|
1063
|
+
this.includeDefinition = config.includeDefinition ?? true;
|
|
1064
|
+
}
|
|
1065
|
+
async contributeEntities(ctx) {
|
|
1066
|
+
await this.execute(ctx);
|
|
1338
1067
|
}
|
|
1339
1068
|
/**
|
|
1340
|
-
*
|
|
1069
|
+
* Execute the grounding process.
|
|
1070
|
+
* Writes discovered views to the context.
|
|
1341
1071
|
*/
|
|
1342
|
-
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1072
|
+
async execute(ctx) {
|
|
1073
|
+
const viewNames = await this.applyFilter();
|
|
1074
|
+
const views2 = await Promise.all(
|
|
1075
|
+
viewNames.map(async (name, index2) => {
|
|
1076
|
+
ctx.onProgress({
|
|
1077
|
+
type: "phase:progress",
|
|
1078
|
+
phase: "views",
|
|
1079
|
+
table: name,
|
|
1080
|
+
message: `Loading view ${name}...`,
|
|
1081
|
+
current: index2 + 1,
|
|
1082
|
+
total: viewNames.length
|
|
1083
|
+
});
|
|
1084
|
+
return applyColumnFilter(await this.getView(name), this.#columns);
|
|
1085
|
+
})
|
|
1347
1086
|
);
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1087
|
+
ctx.views.push(...views2);
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Apply the filter to get view names.
|
|
1091
|
+
* If filter is an explicit array, skip querying all view names.
|
|
1092
|
+
*/
|
|
1093
|
+
async applyFilter() {
|
|
1094
|
+
const filter = this.#filter;
|
|
1095
|
+
if (Array.isArray(filter)) {
|
|
1096
|
+
return filter;
|
|
1097
|
+
}
|
|
1098
|
+
const names = await this.getAllViewNames();
|
|
1099
|
+
if (!filter) {
|
|
1100
|
+
return names;
|
|
1101
|
+
}
|
|
1102
|
+
if (filter instanceof RegExp) {
|
|
1103
|
+
return names.filter((name) => filter.test(name));
|
|
1104
|
+
}
|
|
1105
|
+
return names.filter(filter);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// packages/text2sql/src/lib/adapters/duckdb/duckdb-identifiers.ts
|
|
1110
|
+
import { quotedIdentifier } from "@duckdb/node-api";
|
|
1111
|
+
function formatDuckDBIdentifierPath(parts) {
|
|
1112
|
+
return parts.map(quotedIdentifier).join(".");
|
|
1113
|
+
}
|
|
1114
|
+
function parseDuckDBRelationName(name) {
|
|
1115
|
+
const parts = splitIdentifierPath(name);
|
|
1116
|
+
if (parts.length === 1) return { table: parts[0] };
|
|
1117
|
+
if (parts.length === 2) {
|
|
1118
|
+
return { schema: parts[0], table: parts[1] };
|
|
1119
|
+
}
|
|
1120
|
+
if (parts.length === 3) {
|
|
1121
|
+
return { catalog: parts[0], schema: parts[1], table: parts[2] };
|
|
1122
|
+
}
|
|
1123
|
+
throw new Error(`DuckDB relation must have at most three parts: ${name}`);
|
|
1124
|
+
}
|
|
1125
|
+
function splitIdentifierPath(input) {
|
|
1126
|
+
const parts = [];
|
|
1127
|
+
let part = "";
|
|
1128
|
+
let quoted = false;
|
|
1129
|
+
for (let index2 = 0; index2 < input.length; index2++) {
|
|
1130
|
+
const character = input[index2];
|
|
1131
|
+
if (character === '"') {
|
|
1132
|
+
if (quoted && input[index2 + 1] === '"') {
|
|
1133
|
+
part += '"';
|
|
1134
|
+
index2++;
|
|
1135
|
+
} else {
|
|
1136
|
+
quoted = !quoted;
|
|
1354
1137
|
}
|
|
1138
|
+
} else if (character === "." && !quoted) {
|
|
1139
|
+
if (!part) throw new Error(`Invalid DuckDB relation: ${input}`);
|
|
1140
|
+
parts.push(part);
|
|
1141
|
+
part = "";
|
|
1142
|
+
} else {
|
|
1143
|
+
part += character;
|
|
1355
1144
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1145
|
+
}
|
|
1146
|
+
if (quoted || !part) throw new Error(`Invalid DuckDB relation: ${input}`);
|
|
1147
|
+
parts.push(part);
|
|
1148
|
+
return parts;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// packages/text2sql/src/lib/adapters/duckdb/duckdb.groundings.ts
|
|
1152
|
+
var tableNameRow = z3.object({
|
|
1153
|
+
database_name: z3.string(),
|
|
1154
|
+
schema_name: z3.string(),
|
|
1155
|
+
table_name: z3.string()
|
|
1156
|
+
});
|
|
1157
|
+
var viewNameRow = z3.object({
|
|
1158
|
+
database_name: z3.string(),
|
|
1159
|
+
schema_name: z3.string(),
|
|
1160
|
+
view_name: z3.string()
|
|
1161
|
+
});
|
|
1162
|
+
var columnRow = z3.object({ column_name: z3.string(), data_type: z3.string() });
|
|
1163
|
+
var columnNameRow = z3.object({ column_name: z3.string() });
|
|
1164
|
+
var columnDefaultRow = z3.object({
|
|
1165
|
+
column_name: z3.string(),
|
|
1166
|
+
column_default: z3.string()
|
|
1167
|
+
});
|
|
1168
|
+
var infoRow = z3.object({
|
|
1169
|
+
version: z3.string(),
|
|
1170
|
+
catalog: z3.string(),
|
|
1171
|
+
schema: z3.string()
|
|
1172
|
+
});
|
|
1173
|
+
var viewSqlRow = z3.object({ sql: z3.string().nullable() });
|
|
1174
|
+
var constraintRow = z3.object({
|
|
1175
|
+
constraint_name: z3.string(),
|
|
1176
|
+
constraint_type: z3.string(),
|
|
1177
|
+
expression: z3.string().nullable(),
|
|
1178
|
+
constraint_column_names: z3.array(z3.string()),
|
|
1179
|
+
referenced_table: z3.string().nullable(),
|
|
1180
|
+
referenced_column_names: z3.array(z3.string())
|
|
1181
|
+
});
|
|
1182
|
+
var foreignKeyRow = constraintRow.extend({ table_name: z3.string() });
|
|
1183
|
+
var indexRow = z3.object({
|
|
1184
|
+
index_name: z3.string(),
|
|
1185
|
+
expressions: z3.array(z3.string()).nullable(),
|
|
1186
|
+
is_unique: z3.boolean(),
|
|
1187
|
+
is_primary: z3.boolean()
|
|
1188
|
+
});
|
|
1189
|
+
var estimatedSizeRow = z3.object({ estimated_size: numericValue.nullable() });
|
|
1190
|
+
var columnStatsRow = z3.object({
|
|
1191
|
+
min_value: z3.string().nullable(),
|
|
1192
|
+
max_value: z3.string().nullable(),
|
|
1193
|
+
null_fraction: z3.number().nullable(),
|
|
1194
|
+
n_distinct: numericValue
|
|
1195
|
+
});
|
|
1196
|
+
var CONSTRAINT_TYPES = /* @__PURE__ */ new Map([
|
|
1197
|
+
["CHECK", "CHECK"],
|
|
1198
|
+
["UNIQUE", "UNIQUE"],
|
|
1199
|
+
["NOT NULL", "NOT_NULL"],
|
|
1200
|
+
["PRIMARY KEY", "PRIMARY_KEY"],
|
|
1201
|
+
["FOREIGN KEY", "FOREIGN_KEY"]
|
|
1202
|
+
]);
|
|
1203
|
+
var DuckDBTableGrounding = class extends TableGrounding {
|
|
1204
|
+
#adapter;
|
|
1205
|
+
constructor(adapter, config = {}) {
|
|
1206
|
+
super(config);
|
|
1207
|
+
this.#adapter = adapter;
|
|
1208
|
+
}
|
|
1209
|
+
async applyFilter() {
|
|
1210
|
+
const names = await super.applyFilter();
|
|
1211
|
+
const { catalogs, schemas } = await this.#adapter.scopedNamespaces();
|
|
1212
|
+
const result = [];
|
|
1213
|
+
for (const name of names) {
|
|
1214
|
+
const relation = await this.#adapter.resolveRelationName(name);
|
|
1215
|
+
if (catalogs.includes(relation.catalog) && schemas.includes(relation.schema)) {
|
|
1216
|
+
result.push(
|
|
1217
|
+
this.#adapter.formatRelationName(
|
|
1218
|
+
relation.catalog,
|
|
1219
|
+
relation.schema,
|
|
1220
|
+
relation.table
|
|
1221
|
+
)
|
|
1222
|
+
);
|
|
1366
1223
|
}
|
|
1367
1224
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1225
|
+
return result;
|
|
1226
|
+
}
|
|
1227
|
+
async getAllTableNames() {
|
|
1228
|
+
const { catalogs, schemas } = await this.#adapter.scopedNamespaces();
|
|
1229
|
+
const rows = await this.#adapter.runQuery(
|
|
1230
|
+
`
|
|
1231
|
+
SELECT database_name, schema_name, table_name
|
|
1232
|
+
FROM duckdb_tables()
|
|
1233
|
+
WHERE NOT internal
|
|
1234
|
+
AND NOT temporary
|
|
1235
|
+
AND database_name IN (${sqlLiterals(this.#adapter, catalogs)})
|
|
1236
|
+
AND schema_name IN (${sqlLiterals(this.#adapter, schemas)})
|
|
1237
|
+
ORDER BY database_name, schema_name, table_name
|
|
1238
|
+
`,
|
|
1239
|
+
tableNameRow
|
|
1381
1240
|
);
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
columns: idx.columns,
|
|
1389
|
-
unique: idx.unique,
|
|
1390
|
-
type: idx.type
|
|
1391
|
-
})
|
|
1241
|
+
return rows.map(
|
|
1242
|
+
(row) => this.#adapter.formatRelationName(
|
|
1243
|
+
row.database_name,
|
|
1244
|
+
row.schema_name,
|
|
1245
|
+
row.table_name
|
|
1246
|
+
)
|
|
1392
1247
|
);
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1248
|
+
}
|
|
1249
|
+
async getTable(tableName) {
|
|
1250
|
+
const { catalog, schema, table: table2 } = await this.#adapter.resolveRelationName(tableName);
|
|
1251
|
+
const rows = await this.#adapter.runQuery(
|
|
1252
|
+
`
|
|
1253
|
+
SELECT column_name, data_type
|
|
1254
|
+
FROM duckdb_columns()
|
|
1255
|
+
WHERE database_name = '${this.#adapter.escapeString(catalog)}'
|
|
1256
|
+
AND schema_name = '${this.#adapter.escapeString(schema)}'
|
|
1257
|
+
AND table_name = '${this.#adapter.escapeString(table2)}'
|
|
1258
|
+
ORDER BY column_index
|
|
1259
|
+
`,
|
|
1260
|
+
columnRow
|
|
1404
1261
|
);
|
|
1405
|
-
return
|
|
1406
|
-
name:
|
|
1407
|
-
schema:
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1262
|
+
return {
|
|
1263
|
+
name: this.#adapter.formatRelationName(catalog, schema, table2),
|
|
1264
|
+
schema: formatDuckDBIdentifierPath([catalog, schema]),
|
|
1265
|
+
rawName: table2,
|
|
1266
|
+
columns: rows.map(columnFromRow)
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
async findOutgoingRelations(tableName) {
|
|
1270
|
+
const relation = await this.#adapter.resolveRelationName(tableName);
|
|
1271
|
+
const rows = await foreignKeys(this.#adapter, relation, "outgoing");
|
|
1272
|
+
return rows.map((row) => relationshipFromRow(this.#adapter, relation, row));
|
|
1273
|
+
}
|
|
1274
|
+
async findIncomingRelations(tableName) {
|
|
1275
|
+
const relation = await this.#adapter.resolveRelationName(tableName);
|
|
1276
|
+
const rows = await foreignKeys(this.#adapter, relation, "incoming");
|
|
1277
|
+
return rows.map((row) => {
|
|
1278
|
+
const source = {
|
|
1279
|
+
catalog: relation.catalog,
|
|
1280
|
+
schema: relation.schema,
|
|
1281
|
+
table: row.table_name
|
|
1282
|
+
};
|
|
1283
|
+
return relationshipFromRow(this.#adapter, source, row);
|
|
1413
1284
|
});
|
|
1414
1285
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1286
|
+
};
|
|
1287
|
+
var DuckDBInfoGrounding = class extends InfoGrounding {
|
|
1288
|
+
#adapter;
|
|
1289
|
+
constructor(adapter, config = {}) {
|
|
1290
|
+
super(config);
|
|
1291
|
+
this.#adapter = adapter;
|
|
1292
|
+
}
|
|
1293
|
+
async collectInfo() {
|
|
1294
|
+
const rows = await this.#adapter.runQuery(
|
|
1295
|
+
`
|
|
1296
|
+
SELECT
|
|
1297
|
+
version() AS version,
|
|
1298
|
+
current_database() AS catalog,
|
|
1299
|
+
current_schema() AS schema
|
|
1300
|
+
`,
|
|
1301
|
+
infoRow
|
|
1426
1302
|
);
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1303
|
+
const row = rows.at(0);
|
|
1304
|
+
return {
|
|
1305
|
+
dialect: "duckdb",
|
|
1306
|
+
version: row?.version,
|
|
1307
|
+
database: row?.catalog,
|
|
1308
|
+
details: {
|
|
1309
|
+
currentSchema: row?.schema ?? "unknown",
|
|
1310
|
+
identifierQualification: "catalog.schema.table",
|
|
1311
|
+
parameterPlaceholders: ["$1", "$name"]
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1433
1314
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1315
|
+
};
|
|
1316
|
+
var DuckDBViewGrounding = class extends ViewGrounding {
|
|
1317
|
+
#adapter;
|
|
1318
|
+
constructor(adapter, config = {}) {
|
|
1319
|
+
super(config);
|
|
1320
|
+
this.#adapter = adapter;
|
|
1321
|
+
}
|
|
1322
|
+
async applyFilter() {
|
|
1323
|
+
const names = await super.applyFilter();
|
|
1324
|
+
const { catalogs, schemas } = await this.#adapter.scopedNamespaces();
|
|
1325
|
+
const result = [];
|
|
1326
|
+
for (const name of names) {
|
|
1327
|
+
const relation = await this.#adapter.resolveRelationName(name);
|
|
1328
|
+
if (catalogs.includes(relation.catalog) && schemas.includes(relation.schema)) {
|
|
1329
|
+
result.push(
|
|
1330
|
+
this.#adapter.formatRelationName(
|
|
1331
|
+
relation.catalog,
|
|
1332
|
+
relation.schema,
|
|
1333
|
+
relation.table
|
|
1334
|
+
)
|
|
1335
|
+
);
|
|
1450
1336
|
}
|
|
1451
1337
|
}
|
|
1452
|
-
return
|
|
1453
|
-
from: { table: rel.table, columns: rel.from },
|
|
1454
|
-
to: { table: rel.referenced_table, columns: rel.to },
|
|
1455
|
-
cardinality
|
|
1456
|
-
});
|
|
1338
|
+
return result;
|
|
1457
1339
|
}
|
|
1458
|
-
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1340
|
+
async getAllViewNames() {
|
|
1341
|
+
const { catalogs, schemas } = await this.#adapter.scopedNamespaces();
|
|
1342
|
+
const rows = await this.#adapter.runQuery(
|
|
1343
|
+
`
|
|
1344
|
+
SELECT database_name, schema_name, view_name
|
|
1345
|
+
FROM duckdb_views()
|
|
1346
|
+
WHERE NOT internal
|
|
1347
|
+
AND NOT temporary
|
|
1348
|
+
AND database_name IN (${sqlLiterals(this.#adapter, catalogs)})
|
|
1349
|
+
AND schema_name IN (${sqlLiterals(this.#adapter, schemas)})
|
|
1350
|
+
ORDER BY database_name, schema_name, view_name
|
|
1351
|
+
`,
|
|
1352
|
+
viewNameRow
|
|
1353
|
+
);
|
|
1354
|
+
return rows.map(
|
|
1355
|
+
(row) => this.#adapter.formatRelationName(
|
|
1356
|
+
row.database_name,
|
|
1357
|
+
row.schema_name,
|
|
1358
|
+
row.view_name
|
|
1359
|
+
)
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
async getView(viewName) {
|
|
1363
|
+
const relation = await this.#adapter.resolveRelationName(viewName);
|
|
1364
|
+
const where = relationPredicate(this.#adapter, relation);
|
|
1365
|
+
const [columns, definitions] = await Promise.all([
|
|
1366
|
+
this.#adapter.runQuery(
|
|
1367
|
+
`
|
|
1368
|
+
SELECT column_name, data_type
|
|
1369
|
+
FROM duckdb_columns()
|
|
1370
|
+
WHERE ${where}
|
|
1371
|
+
ORDER BY column_index
|
|
1372
|
+
`,
|
|
1373
|
+
columnRow
|
|
1374
|
+
),
|
|
1375
|
+
this.includeDefinition ? this.#adapter.runQuery(
|
|
1376
|
+
`
|
|
1377
|
+
SELECT sql
|
|
1378
|
+
FROM duckdb_views()
|
|
1379
|
+
WHERE database_name = '${this.#adapter.escapeString(relation.catalog)}'
|
|
1380
|
+
AND schema_name = '${this.#adapter.escapeString(relation.schema)}'
|
|
1381
|
+
AND view_name = '${this.#adapter.escapeString(relation.table)}'
|
|
1382
|
+
`,
|
|
1383
|
+
viewSqlRow
|
|
1384
|
+
) : Promise.resolve([])
|
|
1385
|
+
]);
|
|
1386
|
+
return {
|
|
1387
|
+
name: this.#adapter.formatRelationName(
|
|
1388
|
+
relation.catalog,
|
|
1389
|
+
relation.schema,
|
|
1390
|
+
relation.table
|
|
1391
|
+
),
|
|
1392
|
+
schema: formatDuckDBIdentifierPath([relation.catalog, relation.schema]),
|
|
1393
|
+
rawName: relation.table,
|
|
1394
|
+
definition: definitions.at(0)?.sql ?? void 0,
|
|
1395
|
+
columns: columns.map(columnFromRow)
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
var DuckDBConstraintGrounding = class extends ConstraintGrounding {
|
|
1400
|
+
#adapter;
|
|
1401
|
+
constructor(adapter, config = {}) {
|
|
1402
|
+
super(config);
|
|
1403
|
+
this.#adapter = adapter;
|
|
1404
|
+
}
|
|
1405
|
+
async getConstraints(tableName) {
|
|
1406
|
+
const relation = await this.#adapter.resolveRelationName(tableName);
|
|
1407
|
+
const [rows, columns] = await Promise.all([
|
|
1408
|
+
this.#adapter.runQuery(
|
|
1409
|
+
`
|
|
1410
|
+
SELECT
|
|
1411
|
+
constraint_name,
|
|
1412
|
+
constraint_type,
|
|
1413
|
+
expression,
|
|
1414
|
+
constraint_column_names,
|
|
1415
|
+
referenced_table,
|
|
1416
|
+
referenced_column_names
|
|
1417
|
+
FROM duckdb_constraints()
|
|
1418
|
+
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
1419
|
+
ORDER BY constraint_index
|
|
1420
|
+
`,
|
|
1421
|
+
constraintRow
|
|
1422
|
+
),
|
|
1423
|
+
this.#adapter.runQuery(
|
|
1424
|
+
`
|
|
1425
|
+
SELECT column_name, column_default
|
|
1426
|
+
FROM duckdb_columns()
|
|
1427
|
+
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
1428
|
+
AND column_default IS NOT NULL
|
|
1429
|
+
ORDER BY column_index
|
|
1430
|
+
`,
|
|
1431
|
+
columnDefaultRow
|
|
1432
|
+
)
|
|
1433
|
+
]);
|
|
1434
|
+
const constraints2 = rows.map(
|
|
1435
|
+
(row) => constraintFromRow(this.#adapter, relation, row)
|
|
1436
|
+
);
|
|
1437
|
+
for (const column2 of columns) {
|
|
1438
|
+
constraints2.push({
|
|
1439
|
+
name: `${relation.table}_${column2.column_name}_default`,
|
|
1440
|
+
type: "DEFAULT",
|
|
1441
|
+
columns: [column2.column_name],
|
|
1442
|
+
defaultValue: column2.column_default
|
|
1443
|
+
});
|
|
1464
1444
|
}
|
|
1445
|
+
return constraints2;
|
|
1465
1446
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
this.#cachedAllowedEntities = await this.resolveAllowedEntities();
|
|
1473
|
-
return this.#cachedAllowedEntities;
|
|
1447
|
+
};
|
|
1448
|
+
var DuckDBIndexesGrounding = class extends IndexesGrounding {
|
|
1449
|
+
#adapter;
|
|
1450
|
+
constructor(adapter, config = {}) {
|
|
1451
|
+
super(config);
|
|
1452
|
+
this.#adapter = adapter;
|
|
1474
1453
|
}
|
|
1475
|
-
async
|
|
1476
|
-
|
|
1477
|
-
|
|
1454
|
+
async getIndexes(tableName) {
|
|
1455
|
+
const relation = await this.#adapter.resolveRelationName(tableName);
|
|
1456
|
+
const [rows, columnRows] = await Promise.all([
|
|
1457
|
+
this.#adapter.runQuery(
|
|
1458
|
+
`
|
|
1459
|
+
SELECT
|
|
1460
|
+
index_name,
|
|
1461
|
+
TRY_CAST(expressions AS VARCHAR[]) AS expressions,
|
|
1462
|
+
is_unique,
|
|
1463
|
+
is_primary
|
|
1464
|
+
FROM duckdb_indexes()
|
|
1465
|
+
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
1466
|
+
ORDER BY index_name
|
|
1467
|
+
`,
|
|
1468
|
+
indexRow
|
|
1469
|
+
),
|
|
1470
|
+
this.#adapter.runQuery(
|
|
1471
|
+
`
|
|
1472
|
+
SELECT column_name
|
|
1473
|
+
FROM duckdb_columns()
|
|
1474
|
+
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
1475
|
+
ORDER BY column_index
|
|
1476
|
+
`,
|
|
1477
|
+
columnNameRow
|
|
1478
|
+
)
|
|
1479
|
+
]);
|
|
1480
|
+
const tableColumns = columnRows.map((row) => row.column_name);
|
|
1481
|
+
return rows.flatMap((row) => {
|
|
1482
|
+
if (row.expressions === null) {
|
|
1483
|
+
return [];
|
|
1484
|
+
}
|
|
1485
|
+
const columns = row.expressions.map(
|
|
1486
|
+
(expression) => tableColumns.find(
|
|
1487
|
+
(column2) => expression === column2 || expression === this.#adapter.quoteIdentifier(column2)
|
|
1488
|
+
)
|
|
1489
|
+
);
|
|
1490
|
+
return columns.length > 0 && columns.every((column2) => column2 !== void 0) ? [
|
|
1491
|
+
{
|
|
1492
|
+
name: row.index_name,
|
|
1493
|
+
columns,
|
|
1494
|
+
unique: row.is_unique,
|
|
1495
|
+
type: row.is_primary ? "PRIMARY" : void 0
|
|
1496
|
+
}
|
|
1497
|
+
] : [];
|
|
1478
1498
|
});
|
|
1479
1499
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1500
|
+
};
|
|
1501
|
+
var DuckDBRowCountGrounding = class extends RowCountGrounding {
|
|
1502
|
+
#adapter;
|
|
1503
|
+
constructor(adapter, config = {}) {
|
|
1504
|
+
super(config);
|
|
1505
|
+
this.#adapter = adapter;
|
|
1486
1506
|
}
|
|
1487
|
-
async
|
|
1488
|
-
|
|
1507
|
+
async getRowCount(tableName) {
|
|
1508
|
+
const relation = await this.#adapter.resolveRelationName(tableName);
|
|
1509
|
+
const rows = await this.#adapter.runQuery(
|
|
1510
|
+
`
|
|
1511
|
+
SELECT estimated_size
|
|
1512
|
+
FROM duckdb_tables()
|
|
1513
|
+
WHERE ${relationPredicate(this.#adapter, relation)}
|
|
1514
|
+
`,
|
|
1515
|
+
estimatedSizeRow
|
|
1516
|
+
);
|
|
1517
|
+
return this.#adapter.toNumber(rows.at(0)?.estimated_size);
|
|
1489
1518
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (violation?.kind === "scope") {
|
|
1497
|
-
throw new SQLScopeError(violation.payload);
|
|
1498
|
-
}
|
|
1499
|
-
return decoded;
|
|
1519
|
+
};
|
|
1520
|
+
var DuckDBColumnStatsGrounding = class extends ColumnStatsGrounding {
|
|
1521
|
+
#adapter;
|
|
1522
|
+
constructor(adapter, config = {}) {
|
|
1523
|
+
super(config);
|
|
1524
|
+
this.#adapter = adapter;
|
|
1500
1525
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
*/
|
|
1505
|
-
toNumber(value) {
|
|
1506
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1507
|
-
return value;
|
|
1508
|
-
}
|
|
1509
|
-
if (typeof value === "bigint") {
|
|
1510
|
-
return Number(value);
|
|
1511
|
-
}
|
|
1512
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
1513
|
-
const parsed = Number(value);
|
|
1514
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1526
|
+
async collectStats(tableName, column2) {
|
|
1527
|
+
if (!/int|real|numeric|double|float|decimal|date|time|bool/i.test(column2.type)) {
|
|
1528
|
+
return void 0;
|
|
1515
1529
|
}
|
|
1516
|
-
|
|
1530
|
+
const relation = this.#adapter.quoteRelationName(tableName);
|
|
1531
|
+
const identifier = this.#adapter.quoteIdentifier(column2.name);
|
|
1532
|
+
const rows = await this.#adapter.runQuery(
|
|
1533
|
+
`
|
|
1534
|
+
SELECT
|
|
1535
|
+
CAST(MIN(${identifier}) AS VARCHAR) AS min_value,
|
|
1536
|
+
CAST(MAX(${identifier}) AS VARCHAR) AS max_value,
|
|
1537
|
+
CASE
|
|
1538
|
+
WHEN COUNT(*) = 0 THEN NULL
|
|
1539
|
+
ELSE COUNT(*) FILTER (WHERE ${identifier} IS NULL)::DOUBLE / COUNT(*)
|
|
1540
|
+
END AS null_fraction,
|
|
1541
|
+
COUNT(DISTINCT ${identifier}) AS n_distinct
|
|
1542
|
+
FROM ${relation}
|
|
1543
|
+
`,
|
|
1544
|
+
columnStatsRow
|
|
1545
|
+
);
|
|
1546
|
+
const row = rows.at(0);
|
|
1547
|
+
if (!row) return void 0;
|
|
1548
|
+
return {
|
|
1549
|
+
min: row.min_value ?? void 0,
|
|
1550
|
+
max: row.max_value ?? void 0,
|
|
1551
|
+
nullFraction: this.#adapter.toNumber(row.null_fraction),
|
|
1552
|
+
nDistinct: this.#adapter.toNumber(row.n_distinct)
|
|
1553
|
+
};
|
|
1517
1554
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
return { schema, table: rest.join(".") };
|
|
1525
|
-
}
|
|
1526
|
-
return { schema: this.defaultSchema ?? "", table: name };
|
|
1555
|
+
};
|
|
1556
|
+
var DuckDBColumnValuesGrounding = class extends ColumnValuesGrounding {
|
|
1557
|
+
#adapter;
|
|
1558
|
+
constructor(adapter, config = {}) {
|
|
1559
|
+
super(config);
|
|
1560
|
+
this.#adapter = adapter;
|
|
1527
1561
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
* Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
|
|
1531
|
-
*/
|
|
1532
|
-
escapeString(value) {
|
|
1533
|
-
return value.replace(/'/g, "''");
|
|
1562
|
+
async collectEnumValues(_tableName, column2) {
|
|
1563
|
+
return parseEnumLabels(column2.type);
|
|
1534
1564
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
* @param allowedSchemas - If provided, filter to these schemas only
|
|
1539
|
-
*/
|
|
1540
|
-
buildSchemaFilter(columnName, allowedSchemas) {
|
|
1541
|
-
if (allowedSchemas && allowedSchemas.length > 0) {
|
|
1542
|
-
const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
|
|
1543
|
-
return `AND ${columnName} IN (${values})`;
|
|
1565
|
+
async collectLowCardinality(tableName, column2) {
|
|
1566
|
+
if (/\b(?:BLOB|STRUCT|MAP|LIST|UNION|ARRAY)\b/i.test(column2.type)) {
|
|
1567
|
+
return void 0;
|
|
1544
1568
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1569
|
+
const relation = this.#adapter.quoteRelationName(tableName);
|
|
1570
|
+
const identifier = this.#adapter.quoteIdentifier(column2.name);
|
|
1571
|
+
const rows = await this.#adapter.runQuery(
|
|
1572
|
+
`
|
|
1573
|
+
SELECT DISTINCT CAST(${identifier} AS VARCHAR) AS value
|
|
1574
|
+
FROM ${relation}
|
|
1575
|
+
WHERE ${identifier} IS NOT NULL
|
|
1576
|
+
ORDER BY value
|
|
1577
|
+
LIMIT ${this.lowCardinalityLimit + 1}
|
|
1578
|
+
`,
|
|
1579
|
+
textValueRow
|
|
1580
|
+
);
|
|
1581
|
+
if (rows.length === 0 || rows.length > this.lowCardinalityLimit) {
|
|
1582
|
+
return void 0;
|
|
1548
1583
|
}
|
|
1549
|
-
return
|
|
1584
|
+
return rows.map((row) => row.value);
|
|
1550
1585
|
}
|
|
1551
1586
|
};
|
|
1587
|
+
function sqlLiterals(adapter, values) {
|
|
1588
|
+
return values.map((value) => `'${adapter.escapeString(value)}'`).join(", ");
|
|
1589
|
+
}
|
|
1590
|
+
function relationPredicate(adapter, relation) {
|
|
1591
|
+
return `database_name = '${adapter.escapeString(relation.catalog)}'
|
|
1592
|
+
AND schema_name = '${adapter.escapeString(relation.schema)}'
|
|
1593
|
+
AND table_name = '${adapter.escapeString(relation.table)}'`;
|
|
1594
|
+
}
|
|
1595
|
+
function columnFromRow(row) {
|
|
1596
|
+
return { name: row.column_name, type: row.data_type };
|
|
1597
|
+
}
|
|
1598
|
+
async function foreignKeys(adapter, relation, direction) {
|
|
1599
|
+
const predicate = direction === "outgoing" ? `table_name = '${adapter.escapeString(relation.table)}'` : `referenced_table = '${adapter.escapeString(relation.table)}'`;
|
|
1600
|
+
return adapter.runQuery(
|
|
1601
|
+
`
|
|
1602
|
+
SELECT
|
|
1603
|
+
table_name,
|
|
1604
|
+
constraint_name,
|
|
1605
|
+
constraint_type,
|
|
1606
|
+
expression,
|
|
1607
|
+
constraint_column_names,
|
|
1608
|
+
referenced_table,
|
|
1609
|
+
referenced_column_names
|
|
1610
|
+
FROM duckdb_constraints()
|
|
1611
|
+
WHERE database_name = '${adapter.escapeString(relation.catalog)}'
|
|
1612
|
+
AND schema_name = '${adapter.escapeString(relation.schema)}'
|
|
1613
|
+
AND constraint_type = 'FOREIGN KEY'
|
|
1614
|
+
AND ${predicate}
|
|
1615
|
+
ORDER BY table_name, constraint_index
|
|
1616
|
+
`,
|
|
1617
|
+
foreignKeyRow
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
function relationshipFromRow(adapter, source, row) {
|
|
1621
|
+
return {
|
|
1622
|
+
table: adapter.formatRelationName(
|
|
1623
|
+
source.catalog,
|
|
1624
|
+
source.schema,
|
|
1625
|
+
source.table
|
|
1626
|
+
),
|
|
1627
|
+
from: row.constraint_column_names,
|
|
1628
|
+
referenced_table: adapter.formatRelationName(
|
|
1629
|
+
source.catalog,
|
|
1630
|
+
source.schema,
|
|
1631
|
+
requiredString(row.referenced_table, "referenced_table")
|
|
1632
|
+
),
|
|
1633
|
+
to: row.referenced_column_names
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function constraintFromRow(adapter, relation, row) {
|
|
1637
|
+
const mapped = CONSTRAINT_TYPES.get(row.constraint_type);
|
|
1638
|
+
if (!mapped) {
|
|
1639
|
+
throw new Error(`Unknown DuckDB constraint type: ${row.constraint_type}`);
|
|
1640
|
+
}
|
|
1641
|
+
const referenced = mapped === "FOREIGN_KEY" ? adapter.formatRelationName(
|
|
1642
|
+
relation.catalog,
|
|
1643
|
+
relation.schema,
|
|
1644
|
+
requiredString(row.referenced_table, "referenced_table")
|
|
1645
|
+
) : void 0;
|
|
1646
|
+
return {
|
|
1647
|
+
name: row.constraint_name,
|
|
1648
|
+
type: mapped,
|
|
1649
|
+
columns: row.constraint_column_names,
|
|
1650
|
+
definition: mapped === "CHECK" && row.expression !== null ? row.expression : void 0,
|
|
1651
|
+
referencedTable: referenced,
|
|
1652
|
+
referencedColumns: mapped === "FOREIGN_KEY" ? row.referenced_column_names : void 0
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function parseEnumLabels(type) {
|
|
1656
|
+
const body = /^ENUM\((.*)\)$/i.exec(type)?.[1];
|
|
1657
|
+
if (body === void 0) return void 0;
|
|
1658
|
+
const labels = [...body.matchAll(/'((?:''|[^'])*)'/g)].map(
|
|
1659
|
+
(match) => match[1].replaceAll("''", "'")
|
|
1660
|
+
);
|
|
1661
|
+
return labels.length > 0 ? labels : void 0;
|
|
1662
|
+
}
|
|
1663
|
+
function requiredString(value, field) {
|
|
1664
|
+
if (value === null) {
|
|
1665
|
+
throw new Error(`DuckDB catalog field ${field} must be a string.`);
|
|
1666
|
+
}
|
|
1667
|
+
return value;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// packages/text2sql/src/lib/adapters/duckdb/duckdb.ts
|
|
1671
|
+
import { z as z4 } from "zod";
|
|
1552
1672
|
|
|
1553
1673
|
// packages/text2sql/src/lib/sql-scope-error.ts
|
|
1554
1674
|
function buildScopeParseErrorPayload(sql, parserDialect, error) {
|
|
@@ -1764,6 +1884,7 @@ function isRecord(value) {
|
|
|
1764
1884
|
}
|
|
1765
1885
|
|
|
1766
1886
|
// packages/text2sql/src/lib/adapters/duckdb/duckdb.ts
|
|
1887
|
+
var namespaceRow = z4.object({ catalog: z4.string(), schema: z4.string() });
|
|
1767
1888
|
var DuckDB = class extends Adapter {
|
|
1768
1889
|
#options;
|
|
1769
1890
|
#namespace;
|
|
@@ -1788,7 +1909,7 @@ var DuckDB = class extends Adapter {
|
|
|
1788
1909
|
return this.#options.schemas;
|
|
1789
1910
|
}
|
|
1790
1911
|
async executeImpl(sql) {
|
|
1791
|
-
return
|
|
1912
|
+
return this.rowsFrom(await this.#options.execute(sql), ["data", "rows"]);
|
|
1792
1913
|
}
|
|
1793
1914
|
async validateImpl(sql) {
|
|
1794
1915
|
try {
|
|
@@ -1804,8 +1925,8 @@ var DuckDB = class extends Adapter {
|
|
|
1804
1925
|
});
|
|
1805
1926
|
}
|
|
1806
1927
|
}
|
|
1807
|
-
async
|
|
1808
|
-
return
|
|
1928
|
+
async queryRows(sql) {
|
|
1929
|
+
return this.rowsFrom(await this.#options.execute(sql), ["data", "rows"]);
|
|
1809
1930
|
}
|
|
1810
1931
|
quoteIdentifier(name) {
|
|
1811
1932
|
return formatDuckDBIdentifierPath([name]);
|
|
@@ -1852,11 +1973,14 @@ var DuckDB = class extends Adapter {
|
|
|
1852
1973
|
};
|
|
1853
1974
|
}
|
|
1854
1975
|
async #loadNamespace() {
|
|
1855
|
-
const rows = await this.runQuery(
|
|
1976
|
+
const rows = await this.runQuery(
|
|
1977
|
+
`
|
|
1856
1978
|
SELECT current_database() AS catalog, current_schema() AS schema
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1979
|
+
`,
|
|
1980
|
+
namespaceRow
|
|
1981
|
+
);
|
|
1982
|
+
const row = rows.at(0);
|
|
1983
|
+
if (rows.length !== 1 || !row) {
|
|
1860
1984
|
throw new Error(
|
|
1861
1985
|
"DuckDB current namespace returned an unknown row shape."
|
|
1862
1986
|
);
|
|
@@ -1869,42 +1993,55 @@ function validateScopeOption(name, values) {
|
|
|
1869
1993
|
throw new Error(`DuckDB ${name} must contain at least one non-empty name.`);
|
|
1870
1994
|
}
|
|
1871
1995
|
}
|
|
1872
|
-
function rowsFromResult(result) {
|
|
1873
|
-
if (Array.isArray(result)) return result;
|
|
1874
|
-
if (result && typeof result === "object") {
|
|
1875
|
-
const candidate = result;
|
|
1876
|
-
if (Array.isArray(candidate.data)) return candidate.data;
|
|
1877
|
-
if (Array.isArray(candidate.rows)) return candidate.rows;
|
|
1878
|
-
}
|
|
1879
|
-
throw new Error(
|
|
1880
|
-
"DuckDB execute() must return an array of rows, { data: rows }, or { rows }."
|
|
1881
|
-
);
|
|
1882
|
-
}
|
|
1883
1996
|
|
|
1884
1997
|
// packages/text2sql/src/lib/adapters/duckdb/index.ts
|
|
1885
1998
|
function tables(config = {}) {
|
|
1886
|
-
return (
|
|
1999
|
+
return groundingFor(
|
|
2000
|
+
DuckDB,
|
|
2001
|
+
(adapter) => new DuckDBTableGrounding(adapter, config)
|
|
2002
|
+
);
|
|
1887
2003
|
}
|
|
1888
2004
|
function info(config = {}) {
|
|
1889
|
-
return (
|
|
2005
|
+
return groundingFor(
|
|
2006
|
+
DuckDB,
|
|
2007
|
+
(adapter) => new DuckDBInfoGrounding(adapter, config)
|
|
2008
|
+
);
|
|
1890
2009
|
}
|
|
1891
2010
|
function views(config = {}) {
|
|
1892
|
-
return (
|
|
2011
|
+
return groundingFor(
|
|
2012
|
+
DuckDB,
|
|
2013
|
+
(adapter) => new DuckDBViewGrounding(adapter, config)
|
|
2014
|
+
);
|
|
1893
2015
|
}
|
|
1894
2016
|
function constraints(config = {}) {
|
|
1895
|
-
return (
|
|
2017
|
+
return groundingFor(
|
|
2018
|
+
DuckDB,
|
|
2019
|
+
(adapter) => new DuckDBConstraintGrounding(adapter, config)
|
|
2020
|
+
);
|
|
1896
2021
|
}
|
|
1897
2022
|
function indexes(config = {}) {
|
|
1898
|
-
return (
|
|
2023
|
+
return groundingFor(
|
|
2024
|
+
DuckDB,
|
|
2025
|
+
(adapter) => new DuckDBIndexesGrounding(adapter, config)
|
|
2026
|
+
);
|
|
1899
2027
|
}
|
|
1900
2028
|
function rowCount(config = {}) {
|
|
1901
|
-
return (
|
|
2029
|
+
return groundingFor(
|
|
2030
|
+
DuckDB,
|
|
2031
|
+
(adapter) => new DuckDBRowCountGrounding(adapter, config)
|
|
2032
|
+
);
|
|
1902
2033
|
}
|
|
1903
2034
|
function columnStats(config = {}) {
|
|
1904
|
-
return (
|
|
2035
|
+
return groundingFor(
|
|
2036
|
+
DuckDB,
|
|
2037
|
+
(adapter) => new DuckDBColumnStatsGrounding(adapter, config)
|
|
2038
|
+
);
|
|
1905
2039
|
}
|
|
1906
2040
|
function columnValues(config = {}) {
|
|
1907
|
-
return (
|
|
2041
|
+
return groundingFor(
|
|
2042
|
+
DuckDB,
|
|
2043
|
+
(adapter) => new DuckDBColumnValuesGrounding(adapter, config)
|
|
2044
|
+
);
|
|
1908
2045
|
}
|
|
1909
2046
|
var duckdb_default = {
|
|
1910
2047
|
tables,
|