Skip to content

Commit da698b5

Browse files
authored
Merge pull request #1477 from tonyatml/main
Add Exasol db connection support.
2 parents ae9de4d + 5838048 commit da698b5

File tree

10 files changed

+515
-9
lines changed

10 files changed

+515
-9
lines changed

.DS_Store

6 KB
Binary file not shown.

package.json

+20-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
"url": "https://github.com/cweijan/vscode-database-client.git"
3030
},
3131
"activationEvents": [
32-
"*"
32+
"onCommand:mysql.connection.add",
33+
"onCommand:mysql.refresh",
34+
"onView:github.cweijan.mysql",
35+
"onView:github.cweijan.nosql"
3336
],
3437
"main": "./out/extension",
3538
"contributes": {
@@ -1018,18 +1021,23 @@
10181021
},
10191022
"devDependencies": {
10201023
"@antv/g2": "^4.0.9",
1024+
"@babel/core": "^7.26.10",
1025+
"@babel/preset-env": "^7.26.9",
1026+
"@types/bson": "^4.2.4",
1027+
"@types/ioredis": "^4.22.0",
10211028
"@types/mongodb": "^3.6.3",
10221029
"@types/node": "^12.12.6",
1023-
"@types/ssh2": "^0.5.43",
1024-
"@types/vscode": "1.51.0",
10251030
"@types/pg": "^7.14.7",
10261031
"@types/redis": "^2.8.18",
1032+
"@types/ssh2": "^0.5.43",
10271033
"@types/tedious": "^4.0.3",
1034+
"@types/vscode": "1.51.0",
1035+
"@types/webpack-sources": "^3.2.3",
1036+
"babel-loader": "^8.1.0",
10281037
"copy-webpack-plugin": "^6.3.0",
10291038
"css-loader": "^3.5.3",
10301039
"file-loader": "^6.0.0",
10311040
"html-webpack-plugin": "^4.3.0",
1032-
"@types/ioredis": "^4.22.0",
10331041
"postcss": "^8.2.1",
10341042
"postcss-loader": "^4.1.0",
10351043
"style-loader": "^1.2.1",
@@ -1044,9 +1052,13 @@
10441052
"webpack-cli": "^3.3.11"
10451053
},
10461054
"dependencies": {
1055+
"@exasol/exasol-driver-ts": "^0.3.0",
1056+
"@types/ws": "^8.18.0",
10471057
"axios": "^0.21.1",
1058+
"bson": "^4.7.2",
1059+
"bufferutil": "^4.0.9",
10481060
"command-exists": "^1.2.9",
1049-
"comment-json": "^4.1.0",
1061+
"comment-json": "^3.0.3",
10501062
"compare-versions": "^3.6.0",
10511063
"date-format": "^3.0.0",
10521064
"deepmerge": "^3.2.0",
@@ -1068,10 +1080,12 @@
10681080
"routington": "^1.0.3",
10691081
"sqlstring": "^2.3.2",
10701082
"ssh2": "0.5.4",
1071-
"supports-color": "^9.0.1",
1083+
"supports-color": "^8.1.1",
10721084
"umy-table": "1.0.8",
1085+
"utf-8-validate": "^6.0.5",
10731086
"vue": "^2.6.11",
10741087
"vue-router": "^3.4.1",
1088+
"ws": "^8.18.1",
10751089
"xregexp": "2.0.0",
10761090
"xterm": "^4.12.0",
10771091
"xterm-addon-fit": "^0.5.0",

src/.DS_Store

6 KB
Binary file not shown.

src/common/constants.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ export enum Confirm {
4949
}
5050

5151
export enum DatabaseType {
52-
MYSQL = "MySQL", PG = "PostgreSQL",SQLITE = "SQLite",
53-
MSSQL = "SqlServer", MONGO_DB="MongoDB",
54-
ES = "ElasticSearch", REDIS = "Redis",SSH="SSH",FTP="FTP"
52+
MYSQL = "MySQL", PG = "PostgreSQL", SQLITE = "SQLite",
53+
MSSQL = "SqlServer", MONGO_DB = "MongoDB",
54+
ES = "ElasticSearch", REDIS = "Redis", SSH = "SSH", FTP = "FTP",
55+
EXASOL = "Exasol"
5556
}
5657

5758
export enum ModelType {

src/service/.DS_Store

6 KB
Binary file not shown.
+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import { ExasolDriver } from '@exasol/exasol-driver-ts';
2+
import { Node } from '../../model/interface/node';
3+
import { IConnection } from './connection';
4+
import { Console } from '../../common/Console';
5+
import { EventEmitter } from 'events';
6+
import { ConnectionNode } from '../../model/database/connectionNode';
7+
import { DatabaseType } from '../../common/constants';
8+
import { DbTreeDataProvider } from '../../provider/treeDataProvider';
9+
import { CommandKey } from '../../model/interface/node';
10+
import { ConnectionManager } from '../../service/connectionManager';
11+
import { GlobalState } from '../../common/state';
12+
import { CacheKey } from '../../common/constants';
13+
import { WebSocket } from 'ws';
14+
import { DatabaseCache } from '../common/databaseCache';
15+
import { SchemaNode } from '../../model/database/schemaNode';
16+
import * as vscode from 'vscode';
17+
18+
type queryCallback = (error: Error | null, rows?: any[], fields?: any[]) => void;
19+
20+
export class ExasolConnection extends IConnection {
21+
private driver: ExasolDriver;
22+
protected node: Node;
23+
private connected: boolean;
24+
private websocket: WebSocket | null = null;
25+
26+
constructor(node: Node) {
27+
super();
28+
this.node = node;
29+
this.node.host = node.host || '127.0.0.1';
30+
this.node.port = node.port || 8563;
31+
this.node.user = node.user || 'sys';
32+
this.node.password = node.password || '';
33+
this.connected = false;
34+
}
35+
36+
public connect(callback: (err: Error) => void): void {
37+
if (this.connected) {
38+
callback(null);
39+
return;
40+
}
41+
42+
Console.log('[Exasol] Attempting to connect to: ' + this.node.host);
43+
44+
const websocketFactory = (url: string) => {
45+
if (this.websocket) {
46+
return this.websocket;
47+
}
48+
49+
this.websocket = new WebSocket(url);
50+
this.websocket.on('error', () => this.websocket = null);
51+
this.websocket.on('close', () => {
52+
this.websocket = null;
53+
this.connected = false;
54+
});
55+
56+
return this.websocket;
57+
};
58+
59+
this.driver = new ExasolDriver(
60+
websocketFactory,
61+
{
62+
host: String(this.node.host.trim()),
63+
port: Number(this.node.port),
64+
user: String(this.node.user),
65+
password: String(this.node.password),
66+
clientName: 'VSCode Database Client',
67+
clientVersion: '3.9.8',
68+
autocommit: true,
69+
encryption: true,
70+
compression: false,
71+
fetchSize: 1000,
72+
resultSetMaxRows: 100000
73+
}
74+
);
75+
76+
this.driver.connect()
77+
.then(() => {
78+
this.connected = true;
79+
callback(null);
80+
})
81+
.catch(error => {
82+
this.connected = false;
83+
callback(error);
84+
});
85+
}
86+
87+
public query(sql: string, callback?: queryCallback): void | EventEmitter;
88+
public query(sql: string, values: any, callback?: queryCallback): void | EventEmitter;
89+
public query(sql: any, values?: any, callback?: any) {
90+
if (!callback && values instanceof Function) {
91+
callback = values;
92+
}
93+
94+
const event = new EventEmitter();
95+
96+
const executeQuery = () => {
97+
try {
98+
this.driver.query(sql).then(result => {
99+
const rows = result.getRows() || [];
100+
const columns = result.getColumns() || [];
101+
102+
Console.log('[Exasol] Query result:');
103+
Console.log('[Exasol] Columns: ' + JSON.stringify(columns, null, 2));
104+
Console.log('[Exasol] Row count: ' + rows.length);
105+
Console.log('[Exasol] Rows: ' + JSON.stringify(rows, null, 2));
106+
107+
if (!callback) {
108+
if (rows.length === 0) {
109+
event.emit("end");
110+
}
111+
for (let i = 1; i <= rows.length; i++) {
112+
const row = rows[i - 1];
113+
event.emit("result", this.convertToDump(row), rows.length === i);
114+
}
115+
} else {
116+
// 将结果转换为标准格式
117+
const fields = columns.map(col => ({
118+
name: col.name,
119+
dataType: col.dataType
120+
}));
121+
122+
// 如果是非 SELECT 语句
123+
if (!columns.length) {
124+
callback(null, { affectedRows: rows.length });
125+
} else {
126+
// 将行数据转换为对象格式,并处理特殊的 schema 查询
127+
const formattedRows = rows.map((row, rowIndex) => {
128+
const obj: { [key: string]: any } = {};
129+
// 处理数组格式的行数据
130+
if (Array.isArray(row)) {
131+
columns.forEach((col, colIndex) => {
132+
const value = row[colIndex];
133+
if (col.name === 'SCHEMA_NAME') {
134+
obj.schema = value;
135+
obj.Database = value;
136+
} else if (col.name === 'TABLE_NAME') {
137+
obj.name = value;
138+
} else if (col.name === 'COLUMN_NAME') {
139+
obj.name = value;
140+
} else if (col.name === 'COLUMN_TYPE') {
141+
obj.type = value;
142+
obj.simpleType = value;
143+
} else if (col.name === 'IS_NULLABLE') {
144+
obj.nullable = value;
145+
} else if (col.name === 'COLUMN_DEFAULT') {
146+
obj.defaultValue = value;
147+
} else if (col.name === 'name') {
148+
obj.name = value;
149+
} else if (col.name === 'type') {
150+
obj.type = value;
151+
obj.simpleType = value;
152+
} else {
153+
obj[col.name] = value;
154+
}
155+
});
156+
} else {
157+
// 处理对象格式的行数据
158+
if (row.SCHEMA_NAME) {
159+
obj.schema = row.SCHEMA_NAME;
160+
obj.Database = row.SCHEMA_NAME;
161+
}
162+
if (row.TABLE_NAME) {
163+
obj.name = row.TABLE_NAME;
164+
}
165+
if (row.COLUMN_NAME) {
166+
obj.name = row.COLUMN_NAME;
167+
}
168+
if (row.COLUMN_TYPE) {
169+
obj.type = row.COLUMN_TYPE;
170+
obj.simpleType = row.COLUMN_TYPE;
171+
}
172+
if (row.IS_NULLABLE) {
173+
obj.nullable = row.IS_NULLABLE;
174+
}
175+
if (row.COLUMN_DEFAULT) {
176+
obj.defaultValue = row.COLUMN_DEFAULT;
177+
}
178+
if (row.name) {
179+
obj.name = row.name;
180+
}
181+
if (row.type) {
182+
obj.type = row.type;
183+
obj.simpleType = row.type;
184+
}
185+
Object.keys(row).forEach(key => {
186+
if (!['name', 'type'].includes(key)) {
187+
obj[key] = row[key];
188+
}
189+
});
190+
}
191+
return obj;
192+
});
193+
194+
// 如果是查询 schema 列表,确保每个结果都有 schema 字段
195+
if (sql.includes('SYS.EXA_SCHEMAS')) {
196+
Console.log('[Exasol] Schema list: ' + JSON.stringify(formattedRows));
197+
}
198+
199+
callback(null, formattedRows, fields);
200+
}
201+
}
202+
}).catch(err => {
203+
if (callback) {
204+
callback(err);
205+
}
206+
event.emit("error", err.message);
207+
});
208+
} catch (err) {
209+
if (callback) {
210+
callback(err);
211+
}
212+
event.emit("error", err.message);
213+
}
214+
};
215+
216+
if (!this.connected) {
217+
this.connect((err) => {
218+
if (err) {
219+
if (callback) {
220+
callback(err);
221+
}
222+
event.emit("error", err.message);
223+
} else {
224+
executeQuery();
225+
}
226+
});
227+
} else {
228+
executeQuery();
229+
}
230+
231+
return event;
232+
}
233+
234+
public close(callback?: (err: Error) => void): void {
235+
if (this.driver) {
236+
this.driver.close()
237+
.then(() => {
238+
this.connected = false;
239+
if (this.websocket) {
240+
this.websocket.close();
241+
this.websocket = null;
242+
}
243+
if (callback) callback(null);
244+
})
245+
.catch(error => {
246+
if (callback) callback(error);
247+
});
248+
} else if (callback) {
249+
callback(null);
250+
}
251+
}
252+
253+
public end(callback?: (err: Error) => void): void {
254+
this.close(callback);
255+
}
256+
257+
public beginTransaction(callback?: (err: Error) => void): void {
258+
this.query('START TRANSACTION', callback);
259+
}
260+
261+
public commit(callback?: (err: Error) => void): void {
262+
this.query('COMMIT', callback);
263+
}
264+
265+
public rollback(callback?: (err: Error) => void): void {
266+
this.query('ROLLBACK', callback);
267+
}
268+
269+
public isAlive(): boolean {
270+
return this.connected;
271+
}
272+
}

src/service/connectionManager.ts

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { FTPConnection } from "./connect/ftpConnection";
1919
import { SqliteConnection } from "./connect/sqliteConnection";
2020
import { Console } from "@/common/Console";
2121
import { MongoConnection } from "./connect/mongoConnection";
22+
import { ExasolConnection } from './connect/exasolConnection';
2223

2324
interface ConnectionWrapper {
2425
connection: IConnection;
@@ -155,6 +156,8 @@ export class ConnectionManager {
155156
return new RedisConnection(opt);
156157
case DatabaseType.FTP:
157158
return new FTPConnection(opt);
159+
case DatabaseType.EXASOL:
160+
return new ExasolConnection(opt);
158161
}
159162
return new MysqlConnection(opt)
160163
}

0 commit comments

Comments
 (0)