Merge branch 'master' into tableeditor2

This commit is contained in:
Jan Prochazka
2021-07-18 07:59:31 +02:00
19 changed files with 86 additions and 56 deletions

View File

@@ -1,5 +1,10 @@
# ChangeLog # ChangeLog
### 4.2.6
- Fixed MongoDB import
- Configurable thousands separator #136
- Using case insensitive text search in postgres
### 4.2.5 ### 4.2.5
- FIXED: Fixed crash when using large model on some installations - FIXED: Fixed crash when using large model on some installations
- FIXED: Postgre SQL CREATE function - FIXED: Postgre SQL CREATE function

View File

@@ -64,7 +64,7 @@ There are many database managers now, so why DbGate?
* Backend - NodeJs, ExpressJs, socket.io, database connection drivers * Backend - NodeJs, ExpressJs, socket.io, database connection drivers
* JavaScript + TypeScript * JavaScript + TypeScript
* App - electron * App - electron
* Platform independed - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac * Platform independent - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
## Plugins ## Plugins
Plugins are standard NPM packages published on [npmjs.com](https://www.npmjs.com). Plugins are standard NPM packages published on [npmjs.com](https://www.npmjs.com).

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "4.2.6-beta.2", "version": "4.2.7",
"name": "dbgate-all", "name": "dbgate-all",
"workspaces": [ "workspaces": [
"packages/*", "packages/*",

View File

@@ -75,6 +75,7 @@ export abstract class GridDisplay {
} }
changeSetKeyFields: string[] = null; changeSetKeyFields: string[] = null;
sortable = false; sortable = false;
groupable = false;
filterable = false; filterable = false;
editable = false; editable = false;
isLoadedCorrectly = true; isLoadedCorrectly = true;

View File

@@ -35,6 +35,7 @@ export class TableGridDisplay extends GridDisplay {
this.columns = this.getDisplayColumns(this.table, []); this.columns = this.getDisplayColumns(this.table, []);
this.filterable = true; this.filterable = true;
this.sortable = true; this.sortable = true;
this.groupable = true;
this.editable = true; this.editable = true;
this.supportsReload = true; this.supportsReload = true;
this.baseTable = this.table; this.baseTable = this.table;

View File

@@ -17,6 +17,7 @@ export class ViewGridDisplay extends GridDisplay {
this.columns = this.getDisplayColumns(view); this.columns = this.getDisplayColumns(view);
this.filterable = true; this.filterable = true;
this.sortable = true; this.sortable = true;
this.groupable = true;
this.editable = false; this.editable = false;
this.supportsReload = true; this.supportsReload = true;
} }

View File

@@ -38,6 +38,14 @@
} }
} }
function handleMouseUp(e) {
if (e.button == 1) {
dispatch('middleclick');
e.preventDefault();
e.stopPropagation();
}
}
function setChecked(value) { function setChecked(value) {
if (!value && isChecked) { if (!value && isChecked) {
checkedObjectsStore.update(x => x.filter(y => module.extractKey(data) != module.extractKey(y))); checkedObjectsStore.update(x => x.filter(y => module.extractKey(data) != module.extractKey(y)));
@@ -53,6 +61,7 @@
class:isBold class:isBold
draggable={true} draggable={true}
on:click={handleClick} on:click={handleClick}
on:mouseup={handleMouseUp}
use:contextMenu={disableContextMenu ? null : menu} use:contextMenu={disableContextMenu ? null : menu}
on:dragstart={e => { on:dragstart={e => {
e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data)); e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data));

View File

@@ -123,7 +123,7 @@
text: 'Connect', text: 'Connect',
onClick: handleConnect, onClick: handleConnect,
}, },
{ onClick: handleNewQuery, text: 'New query' }, { onClick: handleNewQuery, text: 'New query', isNewQuery: true },
$openedConnections.includes(data._id) && $openedConnections.includes(data._id) &&
data.status && { data.status && {
text: 'Refresh', text: 'Refresh',
@@ -190,4 +190,9 @@
on:click={handleConnect} on:click={handleConnect}
on:click on:click
on:expand on:expand
on:middleclick={() => {
_.flattenDeep(getContextMenu())
.find(x => x.isNewQuery)
.onClick();
}}
/> />

View File

@@ -54,7 +54,7 @@
}; };
return [ return [
{ onClick: handleNewQuery, text: 'New query' }, { onClick: handleNewQuery, text: 'New query', isNewQuery: true },
{ onClick: handleImport, text: 'Import' }, { onClick: handleImport, text: 'Import' },
{ onClick: handleExport, text: 'Export' }, { onClick: handleExport, text: 'Export' },
{ onClick: handleSqlGenerator, text: 'SQL Generator' }, { onClick: handleSqlGenerator, text: 'SQL Generator' },
@@ -68,7 +68,7 @@
<script lang="ts"> <script lang="ts">
import getConnectionLabel from '../utility/getConnectionLabel'; import getConnectionLabel from '../utility/getConnectionLabel';
import _ from 'lodash'; import _, { find } from 'lodash';
import ImportExportModal from '../modals/ImportExportModal.svelte'; import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte'; import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
@@ -93,5 +93,10 @@
isBold={_.get($currentDatabase, 'connection._id') == _.get(data.connection, '_id') && isBold={_.get($currentDatabase, 'connection._id') == _.get(data.connection, '_id') &&
_.get($currentDatabase, 'name') == data.name} _.get($currentDatabase, 'name') == data.name}
on:click={() => ($currentDatabase = data)} on:click={() => ($currentDatabase = data)}
on:middleclick={() => {
createMenu()
.find(x => x.isNewQuery)
.onClick();
}}
menu={createMenu} menu={createMenu}
/> />

View File

@@ -328,7 +328,6 @@
{ forceNewTab } { forceNewTab }
); );
} }
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -351,7 +350,7 @@
export let data; export let data;
function handleClick() { function handleClick(forceNewTab = false) {
const { schemaName, pureName, conid, database, objectTypeField } = data; const { schemaName, pureName, conid, database, objectTypeField } = data;
openDatabaseObjectDetail( openDatabaseObjectDetail(
@@ -364,7 +363,7 @@
database, database,
objectTypeField, objectTypeField,
}, },
false, forceNewTab,
null null
); );
@@ -508,7 +507,6 @@
}; };
}); });
} }
</script> </script>
<AppObjectCore <AppObjectCore
@@ -518,6 +516,7 @@
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName} title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
icon={icons[data.objectTypeField]} icon={icons[data.objectTypeField]}
menu={createMenu} menu={createMenu}
on:click={handleClick} on:click={() => handleClick()}
on:middleclick={() => handleClick(true)}
on:expand on:expand
/> />

View File

@@ -6,6 +6,7 @@
import ColumnLabel from '../elements/ColumnLabel.svelte'; import ColumnLabel from '../elements/ColumnLabel.svelte';
import { isTypeDateTime } from 'dbgate-tools'; import { isTypeDateTime } from 'dbgate-tools';
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte'; import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
import { copyTextToClipboard } from '../utility/clipboard';
export let column; export let column;
export let conid = undefined; export let conid = undefined;
@@ -27,19 +28,20 @@
function getMenu() { function getMenu() {
return [ return [
{ onClick: () => setSort('ASC'), text: 'Sort ascending' }, setSort && { onClick: () => setSort('ASC'), text: 'Sort ascending' },
{ onClick: () => setSort('DESC'), text: 'Sort descending' }, setSort && { onClick: () => setSort('DESC'), text: 'Sort descending' },
{ onClick: () => copyTextToClipboard(column.columnName), text: 'Copy column name' },
column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }], column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }],
{ divider: true }, setGrouping && { divider: true },
{ onClick: () => setGrouping('GROUP'), text: 'Group by' }, setGrouping && { onClick: () => setGrouping('GROUP'), text: 'Group by' },
{ onClick: () => setGrouping('MAX'), text: 'MAX' }, setGrouping && { onClick: () => setGrouping('MAX'), text: 'MAX' },
{ onClick: () => setGrouping('MIN'), text: 'MIN' }, setGrouping && { onClick: () => setGrouping('MIN'), text: 'MIN' },
{ onClick: () => setGrouping('SUM'), text: 'SUM' }, setGrouping && { onClick: () => setGrouping('SUM'), text: 'SUM' },
{ onClick: () => setGrouping('AVG'), text: 'AVG' }, setGrouping && { onClick: () => setGrouping('AVG'), text: 'AVG' },
{ onClick: () => setGrouping('COUNT'), text: 'COUNT' }, setGrouping && { onClick: () => setGrouping('COUNT'), text: 'COUNT' },
{ onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' }, setGrouping && { onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
isTypeDateTime(column.dataType) && [ isTypeDateTime(column.dataType) && [
{ divider: true }, { divider: true },

View File

@@ -12,7 +12,8 @@
return value; return value;
} }
const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/; // const dateTimeRegex = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\d)?Z?$/;
const dateTimeRegex = /^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|()|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
function formatNumber(value) { function formatNumber(value) {
if (value >= 10000 || value <= -10000) { if (value >= 10000 || value <= -10000) {
@@ -25,12 +26,15 @@
return value.toString(); return value.toString();
} }
function formatDateTime(testedString) {
const m = testedString.match(dateTimeRegex);
return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}:${m[6]}`;
}
</script> </script>
<script lang="ts"> <script lang="ts">
import moment from 'moment';
import _ from 'lodash'; import _ from 'lodash';
import { isTypeLogical } from 'dbgate-tools';
import ShowFormButton from '../formview/ShowFormButton.svelte'; import ShowFormButton from '../formview/ShowFormButton.svelte';
import { getBoolSettingsValue } from '../settings/settingsTools'; import { getBoolSettingsValue } from '../settings/settingsTools';
@@ -79,28 +83,18 @@
{:else if value === undefined} {:else if value === undefined}
<span class="null">(No field)</span> <span class="null">(No field)</span>
{:else if _.isDate(value)} {:else if _.isDate(value)}
{moment(value).format('YYYY-MM-DD HH:mm:ss')} {value.toString()}
{:else if value === true} {:else if value === true}
{#if isDynamicStructure} <span class="value">true</span>
<span class="value">true</span>
{:else}
1
{/if}
{:else if value === false} {:else if value === false}
{#if isDynamicStructure} <span class="value">false</span>
<span class="value">false</span>
{:else}
0
{/if}
{:else if _.isNumber(value)} {:else if _.isNumber(value)}
{#if isDynamicStructure} <span class="value">{formatNumber(value)}</span>
<span class="value">{formatNumber(value)}</span>
{:else}
{formatNumber(value)}
{/if}
{:else if _.isString(value)} {:else if _.isString(value)}
{#if dateTimeRegex.test(value)} {#if dateTimeRegex.test(value)}
{moment(value).format('YYYY-MM-DD HH:mm:ss')} <span class="value">
{formatDateTime(value)}
</span>
{:else} {:else}
{highlightSpecialCharacters(value)} {highlightSpecialCharacters(value)}
{/if} {/if}

View File

@@ -54,7 +54,7 @@
name: 'Set NULL', name: 'Set NULL',
keyText: 'Ctrl+0', keyText: 'Ctrl+0',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable, testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
onClick: () => getCurrentDataGrid().setNull(), onClick: () => getCurrentDataGrid().setFixedValue(null),
}); });
registerCommand({ registerCommand({
@@ -310,10 +310,10 @@
} }
} }
export function setNull() { export function setFixedValue(value) {
grider.beginUpdate(); grider.beginUpdate();
selectedCells.filter(isRegularCell).forEach(cell => { selectedCells.filter(isRegularCell).forEach(cell => {
setCellValue(cell, null); setCellValue(cell, value);
}); });
grider.endUpdate(); grider.endUpdate();
} }
@@ -733,6 +733,7 @@
!event.altKey && !event.altKey &&
((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) || ((event.keyCode >= keycodes.a && event.keyCode <= keycodes.z) ||
(event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) || (event.keyCode >= keycodes.n0 && event.keyCode <= keycodes.n9) ||
(event.keyCode >= keycodes.numPad0 && event.keyCode <= keycodes.numPad9) ||
event.keyCode == keycodes.dash) event.keyCode == keycodes.dash)
) { ) {
// @ts-ignore // @ts-ignore
@@ -1050,7 +1051,7 @@
// @ts-ignore // @ts-ignore
display.resizeColumn(col.uniqueName, col.width, e.detail); display.resizeColumn(col.uniqueName, col.width, e.detail);
}} }}
setGrouping={display.sortable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null} setGrouping={display.groupable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
grouping={display.getGrouping(col.uniqueName)} grouping={display.getGrouping(col.uniqueName)}
/> />
</td> </td>

View File

@@ -49,7 +49,7 @@
name: 'Set NULL', name: 'Set NULL',
keyText: 'Ctrl+0', keyText: 'Ctrl+0',
testEnabled: () => getCurrentDataForm() != null, testEnabled: () => getCurrentDataForm() != null,
onClick: () => getCurrentDataForm().setNull(), onClick: () => getCurrentDataForm().setFixedValue(null),
}); });
registerCommand({ registerCommand({
@@ -247,9 +247,9 @@
// if (onSave) onSave(); // if (onSave) onSave();
// } // }
export function setNull() { export function setFixedValue(value) {
if (isDataCell(currentCell)) { if (isDataCell(currentCell)) {
setCellValue(currentCell, null); setCellValue(currentCell, value);
} }
} }

View File

@@ -49,7 +49,7 @@
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database; if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
if (tab.props && tab.props.conid) { if (tab.props && tab.props.conid) {
const connection = connectionList?.find(x => x._id == tab.props.conid); const connection = connectionList?.find(x => x._id == tab.props.conid);
if (connection) return getConnectionLabel(connection.displayName, { allowExplicitDatabase: false }); if (connection) return getConnectionLabel(connection, { allowExplicitDatabase: false });
return '???'; return '???';
} }
if (tab.props && tab.props.archiveFolder) return tab.props.archiveFolder; if (tab.props && tab.props.archiveFolder) return tab.props.archiveFolder;

View File

@@ -1,3 +1,4 @@
const _isString = require('lodash/isString');
const { driverBase } = global.DBGATE_TOOLS; const { driverBase } = global.DBGATE_TOOLS;
const Dumper = require('./Dumper'); const Dumper = require('./Dumper');
const { noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options'); const { noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options');
@@ -5,7 +6,7 @@ const { noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options');
const mongoIdRegex = /^[0-9a-f]{24}$/; const mongoIdRegex = /^[0-9a-f]{24}$/;
function getConditionPreview(condition) { function getConditionPreview(condition) {
if (condition && _.isString(condition._id) && condition._id.match(mongoIdRegex)) { if (condition && _isString(condition._id) && condition._id.match(mongoIdRegex)) {
return `{ _id: ObjectId('${condition._id}') }`; return `{ _id: ObjectId('${condition._id}') }`;
} }
return JSON.stringify(condition); return JSON.stringify(condition);

View File

@@ -88,7 +88,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const tables = tablesRows.rows.map(row => ({ const tables = tablesRows.rows.map(row => ({
...row, ...row,
contentHash: row.modifyDate.toISOString(), contentHash: row.modifyDate && row.modifyDate.toISOString(),
columns: columnsRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo), columns: columnsRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows), primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows),
foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows), foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows),
@@ -96,7 +96,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
const views = viewsRows.rows.map(row => ({ const views = viewsRows.rows.map(row => ({
...row, ...row,
contentHash: row.modifyDate.toISOString(), contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row), createSql: getCreateSql(row),
columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo), columns: viewColumnRows.rows.filter(col => col.objectId == row.objectId).map(getColumnInfo),
})); }));
@@ -105,7 +105,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => x.sqlObjectType.trim() == 'P') .filter(x => x.sqlObjectType.trim() == 'P')
.map(row => ({ .map(row => ({
...row, ...row,
contentHash: row.modifyDate.toISOString(), contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row), createSql: getCreateSql(row),
})); }));
@@ -113,7 +113,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
.filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim())) .filter(x => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim()))
.map(row => ({ .map(row => ({
...row, ...row,
contentHash: row.modifyDate.toISOString(), contentHash: row.modifyDate && row.modifyDate.toISOString(),
createSql: getCreateSql(row), createSql: getCreateSql(row),
})); }));
@@ -137,7 +137,7 @@ class MsSqlAnalyser extends DatabaseAnalyser {
res[field].push({ res[field].push({
objectId, objectId,
contentHash: modifyDate.toISOString(), contentHash: modifyDate && modifyDate.toISOString(),
schemaName, schemaName,
pureName, pureName,
}); });

View File

@@ -128,14 +128,14 @@ class Analyser extends DatabaseAnalyser {
.map(x => ({ .map(x => ({
...x, ...x,
objectId: x.pureName, objectId: x.pureName,
contentHash: x.modifyDate.toISOString(), contentHash: x.modifyDate && x.modifyDate.toISOString(),
})), })),
views: tableModificationsQueryData.rows views: tableModificationsQueryData.rows
.filter(x => x.objectType == 'VIEW') .filter(x => x.objectType == 'VIEW')
.map(x => ({ .map(x => ({
...x, ...x,
objectId: x.pureName, objectId: x.pureName,
contentHash: x.modifyDate.toISOString(), contentHash: x.modifyDate && x.modifyDate.toISOString(),
})), })),
procedures: procedureModificationsQueryData.rows.map(x => ({ procedures: procedureModificationsQueryData.rows.map(x => ({
contentHash: x.Modified, contentHash: x.Modified,

View File

@@ -86,6 +86,12 @@ class Dumper extends SqlDumper {
} }
} }
} }
putValue(value) {
if (value === true) this.putRaw('true');
else if (value === false) this.putRaw('false');
else super.putValue(value);
}
} }
module.exports = Dumper; module.exports = Dumper;