@cmd233/mcp-database-server 1.1.1
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/dist/src/db/adapter.js +29 -0
- package/dist/src/db/index.js +95 -0
- package/dist/src/db/mysql-adapter.js +205 -0
- package/dist/src/db/postgresql-adapter.js +169 -0
- package/dist/src/db/sqlite-adapter.js +134 -0
- package/dist/src/db/sqlserver-adapter.js +186 -0
- package/dist/src/handlers/resourceHandlers.js +71 -0
- package/dist/src/handlers/toolHandlers.js +158 -0
- package/dist/src/index.js +259 -0
- package/dist/src/tools/insightTools.js +54 -0
- package/dist/src/tools/queryTools.js +73 -0
- package/dist/src/tools/schemaTools.js +118 -0
- package/dist/src/utils/formatUtils.js +56 -0
- package/package.json +41 -0
- package/readme.md +328 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ExecuteAutomation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Import adapters using dynamic imports
|
|
2
|
+
import { SqliteAdapter } from './sqlite-adapter.js';
|
|
3
|
+
import { SqlServerAdapter } from './sqlserver-adapter.js';
|
|
4
|
+
import { PostgresqlAdapter } from './postgresql-adapter.js';
|
|
5
|
+
import { MysqlAdapter } from './mysql-adapter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Factory function to create the appropriate database adapter
|
|
8
|
+
*/
|
|
9
|
+
export function createDbAdapter(type, connectionInfo) {
|
|
10
|
+
switch (type.toLowerCase()) {
|
|
11
|
+
case 'sqlite':
|
|
12
|
+
// For SQLite, if connectionInfo is a string, use it directly as path
|
|
13
|
+
if (typeof connectionInfo === 'string') {
|
|
14
|
+
return new SqliteAdapter(connectionInfo);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return new SqliteAdapter(connectionInfo.path);
|
|
18
|
+
}
|
|
19
|
+
case 'sqlserver':
|
|
20
|
+
return new SqlServerAdapter(connectionInfo);
|
|
21
|
+
case 'postgresql':
|
|
22
|
+
case 'postgres':
|
|
23
|
+
return new PostgresqlAdapter(connectionInfo);
|
|
24
|
+
case 'mysql':
|
|
25
|
+
return new MysqlAdapter(connectionInfo);
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Unsupported database type: ${type}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createDbAdapter } from './adapter.js';
|
|
2
|
+
// Store the active database adapter
|
|
3
|
+
let dbAdapter = null;
|
|
4
|
+
/**
|
|
5
|
+
* Initialize the database connection
|
|
6
|
+
* @param connectionInfo Connection information object or SQLite path string
|
|
7
|
+
* @param dbType Database type ('sqlite' or 'sqlserver')
|
|
8
|
+
*/
|
|
9
|
+
export async function initDatabase(connectionInfo, dbType = 'sqlite') {
|
|
10
|
+
try {
|
|
11
|
+
// If connectionInfo is a string, assume it's a SQLite path
|
|
12
|
+
if (typeof connectionInfo === 'string') {
|
|
13
|
+
connectionInfo = { path: connectionInfo };
|
|
14
|
+
}
|
|
15
|
+
// Create appropriate adapter based on database type
|
|
16
|
+
dbAdapter = createDbAdapter(dbType, connectionInfo);
|
|
17
|
+
// Initialize the connection
|
|
18
|
+
await dbAdapter.init();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Failed to initialize database: ${error.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute a SQL query and get all results
|
|
26
|
+
* @param query SQL query to execute
|
|
27
|
+
* @param params Query parameters
|
|
28
|
+
* @returns Promise with query results
|
|
29
|
+
*/
|
|
30
|
+
export function dbAll(query, params = []) {
|
|
31
|
+
if (!dbAdapter) {
|
|
32
|
+
throw new Error("Database not initialized");
|
|
33
|
+
}
|
|
34
|
+
return dbAdapter.all(query, params);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Execute a SQL query that modifies data
|
|
38
|
+
* @param query SQL query to execute
|
|
39
|
+
* @param params Query parameters
|
|
40
|
+
* @returns Promise with result info
|
|
41
|
+
*/
|
|
42
|
+
export function dbRun(query, params = []) {
|
|
43
|
+
if (!dbAdapter) {
|
|
44
|
+
throw new Error("Database not initialized");
|
|
45
|
+
}
|
|
46
|
+
return dbAdapter.run(query, params);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Execute multiple SQL statements
|
|
50
|
+
* @param query SQL statements to execute
|
|
51
|
+
* @returns Promise that resolves when execution completes
|
|
52
|
+
*/
|
|
53
|
+
export function dbExec(query) {
|
|
54
|
+
if (!dbAdapter) {
|
|
55
|
+
throw new Error("Database not initialized");
|
|
56
|
+
}
|
|
57
|
+
return dbAdapter.exec(query);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Close the database connection
|
|
61
|
+
*/
|
|
62
|
+
export function closeDatabase() {
|
|
63
|
+
if (!dbAdapter) {
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
return dbAdapter.close();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get database metadata
|
|
70
|
+
*/
|
|
71
|
+
export function getDatabaseMetadata() {
|
|
72
|
+
if (!dbAdapter) {
|
|
73
|
+
throw new Error("Database not initialized");
|
|
74
|
+
}
|
|
75
|
+
return dbAdapter.getMetadata();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get database-specific query for listing tables
|
|
79
|
+
*/
|
|
80
|
+
export function getListTablesQuery() {
|
|
81
|
+
if (!dbAdapter) {
|
|
82
|
+
throw new Error("Database not initialized");
|
|
83
|
+
}
|
|
84
|
+
return dbAdapter.getListTablesQuery();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get database-specific query for describing a table
|
|
88
|
+
* @param tableName Table name
|
|
89
|
+
*/
|
|
90
|
+
export function getDescribeTableQuery(tableName) {
|
|
91
|
+
if (!dbAdapter) {
|
|
92
|
+
throw new Error("Database not initialized");
|
|
93
|
+
}
|
|
94
|
+
return dbAdapter.getDescribeTableQuery(tableName);
|
|
95
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import { Signer } from "@aws-sdk/rds-signer";
|
|
3
|
+
/**
|
|
4
|
+
* MySQL database adapter implementation
|
|
5
|
+
*/
|
|
6
|
+
export class MysqlAdapter {
|
|
7
|
+
constructor(connectionInfo) {
|
|
8
|
+
this.connection = null;
|
|
9
|
+
this.host = connectionInfo.host;
|
|
10
|
+
this.database = connectionInfo.database;
|
|
11
|
+
this.awsIamAuth = connectionInfo.awsIamAuth || false;
|
|
12
|
+
this.awsRegion = connectionInfo.awsRegion;
|
|
13
|
+
this.config = {
|
|
14
|
+
host: connectionInfo.host,
|
|
15
|
+
database: connectionInfo.database,
|
|
16
|
+
port: connectionInfo.port || 3306,
|
|
17
|
+
user: connectionInfo.user,
|
|
18
|
+
password: connectionInfo.password,
|
|
19
|
+
connectTimeout: connectionInfo.connectionTimeout || 30000,
|
|
20
|
+
multipleStatements: true,
|
|
21
|
+
};
|
|
22
|
+
if (typeof connectionInfo.ssl === 'object' || typeof connectionInfo.ssl === 'string') {
|
|
23
|
+
this.config.ssl = connectionInfo.ssl;
|
|
24
|
+
}
|
|
25
|
+
else if (connectionInfo.ssl === true) {
|
|
26
|
+
// For AWS IAM authentication, configure SSL appropriately for RDS
|
|
27
|
+
if (this.awsIamAuth) {
|
|
28
|
+
this.config.ssl = {
|
|
29
|
+
rejectUnauthorized: false // AWS RDS handles certificate validation
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
this.config.ssl = {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Validate port
|
|
37
|
+
if (connectionInfo.port && typeof connectionInfo.port !== 'number') {
|
|
38
|
+
const parsedPort = parseInt(connectionInfo.port, 10);
|
|
39
|
+
if (isNaN(parsedPort)) {
|
|
40
|
+
throw new Error(`Invalid port value for MySQL: ${connectionInfo.port}`);
|
|
41
|
+
}
|
|
42
|
+
this.config.port = parsedPort;
|
|
43
|
+
}
|
|
44
|
+
// Log the port for debugging
|
|
45
|
+
console.error(`[DEBUG] MySQL connection will use port: ${this.config.port}`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate AWS RDS authentication token
|
|
49
|
+
*/
|
|
50
|
+
async generateAwsAuthToken() {
|
|
51
|
+
if (!this.awsRegion) {
|
|
52
|
+
throw new Error("AWS region is required for IAM authentication");
|
|
53
|
+
}
|
|
54
|
+
if (!this.config.user) {
|
|
55
|
+
throw new Error("AWS username is required for IAM authentication");
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
console.info(`[INFO] Generating AWS auth token for region: ${this.awsRegion}, host: ${this.host}, user: ${this.config.user}`);
|
|
59
|
+
const signer = new Signer({
|
|
60
|
+
region: this.awsRegion,
|
|
61
|
+
hostname: this.host,
|
|
62
|
+
port: this.config.port || 3306,
|
|
63
|
+
username: this.config.user,
|
|
64
|
+
});
|
|
65
|
+
const token = await signer.getAuthToken();
|
|
66
|
+
console.info(`[INFO] AWS auth token generated successfully`);
|
|
67
|
+
return token;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
console.error(`[ERROR] Failed to generate AWS auth token: ${err.message}`);
|
|
71
|
+
throw new Error(`AWS IAM authentication failed: ${err.message}. Please check your AWS credentials and IAM permissions.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Initialize MySQL connection
|
|
76
|
+
*/
|
|
77
|
+
async init() {
|
|
78
|
+
try {
|
|
79
|
+
console.info(`[INFO] Connecting to MySQL: ${this.host}, Database: ${this.database}`);
|
|
80
|
+
// Handle AWS IAM authentication
|
|
81
|
+
if (this.awsIamAuth) {
|
|
82
|
+
console.info(`[INFO] Using AWS IAM authentication for user: ${this.config.user}`);
|
|
83
|
+
try {
|
|
84
|
+
const authToken = await this.generateAwsAuthToken();
|
|
85
|
+
// Create a new config with the generated token as password
|
|
86
|
+
const awsConfig = {
|
|
87
|
+
...this.config,
|
|
88
|
+
password: authToken
|
|
89
|
+
};
|
|
90
|
+
this.connection = await mysql.createConnection(awsConfig);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
console.error(`[ERROR] AWS IAM authentication failed: ${err.message}`);
|
|
94
|
+
throw new Error(`AWS IAM authentication failed: ${err.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
this.connection = await mysql.createConnection(this.config);
|
|
99
|
+
}
|
|
100
|
+
console.info(`[INFO] MySQL connection established successfully`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
console.error(`[ERROR] MySQL connection error: ${err.message}`);
|
|
104
|
+
if (this.awsIamAuth) {
|
|
105
|
+
throw new Error(`Failed to connect to MySQL with AWS IAM authentication: ${err.message}. Please verify your AWS credentials, IAM permissions, and RDS configuration.`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error(`Failed to connect to MySQL: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Execute a SQL query and get all results
|
|
114
|
+
*/
|
|
115
|
+
async all(query, params = []) {
|
|
116
|
+
if (!this.connection) {
|
|
117
|
+
throw new Error("Database not initialized");
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const [rows] = await this.connection.execute(query, params);
|
|
121
|
+
return Array.isArray(rows) ? rows : [];
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
throw new Error(`MySQL query error: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Execute a SQL query that modifies data
|
|
129
|
+
*/
|
|
130
|
+
async run(query, params = []) {
|
|
131
|
+
if (!this.connection) {
|
|
132
|
+
throw new Error("Database not initialized");
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const [result] = await this.connection.execute(query, params);
|
|
136
|
+
const changes = result.affectedRows || 0;
|
|
137
|
+
const lastID = result.insertId || 0;
|
|
138
|
+
return { changes, lastID };
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
throw new Error(`MySQL query error: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Execute multiple SQL statements
|
|
146
|
+
*/
|
|
147
|
+
async exec(query) {
|
|
148
|
+
if (!this.connection) {
|
|
149
|
+
throw new Error("Database not initialized");
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await this.connection.query(query);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
throw new Error(`MySQL batch error: ${err.message}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Close the database connection
|
|
160
|
+
*/
|
|
161
|
+
async close() {
|
|
162
|
+
if (this.connection) {
|
|
163
|
+
await this.connection.end();
|
|
164
|
+
this.connection = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get database metadata
|
|
169
|
+
*/
|
|
170
|
+
getMetadata() {
|
|
171
|
+
return {
|
|
172
|
+
name: "MySQL",
|
|
173
|
+
type: "mysql",
|
|
174
|
+
server: this.host,
|
|
175
|
+
database: this.database,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get database-specific query for listing tables
|
|
180
|
+
*/
|
|
181
|
+
getListTablesQuery() {
|
|
182
|
+
return `SELECT table_name AS name FROM information_schema.tables WHERE table_schema = '${this.database}'`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get database-specific query for describing a table
|
|
186
|
+
*/
|
|
187
|
+
getDescribeTableQuery(tableName) {
|
|
188
|
+
// MySQL DESCRIBE returns columns with different names, so we use a query that matches the expected format
|
|
189
|
+
return `
|
|
190
|
+
SELECT
|
|
191
|
+
COLUMN_NAME as name,
|
|
192
|
+
DATA_TYPE as type,
|
|
193
|
+
CASE WHEN IS_NULLABLE = 'NO' THEN 1 ELSE 0 END as notnull,
|
|
194
|
+
CASE WHEN COLUMN_KEY = 'PRI' THEN 1 ELSE 0 END as pk,
|
|
195
|
+
COLUMN_DEFAULT as dflt_value
|
|
196
|
+
FROM
|
|
197
|
+
INFORMATION_SCHEMA.COLUMNS
|
|
198
|
+
WHERE
|
|
199
|
+
TABLE_NAME = '${tableName}'
|
|
200
|
+
AND TABLE_SCHEMA = '${this.database}'
|
|
201
|
+
ORDER BY
|
|
202
|
+
ORDINAL_POSITION
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL database adapter implementation
|
|
4
|
+
*/
|
|
5
|
+
export class PostgresqlAdapter {
|
|
6
|
+
constructor(connectionInfo) {
|
|
7
|
+
this.client = null;
|
|
8
|
+
this.host = connectionInfo.host;
|
|
9
|
+
this.database = connectionInfo.database;
|
|
10
|
+
// Create PostgreSQL connection config
|
|
11
|
+
this.config = {
|
|
12
|
+
host: connectionInfo.host,
|
|
13
|
+
database: connectionInfo.database,
|
|
14
|
+
port: connectionInfo.port || 5432,
|
|
15
|
+
user: connectionInfo.user,
|
|
16
|
+
password: connectionInfo.password,
|
|
17
|
+
ssl: connectionInfo.ssl,
|
|
18
|
+
// Add connection timeout if provided (in milliseconds)
|
|
19
|
+
connectionTimeoutMillis: connectionInfo.connectionTimeout || 30000,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Initialize PostgreSQL connection
|
|
24
|
+
*/
|
|
25
|
+
async init() {
|
|
26
|
+
try {
|
|
27
|
+
console.error(`[INFO] Connecting to PostgreSQL: ${this.host}, Database: ${this.database}`);
|
|
28
|
+
console.error(`[DEBUG] Connection details:`, {
|
|
29
|
+
host: this.host,
|
|
30
|
+
database: this.database,
|
|
31
|
+
port: this.config.port,
|
|
32
|
+
user: this.config.user,
|
|
33
|
+
connectionTimeoutMillis: this.config.connectionTimeoutMillis,
|
|
34
|
+
ssl: !!this.config.ssl
|
|
35
|
+
});
|
|
36
|
+
this.client = new pg.Client(this.config);
|
|
37
|
+
await this.client.connect();
|
|
38
|
+
console.error(`[INFO] PostgreSQL connection established successfully`);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`[ERROR] PostgreSQL connection error: ${err.message}`);
|
|
42
|
+
throw new Error(`Failed to connect to PostgreSQL: ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Execute a SQL query and get all results
|
|
47
|
+
* @param query SQL query to execute
|
|
48
|
+
* @param params Query parameters
|
|
49
|
+
* @returns Promise with query results
|
|
50
|
+
*/
|
|
51
|
+
async all(query, params = []) {
|
|
52
|
+
if (!this.client) {
|
|
53
|
+
throw new Error("Database not initialized");
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
// PostgreSQL uses $1, $2, etc. for parameterized queries
|
|
57
|
+
const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
|
|
58
|
+
const result = await this.client.query(preparedQuery, params);
|
|
59
|
+
return result.rows;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new Error(`PostgreSQL query error: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Execute a SQL query that modifies data
|
|
67
|
+
* @param query SQL query to execute
|
|
68
|
+
* @param params Query parameters
|
|
69
|
+
* @returns Promise with result info
|
|
70
|
+
*/
|
|
71
|
+
async run(query, params = []) {
|
|
72
|
+
if (!this.client) {
|
|
73
|
+
throw new Error("Database not initialized");
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
// Replace ? with numbered parameters
|
|
77
|
+
const preparedQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
|
|
78
|
+
let lastID = 0;
|
|
79
|
+
let changes = 0;
|
|
80
|
+
// For INSERT queries, try to get the inserted ID
|
|
81
|
+
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
|
82
|
+
// Add RETURNING clause to get the inserted ID if it doesn't already have one
|
|
83
|
+
const returningQuery = preparedQuery.includes('RETURNING')
|
|
84
|
+
? preparedQuery
|
|
85
|
+
: `${preparedQuery} RETURNING id`;
|
|
86
|
+
const result = await this.client.query(returningQuery, params);
|
|
87
|
+
changes = result.rowCount || 0;
|
|
88
|
+
lastID = result.rows[0]?.id || 0;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const result = await this.client.query(preparedQuery, params);
|
|
92
|
+
changes = result.rowCount || 0;
|
|
93
|
+
}
|
|
94
|
+
return { changes, lastID };
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
throw new Error(`PostgreSQL query error: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Execute multiple SQL statements
|
|
102
|
+
* @param query SQL statements to execute
|
|
103
|
+
* @returns Promise that resolves when execution completes
|
|
104
|
+
*/
|
|
105
|
+
async exec(query) {
|
|
106
|
+
if (!this.client) {
|
|
107
|
+
throw new Error("Database not initialized");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await this.client.query(query);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
throw new Error(`PostgreSQL batch error: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Close the database connection
|
|
118
|
+
*/
|
|
119
|
+
async close() {
|
|
120
|
+
if (this.client) {
|
|
121
|
+
await this.client.end();
|
|
122
|
+
this.client = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get database metadata
|
|
127
|
+
*/
|
|
128
|
+
getMetadata() {
|
|
129
|
+
return {
|
|
130
|
+
name: "PostgreSQL",
|
|
131
|
+
type: "postgresql",
|
|
132
|
+
server: this.host,
|
|
133
|
+
database: this.database
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get database-specific query for listing tables
|
|
138
|
+
*/
|
|
139
|
+
getListTablesQuery() {
|
|
140
|
+
return "SELECT table_name as name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get database-specific query for describing a table
|
|
144
|
+
* @param tableName Table name
|
|
145
|
+
*/
|
|
146
|
+
getDescribeTableQuery(tableName) {
|
|
147
|
+
return `
|
|
148
|
+
SELECT
|
|
149
|
+
c.column_name as name,
|
|
150
|
+
c.data_type as type,
|
|
151
|
+
CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END as notnull,
|
|
152
|
+
CASE WHEN pk.constraint_name IS NOT NULL THEN 1 ELSE 0 END as pk,
|
|
153
|
+
c.column_default as dflt_value
|
|
154
|
+
FROM
|
|
155
|
+
information_schema.columns c
|
|
156
|
+
LEFT JOIN
|
|
157
|
+
information_schema.key_column_usage kcu
|
|
158
|
+
ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name
|
|
159
|
+
LEFT JOIN
|
|
160
|
+
information_schema.table_constraints pk
|
|
161
|
+
ON kcu.constraint_name = pk.constraint_name AND pk.constraint_type = 'PRIMARY KEY'
|
|
162
|
+
WHERE
|
|
163
|
+
c.table_name = '${tableName}'
|
|
164
|
+
AND c.table_schema = 'public'
|
|
165
|
+
ORDER BY
|
|
166
|
+
c.ordinal_position
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import sqlite3 from "sqlite3";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite database adapter implementation
|
|
4
|
+
*/
|
|
5
|
+
export class SqliteAdapter {
|
|
6
|
+
constructor(dbPath) {
|
|
7
|
+
this.db = null;
|
|
8
|
+
this.dbPath = dbPath;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the SQLite database connection
|
|
12
|
+
*/
|
|
13
|
+
async init() {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
// Ensure the dbPath is accessible
|
|
16
|
+
console.error(`[INFO] Opening SQLite database at: ${this.dbPath}`);
|
|
17
|
+
this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
console.error(`[ERROR] SQLite connection error: ${err.message}`);
|
|
20
|
+
reject(err);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.error("[INFO] SQLite database opened successfully");
|
|
24
|
+
resolve();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Execute a SQL query and get all results
|
|
31
|
+
* @param query SQL query to execute
|
|
32
|
+
* @param params Query parameters
|
|
33
|
+
* @returns Promise with query results
|
|
34
|
+
*/
|
|
35
|
+
async all(query, params = []) {
|
|
36
|
+
if (!this.db) {
|
|
37
|
+
throw new Error("Database not initialized");
|
|
38
|
+
}
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
this.db.all(query, params, (err, rows) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
reject(err);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resolve(rows);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Execute a SQL query that modifies data
|
|
52
|
+
* @param query SQL query to execute
|
|
53
|
+
* @param params Query parameters
|
|
54
|
+
* @returns Promise with result info
|
|
55
|
+
*/
|
|
56
|
+
async run(query, params = []) {
|
|
57
|
+
if (!this.db) {
|
|
58
|
+
throw new Error("Database not initialized");
|
|
59
|
+
}
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
this.db.run(query, params, function (err) {
|
|
62
|
+
if (err) {
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
resolve({ changes: this.changes, lastID: this.lastID });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute multiple SQL statements
|
|
73
|
+
* @param query SQL statements to execute
|
|
74
|
+
* @returns Promise that resolves when execution completes
|
|
75
|
+
*/
|
|
76
|
+
async exec(query) {
|
|
77
|
+
if (!this.db) {
|
|
78
|
+
throw new Error("Database not initialized");
|
|
79
|
+
}
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
this.db.exec(query, (err) => {
|
|
82
|
+
if (err) {
|
|
83
|
+
reject(err);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Close the database connection
|
|
93
|
+
*/
|
|
94
|
+
async close() {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
if (!this.db) {
|
|
97
|
+
resolve();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.db.close((err) => {
|
|
101
|
+
if (err) {
|
|
102
|
+
reject(err);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.db = null;
|
|
106
|
+
resolve();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get database metadata
|
|
113
|
+
*/
|
|
114
|
+
getMetadata() {
|
|
115
|
+
return {
|
|
116
|
+
name: "SQLite",
|
|
117
|
+
type: "sqlite",
|
|
118
|
+
path: this.dbPath
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get database-specific query for listing tables
|
|
123
|
+
*/
|
|
124
|
+
getListTablesQuery() {
|
|
125
|
+
return "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get database-specific query for describing a table
|
|
129
|
+
* @param tableName Table name
|
|
130
|
+
*/
|
|
131
|
+
getDescribeTableQuery(tableName) {
|
|
132
|
+
return `PRAGMA table_info(${tableName})`;
|
|
133
|
+
}
|
|
134
|
+
}
|