diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js index 7aad6ebb..63b3c872 100644 --- a/packages/api/src/controllers/connections.js +++ b/packages/api/src/controllers/connections.js @@ -53,6 +53,8 @@ function getPortalCollections() { databaseUrl: process.env[`URL_${id}`], useDatabaseUrl: !!process.env[`URL_${id}`], databaseFile: process.env[`FILE_${id}`], + socketPath: process.env[`SOCKET_PATH_${id}`], + authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined), defaultDatabase: process.env[`DATABASE_${id}`] || (process.env[`FILE_${id}`] ? getDatabaseFileLabel(process.env[`FILE_${id}`]) : null), diff --git a/packages/tools/src/SqlDumper.ts b/packages/tools/src/SqlDumper.ts index 3a6de60c..c8bb5c6a 100644 --- a/packages/tools/src/SqlDumper.ts +++ b/packages/tools/src/SqlDumper.ts @@ -32,6 +32,12 @@ export class SqlDumper implements AlterProcessor { dialect: SqlDialect; indentLevel = 0; + static keywordsCase = 'upperCase'; + static convertKeywordCase(keyword: any): string { + if (this.keywordsCase == 'lowerCase') return keyword?.toString()?.toLowerCase(); + return keyword?.toString()?.toUpperCase(); + } + constructor(driver: EngineDriver) { this.driver = driver; this.dialect = driver.dialect; @@ -60,10 +66,10 @@ export class SqlDumper implements AlterProcessor { this.putRaw("'"); } putByteArrayValue(value) { - this.putRaw('NULL'); + this.put('^null'); } putValue(value) { - if (value === null) this.putRaw('NULL'); + if (value === null) this.put('^null'); else if (value === true) this.putRaw('1'); else if (value === false) this.putRaw('0'); else if (_isString(value)) this.putStringValue(value); @@ -71,7 +77,7 @@ export class SqlDumper implements AlterProcessor { else if (_isDate(value)) this.putStringValue(new Date(value).toISOString()); else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data); else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value)); - else this.putRaw('NULL'); + else this.put('^null'); } putCmd(format, ...args) { this.put(format, ...args); @@ -92,7 +98,7 @@ export class SqlDumper implements AlterProcessor { case 'k': { if (value) { - this.putRaw(value.toUpperCase()); + this.putRaw(SqlDumper.convertKeywordCase(value)); } } break; @@ -128,7 +134,7 @@ export class SqlDumper implements AlterProcessor { switch (c) { case '^': while (i < length && format[i].match(/[a-z0-9_]/i)) { - this.putRaw(format[i].toUpperCase()); + this.putRaw(SqlDumper.convertKeywordCase(format[i])); i++; } break; diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 67c6b22c..6938737e 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -19,6 +19,7 @@ import AppTitleProvider from './utility/AppTitleProvider.svelte'; import getElectron from './utility/getElectron'; import AppStartInfo from './widgets/AppStartInfo.svelte'; + import SettingsListener from './utility/SettingsListener.svelte'; let loadedApi = false; let loadedPlugins = false; @@ -79,6 +80,7 @@ {#if loadedPlugins} + {:else} { + openNewTab({ + title: 'Query #', + icon: 'img query-design', + tabComponent: 'QueryDesignTab', + props: { + conid: connection._id, + database: name, + }, + }); + }; + + const handleNewPerspective = () => { + openNewTab({ + title: 'Perspective #', + icon: 'img perspective', + tabComponent: 'PerspectiveTab', + props: { + conid: connection._id, + database: name, + }, + }); + }; + async function handleConfirmSql(sql) { saveScriptToDatabase({ conid: connection._id, database: name }, sql, false); } @@ -244,16 +268,21 @@ { onClick: handleNewQuery, text: 'New query', isNewQuery: true }, driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: 'New table' }, driver?.databaseEngineTypes?.includes('document') && { onClick: handleNewCollection, text: 'New collection' }, - isSqlOrDoc && - !connection.isReadOnly && - !connection.singleDatabase && { onClick: handleDropDatabase, text: 'Drop database' }, + driver?.databaseEngineTypes?.includes('sql') && { onClick: handleQueryDesigner, text: 'Design query' }, + driver?.databaseEngineTypes?.includes('sql') && { + onClick: handleNewPerspective, + text: 'Design perspective query', + }, { divider: true }, isSqlOrDoc && !connection.isReadOnly && { onClick: handleImport, text: 'Import wizard' }, isSqlOrDoc && { onClick: handleExport, text: 'Export wizard' }, driver?.databaseEngineTypes?.includes('sql') && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' }, driver?.supportsDatabaseDump && { onClick: handleSqlDump, text: 'Backup/export SQL dump' }, + isSqlOrDoc && + !connection.isReadOnly && + !connection.singleDatabase && { onClick: handleDropDatabase, text: 'Drop database' }, { divider: true }, - isSqlOrDoc && { onClick: handleShowDiagram, text: 'Show diagram' }, + driver?.databaseEngineTypes?.includes('sql') && { onClick: handleShowDiagram, text: 'Show diagram' }, isSqlOrDoc && { onClick: handleSqlGenerator, text: 'SQL Generator' }, isSqlOrDoc && { onClick: handleOpenJsonModel, text: 'Open model as JSON' }, isSqlOrDoc && { onClick: handleExportModel, text: 'Export DB model - experimental' }, diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.svelte b/packages/web/src/appobj/DatabaseObjectAppObject.svelte index 47dbbad1..2794baf5 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.svelte +++ b/packages/web/src/appobj/DatabaseObjectAppObject.svelte @@ -52,7 +52,12 @@ icon: 'img table-structure', }, { - label: 'Open perspective', + label: 'Design query', + isQueryDesigner: true, + requiresWriteAccess: true, + }, + { + label: 'Design perspective query', tab: 'PerspectiveTab', forceNewTab: true, icon: 'img perspective', @@ -80,11 +85,6 @@ isDuplicateTable: true, requiresWriteAccess: true, }, - { - label: 'Query designer', - isQueryDesigner: true, - requiresWriteAccess: true, - }, { label: 'Show diagram', isDiagram: true, @@ -155,7 +155,11 @@ icon: 'img view-structure', }, { - label: 'Open perspective', + label: 'Design query', + isQueryDesigner: true, + }, + { + label: 'Design perspective query', tab: 'PerspectiveTab', forceNewTab: true, icon: 'img perspective', @@ -164,10 +168,6 @@ label: 'Drop view', isDrop: true, }, - { - label: 'Query designer', - isQueryDesigner: true, - }, { divider: true, }, diff --git a/packages/web/src/commands/stdCommands.ts b/packages/web/src/commands/stdCommands.ts index 951a6759..d5d7834f 100644 --- a/packages/web/src/commands/stdCommands.ts +++ b/packages/web/src/commands/stdCommands.ts @@ -127,6 +127,9 @@ registerCommand({ name: 'Query design', menuName: 'New query design', onClick: () => newQueryDesign(), + testEnabled: () => + getCurrentDatabase() && + findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'), }); registerCommand({ @@ -144,6 +147,9 @@ registerCommand({ icon: 'img diagram', name: 'ER Diagram', menuName: 'New ER diagram', + testEnabled: () => + getCurrentDatabase() && + findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'), onClick: () => newDiagram(), }); diff --git a/packages/web/src/elements/Pager.svelte b/packages/web/src/elements/Pager.svelte index 0f3af138..d2db8556 100644 --- a/packages/web/src/elements/Pager.svelte +++ b/packages/web/src/elements/Pager.svelte @@ -22,7 +22,7 @@
{ - skip -= limit; + skip = parseInt(skip) - parseInt(limit); if (skip < 0) skip = 0; dispatch('load'); }} @@ -35,7 +35,7 @@ dispatch('load')} on:keydown={handleKeyDown} /> { - skip += limit; + skip = parseInt(skip) + parseInt(limit); dispatch('load'); }} > diff --git a/packages/web/src/modals/InsertJoinModal.svelte b/packages/web/src/modals/InsertJoinModal.svelte index ed19eb7b..e1cd0177 100644 --- a/packages/web/src/modals/InsertJoinModal.svelte +++ b/packages/web/src/modals/InsertJoinModal.svelte @@ -1,4 +1,5 @@ - + {#if value !== undefined} {#if displayType == 'json'} @@ -23,6 +23,8 @@ {:else} (no image) {/if} + {:else if _.isArray(value) || _.isPlainObject(value)} + {:else} {/if} diff --git a/packages/web/src/perspectives/PerspectiveTable.svelte b/packages/web/src/perspectives/PerspectiveTable.svelte index 4f7990c4..32a9b97d 100644 --- a/packages/web/src/perspectives/PerspectiveTable.svelte +++ b/packages/web/src/perspectives/PerspectiveTable.svelte @@ -57,6 +57,7 @@ let errorMessage; let rowCount; let isLoading = false; + let isLoadQueued = false; const lastVisibleRowIndexRef = createRef(0); const disableLoadNextRef = createRef(false); @@ -121,6 +122,12 @@ } async function loadData(node: PerspectiveTreeNode, counts) { + if (isLoading) { + isLoadQueued = true; + return; + } else { + isLoadQueued = false; + } // console.log('LOADING', node); if (!node) return; const rows = []; @@ -147,6 +154,10 @@ // loadProps.push(child.getNodeLoadProps()); // } // } + + if (isLoadQueued) { + loadData(root, $loadedCounts); + } } export function openJson() { diff --git a/packages/web/src/query/codeCompletion.ts b/packages/web/src/query/codeCompletion.ts index e59503b5..ddb66209 100644 --- a/packages/web/src/query/codeCompletion.ts +++ b/packages/web/src/query/codeCompletion.ts @@ -2,6 +2,7 @@ import _ from 'lodash'; import { addCompleter, setCompleters } from 'ace-builds/src-noconflict/ext-language_tools'; import { getDatabaseInfo } from '../utility/metadataLoaders'; import analyseQuerySources from './analyseQuerySources'; +import { getStringSettingsValue } from '../settings/settingsTools'; const COMMON_KEYWORDS = [ 'select', @@ -78,13 +79,21 @@ export function mountCodeCompletion({ conid, database, editor, getText }) { const line = session.getLine(cursor.row).slice(0, cursor.column); const dbinfo = await getDatabaseInfo({ conid, database }); - let list = COMMON_KEYWORDS.map(word => ({ - name: word, - value: word, - caption: word, - meta: 'keyword', - score: 800, - })); + const convertUpper = getStringSettingsValue('sqlEditor.sqlCommandsCase', 'upperCase') == 'upperCase'; + + let list = COMMON_KEYWORDS.map(word => { + if (convertUpper) { + word = word.toUpperCase(); + } + + return { + name: word, + value: word, + caption: word, + meta: 'keyword', + score: 800, + }; + }); if (dbinfo) { const colMatch = line.match(/([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]*)?$/); diff --git a/packages/web/src/settings/SettingsModal.svelte b/packages/web/src/settings/SettingsModal.svelte index 99e0ca35..ebf2d9a1 100644 --- a/packages/web/src/settings/SettingsModal.svelte +++ b/packages/web/src/settings/SettingsModal.svelte @@ -111,6 +111,19 @@ ORDER BY defaultValue="30" disabled={values['connection.autoRefresh'] === false} /> + +
SQL editor
+ +
Application theme
diff --git a/packages/web/src/settings/settingsTools.ts b/packages/web/src/settings/settingsTools.ts index 7c27ad20..dbf739ee 100644 --- a/packages/web/src/settings/settingsTools.ts +++ b/packages/web/src/settings/settingsTools.ts @@ -21,3 +21,10 @@ export function getBoolSettingsValue(name, defaultValue) { if (res == null) return defaultValue; return !!res; } + +export function getStringSettingsValue(name, defaultValue) { + const settings = getCurrentSettings(); + const res = settings[name]; + if (res == null) return defaultValue; + return res; +} diff --git a/packages/web/src/utility/SettingsListener.svelte b/packages/web/src/utility/SettingsListener.svelte new file mode 100644 index 00000000..7dedd6a8 --- /dev/null +++ b/packages/web/src/utility/SettingsListener.svelte @@ -0,0 +1,8 @@ + diff --git a/plugins/dbgate-plugin-mongo/src/backend/driver.js b/plugins/dbgate-plugin-mongo/src/backend/driver.js index a120145d..432593d4 100644 --- a/plugins/dbgate-plugin-mongo/src/backend/driver.js +++ b/plugins/dbgate-plugin-mongo/src/backend/driver.js @@ -9,7 +9,9 @@ const AbstractCursor = require('mongodb').AbstractCursor; const createBulkInsertStream = require('./createBulkInsertStream'); function transformMongoData(row) { - return _.mapValues(row, (v) => (v && v.constructor == ObjectId ? { $oid: v.toString() } : v)); + return _.cloneDeepWith(row, (x) => { + if (x && x.constructor == ObjectId) return { $oid: x.toString() }; + }); } async function readCursor(cursor, options) {