1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-03-29 01:30:10 +01:00

Improvements to query builder

This commit is contained in:
Fabio 2020-06-15 18:23:51 +02:00
parent aa7618ec8d
commit 6f03e66ef2
12 changed files with 200 additions and 68 deletions

3
.gitignore vendored
View File

@ -4,4 +4,5 @@ node_modules/
thumbs.db
.idea/
.vscode
TODO.md
TODO.md
*.txt

30
package-lock.json generated
View File

@ -1164,9 +1164,9 @@
"integrity": "sha512-4mXKoDptrXAwZErQHrLzpe0FN/0Wmf5JRniSVIdwUrtDf9wnmEV1teCNLBo/TwuXhkK/bVegoEn/wmb+x0AuPg=="
},
"@types/readable-stream": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.5.tgz",
"integrity": "sha512-Mq2eLkGYamlcolW603FY2ROBvcl90jPF+3jLkjpBV6qS+2aVeJqlgRG0TVAa1oWbmPdb5yOWlOPVvQle76nUNw==",
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-oAbOdOKhx5nu1+pBPiwEHh6gqA3ojgwwNwNIjNd67TqwEjvVePqe21mnYc6RrnGZ8aVPFBe6sci3cU2pKKwuug==",
"requires": {
"@types/node": "*",
"safe-buffer": "*"
@ -1522,9 +1522,9 @@
},
"dependencies": {
"@types/node": {
"version": "8.10.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz",
"integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg=="
"version": "8.10.61",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz",
"integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q=="
}
}
},
@ -7715,9 +7715,9 @@
}
},
"jsbi": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.2.tgz",
"integrity": "sha512-5nDXo1X9QVaXK/Cpb5VECV9ss1QPbjUuk1qSruHB1PK/g39Sd414K4nci99ElFDZv0vzxDEnKn3o49/Tn9Yagw=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.3.tgz",
"integrity": "sha512-nBJqA0C6Qns+ZxurbEoIR56wyjiUszpNy70FHvxO5ervMoCbZVE3z3kxr5nKGhlxr/9MhKTSUBs7cAwwuf3g9w=="
},
"jsbn": {
"version": "0.1.1",
@ -8538,13 +8538,6 @@
"debug": "^4",
"tarn": "^1.1.5",
"tedious": "^6.6.2"
},
"dependencies": {
"tarn": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-1.1.5.tgz",
"integrity": "sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g=="
}
}
},
"multicast-dns": {
@ -11973,6 +11966,11 @@
"inherits": "2"
}
},
"tarn": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/tarn/-/tarn-1.1.5.tgz",
"integrity": "sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g=="
},
"tedious": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/tedious/-/tedious-6.7.0.tgz",

View File

@ -4,9 +4,7 @@ import { AntaresConnector } from '../libs/AntaresConnector';
import InformationSchema from '../models/InformationSchema';
import Generic from '../models/Generic';
const connections = {};
export default () => {
export default (connections) => {
ipcMain.handle('testConnection', async (event, conn) => {
const Connection = new AntaresConnector({
client: conn.client,
@ -46,9 +44,9 @@ export default () => {
poolSize: 3
});
Connection.connect();
try {
await Connection.connect();
const { rows: structure } = await InformationSchema.getStructure(Connection);
connections[conn.uid] = Connection;
return { status: 'success', response: structure };
@ -73,10 +71,10 @@ export default () => {
}
});
ipcMain.handle('rawQuery', async (event, { uid, query, database }) => {
ipcMain.handle('rawQuery', async (event, { uid, query, schema }) => {
if (!query) return;
try {
const result = await Generic.raw(connections[uid], query, database);
const result = await Generic.raw(connections[uid], query, schema);
return { status: 'success', response: result };
}
catch (err) {

View File

@ -1,5 +1,9 @@
import connection from './connection';
import structure from './structure';
const connections = {};
export default () => {
connection();
connection(connections);
structure(connections);
};

View File

@ -0,0 +1,25 @@
import { ipcMain } from 'electron';
import InformationSchema from '../models/InformationSchema';
import Generic from '../models/Generic';
export default (connections) => {
ipcMain.handle('getTableColumns', async (event, { uid, schema, table }) => {
try {
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('getTableData', async (event, { uid, schema, table }) => {
try {
const result = await Generic.getTableData(connections[uid], schema, table);
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@ -1,5 +1,6 @@
'use strict';
import mysql from 'mysql2';
import mssql from 'mssql';
/**
* As Simple As Possible Query Builder
@ -26,7 +27,7 @@ export class AntaresConnector {
where: [],
groupBy: [],
orderBy: [],
limit: '',
limit: [],
join: [],
update: [],
insert: [],
@ -62,7 +63,7 @@ export class AntaresConnector {
/**
* @memberof AntaresConnector
*/
connect () {
async connect () {
switch (this._client) {
case 'maria':
case 'mysql':
@ -75,7 +76,15 @@ export class AntaresConnector {
this._connection = pool.promise();
}
break;
case 'mssql': {
const mssqlParams = {
user: this._params.user,
password: this._params.password,
server: this._params.host
};
this._connection = await mssql.connect(mssqlParams);
}
break;
default:
break;
}
@ -111,28 +120,81 @@ export class AntaresConnector {
return this;
}
getQueryString () {
const selectArray = this._query.select.reduce(this._reducer, []);
const selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')}` : 'SELECT *';
const fromRaw = this._query.from ? `FROM ${this._query.schema ? `\`${this._query.schema}\`.` : ''} \`${this._query.from}\`` : '';
const whereArray = this._query.where.reduce(this._reducer, []);
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(', AND ')}` : '';
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')}` : '';
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')}` : '';
return `${selectRaw} ${fromRaw} ${whereRaw} ${groupByRaw} ${orderByRaw}`;
limit (...args) {
this._query.limit = args;
return this;
}
run () {
const rawQuery = this.getQueryString();
/**
* @returns {string} SQL string
* @memberof AntaresConnector
*/
getSQL () {
const selectArray = this._query.select.reduce(this._reducer, []);
let selectRaw;
switch (this._client) {
case 'maria':
case 'mysql':
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
break;
case 'mssql': {
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
}
break;
default:
break;
}
let fromRaw;
switch (this._client) {
case 'maria':
case 'mysql':
fromRaw = this._query.from ? `FROM ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
break;
case 'mssql':
fromRaw = this._query.from ? `FROM ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
break;
default:
break;
}
const whereArray = this._query.where.reduce(this._reducer, []);
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
let limitRaw;
switch (this._client) {
case 'maria':
case 'mysql':
limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
break;
case 'mssql':
limitRaw = '';
break;
default:
break;
}
return `${selectRaw}${fromRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
}
/**
* @returns {Promise}
* @memberof AntaresConnector
*/
async run () {
const rawQuery = this.getSQL();
if (process.env.NODE_ENV === 'development') console.log(rawQuery);
this._resetQuery();
return this.raw(rawQuery);
}
/**
* @param {*} sql raw SQL query
* @param {string} sql raw SQL query
* @returns {Promise}
* @memberof AntaresConnector
*/
@ -143,6 +205,10 @@ export class AntaresConnector {
const [rows, fields] = await this._connection.query(sql);
return { rows, fields };
}
case 'mssql': {
const results = await this._connection.request().query(sql);
return { rows: results.recordsets[0] };
}
default:
break;
}
@ -154,10 +220,12 @@ export class AntaresConnector {
destroy () {
switch (this._client) {
case 'maria':
case 'mysql': {
case 'mysql':
this._connection.end();
break;
}
case 'mssql':
this._connection.close();
break;
default:
break;
}

View File

@ -1,7 +1,16 @@
'use strict';
export default class {
static async raw (connection, query, database) {
if (database) await connection.raw(`USE \`${database}\``);
static async raw (connection, query, schema) {
if (schema) await connection.raw(`USE \`${schema}\``);
return connection.raw(query);
}
static async getTableData (connection, schema, table) {
return connection
.select('*')
.schema(schema)
.from(table)
.limit(1000)
.run();
}
}

View File

@ -5,7 +5,6 @@ export default class {
}
static getStructure (connection) {
// return connection.raw('SELECT * FROM information_schema.TABLES ORDER BY TABLE_SCHEMA ASC, TABLE_NAME ASC');
return connection
.select('*')
.schema('information_schema')
@ -14,5 +13,13 @@ export default class {
.run();
}
// TODO: SELECT * FROM `information_schema`.`COLUMNS` WHERE TABLE_SCHEMA='fepcomdb' AND TABLE_NAME='macchine' ORDER BY ORDINAL_POSITION;
static getTableColumns (connection, schema, table) {
return connection
.select('*')
.schema('information_schema')
.from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' })
.run();
}
}

View File

@ -2,8 +2,8 @@
<details class="accordion workspace-explorebar-database">
<summary
class="accordion-header database-name pb-0"
:class="{'text-bold': breadcrumbs.database === database.name}"
@click="changeBreadcrumbs({database: database.name, table:null})"
:class="{'text-bold': breadcrumbs.schema === database.name}"
@click="changeBreadcrumbs({schema: database.name, table:null})"
>
<i class="icon material-icons md-18 mr-1">navigate_next</i>
<i class="material-icons md-18 mr-1">view_agenda</i>
@ -16,8 +16,8 @@
v-for="table of database.tables"
:key="table.TABLE_NAME"
class="menu-item"
:class="{'text-bold': breadcrumbs.database === database.name && breadcrumbs.table === table.TABLE_NAME}"
@click="changeBreadcrumbs({database: database.name, table: table.TABLE_NAME})"
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.TABLE_NAME}"
@click="changeBreadcrumbs({schema: database.name, table: table.TABLE_NAME})"
>
<a class="table-name">
<i class="material-icons md-18 mr-1">grid_on</i>

View File

@ -22,8 +22,8 @@
<div v-if="results.rows">
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
</div>
<div v-if="workspace.breadcrumbs.database">
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
<div v-if="workspace.breadcrumbs.schema">
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.schema }}</b>
</div>
</div>
</div>
@ -76,7 +76,7 @@ export default {
const params = {
uid: this.connection.uid,
query: this.query,
database: this.workspace.breadcrumbs.database
schema: this.workspace.breadcrumbs.schema
};
try {

View File

@ -6,7 +6,7 @@
<button
class="btn btn-link btn-sm"
:class="{'loading':isQuering}"
@click="runQuery"
@click="getTableData"
>
<span>{{ $t('word.refresh') }}</span>
<i class="material-icons ml-1">refresh</i>
@ -33,7 +33,7 @@
</template>
<script>
import Connection from '@/ipc-api/Connection';
import Structure from '@/ipc-api/Structure';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
@ -50,6 +50,7 @@ export default {
return {
isQuering: false,
results: {},
fields: {},
lastTable: null
};
},
@ -62,45 +63,54 @@ export default {
},
isSelected () {
return this.workspace.selected_tab === 1;
},
query () {
return `SELECT * FROM \`${this.table}\` LIMIT 1000`;// TODO: use query builder
}
},
watch: {
table: function () {
if (this.isSelected) {
this.runQuery();
this.getTableData();
this.lastTable = this.table;
}
},
isSelected: function (val) {
if (val && this.lastTable !== this.table) {
this.runQuery();
this.getTableData();
this.lastTable = this.table;
}
}
},
created () {
this.runQuery();
this.getTableData();
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async runQuery () {
async getTableData () {
if (!this.table) return;
this.isQuering = true;
this.results = {};
const params = {
uid: this.connection.uid,
query: this.query,
database: this.workspace.breadcrumbs.database
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table
};
try {
const { status, response } = await Connection.rawQuery(params);
const { status, response } = await Structure.getTableColumns(params);
if (status === 'success')
this.fields = response.rows;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
try {
const { status, response } = await Structure.getTableData(params);
console.log(status, response);
if (status === 'success')
this.results = response;
else

View File

@ -0,0 +1,12 @@
'use strict';
import { ipcRenderer } from 'electron';
export default class {
static getTableColumns (params) {
return ipcRenderer.invoke('getTableColumns', params);
}
static getTableData (params) {
return ipcRenderer.invoke('getTableData', params);
}
}