mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
dialect, dumper
This commit is contained in:
parent
9f3679fefb
commit
6188e90340
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 mssql = require('mssql');
|
||||
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 }) {
|
||||
const pool = await mssql.connect({ server, port, user, password, database });
|
||||
return pool;
|
||||
@ -26,5 +36,11 @@ module.exports = {
|
||||
await analyser.runAnalysis();
|
||||
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 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 }) {
|
||||
const connection = mysql.createConnection({ host: server, port, user, password, database });
|
||||
connection._database_name = database;
|
||||
return connection;
|
||||
},
|
||||
async query(connection, sql) {
|
||||
@ -18,8 +30,19 @@ module.exports = {
|
||||
const version = rows[0].Value;
|
||||
return { version };
|
||||
},
|
||||
async analyseFull(pool) {
|
||||
const analyser = new MySqlAnalyser(pool, this);
|
||||
await analyser.runAnalysis();
|
||||
return analyser.result;
|
||||
},
|
||||
async listDatabases(connection) {
|
||||
const { rows } = await this.query(connection, 'show databases');
|
||||
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');
|
||||
|
||||
/** @type {import('dbgate').SqlDialect} */
|
||||
const dialect = {
|
||||
rangeSelect: true,
|
||||
quoteIdentifier(s) {
|
||||
return '"' + s + '"';
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
async connect({ server, port, user, password, database }) {
|
||||
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');
|
||||
return rows;
|
||||
},
|
||||
dialect,
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
const engines = require('../engines');
|
||||
const Select = require('../dmlf/select');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@ -33,7 +34,16 @@ function waitConnected() {
|
||||
async function handleTableData({ msgid, schemaName, pureName }) {
|
||||
await waitConnected();
|
||||
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 });
|
||||
}
|
||||
|
||||
|
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 { SqlDialect } from "./dialect";
|
||||
import { SqlDumper } from "./dumper";
|
||||
import { DatabaseInfo } from "./dbinfo";
|
||||
|
||||
export interface EngineDriver {
|
||||
connect({
|
||||
server,
|
||||
port,
|
||||
user,
|
||||
password
|
||||
password,
|
||||
database
|
||||
}: {
|
||||
server: any;
|
||||
port: any;
|
||||
user: any;
|
||||
password: any;
|
||||
database: any;
|
||||
}): any;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
getVersion(pool: any): Promise<string>;
|
||||
getVersion(pool: any): Promise<{ version: string }>;
|
||||
listDatabases(
|
||||
pool: any
|
||||
): Promise<
|
||||
@ -20,6 +26,8 @@ export interface EngineDriver {
|
||||
name: string;
|
||||
}[]
|
||||
>;
|
||||
analyseFull(pool: any): Promise<void>;
|
||||
analyseIncremental(pool: any): Promise<void>;
|
||||
analyseFull(pool: any): Promise<DatabaseInfo>;
|
||||
// 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 "./dbinfo";
|
||||
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 {
|
||||
rows: any[];
|
||||
export interface RangeDefinition {
|
||||
offset: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface QueryResult {
|
||||
rows: any[];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user