@mcpolyglot/connector-sql 0.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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/sqlite.integration.test.d.ts +2 -0
- package/dist/__tests__/sqlite.integration.test.d.ts.map +1 -0
- package/dist/__tests__/sqlite.integration.test.js +145 -0
- package/dist/__tests__/sqlite.integration.test.js.map +1 -0
- package/dist/connector.d.ts +27 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +113 -0
- package/dist/connector.js.map +1 -0
- package/dist/dialect.d.ts +25 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +2 -0
- package/dist/dialect.js.map +1 -0
- package/dist/dialects/mysql.d.ts +32 -0
- package/dist/dialects/mysql.d.ts.map +1 -0
- package/dist/dialects/mysql.js +146 -0
- package/dist/dialects/mysql.js.map +1 -0
- package/dist/dialects/postgres.d.ts +23 -0
- package/dist/dialects/postgres.d.ts.map +1 -0
- package/dist/dialects/postgres.js +131 -0
- package/dist/dialects/postgres.js.map +1 -0
- package/dist/dialects/sqlite.d.ts +28 -0
- package/dist/dialects/sqlite.d.ts.map +1 -0
- package/dist/dialects/sqlite.js +96 -0
- package/dist/dialects/sqlite.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { McpolyglotError } from '@mcpolyglot/core';
|
|
3
|
+
export class SqlConnector {
|
|
4
|
+
id;
|
|
5
|
+
kind = 'sql';
|
|
6
|
+
dialect;
|
|
7
|
+
logger;
|
|
8
|
+
cachedTables;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.id = opts.id;
|
|
11
|
+
this.dialect = opts.dialect;
|
|
12
|
+
}
|
|
13
|
+
async init(ctx) {
|
|
14
|
+
this.logger = ctx.logger;
|
|
15
|
+
await this.dialect.connect();
|
|
16
|
+
this.logger.info('connector.sql.connected', { id: this.id, dialect: this.dialect.kind });
|
|
17
|
+
}
|
|
18
|
+
async close() {
|
|
19
|
+
await this.dialect.close();
|
|
20
|
+
}
|
|
21
|
+
async health() {
|
|
22
|
+
return this.dialect.ping();
|
|
23
|
+
}
|
|
24
|
+
async introspect() {
|
|
25
|
+
const tables = await this.dialect.listTables();
|
|
26
|
+
this.cachedTables = tables;
|
|
27
|
+
return { kind: 'sql', tables };
|
|
28
|
+
}
|
|
29
|
+
listPrimitiveTools() {
|
|
30
|
+
const id = this.id;
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
name: `${id}.list_tables`,
|
|
34
|
+
description: `List all tables and their columns in the ${id} database.`,
|
|
35
|
+
inputSchema: z.object({}).strict(),
|
|
36
|
+
scopes: ['schema:read'],
|
|
37
|
+
readOnly: true,
|
|
38
|
+
handler: async () => {
|
|
39
|
+
const tables = await this.cachedOrFetchTables();
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'json', data: tables }],
|
|
42
|
+
metadata: { rows: tables.length },
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: `${id}.describe_table`,
|
|
48
|
+
description: `Describe one table in the ${id} database, including columns, types, and primary key.`,
|
|
49
|
+
inputSchema: z
|
|
50
|
+
.object({
|
|
51
|
+
name: z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1)
|
|
54
|
+
.describe('Table name. Use schema-qualified form if needed (e.g. "public.users").'),
|
|
55
|
+
})
|
|
56
|
+
.strict(),
|
|
57
|
+
scopes: ['schema:read'],
|
|
58
|
+
readOnly: true,
|
|
59
|
+
handler: async ({ name }) => {
|
|
60
|
+
const tables = await this.cachedOrFetchTables();
|
|
61
|
+
const found = tables.find((t) => qualifiedName(t).toLowerCase() === name.toLowerCase() ||
|
|
62
|
+
t.name.toLowerCase() === name.toLowerCase());
|
|
63
|
+
if (!found) {
|
|
64
|
+
throw new McpolyglotError('not_found', `Table "${name}" not found in ${id}`);
|
|
65
|
+
}
|
|
66
|
+
return { content: [{ type: 'json', data: found }] };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: `${id}.query`,
|
|
71
|
+
description: `Run a read-only SQL query against the ${id} database. Use parameterized form ($1, $2, ...). Writes are rejected.`,
|
|
72
|
+
inputSchema: z
|
|
73
|
+
.object({
|
|
74
|
+
sql: z
|
|
75
|
+
.string()
|
|
76
|
+
.min(1)
|
|
77
|
+
.describe('SQL query string. Read-only — INSERT/UPDATE/DELETE/DDL will be rejected.'),
|
|
78
|
+
params: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional(),
|
|
79
|
+
limit: z.number().int().positive().max(1000).optional(),
|
|
80
|
+
})
|
|
81
|
+
.strict(),
|
|
82
|
+
scopes: ['tables:read', 'query:raw'],
|
|
83
|
+
readOnly: true,
|
|
84
|
+
handler: async ({ sql, params, limit }, ctx) => {
|
|
85
|
+
const rowCap = Math.min(limit ?? ctx.limits.rowCap, ctx.limits.rowCap);
|
|
86
|
+
const result = await this.dialect.runReadOnly(sql, params ?? [], {
|
|
87
|
+
rowCap,
|
|
88
|
+
timeoutMs: ctx.limits.timeoutMs,
|
|
89
|
+
signal: ctx.signal,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: 'json', data: result }],
|
|
93
|
+
metadata: { rows: result.rowCount, truncated: result.truncated },
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
generatePerEntityTools(_cfg) {
|
|
100
|
+
// Wave 3 — opt-in per-table list/get/count tools generated from cached schema.
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
async cachedOrFetchTables() {
|
|
104
|
+
if (!this.cachedTables) {
|
|
105
|
+
this.cachedTables = await this.dialect.listTables();
|
|
106
|
+
}
|
|
107
|
+
return this.cachedTables;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function qualifiedName(t) {
|
|
111
|
+
return t.schema ? `${t.schema}.${t.name}` : t.name;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connector.js","sourceRoot":"","sources":["../src/connector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TableSchema } from '@mcpolyglot/core';
|
|
2
|
+
export interface SqlQueryResult {
|
|
3
|
+
columns: string[];
|
|
4
|
+
rows: Record<string, unknown>[];
|
|
5
|
+
rowCount: number;
|
|
6
|
+
truncated: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface SqlDialect {
|
|
9
|
+
readonly kind: 'postgres' | 'mysql' | 'mariadb' | 'sqlite';
|
|
10
|
+
connect(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
ping(): Promise<{
|
|
13
|
+
ok: boolean;
|
|
14
|
+
latencyMs: number;
|
|
15
|
+
details?: string;
|
|
16
|
+
}>;
|
|
17
|
+
listTables(): Promise<TableSchema[]>;
|
|
18
|
+
/** Run a read-only query with timeout + row cap. Implementations MUST refuse writes. */
|
|
19
|
+
runReadOnly(sql: string, params: ReadonlyArray<unknown>, opts: {
|
|
20
|
+
rowCap: number;
|
|
21
|
+
timeoutMs: number;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}): Promise<SqlQueryResult>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACrC,wFAAwF;IACxF,WAAW,CACT,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChE,OAAO,CAAC,cAAc,CAAC,CAAC;CAC5B"}
|
package/dist/dialect.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialect.js","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TableSchema } from '@mcpolyglot/core';
|
|
2
|
+
import type { SqlDialect, SqlQueryResult } from '../dialect.js';
|
|
3
|
+
export declare class MysqlDialect implements SqlDialect {
|
|
4
|
+
private readonly connectionString;
|
|
5
|
+
readonly kind: "mysql";
|
|
6
|
+
private pool?;
|
|
7
|
+
private readonly parser;
|
|
8
|
+
constructor(connectionString: string);
|
|
9
|
+
connect(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
ping(): Promise<{
|
|
12
|
+
ok: boolean;
|
|
13
|
+
latencyMs: number;
|
|
14
|
+
details?: string;
|
|
15
|
+
}>;
|
|
16
|
+
listTables(): Promise<TableSchema[]>;
|
|
17
|
+
runReadOnly(sql: string, params: ReadonlyArray<unknown>, opts: {
|
|
18
|
+
rowCap: number;
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
}): Promise<SqlQueryResult>;
|
|
22
|
+
/**
|
|
23
|
+
* MySQL `SET TRANSACTION READ ONLY` is partially honored; some functions
|
|
24
|
+
* (e.g., `SLEEP`, write-time UDFs) bypass it. We pre-screen with a parser
|
|
25
|
+
* that rejects any non-read top-level statement.
|
|
26
|
+
*/
|
|
27
|
+
private assertReadOnlyOrThrow;
|
|
28
|
+
/** Inject `MAX_EXECUTION_TIME` hint on SELECTs so the server cancels long queries. */
|
|
29
|
+
private injectMaxExecutionTime;
|
|
30
|
+
private requirePool;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=mysql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../../src/dialects/mysql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQhE,qBAAa,YAAa,YAAW,UAAU;IAKjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAJ7C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAU;IACjC,OAAO,CAAC,IAAI,CAAC,CAAY;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAEV,gBAAgB,EAAE,MAAM;IAE/C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAexB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAerE,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAqCpC,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChE,OAAO,CAAC,cAAc,CAAC;IA4B1B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAsB7B,sFAAsF;IACtF,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import sqlParser from 'node-sql-parser';
|
|
2
|
+
import { McpolyglotError } from '@mcpolyglot/core';
|
|
3
|
+
const { Parser } = sqlParser;
|
|
4
|
+
const READ_OPS = new Set(['select', 'show', 'describe', 'desc', 'explain', 'with']);
|
|
5
|
+
export class MysqlDialect {
|
|
6
|
+
connectionString;
|
|
7
|
+
kind = 'mysql';
|
|
8
|
+
pool;
|
|
9
|
+
parser = new Parser();
|
|
10
|
+
constructor(connectionString) {
|
|
11
|
+
this.connectionString = connectionString;
|
|
12
|
+
}
|
|
13
|
+
async connect() {
|
|
14
|
+
const mysql = await loadMysql();
|
|
15
|
+
this.pool = mysql.createPool({
|
|
16
|
+
uri: this.connectionString,
|
|
17
|
+
connectionLimit: 4,
|
|
18
|
+
enableKeepAlive: true,
|
|
19
|
+
});
|
|
20
|
+
const c = await this.pool.getConnection();
|
|
21
|
+
try {
|
|
22
|
+
await c.query('SELECT 1');
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
c.release();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async close() {
|
|
29
|
+
await this.pool?.end();
|
|
30
|
+
this.pool = undefined;
|
|
31
|
+
}
|
|
32
|
+
async ping() {
|
|
33
|
+
const t0 = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
const c = await this.requirePool().getConnection();
|
|
36
|
+
try {
|
|
37
|
+
await c.query('SELECT 1');
|
|
38
|
+
return { ok: true, latencyMs: Date.now() - t0 };
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
c.release();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return { ok: false, latencyMs: Date.now() - t0, details: err.message };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async listTables() {
|
|
49
|
+
const c = await this.requirePool().getConnection();
|
|
50
|
+
try {
|
|
51
|
+
const [rows] = (await c.query(`SELECT TABLE_SCHEMA AS \`schema\`, TABLE_NAME AS \`name\`
|
|
52
|
+
FROM information_schema.tables
|
|
53
|
+
WHERE TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema','sys')
|
|
54
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME`));
|
|
55
|
+
const tables = [];
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
const [colRows] = (await c.query(`SELECT COLUMN_NAME AS \`name\`, COLUMN_TYPE AS \`type\`,
|
|
58
|
+
IS_NULLABLE AS \`isNullable\`, COLUMN_KEY AS \`columnKey\`
|
|
59
|
+
FROM information_schema.columns
|
|
60
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
61
|
+
ORDER BY ORDINAL_POSITION`, [row.schema, row.name]));
|
|
62
|
+
const columns = colRows.map((cr) => ({
|
|
63
|
+
name: cr.name,
|
|
64
|
+
type: cr.type,
|
|
65
|
+
nullable: cr.isNullable === 'YES',
|
|
66
|
+
primaryKey: cr.columnKey === 'PRI',
|
|
67
|
+
}));
|
|
68
|
+
tables.push({ schema: row.schema, name: row.name, columns });
|
|
69
|
+
}
|
|
70
|
+
return tables;
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
c.release();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async runReadOnly(sql, params, opts) {
|
|
77
|
+
// 1. AST gate: parse with mysql grammar; refuse anything that isn't a read.
|
|
78
|
+
this.assertReadOnlyOrThrow(sql);
|
|
79
|
+
const c = await this.requirePool().getConnection();
|
|
80
|
+
try {
|
|
81
|
+
// 2. Session read-only.
|
|
82
|
+
await c.query('SET SESSION TRANSACTION READ ONLY');
|
|
83
|
+
await c.query('START TRANSACTION');
|
|
84
|
+
try {
|
|
85
|
+
const [rows, fields] = (await c.query({
|
|
86
|
+
sql: this.injectMaxExecutionTime(sql, opts.timeoutMs),
|
|
87
|
+
values: params,
|
|
88
|
+
rowsAsArray: false,
|
|
89
|
+
}));
|
|
90
|
+
const columns = fields?.map((f) => f.name) ?? (rows[0] ? Object.keys(rows[0]) : []);
|
|
91
|
+
const truncated = rows.length > opts.rowCap;
|
|
92
|
+
const sliced = truncated ? rows.slice(0, opts.rowCap) : rows;
|
|
93
|
+
return { columns, rows: sliced, rowCount: sliced.length, truncated };
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await c.query('ROLLBACK').catch(() => { });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
c.release();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* MySQL `SET TRANSACTION READ ONLY` is partially honored; some functions
|
|
105
|
+
* (e.g., `SLEEP`, write-time UDFs) bypass it. We pre-screen with a parser
|
|
106
|
+
* that rejects any non-read top-level statement.
|
|
107
|
+
*/
|
|
108
|
+
assertReadOnlyOrThrow(sql) {
|
|
109
|
+
let asts;
|
|
110
|
+
try {
|
|
111
|
+
asts = this.parser.astify(sql, { database: 'mysql' });
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
throw new McpolyglotError('forbidden.read_only', `Could not parse SQL: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
const list = Array.isArray(asts) ? asts : [asts];
|
|
117
|
+
for (const node of list) {
|
|
118
|
+
const type = (node?.type ?? '').toLowerCase();
|
|
119
|
+
if (!READ_OPS.has(type)) {
|
|
120
|
+
throw new McpolyglotError('forbidden.read_only', `Statement is not read-only (got "${type || 'unknown'}")`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Inject `MAX_EXECUTION_TIME` hint on SELECTs so the server cancels long queries. */
|
|
125
|
+
injectMaxExecutionTime(sql, timeoutMs) {
|
|
126
|
+
const trimmed = sql.replace(/^\s+/, '');
|
|
127
|
+
if (/^select\s/i.test(trimmed) && !/MAX_EXECUTION_TIME/i.test(trimmed)) {
|
|
128
|
+
return trimmed.replace(/^select\s/i, `SELECT /*+ MAX_EXECUTION_TIME(${timeoutMs}) */ `);
|
|
129
|
+
}
|
|
130
|
+
return sql;
|
|
131
|
+
}
|
|
132
|
+
requirePool() {
|
|
133
|
+
if (!this.pool)
|
|
134
|
+
throw new McpolyglotError('connector.not_initialized', 'MysqlDialect not connected');
|
|
135
|
+
return this.pool;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function loadMysql() {
|
|
139
|
+
try {
|
|
140
|
+
return await import('mysql2/promise');
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new McpolyglotError('connector.missing_dep', 'The "mysql2" package is required for the mysql dialect. Install it with: pnpm add mysql2');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=mysql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mysql.js","sourceRoot":"","sources":["../../src/dialects/mysql.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TableSchema } from '@mcpolyglot/core';
|
|
2
|
+
import type { SqlDialect, SqlQueryResult } from '../dialect.js';
|
|
3
|
+
export declare class PostgresDialect implements SqlDialect {
|
|
4
|
+
private readonly connectionString;
|
|
5
|
+
readonly kind: "postgres";
|
|
6
|
+
private pool?;
|
|
7
|
+
constructor(connectionString: string);
|
|
8
|
+
connect(): Promise<void>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
ping(): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
latencyMs: number;
|
|
13
|
+
details?: string;
|
|
14
|
+
}>;
|
|
15
|
+
listTables(): Promise<TableSchema[]>;
|
|
16
|
+
runReadOnly(sql: string, params: ReadonlyArray<unknown>, opts: {
|
|
17
|
+
rowCap: number;
|
|
18
|
+
timeoutMs: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}): Promise<SqlQueryResult>;
|
|
21
|
+
private requirePool;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/dialects/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,qBAAa,eAAgB,YAAW,UAAU;IAIpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAH7C,QAAQ,CAAC,IAAI,EAAG,UAAU,CAAU;IACpC,OAAO,CAAC,IAAI,CAAC,CAAoB;gBAEJ,gBAAgB,EAAE,MAAM;IAE/C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAerE,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IA0CpC,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChE,OAAO,CAAC,cAAc,CAAC;IAiC1B,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { McpolyglotError } from '@mcpolyglot/core';
|
|
2
|
+
export class PostgresDialect {
|
|
3
|
+
connectionString;
|
|
4
|
+
kind = 'postgres';
|
|
5
|
+
pool;
|
|
6
|
+
constructor(connectionString) {
|
|
7
|
+
this.connectionString = connectionString;
|
|
8
|
+
}
|
|
9
|
+
async connect() {
|
|
10
|
+
const { Pool } = await loadPg();
|
|
11
|
+
this.pool = new Pool({
|
|
12
|
+
connectionString: this.connectionString,
|
|
13
|
+
max: 4,
|
|
14
|
+
idleTimeoutMillis: 30_000,
|
|
15
|
+
application_name: 'mcpolyglot',
|
|
16
|
+
});
|
|
17
|
+
// probe
|
|
18
|
+
const c = await this.pool.connect();
|
|
19
|
+
try {
|
|
20
|
+
await c.query('SELECT 1');
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
c.release();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async close() {
|
|
27
|
+
await this.pool?.end();
|
|
28
|
+
this.pool = undefined;
|
|
29
|
+
}
|
|
30
|
+
async ping() {
|
|
31
|
+
const t0 = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
const c = await this.requirePool().connect();
|
|
34
|
+
try {
|
|
35
|
+
await c.query('SELECT 1');
|
|
36
|
+
return { ok: true, latencyMs: Date.now() - t0 };
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
c.release();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { ok: false, latencyMs: Date.now() - t0, details: err.message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async listTables() {
|
|
47
|
+
const c = await this.requirePool().connect();
|
|
48
|
+
try {
|
|
49
|
+
const sql = `
|
|
50
|
+
SELECT
|
|
51
|
+
n.nspname AS schema,
|
|
52
|
+
c.relname AS table,
|
|
53
|
+
jsonb_agg(jsonb_build_object(
|
|
54
|
+
'name', a.attname,
|
|
55
|
+
'type', format_type(a.atttypid, a.atttypmod),
|
|
56
|
+
'nullable', NOT a.attnotnull,
|
|
57
|
+
'primaryKey', COALESCE(pk.is_pk, false)
|
|
58
|
+
) ORDER BY a.attnum) AS columns
|
|
59
|
+
FROM pg_class c
|
|
60
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
61
|
+
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0 AND NOT a.attisdropped
|
|
62
|
+
LEFT JOIN (
|
|
63
|
+
SELECT i.indrelid, k.attnum, true AS is_pk
|
|
64
|
+
FROM pg_index i
|
|
65
|
+
JOIN pg_attribute k ON k.attrelid = i.indrelid AND k.attnum = ANY(i.indkey)
|
|
66
|
+
WHERE i.indisprimary
|
|
67
|
+
) pk ON pk.indrelid = c.oid AND pk.attnum = a.attnum
|
|
68
|
+
WHERE c.relkind IN ('r','p','v','m')
|
|
69
|
+
AND n.nspname NOT IN ('pg_catalog','information_schema')
|
|
70
|
+
GROUP BY n.nspname, c.relname
|
|
71
|
+
ORDER BY n.nspname, c.relname;
|
|
72
|
+
`;
|
|
73
|
+
const res = await c.query(sql);
|
|
74
|
+
return res.rows.map((r) => ({
|
|
75
|
+
schema: r.schema,
|
|
76
|
+
name: r.table,
|
|
77
|
+
columns: r.columns,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
c.release();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async runReadOnly(sql, params, opts) {
|
|
85
|
+
const c = await this.requirePool().connect();
|
|
86
|
+
try {
|
|
87
|
+
await c.query('BEGIN READ ONLY');
|
|
88
|
+
await c.query(`SET LOCAL statement_timeout = ${Math.max(1, Math.floor(opts.timeoutMs))}`);
|
|
89
|
+
const onAbort = () => {
|
|
90
|
+
// best-effort cancellation
|
|
91
|
+
c.query('SELECT pg_cancel_backend(pg_backend_pid())').catch(() => { });
|
|
92
|
+
};
|
|
93
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true });
|
|
94
|
+
try {
|
|
95
|
+
const res = await c.query({ text: sql, values: params, rowMode: 'array' });
|
|
96
|
+
const fields = res.fields.map((f) => f.name);
|
|
97
|
+
const rawRows = res.rows;
|
|
98
|
+
const truncated = rawRows.length > opts.rowCap;
|
|
99
|
+
const rows = (truncated ? rawRows.slice(0, opts.rowCap) : rawRows).map((row) => {
|
|
100
|
+
const obj = {};
|
|
101
|
+
fields.forEach((f, i) => {
|
|
102
|
+
obj[f] = row[i];
|
|
103
|
+
});
|
|
104
|
+
return obj;
|
|
105
|
+
});
|
|
106
|
+
return { columns: fields, rows, rowCount: rows.length, truncated };
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
110
|
+
await c.query('ROLLBACK').catch(() => { });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
c.release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
requirePool() {
|
|
118
|
+
if (!this.pool)
|
|
119
|
+
throw new McpolyglotError('connector.not_initialized', 'PostgresDialect not connected');
|
|
120
|
+
return this.pool;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function loadPg() {
|
|
124
|
+
try {
|
|
125
|
+
return await import('pg');
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
throw new McpolyglotError('connector.missing_dep', 'The "pg" package is required for the postgres dialect. Install it with: pnpm add pg');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/dialects/postgres.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,OAAO,eAAe;IAIG;IAHpB,IAAI,GAAG,UAAmB,CAAC;IAC5B,IAAI,CAAqB;IAEjC,YAA6B,gBAAwB;QAAxB,qBAAgB,GAAhB,gBAAgB,CAAQ;IAAG,CAAC;IAEzD,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC;YACnB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,GAAG,EAAE,CAAC;YACN,iBAAiB,EAAE,MAAM;YACzB,gBAAgB,EAAE,YAAY;SAC/B,CAAC,CAAC;QACH,QAAQ;QACR,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC5B,CAAC;gBAAS,CAAC;YACT,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC1B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;YAClD,CAAC;oBAAS,CAAC;gBACT,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC;QACpF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;OAuBX,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,KAAK,CAItB,GAAG,CAAC,CAAC;YACR,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,IAAI,EAAE,CAAC,CAAC,KAAK;gBACb,OAAO,EAAE,CAAC,CAAC,OAAO;aACnB,CAAC,CAAC,CAAC;QACN,CAAC;gBAAS,CAAC;YACT,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CACf,GAAW,EACX,MAA8B,EAC9B,IAAiE;QAEjE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACjC,MAAM,CAAC,CAAC,KAAK,CAAC,iCAAiC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;YAC1F,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,2BAA2B;gBAC3B,CAAC,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACxE,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAEhE,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;gBACxF,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAmB,CAAC;gBACxC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;gBAC/C,MAAM,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;oBAC7E,MAAM,GAAG,GAA4B,EAAE,CAAC;oBACxC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;wBACtB,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;oBAClB,CAAC,CAAC,CAAC;oBACH,OAAO,GAAG,CAAC;gBACb,CAAC,CAAC,CAAC;gBACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;YACrE,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACnD,MAAM,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI;YACZ,MAAM,IAAI,eAAe,CAAC,2BAA2B,EAAE,+BAA+B,CAAC,CAAC;QAC1F,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF;AAED,KAAK,UAAU,MAAM;IACnB,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,qFAAqF,CACtF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TableSchema } from '@mcpolyglot/core';
|
|
2
|
+
import type { SqlDialect, SqlQueryResult } from '../dialect.js';
|
|
3
|
+
export declare class SqliteDialect implements SqlDialect {
|
|
4
|
+
private readonly fileOrUri;
|
|
5
|
+
readonly kind: "sqlite";
|
|
6
|
+
private db?;
|
|
7
|
+
/**
|
|
8
|
+
* @param fileOrUri Either an absolute path or a `sqlite://` URI.
|
|
9
|
+
* The file is opened **read-only** regardless of caller intent.
|
|
10
|
+
*/
|
|
11
|
+
constructor(fileOrUri: string);
|
|
12
|
+
connect(): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
ping(): Promise<{
|
|
15
|
+
ok: boolean;
|
|
16
|
+
latencyMs: number;
|
|
17
|
+
details?: string;
|
|
18
|
+
}>;
|
|
19
|
+
listTables(): Promise<TableSchema[]>;
|
|
20
|
+
runReadOnly(sql: string, params: ReadonlyArray<unknown>, opts: {
|
|
21
|
+
rowCap: number;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}): Promise<SqlQueryResult>;
|
|
25
|
+
private requireDb;
|
|
26
|
+
private resolvePath;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/dialects/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKhE,qBAAa,aAAc,YAAW,UAAU;IAQlC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAPtC,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,OAAO,CAAC,EAAE,CAAC,CAAwB;IAEnC;;;OAGG;gBAC0B,SAAS,EAAE,MAAM;IAExC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAUrE,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyBpC,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,EAC9B,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAChE,OAAO,CAAC,cAAc,CAAC;IAsB1B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { McpolyglotError } from '@mcpolyglot/core';
|
|
2
|
+
export class SqliteDialect {
|
|
3
|
+
fileOrUri;
|
|
4
|
+
kind = 'sqlite';
|
|
5
|
+
db;
|
|
6
|
+
/**
|
|
7
|
+
* @param fileOrUri Either an absolute path or a `sqlite://` URI.
|
|
8
|
+
* The file is opened **read-only** regardless of caller intent.
|
|
9
|
+
*/
|
|
10
|
+
constructor(fileOrUri) {
|
|
11
|
+
this.fileOrUri = fileOrUri;
|
|
12
|
+
}
|
|
13
|
+
async connect() {
|
|
14
|
+
const Database = await loadSqlite();
|
|
15
|
+
const path = this.resolvePath();
|
|
16
|
+
const db = new Database(path, { readonly: true, fileMustExist: true });
|
|
17
|
+
db.pragma('query_only = ON');
|
|
18
|
+
this.db = db;
|
|
19
|
+
}
|
|
20
|
+
async close() {
|
|
21
|
+
this.db?.close();
|
|
22
|
+
this.db = undefined;
|
|
23
|
+
}
|
|
24
|
+
async ping() {
|
|
25
|
+
const t0 = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
this.requireDb().prepare('SELECT 1').get();
|
|
28
|
+
return { ok: true, latencyMs: Date.now() - t0 };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return { ok: false, latencyMs: Date.now() - t0, details: err.message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async listTables() {
|
|
35
|
+
const db = this.requireDb();
|
|
36
|
+
const tables = db
|
|
37
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' ORDER BY name`)
|
|
38
|
+
.all();
|
|
39
|
+
return tables.map(({ name }) => {
|
|
40
|
+
const cols = db.prepare(`PRAGMA table_info(${quoteIdent(name)})`).all();
|
|
41
|
+
const columns = cols.map((c) => ({
|
|
42
|
+
name: c.name,
|
|
43
|
+
type: c.type || 'ANY',
|
|
44
|
+
nullable: c.notnull === 0,
|
|
45
|
+
primaryKey: c.pk > 0,
|
|
46
|
+
}));
|
|
47
|
+
return { name, columns };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async runReadOnly(sql, params, opts) {
|
|
51
|
+
const db = this.requireDb();
|
|
52
|
+
if (opts.signal?.aborted)
|
|
53
|
+
throw new McpolyglotError('aborted', 'Aborted before execution');
|
|
54
|
+
// SQLite is synchronous via better-sqlite3 — `query_only` pragma + readonly handle blocks writes.
|
|
55
|
+
const stmt = db.prepare(sql);
|
|
56
|
+
if (!stmt.reader) {
|
|
57
|
+
throw new McpolyglotError('forbidden.read_only', 'Statement is not read-only');
|
|
58
|
+
}
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
const all = stmt.all(...params);
|
|
61
|
+
if (Date.now() - startedAt > opts.timeoutMs) {
|
|
62
|
+
throw new McpolyglotError('timeout', `Query exceeded ${opts.timeoutMs}ms`);
|
|
63
|
+
}
|
|
64
|
+
const truncated = all.length > opts.rowCap;
|
|
65
|
+
const rows = truncated ? all.slice(0, opts.rowCap) : all;
|
|
66
|
+
const columns = rows[0]
|
|
67
|
+
? Object.keys(rows[0])
|
|
68
|
+
: (stmt.columns().map((c) => c.name) ?? []);
|
|
69
|
+
return { columns, rows, rowCount: rows.length, truncated };
|
|
70
|
+
}
|
|
71
|
+
requireDb() {
|
|
72
|
+
if (!this.db)
|
|
73
|
+
throw new McpolyglotError('connector.not_initialized', 'SqliteDialect not connected');
|
|
74
|
+
return this.db;
|
|
75
|
+
}
|
|
76
|
+
resolvePath() {
|
|
77
|
+
const v = this.fileOrUri;
|
|
78
|
+
if (v.startsWith('sqlite://'))
|
|
79
|
+
return v.replace(/^sqlite:\/\//, '');
|
|
80
|
+
return v;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function quoteIdent(name) {
|
|
84
|
+
return '"' + name.replace(/"/g, '""') + '"';
|
|
85
|
+
}
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
async function loadSqlite() {
|
|
88
|
+
try {
|
|
89
|
+
const mod = (await import('better-sqlite3'));
|
|
90
|
+
return mod.default ?? mod;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new McpolyglotError('connector.missing_dep', 'The "better-sqlite3" package is required for the sqlite dialect. Install it with: pnpm add better-sqlite3');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=sqlite.js.map
|