Browse Source

Add Exasol db connection support.

pull/1477/head
tonyatml 4 months ago
parent
commit
583804821d
  1. BIN
      .DS_Store
  2. 26
      package.json
  3. BIN
      src/.DS_Store
  4. 7
      src/common/constants.ts
  5. BIN
      src/service/.DS_Store
  6. 272
      src/service/connect/exasolConnection.ts
  7. 3
      src/service/connectionManager.ts
  8. 205
      src/service/dialect/exasolDialect.ts
  9. 5
      src/service/serviceManager.ts
  10. 6
      src/vue/connect/index.vue

BIN
.DS_Store

26
package.json

@ -29,7 +29,10 @@
"url": "https://github.com/cweijan/vscode-database-client.git"
},
"activationEvents": [
"*"
"onCommand:mysql.connection.add",
"onCommand:mysql.refresh",
"onView:github.cweijan.mysql",
"onView:github.cweijan.nosql"
],
"main": "./out/extension",
"contributes": {
@ -1018,18 +1021,23 @@
},
"devDependencies": {
"@antv/g2": "^4.0.9",
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@types/bson": "^4.2.4",
"@types/ioredis": "^4.22.0",
"@types/mongodb": "^3.6.3",
"@types/node": "^12.12.6",
"@types/ssh2": "^0.5.43",
"@types/vscode": "1.51.0",
"@types/pg": "^7.14.7",
"@types/redis": "^2.8.18",
"@types/ssh2": "^0.5.43",
"@types/tedious": "^4.0.3",
"@types/vscode": "1.51.0",
"@types/webpack-sources": "^3.2.3",
"babel-loader": "^8.1.0",
"copy-webpack-plugin": "^6.3.0",
"css-loader": "^3.5.3",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"@types/ioredis": "^4.22.0",
"postcss": "^8.2.1",
"postcss-loader": "^4.1.0",
"style-loader": "^1.2.1",
@ -1044,9 +1052,13 @@
"webpack-cli": "^3.3.11"
},
"dependencies": {
"@exasol/exasol-driver-ts": "^0.3.0",
"@types/ws": "^8.18.0",
"axios": "^0.21.1",
"bson": "^4.7.2",
"bufferutil": "^4.0.9",
"command-exists": "^1.2.9",
"comment-json": "^4.1.0",
"comment-json": "^3.0.3",
"compare-versions": "^3.6.0",
"date-format": "^3.0.0",
"deepmerge": "^3.2.0",
@ -1068,10 +1080,12 @@
"routington": "^1.0.3",
"sqlstring": "^2.3.2",
"ssh2": "0.5.4",
"supports-color": "^9.0.1",
"supports-color": "^8.1.1",
"umy-table": "1.0.8",
"utf-8-validate": "^6.0.5",
"vue": "^2.6.11",
"vue-router": "^3.4.1",
"ws": "^8.18.1",
"xregexp": "2.0.0",
"xterm": "^4.12.0",
"xterm-addon-fit": "^0.5.0",

BIN
src/.DS_Store

7
src/common/constants.ts

@ -49,9 +49,10 @@ export enum Confirm {
}
export enum DatabaseType {
MYSQL = "MySQL", PG = "PostgreSQL",SQLITE = "SQLite",
MSSQL = "SqlServer", MONGO_DB="MongoDB",
ES = "ElasticSearch", REDIS = "Redis",SSH="SSH",FTP="FTP"
MYSQL = "MySQL", PG = "PostgreSQL", SQLITE = "SQLite",
MSSQL = "SqlServer", MONGO_DB = "MongoDB",
ES = "ElasticSearch", REDIS = "Redis", SSH = "SSH", FTP = "FTP",
EXASOL = "Exasol"
}
export enum ModelType {

BIN
src/service/.DS_Store

272
src/service/connect/exasolConnection.ts

@ -0,0 +1,272 @@
import { ExasolDriver } from '@exasol/exasol-driver-ts';
import { Node } from '../../model/interface/node';
import { IConnection } from './connection';
import { Console } from '../../common/Console';
import { EventEmitter } from 'events';
import { ConnectionNode } from '../../model/database/connectionNode';
import { DatabaseType } from '../../common/constants';
import { DbTreeDataProvider } from '../../provider/treeDataProvider';
import { CommandKey } from '../../model/interface/node';
import { ConnectionManager } from '../../service/connectionManager';
import { GlobalState } from '../../common/state';
import { CacheKey } from '../../common/constants';
import { WebSocket } from 'ws';
import { DatabaseCache } from '../common/databaseCache';
import { SchemaNode } from '../../model/database/schemaNode';
import * as vscode from 'vscode';
type queryCallback = (error: Error | null, rows?: any[], fields?: any[]) => void;
export class ExasolConnection extends IConnection {
private driver: ExasolDriver;
protected node: Node;
private connected: boolean;
private websocket: WebSocket | null = null;
constructor(node: Node) {
super();
this.node = node;
this.node.host = node.host || '127.0.0.1';
this.node.port = node.port || 8563;
this.node.user = node.user || 'sys';
this.node.password = node.password || '';
this.connected = false;
}
public connect(callback: (err: Error) => void): void {
if (this.connected) {
callback(null);
return;
}
Console.log('[Exasol] Attempting to connect to: ' + this.node.host);
const websocketFactory = (url: string) => {
if (this.websocket) {
return this.websocket;
}
this.websocket = new WebSocket(url);
this.websocket.on('error', () => this.websocket = null);
this.websocket.on('close', () => {
this.websocket = null;
this.connected = false;
});
return this.websocket;
};
this.driver = new ExasolDriver(
websocketFactory,
{
host: String(this.node.host.trim()),
port: Number(this.node.port),
user: String(this.node.user),
password: String(this.node.password),
clientName: 'VSCode Database Client',
clientVersion: '3.9.8',
autocommit: true,
encryption: true,
compression: false,
fetchSize: 1000,
resultSetMaxRows: 100000
}
);
this.driver.connect()
.then(() => {
this.connected = true;
callback(null);
})
.catch(error => {
this.connected = false;
callback(error);
});
}
public query(sql: string, callback?: queryCallback): void | EventEmitter;
public query(sql: string, values: any, callback?: queryCallback): void | EventEmitter;
public query(sql: any, values?: any, callback?: any) {
if (!callback && values instanceof Function) {
callback = values;
}
const event = new EventEmitter();
const executeQuery = () => {
try {
this.driver.query(sql).then(result => {
const rows = result.getRows() || [];
const columns = result.getColumns() || [];
Console.log('[Exasol] Query result:');
Console.log('[Exasol] Columns: ' + JSON.stringify(columns, null, 2));
Console.log('[Exasol] Row count: ' + rows.length);
Console.log('[Exasol] Rows: ' + JSON.stringify(rows, null, 2));
if (!callback) {
if (rows.length === 0) {
event.emit("end");
}
for (let i = 1; i <= rows.length; i++) {
const row = rows[i - 1];
event.emit("result", this.convertToDump(row), rows.length === i);
}
} else {
// 将结果转换为标准格式
const fields = columns.map(col => ({
name: col.name,
dataType: col.dataType
}));
// 如果是非 SELECT 语句
if (!columns.length) {
callback(null, { affectedRows: rows.length });
} else {
// 将行数据转换为对象格式,并处理特殊的 schema 查询
const formattedRows = rows.map((row, rowIndex) => {
const obj: { [key: string]: any } = {};
// 处理数组格式的行数据
if (Array.isArray(row)) {
columns.forEach((col, colIndex) => {
const value = row[colIndex];
if (col.name === 'SCHEMA_NAME') {
obj.schema = value;
obj.Database = value;
} else if (col.name === 'TABLE_NAME') {
obj.name = value;
} else if (col.name === 'COLUMN_NAME') {
obj.name = value;
} else if (col.name === 'COLUMN_TYPE') {
obj.type = value;
obj.simpleType = value;
} else if (col.name === 'IS_NULLABLE') {
obj.nullable = value;
} else if (col.name === 'COLUMN_DEFAULT') {
obj.defaultValue = value;
} else if (col.name === 'name') {
obj.name = value;
} else if (col.name === 'type') {
obj.type = value;
obj.simpleType = value;
} else {
obj[col.name] = value;
}
});
} else {
// 处理对象格式的行数据
if (row.SCHEMA_NAME) {
obj.schema = row.SCHEMA_NAME;
obj.Database = row.SCHEMA_NAME;
}
if (row.TABLE_NAME) {
obj.name = row.TABLE_NAME;
}
if (row.COLUMN_NAME) {
obj.name = row.COLUMN_NAME;
}
if (row.COLUMN_TYPE) {
obj.type = row.COLUMN_TYPE;
obj.simpleType = row.COLUMN_TYPE;
}
if (row.IS_NULLABLE) {
obj.nullable = row.IS_NULLABLE;
}
if (row.COLUMN_DEFAULT) {
obj.defaultValue = row.COLUMN_DEFAULT;
}
if (row.name) {
obj.name = row.name;
}
if (row.type) {
obj.type = row.type;
obj.simpleType = row.type;
}
Object.keys(row).forEach(key => {
if (!['name', 'type'].includes(key)) {
obj[key] = row[key];
}
});
}
return obj;
});
// 如果是查询 schema 列表,确保每个结果都有 schema 字段
if (sql.includes('SYS.EXA_SCHEMAS')) {
Console.log('[Exasol] Schema list: ' + JSON.stringify(formattedRows));
}
callback(null, formattedRows, fields);
}
}
}).catch(err => {
if (callback) {
callback(err);
}
event.emit("error", err.message);
});
} catch (err) {
if (callback) {
callback(err);
}
event.emit("error", err.message);
}
};
if (!this.connected) {
this.connect((err) => {
if (err) {
if (callback) {
callback(err);
}
event.emit("error", err.message);
} else {
executeQuery();
}
});
} else {
executeQuery();
}
return event;
}
public close(callback?: (err: Error) => void): void {
if (this.driver) {
this.driver.close()
.then(() => {
this.connected = false;
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
if (callback) callback(null);
})
.catch(error => {
if (callback) callback(error);
});
} else if (callback) {
callback(null);
}
}
public end(callback?: (err: Error) => void): void {
this.close(callback);
}
public beginTransaction(callback?: (err: Error) => void): void {
this.query('START TRANSACTION', callback);
}
public commit(callback?: (err: Error) => void): void {
this.query('COMMIT', callback);
}
public rollback(callback?: (err: Error) => void): void {
this.query('ROLLBACK', callback);
}
public isAlive(): boolean {
return this.connected;
}
}

3
src/service/connectionManager.ts

@ -19,6 +19,7 @@ import { FTPConnection } from "./connect/ftpConnection";
import { SqliteConnection } from "./connect/sqliteConnection";
import { Console } from "@/common/Console";
import { MongoConnection } from "./connect/mongoConnection";
import { ExasolConnection } from './connect/exasolConnection';
interface ConnectionWrapper {
connection: IConnection;
@ -155,6 +156,8 @@ export class ConnectionManager {
return new RedisConnection(opt);
case DatabaseType.FTP:
return new FTPConnection(opt);
case DatabaseType.EXASOL:
return new ExasolConnection(opt);
}
return new MysqlConnection(opt)
}

205
src/service/dialect/exasolDialect.ts

@ -0,0 +1,205 @@
import { SqlDialect } from "./sqlDialect";
import { CreateIndexParam } from "./param/createIndexParam";
export class ExasolDialect extends SqlDialect {
createIndex(createIndexParam: CreateIndexParam): string {
return `CREATE INDEX ${createIndexParam.table}_idx ON ${createIndexParam.table} (${createIndexParam.column});`;
}
showVersion(): string {
return "SELECT PARAM_VALUE FROM SYS.EXA_PARAMETERS WHERE PARAM_NAME = 'databaseProductVersion';";
}
showDatabases(): string {
return "SELECT SCHEMA_NAME FROM SYS.EXA_SCHEMAS ORDER BY SCHEMA_NAME;";
}
showSchemas(): string {
return this.showDatabases();
}
showTables(database: string): string {
return `SELECT TABLE_NAME FROM SYS.EXA_ALL_TABLES WHERE TABLE_SCHEMA = '${database}' ORDER BY TABLE_NAME;`;
}
showViews(database: string): string {
return `SELECT VIEW_NAME FROM SYS.EXA_ALL_VIEWS WHERE VIEW_SCHEMA = '${database}' ORDER BY VIEW_NAME;`;
}
showColumns(database: string, table: string): string {
return `SELECT
COLUMN_NAME AS "name",
COLUMN_TYPE AS "type",
COLUMN_TYPE AS "simpleType"
FROM SYS.EXA_ALL_COLUMNS
WHERE COLUMN_SCHEMA = '${database}'
AND COLUMN_TABLE = '${table}'
ORDER BY COLUMN_ORDINAL_POSITION;`;
}
showTriggers(database: string): string {
return `SELECT TRIGGER_NAME FROM SYS.EXA_ALL_TRIGGERS WHERE TRIGGER_SCHEMA = '${database}' ORDER BY TRIGGER_NAME;`;
}
showProcedures(database: string): string {
return `SELECT PROCEDURE_NAME FROM SYS.EXA_ALL_PROCEDURES WHERE PROCEDURE_SCHEMA = '${database}' ORDER BY PROCEDURE_NAME;`;
}
showFunctions(database: string): string {
return `SELECT FUNCTION_NAME FROM SYS.EXA_ALL_FUNCTIONS WHERE FUNCTION_SCHEMA = '${database}' ORDER BY FUNCTION_NAME;`;
}
tableTemplate(): string {
return `CREATE TABLE table_name (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`;
}
viewTemplate(): string {
return `CREATE VIEW view_name AS
SELECT * FROM table_name
WHERE condition;`;
}
procedureTemplate(): string {
return `CREATE PROCEDURE procedure_name(IN param1 INTEGER)
AS
BEGIN
-- Procedure logic here
END;`;
}
functionTemplate(): string {
return `CREATE FUNCTION function_name(param1 INTEGER)
RETURNS INTEGER AS
BEGIN
-- Function logic here
RETURN 0;
END;`;
}
triggerTemplate(): string {
return `CREATE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
-- Trigger logic here
END;`;
}
showUsers(): string {
return "SELECT USER_NAME FROM SYS.EXA_ALL_USERS ORDER BY USER_NAME;";
}
userTemplate(): string {
return `CREATE USER user_name IDENTIFIED BY 'password';`;
}
truncateDatabase(database: string): string {
return `SELECT 'TRUNCATE TABLE ' || TABLE_SCHEMA || '.' || TABLE_NAME || ';'
FROM SYS.EXA_ALL_TABLES
WHERE TABLE_SCHEMA = '${database}';`;
}
createUser(): string {
return `CREATE USER user_name IDENTIFIED BY 'password';`;
}
updateUser(): string {
return `ALTER USER user_name IDENTIFIED BY 'new_password';`;
}
grantPrivileges(): string {
return `GRANT privileges ON schema_name.* TO user_name;`;
}
updateColumn(): string {
return `ALTER TABLE table_name MODIFY column_name column_type;`;
}
addColumn(): string {
return `ALTER TABLE table_name ADD column_name column_type;`;
}
buildPageSql(database: string, table: string, pageSize: number): string {
return `SELECT * FROM "${database}"."${table}" LIMIT 1000`;
}
countSql(database: string, table: string): string {
return `SELECT COUNT(1) as count FROM "${database}"."${table}"`;
}
updateTable(): string {
return `ALTER TABLE old_table_name RENAME TO new_table_name`;
}
showTableSource(database: string, table: string): string {
return `SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT,
CASE WHEN COLUMN_IS_NULLABLE = 'YES' THEN 'NULL' ELSE 'NOT NULL' END as nullable,
COLUMN_COMMENT
FROM SYS.EXA_ALL_COLUMNS
WHERE COLUMN_SCHEMA = '${database}'
AND COLUMN_TABLE = '${table}'
ORDER BY COLUMN_ORDINAL_POSITION`;
}
showViewSource(database: string, view: string): string {
return `SELECT VIEW_TEXT
FROM SYS.EXA_ALL_VIEWS
WHERE VIEW_SCHEMA = '${database}'
AND VIEW_NAME = '${view}'`;
}
showProcedureSource(database: string, procedure: string): string {
return `SELECT PROCEDURE_TEXT
FROM SYS.EXA_ALL_PROCEDURES
WHERE PROCEDURE_SCHEMA = '${database}'
AND PROCEDURE_NAME = '${procedure}'`;
}
showFunctionSource(database: string, function_name: string): string {
return `SELECT FUNCTION_TEXT
FROM SYS.EXA_ALL_FUNCTIONS
WHERE FUNCTION_SCHEMA = '${database}'
AND FUNCTION_NAME = '${function_name}'`;
}
showTriggerSource(database: string, trigger: string): string {
return `SELECT TRIGGER_TEXT
FROM SYS.EXA_ALL_TRIGGERS
WHERE TRIGGER_SCHEMA = '${database}'
AND TRIGGER_NAME = '${trigger}'`;
}
showVariables(): string {
return `SELECT PARAM_NAME, PARAM_VALUE
FROM SYS.EXA_PARAMETERS
ORDER BY PARAM_NAME`;
}
showStatus(): string {
return `SELECT 'RUNNING' as status,
current_timestamp as server_time,
current_schema as current_database`;
}
processList(): string {
return `SELECT SESSION_ID, USER_NAME, ACTIVITY, COMMAND_NAME, DURATION
FROM SYS.EXA_DBA_SESSIONS
WHERE STATUS = 'RUNNING'`;
}
variableList(): string {
return this.showVariables();
}
statusList(): string {
return this.showStatus();
}
createDatabase(): string {
return `CREATE SCHEMA schema_name`;
}
}

5
src/service/serviceManager.ts

@ -38,6 +38,7 @@ import { MongoPageService } from "./page/mongoPageService";
import { HighlightCreator } from "@/provider/codelen/highlightCreator";
import { SQLSymbolProvide } from "@/provider/sqlSymbolProvide";
import { MysqlDumpService } from "./dump/mysqlDumpService";
import { ExasolDialect } from './dialect/exasolDialect';
export class ServiceManager {
@ -151,6 +152,8 @@ export class ServiceManager {
return new EsDialect();
case DatabaseType.MONGO_DB:
return new MongoDialect();
case DatabaseType.EXASOL:
return new ExasolDialect();
}
return new MysqlDialect()
}
@ -166,6 +169,8 @@ export class ServiceManager {
return new MongoPageService();
case DatabaseType.ES:
return new EsPageService();
case DatabaseType.EXASOL:
return new MysqlPageSerivce();
}
return new MysqlPageSerivce();

6
src/vue/connect/index.vue

@ -271,6 +271,7 @@ export default {
"ElasticSearch",
"SSH",
"FTP",
"Exasol"
],
connect: {
loading: false,
@ -408,6 +409,11 @@ export default {
break;
case "SSH":
break;
case "Exasol":
this.connectionOption.user = "sys";
this.connectionOption.port = 8563;
this.connectionOption.database = null;
break;
}
this.$forceUpdate();
},

Loading…
Cancel
Save