@hyqf98/easy_db_mcp_server 1.0.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 +166 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +58 -0
- package/dist/database/base.d.ts +44 -0
- package/dist/database/base.js +1 -0
- package/dist/database/factory.d.ts +3 -0
- package/dist/database/factory.js +15 -0
- package/dist/database/mysql.d.ts +15 -0
- package/dist/database/mysql.js +96 -0
- package/dist/database/postgresql.d.ts +15 -0
- package/dist/database/postgresql.js +97 -0
- package/dist/database/sqlite.d.ts +15 -0
- package/dist/database/sqlite.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +213 -0
- package/package.json +40 -0
- package/src/config.ts +75 -0
- package/src/database/base.ts +50 -0
- package/src/database/factory.ts +18 -0
- package/src/database/mysql.ts +124 -0
- package/src/database/postgresql.ts +126 -0
- package/src/database/sqlite.ts +93 -0
- package/src/index.ts +256 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# EasyDB MCP Server
|
|
2
|
+
|
|
3
|
+
一个通过 npx 部署的 MCP 服务器,为 AI 助手提供统一的数据库访问接口。
|
|
4
|
+
|
|
5
|
+
## 支持的数据库
|
|
6
|
+
|
|
7
|
+
- MySQL (5.7+)
|
|
8
|
+
- PostgreSQL (12+)
|
|
9
|
+
- SQLite (3+)
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx hyqf98@easy_db_mcp_server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 配置
|
|
18
|
+
|
|
19
|
+
### 环境变量
|
|
20
|
+
|
|
21
|
+
| 变量 | 必填 | 说明 |
|
|
22
|
+
|-----|-----|------|
|
|
23
|
+
| `EASYDB_TYPE` | 是 | 数据库类型:`mysql`、`postgresql` 或 `sqlite` |
|
|
24
|
+
| `EASYDB_HOST` | 是* | 数据库主机(SQLite 不需要) |
|
|
25
|
+
| `EASYDB_PORT` | 否 | 数据库端口(默认值:MySQL=3306、PostgreSQL=5432) |
|
|
26
|
+
| `EASYDB_USER` | 是* | 数据库用户(SQLite 不需要) |
|
|
27
|
+
| `EASYDB_PASSWORD` | 是* | 数据库密码(SQLite 不需要) |
|
|
28
|
+
| `EASYDB_DATABASE` | 否 | 默认数据库名或 SQLite 文件路径 |
|
|
29
|
+
| `EASYDB_ALLOW_WRITE` | 否 | 允许写入操作(默认:false) |
|
|
30
|
+
| `EASYDB_ALLOW_DDL` | 否 | 允许 DDL 操作(默认:false) |
|
|
31
|
+
|
|
32
|
+
### 配置示例
|
|
33
|
+
|
|
34
|
+
**MySQL:**
|
|
35
|
+
```bash
|
|
36
|
+
export EASYDB_TYPE=mysql
|
|
37
|
+
export EASYDB_HOST=localhost
|
|
38
|
+
export EASYDB_USER=root
|
|
39
|
+
export EASYDB_PASSWORD=secret
|
|
40
|
+
export EASYDB_DATABASE=mydb
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**PostgreSQL:**
|
|
44
|
+
```bash
|
|
45
|
+
export EASYDB_TYPE=postgresql
|
|
46
|
+
export EASYDB_HOST=localhost
|
|
47
|
+
export EASYDB_USER=postgres
|
|
48
|
+
export EASYDB_PASSWORD=secret
|
|
49
|
+
export EASYDB_DATABASE=mydb
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**SQLite:**
|
|
53
|
+
```bash
|
|
54
|
+
export EASYDB_TYPE=sqlite
|
|
55
|
+
export EASYDB_DATABASE=/path/to/database.db
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Claude Desktop 配置
|
|
59
|
+
|
|
60
|
+
将以下配置添加到 Claude Desktop 配置文件中:
|
|
61
|
+
|
|
62
|
+
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
63
|
+
**Windows:** `%APPDATA%/Claude/claude_desktop_config.json`
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"database": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["hyqf98@easy_db_mcp_server"],
|
|
71
|
+
"env": {
|
|
72
|
+
"EASYDB_TYPE": "mysql",
|
|
73
|
+
"EASYDB_HOST": "localhost",
|
|
74
|
+
"EASYDB_USER": "root",
|
|
75
|
+
"EASYDB_PASSWORD": "your_password",
|
|
76
|
+
"EASYDB_DATABASE": "mydb"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 可用工具
|
|
84
|
+
|
|
85
|
+
### list_tables
|
|
86
|
+
|
|
87
|
+
列出数据库中的所有表。
|
|
88
|
+
|
|
89
|
+
**参数:**
|
|
90
|
+
- `database`(可选):数据库名
|
|
91
|
+
|
|
92
|
+
**示例:**
|
|
93
|
+
```
|
|
94
|
+
请显示数据库中的所有表
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### describe_table
|
|
98
|
+
|
|
99
|
+
获取表的结构,包括列、类型和约束。
|
|
100
|
+
|
|
101
|
+
**参数:**
|
|
102
|
+
- `table`(必填):表名
|
|
103
|
+
- `database`(可选):数据库名
|
|
104
|
+
|
|
105
|
+
**示例:**
|
|
106
|
+
```
|
|
107
|
+
显示 users 表的结构
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### execute_query
|
|
111
|
+
|
|
112
|
+
执行 SELECT 查询(只读)。
|
|
113
|
+
|
|
114
|
+
**参数:**
|
|
115
|
+
- `sql`(必填):SQL SELECT 查询语句
|
|
116
|
+
- `database`(可选):数据库名
|
|
117
|
+
|
|
118
|
+
**示例:**
|
|
119
|
+
```
|
|
120
|
+
查找过去 7 天内创建的所有用户
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### execute_sql
|
|
124
|
+
|
|
125
|
+
执行任意 SQL 语句(需要 `EASYDB_ALLOW_WRITE=true`)。
|
|
126
|
+
|
|
127
|
+
**参数:**
|
|
128
|
+
- `sql`(必填):SQL 语句
|
|
129
|
+
- `database`(可选):数据库名
|
|
130
|
+
|
|
131
|
+
**注意:** DDL 语句(CREATE、DROP、ALTER)还需要 `EASYDB_ALLOW_DDL=true`。
|
|
132
|
+
|
|
133
|
+
## 安全性
|
|
134
|
+
|
|
135
|
+
默认情况下,服务器只允许读取操作。要启用写入权限:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
export EASYDB_ALLOW_WRITE=true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
要启用 DDL 操作(CREATE、DROP、ALTER):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
export EASYDB_ALLOW_WRITE=true
|
|
145
|
+
export EASYDB_ALLOW_DDL=true
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 开发
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# 安装依赖
|
|
152
|
+
npm install
|
|
153
|
+
|
|
154
|
+
# 构建
|
|
155
|
+
npm run build
|
|
156
|
+
|
|
157
|
+
# 开发模式运行
|
|
158
|
+
npm run dev
|
|
159
|
+
|
|
160
|
+
# 运行生产构建
|
|
161
|
+
npm start
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 许可证
|
|
165
|
+
|
|
166
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface DatabaseConfig {
|
|
2
|
+
type: 'mysql' | 'postgresql' | 'sqlite';
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
user?: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
database?: string;
|
|
8
|
+
allowWrite: boolean;
|
|
9
|
+
allowDDL: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadConfig(): DatabaseConfig;
|
|
12
|
+
export declare function getDefaultPort(type: DatabaseConfig['type']): number;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function loadConfig() {
|
|
2
|
+
const type = process.env.EASYDB_TYPE?.trim();
|
|
3
|
+
if (!type) {
|
|
4
|
+
throw new Error('EASYDB_TYPE is required. Must be one of: mysql, postgresql, sqlite');
|
|
5
|
+
}
|
|
6
|
+
const normalizedType = type.toLowerCase();
|
|
7
|
+
if (!['mysql', 'postgresql', 'sqlite'].includes(normalizedType)) {
|
|
8
|
+
throw new Error(`EASYDB_TYPE must be one of: mysql, postgresql, sqlite. Got: ${type}`);
|
|
9
|
+
}
|
|
10
|
+
// Parse and validate port
|
|
11
|
+
let port;
|
|
12
|
+
const portEnv = process.env.EASYDB_PORT?.trim();
|
|
13
|
+
if (portEnv) {
|
|
14
|
+
port = parseInt(portEnv, 10);
|
|
15
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
16
|
+
throw new Error(`EASYDB_PORT must be a valid port number (1-65535). Got: ${portEnv}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const config = {
|
|
20
|
+
type: normalizedType,
|
|
21
|
+
host: process.env.EASYDB_HOST?.trim(),
|
|
22
|
+
port,
|
|
23
|
+
user: process.env.EASYDB_USER?.trim(),
|
|
24
|
+
password: process.env.EASYDB_PASSWORD,
|
|
25
|
+
database: process.env.EASYDB_DATABASE?.trim(),
|
|
26
|
+
allowWrite: process.env.EASYDB_ALLOW_WRITE === 'true',
|
|
27
|
+
allowDDL: process.env.EASYDB_ALLOW_DDL === 'true',
|
|
28
|
+
};
|
|
29
|
+
// Validate required fields based on type
|
|
30
|
+
if (normalizedType !== 'sqlite') {
|
|
31
|
+
if (!config.host || config.host.length === 0) {
|
|
32
|
+
throw new Error('EASYDB_HOST is required for mysql/postgresql');
|
|
33
|
+
}
|
|
34
|
+
if (!config.user || config.user.length === 0) {
|
|
35
|
+
throw new Error('EASYDB_USER is required for mysql/postgresql');
|
|
36
|
+
}
|
|
37
|
+
if (!config.password) {
|
|
38
|
+
throw new Error('EASYDB_PASSWORD is required for mysql/postgresql');
|
|
39
|
+
}
|
|
40
|
+
// database is optional - can be specified in tool calls
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (!config.database || config.database.length === 0) {
|
|
44
|
+
throw new Error('EASYDB_DATABASE (file path) is required for sqlite');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
export function getDefaultPort(type) {
|
|
50
|
+
switch (type) {
|
|
51
|
+
case 'mysql':
|
|
52
|
+
return 3306;
|
|
53
|
+
case 'postgresql':
|
|
54
|
+
return 5432;
|
|
55
|
+
case 'sqlite':
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface TableColumn {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
nullable: boolean;
|
|
5
|
+
defaultValue: string | null;
|
|
6
|
+
primaryKey: boolean;
|
|
7
|
+
extra?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TableInfo {
|
|
10
|
+
name: string;
|
|
11
|
+
rowCount?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface QueryResult {
|
|
14
|
+
rows: Record<string, unknown>[];
|
|
15
|
+
rowCount: number;
|
|
16
|
+
}
|
|
17
|
+
export interface DatabaseAdapter {
|
|
18
|
+
/**
|
|
19
|
+
* Establish connection to the database
|
|
20
|
+
*/
|
|
21
|
+
connect(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* List all tables in the database
|
|
24
|
+
*/
|
|
25
|
+
listTables(database?: string): Promise<TableInfo[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Get table structure/column information
|
|
28
|
+
*/
|
|
29
|
+
describeTable(table: string, database?: string): Promise<TableColumn[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Execute a SELECT query
|
|
32
|
+
*/
|
|
33
|
+
executeQuery(sql: string, database?: string): Promise<QueryResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Execute any SQL statement (INSERT, UPDATE, DELETE, DDL)
|
|
36
|
+
*/
|
|
37
|
+
executeSQL(sql: string, database?: string): Promise<QueryResult | {
|
|
38
|
+
affectedRows: number;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Close the database connection
|
|
42
|
+
*/
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MySQLAdapter } from './mysql.js';
|
|
2
|
+
import { PostgreSQLAdapter } from './postgresql.js';
|
|
3
|
+
import { SQLiteAdapter } from './sqlite.js';
|
|
4
|
+
export function createAdapter(config) {
|
|
5
|
+
switch (config.type) {
|
|
6
|
+
case 'mysql':
|
|
7
|
+
return new MySQLAdapter(config);
|
|
8
|
+
case 'postgresql':
|
|
9
|
+
return new PostgreSQLAdapter(config);
|
|
10
|
+
case 'sqlite':
|
|
11
|
+
return new SQLiteAdapter(config);
|
|
12
|
+
default:
|
|
13
|
+
throw new Error(`Unsupported database type: ${config.type}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseAdapter, TableInfo, TableColumn, QueryResult } from './base.js';
|
|
2
|
+
import type { DatabaseConfig } from '../config.js';
|
|
3
|
+
export declare class MySQLAdapter implements DatabaseAdapter {
|
|
4
|
+
private pool?;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config: DatabaseConfig);
|
|
7
|
+
connect(): Promise<void>;
|
|
8
|
+
listTables(database?: string): Promise<TableInfo[]>;
|
|
9
|
+
describeTable(table: string, database?: string): Promise<TableColumn[]>;
|
|
10
|
+
executeQuery(sql: string, database?: string): Promise<QueryResult>;
|
|
11
|
+
executeSQL(sql: string, database?: string): Promise<QueryResult | {
|
|
12
|
+
affectedRows: number;
|
|
13
|
+
}>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import mysql from 'mysql2/promise';
|
|
2
|
+
export class MySQLAdapter {
|
|
3
|
+
pool;
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
async connect() {
|
|
9
|
+
this.pool = mysql.createPool({
|
|
10
|
+
host: this.config.host,
|
|
11
|
+
port: this.config.port || 3306,
|
|
12
|
+
user: this.config.user,
|
|
13
|
+
password: this.config.password,
|
|
14
|
+
database: this.config.database,
|
|
15
|
+
waitForConnections: true,
|
|
16
|
+
connectionLimit: 10,
|
|
17
|
+
});
|
|
18
|
+
// Test connection
|
|
19
|
+
const connection = await this.pool.getConnection();
|
|
20
|
+
connection.release();
|
|
21
|
+
}
|
|
22
|
+
async listTables(database) {
|
|
23
|
+
if (!this.pool)
|
|
24
|
+
throw new Error('Not connected');
|
|
25
|
+
const dbName = database || this.config.database;
|
|
26
|
+
if (!dbName)
|
|
27
|
+
throw new Error('Database name required');
|
|
28
|
+
const [rows] = await this.pool.query('SELECT TABLE_NAME as name, TABLE_ROWS as rowCount FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?', [dbName]);
|
|
29
|
+
return rows.map((row) => ({
|
|
30
|
+
name: row.name,
|
|
31
|
+
rowCount: row.rowCount,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
async describeTable(table, database) {
|
|
35
|
+
if (!this.pool)
|
|
36
|
+
throw new Error('Not connected');
|
|
37
|
+
const dbName = database || this.config.database;
|
|
38
|
+
if (!dbName)
|
|
39
|
+
throw new Error('Database name required');
|
|
40
|
+
const [rows] = await this.pool.query(`SELECT
|
|
41
|
+
COLUMN_NAME as name,
|
|
42
|
+
DATA_TYPE as type,
|
|
43
|
+
IS_NULLABLE as nullable,
|
|
44
|
+
COLUMN_DEFAULT as defaultValue,
|
|
45
|
+
COLUMN_KEY as primaryKey,
|
|
46
|
+
EXTRA as extra
|
|
47
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
48
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
|
49
|
+
ORDER BY ORDINAL_POSITION`, [dbName, table]);
|
|
50
|
+
return rows.map((row) => ({
|
|
51
|
+
name: row.name,
|
|
52
|
+
type: row.type,
|
|
53
|
+
nullable: row.nullable === 'YES',
|
|
54
|
+
defaultValue: row.defaultValue,
|
|
55
|
+
primaryKey: row.primaryKey === 'PRI',
|
|
56
|
+
extra: row.extra,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
async executeQuery(sql, database) {
|
|
60
|
+
if (!this.pool)
|
|
61
|
+
throw new Error('Not connected');
|
|
62
|
+
// If database is specified and different from config, we need to handle it
|
|
63
|
+
// For now, we'll allow queries to use fully qualified table names
|
|
64
|
+
// Verify it's a SELECT query
|
|
65
|
+
const trimmed = sql.trim().toUpperCase();
|
|
66
|
+
if (!trimmed.startsWith('SELECT')) {
|
|
67
|
+
throw new Error('Only SELECT queries allowed in executeQuery');
|
|
68
|
+
}
|
|
69
|
+
const [rows] = await this.pool.query(sql);
|
|
70
|
+
return {
|
|
71
|
+
rows: rows,
|
|
72
|
+
rowCount: Array.isArray(rows) ? rows.length : 0,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async executeSQL(sql, database) {
|
|
76
|
+
if (!this.pool)
|
|
77
|
+
throw new Error('Not connected');
|
|
78
|
+
// Allow SQL execution with database-specific queries
|
|
79
|
+
const [result] = await this.pool.query(sql);
|
|
80
|
+
// @ts-ignore - MySQL result structure
|
|
81
|
+
if (result.affectedRows !== undefined) {
|
|
82
|
+
// @ts-ignore
|
|
83
|
+
return { affectedRows: result.affectedRows };
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
rows: result,
|
|
87
|
+
rowCount: Array.isArray(result) ? result.length : 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async close() {
|
|
91
|
+
if (this.pool) {
|
|
92
|
+
await this.pool.end();
|
|
93
|
+
this.pool = undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseAdapter, TableInfo, TableColumn, QueryResult } from './base.js';
|
|
2
|
+
import type { DatabaseConfig } from '../config.js';
|
|
3
|
+
export declare class PostgreSQLAdapter implements DatabaseAdapter {
|
|
4
|
+
private pool?;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config: DatabaseConfig);
|
|
7
|
+
connect(): Promise<void>;
|
|
8
|
+
listTables(database?: string): Promise<TableInfo[]>;
|
|
9
|
+
describeTable(table: string, database?: string): Promise<TableColumn[]>;
|
|
10
|
+
executeQuery(sql: string, database?: string): Promise<QueryResult>;
|
|
11
|
+
executeSQL(sql: string, database?: string): Promise<QueryResult | {
|
|
12
|
+
affectedRows: number;
|
|
13
|
+
}>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
const { Pool } = pg;
|
|
3
|
+
export class PostgreSQLAdapter {
|
|
4
|
+
pool;
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async connect() {
|
|
10
|
+
this.pool = new Pool({
|
|
11
|
+
host: this.config.host,
|
|
12
|
+
port: this.config.port || 5432,
|
|
13
|
+
user: this.config.user,
|
|
14
|
+
password: this.config.password,
|
|
15
|
+
database: this.config.database,
|
|
16
|
+
max: 10,
|
|
17
|
+
});
|
|
18
|
+
// Test connection
|
|
19
|
+
const client = await this.pool.connect();
|
|
20
|
+
client.release();
|
|
21
|
+
}
|
|
22
|
+
async listTables(database) {
|
|
23
|
+
if (!this.pool)
|
|
24
|
+
throw new Error('Not connected');
|
|
25
|
+
const dbName = database || this.config.database;
|
|
26
|
+
if (!dbName)
|
|
27
|
+
throw new Error('Database name required');
|
|
28
|
+
const result = await this.pool.query(`SELECT tablename as name, n_live_tup as "rowCount"
|
|
29
|
+
FROM pg_stat_user_tables
|
|
30
|
+
WHERE schemaname = 'public'`);
|
|
31
|
+
return result.rows.map((row) => ({
|
|
32
|
+
name: row.name,
|
|
33
|
+
rowCount: row.rowCount,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
async describeTable(table, database) {
|
|
37
|
+
if (!this.pool)
|
|
38
|
+
throw new Error('Not connected');
|
|
39
|
+
const dbName = database || this.config.database;
|
|
40
|
+
const result = await this.pool.query(`SELECT
|
|
41
|
+
column_name as name,
|
|
42
|
+
data_type as type,
|
|
43
|
+
is_nullable as nullable,
|
|
44
|
+
column_default as "defaultValue",
|
|
45
|
+
COALESCE(pk.primary, false) as "primaryKey"
|
|
46
|
+
FROM information_schema.columns
|
|
47
|
+
LEFT JOIN (
|
|
48
|
+
SELECT a.attname as primary, c.relname
|
|
49
|
+
FROM pg_index i
|
|
50
|
+
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
|
51
|
+
JOIN pg_class c ON c.oid = i.indrelid
|
|
52
|
+
WHERE i.indisprimary
|
|
53
|
+
) pk ON pk.primary = column_name AND pk.relname = $1
|
|
54
|
+
WHERE table_schema = 'public' AND table_name = $1
|
|
55
|
+
ORDER BY ordinal_position`, [table]);
|
|
56
|
+
return result.rows.map((row) => ({
|
|
57
|
+
name: row.name,
|
|
58
|
+
type: row.type,
|
|
59
|
+
nullable: row.nullable === 'YES',
|
|
60
|
+
defaultValue: row.defaultValue,
|
|
61
|
+
primaryKey: row.primaryKey,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
async executeQuery(sql, database) {
|
|
65
|
+
if (!this.pool)
|
|
66
|
+
throw new Error('Not connected');
|
|
67
|
+
// Allow queries with fully qualified table names (schema.table)
|
|
68
|
+
const trimmed = sql.trim().toUpperCase();
|
|
69
|
+
if (!trimmed.startsWith('SELECT')) {
|
|
70
|
+
throw new Error('Only SELECT queries allowed in executeQuery');
|
|
71
|
+
}
|
|
72
|
+
const result = await this.pool.query(sql);
|
|
73
|
+
return {
|
|
74
|
+
rows: result.rows,
|
|
75
|
+
rowCount: result.rowCount || 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async executeSQL(sql, database) {
|
|
79
|
+
if (!this.pool)
|
|
80
|
+
throw new Error('Not connected');
|
|
81
|
+
// Allow SQL execution with database-specific queries
|
|
82
|
+
const result = await this.pool.query(sql);
|
|
83
|
+
if (result.rowCount !== null && result.command !== 'SELECT') {
|
|
84
|
+
return { affectedRows: result.rowCount };
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
rows: result.rows,
|
|
88
|
+
rowCount: result.rowCount || 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async close() {
|
|
92
|
+
if (this.pool) {
|
|
93
|
+
await this.pool.end();
|
|
94
|
+
this.pool = undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseAdapter, TableInfo, TableColumn, QueryResult } from './base.js';
|
|
2
|
+
import type { DatabaseConfig } from '../config.js';
|
|
3
|
+
export declare class SQLiteAdapter implements DatabaseAdapter {
|
|
4
|
+
private db?;
|
|
5
|
+
private config;
|
|
6
|
+
constructor(config: DatabaseConfig);
|
|
7
|
+
connect(): Promise<void>;
|
|
8
|
+
listTables(): Promise<TableInfo[]>;
|
|
9
|
+
describeTable(table: string): Promise<TableColumn[]>;
|
|
10
|
+
executeQuery(sql: string): Promise<QueryResult>;
|
|
11
|
+
executeSQL(sql: string): Promise<QueryResult | {
|
|
12
|
+
affectedRows: number;
|
|
13
|
+
}>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
export class SQLiteAdapter {
|
|
3
|
+
db;
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
async connect() {
|
|
9
|
+
if (!this.config.database) {
|
|
10
|
+
throw new Error('Database file path required for SQLite');
|
|
11
|
+
}
|
|
12
|
+
this.db = new Database(this.config.database);
|
|
13
|
+
}
|
|
14
|
+
async listTables() {
|
|
15
|
+
if (!this.db)
|
|
16
|
+
throw new Error('Not connected');
|
|
17
|
+
const rows = this.db
|
|
18
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`)
|
|
19
|
+
.all();
|
|
20
|
+
return rows.map((row) => ({
|
|
21
|
+
name: row.name,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
async describeTable(table) {
|
|
25
|
+
if (!this.db)
|
|
26
|
+
throw new Error('Not connected');
|
|
27
|
+
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
28
|
+
return rows.map((row) => ({
|
|
29
|
+
name: row.name,
|
|
30
|
+
type: row.type,
|
|
31
|
+
nullable: row.notnull === 0,
|
|
32
|
+
defaultValue: row.dflt_value,
|
|
33
|
+
primaryKey: row.pk > 0,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
async executeQuery(sql) {
|
|
37
|
+
if (!this.db)
|
|
38
|
+
throw new Error('Not connected');
|
|
39
|
+
const trimmed = sql.trim().toUpperCase();
|
|
40
|
+
if (!trimmed.startsWith('SELECT')) {
|
|
41
|
+
throw new Error('Only SELECT queries allowed in executeQuery');
|
|
42
|
+
}
|
|
43
|
+
const rows = this.db.prepare(sql).all();
|
|
44
|
+
return {
|
|
45
|
+
rows,
|
|
46
|
+
rowCount: rows.length,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async executeSQL(sql) {
|
|
50
|
+
if (!this.db)
|
|
51
|
+
throw new Error('Not connected');
|
|
52
|
+
const trimmed = sql.trim().toUpperCase();
|
|
53
|
+
if (trimmed.startsWith('SELECT')) {
|
|
54
|
+
const rows = this.db.prepare(sql).all();
|
|
55
|
+
return {
|
|
56
|
+
rows,
|
|
57
|
+
rowCount: rows.length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const result = this.db.prepare(sql).run();
|
|
61
|
+
return { affectedRows: result.changes };
|
|
62
|
+
}
|
|
63
|
+
async close() {
|
|
64
|
+
if (this.db) {
|
|
65
|
+
this.db.close();
|
|
66
|
+
this.db = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED