mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-18 18:26:00 +00:00
dialect, dumper
This commit is contained in:
13
api/src/dmlf/command.js
Normal file
13
api/src/dmlf/command.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class Command {
|
||||||
|
/** @param driver {import('dbgate').EngineDriver} */
|
||||||
|
toSql(driver) {
|
||||||
|
const dumper = driver.createDumper();
|
||||||
|
this.dumpSql(dumper);
|
||||||
|
return dumper.s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param dumper {import('dbgate').SqlDumper} */
|
||||||
|
dumpSql(dumper) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
||||||
37
api/src/dmlf/select.js
Normal file
37
api/src/dmlf/select.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const Command = require('./command');
|
||||||
|
|
||||||
|
class Select extends Command {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
/** @type {number} */
|
||||||
|
this.topRecords = undefined;
|
||||||
|
/** @type {import('dbgate').NamedObjectInfo} */
|
||||||
|
this.from = undefined;
|
||||||
|
/** @type {import('dbgate').RangeDefinition} */
|
||||||
|
this.range = undefined;
|
||||||
|
this.distinct = false;
|
||||||
|
this.selectAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param dumper {import('dbgate').SqlDumper} */
|
||||||
|
dumpSql(dumper) {
|
||||||
|
dumper.put('^select ');
|
||||||
|
if (this.topRecords) {
|
||||||
|
dumper.put('^top %s ', this.topRecords);
|
||||||
|
}
|
||||||
|
if (this.distinct) {
|
||||||
|
dumper.put('^distinct ');
|
||||||
|
}
|
||||||
|
if (this.selectAll) {
|
||||||
|
dumper.put('* ');
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
dumper.put('^from %f ', this.from);
|
||||||
|
if (this.range) {
|
||||||
|
dumper.put('^limit %s ^offset %s ', this.range.limit, this.range.offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Select;
|
||||||
64
api/src/engines/default/SqlDumper.js
Normal file
64
api/src/engines/default/SqlDumper.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
class SqlDumper {
|
||||||
|
/** @param driver {import('dbgate').EngineDriver} */
|
||||||
|
constructor(driver) {
|
||||||
|
this.s = '';
|
||||||
|
this.driver = driver;
|
||||||
|
this.dialect = driver.dialect;
|
||||||
|
}
|
||||||
|
putRaw(text) {
|
||||||
|
this.s += text;
|
||||||
|
}
|
||||||
|
putCmd(text) {
|
||||||
|
this.putRaw(text);
|
||||||
|
this.putRaw(';\n');
|
||||||
|
}
|
||||||
|
putFormattedValue(c, value) {
|
||||||
|
switch (c) {
|
||||||
|
case 's':
|
||||||
|
if (value != null) {
|
||||||
|
this.putRaw(value.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
const { schemaName, pureName } = value;
|
||||||
|
if (schemaName) {
|
||||||
|
this.putRaw(this.dialect.quoteIdentifier(schemaName));
|
||||||
|
this.putRaw('.');
|
||||||
|
}
|
||||||
|
this.putRaw(this.dialect.quoteIdentifier(pureName));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** @param format {string} */
|
||||||
|
put(format, ...args) {
|
||||||
|
let i = 0;
|
||||||
|
let argIndex = 0;
|
||||||
|
const length = format.length;
|
||||||
|
while (i < length) {
|
||||||
|
let c = format[i];
|
||||||
|
i++;
|
||||||
|
switch (c) {
|
||||||
|
case '^':
|
||||||
|
while (i < length && format[i].match(/[a-z]/i)) {
|
||||||
|
this.putRaw(format[i].toUpperCase());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
c = format[i];
|
||||||
|
i++;
|
||||||
|
this.putFormattedValue(c, args[argIndex]);
|
||||||
|
argIndex++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.putRaw(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SqlDumper;
|
||||||
5
api/src/engines/mssql/MsSqlDumper.js
Normal file
5
api/src/engines/mssql/MsSqlDumper.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const SqlDumper = require('../default/SqlDumper');
|
||||||
|
|
||||||
|
class MsSqlDumper extends SqlDumper {}
|
||||||
|
|
||||||
|
module.exports = MsSqlDumper;
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const mssql = require('mssql');
|
const mssql = require('mssql');
|
||||||
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
const MsSqlAnalyser = require('./MsSqlAnalyser');
|
||||||
|
const MsSqlDumper = require('./MsSqlDumper');
|
||||||
|
|
||||||
module.exports = {
|
/** @type {import('dbgate').SqlDialect} */
|
||||||
|
const dialect = {
|
||||||
|
limitSelect: true,
|
||||||
|
quoteIdentifier(s) {
|
||||||
|
return `[${s}]`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('dbgate').EngineDriver} */
|
||||||
|
const driver = {
|
||||||
async connect({ server, port, user, password, database }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const pool = await mssql.connect({ server, port, user, password, database });
|
const pool = await mssql.connect({ server, port, user, password, database });
|
||||||
return pool;
|
return pool;
|
||||||
@@ -26,5 +36,11 @@ module.exports = {
|
|||||||
await analyser.runAnalysis();
|
await analyser.runAnalysis();
|
||||||
return analyser.result;
|
return analyser.result;
|
||||||
},
|
},
|
||||||
async analyseIncremental(pool) {},
|
// async analyseIncremental(pool) {},
|
||||||
|
createDumper() {
|
||||||
|
return new MsSqlDumper(this);
|
||||||
|
},
|
||||||
|
dialect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = driver;
|
||||||
|
|||||||
54
api/src/engines/mysql/MySqlAnalyser.js
Normal file
54
api/src/engines/mysql/MySqlAnalyser.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const fp = require('lodash/fp');
|
||||||
|
const path = require('path');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
|
||||||
|
|
||||||
|
/** @returns {Promise<string>} */
|
||||||
|
async function loadQuery(name) {
|
||||||
|
return await fs.readFile(path.join(__dirname, name), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
class MySqlAnalyser extends DatabaseAnalayser {
|
||||||
|
constructor(pool, driver) {
|
||||||
|
super(pool, driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createQuery(
|
||||||
|
resFileName,
|
||||||
|
tables = false,
|
||||||
|
views = false,
|
||||||
|
procedures = false,
|
||||||
|
functions = false,
|
||||||
|
triggers = false
|
||||||
|
) {
|
||||||
|
console.log('DB', this.pool._database_name);
|
||||||
|
let res = await loadQuery(resFileName);
|
||||||
|
res = res.replace('=[OBJECT_NAME_CONDITION]', ' is not null');
|
||||||
|
res = res.replace('#DATABASE#', this.pool._database_name);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
async runAnalysis() {
|
||||||
|
const tables = await this.driver.query(this.pool, await this.createQuery('tables.sql'));
|
||||||
|
const columns = await this.driver.query(this.pool, await this.createQuery('columns.sql'));
|
||||||
|
// const pkColumns = await this.driver.query(this.pool, await this.createQuery('primary_keys.sql'));
|
||||||
|
// const fkColumns = await this.driver.query(this.pool, await this.createQuery('foreign_keys.sql'));
|
||||||
|
|
||||||
|
this.result.tables = tables.rows.map(table => ({
|
||||||
|
...table,
|
||||||
|
columns: columns.rows
|
||||||
|
.filter(col => col.objectId == table.objectId)
|
||||||
|
.map(({ isNullable, extra, ...col }) => ({
|
||||||
|
...col,
|
||||||
|
notNull: !isNullable,
|
||||||
|
autoIncrement: extra && extra.toLowerCase().includes('auto_increment'),
|
||||||
|
})),
|
||||||
|
foreignKeys: [],
|
||||||
|
// primaryKey: extractPrimaryKeys(table, pkColumns.rows),
|
||||||
|
// foreignKeys: extractForeignKeys(table, fkColumns.rows),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MySqlAnalyser;
|
||||||
5
api/src/engines/mysql/MySqlDumper.js
Normal file
5
api/src/engines/mysql/MySqlDumper.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const SqlDumper = require('../default/SqlDumper');
|
||||||
|
|
||||||
|
class MySqlDumper extends SqlDumper {}
|
||||||
|
|
||||||
|
module.exports = MySqlDumper;
|
||||||
13
api/src/engines/mysql/columns.sql
Normal file
13
api/src/engines/mysql/columns.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
select
|
||||||
|
TABLE_NAME as pureName,
|
||||||
|
COLUMN_NAME as columnName,
|
||||||
|
IS_NULLABLE as isNullable,
|
||||||
|
DATA_TYPE as dataType,
|
||||||
|
CHARACTER_MAXIMUM_LENGTH,
|
||||||
|
NUMERIC_PRECISION,
|
||||||
|
NUMERIC_SCALE,
|
||||||
|
COLUMN_DEFAULT,
|
||||||
|
EXTRA as extra
|
||||||
|
from INFORMATION_SCHEMA.COLUMNS
|
||||||
|
where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION]
|
||||||
|
order by ORDINAL_POSITION
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
const mysql = require('mysql');
|
const mysql = require('mysql');
|
||||||
|
const MySqlAnalyser = require('./MySqlAnalyser');
|
||||||
|
const MySqlDumper = require('./MySqlDumper');
|
||||||
|
|
||||||
module.exports = {
|
/** @type {import('dbgate').SqlDialect} */
|
||||||
|
const dialect = {
|
||||||
|
rangeSelect: true,
|
||||||
|
quoteIdentifier(s) {
|
||||||
|
return '`' + s + '`';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('dbgate').EngineDriver} */
|
||||||
|
const driver = {
|
||||||
async connect({ server, port, user, password, database }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const connection = mysql.createConnection({ host: server, port, user, password, database });
|
const connection = mysql.createConnection({ host: server, port, user, password, database });
|
||||||
|
connection._database_name = database;
|
||||||
return connection;
|
return connection;
|
||||||
},
|
},
|
||||||
async query(connection, sql) {
|
async query(connection, sql) {
|
||||||
@@ -18,8 +30,19 @@ module.exports = {
|
|||||||
const version = rows[0].Value;
|
const version = rows[0].Value;
|
||||||
return { version };
|
return { version };
|
||||||
},
|
},
|
||||||
|
async analyseFull(pool) {
|
||||||
|
const analyser = new MySqlAnalyser(pool, this);
|
||||||
|
await analyser.runAnalysis();
|
||||||
|
return analyser.result;
|
||||||
|
},
|
||||||
async listDatabases(connection) {
|
async listDatabases(connection) {
|
||||||
const { rows } = await this.query(connection, 'show databases');
|
const { rows } = await this.query(connection, 'show databases');
|
||||||
return rows.map(x => ({ name: x.Database }));
|
return rows.map(x => ({ name: x.Database }));
|
||||||
},
|
},
|
||||||
|
createDumper() {
|
||||||
|
return new MySqlDumper(this);
|
||||||
|
},
|
||||||
|
dialect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = driver;
|
||||||
|
|||||||
5
api/src/engines/mysql/tables.sql
Normal file
5
api/src/engines/mysql/tables.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
select
|
||||||
|
TABLE_NAME as pureName,
|
||||||
|
case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as alterTime
|
||||||
|
from information_schema.tables
|
||||||
|
where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION];
|
||||||
5
api/src/engines/postgres/PostgreDumper.js
Normal file
5
api/src/engines/postgres/PostgreDumper.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const SqlDumper = require('../default/SqlDumper');
|
||||||
|
|
||||||
|
class PostgreDumper extends SqlDumper {}
|
||||||
|
|
||||||
|
module.exports = PostgreDumper;
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
const { Client } = require('pg');
|
const { Client } = require('pg');
|
||||||
|
|
||||||
|
/** @type {import('dbgate').SqlDialect} */
|
||||||
|
const dialect = {
|
||||||
|
rangeSelect: true,
|
||||||
|
quoteIdentifier(s) {
|
||||||
|
return '"' + s + '"';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async connect({ server, port, user, password, database }) {
|
async connect({ server, port, user, password, database }) {
|
||||||
const client = new Client({ host: server, port, user, password, database: database || 'postgres' });
|
const client = new Client({ host: server, port, user, password, database: database || 'postgres' });
|
||||||
@@ -19,4 +27,5 @@ module.exports = {
|
|||||||
const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false');
|
const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false');
|
||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
|
dialect,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const engines = require('../engines');
|
const engines = require('../engines');
|
||||||
|
const Select = require('../dmlf/select');
|
||||||
|
|
||||||
let systemConnection;
|
let systemConnection;
|
||||||
let storedConnection;
|
let storedConnection;
|
||||||
@@ -33,7 +34,16 @@ function waitConnected() {
|
|||||||
async function handleTableData({ msgid, schemaName, pureName }) {
|
async function handleTableData({ msgid, schemaName, pureName }) {
|
||||||
await waitConnected();
|
await waitConnected();
|
||||||
const driver = engines(storedConnection);
|
const driver = engines(storedConnection);
|
||||||
const res = await driver.query(systemConnection, `SELECT TOP(100) * FROM ${pureName}`);
|
|
||||||
|
const select = new Select();
|
||||||
|
if (driver.dialect.limitSelect) select.topRecords = 100;
|
||||||
|
if (driver.dialect.rangeSelect) select.range = { offset: 0, limit: 100 };
|
||||||
|
select.from = { schemaName, pureName };
|
||||||
|
select.selectAll = true;
|
||||||
|
const sql = select.toSql(driver);
|
||||||
|
console.log('SQL', sql);
|
||||||
|
const res = await driver.query(systemConnection, sql);
|
||||||
|
|
||||||
process.send({ msgtype: 'response', msgid, ...res });
|
process.send({ msgtype: 'response', msgid, ...res });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
types/dialect.d.ts
vendored
Normal file
5
types/dialect.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface SqlDialect {
|
||||||
|
rangeSelect?: boolean;
|
||||||
|
limitSelect?: boolean;
|
||||||
|
quoteIdentifier(s: string): string;
|
||||||
|
}
|
||||||
5
types/dumper.d.ts
vendored
Normal file
5
types/dumper.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface SqlDumper {
|
||||||
|
s: string;
|
||||||
|
put(format: string, ...args);
|
||||||
|
putCmd(format: string, ...args);
|
||||||
|
}
|
||||||
16
types/engines.d.ts
vendored
16
types/engines.d.ts
vendored
@@ -1,18 +1,24 @@
|
|||||||
import { QueryResult } from "./query";
|
import { QueryResult } from "./query";
|
||||||
|
import { SqlDialect } from "./dialect";
|
||||||
|
import { SqlDumper } from "./dumper";
|
||||||
|
import { DatabaseInfo } from "./dbinfo";
|
||||||
|
|
||||||
export interface EngineDriver {
|
export interface EngineDriver {
|
||||||
connect({
|
connect({
|
||||||
server,
|
server,
|
||||||
port,
|
port,
|
||||||
user,
|
user,
|
||||||
password
|
password,
|
||||||
|
database
|
||||||
}: {
|
}: {
|
||||||
server: any;
|
server: any;
|
||||||
port: any;
|
port: any;
|
||||||
user: any;
|
user: any;
|
||||||
password: any;
|
password: any;
|
||||||
|
database: any;
|
||||||
}): any;
|
}): any;
|
||||||
query(pool: any, sql: string): Promise<QueryResult>;
|
query(pool: any, sql: string): Promise<QueryResult>;
|
||||||
getVersion(pool: any): Promise<string>;
|
getVersion(pool: any): Promise<{ version: string }>;
|
||||||
listDatabases(
|
listDatabases(
|
||||||
pool: any
|
pool: any
|
||||||
): Promise<
|
): Promise<
|
||||||
@@ -20,6 +26,8 @@ export interface EngineDriver {
|
|||||||
name: string;
|
name: string;
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
analyseFull(pool: any): Promise<void>;
|
analyseFull(pool: any): Promise<DatabaseInfo>;
|
||||||
analyseIncremental(pool: any): Promise<void>;
|
// analyseIncremental(pool: any): Promise<void>;
|
||||||
|
dialect: SqlDialect;
|
||||||
|
createDumper(): SqlDumper;
|
||||||
}
|
}
|
||||||
|
|||||||
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
@@ -9,3 +9,5 @@ export interface OpenedDatabaseConnection {
|
|||||||
export * from "./engines";
|
export * from "./engines";
|
||||||
export * from "./dbinfo";
|
export * from "./dbinfo";
|
||||||
export * from "./query";
|
export * from "./query";
|
||||||
|
export * from "./dialect";
|
||||||
|
export * from "./dumper";
|
||||||
|
|||||||
9
types/query.d.ts
vendored
9
types/query.d.ts
vendored
@@ -1,3 +1,8 @@
|
|||||||
export interface QueryResult {
|
export interface RangeDefinition {
|
||||||
rows: any[];
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryResult {
|
||||||
|
rows: any[];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user