diff --git a/app/package.json b/app/package.json
index 5f29eaaa..b031b339 100644
--- a/app/package.json
+++ b/app/package.json
@@ -20,6 +20,6 @@
"babel-core": "^5.8.29",
"babel-runtime": "^5.8.29",
"debug": "^2.2.0",
- "sqlectron-core": "^3.4.0"
+ "sqlectron-core": "^3.6.0"
}
}
diff --git a/src/renderer/actions/sqlscripts.js b/src/renderer/actions/sqlscripts.js
new file mode 100644
index 00000000..fefc93f6
--- /dev/null
+++ b/src/renderer/actions/sqlscripts.js
@@ -0,0 +1,72 @@
+import { getDBConnByName } from './connections';
+import { updateQueryIfNeeded } from './queries';
+
+
+export const GET_SCRIPT_REQUEST = 'GET_SCRIPT_REQUEST';
+export const GET_SCRIPT_SUCCESS = 'GET_SCRIPT_SUCCESS';
+export const GET_SCRIPT_FAILURE = 'GET_SCRIPT_FAILURE';
+
+
+export function getSQLScriptIfNeeded(database, item, actionType, objectType) {
+ return (dispatch, getState) => {
+ const state = getState();
+ if (shouldFetchScript(state, database, item, actionType)) {
+ return dispatch(getSQLScript(database, item, actionType, objectType));
+ } else if (isScriptAlreadyFetched(state, database, item, actionType)) {
+ const script = getAlreadyFetchedScript(state, database, item, actionType);
+ return dispatch(updateQueryIfNeeded(script));
+ }
+ };
+}
+
+function shouldFetchScript (state, database, item, actionType) {
+ const scripts = state.sqlscripts;
+ if (!scripts) return true;
+ if (scripts.isFetching) return false;
+ if (!scripts.scriptsByObject[database]) return true;
+ if (!scripts.scriptsByObject[database][item]) return true;
+ if (!scripts.scriptsByObject[database][item][actionType]) return true;
+ return scripts.didInvalidate;
+}
+
+function isScriptAlreadyFetched (state, database, item, actionType) {
+ const scripts = state.sqlscripts;
+ if (!scripts.scriptsByObject[database]) return false;
+ if (!scripts.scriptsByObject[database][item]) return false;
+ if (scripts.scriptsByObject[database][item][actionType]) return true;
+ return false;
+}
+
+function getAlreadyFetchedScript (state, database, item, actionType) {
+ return state.sqlscripts.scriptsByObject[database][item][actionType];
+}
+
+
+function getSQLScript (database, item, actionType, objectType) {
+ return async (dispatch) => {
+ dispatch({ type: GET_SCRIPT_REQUEST, database, item, actionType, objectType });
+ try {
+ const dbConn = getDBConnByName(database);
+ let script;
+ if (actionType === 'CREATE') {
+ [script] = objectType === 'Table'
+ ? await dbConn.getTableCreateScript(item)
+ : (objectType === 'View')
+ ? await dbConn.getViewCreateScript(item)
+ : await dbConn.getRoutineCreateScript(item, objectType);
+ } else if (actionType === 'SELECT') {
+ script = await dbConn.getTableSelectScript(item);
+ } else if (actionType === 'INSERT') {
+ script = await dbConn.getTableInsertScript(item);
+ } else if (actionType === 'UPDATE') {
+ script = await dbConn.getTableUpdateScript(item);
+ } else if (actionType === 'DELETE') {
+ script = await dbConn.getTableDeleteScript(item);
+ }
+ dispatch({ type: GET_SCRIPT_SUCCESS, database, item, script, actionType, objectType });
+ dispatch(updateQueryIfNeeded(script));
+ } catch (error) {
+ dispatch({ type: GET_SCRIPT_FAILURE, error });
+ }
+ };
+}
diff --git a/src/renderer/components/database-item.jsx b/src/renderer/components/database-item.jsx
new file mode 100644
index 00000000..041bf94b
--- /dev/null
+++ b/src/renderer/components/database-item.jsx
@@ -0,0 +1,135 @@
+import React, { Component, PropTypes } from 'react';
+import TableSubmenu from './table-submenu.jsx';
+import { Menu, MenuItem } from 'remote';
+
+
+const STYLE = {
+ item: { wordBreak: 'break-all', cursor: 'default' },
+};
+
+
+export default class DatabaseItem extends Component {
+ static propTypes = {
+ database: PropTypes.object.isRequired,
+ item: PropTypes.object.isRequired,
+ dbObjectType: PropTypes.string.isRequired,
+ style: PropTypes.object,
+ columnsByTable: PropTypes.object,
+ triggersByTable: PropTypes.object,
+ onSelectItem: PropTypes.func,
+ onExecuteDefaultQuery: PropTypes.func,
+ onGetSQLScript: PropTypes.func,
+ }
+
+ constructor(props, context) {
+ super(props, context);
+ this.state = {};
+ this.contextMenu;
+ }
+
+ // Context menu is built dinamically on click (if it does not exist), because building
+ // menu onComponentDidMount or onComponentWillMount slows table listing when database
+ // has a loads of tables, because menu will be created (unnecessarily) for every table shown
+ onContextMenu(e){
+ e.preventDefault();
+ if (!this.contextMenu){
+ this.buildContextMenu();
+ }
+ this.contextMenu.popup(e.clientX, e.clientY);
+ }
+
+ buildContextMenu(){
+ const {
+ database,
+ item,
+ dbObjectType,
+ onExecuteDefaultQuery,
+ onGetSQLScript,
+ } = this.props;
+ const actionTypes = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'];
+
+ this.contextMenu = new Menu();
+ if (dbObjectType === 'Table' || dbObjectType === 'View') {
+ this.contextMenu.append(new MenuItem({
+ label: 'Execute default query',
+ click: onExecuteDefaultQuery.bind(this, database, item)
+ }));
+ }
+ this.contextMenu.append(new MenuItem({
+ label: 'CREATE script',
+ click: onGetSQLScript.bind(this, database, item, 'CREATE', dbObjectType)
+ }));
+ if (dbObjectType === 'Table') {
+ actionTypes.map((actionType, index) => {
+ this.contextMenu.append(new MenuItem({
+ label: `${actionType} script`,
+ click: onGetSQLScript.bind(this, database, item, actionType, dbObjectType)
+ }));
+ });
+ }
+ }
+
+ toggleTableCollapse() {
+ this.setState({ tableCollapsed: !this.state.tableCollapsed });
+ }
+
+ renderSubItems(table) {
+ const { columnsByTable, triggersByTable, database } = this.props;
+
+ if (!columnsByTable || !columnsByTable[table]) {
+ return null;
+ }
+
+ const displayStyle = {};
+ if (!this.state.tableCollapsed) {
+ displayStyle.display = 'none';
+ }
+
+ return (
+
+ );
+ }
+
+ render() {
+ const { database, item, style, onSelectItem, dbObjectType } = this.props;
+ const hasChildElements = !!onSelectItem;
+ const onSingleClick = hasChildElements
+ ? () => {onSelectItem(database, item); this.toggleTableCollapse();}
+ : () => {};
+
+ const collapseCssClass = this.state.tableCollapsed ? 'down' : 'right';
+ const collapseIcon = (
+
+ );
+ const tableIcon = (
+
+ );
+
+ return (
+
+
+ { dbObjectType === 'Table' ? collapseIcon : null }
+ { dbObjectType === 'Table' ? tableIcon : null }
+ {item.name}
+
+ {this.renderSubItems(item.name)}
+
+ );
+ }
+}
diff --git a/src/renderer/components/database-list-item-metadata.jsx b/src/renderer/components/database-list-item-metadata.jsx
index f80c77bd..d16812fc 100644
--- a/src/renderer/components/database-list-item-metadata.jsx
+++ b/src/renderer/components/database-list-item-metadata.jsx
@@ -1,5 +1,6 @@
import React, { Component, PropTypes } from 'react';
-import TableSubmenu from './table-submenu.jsx';
+import DatabaseItem from './database-item.jsx';
+
const STYLE = {
header: { fontSize: '0.85em', color: '#636363' },
@@ -16,8 +17,9 @@ export default class DbMetadataList extends Component {
triggersByTable: PropTypes.object,
collapsed: PropTypes.bool,
database: PropTypes.object.isRequired,
- onDoubleClickItem: PropTypes.func,
+ onExecuteDefaultQuery: PropTypes.func,
onSelectItem: PropTypes.func,
+ onGetSQLScript: PropTypes.func,
}
constructor(props, context) {
@@ -35,10 +37,6 @@ export default class DbMetadataList extends Component {
this.setState({ collapsed: !this.state.collapsed });
}
- toggleTableCollapse(tableName) {
- this.setState({ [tableName]: !this.state[tableName] });
- }
-
renderHeader() {
const title = this.state.collapsed ? 'Expand' : 'Collapse';
const cssClass = this.state.collapsed ? 'right' : 'down';
@@ -56,7 +54,13 @@ export default class DbMetadataList extends Component {
}
renderItems() {
- const { onDoubleClickItem, onSelectItem, items, database } = this.props;
+ const {
+ onExecuteDefaultQuery,
+ onSelectItem,
+ items,
+ database,
+ onGetSQLScript,
+ } = this.props;
if (!items || this.state.collapsed) {
return null;
@@ -69,79 +73,30 @@ export default class DbMetadataList extends Component {
}
return items.map(item => {
- const isClickable = !!onDoubleClickItem;
const hasChildElements = !!onSelectItem;
- const title = isClickable ? 'Click twice to select default query' : '';
- const onDoubleClick = isClickable
- ? onDoubleClickItem.bind(this, database, item)
- : () => {};
- const onSingleClick = hasChildElements
- ? () => {onSelectItem(database, item); this.toggleTableCollapse(item.name);}
- : () => {};
const cssStyle = {...STYLE.item};
if (this.state.collapsed) {
cssStyle.display = 'none';
}
cssStyle.cursor = hasChildElements ? 'pointer' : 'default';
- const collapseCssClass = this.state[item.name] ? 'down' : 'right';
- const collapseIcon = (
-
- );
- const tableIcon = (
-
- );
- /*
- TODO: Move standard table query to context menu
- */
return (
-
-
- { this.props.title === 'Tables' ? collapseIcon : null }
- { this.props.title === 'Tables' ? tableIcon : null }
- {item.name}
-
- {this.renderSubItems(item.name)}
-
+
);
});
}
- renderSubItems(table) {
- const { columnsByTable, triggersByTable, database } = this.props;
-
- if (!columnsByTable || !columnsByTable[table]) {
- return null;
- }
-
- const displayStyle = {};
- if (!this.state[table]) {
- displayStyle.display = 'none';
- }
-
- return (
-
- );
- }
-
render() {
return (
diff --git a/src/renderer/components/database-list-item.jsx b/src/renderer/components/database-list-item.jsx
index c4c4307e..b1ee861f 100644
--- a/src/renderer/components/database-list-item.jsx
+++ b/src/renderer/components/database-list-item.jsx
@@ -20,9 +20,10 @@ export default class DatabaseListItem extends Component {
functions: PropTypes.array,
procedures: PropTypes.array,
database: PropTypes.object.isRequired,
- onDoubleClickTable: PropTypes.func.isRequired,
+ onExecuteDefaultQuery: PropTypes.func.isRequired,
onSelectTable: PropTypes.func.isRequired,
onSelectDatabase: PropTypes.func.isRequired,
+ onGetSQLScript: PropTypes.func.isRequired,
}
constructor(props, context) {
@@ -73,9 +74,10 @@ export default class DatabaseListItem extends Component {
functions,
procedures,
database,
- onDoubleClickTable,
+ onExecuteDefaultQuery,
onSelectTable,
- onSelectDatabase
+ onSelectDatabase,
+ onGetSQLScript,
} = this.props;
let filteredTables, filteredViews, filteredFunctions, filteredProcedures;
@@ -112,24 +114,28 @@ export default class DatabaseListItem extends Component {
columnsByTable={columnsByTable}
triggersByTable={triggersByTable}
database={database}
- onDoubleClickItem={onDoubleClickTable}
- onSelectItem={onSelectTable} />
+ onExecuteDefaultQuery={onExecuteDefaultQuery}
+ onSelectItem={onSelectTable}
+ onGetSQLScript={onGetSQLScript} />
+ onExecuteDefaultQuery={onExecuteDefaultQuery}
+ onGetSQLScript={onGetSQLScript} />
+ database={database}
+ onGetSQLScript={onGetSQLScript} />
+ database={database}
+ onGetSQLScript={onGetSQLScript} />
);
diff --git a/src/renderer/components/database-list.jsx b/src/renderer/components/database-list.jsx
index 46071bc5..acb95e72 100644
--- a/src/renderer/components/database-list.jsx
+++ b/src/renderer/components/database-list.jsx
@@ -13,8 +13,9 @@ export default class DatabaseList extends Component {
functionsByDatabase: PropTypes.object.isRequired,
proceduresByDatabase: PropTypes.object.isRequired,
onSelectDatabase: PropTypes.func.isRequired,
- onDoubleClickTable: PropTypes.func.isRequired,
+ onExecuteDefaultQuery: PropTypes.func.isRequired,
onSelectTable: PropTypes.func.isRequired,
+ onGetSQLScript: PropTypes.func.isRequired,
}
constructor(props, context) {
@@ -32,9 +33,10 @@ export default class DatabaseList extends Component {
viewsByDatabase,
functionsByDatabase,
proceduresByDatabase,
- onDoubleClickTable,
+ onExecuteDefaultQuery,
onSelectTable,
onSelectDatabase,
+ onGetSQLScript,
} = this.props;
if (isFetching) {
@@ -62,9 +64,10 @@ export default class DatabaseList extends Component {
views={viewsByDatabase[database.name]}
functions={functionsByDatabase[database.name]}
procedures={proceduresByDatabase[database.name]}
- onDoubleClickTable={onDoubleClickTable}
+ onExecuteDefaultQuery={onExecuteDefaultQuery}
onSelectTable={onSelectTable}
- onSelectDatabase={onSelectDatabase} />
+ onSelectDatabase={onSelectDatabase}
+ onGetSQLScript={onGetSQLScript} />
))
}
diff --git a/src/renderer/containers/query-browser.jsx b/src/renderer/containers/query-browser.jsx
index 29ae0674..db457f05 100644
--- a/src/renderer/containers/query-browser.jsx
+++ b/src/renderer/containers/query-browser.jsx
@@ -11,6 +11,7 @@ import { fetchTableColumnsIfNeeded } from '../actions/columns';
import { fetchTableTriggersIfNeeded } from '../actions/triggers';
import { fetchViewsIfNeeded } from '../actions/views';
import { fetchRoutinesIfNeeded } from '../actions/routines';
+import { getSQLScriptIfNeeded } from '../actions/sqlscripts';
import DatabaseFilter from '../components/database-filter.jsx';
import DatabaseList from '../components/database-list.jsx';
import Header from '../components/header.jsx';
@@ -51,6 +52,7 @@ class QueryBrowserContainer extends Component {
views: PropTypes.object.isRequired,
routines: PropTypes.object.isRequired,
queries: PropTypes.object.isRequired,
+ sqlscripts: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
route: PropTypes.object.isRequired,
@@ -110,7 +112,7 @@ class QueryBrowserContainer extends Component {
dispatch(ConnActions.connect(params.id, database.name));
}
- onDoubleClickTable(database, table) {
+ onExecuteDefaultQuery(database, table) {
this.props.dispatch(QueryActions.executeDefaultSelectQueryIfNeeded(database.name, table.name));
}
@@ -119,6 +121,10 @@ class QueryBrowserContainer extends Component {
this.props.dispatch(fetchTableTriggersIfNeeded(database.name, table.name));
}
+ onGetSQLScript(database, item, actionType, objectType) {
+ this.props.dispatch(getSQLScriptIfNeeded(database.name, item.name, actionType, objectType));
+ }
+
onSQLChange (sqlQuery) {
this.props.dispatch(QueryActions.updateQueryIfNeeded(sqlQuery));
}
@@ -308,8 +314,9 @@ class QueryBrowserContainer extends Component {
functionsByDatabase={routines.functionsByDatabase}
proceduresByDatabase={routines.proceduresByDatabase}
onSelectDatabase={::this.onSelectDatabase}
- onDoubleClickTable={::this.onDoubleClickTable}
- onSelectTable={::this.onSelectTable} />
+ onExecuteDefaultQuery={::this.onExecuteDefaultQuery}
+ onSelectTable={::this.onSelectTable}
+ onGetSQLScript={::this.onGetSQLScript} />
@@ -327,7 +334,7 @@ class QueryBrowserContainer extends Component {
function mapStateToProps (state) {
- const { connections, databases, tables, columns, triggers, views, routines, queries, status } = state;
+ const { connections, databases, tables, columns, triggers, views, routines, queries, sqlscripts, status } = state;
return {
connections,
@@ -338,6 +345,7 @@ function mapStateToProps (state) {
views,
routines,
queries,
+ sqlscripts,
status,
};
}
diff --git a/src/renderer/reducers/index.js b/src/renderer/reducers/index.js
index d83b95a1..3209d4dc 100644
--- a/src/renderer/reducers/index.js
+++ b/src/renderer/reducers/index.js
@@ -9,6 +9,7 @@ import views from './views';
import routines from './routines';
import columns from './columns';
import triggers from './triggers';
+import sqlscripts from './sqlscripts';
const rootReducer = combineReducers({
@@ -22,6 +23,7 @@ const rootReducer = combineReducers({
routines,
columns,
triggers,
+ sqlscripts,
});
diff --git a/src/renderer/reducers/routines.js b/src/renderer/reducers/routines.js
index 224fc236..3f26ee51 100644
--- a/src/renderer/reducers/routines.js
+++ b/src/renderer/reducers/routines.js
@@ -28,12 +28,14 @@ export default function (state = INITIAL_STATE, action) {
functionsByDatabase: {
...state.functionsByDatabase,
[action.database]: action.routines.filter(_isFunction).map(routine => ({
- name: routine.routineName })),
+ name: routine.routineName,
+ routineDefinition: routine.routineDefinition })),
},
proceduresByDatabase: {
...state.proceduresByDatabase,
[action.database]: action.routines.filter(_isProcedure).map(routine => ({
- name: routine.routineName })),
+ name: routine.routineName,
+ routineDefinition: routine.routineDefinition })),
},
error: null,
};
diff --git a/src/renderer/reducers/sqlscripts.js b/src/renderer/reducers/sqlscripts.js
new file mode 100644
index 00000000..bfb1f553
--- /dev/null
+++ b/src/renderer/reducers/sqlscripts.js
@@ -0,0 +1,60 @@
+import * as connTypes from '../actions/connections';
+import * as types from '../actions/sqlscripts';
+
+
+const INITIAL_STATE = {
+ isFetching: false,
+ didInvalidate: false,
+ scriptsByObject: {},
+};
+
+
+export default function (state = INITIAL_STATE, action) {
+ switch (action.type) {
+ case connTypes.CONNECTION_REQUEST: {
+ return action.isServerConnection
+ ? { ...INITIAL_STATE, didInvalidate: true }
+ : state;
+ }
+ case types.GET_SCRIPT_REQUEST: {
+ return {
+ ...state,
+ scriptType: action.scriptType,
+ isFetching: true,
+ didInvalidate: false,
+ error: null,
+ };
+ }
+ case types.GET_SCRIPT_SUCCESS: {
+ const scriptsByItem = !state.scriptsByObject[action.database]
+ ? null
+ : state.scriptsByObject[action.database][action.item];
+ return {
+ ...state,
+ isFetching: false,
+ didInvalidate: false,
+ error: null,
+ scriptsByObject: {
+ ...state.scriptsByObject,
+ [action.database]: {
+ ...state.scriptsByObject[action.database],
+ [action.item]: {
+ ...scriptsByItem,
+ objectType: action.objectType,
+ [action.actionType]: action.script,
+ },
+ },
+ },
+ };
+ }
+ case types.GET_SCRIPT_FAILURE: {
+ return {
+ ...state,
+ isFetching: false,
+ didInvalidate: true,
+ error: action.error,
+ };
+ }
+ default : return state;
+ }
+}