mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-02 12:33:58 +00:00
query - basic print workflow - messages on client
This commit is contained in:
@@ -49,7 +49,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/** @param {import('@dbgate/types').OpenedDatabaseConnection} conn */
|
/** @param {import('@dbgate/types').OpenedDatabaseConnection} conn */
|
||||||
async sendRequest(conn, message) {
|
sendRequest(conn, message) {
|
||||||
const msgid = uuidv1();
|
const msgid = uuidv1();
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
this.requests[msgid] = [resolve, reject];
|
this.requests[msgid] = [resolve, reject];
|
||||||
|
|||||||
68
packages/api/src/controllers/sessions.js
Normal file
68
packages/api/src/controllers/sessions.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const uuidv1 = require('uuid/v1');
|
||||||
|
const connections = require('./connections');
|
||||||
|
const socket = require('../utility/socket');
|
||||||
|
const { fork } = require('child_process');
|
||||||
|
const DatabaseAnalyser = require('@dbgate/engines/default/DatabaseAnalyser');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/** @type {import('@dbgate/types').OpenedSession[]} */
|
||||||
|
opened: [],
|
||||||
|
|
||||||
|
handle_error(sesid, props) {
|
||||||
|
const { error } = props;
|
||||||
|
console.log(`Error in database session ${sesid}: ${error}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// handle_row(sesid, props) {
|
||||||
|
// const { row } = props;
|
||||||
|
// socket.emit('sessionRow', row);
|
||||||
|
// },
|
||||||
|
|
||||||
|
handle_info(sesid, props) {
|
||||||
|
const { info } = props;
|
||||||
|
socket.emit(`session-info-${sesid}`, info);
|
||||||
|
},
|
||||||
|
|
||||||
|
create_meta: 'post',
|
||||||
|
async create({ conid, database }) {
|
||||||
|
const sesid = uuidv1();
|
||||||
|
const connection = await connections.get({ conid });
|
||||||
|
const subprocess = fork(process.argv[1], ['sessionProcess']);
|
||||||
|
const newOpened = {
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
subprocess,
|
||||||
|
connection,
|
||||||
|
sesid,
|
||||||
|
};
|
||||||
|
this.opened.push(newOpened);
|
||||||
|
// @ts-ignore
|
||||||
|
subprocess.on('message', ({ msgtype, ...message }) => {
|
||||||
|
this[`handle_${msgtype}`](sesid, message);
|
||||||
|
});
|
||||||
|
subprocess.send({ msgtype: 'connect', ...connection, database });
|
||||||
|
return newOpened;
|
||||||
|
},
|
||||||
|
|
||||||
|
executeQuery_meta: 'post',
|
||||||
|
async executeQuery({ sesid, sql }) {
|
||||||
|
const session = this.opened.find((x) => x.sesid == sesid);
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Invalid session');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Processing query, sesid=${sesid}, sql=${sql}`);
|
||||||
|
session.subprocess.send({ msgtype: 'executeQuery', sql });
|
||||||
|
|
||||||
|
return { state: 'ok' };
|
||||||
|
},
|
||||||
|
|
||||||
|
// runCommand_meta: 'post',
|
||||||
|
// async runCommand({ conid, database, sql }) {
|
||||||
|
// console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
|
||||||
|
// const opened = await this.ensureOpened(conid, database);
|
||||||
|
// const res = await this.sendRequest(opened, { msgtype: 'queryData', sql });
|
||||||
|
// return res;
|
||||||
|
// },
|
||||||
|
};
|
||||||
@@ -10,6 +10,7 @@ const connections = require('./controllers/connections');
|
|||||||
const serverConnections = require('./controllers/serverConnections');
|
const serverConnections = require('./controllers/serverConnections');
|
||||||
const databaseConnections = require('./controllers/databaseConnections');
|
const databaseConnections = require('./controllers/databaseConnections');
|
||||||
const tables = require('./controllers/tables');
|
const tables = require('./controllers/tables');
|
||||||
|
const sessions = require('./controllers/sessions');
|
||||||
const socket = require('./utility/socket');
|
const socket = require('./utility/socket');
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
@@ -27,6 +28,7 @@ function start() {
|
|||||||
useController(app, '/server-connections', serverConnections);
|
useController(app, '/server-connections', serverConnections);
|
||||||
useController(app, '/database-connections', databaseConnections);
|
useController(app, '/database-connections', databaseConnections);
|
||||||
useController(app, '/tables', tables);
|
useController(app, '/tables', tables);
|
||||||
|
useController(app, '/sessions', sessions);
|
||||||
|
|
||||||
if (fs.existsSync('/home/dbgate-docker/build')) {
|
if (fs.existsSync('/home/dbgate-docker/build')) {
|
||||||
// server static files inside docker container
|
// server static files inside docker container
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
const connectProcess = require('./connectProcess');
|
const connectProcess = require('./connectProcess');
|
||||||
const databaseConnectionProcess = require('./databaseConnectionProcess');
|
const databaseConnectionProcess = require('./databaseConnectionProcess');
|
||||||
const serverConnectionProcess = require('./serverConnectionProcess');
|
const serverConnectionProcess = require('./serverConnectionProcess');
|
||||||
|
const sessionProcess = require('./sessionProcess');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
connectProcess,
|
connectProcess,
|
||||||
databaseConnectionProcess,
|
databaseConnectionProcess,
|
||||||
serverConnectionProcess,
|
serverConnectionProcess,
|
||||||
|
sessionProcess,
|
||||||
};
|
};
|
||||||
|
|||||||
69
packages/api/src/proc/sessionProcess.js
Normal file
69
packages/api/src/proc/sessionProcess.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const engines = require('@dbgate/engines');
|
||||||
|
const driverConnect = require('../utility/driverConnect');
|
||||||
|
|
||||||
|
let systemConnection;
|
||||||
|
let storedConnection;
|
||||||
|
let afterConnectCallbacks = [];
|
||||||
|
|
||||||
|
async function handleConnect(connection) {
|
||||||
|
storedConnection = connection;
|
||||||
|
|
||||||
|
const driver = engines(storedConnection);
|
||||||
|
systemConnection = await driverConnect(driver, storedConnection);
|
||||||
|
for (const [resolve, reject] of afterConnectCallbacks) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
afterConnectCallbacks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitConnected() {
|
||||||
|
if (systemConnection) return Promise.resolve();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
afterConnectCallbacks.push([resolve, reject]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleExecuteQuery({ sql }) {
|
||||||
|
await waitConnected();
|
||||||
|
const driver = engines(storedConnection);
|
||||||
|
|
||||||
|
await driver.stream(systemConnection, sql, {
|
||||||
|
recordset: (columns) => {
|
||||||
|
process.send({ msgtype: 'recordset', columns });
|
||||||
|
},
|
||||||
|
row: (row) => {
|
||||||
|
process.send({ msgtype: 'row', row });
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
process.send({ msgtype: 'error', error });
|
||||||
|
},
|
||||||
|
done: (result) => {
|
||||||
|
process.send({ msgtype: 'done', result });
|
||||||
|
},
|
||||||
|
info: (info) => {
|
||||||
|
process.send({ msgtype: 'info', info });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageHandlers = {
|
||||||
|
connect: handleConnect,
|
||||||
|
executeQuery: handleExecuteQuery,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleMessage({ msgtype, ...other }) {
|
||||||
|
const handler = messageHandlers[msgtype];
|
||||||
|
await handler(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
process.on('message', async (message) => {
|
||||||
|
try {
|
||||||
|
await handleMessage(message);
|
||||||
|
} catch (e) {
|
||||||
|
process.send({ msgtype: 'error', error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { start };
|
||||||
@@ -44,6 +44,35 @@ const driver = {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
async stream(pool, sql, options) {
|
||||||
|
const request = await pool.request();
|
||||||
|
|
||||||
|
const handleInfo = (info) => {
|
||||||
|
const { message, lineNumber, procName } = info;
|
||||||
|
options.info({
|
||||||
|
message,
|
||||||
|
line: lineNumber,
|
||||||
|
procedure: procName,
|
||||||
|
time: new Date(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDone = (result) => {
|
||||||
|
console.log('RESULT', result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRow = (row) => {
|
||||||
|
console.log('ROW', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.stream = true;
|
||||||
|
request.on('recordset', options.recordset);
|
||||||
|
request.on('row', handleRow);
|
||||||
|
request.on('error', options.error);
|
||||||
|
request.on('done', handleDone);
|
||||||
|
request.on('info', handleInfo);
|
||||||
|
request.query(sql);
|
||||||
|
},
|
||||||
async getVersion(pool) {
|
async getVersion(pool) {
|
||||||
const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0];
|
const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0];
|
||||||
return { version };
|
return { version };
|
||||||
|
|||||||
17
packages/types/engines.d.ts
vendored
17
packages/types/engines.d.ts
vendored
@@ -1,12 +1,21 @@
|
|||||||
import { QueryResult } from "./query";
|
import { QueryResult } from './query';
|
||||||
import { SqlDialect } from "./dialect";
|
import { SqlDialect } from './dialect';
|
||||||
import { SqlDumper } from "./dumper";
|
import { SqlDumper } from './dumper';
|
||||||
import { DatabaseInfo } from "./dbinfo";
|
import { DatabaseInfo } from './dbinfo';
|
||||||
|
|
||||||
|
export interface StreamOptions {
|
||||||
|
recordset: (columns) => void;
|
||||||
|
row: (row) => void;
|
||||||
|
error: (error) => void;
|
||||||
|
done: (result) => void;
|
||||||
|
info: (info) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EngineDriver {
|
export interface EngineDriver {
|
||||||
engine: string;
|
engine: string;
|
||||||
connect(nativeModules, { server, port, user, password, database }): any;
|
connect(nativeModules, { server, port, user, password, database }): any;
|
||||||
query(pool: any, sql: string): Promise<QueryResult>;
|
query(pool: any, sql: string): Promise<QueryResult>;
|
||||||
|
stream(pool: any, sql: string, options: StreamOptions);
|
||||||
getVersion(pool: any): Promise<{ version: string }>;
|
getVersion(pool: any): Promise<{ version: string }>;
|
||||||
listDatabases(
|
listDatabases(
|
||||||
pool: any
|
pool: any
|
||||||
|
|||||||
7
packages/types/index.d.ts
vendored
7
packages/types/index.d.ts
vendored
@@ -7,6 +7,13 @@ export interface OpenedDatabaseConnection {
|
|||||||
subprocess: ChildProcess;
|
subprocess: ChildProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenedSession {
|
||||||
|
sesid: string;
|
||||||
|
conid: string;
|
||||||
|
database: string;
|
||||||
|
subprocess: ChildProcess;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StoredConnection {
|
export interface StoredConnection {
|
||||||
engine: string;
|
engine: string;
|
||||||
server: string;
|
server: string;
|
||||||
|
|||||||
24
packages/web/src/query/MessagesView.js
Normal file
24
packages/web/src/query/MessagesView.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function MessagesView({ items }) {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Number</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Procedure</th>
|
||||||
|
<th>Line</th>
|
||||||
|
</tr>
|
||||||
|
{items.map((row, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{index + 1}</td>
|
||||||
|
<td>{row.message}</td>
|
||||||
|
<td>{row.time}</td>
|
||||||
|
<td>{row.procedure}</td>
|
||||||
|
<td>{row.line}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react';
|
||||||
import ToolbarButton from '../widgets/ToolbarButton'
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function QueryToolbar() {
|
export default function QueryToolbar({ execute,isDatabaseDefined }) {
|
||||||
return <>
|
return (
|
||||||
<ToolbarButton onClick={()=>{}}>Execute</ToolbarButton>
|
<>
|
||||||
|
<ToolbarButton disabled={!isDatabaseDefined} onClick={execute}>Execute</ToolbarButton>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|||||||
21
packages/web/src/query/SessionMessagesView.js
Normal file
21
packages/web/src/query/SessionMessagesView.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MessagesView from './MessagesView';
|
||||||
|
import useSocket from '../utility/SocketProvider';
|
||||||
|
|
||||||
|
export default function SessionMessagesView({ sessionId }) {
|
||||||
|
const [messages, setMessages] = React.useState([]);
|
||||||
|
const socket = useSocket();
|
||||||
|
|
||||||
|
const handleInfo = React.useCallback((info) => setMessages((items) => [...items, info]), []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (sessionId && socket) {
|
||||||
|
socket.on(`session-info-${sessionId}`, handleInfo);
|
||||||
|
return () => {
|
||||||
|
socket.off(`session-info-${sessionId}`, handleInfo);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [sessionId, socket]);
|
||||||
|
|
||||||
|
return <MessagesView items={messages} />;
|
||||||
|
}
|
||||||
@@ -1,17 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import axios from '../utility/axios';
|
||||||
import engines from '@dbgate/engines';
|
import engines from '@dbgate/engines';
|
||||||
import useTableInfo from '../utility/useTableInfo';
|
import useTableInfo from '../utility/useTableInfo';
|
||||||
import useConnectionInfo from '../utility/useConnectionInfo';
|
import useConnectionInfo from '../utility/useConnectionInfo';
|
||||||
import SqlEditor from '../sqleditor/SqlEditor';
|
import SqlEditor from '../sqleditor/SqlEditor';
|
||||||
import { useUpdateDatabaseForTab } from '../utility/globalState';
|
import { useUpdateDatabaseForTab } from '../utility/globalState';
|
||||||
import QueryToolbar from '../query/QueryToolbar';
|
import QueryToolbar from '../query/QueryToolbar';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import SessionMessagesView from '../query/SessionMessagesView';
|
||||||
|
|
||||||
|
const MainContainer = styled.div``;
|
||||||
|
|
||||||
|
const EditorContainer = styled.div`
|
||||||
|
height: 600px;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MessagesContainer = styled.div``;
|
||||||
|
|
||||||
export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPortalRef }) {
|
export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPortalRef }) {
|
||||||
const localStorageKey = `sql_${tabid}`;
|
const localStorageKey = `sql_${tabid}`;
|
||||||
const [queryText, setQueryText] = React.useState(() => localStorage.getItem(localStorageKey) || '');
|
const [queryText, setQueryText] = React.useState(() => localStorage.getItem(localStorageKey) || '');
|
||||||
const queryTextRef = React.useRef(queryText);
|
const queryTextRef = React.useRef(queryText);
|
||||||
|
const [sessionId, setSessionId] = React.useState(null);
|
||||||
|
|
||||||
const saveToStorage = React.useCallback(() => localStorage.setItem(localStorageKey, queryTextRef.current), [
|
const saveToStorage = React.useCallback(() => localStorage.setItem(localStorageKey, queryTextRef.current), [
|
||||||
localStorageKey,
|
localStorageKey,
|
||||||
@@ -22,26 +35,57 @@ export default function QueryTab({ tabid, conid, database, tabVisible, toolbarPo
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener('beforeunload', saveToStorage);
|
window.addEventListener('beforeunload', saveToStorage);
|
||||||
return () => {
|
return () => {
|
||||||
|
saveToStorage();
|
||||||
window.removeEventListener('beforeunload', saveToStorage);
|
window.removeEventListener('beforeunload', saveToStorage);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useUpdateDatabaseForTab(tabVisible, conid, database);
|
useUpdateDatabaseForTab(tabVisible, conid, database);
|
||||||
|
const connection = useConnectionInfo(conid);
|
||||||
|
|
||||||
const handleChange = text => {
|
const handleChange = (text) => {
|
||||||
if (text != null) queryTextRef.current = text;
|
if (text != null) queryTextRef.current = text;
|
||||||
setQueryText(text);
|
setQueryText(text);
|
||||||
saveToStorageDebounced();
|
saveToStorageDebounced();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const handleExecute = async () => {
|
||||||
<>
|
let sesid = sessionId;
|
||||||
<SqlEditor value={queryText} onChange={handleChange} tabVisible={tabVisible} />
|
if (!sesid) {
|
||||||
|
const resp = await axios.post('sessions/create', {
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
});
|
||||||
|
sesid = resp.data.sesid;
|
||||||
|
setSessionId(sesid);
|
||||||
|
}
|
||||||
|
const resp2 = await axios.post('sessions/execute-query', {
|
||||||
|
sesid,
|
||||||
|
sql: queryText,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
{toolbarPortalRef &&
|
return (
|
||||||
toolbarPortalRef.current &&
|
<MainContainer>
|
||||||
tabVisible &&
|
<EditorContainer>
|
||||||
ReactDOM.createPortal(<QueryToolbar />, toolbarPortalRef.current)}
|
<SqlEditor
|
||||||
</>
|
value={queryText}
|
||||||
|
onChange={handleChange}
|
||||||
|
tabVisible={tabVisible}
|
||||||
|
engine={connection && connection.engine}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{toolbarPortalRef &&
|
||||||
|
toolbarPortalRef.current &&
|
||||||
|
tabVisible &&
|
||||||
|
ReactDOM.createPortal(
|
||||||
|
<QueryToolbar isDatabaseDefined={conid && database} execute={handleExecute} />,
|
||||||
|
toolbarPortalRef.current
|
||||||
|
)}
|
||||||
|
</EditorContainer>
|
||||||
|
<MessagesContainer>
|
||||||
|
<SessionMessagesView sessionId={sessionId} />
|
||||||
|
</MessagesContainer>
|
||||||
|
</MainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ export default function useFetch({
|
|||||||
const [loadCounter, setLoadCounter] = React.useState(0);
|
const [loadCounter, setLoadCounter] = React.useState(0);
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
|
|
||||||
const handleReload = () => {
|
const handleReload = React.useCallback(() => {
|
||||||
setLoadCounter(loadCounter + 1);
|
setLoadCounter((counter) => counter + 1);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const indicators = [url, stableStringify(data), stableStringify(params), loadCounter];
|
const indicators = [url, stableStringify(data), stableStringify(params), loadCounter];
|
||||||
|
|
||||||
@@ -32,15 +32,29 @@ export default function useFetch({
|
|||||||
});
|
});
|
||||||
setValue([resp.data, loadedIndicators]);
|
setValue([resp.data, loadedIndicators]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// React.useEffect(() => {
|
||||||
|
// loadValue(indicators);
|
||||||
|
// if (reloadTrigger && socket) {
|
||||||
|
// socket.on(reloadTrigger, handleReload);
|
||||||
|
// return () => {
|
||||||
|
// socket.off(reloadTrigger, handleReload);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }, [...indicators, socket]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
loadValue(indicators);
|
loadValue(indicators);
|
||||||
|
}, [...indicators]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
if (reloadTrigger && socket) {
|
if (reloadTrigger && socket) {
|
||||||
socket.on(reloadTrigger, handleReload);
|
socket.on(reloadTrigger, handleReload);
|
||||||
return () => {
|
return () => {
|
||||||
socket.off(reloadTrigger, handleReload);
|
socket.off(reloadTrigger, handleReload);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [...indicators, socket]);
|
}, [socket, reloadTrigger]);
|
||||||
|
|
||||||
const [returnValue, loadedIndicators] = value;
|
const [returnValue, loadedIndicators] = value;
|
||||||
if (_.isEqual(indicators, loadedIndicators)) return returnValue;
|
if (_.isEqual(indicators, loadedIndicators)) return returnValue;
|
||||||
|
|||||||
Reference in New Issue
Block a user