generate update script

This commit is contained in:
Jan Prochazka 2020-03-23 20:41:40 +01:00
parent 1560b7c2e0
commit 464662cb18
17 changed files with 281 additions and 71 deletions

View File

@ -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),
];
}

View File

@ -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,

View File

@ -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() {

View File

@ -57,7 +57,8 @@ const driver = {
createDumper() {
return new MsSqlDumper(this);
},
dialect
dialect,
engine: 'mssql',
};
module.exports = driver;

View File

@ -52,7 +52,8 @@ const driver = {
createDumper() {
return new MySqlDumper(this);
},
dialect
dialect,
engine: 'mysql',
};
module.exports = driver;

View File

@ -40,6 +40,7 @@ const driver = {
return rows;
},
dialect,
engine: 'postgres',
};
module.exports = driver;

View File

@ -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;
}
}

View File

@ -1,5 +1,5 @@
export * from './types';
export * from './dumpSqlCommand';
export * from './treeToSql';
export * from './utility';
export * from './dumpSqlSource';
export * from './dumpSqlCondition';

View File

@ -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;
}

View File

@ -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";

View 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;
}

View File

@ -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 }>;

View File

@ -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) {

View File

@ -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) {

View File

@ -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';

View File

@ -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>
);
}

View 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>
);
}