This guide explains how to create custom database drivers for Open Data Diff.
A plugin is a Node.js module that exports a function returning a DatabaseDriver implementation.
my-plugin/
├── package.json
├── index.js (or index.ts)
└── README.md
{
"name": "my-database-plugin",
"version": "1.0.0",
"description": "Custom database driver for MyDB",
"main": "index.js",
"databaseDriver": "mydb",
"author": "Your Name",
"keywords": ["database", "open-data-diff", "plugin"]
}The databaseDriver field is required and specifies the driver name.
interface DatabaseDriver {
name: string;
testConnection(connection: any): Promise<boolean>;
getSchema(connection: any): Promise<DatabaseSchema>;
executeQuery(connection: any, query: string): Promise<any[]>;
disconnect(): Promise<void>;
}Tests if a connection can be established to the database.
- Parameters: Connection configuration object
- Returns:
Promise<boolean>- true if connection successful
Retrieves the complete database schema.
- Parameters: Connection configuration object
- Returns:
Promise<DatabaseSchema>- schema object with tables, views, etc.
Executes a SQL query and returns results.
- Parameters:
connection: Connection configuration objectquery: SQL query string
- Returns:
Promise<any[]>- array of result rows
Closes all connections and cleans up resources.
- Returns:
Promise<void>
// index.js
const mysql = require('mysql2/promise');
class MySQLDriver {
constructor() {
this.name = 'mysql';
this.pools = new Map();
}
async testConnection(connection) {
try {
const pool = await this.getPool(connection);
const [rows] = await pool.execute('SELECT 1 as test');
return rows.length > 0;
} catch (error) {
return false;
}
}
async getSchema(connection) {
const pool = await this.getPool(connection);
// Implement schema retrieval logic
const tables = await this.getTables(pool);
const views = await this.getViews(pool);
return {
tables,
views,
procedures: [],
functions: []
};
}
async executeQuery(connection, query) {
const pool = await this.getPool(connection);
const [rows] = await pool.execute(query);
return rows;
}
async disconnect() {
for (const pool of this.pools.values()) {
await pool.end();
}
this.pools.clear();
}
async getPool(connection) {
const key = `${connection.host}:${connection.port}:${connection.database}`;
if (!this.pools.has(key)) {
const pool = mysql.createPool({
host: connection.host,
port: connection.port || 3306,
user: connection.username,
password: connection.password,
database: connection.database,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
this.pools.set(key, pool);
}
return this.pools.get(key);
}
// Implement getTables, getViews, etc.
}
module.exports = function() {
return new MySQLDriver();
};- Place your plugin directory in the
plugins/folder of the application - The plugin will be automatically discovered on startup
- Load the plugin through the UI or API
- Always handle connection errors gracefully
- Implement proper connection pooling
- Clean up resources in the
disconnect()method - Use parameterized queries to prevent SQL injection
- Follow the schema format exactly as defined in the types
- Test your plugin thoroughly with different connection scenarios
Ensure your getSchema() method returns data in the correct format:
interface DatabaseSchema {
tables: TableSchema[];
views: ViewSchema[];
procedures: ProcedureSchema[];
functions: FunctionSchema[];
}See the TypeScript definitions in src/types/database.ts for complete schema structure.