第十一章:扩展开发
本章介绍如何扩展 VSDB,包括添加新数据库支持、自定义功能和插件机制。
11.1 添加新数据库支持
扩展架构
graph TD
A["新数据库支持"] --> B["驱动实现"]
A --> C["Schema Inspector"]
A --> D["配置解析器"]
B --> B1["实现 Driver 接口"]
B --> B2["connect/disconnect"]
B --> B3["query/streamQuery"]
C --> C1["getDatabases"]
C --> C2["getTables"]
C --> C3["getColumns"]
D --> D1["更新 ScannerEngine"]
D --> D2["添加 Parser"]
Driver 接口实现
以 SQLite 为例:
// src/worker/driver/sqlite.ts
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import type { DbConnection, QueryResult } from '../../shared/types';
export class SqliteDriver {
private db: sqlite.Database | null = null;
async connect(config: DbConnection): Promise<void> {
// SQLite 连接配置使用文件路径
const dbPath = config.host; // SQLite 使用 host 字段存储路径
this.db = await open({
filename: dbPath,
driver: sqlite3.Database,
});
}
async disconnect(): Promise<void> {
if (this.db) {
await this.db.close();
this.db = null;
}
}
async query(sql: string, params?: any[]): Promise<QueryResult> {
if (!this.db) {
throw new Error('SQLite: not connected');
}
const startTime = Date.now();
const result = await this.db.all(sql, params || []);
const executionTime = Date.now() - startTime;
const rows = result as Record<string, unknown>[];
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
return {
columns,
rows,
rowCount: rows.length,
executionTime,
};
}
async *streamQuery(
sql: string,
params?: any[],
chunkSize = 1000
): AsyncGenerator<StreamChunkData> {
if (!this.db) {
throw new Error('SQLite: not connected');
}
const result = await this.db.all(sql, params || []);
const rows = result;
const totalRows = rows.length;
for (let offset = 0; offset < rows.length; offset += chunkSize) {
const chunk = rows.slice(offset, offset + chunkSize);
const isLast = offset + chunkSize >= rows.length;
yield {
chunkIndex: Math.floor(offset / chunkSize),
rows: chunk as Record<string, unknown>[],
...(isLast ? { totalRows } : {}),
};
}
}
getDb(): sqlite.Database | null {
return this.db;
}
}
Schema Inspector 实现
// src/worker/schema/sqliteSchema.ts
export class SqliteSchema {
async getTables(db: sqlite.Database): Promise<SchemaResult> {
const rows = await db.all(
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`
);
return {
type: 'tables',
data: rows.map(r => ({ name: r.name })),
};
}
async getColumns(db: sqlite.Database, table: string): Promise<SchemaResult> {
const rows = await db.all(`PRAGMA table_info(${table})`);
return {
type: 'columns',
data: rows.map(r => ({
name: r.name,
type: r.type,
nullable: r.notnull === 0,
defaultValue: r.dflt_value,
isPrimaryKey: r.pk === 1,
isAutoIncrement: false, // SQLite 无原生 auto_increment 标记
})),
};
}
async getIndexes(db: sqlite.Database, table: string): Promise<SchemaResult> {
const rows = await db.all(`PRAGMA index_list(${table})`);
const indexes: IndexInfo[] = [];
for (const row of rows) {
const indexInfo = await db.all(`PRAGMA index_info(${row.name})`);
indexes.push({
name: row.name,
columns: indexInfo.map(i => i.name),
isUnique: row.unique === 1,
isPrimary: row.origin === 'pk',
});
}
return {
type: 'indexes',
data: indexes,
};
}
async getDDL(db: sqlite.Database, table: string): Promise<SchemaResult> {
const row = await db.get(
`SELECT sql FROM sqlite_master WHERE type='table' AND name=?`,
table
);
return {
type: 'ddl',
data: [{ ddl: row?.sql || '' }],
};
}
}
Worker 路由更新
// worker.ts
import { SqliteDriver } from './driver/sqlite';
import { SqliteSchema } from './schema/sqliteSchema';
async function handleConnect(request: WorkerRequest): Promise<void> {
const config = request.payload.config;
let driver;
switch (config.type) {
case 'mysql':
driver = new MySqlDriver();
break;
case 'postgresql':
driver = new PostgreSqlDriver();
break;
case 'sqlite': // 新增
driver = new SqliteDriver();
break;
default:
sendError(request.id, 'UNSUPPORTED_DATABASE', `Unsupported database type: ${config.type}`);
return;
}
// ... 连接逻辑
}
async function executeSchema(schemaType: string, driver: any, ...) {
// 新增 SQLite 路由
if (config.type === 'sqlite') {
const db = (driver as SqliteDriver).getDb();
return sqliteSchema[schemaType](db, ...);
}
// ...
}
类型定义更新
// types.ts
export interface DbConnection {
// ...
type: 'mysql' | 'postgresql' | 'sqlite' | 'redis' | 'mongodb';
// ...
}
11.2 自定义功能扩展
添加新命令
// extension.ts
const customCommand = vscode.commands.registerCommand('vsdb.customAction', async () => {
// 实现自定义功能
const selected = await vscode.window.showQuickPick([...]);
if (selected) {
// 处理逻辑
}
});
context.subscriptions.push(customCommand);
扩展 TreeView
// treeItems.ts - 添加自定义节点类型
class CustomNode extends vscode.TreeItem {
constructor(public data: any) {
super('Custom Node', vscode.TreeItemCollapsibleState.None);
this.contextValue = 'custom';
this.iconPath = new vscode.ThemeIcon('star');
this.command = {
command: 'vsdb.customAction',
title: 'Custom Action',
};
}
}
// treeProvider.ts - 返回自定义节点
async getChildren(element?: TreeItem): Promise<TreeItem[]> {
if (!element) {
const nodes: TreeItem[] = [];
// 添加标准连接节点
nodes.push(...await this.getConnectionNodes());
// 添加自定义节点
nodes.push(new CustomNode({ ... }));
return nodes;
}
}
扩展 Webview
// 创建新的 Webview Panel
export class CustomPanel {
public static createOrShow(extensionUri: vscode.Uri): CustomPanel {
const panel = vscode.window.createWebviewPanel(
'vsdb.customPanel',
'VSDB Custom Panel',
vscode.ViewColumn.One,
{ enableScripts: true }
);
return new CustomPanel(panel, extensionUri);
}
private getHtml(): string {
return `
<!DOCTYPE html>
<html>
<head>
<script src="${this.panel.webview.asWebviewUri(...)}"></script>
</head>
<body>
<div id="root"></div>
<script>
// React 应用入口
</script>
</body>
</html>
`;
}
}
11.3 配置解析器扩展
添加新 Parser
// src/scanner/customParser.ts
export class CustomParser {
async scan(root: string): Promise<ScannerResult> {
const result: ScannerResult = {
connections: [],
errors: [],
scannedFiles: [],
};
// 查找特定配置文件
const configFiles = this.findConfigFiles(root);
for (const file of configFiles) {
try {
const content = fs.readFileSync(file, 'utf-8');
const connections = this.parseContent(content, file);
result.connections.push(...connections);
result.scannedFiles.push(file);
} catch (err: any) {
result.errors.push({ file, error: err.message });
}
}
return result;
}
private parseContent(content: string, sourceFile: string): ScannedConnection[] {
// 自定义解析逻辑
// ...
return [{
name: 'custom-connection',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'user',
password: 'pass',
database: 'db',
source: 'custom',
sourceFile,
confidence: 'high',
}];
}
}
注册到 ScannerEngine
// scanner.ts
export class ScannerEngine {
private envParser = new EnvParser();
private dockerComposeParser = new DockerComposeParser();
private frameworkParser = new FrameworkParser();
private customParser = new CustomParser(); // 新增
async scan(root: string, existing: DbConnection[]): Promise<ScannerResult> {
// 执行所有 Parser
const results = [
await this.envParser.scan(root),
await this.dockerComposeParser.scan(root),
await this.frameworkParser.scan(root),
await this.customParser.scan(root), // 新增
];
// 合并结果
// ...
}
}
11.4 扩展最佳实践
代码组织
src/
├── worker/
│ ├── driver/
│ │ ├── mysql.ts # 已有
│ │ ├── postgresql.ts # 已有
│ │ ├── sqlite.ts # 新增
│ │ └── redis.ts # 未来扩展
│ └── schema/
│ ├── mysqlSchema.ts
│ ├── pgSchema.ts
│ ├── sqliteSchema.ts # 新增
├── scanner/
│ ├── envParser.ts
│ ├── dockerComposeParser.ts
│ ├── customParser.ts # 新增
测试覆盖
// __tests__/worker/driver/sqlite.test.ts
describe('SqliteDriver', () => {
it('should connect to sqlite file', async () => {
const driver = new SqliteDriver();
await driver.connect({
type: 'sqlite',
host: './test.db',
// ...
});
expect(driver.isConnected()).toBe(true);
});
it('should execute query', async () => {
// ...
});
});
文档更新
// README.md
## Supported Databases
| Database | Status |
|----------|--------|
| MySQL | ✅ Full support |
| PostgreSQL | ✅ Full support |
| SQLite | ✅ Added in v0.2.0 |
| Redis | 🔜 Planned |
11.5 小结
本章介绍了 VSDB 的扩展开发:
| 扩展类型 | 步骤 |
|---|---|
| 新数据库支持 | Driver + Schema Inspector + Worker 路由 |
| 自定义命令 | registerCommand + 实现 |
| TreeView 扩展 | 自定义节点 + TreeProvider |
| Webview 扩展 | 新 Panel + React 组件 |
| 配置解析器 | Parser + ScannerEngine 注册 |
关键要点:
- Driver 接口:遵循 connect/disconnect/query/streamQuery
- Schema Inspector:实现 getTables/getColumns/getIndexes
- Worker 路由:在 worker.ts 添加新类型判断
- 类型更新:扩展 DbConnection.type
- 测试覆盖:新增模块需要完整测试
下一章将介绍最佳实践。