@bytebase/dbhub 0.0.8 → 0.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/README.md +34 -33
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1489 -13
- package/dist/resources/employee-sqlite/employee.sql +117 -0
- package/dist/resources/employee-sqlite/load_department.sql +10 -0
- package/dist/resources/employee-sqlite/load_dept_emp.sql +1103 -0
- package/dist/resources/employee-sqlite/load_dept_manager.sql +17 -0
- package/dist/resources/employee-sqlite/load_employee.sql +1000 -0
- package/dist/resources/employee-sqlite/load_salary1.sql +9488 -0
- package/dist/resources/employee-sqlite/load_title.sql +1470 -0
- package/dist/resources/employee-sqlite/object.sql +74 -0
- package/dist/resources/employee-sqlite/show_elapsed.sql +4 -0
- package/dist/resources/employee-sqlite/test_employee_md5.sql +119 -0
- package/package.json +5 -4
- package/dist/config/demo-loader.js +0 -45
- package/dist/config/env.js +0 -146
- package/dist/connectors/interface.js +0 -55
- package/dist/connectors/manager.js +0 -91
- package/dist/connectors/mysql/index.js +0 -169
- package/dist/connectors/postgres/index.js +0 -172
- package/dist/connectors/sqlite/index.js +0 -208
- package/dist/connectors/sqlserver/index.js +0 -186
- package/dist/prompts/db-explainer.js +0 -201
- package/dist/prompts/index.js +0 -11
- package/dist/prompts/sql-generator.js +0 -114
- package/dist/resources/index.js +0 -12
- package/dist/resources/schema.js +0 -36
- package/dist/resources/tables.js +0 -17
- package/dist/server.js +0 -140
- package/dist/tools/index.js +0 -11
- package/dist/tools/list-connectors.js +0 -23
- package/dist/tools/run-query.js +0 -32
- package/dist/utils/response-formatter.js +0 -109
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import mysql from 'mysql2/promise';
|
|
2
|
-
import { ConnectorRegistry } from '../interface.js';
|
|
3
|
-
/**
|
|
4
|
-
* MySQL DSN Parser
|
|
5
|
-
* Handles DSN strings like: mysql://user:password@localhost:3306/dbname
|
|
6
|
-
*/
|
|
7
|
-
class MySQLDSNParser {
|
|
8
|
-
parse(dsn) {
|
|
9
|
-
// Basic validation
|
|
10
|
-
if (!this.isValidDSN(dsn)) {
|
|
11
|
-
throw new Error(`Invalid MySQL DSN: ${dsn}`);
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
const url = new URL(dsn);
|
|
15
|
-
const config = {
|
|
16
|
-
host: url.hostname,
|
|
17
|
-
port: url.port ? parseInt(url.port) : 3306,
|
|
18
|
-
database: url.pathname.substring(1), // Remove leading '/'
|
|
19
|
-
user: url.username,
|
|
20
|
-
password: url.password,
|
|
21
|
-
};
|
|
22
|
-
// Handle query parameters
|
|
23
|
-
url.searchParams.forEach((value, key) => {
|
|
24
|
-
if (key === 'ssl') {
|
|
25
|
-
config.ssl = value === 'true' ? {} : undefined;
|
|
26
|
-
}
|
|
27
|
-
// Add other parameters as needed
|
|
28
|
-
});
|
|
29
|
-
return config;
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
throw new Error(`Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
getSampleDSN() {
|
|
36
|
-
return 'mysql://root:password@localhost:3306/mysql';
|
|
37
|
-
}
|
|
38
|
-
isValidDSN(dsn) {
|
|
39
|
-
try {
|
|
40
|
-
const url = new URL(dsn);
|
|
41
|
-
return url.protocol === 'mysql:';
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* MySQL Connector Implementation
|
|
50
|
-
*/
|
|
51
|
-
export class MySQLConnector {
|
|
52
|
-
constructor() {
|
|
53
|
-
this.id = 'mysql';
|
|
54
|
-
this.name = 'MySQL';
|
|
55
|
-
this.dsnParser = new MySQLDSNParser();
|
|
56
|
-
this.pool = null;
|
|
57
|
-
}
|
|
58
|
-
async connect(dsn) {
|
|
59
|
-
try {
|
|
60
|
-
const config = this.dsnParser.parse(dsn);
|
|
61
|
-
this.pool = mysql.createPool(config);
|
|
62
|
-
// Test the connection
|
|
63
|
-
const [rows] = await this.pool.query('SELECT 1');
|
|
64
|
-
console.error("Successfully connected to MySQL database");
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
console.error("Failed to connect to MySQL database:", err);
|
|
68
|
-
throw err;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async disconnect() {
|
|
72
|
-
if (this.pool) {
|
|
73
|
-
await this.pool.end();
|
|
74
|
-
this.pool = null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async getTables() {
|
|
78
|
-
if (!this.pool) {
|
|
79
|
-
throw new Error('Not connected to database');
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
// Get all tables from the current database
|
|
83
|
-
const [rows] = await this.pool.query(`
|
|
84
|
-
SELECT table_name
|
|
85
|
-
FROM information_schema.tables
|
|
86
|
-
WHERE table_schema = DATABASE()
|
|
87
|
-
ORDER BY table_name
|
|
88
|
-
`);
|
|
89
|
-
return rows.map(row => row.table_name);
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
console.error("Error getting tables:", error);
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async tableExists(tableName) {
|
|
97
|
-
if (!this.pool) {
|
|
98
|
-
throw new Error('Not connected to database');
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
const [rows] = await this.pool.query(`
|
|
102
|
-
SELECT COUNT(*) as count
|
|
103
|
-
FROM information_schema.tables
|
|
104
|
-
WHERE table_schema = DATABASE()
|
|
105
|
-
AND table_name = ?
|
|
106
|
-
`, [tableName]);
|
|
107
|
-
return rows[0].count > 0;
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
console.error("Error checking if table exists:", error);
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async getTableSchema(tableName) {
|
|
115
|
-
if (!this.pool) {
|
|
116
|
-
throw new Error('Not connected to database');
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
// Get table columns
|
|
120
|
-
const [rows] = await this.pool.query(`
|
|
121
|
-
SELECT
|
|
122
|
-
column_name,
|
|
123
|
-
data_type,
|
|
124
|
-
is_nullable,
|
|
125
|
-
column_default
|
|
126
|
-
FROM information_schema.columns
|
|
127
|
-
WHERE table_schema = DATABASE()
|
|
128
|
-
AND table_name = ?
|
|
129
|
-
ORDER BY ordinal_position
|
|
130
|
-
`, [tableName]);
|
|
131
|
-
return rows;
|
|
132
|
-
}
|
|
133
|
-
catch (error) {
|
|
134
|
-
console.error("Error getting table schema:", error);
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async executeQuery(query) {
|
|
139
|
-
if (!this.pool) {
|
|
140
|
-
throw new Error('Not connected to database');
|
|
141
|
-
}
|
|
142
|
-
const safetyCheck = this.validateQuery(query);
|
|
143
|
-
if (!safetyCheck.isValid) {
|
|
144
|
-
throw new Error(safetyCheck.message || "Query validation failed");
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
const [rows, fields] = await this.pool.query(query);
|
|
148
|
-
return { rows, fields };
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
console.error("Error executing query:", error);
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
validateQuery(query) {
|
|
156
|
-
// Basic check to prevent non-SELECT queries
|
|
157
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
158
|
-
if (!normalizedQuery.startsWith('select')) {
|
|
159
|
-
return {
|
|
160
|
-
isValid: false,
|
|
161
|
-
message: "Only SELECT queries are allowed for security reasons."
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return { isValid: true };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Create and register the connector
|
|
168
|
-
const mysqlConnector = new MySQLConnector();
|
|
169
|
-
ConnectorRegistry.register(mysqlConnector);
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import pg from 'pg';
|
|
2
|
-
const { Pool } = pg;
|
|
3
|
-
import { ConnectorRegistry } from '../interface.js';
|
|
4
|
-
/**
|
|
5
|
-
* PostgreSQL DSN Parser
|
|
6
|
-
* Handles DSN strings like: postgres://user:password@localhost:5432/dbname?sslmode=disable
|
|
7
|
-
*/
|
|
8
|
-
class PostgresDSNParser {
|
|
9
|
-
parse(dsn) {
|
|
10
|
-
// Basic validation
|
|
11
|
-
if (!this.isValidDSN(dsn)) {
|
|
12
|
-
throw new Error(`Invalid PostgreSQL DSN: ${dsn}`);
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
// For PostgreSQL, we can actually pass the DSN directly to the Pool constructor
|
|
16
|
-
// But we'll parse it here for consistency and to extract components if needed
|
|
17
|
-
const url = new URL(dsn);
|
|
18
|
-
const config = {
|
|
19
|
-
host: url.hostname,
|
|
20
|
-
port: url.port ? parseInt(url.port) : 5432,
|
|
21
|
-
database: url.pathname.substring(1), // Remove leading '/'
|
|
22
|
-
user: url.username,
|
|
23
|
-
password: url.password,
|
|
24
|
-
};
|
|
25
|
-
// Handle query parameters (like sslmode, etc.)
|
|
26
|
-
url.searchParams.forEach((value, key) => {
|
|
27
|
-
if (key === 'sslmode') {
|
|
28
|
-
config.ssl = value !== 'disable';
|
|
29
|
-
}
|
|
30
|
-
// Add other parameters as needed
|
|
31
|
-
});
|
|
32
|
-
return config;
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
throw new Error(`Failed to parse PostgreSQL DSN: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
getSampleDSN() {
|
|
39
|
-
return 'postgres://postgres:password@localhost:5432/postgres?sslmode=disable';
|
|
40
|
-
}
|
|
41
|
-
isValidDSN(dsn) {
|
|
42
|
-
try {
|
|
43
|
-
const url = new URL(dsn);
|
|
44
|
-
return url.protocol === 'postgres:' || url.protocol === 'postgresql:';
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* PostgreSQL Connector Implementation
|
|
53
|
-
*/
|
|
54
|
-
export class PostgresConnector {
|
|
55
|
-
constructor() {
|
|
56
|
-
this.id = 'postgres';
|
|
57
|
-
this.name = 'PostgreSQL';
|
|
58
|
-
this.dsnParser = new PostgresDSNParser();
|
|
59
|
-
this.pool = null;
|
|
60
|
-
}
|
|
61
|
-
async connect(dsn) {
|
|
62
|
-
try {
|
|
63
|
-
const config = this.dsnParser.parse(dsn);
|
|
64
|
-
this.pool = new Pool(config);
|
|
65
|
-
// Test the connection
|
|
66
|
-
const client = await this.pool.connect();
|
|
67
|
-
console.error("Successfully connected to PostgreSQL database");
|
|
68
|
-
client.release();
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
console.error("Failed to connect to PostgreSQL database:", err);
|
|
72
|
-
throw err;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async disconnect() {
|
|
76
|
-
if (this.pool) {
|
|
77
|
-
await this.pool.end();
|
|
78
|
-
this.pool = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async getTables() {
|
|
82
|
-
if (!this.pool) {
|
|
83
|
-
throw new Error('Not connected to database');
|
|
84
|
-
}
|
|
85
|
-
const client = await this.pool.connect();
|
|
86
|
-
try {
|
|
87
|
-
const result = await client.query(`
|
|
88
|
-
SELECT table_name
|
|
89
|
-
FROM information_schema.tables
|
|
90
|
-
WHERE table_schema = 'public'
|
|
91
|
-
ORDER BY table_name
|
|
92
|
-
`);
|
|
93
|
-
return result.rows.map(row => row.table_name);
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
client.release();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
async tableExists(tableName) {
|
|
100
|
-
if (!this.pool) {
|
|
101
|
-
throw new Error('Not connected to database');
|
|
102
|
-
}
|
|
103
|
-
const client = await this.pool.connect();
|
|
104
|
-
try {
|
|
105
|
-
const result = await client.query(`
|
|
106
|
-
SELECT EXISTS (
|
|
107
|
-
SELECT FROM information_schema.tables
|
|
108
|
-
WHERE table_schema = 'public'
|
|
109
|
-
AND table_name = $1
|
|
110
|
-
)
|
|
111
|
-
`, [tableName]);
|
|
112
|
-
return result.rows[0].exists;
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
client.release();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async getTableSchema(tableName) {
|
|
119
|
-
if (!this.pool) {
|
|
120
|
-
throw new Error('Not connected to database');
|
|
121
|
-
}
|
|
122
|
-
const client = await this.pool.connect();
|
|
123
|
-
try {
|
|
124
|
-
// Get table columns
|
|
125
|
-
const result = await client.query(`
|
|
126
|
-
SELECT
|
|
127
|
-
column_name,
|
|
128
|
-
data_type,
|
|
129
|
-
is_nullable,
|
|
130
|
-
column_default
|
|
131
|
-
FROM information_schema.columns
|
|
132
|
-
WHERE table_schema = 'public'
|
|
133
|
-
AND table_name = $1
|
|
134
|
-
ORDER BY ordinal_position
|
|
135
|
-
`, [tableName]);
|
|
136
|
-
return result.rows;
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
client.release();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
async executeQuery(query) {
|
|
143
|
-
if (!this.pool) {
|
|
144
|
-
throw new Error('Not connected to database');
|
|
145
|
-
}
|
|
146
|
-
const safetyCheck = this.validateQuery(query);
|
|
147
|
-
if (!safetyCheck.isValid) {
|
|
148
|
-
throw new Error(safetyCheck.message || "Query validation failed");
|
|
149
|
-
}
|
|
150
|
-
const client = await this.pool.connect();
|
|
151
|
-
try {
|
|
152
|
-
return await client.query(query);
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
client.release();
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
validateQuery(query) {
|
|
159
|
-
// Basic check to prevent non-SELECT queries
|
|
160
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
161
|
-
if (!normalizedQuery.startsWith('select')) {
|
|
162
|
-
return {
|
|
163
|
-
isValid: false,
|
|
164
|
-
message: "Only SELECT queries are allowed for security reasons."
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
return { isValid: true };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Create and register the connector
|
|
171
|
-
const postgresConnector = new PostgresConnector();
|
|
172
|
-
ConnectorRegistry.register(postgresConnector);
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite Connector Implementation
|
|
3
|
-
*
|
|
4
|
-
* Implements SQLite database connectivity for DBHub
|
|
5
|
-
* To use this connector:
|
|
6
|
-
* 1. Set DSN=sqlite:///path/to/database.db in your .env file
|
|
7
|
-
* 2. Or set DB_CONNECTOR_TYPE=sqlite for default in-memory database
|
|
8
|
-
*/
|
|
9
|
-
import { ConnectorRegistry } from '../interface.js';
|
|
10
|
-
import sqlite3 from 'sqlite3';
|
|
11
|
-
/**
|
|
12
|
-
* SQLite DSN Parser
|
|
13
|
-
* Handles DSN strings like:
|
|
14
|
-
* - sqlite:///path/to/database.db (absolute path)
|
|
15
|
-
* - sqlite://./relative/path/to/database.db (relative path)
|
|
16
|
-
* - sqlite::memory: (in-memory database)
|
|
17
|
-
*/
|
|
18
|
-
class SQLiteDSNParser {
|
|
19
|
-
parse(dsn) {
|
|
20
|
-
// Basic validation
|
|
21
|
-
if (!this.isValidDSN(dsn)) {
|
|
22
|
-
throw new Error(`Invalid SQLite DSN: ${dsn}`);
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
const url = new URL(dsn);
|
|
26
|
-
let dbPath;
|
|
27
|
-
// Handle in-memory database
|
|
28
|
-
if (url.hostname === '' && url.pathname === ':memory:') {
|
|
29
|
-
dbPath = ':memory:';
|
|
30
|
-
}
|
|
31
|
-
// Handle file paths
|
|
32
|
-
else {
|
|
33
|
-
// Get the path part, handling both relative and absolute paths
|
|
34
|
-
if (url.pathname.startsWith('//')) {
|
|
35
|
-
// Absolute path: sqlite:///path/to/db.sqlite
|
|
36
|
-
dbPath = url.pathname.substring(2); // Remove leading //
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
// Relative path: sqlite://./path/to/db.sqlite
|
|
40
|
-
dbPath = url.pathname;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return { dbPath };
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
throw new Error(`Failed to parse SQLite DSN: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
getSampleDSN() {
|
|
50
|
-
return 'sqlite:///path/to/database.db';
|
|
51
|
-
}
|
|
52
|
-
isValidDSN(dsn) {
|
|
53
|
-
try {
|
|
54
|
-
const url = new URL(dsn);
|
|
55
|
-
return url.protocol === 'sqlite:';
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export class SQLiteConnector {
|
|
63
|
-
constructor() {
|
|
64
|
-
this.id = 'sqlite';
|
|
65
|
-
this.name = 'SQLite';
|
|
66
|
-
this.dsnParser = new SQLiteDSNParser();
|
|
67
|
-
this.db = null;
|
|
68
|
-
this.dbPath = ':memory:'; // Default to in-memory database
|
|
69
|
-
}
|
|
70
|
-
async connect(dsn, initScript) {
|
|
71
|
-
const config = this.dsnParser.parse(dsn);
|
|
72
|
-
this.dbPath = config.dbPath;
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
this.db = new sqlite3.Database(this.dbPath, (err) => {
|
|
75
|
-
if (err) {
|
|
76
|
-
console.error("Failed to connect to SQLite database:", err);
|
|
77
|
-
reject(err);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
// Can't use console.log here because it will break the stdio transport
|
|
81
|
-
console.error("Successfully connected to SQLite database");
|
|
82
|
-
// If an initialization script is provided, run it
|
|
83
|
-
if (initScript) {
|
|
84
|
-
this.db.exec(initScript, (err) => {
|
|
85
|
-
if (err) {
|
|
86
|
-
console.error("Failed to initialize database with script:", err);
|
|
87
|
-
reject(err);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.error("Successfully initialized database with script");
|
|
91
|
-
resolve();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
resolve();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
async disconnect() {
|
|
103
|
-
// Close the SQLite connection
|
|
104
|
-
if (this.db) {
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
this.db.close((err) => {
|
|
107
|
-
if (err) {
|
|
108
|
-
reject(err);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
this.db = null;
|
|
112
|
-
resolve();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
return Promise.resolve();
|
|
118
|
-
}
|
|
119
|
-
async getTables() {
|
|
120
|
-
if (!this.db) {
|
|
121
|
-
throw new Error("Not connected to SQLite database");
|
|
122
|
-
}
|
|
123
|
-
return new Promise((resolve, reject) => {
|
|
124
|
-
this.db.all(`SELECT name FROM sqlite_master
|
|
125
|
-
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
126
|
-
ORDER BY name`, (err, rows) => {
|
|
127
|
-
if (err) {
|
|
128
|
-
reject(err);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
resolve(rows.map(row => row.name));
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
async tableExists(tableName) {
|
|
137
|
-
if (!this.db) {
|
|
138
|
-
throw new Error("Not connected to SQLite database");
|
|
139
|
-
}
|
|
140
|
-
return new Promise((resolve, reject) => {
|
|
141
|
-
this.db.get(`SELECT name FROM sqlite_master
|
|
142
|
-
WHERE type='table' AND name = ?`, [tableName], (err, row) => {
|
|
143
|
-
if (err) {
|
|
144
|
-
reject(err);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
resolve(!!row);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
async getTableSchema(tableName) {
|
|
153
|
-
if (!this.db) {
|
|
154
|
-
throw new Error("Not connected to SQLite database");
|
|
155
|
-
}
|
|
156
|
-
return new Promise((resolve, reject) => {
|
|
157
|
-
this.db.all(`PRAGMA table_info(${tableName})`, (err, rows) => {
|
|
158
|
-
if (err) {
|
|
159
|
-
reject(err);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// Convert SQLite schema format to our standard TableColumn format
|
|
163
|
-
const columns = rows.map(row => ({
|
|
164
|
-
column_name: row.name,
|
|
165
|
-
data_type: row.type,
|
|
166
|
-
is_nullable: row.notnull === 0 ? 'YES' : 'NO', // In SQLite, 0 means nullable
|
|
167
|
-
column_default: row.dflt_value
|
|
168
|
-
}));
|
|
169
|
-
resolve(columns);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
async executeQuery(query) {
|
|
175
|
-
if (!this.db) {
|
|
176
|
-
throw new Error("Not connected to SQLite database");
|
|
177
|
-
}
|
|
178
|
-
// Validate query for safety
|
|
179
|
-
const safetyCheck = this.validateQuery(query);
|
|
180
|
-
if (!safetyCheck.isValid) {
|
|
181
|
-
throw new Error(safetyCheck.message || "Query validation failed");
|
|
182
|
-
}
|
|
183
|
-
return new Promise((resolve, reject) => {
|
|
184
|
-
this.db.all(query, (err, rows) => {
|
|
185
|
-
if (err) {
|
|
186
|
-
reject(err);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
resolve({ rows });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
validateQuery(query) {
|
|
195
|
-
// Basic check to prevent non-SELECT queries
|
|
196
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
197
|
-
if (!normalizedQuery.startsWith('select')) {
|
|
198
|
-
return {
|
|
199
|
-
isValid: false,
|
|
200
|
-
message: "Only SELECT queries are allowed for security reasons."
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return { isValid: true };
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
// Register the SQLite connector
|
|
207
|
-
const sqliteConnector = new SQLiteConnector();
|
|
208
|
-
ConnectorRegistry.register(sqliteConnector);
|