Merge branch 'master' into develop

This commit is contained in:
Jan Prochazka
2021-09-30 08:28:25 +02:00
56 changed files with 752 additions and 225 deletions

View File

@@ -19,7 +19,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 10.x

View File

@@ -23,7 +23,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 10.x

View File

@@ -21,7 +21,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 10.x

View File

@@ -27,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 10.x

View File

@@ -27,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Use Node.js 10.x

View File

@@ -15,7 +15,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 1
- name: yarn install

View File

@@ -1,5 +1,12 @@
# ChangeLog
### 4.3.1
- FIXED: #173 Using key phrase for SSH key file connection
- ADDED: #172 Abiloity to quick search within database names
- ADDED: Database search added to command palette (Ctrl+P)
- FIXED: #171 fixed PostgreSQL analyser for older versions than 9.3 (matviews don't exist)
- ADDED: DELETE cascade option - ability to delete all referenced rows, when deleting rows
### 4.3.0
- ADDED: Table structure editor
- ADDED: Index support

View File

@@ -50,8 +50,10 @@ function buildMenu() {
commandItem('file.open'),
commandItem('group.save'),
commandItem('group.saveAs'),
commandItem('database.search'),
{ type: 'separator' },
{ role: 'close' },
commandItem('tabs.closeTab'),
commandItem('file.exit'),
],
},
{

View File

@@ -1,6 +1,6 @@
{
"private": true,
"version": "4.3.0",
"version": "4.3.1",
"name": "dbgate-all",
"workspaces": [
"packages/*",

View File

@@ -66,7 +66,7 @@
"env-cmd": "^10.1.0",
"node-loader": "^1.0.2",
"nodemon": "^2.0.2",
"typescript": "^3.7.4",
"typescript": "^4.4.3",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
},

View File

@@ -14,6 +14,7 @@ let analysedStructure = null;
let lastPing = null;
let lastStatus = null;
let analysedTime = 0;
let serverVersion;
async function checkedAsyncCall(promise) {
try {
@@ -36,7 +37,7 @@ async function handleFullRefresh() {
loadingModel = true;
const driver = requireEngineDriver(storedConnection);
setStatusName('loadStructure');
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection, serverVersion));
analysedTime = new Date().getTime();
process.send({ msgtype: 'structure', structure: analysedStructure });
process.send({ msgtype: 'structureTime', analysedTime });
@@ -48,7 +49,7 @@ async function handleIncrementalRefresh(forceSend) {
loadingModel = true;
const driver = requireEngineDriver(storedConnection);
setStatusName('checkStructure');
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure, serverVersion));
analysedTime = new Date().getTime();
if (newStructure != null) {
analysedStructure = newStructure;
@@ -84,6 +85,7 @@ async function readVersion() {
const driver = requireEngineDriver(storedConnection);
const version = await driver.getVersion(systemConnection);
process.send({ msgtype: 'version', version });
serverVersion = version;
}
async function handleConnect({ connection, structure, globalSettings }) {
@@ -93,7 +95,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
if (!structure) setStatusName('pending');
const driver = requireEngineDriver(storedConnection);
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
readVersion();
await checkedAsyncCall(readVersion());
if (structure) {
analysedStructure = structure;
handleIncrementalRefresh(true);

View File

@@ -70,14 +70,14 @@ function decryptPasswordField(connection, field) {
function encryptConnection(connection) {
connection = encryptPasswordField(connection, 'password');
connection = encryptPasswordField(connection, 'sshPassword');
connection = encryptPasswordField(connection, 'sshKeyFilePassword');
connection = encryptPasswordField(connection, 'sshKeyfilePassword');
return connection;
}
function decryptConnection(connection) {
connection = decryptPasswordField(connection, 'password');
connection = decryptPasswordField(connection, 'sshPassword');
connection = decryptPasswordField(connection, 'sshKeyFilePassword');
connection = decryptPasswordField(connection, 'sshKeyfilePassword');
return connection;
}

View File

@@ -37,7 +37,7 @@ const platformInfo = {
environment: process.env.NODE_ENV,
platform,
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
defaultKeyFile: path.join(os.homedir(), '.ssh/id_rsa'),
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
};
module.exports = platformInfo;

View File

@@ -16,9 +16,9 @@ const CONNECTION_FIELDS = [
'sshLogin',
'sshPassword',
'sshMode',
'sshKeyFile',
'sshKeyfile',
'sshBastionHost',
'sshKeyFilePassword',
'sshKeyfilePassword',
];
const TUNNEL_FIELDS = [...CONNECTION_FIELDS, 'server', 'port'];
@@ -31,7 +31,7 @@ async function getSshConnection(connection) {
endPort: connection.sshPort || 22,
bastionHost: connection.sshBastionHost || '',
agentForward: connection.sshMode == 'agent',
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyFilePassword : undefined,
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
username: connection.sshLogin,
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,

View File

@@ -17,6 +17,6 @@
"devDependencies": {
"dbgate-types": "^4.1.1",
"@types/node": "^13.7.0",
"typescript": "^3.7.5"
"typescript": "^4.4.3"
}
}

View File

@@ -118,7 +118,11 @@ export function setChangeSetValue(
};
}
export function setChangeSetRowData(changeSet: ChangeSet, definition: ChangeSetRowDefinition, document: any): ChangeSet {
export function setChangeSetRowData(
changeSet: ChangeSet,
definition: ChangeSetRowDefinition,
document: any
): ChangeSet {
if (!changeSet || !definition) return changeSet;
let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
if (fieldName == 'deletes') {
@@ -213,7 +217,7 @@ function extractFields(item: ChangeSetItem, allowNulls = true): UpdateField[] {
}));
}
function insertToSql(
function changeSetInsertToSql(
item: ChangeSetItem,
dbinfo: DatabaseInfo = null
): [AllowIdentityInsert, Insert, AllowIdentityInsert] {
@@ -257,7 +261,7 @@ function insertToSql(
];
}
function extractCondition(item: ChangeSetItem): Condition {
export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): Condition {
return {
conditionType: 'and',
conditions: _.keys(item.condition).map(columnName => ({
@@ -271,6 +275,7 @@ function extractCondition(item: ChangeSetItem): Condition {
pureName: item.pureName,
schemaName: item.schemaName,
},
alias,
},
},
right: {
@@ -281,7 +286,7 @@ function extractCondition(item: ChangeSetItem): Condition {
};
}
function updateToSql(item: ChangeSetItem): Update {
function changeSetUpdateToSql(item: ChangeSetItem): Update {
return {
from: {
name: {
@@ -291,11 +296,11 @@ function updateToSql(item: ChangeSetItem): Update {
},
commandType: 'update',
fields: extractFields(item),
where: extractCondition(item),
where: extractChangeSetCondition(item),
};
}
function deleteToSql(item: ChangeSetItem): Delete {
function changeSetDeleteToSql(item: ChangeSetItem): Delete {
return {
from: {
name: {
@@ -304,16 +309,16 @@ function deleteToSql(item: ChangeSetItem): Delete {
},
},
commandType: 'delete',
where: extractCondition(item),
where: extractChangeSetCondition(item),
};
}
export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Command[] {
return _.compact(
_.flatten([
...(changeSet.inserts.map(item => insertToSql(item, dbinfo)) as any),
...changeSet.updates.map(updateToSql),
...changeSet.deletes.map(deleteToSql),
...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any),
...changeSet.updates.map(changeSetUpdateToSql),
...changeSet.deletes.map(changeSetDeleteToSql),
])
);
}

View File

@@ -0,0 +1,136 @@
import _ from 'lodash';
import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree';
import { NamedObjectInfo, DatabaseInfo, ForeignKeyInfo, TableInfo } from 'dbgate-types';
import { ChangeSet, ChangeSetItem, extractChangeSetCondition } from './ChangeSet';
export interface ChangeSetDeleteCascade {
title: string;
commands: Command[];
}
// function getDeleteScript()
function processDependencies(
changeSet: ChangeSet,
result: ChangeSetDeleteCascade[],
allForeignKeys: ForeignKeyInfo[],
fkPath: ForeignKeyInfo[],
table: TableInfo,
baseCmd: ChangeSetItem,
dbinfo: DatabaseInfo
) {
if (result.find(x => x.title == table.pureName)) return;
const dependencies = allForeignKeys.filter(
x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName
);
for (const fk of dependencies) {
const depTable = dbinfo.tables.find(x => x.pureName == fk.pureName && x.schemaName == fk.schemaName);
const subFkPath = [...fkPath, fk];
if (depTable && depTable.pureName != baseCmd.pureName) {
processDependencies(changeSet, result, allForeignKeys, subFkPath, depTable, baseCmd, dbinfo);
}
const refCmd: Delete = {
commandType: 'delete',
from: {
name: {
pureName: fk.pureName,
schemaName: fk.schemaName,
},
},
where: {
conditionType: 'exists',
subQuery: {
commandType: 'select',
selectAll: true,
from: {
name: {
pureName: fk.pureName,
schemaName: fk.schemaName,
},
alias: 't0',
relations: subFkPath.map((fkItem, fkIndex) => ({
joinType: 'INNER JOIN',
alias: `t${fkIndex + 1}`,
name: {
pureName: fkItem.refTableName,
schemaName: fkItem.refSchemaName,
},
conditions: fkItem.columns.map(column => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.columnName,
source: { alias: `t${fkIndex}` },
},
right: {
exprType: 'column',
columnName: column.refColumnName,
source: { alias: `t${fkIndex + 1}` },
},
})),
})),
},
where: {
conditionType: 'and',
conditions: [
extractChangeSetCondition(baseCmd, `t${subFkPath.length}`),
// @ts-ignore
...table.primaryKey.columns.map(column => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName: column.columnName,
source: { alias: 't0' },
},
right: {
exprType: 'column',
columnName: column.columnName,
source: {
name: fk,
},
},
})),
],
},
},
},
};
let resItem = result.find(x => x.title == fk.pureName);
if (!resItem) {
resItem = {
title: fk.pureName,
commands: [],
};
result.push(resItem);
}
resItem.commands.push(refCmd);
}
}
export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] {
const result: ChangeSetDeleteCascade[] = [];
const allForeignKeys = _.flatten(dbinfo.tables.map(x => x.foreignKeys));
for (const baseCmd of changeSet.deletes) {
const table = dbinfo.tables.find(x => x.pureName == baseCmd.pureName && x.schemaName == baseCmd.schemaName);
if (!table.primaryKey) continue;
processDependencies(changeSet, result, allForeignKeys, [], table, baseCmd, dbinfo);
// let resItem = result.find(x => x.title == baseCmd.pureName);
// if (!resItem) {
// resItem = {
// title: baseCmd.pureName,
// commands: [],
// };
// result.push(resItem);
// }
// resItem.commands.push(changeSetDeleteToSql(baseCmd));
}
return result;
}

View File

@@ -11,3 +11,4 @@ export * from './runMacro';
export * from './FormViewDisplay';
export * from './TableFormViewDisplay';
export * from './CollectionGridDisplay';
export * from './deleteCascade';

View File

@@ -18,7 +18,7 @@
"@types/node": "^13.7.0",
"jest": "^24.9.0",
"ts-jest": "^25.2.1",
"typescript": "^3.7.5"
"typescript": "^4.4.3"
},
"dependencies": {
"@types/parsimmon": "^1.10.1",

View File

@@ -32,6 +32,6 @@
"@types/node": "^13.7.0",
"jest": "^24.9.0",
"ts-jest": "^25.2.1",
"typescript": "^3.7.5"
"typescript": "^4.4.3"
}
}

View File

@@ -28,7 +28,7 @@
"devDependencies": {
"@types/node": "^13.7.0",
"dbgate-types": "^4.1.1",
"typescript": "^3.7.5"
"typescript": "^4.4.3"
},
"dependencies": {
"lodash": "^4.17.21"

View File

@@ -28,7 +28,7 @@
"dbgate-types": "^4.1.1",
"jest": "^24.9.0",
"ts-jest": "^25.2.1",
"typescript": "^3.7.5"
"typescript": "^4.4.3"
},
"dependencies": {
"lodash": "^4.17.21",

View File

@@ -1,4 +1,4 @@
import { DatabaseInfo, DatabaseModification, EngineDriver } from 'dbgate-types';
import { DatabaseInfo, DatabaseModification, EngineDriver, SqlDialect } from 'dbgate-types';
import _sortBy from 'lodash/sortBy';
import _groupBy from 'lodash/groupBy';
import _pick from 'lodash/pick';
@@ -13,8 +13,11 @@ export class DatabaseAnalyser {
modifications: DatabaseModification[];
singleObjectFilter: any;
singleObjectId: string = null;
dialect: SqlDialect;
constructor(public pool, public driver: EngineDriver) {}
constructor(public pool, public driver: EngineDriver, version) {
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(version)) || driver?.dialect;
}
async _runAnalysis() {
return DatabaseAnalyser.createEmptyStructure();

View File

@@ -17,8 +17,8 @@ export const driverBase = {
dumperClass: SqlDumper,
dialect,
async analyseFull(pool) {
const analyser = new this.analyserClass(pool, this);
async analyseFull(pool, version) {
const analyser = new this.analyserClass(pool, this, version);
return analyser.fullAnalysis();
},
async analyseSingleObject(pool, name, typeField = 'tables') {
@@ -28,8 +28,8 @@ export const driverBase = {
analyseSingleTable(pool, name) {
return this.analyseSingleObject(pool, name, 'tables');
},
async analyseIncremental(pool, structure) {
const analyser = new this.analyserClass(pool, this);
async analyseIncremental(pool, structure, version) {
const analyser = new this.analyserClass(pool, this, version);
return analyser.incrementalAnalysis(structure);
},
createDumper() {

View File

@@ -67,8 +67,8 @@ export interface EngineDriver {
name: string;
}[]
>;
analyseFull(pool: any): Promise<DatabaseInfo>;
analyseIncremental(pool: any, structure: DatabaseInfo): Promise<DatabaseInfo>;
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
dialect: SqlDialect;
dialectByVersion(version): SqlDialect;
createDumper(): SqlDumper;

View File

@@ -14,10 +14,10 @@
"devDependencies": {
"@ant-design/colors": "^5.0.0",
"@mdi/font": "^5.9.55",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-replace": "^2.4.1",
"@rollup/plugin-typescript": "^6.0.0",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@tsconfig/svelte": "^1.0.0",
"ace-builds": "^1.4.8",
"chart.js": "^2.9.4",
@@ -32,7 +32,7 @@
"lodash": "^4.17.21",
"randomcolor": "^0.6.2",
"resize-observer-polyfill": "^1.5.1",
"rollup": "^2.3.4",
"rollup": "^2.57.0",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
@@ -41,13 +41,13 @@
"sirv-cli": "^1.0.0",
"socket.io-client": "^2.3.0",
"sql-formatter": "^2.3.3",
"svelte": "^3.35.0",
"svelte": "^3.43.0",
"svelte-check": "^1.0.0",
"svelte-markdown": "^0.1.4",
"svelte-preprocess": "^4.0.0",
"svelte-preprocess": "^4.9.5",
"svelte-select": "^3.17.0",
"tslib": "^2.0.0",
"typescript": "^3.9.3",
"tslib": "^2.3.1",
"typescript": "^4.4.3",
"uuid": "^3.4.0"
}
}

View File

@@ -52,8 +52,9 @@
</div>
{/if}
{#each filtered as item}
{#each items as item}
<AppObjectListItem
isHidden={!item.isMatched}
{...$$restProps}
{module}
data={item.data}

View File

@@ -1,5 +1,6 @@
<script>
import _ from 'lodash';
import { asyncFilter } from '../utility/common';
import AppObjectGroup from './AppObjectGroup.svelte';
import AppObjectListItem from './AppObjectListItem.svelte';
@@ -16,11 +17,36 @@
export let groupFunc = undefined;
$: filtered = list.filter(data => {
const matcher = module.createMatcher && module.createMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
});
$: filtered = !groupFunc
? list.filter(data => {
const matcher = module.createMatcher && module.createMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
})
: null;
$: childrenMatched = !groupFunc
? list.filter(data => {
const matcher = module.createChildMatcher && module.createChildMatcher(data);
if (matcher && !matcher(filter)) return false;
return true;
})
: null;
// let filtered = [];
// $: {
// if (!groupFunc) {
// asyncFilter(list, async data => {
// const matcher = module.createMatcher && module.createMatcher(data);
// if (matcher && !(await matcher(filter))) return false;
// return true;
// }).then(res => {
// filtered = res;
// });
// }
// }
$: listGrouped = groupFunc
? _.compact(
@@ -34,7 +60,6 @@
: null;
$: groups = groupFunc ? _.groupBy(listGrouped, 'group') : null;
</script>
{#if groupFunc}
@@ -49,11 +74,13 @@
{checkedObjectsStore}
{groupFunc}
{disableContextMenu}
{filter}
/>
{/each}
{:else}
{#each filtered as data}
{#each list as data}
<AppObjectListItem
isHidden={!filtered.includes(data)}
{module}
{subItemsComponent}
{expandOnClick}
@@ -63,6 +90,8 @@
{expandIconFunc}
{checkedObjectsStore}
{disableContextMenu}
{filter}
isExpandedBySearch={childrenMatched.includes(data)}
/>
{/each}
{/if}

View File

@@ -10,6 +10,8 @@
import { tick } from 'svelte';
import { plusExpandIcon } from '../icons/expandIcons';
export let isHidden;
export let filter;
export let module;
export let data;
export let subItemsComponent;
@@ -18,6 +20,7 @@
export let expandIconFunc = plusExpandIcon;
export let checkedObjectsStore = null;
export let disableContextMenu = false;
export let isExpandedBySearch = false;
let isExpanded = false;
@@ -37,21 +40,23 @@
$: if (!expandable && isExpanded) isExpanded = false;
</script>
<svelte:component
this={module.default}
{data}
on:click={handleExpand}
on:expand={handleExpandButton}
expandIcon={getExpandIcon(expandable, subItemsComponent, isExpanded, expandIconFunc)}
{checkedObjectsStore}
{module}
{disableContextMenu}
/>
{#if !isHidden}
<svelte:component
this={module.default}
{data}
on:click={handleExpand}
on:expand={handleExpandButton}
expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)}
{checkedObjectsStore}
{module}
{disableContextMenu}
/>
{#if isExpanded && subItemsComponent}
<div class="subitems">
<svelte:component this={subItemsComponent} {data} />
</div>
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
<div class="subitems">
<svelte:component this={subItemsComponent} {data} {filter} />
</div>
{/if}
{/if}
<style>

View File

@@ -1,12 +1,22 @@
<script context="module">
export const extractKey = data => data._id;
export const createMatcher = ({ displayName, server }) => filter => filterName(filter, displayName, server);
export const createMatcher = props => filter => {
const { _id, displayName, server } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
return filterName(filter, displayName, server, ...databases.map(x => x.name));
};
export const createChildMatcher = props => filter => {
if (!filter) return false;
const { _id } = props;
const databases = getLocalStorage(`database_list_${_id}`) || [];
return filterName(filter, ...databases.map(x => x.name));
};
</script>
<script lang="ts">
import _ from 'lodash';
import AppObjectCore from './AppObjectCore.svelte';
import { currentDatabase, extensions, getCurrentConfig, openedConnections } from '../stores';
import { currentDatabase, extensions, getCurrentConfig, getOpenedConnections, openedConnections } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import { filterName } from 'dbgate-tools';
import { showModal } from '../modals/modalTools';
@@ -17,6 +27,8 @@
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
import getElectron from '../utility/getElectron';
import getConnectionLabel from '../utility/getConnectionLabel';
import { getDatabaseList } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache';
export let data;

View File

@@ -3,7 +3,7 @@
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
const electron = getElectron();
const icons = {
export const databaseObjectIcons = {
tables: 'img table',
collections: 'img collection',
views: 'img view',
@@ -337,7 +337,7 @@
{
title: scriptTemplate ? 'Query #' : pureName,
tooltip,
icon: scriptTemplate ? 'img sql-file' : icons[objectTypeField],
icon: scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField],
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
props: {
schemaName,
@@ -352,6 +352,24 @@
{ forceNewTab }
);
}
export function handleDatabaseObjectClick(data, forceNewTab = false) {
const { schemaName, pureName, conid, database, objectTypeField } = data;
openDatabaseObjectDetail(
defaultTabs[objectTypeField],
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
{
schemaName,
pureName,
conid,
database,
objectTypeField,
},
forceNewTab,
null
);
}
</script>
<script lang="ts">
@@ -378,34 +396,7 @@
export let data;
function handleClick(forceNewTab = false) {
const { schemaName, pureName, conid, database, objectTypeField } = data;
openDatabaseObjectDetail(
defaultTabs[objectTypeField],
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
{
schemaName,
pureName,
conid,
database,
objectTypeField,
},
forceNewTab,
null
);
// openNewTab({
// title: data.pureName,
// icon: 'img table',
// tabComponent: 'TableDataTab',
// props: {
// schemaName,
// pureName,
// conid,
// database,
// objectTypeField,
// },
// });
handleDatabaseObjectClick(data, forceNewTab);
}
const getDriver = async () => {
@@ -557,7 +548,7 @@
module={$$props.module}
{data}
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
icon={icons[data.objectTypeField]}
icon={databaseObjectIcons[data.objectTypeField]}
menu={createMenu}
on:click={() => handleClick()}
on:middleclick={() => handleClick(true)}

View File

@@ -1,11 +1,16 @@
<script lang="ts">
import { filterName } from 'dbgate-tools';
import { useDatabaseList } from '../utility/metadataLoaders';
import AppObjectList from './AppObjectList.svelte';
import * as databaseAppObject from './DatabaseAppObject.svelte';
export let filter;
export let data;
$: databases = useDatabaseList({ conid: data._id });
</script>
<AppObjectList list={($databases || []).map(db => ({ ...db, connection: data }))} module={databaseAppObject} />
<AppObjectList
list={($databases || []).filter(x => filterName(filter, x.name)).map(db => ({ ...db, connection: data }))}
module={databaseAppObject}
/>

View File

@@ -1,17 +1,62 @@
<script context="module">
const electron = getElectron();
registerCommand({
id: 'commandPalette.show',
category: 'Command palette',
name: 'Show',
toolbarName: 'Menu',
toolbarName: 'Menu+Search',
toolbarOrder: 0,
keyText: 'F1',
toolbar: true,
showDisabled: true,
icon: 'icon menu',
onClick: () => visibleCommandPalette.set(true),
testEnabled: () => !getVisibleCommandPalette(),
onClick: () => visibleCommandPalette.set('menu'),
testEnabled: () => getVisibleCommandPalette() != 'menu',
});
registerCommand({
id: 'database.search',
category: 'Database',
toolbarName: 'Database search',
name: 'Search',
keyText: electron ? 'Ctrl+P' : 'F3',
onClick: () => visibleCommandPalette.set('database'),
testEnabled: () => getVisibleCommandPalette() != 'database',
});
function extractDbItems(db, dbConnectionInfo, connectionList) {
const objectList = _.flatten(
['tables', 'collections', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
_.sortBy(
((db || {})[objectTypeField] || []).map(obj => ({
text: obj.pureName,
onClick: () => handleDatabaseObjectClick({ objectTypeField, ...dbConnectionInfo, ...obj }),
icon: databaseObjectIcons[objectTypeField],
})),
['text']
)
)
);
const databaseList = [];
for (const connection of connectionList || []) {
const conid = connection._id;
const databases = getLocalStorage(`database_list_${conid}`) || [];
for (const db of databases) {
databaseList.push({
text: `${db.name} on ${getConnectionLabel(connection)}`,
icon: 'img database',
onClick: () => currentDatabase.set({ connection, name: db.name }),
});
}
}
return [..._.sortBy(databaseList, 'text'), ...objectList];
// return db?.tables?.map(table => ({
// text: table.pureName,
// onClick: () => handleDatabaseObjectClick({ ...dbinfo, ...table }),
// }));
}
</script>
<script>
@@ -19,9 +64,21 @@
import _ from 'lodash';
import { onMount } from 'svelte';
import { commands, commandsCustomized, getVisibleCommandPalette, visibleCommandPalette } from '../stores';
import { databaseObjectIcons, handleDatabaseObjectClick } from '../appobj/DatabaseObjectAppObject.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import {
commands,
commandsCustomized,
currentDatabase,
getVisibleCommandPalette,
visibleCommandPalette,
} from '../stores';
import clickOutside from '../utility/clickOutside';
import getConnectionLabel from '../utility/getConnectionLabel';
import getElectron from '../utility/getElectron';
import keycodes from '../utility/keycodes';
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache';
import registerCommand from './registerCommand';
let domInput;
@@ -44,9 +101,17 @@
'text'
);
$: filteredItems = (parentCommand ? parentCommand.getSubCommands() : sortedComands).filter(
x => !x.isGroupCommand && filterName(filter, x.text)
);
$: conid = _.get($currentDatabase, 'connection._id');
$: database = _.get($currentDatabase, 'name');
$: databaseInfo = useDatabaseInfo({ conid, database });
$: connectionList = useConnectionList();
$: filteredItems = ($visibleCommandPalette == 'database'
? extractDbItems($databaseInfo, { conid, database }, $connectionList)
: parentCommand
? parentCommand.getSubCommands()
: sortedComands
).filter(x => !x.isGroupCommand && filterName(filter, x.text));
function handleCommand(command) {
if (command.getSubCommands) {
@@ -55,7 +120,7 @@
filter = '';
selectedIndex = 0;
} else {
$visibleCommandPalette = false;
$visibleCommandPalette = null;
command.onClick();
}
}
@@ -68,7 +133,7 @@
e.stopPropagation();
handleCommand(filteredItems[selectedIndex]);
}
if (e.keyCode == keycodes.escape) $visibleCommandPalette = false;
if (e.keyCode == keycodes.escape) $visibleCommandPalette = null;
if (e.keyCode == keycodes.pageDown) selectedIndex = Math.min(selectedIndex + 15, filteredItems.length - 1);
if (e.keyCode == keycodes.pageUp) selectedIndex = Math.max(selectedIndex - 15, 0);
@@ -77,30 +142,65 @@
$: if (domItems[selectedIndex]) domItems[selectedIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });
</script>
<div class="main" use:clickOutside on:clickOutside={() => ($visibleCommandPalette = false)}>
<div class="search">
<input
type="text"
bind:this={domInput}
bind:value={filter}
on:keydown={handleKeyDown}
placeholder={parentCommand?.text || ''}
/>
<div
class="main"
use:clickOutside
on:clickOutside={() => {
$visibleCommandPalette = null;
}}
>
<div class="pages">
<div
class="page"
class:selected={$visibleCommandPalette == 'menu'}
on:click={() => {
$visibleCommandPalette = 'menu';
domInput.focus();
}}
>
<FontIcon icon="icon menu" /> Commands
</div>
<div
class="page"
class:selected={$visibleCommandPalette == 'database'}
on:click={() => {
$visibleCommandPalette = 'database';
domInput.focus();
}}
>
<FontIcon icon="icon database" /> Database
</div>
</div>
<div class="content">
{#each filteredItems as command, index}
<div
class="command"
class:selected={index == selectedIndex}
on:click={() => handleCommand(command)}
bind:this={domItems[index]}
>
<div>{command.text}</div>
{#if command.keyText}
<div class="shortcut">{command.keyText}</div>
{/if}
</div>
{/each}
<div class="mainInner">
<div class="search">
<input
type="text"
bind:this={domInput}
bind:value={filter}
on:keydown={handleKeyDown}
placeholder={parentCommand?.text || ''}
/>
</div>
<div class="content">
{#each filteredItems as command, index}
<div
class="command"
class:selected={index == selectedIndex}
on:click={() => handleCommand(command)}
bind:this={domItems[index]}
>
<div>
{#if command.icon}
<span class="mr-1"><FontIcon icon={command.icon} /></span>
{/if}
{command.text}
</div>
{#if command.keyText}
<div class="shortcut">{command.keyText}</div>
{/if}
</div>
{/each}
</div>
</div>
</div>
@@ -108,6 +208,9 @@
.main {
width: 500px;
background: var(--theme-bg-2);
}
.mainInner {
padding: 5px;
}
@@ -135,4 +238,19 @@
.shortcut {
background: var(--theme-bg-3);
}
.pages {
display: flex;
}
.page {
padding: 5px;
border: 1px solid var(--theme-border);
cursor: pointer;
}
.page:hover {
color: var(--theme-font-hover);
}
.page.selected {
background: var(--theme-bg-1);
}
</style>

View File

@@ -266,6 +266,15 @@ if (hasPermission('settings/change')) {
});
}
if (electron) {
registerCommand({
id: 'file.exit',
category: 'File',
name: 'Exit',
onClick: () => electron.remote.getCurrentWindow().close(),
});
}
export function registerFileCommands({
idPrefix,
category,

View File

@@ -10,6 +10,7 @@
export let isNative = false;
export let isMulti = false;
export let notSelected = null;
export let defaultValue = '';
let listOpen = false;
let isFocused = false;
@@ -21,18 +22,19 @@
{#if isNative}
<select
value={value || defaultValue}
{...$$restProps}
on:change={e => {
dispatch('change', e.target['value']);
}}
>
{#if notSelected}
<option value="" selected={!value}>
<option value="">
{_.isString(notSelected) ? notSelected : '(not selected)'}
</option>
{/if}
{#each _.compact(options) as x (x.value)}
<option value={x.value} selected={value == x.value}>
<option value={x.value}>
{x.label}
</option>
{/each}

View File

@@ -1,10 +1,10 @@
<script>
import _ from 'lodash';
import _, { startsWith } from 'lodash';
import { writable } from 'svelte/store';
import FormStyledButton from '../elements/FormStyledButton.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormProviderCore from '../forms/FormProviderCore.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import FormValues from '../forms/FormValues.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import SqlEditor from '../query/SqlEditor.svelte';
@@ -15,20 +15,77 @@
export let onConfirm;
export let engine;
export let recreates;
export let deleteCascadesScripts;
$: isRecreated = _.sum(_.values(recreates || {})) > 0;
const values = writable({});
// $: console.log('recreates', recreates);
</script>
<FormProvider>
<FormProviderCore {values}>
<ModalBase {...$$restProps}>
<div slot="header">Save changes</div>
<div class="editor">
<SqlEditor {engine} value={sql} readOnly />
<SqlEditor
{engine}
value={$values.deleteReferencesCascade
? [
...deleteCascadesScripts
.filter(({ script, title }) => $values[`deleteReferencesFor_${title}`] !== false)
.map(({ script, title }) => script),
sql,
].join('\n')
: sql}
readOnly
/>
</div>
{#if !_.isEmpty(deleteCascadesScripts)}
<div class="mt-2">
<FormCheckboxField
templateProps={{ noMargin: true }}
label="Delete references CASCADE"
name="deleteReferencesCascade"
/>
</div>
{/if}
{#if $values.deleteReferencesCascade}
<div class="form-margin flex">
<FormStyledButton
value="Check all"
on:click={() => {
$values = _.omitBy($values, (v, k) => k.startsWith('deleteReferencesFor_'));
}}
/>
<FormStyledButton
value="Uncheck all"
on:click={() => {
const newValues = { ...$values };
for (const item of deleteCascadesScripts) {
newValues[`deleteReferencesFor_${item.title}`] = false;
}
$values = newValues;
}}
/>
</div>
<div class="form-margin flex flex-wrap">
{#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable}
<div class="mr-1 nowrap">
<FormCheckboxField
defaultValue={true}
templateProps={{ noMargin: true }}
label={deleteTable.title}
name={`deleteReferencesFor_${deleteTable.title}`}
/>
</div>
{/each}
</div>
{/if}
{#if isRecreated}
<div class="form-margin">
<div>
@@ -44,20 +101,27 @@
{/if}
<div slot="footer">
<FormValues let:values>
<FormSubmit
value="OK"
disabled={isRecreated && !values.allowRecreate}
on:click={() => {
closeCurrentModal();
onConfirm();
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
</FormValues>
<FormSubmit
value="OK"
disabled={isRecreated && !$values.allowRecreate}
on:click={e => {
closeCurrentModal();
onConfirm(
e.detail.deleteReferencesCascade
? [
...deleteCascadesScripts
.filter(({ script, title }) => e.detail[`deleteReferencesFor_${title}`] !== false)
.map(({ script, title }) => script),
sql,
].join('\n')
: null
);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
</div>
</ModalBase>
</FormProvider>
</FormProviderCore>
<style>
.editor {
@@ -69,4 +133,8 @@
.form-margin {
margin: var(--dim-large-form-margin);
}
.flex-wrap {
flex-wrap: wrap;
}
</style>

View File

@@ -24,7 +24,6 @@
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
$: driver = $extensions.drivers.find(x => x.engine == engine);
$: defaultDatabase = $values.defaultDatabase;
</script>
<FormSelectField
@@ -125,6 +124,7 @@
label="Password mode"
isNative
name="passwordMode"
defaultValue="saveEncrypted"
options={[
{ value: 'saveEncrypted', label: 'Save and encrypt' },
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
@@ -154,5 +154,4 @@
.radio :global(label) {
margin-right: 10px;
}
</style>

View File

@@ -22,7 +22,7 @@
$: {
if (!$values.sshMode) setFieldValue('sshMode', 'userPassword');
if (!$values.sshPort) setFieldValue('sshPort', '22');
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyFile);
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyfile);
}
</script>

View File

@@ -43,4 +43,4 @@
}
</script>
<FormSelectFieldRaw {name} options={getOptions()} />
<FormSelectFieldRaw {name} options={getOptions()} isNative />

View File

@@ -35,7 +35,7 @@ export const openedConnections = writable([]);
export const currentDatabase = writable(null);
export const openedTabs = writableWithStorage<TabDefinition[]>([], 'openedTabs');
export const extensions = writable<ExtensionsDirectory>(null);
export const visibleCommandPalette = writable(false);
export const visibleCommandPalette = writable(null);
export const commands = writable({});
export const currentTheme = writableWithStorage('theme-light', 'currentTheme');
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
@@ -156,3 +156,9 @@ extensions.subscribe(value => {
extensionsValue = value;
});
export const getExtensions = () => extensionsValue;
let openedConnectionsValue = null;
openedConnections.subscribe(value => {
openedConnectionsValue = value;
});
export const getOpenedConnections = () => openedConnectionsValue;

View File

@@ -16,10 +16,10 @@
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
export const allowAddToFavorites = props => true;
</script>
<script lang="ts">
import _ from 'lodash';
import App from '../App.svelte';
import TableDataGrid from '../datagrid/TableDataGrid.svelte';
import useGridConfig from '../utility/useGridConfig';
@@ -29,6 +29,7 @@
createChangeSet,
createGridCache,
createGridConfig,
getDeleteCascades,
TableFormViewDisplay,
TableGridDisplay,
} from 'dbgate-datalib';
@@ -87,11 +88,18 @@
export function save() {
const driver = findEngineDriver($connection, $extensions);
const script = changeSetToSql($changeSetStore?.value, $dbinfo);
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo);
const sql = scriptToSql(driver, script);
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
title,
script: scriptToSql(driver, commands),
}));
console.log('deleteCascadesScripts', deleteCascadesScripts);
showModal(ConfirmSqlModal, {
sql,
onConfirm: () => handleConfirmSql(sql),
onConfirm: sqlOverride => handleConfirmSql(sqlOverride || sql),
engine: driver.engine,
deleteCascadesScripts,
});
}
@@ -105,7 +113,6 @@
}
registerMenu({ command: 'tableData.save', tag: 'save' });
</script>
<TableDataGrid

View File

@@ -22,7 +22,7 @@
'Sorry, DbGate has crashed again.\nDo you want to clear local user data to avoid crashing after next reload?'
)
) {
localStorage.clear();
localStorage.removeItem('openedTabs');
try {
await localforage.clear();
} catch (err) {

View File

@@ -32,3 +32,9 @@ export function getObjectTypeFieldLabel(objectTypeField) {
if (objectTypeField == 'matviews') return 'Materialized Views';
return _.startCase(objectTypeField);
}
export async function asyncFilter(arr, predicate) {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}

View File

@@ -8,6 +8,7 @@ import getAsArray from './getAsArray';
import { DatabaseInfo } from 'dbgate-types';
import { derived } from 'svelte/store';
import { extendDatabaseInfo } from 'dbgate-tools';
import { setLocalStorage } from '../utility/storageCache';
const databaseInfoLoader = ({ conid, database }) => ({
url: 'database-connections/structure',
@@ -74,6 +75,9 @@ const databaseListLoader = ({ conid }) => ({
url: 'server-connections/list-databases',
params: { conid },
reloadTrigger: `database-list-changed-${conid}`,
onLoaded: value => {
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
},
});
const serverVersionLoader = ({ conid }) => ({
@@ -135,7 +139,7 @@ const authTypesLoader = ({ engine }) => ({
});
async function getCore(loader, args) {
const { url, params, reloadTrigger, transform } = loader(args);
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
const key = stableStringify({ url, ...params });
async function doLoad() {
@@ -144,7 +148,9 @@ async function getCore(loader, args) {
url,
params,
});
return (transform || (x => x))(resp.data);
const res = (transform || (x => x))(resp.data);
if (onLoaded) onLoaded(res);
return res;
}
const fromCache = cacheGet(key);
@@ -156,7 +162,7 @@ async function getCore(loader, args) {
}
function useCore(loader, args) {
const { url, params, reloadTrigger, transform } = loader(args);
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
const cacheKey = stableStringify({ url, ...params });
return {
@@ -168,7 +174,9 @@ function useCore(loader, args) {
params,
url,
});
return (transform || (x => x))(resp.data);
const res = (transform || (x => x))(resp.data);
if (onLoaded) onLoaded(res);
return res;
}
if (cacheKey) {

View File

@@ -0,0 +1,22 @@
const cache = {};
export function getLocalStorage(key) {
if (key in cache) return cache[key];
const item = localStorage.getItem(key);
if (item) {
const res = JSON.parse(item);
cache[key] = res;
return res;
}
return undefined;
}
export function setLocalStorage(key, value) {
localStorage.setItem(key, JSON.stringify(value));
delete cache[key];
}
export function removeLocalStorage(key) {
localStorage.removeItem(key);
delete cache[key];
}

View File

@@ -1,4 +1,6 @@
<script lang="ts" context="module">
const electron = getElectron();
const closeTabFunc = closeCondition => tabid => {
openedTabs.update(files => {
const active = files.find(x => x.tabid == tabid);
@@ -33,6 +35,9 @@
}))
);
};
const closeCurrentTab = () => {
closeTab(getActiveTabId());
};
const closeWithSameDb = closeTabFunc(
(x, active) =>
_.get(x, 'props.conid') == _.get(active, 'props.conid') &&
@@ -96,6 +101,15 @@
onClick: closeAll,
});
registerCommand({
id: 'tabs.closeTab',
category: 'Tabs',
name: 'Close tab',
keyText: electron ? 'Ctrl+W' : null,
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1,
onClick: closeCurrentTab,
});
registerCommand({
id: 'tabs.addToFavorites',
category: 'Tabs',
@@ -120,11 +134,12 @@
import FavoriteModal from '../modals/FavoriteModal.svelte';
import { showModal } from '../modals/modalTools';
import { currentDatabase, getActiveTab, getOpenedTabs, openedTabs, activeTabId } from '../stores';
import { currentDatabase, getActiveTab, getOpenedTabs, openedTabs, activeTabId, getActiveTabId } from '../stores';
import tabs from '../tabs';
import { setSelectedTab } from '../utility/common';
import contextMenu from '../utility/contextMenu';
import getConnectionLabel from '../utility/getConnectionLabel';
import getElectron from '../utility/getElectron';
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
import { duplicateTab } from '../utility/openNewTab';

View File

@@ -66,7 +66,7 @@
<div class="main">
{#if !$visibleToolbar}
<div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = true)}>
<div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = 'menu')}>
<FontIcon icon="icon menu" />
</div>
{/if}

View File

@@ -1,9 +1,9 @@
diff --git a/node_modules/svelte/internal/index.js b/node_modules/svelte/internal/index.js
index ee20a17..7b6fff8 100644
index 1cce90d..6220522 100644
--- a/node_modules/svelte/internal/index.js
+++ b/node_modules/svelte/internal/index.js
@@ -200,7 +200,7 @@ function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
@@ -374,7 +374,7 @@ function insert_hydration(target, node, anchor) {
}
}
function detach(node) {
- node.parentNode.removeChild(node);
@@ -12,11 +12,11 @@ index ee20a17..7b6fff8 100644
function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) {
diff --git a/node_modules/svelte/internal/index.mjs b/node_modules/svelte/internal/index.mjs
index 4146e56..f5f00c5 100644
index 6650e85..b746187 100644
--- a/node_modules/svelte/internal/index.mjs
+++ b/node_modules/svelte/internal/index.mjs
@@ -196,7 +196,7 @@ function insert(target, node, anchor) {
target.insertBefore(node, anchor || null);
@@ -370,7 +370,7 @@ function insert_hydration(target, node, anchor) {
}
}
function detach(node) {
- node.parentNode.removeChild(node);

View File

@@ -1,8 +1,8 @@
const { DatabaseAnalyser } = require('dbgate-tools');
class Analyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
constructor(pool, driver, version) {
super(pool, driver, version);
}
async _runAnalysis() {

View File

@@ -50,8 +50,8 @@ function getColumnInfo({
}
class MsSqlAnalyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
constructor(pool, driver, version) {
super(pool, driver, version);
}
createQuery(resFileName, typeFields) {

View File

@@ -29,8 +29,8 @@ function getColumnInfo({
}
class Analyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
constructor(pool, driver, version) {
super(pool, driver, version);
}
createQuery(resFileName, typeFields) {

View File

@@ -36,8 +36,8 @@ function getColumnInfo({
}
class Analyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
constructor(pool, driver, version) {
super(pool, driver, version);
}
createQuery(resFileName, typeFields) {

View File

@@ -133,10 +133,15 @@ const drivers = driverBases.map(driverBase => ({
const m = version.match(/([\d\.]+)/);
let versionText = null;
let versionMajor = null;
let versionMinor = null;
if (m) {
if (isCockroach) versionText = `CockroachDB ${m[1]}`;
if (isRedshift) versionText = `Redshift ${m[1]}`;
if (isPostgres) versionText = `PostgreSQL ${m[1]}`;
const numbers = m[1].split('.');
if (numbers[0]) versionMajor = parseInt(numbers[0]);
if (numbers[1]) versionMinor = parseInt(numbers[1]);
}
return {
@@ -145,6 +150,8 @@ const drivers = driverBases.map(driverBase => ({
isPostgres,
isCockroach,
isRedshift,
versionMajor,
versionMinor,
};
},
async readQuery(client, sql, structure) {

View File

@@ -57,6 +57,20 @@ const postgresDriver = {
...dialect,
materializedViews: true,
},
dialectByVersion(version) {
if (version) {
return {
...dialect,
materializedViews:
version &&
version.versionMajor != null &&
version.versionMinor != null &&
(version.versionMajor > 9 || version.versionMajor == 9 || version.versionMinor >= 3),
};
}
return dialect;
},
};
/** @type {import('dbgate-types').EngineDriver} */

View File

@@ -16,8 +16,8 @@ SELECT
`;
class Analyser extends DatabaseAnalyser {
constructor(pool, driver) {
super(pool, driver);
constructor(pool, driver, version) {
super(pool, driver, version);
}
async _getFastSnapshot() {

121
yarn.lock
View File

@@ -942,10 +942,10 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
"@rollup/plugin-commonjs@^17.0.0":
version "17.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d"
integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==
"@rollup/plugin-commonjs@^20.0.0":
version "20.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz#3246872dcbcb18a54aaa6277a8c7d7f1b155b745"
integrity sha512-5K0g5W2Ol8hAcTHqcTBHiA7M58tfmYi1o9KxeJuuRNpGaTa5iLjcyemBitCBcKXaHamOBBEH2dGom6v6Unmqjg==
dependencies:
"@rollup/pluginutils" "^3.1.0"
commondir "^1.0.1"
@@ -955,10 +955,10 @@
magic-string "^0.25.7"
resolve "^1.17.0"
"@rollup/plugin-node-resolve@^11.0.0":
version "11.2.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714"
integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA==
"@rollup/plugin-node-resolve@^13.0.5":
version "13.0.5"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.5.tgz#016abe58796a4ff544d6beac7818921e3d3777fc"
integrity sha512-mVaw6uxtvuGx/XCI4qBQXsDZJUfyx5vp39iE0J/7Hd6wDhEbjHr6aES7Nr9yWbuE0BY+oKp6N7Bq6jX5NCGNmQ==
dependencies:
"@rollup/pluginutils" "^3.1.0"
"@types/resolve" "1.17.1"
@@ -967,18 +967,18 @@
is-module "^1.0.0"
resolve "^1.19.0"
"@rollup/plugin-replace@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.1.tgz#c411b5ab72809fb1bfc8b487d8d02eef661460d3"
integrity sha512-XwC1oK5rrtRJ0tn1ioLHS6OV5JTluJF7QE1J/q1hN3bquwjnVxjtMyY9iCnoyH9DQbf92CxajB3o98wZbP3oAQ==
"@rollup/plugin-replace@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz#3a4c9665d4e7a4ce2c360cf021232784892f3fac"
integrity sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg==
dependencies:
"@rollup/pluginutils" "^3.1.0"
magic-string "^0.25.7"
"@rollup/plugin-typescript@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-6.1.0.tgz#289e7f0ea12fd659bd13ad59dda73b9055538b83"
integrity sha512-hJxaiE6WyNOsK+fZpbFh9CUijZYqPQuAOWO5khaGTUkM8DYNNyA2TDlgamecE+qLOG1G1+CwbWMAx3rbqpp6xQ==
"@rollup/plugin-typescript@^8.2.5":
version "8.2.5"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz#e0319761b2b5105615e5a0c371ae05bc2984b7de"
integrity sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==
dependencies:
"@rollup/pluginutils" "^3.1.0"
resolve "^1.17.0"
@@ -2310,6 +2310,11 @@ bson@^1.1.4:
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a"
integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==
buffer-crc32@^0.2.5:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -3705,6 +3710,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es6-promise@^3.1.2:
version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -4448,7 +4458,7 @@ fsevents@^1.2.7:
bindings "^1.5.0"
nan "^2.12.1"
fsevents@^2.3.2, fsevents@~2.3.1:
fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -4715,6 +4725,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graceful-fs@^4.1.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.3, graceful-fs@^4.2.4:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@@ -9037,7 +9052,7 @@ rimraf@2.6.3:
dependencies:
glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.3:
rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -9109,12 +9124,12 @@ rollup-pluginutils@^2.8.2:
dependencies:
estree-walker "^0.6.1"
rollup@^2.3.4:
version "2.39.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.39.0.tgz#be4f98c9e421793a8fec82c854fb567c35e22ab6"
integrity sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw==
rollup@^2.57.0:
version "2.57.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.57.0.tgz#c1694475eb22e1022477c0f4635fd0ac80713173"
integrity sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==
optionalDependencies:
fsevents "~2.3.1"
fsevents "~2.3.2"
rsvp@^4.8.4:
version "4.8.5"
@@ -9178,6 +9193,16 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sander@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad"
integrity sha1-dB4kXiMfB8r7b98PEzrfohalAq0=
dependencies:
es6-promise "^3.1.2"
graceful-fs "^4.1.3"
mkdirp "^0.5.1"
rimraf "^2.5.2"
sane@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded"
@@ -9562,6 +9587,16 @@ socks@^2.3.3:
ip "^1.1.5"
smart-buffer "^4.1.0"
sorcery@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7"
integrity sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=
dependencies:
buffer-crc32 "^0.2.5"
minimist "^1.2.0"
sander "^0.5.0"
sourcemap-codec "^1.3.0"
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -9614,7 +9649,7 @@ source-map@^0.7.3, source-map@~0.7.2:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4:
sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
@@ -10076,15 +10111,27 @@ svelte-preprocess@^4.0.0:
detect-indent "^6.0.0"
strip-indent "^3.0.0"
svelte-preprocess@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.9.5.tgz#e11bdf3fcdacbd90188cdf29a7371030991f9eba"
integrity sha512-RbJbtuwKbuZq9RyzlljZUmmFNaojrg/zUEyDrS8io7haTcuITQmE4NERx8qiqHreApo6cQst5Qtp4MxUwr58Ew==
dependencies:
"@types/pug" "^2.0.4"
"@types/sass" "^1.16.0"
detect-indent "^6.0.0"
magic-string "^0.25.7"
sorcery "^0.10.0"
strip-indent "^3.0.0"
svelte-select@^3.17.0:
version "3.17.0"
resolved "https://registry.yarnpkg.com/svelte-select/-/svelte-select-3.17.0.tgz#6bea0cb8d0c9465d28a2bac562f2de8a61f48a9f"
integrity sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA==
svelte@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.35.0.tgz#e0d0ba60c4852181c2b4fd851194be6fda493e65"
integrity sha512-gknlZkR2sXheu/X+B7dDImwANVvK1R0QGQLd8CNIfxxGPeXBmePnxfzb6fWwTQRsYQG7lYkZXvpXJvxvpsoB7g==
svelte@^3.43.0:
version "3.43.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.43.0.tgz#d28d06ec523bf0cea3de15558a3241c339a85762"
integrity sha512-T2pMPHrxXp+SM8pLLUXLQgkdo+JhTls7aqj9cD7z8wT2ccP+OrCAmtQS7h6pvMjitaZhXFNnCK582NxDpy8HSw==
symbol-tree@^3.2.2, symbol-tree@^3.2.4:
version "3.2.4"
@@ -10468,6 +10515,11 @@ tslib@^2.0.0, tslib@^2.0.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tslib@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -10542,15 +10594,10 @@ typescript@*:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72"
integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==
typescript@^3.7.4, typescript@^3.7.5:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
typescript@^3.9.3:
version "3.9.9"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
typescript@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
tz-offset@0.0.1:
version "0.0.1"