mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
27 Commits
v0.7.31
...
v0.7.35-be
Author | SHA1 | Date | |
---|---|---|---|
d93ae90373 | |||
86f50521d0 | |||
c966cc4266 | |||
85e516dec0 | |||
|
0b07ee1a87 | ||
|
838810981c | ||
|
b3cf20101b | ||
6c9792f512 | |||
994aa69fd0 | |||
8f84892f07 | |||
a95a76480c | |||
1d1be55d3d | |||
d912faa850 | |||
ba63b049a3 | |||
fcd7e404ba | |||
8eb4d2e114 | |||
acea18e6f0 | |||
973b0fc4be | |||
4adbc575c2 | |||
eb706c3e51 | |||
971df3a989 | |||
3129bf4baa | |||
c6d67cef01 | |||
1d7053ce03 | |||
41e797f9e2 | |||
704f70819b | |||
49a3589536 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [antares-sql,fabio286]
|
||||
github: # [antares-sql,fabio286]
|
||||
patreon: #fabio286
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/fabiodistasio']
|
||||
custom: # ['https://paypal.me/fabiodistasio']
|
||||
|
2
.github/workflows/create-artifact-linux.yml
vendored
2
.github/workflows/create-artifact-linux.yml
vendored
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
49
CHANGELOG.md
49
CHANGELOG.md
@@ -2,6 +2,55 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.7.35-beta.1](https://github.com/antares-sql/antares/compare/v0.7.35-beta.0...v0.7.35-beta.1) (2025-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle id type correctly in update-table-cell where clause, fixes [#974](https://github.com/antares-sql/antares/issues/974) ([8f84892](https://github.com/antares-sql/antares/commit/8f84892f079e5eb56c69170eb4f7bbbebd1fda72))
|
||||
* if webcontents with id 1 not found use first webcontents ([b3cf201](https://github.com/antares-sql/antares/commit/b3cf20101b938a4b47c454c7a94b32b3820bce8e))
|
||||
* improve SQL parameter escaping in update-table-cell, ensuring correct handling of id types ([994aa69](https://github.com/antares-sql/antares/commit/994aa69fd00afc7e24e593b1a6c6667535e090c2))
|
||||
* **MySQL:** handle absence of CHECK_CONSTRAINTS table, fixes [#981](https://github.com/antares-sql/antares/issues/981) ([86f5052](https://github.com/antares-sql/antares/commit/86f50521d05da0afdc9506d74e6ab007e2ae0a84))
|
||||
|
||||
### [0.7.35-beta.0](https://github.com/antares-sql/antares/compare/v0.7.34...v0.7.35-beta.0) (2025-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* custom connection icon disappears during connection, fixes [#939](https://github.com/antares-sql/antares/issues/939) ([1d1be55](https://github.com/antares-sql/antares/commit/1d1be55d3d4ea621364c37e75de616046371feeb))
|
||||
* escape SQL parameters in update and delete for where clauses, fixes [#964](https://github.com/antares-sql/antares/issues/964) ([ba63b04](https://github.com/antares-sql/antares/commit/ba63b049a3a059e77256141dc7b761efbbbf8c1e))
|
||||
* improved handling of query comments, fixes [#963](https://github.com/antares-sql/antares/issues/963) and [#580](https://github.com/antares-sql/antares/issues/580) ([d912faa](https://github.com/antares-sql/antares/commit/d912faa85042219315c9c5658d7f20fda560af44))
|
||||
* use custom elements wrapper for foreign column and description in query ([973b0fc](https://github.com/antares-sql/antares/commit/973b0fc4be1dac25757e430e4520d6fc2212f93b))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **translation:** update Spanish translations, closes [#962](https://github.com/antares-sql/antares/issues/962) ([acea18e](https://github.com/antares-sql/antares/commit/acea18e6f061adab7e79d1249e0e68555a620db5))
|
||||
|
||||
### [0.7.34](https://github.com/antares-sql/antares/compare/v0.7.33...v0.7.34) (2025-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue with some SSH connections, definitely ([eb706c3](https://github.com/antares-sql/antares/commit/eb706c3e51e9cb7577febd291a33594c0650a34a))
|
||||
|
||||
### [0.7.33](https://github.com/antares-sql/antares/compare/v0.7.32...v0.7.33) (2025-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue with some SSH connections, fixes [#947](https://github.com/antares-sql/antares/issues/947) ([3129bf4](https://github.com/antares-sql/antares/commit/3129bf4baa5e72b1d79df986605fd5fad1dce291))
|
||||
|
||||
### [0.7.32](https://github.com/antares-sql/antares/compare/v0.7.31...v0.7.32) (2025-02-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* black background with light theme, fixes [#945](https://github.com/antares-sql/antares/issues/945) ([1d7053c](https://github.com/antares-sql/antares/commit/1d7053ce032efec8377d9500f2e24618f6381ab4))
|
||||
* enhance SVG support in connection customization, fixes [#939](https://github.com/antares-sql/antares/issues/939) ([49a3589](https://github.com/antares-sql/antares/commit/49a3589536d2e75a14125be7b874e29b60fb56c4))
|
||||
* improve error handling in SSH connection ([704f708](https://github.com/antares-sql/antares/commit/704f70819b21a42194d8f68cf9b58ba337f1ada7))
|
||||
* **PostgreSQL:** error with materialized view tabs ([41e797f](https://github.com/antares-sql/antares/commit/41e797f9e27db66370d3ae7750c057f708af76f9))
|
||||
|
||||
### [0.7.31](https://github.com/antares-sql/antares/compare/v0.7.31-beta.5...v0.7.31) (2025-02-11)
|
||||
|
||||
### [0.7.31-beta.5](https://github.com/antares-sql/antares/compare/v0.7.31-beta.4...v0.7.31-beta.5) (2025-02-09)
|
||||
|
1965
package-lock.json
generated
1965
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.7.31",
|
||||
"version": "0.7.35-beta.1",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/antares-sql/antares.git",
|
||||
@@ -214,5 +214,8 @@
|
||||
"vue-eslint-parser": "~8.3.0",
|
||||
"webpack-dev-server": "~4.11.1",
|
||||
"xvfb-maybe": "~0.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "~1.0.11"
|
||||
}
|
||||
}
|
||||
|
@@ -1,86 +0,0 @@
|
||||
import { ClientCode } from 'common/interfaces/antares';
|
||||
|
||||
export const querySplitter =(sql: string, dbType: ClientCode): string[] => {
|
||||
const queries: string[] = [];
|
||||
let currentQuery = '';
|
||||
let insideBlock = false;
|
||||
let insideString = false;
|
||||
let stringDelimiter: string | null = null;
|
||||
let insideDollarTag = false;
|
||||
let dollarTagDelimiter: string | null = null;
|
||||
|
||||
// Regex patterns for BEGIN-END blocks, dollar tags in PostgreSQL, and semicolons
|
||||
const beginRegex = /\bBEGIN\b/i;
|
||||
const endRegex = /\bEND\b;/i;
|
||||
const dollarTagRegex = /\$(\w+)?\$/; // Matches $tag$ or $$
|
||||
|
||||
// Split on semicolons, keeping semicolons attached to the lines
|
||||
const lines = sql.split(/(?<=;)/);
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line) continue;
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i];
|
||||
|
||||
// Handle string boundaries
|
||||
if ((char === '\'' || char === '"') && (!insideString || char === stringDelimiter)) {
|
||||
if (!insideString) {
|
||||
insideString = true;
|
||||
stringDelimiter = char;
|
||||
}
|
||||
else {
|
||||
insideString = false;
|
||||
stringDelimiter = null;
|
||||
}
|
||||
}
|
||||
|
||||
currentQuery += char;
|
||||
|
||||
if (dbType === 'pg') {
|
||||
// Handle dollar-quoted blocks in PostgreSQL
|
||||
if (!insideString && line.slice(i).match(dollarTagRegex)) {
|
||||
const match = line.slice(i).match(dollarTagRegex);
|
||||
if (match) {
|
||||
const tag = match[0];
|
||||
if (!insideDollarTag) {
|
||||
insideDollarTag = true;
|
||||
dollarTagDelimiter = tag;
|
||||
currentQuery += tag;
|
||||
i += tag.length - 1;
|
||||
}
|
||||
else if (dollarTagDelimiter === tag) {
|
||||
insideDollarTag = false;
|
||||
dollarTagDelimiter = null;
|
||||
currentQuery += tag;
|
||||
i += tag.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check BEGIN-END blocks
|
||||
if (!insideString && !insideDollarTag) {
|
||||
if (beginRegex.test(line))
|
||||
insideBlock = true;
|
||||
|
||||
if (insideBlock && endRegex.test(line))
|
||||
insideBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the query if we encounter a semicolon outside a BEGIN-END block, outside a string, and outside dollar tags
|
||||
if (!insideBlock && !insideString && !insideDollarTag && /;\s*$/.test(line)) {
|
||||
queries.push(currentQuery.trim());
|
||||
currentQuery = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining query
|
||||
if (currentQuery.trim())
|
||||
queries.push(currentQuery.trim());
|
||||
|
||||
return queries;
|
||||
};
|
@@ -3,18 +3,161 @@
|
||||
import { lineString, point, polygon } from '@turf/helpers';
|
||||
import { BIT, BLOB, DATE, DATETIME, FLOAT, IS_MULTI_SPATIAL, NUMBER, SPATIAL, TEXT_SEARCH } from 'common/fieldTypes';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { ClientCode } from 'common/interfaces/antares';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import customizations from '../customizations';
|
||||
import { ClientCode } from '../interfaces/antares';
|
||||
import { getArrayDepth } from './getArrayDepth';
|
||||
import hexToBinary, { HexChar } from './hexToBinary';
|
||||
|
||||
/**
|
||||
* Escapes a string fo SQL use
|
||||
* Splits a SQL string into multiple queries based on semicolons (;).
|
||||
* Handles BEGIN-END blocks, strings, comments, and PostgreSQL dollar-quoted tags.
|
||||
*
|
||||
* @param { String } string
|
||||
* @returns { String } Escaped string
|
||||
* @param {string} sql - The SQL string to split.
|
||||
* @param {ClientCode} dbType - The database type (e.g., 'pg', 'mysql').
|
||||
* @returns {string[]} - An array of separated SQL queries.
|
||||
*/
|
||||
export const querySplitter =(sql: string, dbType: ClientCode): string[] => {
|
||||
const queries: string[] = [];
|
||||
let currentQuery = '';
|
||||
let insideBlock = false;
|
||||
let insideString = false;
|
||||
let stringDelimiter: string | null = null;
|
||||
let insideDollarTag = false;
|
||||
let dollarTagDelimiter: string | null = null;
|
||||
|
||||
// Regex patterns for BEGIN-END blocks, dollar tags in PostgreSQL, and semicolons
|
||||
const beginRegex = /\bBEGIN\b/i;
|
||||
const endRegex = /\bEND\b;/i;
|
||||
const dollarTagRegex = /\$(\w+)?\$/; // Matches $tag$ or $$
|
||||
|
||||
// Split on semicolons, keeping semicolons attached to the lines
|
||||
const lines = sql.split(/(?<=;)/);
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
if (!line) continue;
|
||||
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
const char = line[i];
|
||||
|
||||
// Handle string boundaries
|
||||
if ((char === '\'' || char === '"') && (!insideString || char === stringDelimiter)) {
|
||||
if (!insideString) {
|
||||
insideString = true;
|
||||
stringDelimiter = char;
|
||||
}
|
||||
else {
|
||||
insideString = false;
|
||||
stringDelimiter = null;
|
||||
}
|
||||
}
|
||||
|
||||
currentQuery += char;
|
||||
|
||||
if (dbType === 'pg') {
|
||||
// Handle dollar-quoted blocks in PostgreSQL
|
||||
if (!insideString && line.slice(i).match(dollarTagRegex)) {
|
||||
const match = line.slice(i).match(dollarTagRegex);
|
||||
if (match) {
|
||||
const tag = match[0];
|
||||
if (!insideDollarTag) {
|
||||
insideDollarTag = true;
|
||||
dollarTagDelimiter = tag;
|
||||
currentQuery += tag;
|
||||
i += tag.length - 1;
|
||||
}
|
||||
else if (dollarTagDelimiter === tag) {
|
||||
insideDollarTag = false;
|
||||
dollarTagDelimiter = null;
|
||||
currentQuery += tag;
|
||||
i += tag.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check BEGIN-END blocks
|
||||
if (!insideString && !insideDollarTag) {
|
||||
if (beginRegex.test(line))
|
||||
insideBlock = true;
|
||||
|
||||
if (insideBlock && endRegex.test(line))
|
||||
insideBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append the query if we encounter a semicolon outside a BEGIN-END block, outside a string, and outside dollar tags
|
||||
if (!insideBlock && !insideString && !insideDollarTag && /;\s*$/.test(line)) {
|
||||
queries.push(currentQuery.trim());
|
||||
currentQuery = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining query
|
||||
if (currentQuery.trim())
|
||||
queries.push(currentQuery.trim());
|
||||
|
||||
return queries;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all comments (both single-line and multi-line) from a SQL string.
|
||||
*
|
||||
* @param {string} sql - The SQL string to process.
|
||||
* @returns {string} - The SQL string without comments.
|
||||
*/
|
||||
export const removeComments = (sql: string): string => {
|
||||
let result = '';
|
||||
let insideSingleLineComment = false;
|
||||
let insideMultiLineComment = false;
|
||||
|
||||
for (let i = 0; i < sql.length; i++) {
|
||||
const char = sql[i];
|
||||
const nextChar = sql[i + 1] || '';
|
||||
|
||||
// Handle single-line comments (--)
|
||||
if (!insideMultiLineComment && char === '-' && nextChar === '-')
|
||||
insideSingleLineComment = true;
|
||||
|
||||
// Handle multi-line comments (/* */)
|
||||
if (!insideSingleLineComment && char === '/' && nextChar === '*') {
|
||||
insideMultiLineComment = true;
|
||||
i++; // Skip the '*' character
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideMultiLineComment && char === '*' && nextChar === '/') {
|
||||
insideMultiLineComment = false;
|
||||
i++; // Skip the '/' character
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip characters inside comments
|
||||
if (insideSingleLineComment) {
|
||||
if (char === '\n')
|
||||
insideSingleLineComment = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideMultiLineComment)
|
||||
continue;
|
||||
|
||||
// Append non-comment characters to the result
|
||||
result += char;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Escapes a string for safe use in SQL queries.
|
||||
*
|
||||
* @param {string} string - The string to escape.
|
||||
* @returns {string} - The escaped string.
|
||||
*/
|
||||
export const sqlEscaper = (string: string): string => {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
@@ -27,6 +170,12 @@ export const sqlEscaper = (string: string): string => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a value into a GeoJSON object based on its type.
|
||||
*
|
||||
* @param {any} val - The value to convert.
|
||||
* @returns {object} - The generated GeoJSON object.
|
||||
*/
|
||||
export const objectToGeoJSON = (val: any) => {
|
||||
if (Array.isArray(val)) {
|
||||
if (getArrayDepth(val) === 1)
|
||||
@@ -38,6 +187,13 @@ export const objectToGeoJSON = (val: any) => {
|
||||
return point([val.x, val.y]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Escapes and wraps a string in quotes for safe use in SQL queries.
|
||||
*
|
||||
* @param {string} val - The string to process.
|
||||
* @param {ClientCode} client - The database type (e.g., 'pg', 'mysql').
|
||||
* @returns {string} - The escaped and quoted string.
|
||||
*/
|
||||
export const escapeAndQuote = (val: string, client: ClientCode) => {
|
||||
const { stringsWrapper: sw } = customizations[client];
|
||||
// eslint-disable-next-line no-control-regex
|
||||
@@ -74,11 +230,17 @@ export const escapeAndQuote = (val: string, client: ClientCode) => {
|
||||
return `${sw}${escapedVal}${sw}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a value into a SQL string based on the field type and database type.
|
||||
*
|
||||
* @param {object} args - Arguments containing the value, database type, and field type.
|
||||
* @returns {string} - The generated SQL string.
|
||||
*/
|
||||
export const valueToSqlString = (args: {
|
||||
val: any;
|
||||
client: ClientCode;
|
||||
field: {type: string; datePrecision?: number; precision?: number | false; isArray?: boolean};
|
||||
}): string => {
|
||||
val: any;
|
||||
client: ClientCode;
|
||||
field: { type: string; datePrecision?: number; precision?: number | false; isArray?: boolean };
|
||||
}): string => {
|
||||
let parsedValue;
|
||||
const { val, client, field } = args;
|
||||
const { stringsWrapper: sw } = customizations[client];
|
||||
@@ -165,13 +327,19 @@ export const valueToSqlString = (args: {
|
||||
return parsedValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a JSON array into an SQL INSERT query.
|
||||
*
|
||||
* @param {object} args - Arguments containing the JSON data, database type, fields, and options.
|
||||
* @returns {string} - The generated SQL INSERT query.
|
||||
*/
|
||||
export const jsonToSqlInsert = (args: {
|
||||
json: Record<string, any>[];
|
||||
client: ClientCode;
|
||||
fields: Record<string, {type: string; datePrecision: number}>;
|
||||
table: string;
|
||||
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
|
||||
}) => {
|
||||
json: Record<string, any>[];
|
||||
client: ClientCode;
|
||||
fields: Record<string, { type: string; datePrecision: number }>;
|
||||
table: string;
|
||||
options?: { sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows' };
|
||||
}) => {
|
||||
const { client, json, fields, table, options } = args;
|
||||
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
|
||||
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
|
||||
@@ -193,7 +361,7 @@ export const jsonToSqlInsert = (args: {
|
||||
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
|
||||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||
) {
|
||||
insertsString += insertStmt+';';
|
||||
insertsString += insertStmt + ';';
|
||||
insertStmt = `\nINSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
||||
rowsWritten = 0;
|
||||
}
|
||||
@@ -206,11 +374,18 @@ export const jsonToSqlInsert = (args: {
|
||||
}
|
||||
|
||||
if (rowsWritten > 0)
|
||||
insertsString += insertStmt+';';
|
||||
insertsString += insertStmt + ';';
|
||||
|
||||
return insertsString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a JSON value for use in an SQL WHERE clause.
|
||||
*
|
||||
* @param {object} jsonValue - The JSON value to format.
|
||||
* @param {ClientCode} clientType - The database type (e.g., 'pg', 'mysql').
|
||||
* @returns {string} - The formatted SQL WHERE clause.
|
||||
*/
|
||||
export const formatJsonForSqlWhere = (jsonValue: object, clientType: antares.ClientCode) => {
|
||||
const formattedValue = JSON.stringify(jsonValue);
|
||||
|
||||
|
@@ -64,9 +64,9 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
username: conn.sshUser,
|
||||
password: conn.sshPass,
|
||||
port: conn.sshPort ? conn.sshPort : 22,
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null,
|
||||
privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : undefined,
|
||||
passphrase: conn.sshPassphrase,
|
||||
keepaliveInterval: conn.sshKeepAliveInterval ? conn.sshKeepAliveInterval*1000 : null
|
||||
keepaliveInterval: conn.sshKeepAliveInterval ? conn.sshKeepAliveInterval*1000 : undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -90,11 +90,12 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (err) {
|
||||
catch (error) {
|
||||
clearInterval(abortChecker);
|
||||
|
||||
if (!isLocalAborted)
|
||||
return { status: 'error', response: err.toString() };
|
||||
if (error instanceof AggregateError)
|
||||
throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
|
||||
else if (!isLocalAborted)
|
||||
return { status: 'error', response: error.toString() };
|
||||
else
|
||||
return { status: 'abort', response: 'Connection aborted' };
|
||||
}
|
||||
|
@@ -183,6 +183,7 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
const result = await connections[uid].raw(query, {
|
||||
nest: true,
|
||||
details: true,
|
||||
comments: false,
|
||||
schema,
|
||||
tabUid,
|
||||
autocommit
|
||||
|
@@ -135,7 +135,7 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
try { // TODO: move to client classes
|
||||
let escapedParam;
|
||||
let reload = false;
|
||||
const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
|
||||
const id = typeof params.id === 'number' ? params.id : `${sw}${sqlEscaper(params.id)}${sw}`;
|
||||
|
||||
if ([...NUMBER, ...FLOAT].includes(params.type))
|
||||
escapedParam = params.content;
|
||||
@@ -233,7 +233,7 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
|
||||
for (const key in orgRow) {
|
||||
if (typeof orgRow[key] === 'string')
|
||||
orgRow[key] = ` = '${orgRow[key]}'`;
|
||||
orgRow[key] = ` = '${sqlEscaper(orgRow[key])}'`;
|
||||
else if (typeof orgRow[key] === 'object' && orgRow[key] !== null)
|
||||
orgRow[key] = formatJsonForSqlWhere(orgRow[key], connections[params.uid]._client);
|
||||
else if (orgRow[key] === null)
|
||||
@@ -290,7 +290,7 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
for (const row of params.rows) {
|
||||
for (const key in row) {
|
||||
if (typeof row[key] === 'string')
|
||||
row[key] = `'${row[key]}'`;
|
||||
row[key] = `'${sqlEscaper(row[key])}'`;
|
||||
|
||||
if (row[key] === null)
|
||||
row[key] = 'IS NULL';
|
||||
@@ -440,16 +440,17 @@ export default (connections: Record<string, antares.Client>) => {
|
||||
|
||||
ipcMain.handle('get-foreign-list', async (event, { uid, schema, table, column, description }) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
const { elementsWrapper: ew } = customizations[connections[uid]._client];
|
||||
|
||||
try {
|
||||
const query = connections[uid]
|
||||
.select(`${column} AS foreign_column`)
|
||||
.select(`${ew}${column}${ew} AS foreign_column`)
|
||||
.schema(schema)
|
||||
.from(table)
|
||||
.orderBy('foreign_column ASC');
|
||||
|
||||
if (description)
|
||||
query.select(`LEFT(${description}, 20) AS foreign_description`);
|
||||
query.select(`LEFT(${ew}${description}${ew}, 20) AS foreign_description`);
|
||||
|
||||
const results = await query.run<Record<string, string>>();
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { querySplitter } from 'common/libs/sqlUtils';
|
||||
import mysql from 'mysql2/promise';
|
||||
import * as pg from 'pg';
|
||||
import SSH2Promise = require('@fabio286/ssh2-promise');
|
||||
import { querySplitter } from 'common/libs/querySplitter';
|
||||
|
||||
import { ipcLogger, LoggerLevel } from '../misc/ipcLogger';
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import dataTypes from 'common/data-types/firebird';
|
||||
import { FLOAT, NUMBER } from 'common/fieldTypes';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { removeComments } from 'common/libs/sqlUtils';
|
||||
import * as firebird from 'node-firebird';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -1036,7 +1037,7 @@ export class FirebirdSQLClient extends BaseClient {
|
||||
};
|
||||
|
||||
if (!args.comments)
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
sql = removeComments(sql);
|
||||
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
|
@@ -2,6 +2,7 @@ import SSH2Promise = require('@fabio286/ssh2-promise');
|
||||
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
|
||||
import dataTypes from 'common/data-types/mysql';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { removeComments } from 'common/libs/sqlUtils';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
|
||||
import * as EncodingToCharset from '../../../../node_modules/mysql2/lib/constants/encoding_charset.js';
|
||||
@@ -173,13 +174,13 @@ export class MySQLClient extends BaseClient {
|
||||
remotePort: this._params.port
|
||||
});
|
||||
|
||||
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
|
||||
dbConfig.host = undefined;
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
if (this._ssh) {
|
||||
this._ssh.close();
|
||||
this._ssh.closeTunnel();
|
||||
this._ssh.close();
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
@@ -227,8 +228,8 @@ export class MySQLClient extends BaseClient {
|
||||
clearInterval(this._keepaliveTimer);
|
||||
this._keepaliveTimer = undefined;
|
||||
if (this._ssh) {
|
||||
this._ssh.close();
|
||||
this._ssh.closeTunnel();
|
||||
this._ssh.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +303,8 @@ export class MySQLClient extends BaseClient {
|
||||
await this.connect();
|
||||
return this.getConnection(args, true);
|
||||
}
|
||||
else if (error instanceof AggregateError)
|
||||
throw new Error(error.errors.reduce((acc, curr) => acc +' | '+ curr.message, ''));
|
||||
else
|
||||
throw new Error(error.message);
|
||||
}
|
||||
@@ -693,7 +696,12 @@ export class MySQLClient extends BaseClient {
|
||||
return rows.length ? rows[0].count : 0;
|
||||
}
|
||||
|
||||
async getTableChecks ({ schema, table }: { schema: string; table: string }): Promise<antares.TableCheck[]> {
|
||||
async getTableChecks ({ schema, table }: { schema: string; table: string }): Promise<antares.TableCheck[] | false> {
|
||||
const { rows: checkTableExists } = await this.raw('SELECT table_name FROM information_schema.tables WHERE table_schema = "information_schema" AND table_name = "CHECK_CONSTRAINTS"');
|
||||
|
||||
if (!checkTableExists.length)// check if CHECK_CONSTRAINTS table exists
|
||||
return false;
|
||||
|
||||
const { rows } = await this.raw(`
|
||||
SELECT
|
||||
CONSTRAINT_NAME as name,
|
||||
@@ -1749,7 +1757,7 @@ export class MySQLClient extends BaseClient {
|
||||
};
|
||||
|
||||
if (!args.comments)
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
sql = removeComments(sql);
|
||||
|
||||
const nestTables = args.nest ? '.' : false;
|
||||
const resultsArr: antares.QueryResult[] = [];
|
||||
|
@@ -2,6 +2,7 @@ import SSH2Promise = require('@fabio286/ssh2-promise');
|
||||
import SSHConfig from '@fabio286/ssh2-promise/lib/sshConfig';
|
||||
import dataTypes from 'common/data-types/postgresql';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { removeComments } from 'common/libs/sqlUtils';
|
||||
import * as pg from 'pg';
|
||||
import * as pgAst from 'pgsql-ast-parser';
|
||||
import { ConnectionOptions } from 'tls';
|
||||
@@ -179,7 +180,7 @@ export class PostgreSQLClient extends BaseClient {
|
||||
remotePort: this._params.port
|
||||
});
|
||||
|
||||
dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host;
|
||||
dbConfig.host = undefined;
|
||||
dbConfig.port = tunnel.localPort;
|
||||
}
|
||||
catch (err) {
|
||||
@@ -348,7 +349,7 @@ export class PostgreSQLClient extends BaseClient {
|
||||
matviewowner AS owner,
|
||||
ispopulated AS is_populated,
|
||||
definition,
|
||||
'materializedview' AS table_type
|
||||
'materializedView' AS table_type
|
||||
FROM pg_matviews
|
||||
WHERE schemaname = '${db.database}'
|
||||
ORDER BY schema_name,
|
||||
@@ -408,8 +409,8 @@ export class PostgreSQLClient extends BaseClient {
|
||||
name: table.table_name,
|
||||
type: table.table_type === 'VIEW'
|
||||
? 'view'
|
||||
: table.table_type === 'materializedview'
|
||||
? 'materializedview'
|
||||
: table.table_type === 'materializedView'
|
||||
? 'materializedView'
|
||||
: 'table',
|
||||
rows: table.reltuples,
|
||||
size: tableSize,
|
||||
@@ -1660,7 +1661,7 @@ export class PostgreSQLClient extends BaseClient {
|
||||
};
|
||||
|
||||
if (!args.comments)
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
sql = removeComments(sql);
|
||||
|
||||
const resultsArr: antares.QueryResult[] = [];
|
||||
let paramsArr = [];
|
||||
|
@@ -2,6 +2,7 @@ import * as sqlite from 'better-sqlite3';
|
||||
import dataTypes from 'common/data-types/sqlite';
|
||||
import { DATETIME, FLOAT, NUMBER, TIME } from 'common/fieldTypes';
|
||||
import * as antares from 'common/interfaces/antares';
|
||||
import { removeComments } from 'common/libs/sqlUtils';
|
||||
|
||||
import { BaseClient } from './BaseClient';
|
||||
|
||||
@@ -624,7 +625,7 @@ export class SQLiteClient extends BaseClient {
|
||||
};
|
||||
|
||||
if (!args.comments)
|
||||
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
|
||||
sql = removeComments(sql);
|
||||
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
|
@@ -3,7 +3,13 @@ export type LoggerLevel = 'query' | 'error'
|
||||
export const ipcLogger = ({ content, cUid, level }: {content: string; cUid: string; level: LoggerLevel}) => {
|
||||
if (level === 'error') {
|
||||
if (process.type !== undefined) {
|
||||
const mainWindow = require('electron').webContents.fromId(1);
|
||||
const contents = require('electron').webContents.getAllWebContents();
|
||||
let mainWindow = require('electron').webContents.fromId(1);
|
||||
contents.forEach(content => {
|
||||
if (content.send && mainWindow === undefined) {
|
||||
mainWindow = content;
|
||||
}
|
||||
});
|
||||
mainWindow.send('non-blocking-exception', { cUid, message: content, date: new Date() });
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(content);
|
||||
@@ -12,7 +18,13 @@ export const ipcLogger = ({ content, cUid, level }: {content: string; cUid: stri
|
||||
// Remove comments, newlines and multiple spaces
|
||||
const escapedSql = content.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
|
||||
if (process.type !== undefined) {
|
||||
const mainWindow = require('electron').webContents.fromId(1);
|
||||
const contents = require('electron').webContents.getAllWebContents();
|
||||
let mainWindow = require('electron').webContents.fromId(1);
|
||||
contents.forEach(content => {
|
||||
if (content.send && mainWindow === undefined) {
|
||||
mainWindow = content;
|
||||
}
|
||||
});
|
||||
mainWindow.send('query-log', { cUid, sql: escapedSql, date: new Date() });
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development' && process.type === 'browser') console.log(escapedSql);
|
||||
|
@@ -132,11 +132,21 @@ app.on('ready', async () => {
|
||||
// mainWindow.webContents.openDevTools();
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
if (error instanceof AggregateError) {
|
||||
for (const e of error.errors)
|
||||
mainWindow.webContents.send('unhandled-exception', e);
|
||||
}
|
||||
else
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
if (error instanceof AggregateError) {
|
||||
for (const e of error.errors)
|
||||
mainWindow.webContents.send('unhandled-exception', e);
|
||||
}
|
||||
else
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -39,11 +39,11 @@ const props = defineProps({
|
||||
default: () => 'mdi'
|
||||
},
|
||||
flip: {
|
||||
type: String as PropType<'horizontal' | 'vertical' | 'both'>,
|
||||
type: String as PropType<'horizontal' | 'vertical' | 'both' | null>,
|
||||
default: () => null
|
||||
},
|
||||
rotate: {
|
||||
type: Number,
|
||||
type: Number as PropType<number | null>,
|
||||
default: () => null
|
||||
}
|
||||
});
|
||||
@@ -55,8 +55,7 @@ const iconPath = computed(() => {
|
||||
const base64 = getIconByUid(props.iconName)?.base64;
|
||||
const svgString = Buffer
|
||||
.from(base64, 'base64')
|
||||
.toString('utf-8')
|
||||
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
|
||||
.toString('utf-8');
|
||||
|
||||
return svgString;
|
||||
}
|
||||
|
@@ -131,8 +131,10 @@ import Application from '@/ipc-api/Application';
|
||||
import { camelize } from '@/libs/camelize';
|
||||
import { unproxify } from '@/libs/unproxify';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
|
||||
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
|
||||
const { customIcons } = storeToRefs(connectionsStore);
|
||||
@@ -225,12 +227,56 @@ const removeIconHandler = () => {
|
||||
isContext.value = false;
|
||||
};
|
||||
|
||||
const adjustSVGContent = (svgContent: string) => {
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgContent, 'image/svg+xml');
|
||||
|
||||
const parseError = doc.querySelector('parsererror');
|
||||
if (parseError) {
|
||||
addNotification({ status: 'error', message: parseError.textContent });
|
||||
return null;
|
||||
}
|
||||
|
||||
const svg = doc.documentElement;
|
||||
if (svg.tagName.toLowerCase() !== 'svg') {
|
||||
addNotification({ status: 'error', message: t('application.invalidFIle') });
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!svg.hasAttribute('viewBox')) {
|
||||
const width = svg.getAttribute('width') || '36';
|
||||
const height = svg.getAttribute('height') || '36';
|
||||
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
||||
}
|
||||
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
|
||||
const serializer = new XMLSerializer();
|
||||
return serializer.serializeToString(svg);
|
||||
}
|
||||
catch (error) {
|
||||
addNotification({ status: 'error', message: error.stack });
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openFile = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] });
|
||||
const result = await Application.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: '"SVG"', extensions: ['svg'] }]
|
||||
});
|
||||
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
const content = await Application.readFile({ filePath: file, encoding: 'base64url' });
|
||||
addIcon(content);
|
||||
let content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
|
||||
|
||||
content = adjustSVGContent(content);
|
||||
|
||||
const base64Content = Buffer.from(content).toString('base64');
|
||||
|
||||
addIcon(base64Content);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -64,7 +64,7 @@
|
||||
>
|
||||
<BaseIcon
|
||||
class="mt-1 mr-1"
|
||||
:icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
|
||||
:icon-name="['view', 'materializedView'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
|
||||
:size="18"
|
||||
/>
|
||||
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
|
||||
@@ -81,7 +81,7 @@
|
||||
<a v-else-if="element.type === 'data'" class="tab-link">
|
||||
<BaseIcon
|
||||
class="mt-1 mr-1"
|
||||
:icon-name="['view', 'materializedview'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
|
||||
:icon-name="['view', 'materializedView'].includes(element.elementType) ? 'mdiTableEye' : 'mdiTable'"
|
||||
:size="18"
|
||||
/>
|
||||
<span :title="`${t('general.data').toUpperCase()}: ${t(`database.${element.elementType}`)}`">
|
||||
|
@@ -527,14 +527,14 @@ watch(() => props.connection, () => {
|
||||
localConnection.value = JSON.parse(JSON.stringify(props.connection));
|
||||
});
|
||||
|
||||
const startConnection = async () => {
|
||||
const startConnection = async (): Promise<void> => {
|
||||
await saveConnection();
|
||||
isConnecting.value = true;
|
||||
|
||||
if (localConnection.value.ask)
|
||||
isAsking.value = true;
|
||||
else {
|
||||
await connectWorkspace(localConnection.value, { signal: abortController.value.signal }).catch(() => undefined);
|
||||
await connectWorkspace(localConnection.value, { signal: abortController.value.signal }).catch((): void => undefined);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
};
|
||||
@@ -582,7 +582,7 @@ const continueTest = async (credentials: {user: string; password: string }) => {
|
||||
try {
|
||||
if (isConnecting.value) {
|
||||
const params = Object.assign({}, props.connection, credentials);
|
||||
await connectWorkspace(params, { signal: abortController.value.signal }).catch(() => undefined);
|
||||
await connectWorkspace(params, { signal: abortController.value.signal }).catch((): void => undefined);
|
||||
isConnecting.value = false;
|
||||
}
|
||||
else {
|
||||
|
@@ -143,7 +143,7 @@
|
||||
:selected-schema="selectedSchema"
|
||||
:context-event="miscContextEvent"
|
||||
@open-create-view-tab="openCreateElementTab('view')"
|
||||
@open-create-materializedview-tab="openCreateElementTab('materialized-view')"
|
||||
@open-create-materializedView-tab="openCreateElementTab('materialized-view')"
|
||||
@open-create-trigger-tab="openCreateElementTab('trigger')"
|
||||
@open-create-trigger-function-tab="openCreateElementTab('trigger-function')"
|
||||
@open-create-routine-tab="openCreateElementTab('routine')"
|
||||
|
@@ -16,9 +16,9 @@
|
||||
/> {{ t('database.createNewView') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.selectedMisc === 'materializedview'"
|
||||
v-if="props.selectedMisc === 'materializedView'"
|
||||
class="context-element"
|
||||
@click="emit('open-create-materializedview-tab')"
|
||||
@click="emit('open-create-materializedView-tab')"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
@@ -106,7 +106,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits([
|
||||
'open-create-view-tab',
|
||||
'open-create-materializedview-tab',
|
||||
'open-create-materializedView-tab',
|
||||
'open-create-trigger-tab',
|
||||
'open-create-routine-tab',
|
||||
'open-create-function-tab',
|
||||
|
@@ -121,7 +121,7 @@
|
||||
<summary
|
||||
class="accordion-header misc-name"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.trigger}"
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'materializedview')"
|
||||
@contextmenu.prevent="showMiscFolderContext($event, 'materializedView')"
|
||||
>
|
||||
<BaseIcon
|
||||
class="misc-icon mr-1"
|
||||
@@ -133,7 +133,7 @@
|
||||
icon-name="mdiFolderOpen"
|
||||
:size="18"
|
||||
/>
|
||||
{{ t('database.materializedview', 2) }}
|
||||
{{ t('database.materializedView', 2) }}
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
@@ -496,9 +496,9 @@ const filteredViews = computed(() => {
|
||||
|
||||
const filteredMatViews = computed(() => {
|
||||
if (props.searchMethod === 'elements')
|
||||
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedview');
|
||||
return props.database.tables.filter(table => table.name.search(searchTerm.value) >= 0 && table.type === 'materializedView');
|
||||
else
|
||||
return props.database.tables.filter(table => table.type === 'materializedview');
|
||||
return props.database.tables.filter(table => table.type === 'materializedView');
|
||||
});
|
||||
|
||||
const filteredTriggers = computed(() => {
|
||||
|
@@ -50,7 +50,7 @@
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiTableEye"
|
||||
:size="18"
|
||||
/> {{ t('database.materializedview') }}</span>
|
||||
/> {{ t('database.materializedView') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="workspace.customizations.triggerAdd"
|
||||
|
@@ -48,7 +48,7 @@
|
||||
/> {{ t('application.settings') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedTable && selectedTable.type === 'materializedview' && customizations.materializedViewSettings"
|
||||
v-if="selectedTable && selectedTable.type === 'materializedView' && customizations.materializedViewSettings"
|
||||
class="context-element"
|
||||
@click="openMaterializedViewSettingTab"
|
||||
>
|
||||
|
@@ -195,7 +195,7 @@ const saveChanges = async () => {
|
||||
uid: props.connection.uid,
|
||||
schema: props.schema,
|
||||
elementName: localView.value.name,
|
||||
elementType: 'materializedview',
|
||||
elementType: 'materializedView',
|
||||
type: 'materialized-view-props'
|
||||
});
|
||||
|
||||
|
@@ -227,7 +227,7 @@ const saveChanges = async () => {
|
||||
schema: props.schema,
|
||||
elementName: oldName,
|
||||
elementNewName: localView.value.name,
|
||||
elementType: 'materializedview'
|
||||
elementType: 'materializedView'
|
||||
});
|
||||
|
||||
changeBreadcrumbs({ schema: props.schema, view: localView.value.name });
|
||||
|
@@ -73,7 +73,7 @@
|
||||
<span>{{ t('database.foreignKeys') }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="workspace.customizations.tableCheck"
|
||||
v-if="workspace.customizations.tableCheck && originalTableChecks !== false"
|
||||
class="btn btn-dark btn-sm ml-2 mr-0"
|
||||
:disabled="isSaving"
|
||||
:title="t('database.manageTableChecks')"
|
||||
@@ -234,7 +234,7 @@
|
||||
/>
|
||||
<WorkspaceTabPropsTableChecksModal
|
||||
v-if="isTableChecksModal"
|
||||
:local-checks="localTableChecks"
|
||||
:local-checks="localTableChecks || []"
|
||||
:table="table"
|
||||
:workspace="workspace"
|
||||
@hide="hideTableChecksModal"
|
||||
@@ -305,8 +305,8 @@ const originalKeyUsage: Ref<TableForeign[]> = ref([]);
|
||||
const localKeyUsage: Ref<TableForeign[]> = ref([]);
|
||||
const originalIndexes: Ref<TableIndex[]> = ref([]);
|
||||
const localIndexes: Ref<TableIndex[]> = ref([]);
|
||||
const originalTableChecks: Ref<TableCheck[]> = ref([]);
|
||||
const localTableChecks: Ref<TableCheck[]> = ref([]);
|
||||
const originalTableChecks: Ref<TableCheck[] | false> = ref([]);
|
||||
const localTableChecks: Ref<TableCheck[] | false> = ref(false);
|
||||
const tableOptions: Ref<TableOptions> = ref(null);
|
||||
const localOptions: Ref<TableOptions> = ref({} as TableOptions);
|
||||
const lastTable = ref(null);
|
||||
@@ -465,13 +465,19 @@ const getFieldsData = async () => {
|
||||
const { status, response } = await Tables.getTableChecks(params);
|
||||
|
||||
if (status === 'success') {
|
||||
originalTableChecks.value = response.map((check: TableCheck) => {
|
||||
return {
|
||||
_antares_id: uidGen(),
|
||||
...check
|
||||
};
|
||||
});
|
||||
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
|
||||
if (response === false) {
|
||||
originalTableChecks.value = false;
|
||||
localTableChecks.value = false;
|
||||
}
|
||||
else {
|
||||
originalTableChecks.value = response.map((check: TableCheck) => {
|
||||
return {
|
||||
_antares_id: uidGen(),
|
||||
...check
|
||||
};
|
||||
});
|
||||
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
|
||||
}
|
||||
}
|
||||
else
|
||||
addNotification({ status: 'error', message: response });
|
||||
@@ -576,32 +582,68 @@ const saveChanges = async () => {
|
||||
// Foreigns Deletions
|
||||
foreignChanges.deletions = originalKeyUsage.value.filter(foreign => !localForeignIDs.includes(foreign._antares_id));
|
||||
|
||||
// CHECKS
|
||||
if (originalTableChecks.value !== false && localTableChecks.value !== false) {
|
||||
const checkChanges = {
|
||||
additions: [] as TableCheck[],
|
||||
changes: [] as TableCheck[],
|
||||
deletions: [] as TableCheck[]
|
||||
};
|
||||
const originalCheckIDs = originalTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localCheckIDs = localTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Check Additions
|
||||
checkChanges.additions = localTableChecks.value.filter(check => !originalCheckIDs.includes(check._antares_id));
|
||||
|
||||
// Check Changes
|
||||
originalTableChecks.value.forEach(originalCheck => {
|
||||
const lI = Array.isArray(localTableChecks.value)
|
||||
? localTableChecks.value.findIndex(localCheck => localCheck._antares_id === originalCheck._antares_id)
|
||||
: -1;
|
||||
if (Array.isArray(localTableChecks.value) && JSON.stringify(originalCheck) !== JSON.stringify(localTableChecks.value[lI])) {
|
||||
if (localTableChecks.value[lI]) {
|
||||
checkChanges.changes.push({
|
||||
...localTableChecks.value[lI]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check Deletions
|
||||
checkChanges.deletions = originalTableChecks.value.filter(check => !localCheckIDs.includes(check._antares_id));
|
||||
}
|
||||
|
||||
// CHECKS
|
||||
const checkChanges = {
|
||||
additions: [] as TableCheck[],
|
||||
changes: [] as TableCheck[],
|
||||
deletions: [] as TableCheck[]
|
||||
};
|
||||
const originalCheckIDs = originalTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localCheckIDs = localTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Check Additions
|
||||
checkChanges.additions = localTableChecks.value.filter(check => !originalCheckIDs.includes(check._antares_id));
|
||||
if (originalTableChecks.value !== false && localTableChecks.value !== false) {
|
||||
const originalCheckIDs = originalTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
const localCheckIDs = localTableChecks.value.reduce((acc, curr) => [...acc, curr._antares_id], []);
|
||||
|
||||
// Check Changes
|
||||
originalTableChecks.value.forEach(originalCheck => {
|
||||
const lI = localTableChecks.value.findIndex(localCheck => localCheck._antares_id === originalCheck._antares_id);
|
||||
if (JSON.stringify(originalCheck) !== JSON.stringify(localTableChecks.value[lI])) {
|
||||
if (localTableChecks.value[lI]) {
|
||||
checkChanges.changes.push({
|
||||
...localTableChecks.value[lI]
|
||||
});
|
||||
// Check Additions
|
||||
checkChanges.additions = localTableChecks.value.filter(check => !originalCheckIDs.includes(check._antares_id));
|
||||
|
||||
// Check Changes
|
||||
originalTableChecks.value.forEach(originalCheck => {
|
||||
const lI = Array.isArray(localTableChecks.value)
|
||||
? localTableChecks.value.findIndex(localCheck => localCheck._antares_id === originalCheck._antares_id)
|
||||
: -1;
|
||||
if (Array.isArray(localTableChecks.value) && JSON.stringify(originalCheck) !== JSON.stringify(localTableChecks.value[lI])) {
|
||||
if (localTableChecks.value[lI]) {
|
||||
checkChanges.changes.push({
|
||||
...localTableChecks.value[lI]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Check Deletions
|
||||
checkChanges.deletions = originalTableChecks.value.filter(check => !localCheckIDs.includes(check._antares_id));
|
||||
// Check Deletions
|
||||
checkChanges.deletions = originalTableChecks.value.filter(check => !localCheckIDs.includes(check._antares_id));
|
||||
}
|
||||
|
||||
// ALTER
|
||||
const params = {
|
||||
|
@@ -141,7 +141,7 @@ export const csCZ = {
|
||||
total: 'Celkem',
|
||||
table: 'Tabulka | Tabulky',
|
||||
view: 'Pohled | Pohledy',
|
||||
materializedview: 'Materializovaný pohled',
|
||||
materializedView: 'Materializovaný pohled',
|
||||
definer: 'Definér',
|
||||
algorithm: 'Algoritmus',
|
||||
trigger: 'Trigger | Triggery',
|
||||
|
@@ -141,7 +141,7 @@ export const enUS = {
|
||||
total: 'Total',
|
||||
table: 'Table | Tables',
|
||||
view: 'View | Views',
|
||||
materializedview: 'Materialized view | Materialized views',
|
||||
materializedView: 'Materialized view | Materialized views',
|
||||
definer: 'Definer',
|
||||
algorithm: 'Algorithm',
|
||||
trigger: 'Trigger | Triggers',
|
||||
@@ -401,6 +401,7 @@ export const enUS = {
|
||||
ignoreDuplicates: 'Ignore duplicates',
|
||||
wrongImportPassword: 'Wrong import password',
|
||||
wrongFileFormat: 'Wrong file format',
|
||||
invalidFile: 'Invalid file',
|
||||
dataImportSuccess: 'Data successfully imported',
|
||||
note: 'Note | Notes',
|
||||
thereAreNoNotesYet: 'There are no notes yet',
|
||||
|
@@ -36,7 +36,7 @@ export const esES = {
|
||||
stay: 'Mantener',
|
||||
author: 'Autor',
|
||||
upload: 'Subir',
|
||||
browse: 'Navegar',
|
||||
browse: 'Explorar',
|
||||
content: 'Contenido',
|
||||
cut: 'Cortar',
|
||||
copy: 'Copiar',
|
||||
@@ -91,7 +91,7 @@ export const esES = {
|
||||
user: 'Usuario',
|
||||
password: 'Contraseña',
|
||||
credentials: 'Credenciales',
|
||||
connect: 'Connectar',
|
||||
connect: 'Conectar',
|
||||
connected: 'Conectado',
|
||||
disconnect: 'Desconectar',
|
||||
disconnected: 'Desconectado',
|
||||
@@ -117,7 +117,7 @@ export const esES = {
|
||||
readOnlyMode: 'Solo lectura',
|
||||
allConnections: 'Todas las conexiones',
|
||||
searchForConnections: 'Buscar por conexiones',
|
||||
keepAliveInterval: 'Tiempo de mantenimiento de conexión',
|
||||
keepAliveInterval: 'Mantenimiento de conexión',
|
||||
singleConnection: 'Conexión única'
|
||||
},
|
||||
database: { // Database related terms
|
||||
@@ -141,7 +141,7 @@ export const esES = {
|
||||
total: 'Total',
|
||||
table: 'Tabla | Tablas',
|
||||
view: 'Vista | Vistas',
|
||||
materializedview: 'Vista Materializada | Vistas Materializadas',
|
||||
materializedView: 'Vista Materializada | Vistas Materializadas',
|
||||
definer: 'Definidor',
|
||||
algorithm: 'Algoritmo',
|
||||
trigger: 'Disparador | Disparadores',
|
||||
@@ -198,11 +198,11 @@ export const esES = {
|
||||
onUpdate: 'On UPDATE',
|
||||
deleteField: 'Eliminar campo',
|
||||
createNewIndex: 'Crear nuevo índice',
|
||||
createNewCheck: 'Crear nueva verificación',
|
||||
createNewCheck: 'Crear nueva validación',
|
||||
checkClause: 'Comprobar cláusula',
|
||||
addToIndex: 'Añadir al índice',
|
||||
createNewTable: 'Crear nueva tabla',
|
||||
emptyTable: 'Tabla vacía',
|
||||
emptyTable: 'Vaciar tabla',
|
||||
duplicateTable: 'Duplicar tabla',
|
||||
deleteTable: 'Eliminar tabla',
|
||||
exportTable: 'Exportar tabla',
|
||||
@@ -376,10 +376,10 @@ export const esES = {
|
||||
newFolder: 'Crear nueva carpeta',
|
||||
outOfFolder: 'Fuera de la carpeta',
|
||||
editConnectionAppearance: 'Modificar apariencia de conexión',
|
||||
defaultCopyType: 'Default copy type',
|
||||
defaultCopyType: 'Copiar fichero por defecto',
|
||||
showTableSize: 'Mostrar tamaño de tabla en la barra lateral',
|
||||
showTableSizeDescription: 'Solo para MySQL/MariaDB. Habilitar esta opción puede afectar al rendimiento en esquemas con muchas tablas.',
|
||||
switchSearchMethod: 'Switch search method',
|
||||
switchSearchMethod: 'Cambiar método de búsqueda',
|
||||
phpArray: 'Array de PHP',
|
||||
closeAllTabs: 'Cerrar todas las pestañas',
|
||||
closeOtherTabs: 'Cerrar las otras pestañas',
|
||||
@@ -396,27 +396,32 @@ export const esES = {
|
||||
importDataExplanation: 'Importará un fichero con extensión .antares que contiene conexiones. Necesitará la contraseña con la que se encriptó el mismo.',
|
||||
includeConnectionPasswords: 'Incluir contraseñas de conexión',
|
||||
includeFolders: 'Incluir carpetas',
|
||||
encryptionPassword: 'Encryption password',
|
||||
encryptionPasswordError: 'The encryption password must be at least 8 characters long.',
|
||||
ignoreDuplicates: 'Ignore duplicates',
|
||||
wrongImportPassword: 'Wrong import password',
|
||||
wrongFileFormat: 'Wrong file format',
|
||||
dataImportSuccess: 'Data successfully imported',
|
||||
note: 'Note | Notes',
|
||||
thereAreNoNotesYet: 'There are no notes yet',
|
||||
addNote: 'Add note',
|
||||
editNote: 'Edit note',
|
||||
saveAsNote: 'Save as note',
|
||||
showArchivedNotes: 'Show archived notes',
|
||||
hideArchivedNotes: 'Hide archived notes',
|
||||
tag: 'Tag', // Note tag,
|
||||
saveFile: 'Save file',
|
||||
saveFileAs: 'Save file as',
|
||||
openFile: 'Open file',
|
||||
openNotes: 'Open notes',
|
||||
debugConsole: 'Debug console', // <- console tab name
|
||||
executedQueries: 'Executed queries', // <- console tab name
|
||||
sizeLimitError: 'Maximum size of {size} exceeded'
|
||||
encryptionPassword: 'Contraseña de encriptado',
|
||||
encryptionPasswordError: 'La contraseña de encriptado debe tener al menos 8 caracteres',
|
||||
ignoreDuplicates: 'Ignorar duplicados',
|
||||
wrongImportPassword: 'Contraseña de importación errónea',
|
||||
wrongFileFormat: 'Formato de fichero erróneo',
|
||||
invalidFile: 'Fichero no válido',
|
||||
dataImportSuccess: 'Datos importados correctamente',
|
||||
note: 'Nota | Notas',
|
||||
thereAreNoNotesYet: 'No hay notas',
|
||||
addNote: 'Nueva nota',
|
||||
editNote: 'Modificar nota',
|
||||
saveAsNote: 'Guardar como nota',
|
||||
showArchivedNotes: 'Mostrar notas archivadas',
|
||||
hideArchivedNotes: 'Ocultar notas archivadas',
|
||||
tag: 'Etiqueta', // Note tag,
|
||||
saveFile: 'Guardar fichero',
|
||||
saveFileAs: 'Guardar fichero como...',
|
||||
openFile: 'Abrir fichero',
|
||||
openNotes: 'Abrir notas',
|
||||
debugConsole: 'Consola de Depuración', // <- console tab name
|
||||
executedQueries: 'Consultas realizadas', // <- console tab name
|
||||
sizeLimitError: 'Tamaño maximo de {size} excedido',
|
||||
fullScreen: 'Pantalla completa',
|
||||
zoomIn: 'Mas zoom',
|
||||
zoomOut: 'Menos zoom',
|
||||
zoomReset: 'Restablecer zoom'
|
||||
},
|
||||
faker: { // Faker.js methods, used in random generated content
|
||||
address: 'Dirección',
|
||||
@@ -569,12 +574,12 @@ export const esES = {
|
||||
alphaNumeric: 'Alfanumérico',
|
||||
hexaDecimal: 'Hexadecimal',
|
||||
fileName: 'Nombre de fichero',
|
||||
commonFileName: 'Common file name',
|
||||
commonFileName: 'Nombre de fichero común',
|
||||
mimeType: 'Mime-Type',
|
||||
commonFileType: 'Common file type',
|
||||
commonFileExt: 'Common file extension',
|
||||
commonFileType: 'Tipo de fichero común',
|
||||
commonFileExt: 'Extensión de fichero común',
|
||||
fileType: 'Tipo de fichero',
|
||||
fileExt: 'Extension de fichero',
|
||||
fileExt: 'Extensión de fichero',
|
||||
directoryPath: 'Ruta de directorio',
|
||||
filePath: 'Ruta de fichero',
|
||||
semver: 'SemVer',
|
||||
|
@@ -140,7 +140,7 @@ export const heIL = {
|
||||
total: 'סך הכל',
|
||||
table: 'טבלה | טבלאות',
|
||||
view: 'תצוגה | תצוגות',
|
||||
materializedview: 'תצוגה ממומשת | תצוגות ממומשות',
|
||||
materializedView: 'תצוגה ממומשת | תצוגות ממומשות',
|
||||
definer: 'מגדיר',
|
||||
algorithm: 'אלגוריתם',
|
||||
trigger: 'טריגר | טריגרים',
|
||||
|
@@ -275,7 +275,7 @@ export const nlNL = {
|
||||
savedQueries: 'Opgeslagen queries',
|
||||
searchForElements: 'Zoek naar elementen',
|
||||
searchForSchemas: 'Zoek naar schema\'s',
|
||||
materializedview: 'Materialized view | Materialized views',
|
||||
materializedView: 'Materialized view | Materialized views',
|
||||
createNewMaterializedView: 'Materialized view maken',
|
||||
newMaterializedView: 'Nieuwe materialized view'
|
||||
},
|
||||
|
@@ -270,7 +270,7 @@ export const ruRU = {
|
||||
importQueryErrors: 'Внимание: {n} ошибка возникла | Внимание: {n} ошибок произошло',
|
||||
executedQueries: '{n} запрос выполнен | {n} запросов выполнено',
|
||||
insert: 'Вставить',
|
||||
materializedview: 'Материализованное представление | Материализованные представления',
|
||||
materializedView: 'Материализованное представление | Материализованные представления',
|
||||
exportTable: 'Экспорт таблицы',
|
||||
createNewMaterializedView: 'Создать новое материализованное представление',
|
||||
newMaterializedView: 'Новое материализованное представление',
|
||||
|
@@ -270,7 +270,7 @@ export const uzUZ = {
|
||||
importQueryErrors: 'Diqqat: {n} xato yuz berdi | Diqqat: {n} xatolar yuz berdi',
|
||||
executedQueries: '{n} soʻrov bajarildi | {n} soʻrovlar bajarildi',
|
||||
insert: 'Kiritish',
|
||||
materializedview: 'Materializatsiya qilingan ko‘rinish | Materializatsiya qilingan ko‘rinishlar',
|
||||
materializedView: 'Materializatsiya qilingan ko‘rinish | Materializatsiya qilingan ko‘rinishlar',
|
||||
exportTable: 'Jadvalni eksport qilish',
|
||||
createNewMaterializedView: 'Yangi materializatsiya qilingan ko‘rinish yaratish',
|
||||
newMaterializedView: 'Yangi materializatsiya qilingan ko‘rinish',
|
||||
|
@@ -132,7 +132,7 @@ export const zhCN = {
|
||||
total: '总计',
|
||||
table: '表 | 表',
|
||||
view: '视图 | 视图',
|
||||
materializedview: '实体化视图 | 实体化视图',
|
||||
materializedView: '实体化视图 | 实体化视图',
|
||||
definer: '定义者',
|
||||
algorithm: '算法',
|
||||
trigger: '触发器 | 触发器',
|
||||
|
@@ -1,5 +1,7 @@
|
||||
/* stylelint-disable function-no-unknown */
|
||||
.theme-light {
|
||||
background: $body-bg;
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
@@ -163,7 +163,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
uid: connection.uid,
|
||||
client: connection.client,
|
||||
icon: conn.icon,
|
||||
name: conn.name
|
||||
name: conn.name,
|
||||
hasCustomIcon: conn.hasCustomIcon
|
||||
};
|
||||
}
|
||||
return conn;
|
||||
|
@@ -151,7 +151,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: [],
|
||||
structure: [] as WorkspaceStructure[],
|
||||
breadcrumbs: {},
|
||||
loadedSchemas: new Set(),
|
||||
database: connection.database,
|
||||
@@ -167,7 +167,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: [],
|
||||
structure: [] as WorkspaceStructure[],
|
||||
breadcrumbs: {},
|
||||
loadedSchemas: new Set(),
|
||||
connectionStatus: 'disconnected'
|
||||
@@ -187,7 +187,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: [],
|
||||
structure: [] as WorkspaceStructure[],
|
||||
breadcrumbs: {},
|
||||
loadedSchemas: new Set(),
|
||||
connectionStatus: 'failed'
|
||||
@@ -200,9 +200,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
return reject(new Error('Connection aborted by user'));
|
||||
else {
|
||||
let clientCustomizations: Customizations;
|
||||
const { updateLastConnection } = connectionsStore;
|
||||
|
||||
updateLastConnection(connection.uid);
|
||||
connectionsStore.updateLastConnection(connection.uid);
|
||||
|
||||
switch (connection.client) {
|
||||
case 'mysql':
|
||||
@@ -418,7 +416,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: [],
|
||||
structure: [] as WorkspaceStructure[],
|
||||
breadcrumbs: {},
|
||||
loadedSchemas: new Set(),
|
||||
connectionStatus: 'disconnected'
|
||||
|
Reference in New Issue
Block a user