mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
generate update script
This commit is contained in:
parent
1560b7c2e0
commit
464662cb18
@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import { Command, Insert, Update, Delete, UpdateField, Condition } from '@dbgate/sqltree';
|
||||
|
||||
export interface ChangeSetItem {
|
||||
pureName: string;
|
||||
@ -100,3 +101,81 @@ export function setChangeSetValue(
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function extractFields(item: ChangeSetItem): UpdateField[] {
|
||||
return _.keys(item.fields).map(targetColumn => ({
|
||||
targetColumn,
|
||||
exprType: 'value',
|
||||
value: item.fields[targetColumn],
|
||||
}));
|
||||
}
|
||||
|
||||
function insertToSql(item: ChangeSetItem): Insert {
|
||||
return {
|
||||
targetTable: {
|
||||
pureName: item.pureName,
|
||||
schemaName: item.schemaName,
|
||||
},
|
||||
commandType: 'insert',
|
||||
fields: extractFields(item),
|
||||
};
|
||||
}
|
||||
|
||||
function extractCondition(item: ChangeSetItem): Condition {
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: _.keys(item.condition).map(columnName => ({
|
||||
conditionType: 'binary',
|
||||
operator: '=',
|
||||
left: {
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
source: {
|
||||
name: {
|
||||
pureName: item.pureName,
|
||||
schemaName: item.schemaName,
|
||||
},
|
||||
},
|
||||
},
|
||||
right: {
|
||||
exprType: 'value',
|
||||
value: item.condition[columnName],
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function updateToSql(item: ChangeSetItem): Update {
|
||||
return {
|
||||
from: {
|
||||
name: {
|
||||
pureName: item.pureName,
|
||||
schemaName: item.schemaName,
|
||||
},
|
||||
},
|
||||
commandType: 'update',
|
||||
fields: extractFields(item),
|
||||
where: extractCondition(item),
|
||||
};
|
||||
}
|
||||
|
||||
function deleteToSql(item: ChangeSetItem): Delete {
|
||||
return {
|
||||
from: {
|
||||
name: {
|
||||
pureName: item.pureName,
|
||||
schemaName: item.schemaName,
|
||||
},
|
||||
},
|
||||
commandType: 'delete',
|
||||
where: extractCondition(item),
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSetToSql(changeSet: ChangeSet): Command[] {
|
||||
return [
|
||||
...changeSet.inserts.map(insertToSql),
|
||||
...changeSet.updates.map(updateToSql),
|
||||
...changeSet.deletes.map(deleteToSql),
|
||||
];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { GridConfig, GridCache, GridConfigColumns } from './GridConfig';
|
||||
import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType } from '@dbgate/types';
|
||||
import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType, EngineDriver } from '@dbgate/types';
|
||||
import { parseFilter, getFilterType } from '@dbgate/filterparser';
|
||||
import { filterName } from './filterName';
|
||||
import { Select, Expression } from '@dbgate/sqltree';
|
||||
@ -44,12 +44,14 @@ export abstract class GridDisplay {
|
||||
protected setConfig: (config: GridConfig) => void,
|
||||
public cache: GridCache,
|
||||
protected setCache: (config: GridCache) => void,
|
||||
protected getTableInfo: ({ schemaName, pureName }) => Promise<TableInfo>
|
||||
protected getTableInfo: ({ schemaName, pureName }) => Promise<TableInfo>,
|
||||
public driver: EngineDriver
|
||||
) {}
|
||||
abstract getPageQuery(offset: number, count: number): string;
|
||||
columns: DisplayColumn[];
|
||||
baseTable?: TableInfo;
|
||||
changeSetKeyFields: string[] = null;
|
||||
|
||||
setColumnVisibility(uniquePath: string[], isVisible: boolean) {
|
||||
const uniqueName = uniquePath.join('.');
|
||||
if (uniquePath.length == 1) {
|
||||
@ -60,6 +62,10 @@ export abstract class GridDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
get engine() {
|
||||
return this.driver.engine;
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.setCache({
|
||||
...this.cache,
|
||||
|
@ -7,19 +7,21 @@ import { GridConfig, GridCache } from './GridConfig';
|
||||
export class TableGridDisplay extends GridDisplay {
|
||||
constructor(
|
||||
public table: TableInfo,
|
||||
public driver: EngineDriver,
|
||||
driver: EngineDriver,
|
||||
config: GridConfig,
|
||||
setConfig: (config: GridConfig) => void,
|
||||
cache: GridCache,
|
||||
setCache: (config: GridCache) => void,
|
||||
getTableInfo: ({ schemaName, pureName }) => Promise<TableInfo>
|
||||
) {
|
||||
super(config, setConfig, cache, setCache, getTableInfo);
|
||||
super(config, setConfig, cache, setCache, getTableInfo, driver);
|
||||
this.columns = this.getDisplayColumns(table, []);
|
||||
this.baseTable = table;
|
||||
this.changeSetKeyFields = table.primaryKey
|
||||
? table.primaryKey.columns.map(x => x.columnName)
|
||||
: table.columns.map(x => x.columnName);
|
||||
if (table && table.columns) {
|
||||
this.changeSetKeyFields = table.primaryKey
|
||||
? table.primaryKey.columns.map(x => x.columnName)
|
||||
: table.columns.map(x => x.columnName);
|
||||
}
|
||||
}
|
||||
|
||||
createSelect() {
|
||||
|
@ -57,7 +57,8 @@ const driver = {
|
||||
createDumper() {
|
||||
return new MsSqlDumper(this);
|
||||
},
|
||||
dialect
|
||||
dialect,
|
||||
engine: 'mssql',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
|
@ -52,7 +52,8 @@ const driver = {
|
||||
createDumper() {
|
||||
return new MySqlDumper(this);
|
||||
},
|
||||
dialect
|
||||
dialect,
|
||||
engine: 'mysql',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
|
@ -40,6 +40,7 @@ const driver = {
|
||||
return rows;
|
||||
},
|
||||
dialect,
|
||||
engine: 'postgres',
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
|
@ -1,62 +1,111 @@
|
||||
import { SqlDumper } from '@dbgate/types';
|
||||
import { Command, Select } from './types';
|
||||
import { Command, Select, Update, Delete, Insert } from './types';
|
||||
import { dumpSqlExpression } from './dumpSqlExpression';
|
||||
import { dumpSqlFromDefinition } from './dumpSqlSource';
|
||||
import { dumpSqlFromDefinition, dumpSqlSourceRef } from './dumpSqlSource';
|
||||
import { dumpSqlCondition } from './dumpSqlCondition';
|
||||
|
||||
export function dumpSqlSelect(dmp: SqlDumper, select: Select) {
|
||||
export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||
dmp.put('^select ');
|
||||
if (select.topRecords) {
|
||||
dmp.put('^top %s ', select.topRecords);
|
||||
if (cmd.topRecords) {
|
||||
dmp.put('^top %s ', cmd.topRecords);
|
||||
}
|
||||
if (select.distinct) {
|
||||
if (cmd.distinct) {
|
||||
dmp.put('^distinct ');
|
||||
}
|
||||
if (select.selectAll) {
|
||||
if (cmd.selectAll) {
|
||||
dmp.put('* ');
|
||||
}
|
||||
if (select.columns) {
|
||||
if (select.selectAll) dmp.put('&n,');
|
||||
if (cmd.columns) {
|
||||
if (cmd.selectAll) dmp.put('&n,');
|
||||
dmp.put('&>&n');
|
||||
dmp.putCollection(',&n', select.columns, fld => {
|
||||
dmp.putCollection(',&n', cmd.columns, fld => {
|
||||
dumpSqlExpression(dmp, fld);
|
||||
if (fld.alias) dmp.put(' ^as %i', fld.alias);
|
||||
});
|
||||
dmp.put('&n&<');
|
||||
}
|
||||
dmp.put('^from ');
|
||||
dumpSqlFromDefinition(dmp, select.from);
|
||||
if (select.where) {
|
||||
dumpSqlFromDefinition(dmp, cmd.from);
|
||||
if (cmd.where) {
|
||||
dmp.put('&n^where ');
|
||||
dumpSqlCondition(dmp, select.where);
|
||||
dumpSqlCondition(dmp, cmd.where);
|
||||
dmp.put('&n');
|
||||
}
|
||||
if (select.groupBy) {
|
||||
if (cmd.groupBy) {
|
||||
dmp.put('&n^group ^by ');
|
||||
dmp.putCollection(', ', select.groupBy, expr => dumpSqlExpression(dmp, expr));
|
||||
dmp.putCollection(', ', cmd.groupBy, expr => dumpSqlExpression(dmp, expr));
|
||||
dmp.put('&n');
|
||||
}
|
||||
if (select.orderBy) {
|
||||
if (cmd.orderBy) {
|
||||
dmp.put('&n^order ^by ');
|
||||
dmp.putCollection(', ', select.orderBy, expr => {
|
||||
dmp.putCollection(', ', cmd.orderBy, expr => {
|
||||
dumpSqlExpression(dmp, expr);
|
||||
dmp.put(' %k', expr.direction);
|
||||
});
|
||||
dmp.put('&n');
|
||||
}
|
||||
if (select.range) {
|
||||
if (cmd.range) {
|
||||
if (dmp.dialect.offsetFetchRangeSyntax) {
|
||||
dmp.put('^offset %s ^rows ^fetch ^next %s ^rows ^only', select.range.offset, select.range.limit);
|
||||
dmp.put('^offset %s ^rows ^fetch ^next %s ^rows ^only', cmd.range.offset, cmd.range.limit);
|
||||
} else {
|
||||
dmp.put('^limit %s ^offset %s ', select.range.limit, select.range.offset);
|
||||
dmp.put('^limit %s ^offset %s ', cmd.range.limit, cmd.range.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dumpSqlCommand(dmp: SqlDumper, command: Command) {
|
||||
switch (command.commandType) {
|
||||
export function dumpSqlUpdate(dmp: SqlDumper, cmd: Update) {
|
||||
dmp.put('^update ');
|
||||
dumpSqlSourceRef(dmp, cmd.from);
|
||||
|
||||
dmp.put('&n^set ');
|
||||
dmp.put('&>');
|
||||
dmp.putCollection(', ', cmd.fields, col => {
|
||||
dmp.put('%i=', col.targetColumn);
|
||||
dumpSqlExpression(dmp, col);
|
||||
});
|
||||
dmp.put('&<');
|
||||
|
||||
if (cmd.where) {
|
||||
dmp.put('&n^where ');
|
||||
dumpSqlCondition(dmp, cmd.where);
|
||||
dmp.put('&n');
|
||||
}
|
||||
}
|
||||
|
||||
export function dumpSqlDelete(dmp: SqlDumper, cmd: Delete) {
|
||||
dmp.put('^delete ');
|
||||
dumpSqlSourceRef(dmp, cmd.from);
|
||||
|
||||
if (cmd.where) {
|
||||
dmp.put('&n^where ');
|
||||
dumpSqlCondition(dmp, cmd.where);
|
||||
dmp.put('&n');
|
||||
}
|
||||
}
|
||||
|
||||
export function dumpSqlInsert(dmp: SqlDumper, cmd: Insert) {
|
||||
dmp.put(
|
||||
'^insert ^into %f (%,i) ^values (',
|
||||
cmd.targetTable,
|
||||
cmd.fields.map(x => x.targetColumn)
|
||||
);
|
||||
dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x));
|
||||
dmp.put(')');
|
||||
}
|
||||
|
||||
export function dumpSqlCommand(dmp: SqlDumper, cmd: Command) {
|
||||
switch (cmd.commandType) {
|
||||
case 'select':
|
||||
dumpSqlSelect(dmp, command);
|
||||
dumpSqlSelect(dmp, cmd);
|
||||
break;
|
||||
case 'update':
|
||||
dumpSqlUpdate(dmp, cmd);
|
||||
break;
|
||||
case 'delete':
|
||||
dumpSqlDelete(dmp, cmd);
|
||||
break;
|
||||
case 'insert':
|
||||
dumpSqlInsert(dmp, cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export * from './types';
|
||||
export * from './dumpSqlCommand';
|
||||
export * from './treeToSql';
|
||||
export * from './utility';
|
||||
export * from './dumpSqlSource';
|
||||
export * from './dumpSqlCondition';
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { EngineDriver, SqlDumper } from '@dbgate/types';
|
||||
|
||||
export function treeToSql<T>(driver: EngineDriver, object: T, func: (dmp: SqlDumper, obj: T) => void) {
|
||||
const dmp = driver.createDumper();
|
||||
func(dmp, object);
|
||||
return dmp.s;
|
||||
}
|
@ -18,7 +18,31 @@ export interface Select {
|
||||
where?: Condition;
|
||||
}
|
||||
|
||||
export type Command = Select;
|
||||
export type UpdateField = Expression & { targetColumn: string };
|
||||
|
||||
export interface Update {
|
||||
commandType: 'update';
|
||||
fields: UpdateField[];
|
||||
from: FromDefinition;
|
||||
where?: Condition;
|
||||
}
|
||||
|
||||
export interface Delete {
|
||||
commandType: 'delete';
|
||||
from: FromDefinition;
|
||||
where?: Condition;
|
||||
}
|
||||
|
||||
export interface Insert {
|
||||
commandType: 'insert';
|
||||
fields: UpdateField[];
|
||||
targetTable: {
|
||||
schemaName: string;
|
||||
pureName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Command = Select | Update | Delete | Insert;
|
||||
|
||||
// export interface Condition {
|
||||
// conditionType: "eq" | "not" | "binary";
|
||||
|
18
packages/sqltree/src/utility.ts
Normal file
18
packages/sqltree/src/utility.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { EngineDriver, SqlDumper } from '@dbgate/types';
|
||||
import { Command } from './types';
|
||||
import { dumpSqlCommand } from './dumpSqlCommand';
|
||||
|
||||
export function treeToSql<T>(driver: EngineDriver, object: T, func: (dmp: SqlDumper, obj: T) => void) {
|
||||
const dmp = driver.createDumper();
|
||||
func(dmp, object);
|
||||
return dmp.s;
|
||||
}
|
||||
|
||||
export function scriptToSql(driver: EngineDriver, script: Command[]): string {
|
||||
const dmp = driver.createDumper();
|
||||
for (const cmd of script) {
|
||||
dumpSqlCommand(dmp, cmd);
|
||||
dmp.endCommand();
|
||||
}
|
||||
return dmp.s;
|
||||
}
|
1
packages/types/engines.d.ts
vendored
1
packages/types/engines.d.ts
vendored
@ -4,6 +4,7 @@ import { SqlDumper } from "./dumper";
|
||||
import { DatabaseInfo } from "./dbinfo";
|
||||
|
||||
export interface EngineDriver {
|
||||
engine: string;
|
||||
connect(nativeModules, { server, port, user, password, database }): any;
|
||||
query(pool: any, sql: string): Promise<QueryResult>;
|
||||
getVersion(pool: any): Promise<{ version: string }>;
|
||||
|
@ -1,11 +1,4 @@
|
||||
import React from 'react';
|
||||
import { ColumnIcon, SequenceIcon } from '../icons';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import showModal from '../modals/showModal';
|
||||
import ConnectionModal from '../modals/ConnectionModal';
|
||||
import axios from '../utility/axios';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
|
||||
/** @param columnProps {import('@dbgate/types').ColumnInfo} */
|
||||
function getColumnIcon(columnProps) {
|
||||
|
@ -1,11 +1,4 @@
|
||||
import React from 'react';
|
||||
import { PrimaryKeyIcon, ForeignKeyIcon } from '../icons';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import showModal from '../modals/showModal';
|
||||
import ConnectionModal from '../modals/ConnectionModal';
|
||||
import axios from '../utility/axios';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
|
||||
/** @param props {import('@dbgate/types').ConstraintInfo} */
|
||||
function getConstraintIcon(props) {
|
||||
|
@ -1,11 +1,7 @@
|
||||
import React from 'react';
|
||||
import { TableIcon } from '../icons';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import showModal from '../modals/showModal';
|
||||
import ConnectionModal from '../modals/ConnectionModal';
|
||||
import axios from '../utility/axios';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import getConnectionInfo from '../utility/getConnectionInfo';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
|
||||
|
@ -24,6 +24,10 @@ import keycodes from '../utility/keycodes';
|
||||
import InplaceEditor from './InplaceEditor';
|
||||
import DataGridRow from './DataGridRow';
|
||||
import { countColumnSizes, countVisibleRealColumns } from './gridutil';
|
||||
import useModalState from '../modals/useModalState';
|
||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal';
|
||||
import { changeSetToSql } from '@dbgate/datalib';
|
||||
import { scriptToSql } from '@dbgate/sqltree';
|
||||
|
||||
const GridContainer = styled.div`
|
||||
position: absolute;
|
||||
@ -162,6 +166,8 @@ export default function DataGridCore(props) {
|
||||
const [tableBodyRef] = useDimensions();
|
||||
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
|
||||
const [tableRef, { height: tableHeight, width: tableWidth }, tableElement] = useDimensions();
|
||||
const confirmSqlModalState = useModalState();
|
||||
const [confirmSql, setConfirmSql] = React.useState('');
|
||||
|
||||
const columnSizes = React.useMemo(() => countColumnSizes(loadedRows, columns, containerWidth, display), [
|
||||
loadedRows,
|
||||
@ -221,18 +227,20 @@ export default function DataGridCore(props) {
|
||||
[columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns]
|
||||
);
|
||||
|
||||
const cellIsSelected = React.useCallback((row, col) => {
|
||||
const [currentRow, currentCol] = currentCell;
|
||||
if (row == currentRow && col == currentCol) return true;
|
||||
for (const [selectedRow, selectedCol] of selectedCells) {
|
||||
if (row == selectedRow && col == selectedCol) return true;
|
||||
if (selectedRow == 'header' && col == selectedCol) return true;
|
||||
if (row == selectedRow && selectedCol == 'header') return true;
|
||||
if (selectedRow == 'header' && selectedCol == 'header') return true;
|
||||
}
|
||||
return false;
|
||||
}, [currentCell, selectedCells]);
|
||||
|
||||
const cellIsSelected = React.useCallback(
|
||||
(row, col) => {
|
||||
const [currentRow, currentCol] = currentCell;
|
||||
if (row == currentRow && col == currentCol) return true;
|
||||
for (const [selectedRow, selectedCol] of selectedCells) {
|
||||
if (row == selectedRow && col == selectedCol) return true;
|
||||
if (selectedRow == 'header' && col == selectedCol) return true;
|
||||
if (row == selectedRow && selectedCol == 'header') return true;
|
||||
if (selectedRow == 'header' && selectedCol == 'header') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[currentCell, selectedCells]
|
||||
);
|
||||
|
||||
if (!loadedRows || !columns) return null;
|
||||
const rowCountNewIncluded = loadedRows.length;
|
||||
@ -297,6 +305,13 @@ export default function DataGridCore(props) {
|
||||
setvScrollValueToSetDate(new Date());
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
const script = changeSetToSql(changeSet);
|
||||
const sql = scriptToSql(display.driver, script);
|
||||
setConfirmSql(sql);
|
||||
confirmSqlModalState.open();
|
||||
}
|
||||
|
||||
function handleGridKeyDown(event) {
|
||||
if (
|
||||
!event.ctrlKey &&
|
||||
@ -310,6 +325,12 @@ export default function DataGridCore(props) {
|
||||
// console.log('event', event.nativeEvent);
|
||||
}
|
||||
|
||||
if (event.keyCode == keycodes.s && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
handleSave();
|
||||
// this.saveAndFocus();
|
||||
}
|
||||
|
||||
const moved = handleCursorMove(event);
|
||||
|
||||
if (moved) {
|
||||
@ -555,6 +576,7 @@ export default function DataGridCore(props) {
|
||||
onScroll={handleRowScroll}
|
||||
viewportRatio={visibleRowCountUpperBound / rowCountNewIncluded}
|
||||
/>
|
||||
<ConfirmSqlModal modalState={confirmSqlModalState} sql={confirmSql} engine={display.engine} />
|
||||
</GridContainer>
|
||||
);
|
||||
}
|
||||
|
31
packages/web/src/modals/ConfirmSqlModal.js
Normal file
31
packages/web/src/modals/ConfirmSqlModal.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormRow, FormButton, FormTextField, FormSelectField, FormSubmit } from '../utility/forms';
|
||||
import { TextField } from '../utility/inputs';
|
||||
import { Formik, Form } from 'formik';
|
||||
import SqlEditor from '../sqleditor/SqlEditor';
|
||||
// import FormikForm from '../utility/FormikForm';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SqlWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 30vh;
|
||||
width: 40vw;
|
||||
`;
|
||||
|
||||
export default function ConfirmSqlModal({ modalState, sql, engine }) {
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<h2>Save changes</h2>
|
||||
<SqlWrapper>
|
||||
<SqlEditor value={sql} engine={engine} />
|
||||
</SqlWrapper>
|
||||
|
||||
<FormRow>
|
||||
<input type="button" value="OK" onClick={modalState.close} />
|
||||
<input type="button" value="Close" onClick={modalState.close} />
|
||||
</FormRow>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user