From aad9512951dfb6f66086b5d7a423b12fb4278371 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 5 Mar 2020 09:40:05 +0100 Subject: [PATCH] table grid display, SQL for browse table is generated on FE --- .../src/controllers/databaseConnections.js | 8 ++++ packages/api/src/controllers/tables.js | 12 ++--- .../api/src/proc/databaseConnectionProcess.js | 19 ++++---- packages/datalib/.gitignore | 1 + packages/datalib/package.json | 22 ++++++++++ packages/datalib/src/GridDisplay.ts | 5 +++ packages/datalib/src/TableGridDisplay.ts | 20 +++++++++ packages/datalib/src/index.ts | 2 + packages/datalib/tsconfig.json | 12 +++++ packages/engines/default/SqlDumper.js | 7 ++- packages/web/package.json | 2 + packages/web/src/datagrid/DataGrid.js | 44 +++++++++++++------ packages/web/src/tabs/TableDataTab.js | 17 ++++++- packages/web/src/utility/useFetch.js | 20 ++++++--- 14 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 packages/datalib/.gitignore create mode 100644 packages/datalib/package.json create mode 100644 packages/datalib/src/GridDisplay.ts create mode 100644 packages/datalib/src/TableGridDisplay.ts create mode 100644 packages/datalib/src/index.ts create mode 100644 packages/datalib/tsconfig.json diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index d5b15086..2d5bc867 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -66,4 +66,12 @@ module.exports = { tables: _.sortBy(tables, x => `${x.schemaName}.${x.pureName}`), }; // .map(fp.pick(['tableName', 'schemaName'])); }, + + queryData_meta: 'post', + async queryData({ conid, database, sql }) { + console.log(`Processing query, conid=${conid}, database=${database}, sql=${sql}`); + const opened = await this.ensureOpened(conid, database); + const res = await this.sendRequest(opened, { msgtype: 'queryData', sql }); + return res; + }, }; diff --git a/packages/api/src/controllers/tables.js b/packages/api/src/controllers/tables.js index 5581ea25..c679b8e1 100644 --- a/packages/api/src/controllers/tables.js +++ b/packages/api/src/controllers/tables.js @@ -2,12 +2,12 @@ const _ = require('lodash'); const databaseConnections = require('./databaseConnections'); module.exports = { - tableData_meta: 'get', - async tableData({ conid, database, schemaName, pureName }) { - const opened = await databaseConnections.ensureOpened(conid, database); - const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName }); - return res; - }, + // tableData_meta: 'get', + // async tableData({ conid, database, schemaName, pureName }) { + // const opened = await databaseConnections.ensureOpened(conid, database); + // const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName }); + // return res; + // }, tableInfo_meta: 'get', async tableInfo({ conid, database, schemaName, pureName }) { diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index 8198d3c2..59bed790 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -32,24 +32,23 @@ function waitConnected() { }); } -async function handleTableData({ msgid, schemaName, pureName }) { +async function handleQueryData({ msgid, sql }) { + // 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); + await waitConnected(); const driver = engines(storedConnection); - - 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); const res = await driver.query(systemConnection, sql); - process.send({ msgtype: 'response', msgid, ...res }); } const messageHandlers = { connect: handleConnect, - tableData: handleTableData, + queryData: handleQueryData, }; async function handleMessage({ msgtype, ...other }) { diff --git a/packages/datalib/.gitignore b/packages/datalib/.gitignore new file mode 100644 index 00000000..7951405f --- /dev/null +++ b/packages/datalib/.gitignore @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/packages/datalib/package.json b/packages/datalib/package.json new file mode 100644 index 00000000..c16d32c3 --- /dev/null +++ b/packages/datalib/package.json @@ -0,0 +1,22 @@ +{ + "version": "0.1.0", + "name": "@dbgate/datalib", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "prepare": "yarn build", + "build": "tsc", + "start": "tsc --watch" + }, + "files": [ + "lib" + ], + "dependencies": { + "@dbgate/sqltree": "^0.1.0" + }, + "devDependencies": { + "@dbgate/types": "^0.1.0", + "@types/node": "^13.7.0", + "typescript": "^3.7.5" + } +} \ No newline at end of file diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts new file mode 100644 index 00000000..326557db --- /dev/null +++ b/packages/datalib/src/GridDisplay.ts @@ -0,0 +1,5 @@ +import {Select} from '@dbgate/sqltree' + +export default abstract class GridDisplay { + abstract getPageQuery(offse: number, count: number): string; +} diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts new file mode 100644 index 00000000..a70b8ae1 --- /dev/null +++ b/packages/datalib/src/TableGridDisplay.ts @@ -0,0 +1,20 @@ +import GridDisplay from "./GridDisplay"; +import { Select } from "@dbgate/sqltree"; +import { TableInfo, EngineDriver } from "@dbgate/types"; + +export default class TableGridDisplay extends GridDisplay { + constructor(public table: TableInfo, public driver: EngineDriver) { + super(); + } + + getPageQuery(offset: number, count: number) { + const select = new Select(); + if (this.driver.dialect.limitSelect) select.topRecords = count; + if (this.driver.dialect.rangeSelect) + select.range = { offset: offset, limit: count }; + select.from = this.table; + select.selectAll = true; + const sql = select.toSql(this.driver); + return sql; + } +} diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts new file mode 100644 index 00000000..906defb8 --- /dev/null +++ b/packages/datalib/src/index.ts @@ -0,0 +1,2 @@ +export { default as GridDisplay } from "./GridDisplay"; +export { default as TableGridDisplay } from "./TableGridDisplay"; diff --git a/packages/datalib/tsconfig.json b/packages/datalib/tsconfig.json new file mode 100644 index 00000000..4dfd0a5f --- /dev/null +++ b/packages/datalib/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2015", + "module": "commonjs", + "declaration": true, + "skipLibCheck": true, + "outDir": "lib" + }, + "include": [ + "src/**/*" + ] + } \ No newline at end of file diff --git a/packages/engines/default/SqlDumper.js b/packages/engines/default/SqlDumper.js index 3a9c3196..5f34bc14 100644 --- a/packages/engines/default/SqlDumper.js +++ b/packages/engines/default/SqlDumper.js @@ -30,7 +30,9 @@ class SqlDumper { break; case "k": { - this.putRaw(value.toUpperCase()); + if (value) { + this.putRaw(value.toUpperCase()); + } } break; case "f": @@ -46,6 +48,7 @@ class SqlDumper { } } putFormattedList(c, collection) { + if (!collection) return; this.putCollection(", ", collection, item => this.putFormattedValue(c, item) ); @@ -130,7 +133,7 @@ class SqlDumper { if (column.isPersisted) this.put(" ^persisted"); return; } - if (column.dataType) this.put("%k", column.dataType); + this.put("%k", column.dataType); if (column.autoIncrement) { this.autoIncrement(); } diff --git a/packages/web/package.json b/packages/web/package.json index 370b29af..6bd0ec99 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -22,6 +22,8 @@ "socket.io-client": "^2.3.0", "styled-components": "^4.4.1", "uuid": "^3.4.0", + "@dbgate/sqltree": "^0.1.0", + "@dbgate/datalib": "^0.1.0", "@dbgate/engines": "^0.1.0" }, "scripts": { diff --git a/packages/web/src/datagrid/DataGrid.js b/packages/web/src/datagrid/DataGrid.js index 820bebb3..ee25a57c 100644 --- a/packages/web/src/datagrid/DataGrid.js +++ b/packages/web/src/datagrid/DataGrid.js @@ -63,10 +63,26 @@ const TableBodyCell = styled.td` overflow: hidden; `; -export default function DataGrid({ params }) { +/** + * @param {object} props + * @param {number} props.conid + * @param {string} props.database + * @param {import('@dbgate/datalib').GridDisplay} props.display + */ +export default function DataGrid(props) { + const { conid, database, display } = props; + const sql = display.getPageQuery(0, 100); + + console.log(`GRID, conid=${conid}, database=${database}, sql=${sql}`); + const data = useFetch({ - url: 'tables/table-data', - params, + url: 'database-connections/query-data', + method: 'post', + params: { + conid, + database, + }, + data: { sql }, }); const { rows, columns } = data || {}; const [firstVisibleRowScrollIndex, setFirstVisibleRowScrollIndex] = React.useState(0); @@ -116,21 +132,21 @@ export default function DataGrid({ params }) { columnSizes.setExtraordinaryIndexes([], []); for (let colIndex = 0; colIndex < columns.length; colIndex++) { - //this.columnSizes.PutSizeOverride(col, this.columns[col].Name.length * 8); - let column = columns[colIndex]; + //this.columnSizes.PutSizeOverride(col, this.columns[col].Name.length * 8); + let column = columns[colIndex]; - // if (column.columnClientObject != null && column.columnClientObject.notNull) context.font = "bold 14px Helvetica"; - // else context.font = "14px Helvetica"; - context.font = "bold 14px Helvetica"; + // if (column.columnClientObject != null && column.columnClientObject.notNull) context.font = "bold 14px Helvetica"; + // else context.font = "14px Helvetica"; + context.font = 'bold 14px Helvetica'; - let text = column.name; - let headerWidth = context.measureText(text).width + 32; + let text = column.name; + let headerWidth = context.measureText(text).width + 32; - // if (column.columnClientObject != null && column.columnClientObject.icon != null) headerWidth += 16; - // if (this.getFilterOnColumn(column.uniquePath)) headerWidth += 16; - // if (this.getSortOrder(column.uniquePath)) headerWidth += 16; + // if (column.columnClientObject != null && column.columnClientObject.icon != null) headerWidth += 16; + // if (this.getFilterOnColumn(column.uniquePath)) headerWidth += 16; + // if (this.getSortOrder(column.uniquePath)) headerWidth += 16; - columnSizes.putSizeOverride(colIndex, headerWidth); + columnSizes.putSizeOverride(colIndex, headerWidth); } // let headerWidth = this.rowHeaderWidthDefault; diff --git a/packages/web/src/tabs/TableDataTab.js b/packages/web/src/tabs/TableDataTab.js index 86c897b1..2c14ecd4 100644 --- a/packages/web/src/tabs/TableDataTab.js +++ b/packages/web/src/tabs/TableDataTab.js @@ -3,7 +3,22 @@ import useFetch from '../utility/useFetch'; import styled from 'styled-components'; import theme from '../theme'; import DataGrid from '../datagrid/DataGrid'; +import { TableGridDisplay } from '@dbgate/datalib'; +import useTableInfo from '../utility/useTableInfo'; +import useConnectionInfo from '../utility/useConnectionInfo'; +import engines from '@dbgate/engines'; export default function TableDataTab({ conid, database, schemaName, pureName }) { - return ; + const tableInfo = useTableInfo({ conid, database, schemaName, pureName }); + const connection = useConnectionInfo(conid); + if (!tableInfo || !connection) return null; + const display = new TableGridDisplay(tableInfo, engines(connection)); + return ( + + ); } diff --git a/packages/web/src/utility/useFetch.js b/packages/web/src/utility/useFetch.js index 87883f7b..490aa299 100644 --- a/packages/web/src/utility/useFetch.js +++ b/packages/web/src/utility/useFetch.js @@ -1,16 +1,18 @@ import React from 'react'; +import _ from 'lodash'; import axios from './axios'; import useSocket from './SocketProvider'; import stableStringify from 'json-stable-stringify'; export default function useFetch({ url, + data = undefined, params = undefined, defaultValue = undefined, reloadTrigger = undefined, ...config }) { - const [value, setValue] = React.useState(defaultValue); + const [value, setValue] = React.useState([defaultValue, []]); const [loadCounter, setLoadCounter] = React.useState(0); const socket = useSocket(); @@ -18,24 +20,30 @@ export default function useFetch({ setLoadCounter(loadCounter + 1); }; - async function loadValue() { + const indicators = [url, stableStringify(data), stableStringify(params), loadCounter]; + + async function loadValue(loadedIndicators) { const resp = await axios.request({ method: 'get', params, url, + data, ...config, }); - setValue(resp.data); + setValue([resp.data, loadedIndicators]); } React.useEffect(() => { - loadValue(); + loadValue(indicators); if (reloadTrigger && socket) { socket.on(reloadTrigger, handleReload); return () => { socket.off(reloadTrigger, handleReload); }; } - }, [url, stableStringify(params), socket, loadCounter]); + }, [...indicators, socket]); - return value; + const [returnValue, loadedIndicators] = value; + if (_.isEqual(indicators, loadedIndicators)) return returnValue; + + return defaultValue; }