Merge pull request #472 from antares-sql/firebirdsql-support

Firebird SQL support
This commit is contained in:
Fabio Di Stasio 2022-11-17 16:19:59 +01:00 committed by GitHub
commit 038cf68253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1671 additions and 107 deletions

View File

@ -5,6 +5,7 @@
"MySQL", "MySQL",
"PostgreSQL", "PostgreSQL",
"SQLite", "SQLite",
"Firebird SQL",
"Windows", "Windows",
"translation", "translation",
"Linux", "Linux",

View File

@ -12,7 +12,7 @@
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL and SQLite. **At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB, PostgreSQL, SQLite and Firebird SQL.
However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it. However, there are all the features necessary to have a pleasant database management experience, so give it a chance and send us your feedback, we would really appreciate it.
We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
@ -84,8 +84,8 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL - [x] PostgreSQL
- [x] SQLite - [x] SQLite
- [ ] MSSQL - [x] Firebird SQL
- [ ] OracleDB - [ ] SQL Server
- [ ] More... - [ ] More...
### Operating Systems ### Operating Systems

32
package-lock.json generated
View File

@ -26,6 +26,7 @@
"marked": "~4.0.19", "marked": "~4.0.19",
"moment": "~2.29.4", "moment": "~2.29.4",
"mysql2": "~2.3.2", "mysql2": "~2.3.2",
"node-firebird": "~1.1.3",
"pg": "~8.7.1", "pg": "~8.7.1",
"pg-connection-string": "~2.5.0", "pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",
@ -4132,6 +4133,14 @@
"prebuild-install": "^7.1.0" "prebuild-install": "^7.1.0"
} }
}, },
"node_modules/big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/big.js": { "node_modules/big.js": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -11274,6 +11283,15 @@
} }
} }
}, },
"node_modules/node-firebird": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-1.1.3.tgz",
"integrity": "sha512-3VhiP8XMqlKQo8H8nPOmrqYFseEj0uUdoacZ5xutRAOFzLWR9ImXBfVLUdg4AiH34YCshgiU8Lc37AAX3Vc6YQ==",
"dependencies": {
"big-integer": "^1.6.48",
"long": "^4.0.0"
}
},
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@ -19893,6 +19911,11 @@
"prebuild-install": "^7.1.0" "prebuild-install": "^7.1.0"
} }
}, },
"big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
},
"big.js": { "big.js": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@ -25338,6 +25361,15 @@
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
} }
}, },
"node-firebird": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-1.1.3.tgz",
"integrity": "sha512-3VhiP8XMqlKQo8H8nPOmrqYFseEj0uUdoacZ5xutRAOFzLWR9ImXBfVLUdg4AiH34YCshgiU8Lc37AAX3Vc6YQ==",
"requires": {
"big-integer": "^1.6.48",
"long": "^4.0.0"
}
},
"node-forge": { "node-forge": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",

View File

@ -135,6 +135,7 @@
"marked": "~4.0.19", "marked": "~4.0.19",
"moment": "~2.29.4", "moment": "~2.29.4",
"mysql2": "~2.3.2", "mysql2": "~2.3.2",
"node-firebird": "~1.1.3",
"pg": "~8.7.1", "pg": "~8.7.1",
"pg-connection-string": "~2.5.0", "pg-connection-string": "~2.5.0",
"pg-query-stream": "~4.2.3", "pg-query-stream": "~4.2.3",

View File

@ -1,10 +1,14 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
// Everything OFF
export const defaults: Customizations = { export const defaults: Customizations = {
// Defaults // Defaults
defaultPort: null, defaultPort: null,
defaultUser: null, defaultUser: null,
defaultDatabase: null, defaultDatabase: null,
dataTypes: [],
indexTypes: [],
foreignActions: [],
// Core // Core
database: false, database: false,
collations: false, collations: false,
@ -45,9 +49,9 @@ export const defaults: Customizations = {
exportByChunks: false, exportByChunks: false,
schemaImport: false, schemaImport: false,
tableSettings: false, tableSettings: false,
tableOptions: false,
tableArray: false, tableArray: false,
tableRealCount: false, tableRealCount: false,
tableDuplicate: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,
@ -73,6 +77,7 @@ export const defaults: Customizations = {
procedureDataAccess: false, procedureDataAccess: false,
procedureSql: null, procedureSql: null,
procedureContext: false, procedureContext: false,
procedureContextValues: [],
procedureLanguage: false, procedureLanguage: false,
functionDeterministic: false, functionDeterministic: false,
functionDataAccess: false, functionDataAccess: false,

View File

@ -0,0 +1,63 @@
import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults';
import firebirdTypes from '../data-types/firebird';
export const customizations: Customizations = {
...defaults,
// Defaults
defaultPort: 3050,
defaultUser: 'SYSDBA',
defaultDatabase: null,
dataTypes: firebirdTypes,
indexTypes: [
'PRIMARY',
// 'CHECK',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'NO ACTION',
'CASCADE',
'SET NULL',
'SET DEFAULT'
],
// Core
database: true,
collations: false,
engines: false,
connectionSchema: false,
sslConnection: false,
sshConnection: false,
fileConnection: false,
cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
schemas: false,
tables: true,
views: true,
triggers: true,
routines: true,
functions: false,
// Settings
elementsWrapper: '"',
stringsWrapper: '\'',
tableAdd: true,
tableSettings: true,
tableRealCount: true,
viewAdd: true,
viewSettings: true,
triggerAdd: true,
triggerMultipleEvents: true,
triggerSql: 'BEGIN\r\n\r\nEND',
routineAdd: true,
procedureContext: true,
procedureContextValues: ['IN', 'OUT'],
procedureSql: 'BEGIN\r\n\r\nEND',
parametersLength: true,
indexes: true,
foreigns: true,
nullable: true
};

View File

@ -1,16 +1,19 @@
import * as mysql from 'common/customizations/mysql'; import * as mysql from 'common/customizations/mysql';
import * as postgresql from 'common/customizations/postgresql'; import * as postgresql from 'common/customizations/postgresql';
import * as sqlite from 'common/customizations/sqlite'; import * as sqlite from 'common/customizations/sqlite';
import * as firebird from 'common/customizations/firebird';
import { Customizations } from 'common/interfaces/customizations'; import { Customizations } from 'common/interfaces/customizations';
export default { export default {
maria: mysql.customizations, maria: mysql.customizations,
mysql: mysql.customizations, mysql: mysql.customizations,
pg: postgresql.customizations, pg: postgresql.customizations,
sqlite: sqlite.customizations sqlite: sqlite.customizations,
firebird: firebird.customizations
} as { } as {
maria: Customizations; maria: Customizations;
mysql: Customizations; mysql: Customizations;
pg: Customizations; pg: Customizations;
sqlite: Customizations; sqlite: Customizations;
firebird: Customizations;
}; };

View File

@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import mysqlTypes from '../data-types/mysql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@ -7,6 +8,19 @@ export const customizations: Customizations = {
defaultPort: 3306, defaultPort: 3306,
defaultUser: 'root', defaultUser: 'root',
defaultDatabase: null, defaultDatabase: null,
dataTypes: mysqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
connectionSchema: true, connectionSchema: true,
collations: true, collations: true,
@ -29,6 +43,7 @@ export const customizations: Customizations = {
stringsWrapper: '"', stringsWrapper: '"',
tableAdd: true, tableAdd: true,
tableTruncateDisableFKCheck: true, tableTruncateDisableFKCheck: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
routineAdd: true, routineAdd: true,
@ -51,7 +66,6 @@ export const customizations: Customizations = {
unsigned: true, unsigned: true,
nullable: true, nullable: true,
zerofill: true, zerofill: true,
tableOptions: true,
autoIncrement: true, autoIncrement: true,
comment: true, comment: true,
collation: true, collation: true,
@ -64,6 +78,7 @@ export const customizations: Customizations = {
procedureDataAccess: true, procedureDataAccess: true,
procedureSql: 'BEGIN\r\n\r\nEND', procedureSql: 'BEGIN\r\n\r\nEND',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
triggerSql: 'BEGIN\r\n\r\nEND', triggerSql: 'BEGIN\r\n\r\nEND',
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,

View File

@ -1,5 +1,6 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import postgresqlTypes from '../data-types/postgresql';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
@ -7,6 +8,18 @@ export const customizations: Customizations = {
defaultPort: 5432, defaultPort: 5432,
defaultUser: 'postgres', defaultUser: 'postgres',
defaultDatabase: 'postgres', defaultDatabase: 'postgres',
dataTypes: postgresqlTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
database: true, database: true,
sslConnection: true, sslConnection: true,
@ -26,6 +39,7 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
triggerFunctionAdd: true, triggerFunctionAdd: true,
@ -47,6 +61,7 @@ export const customizations: Customizations = {
tableArray: true, tableArray: true,
procedureSql: '$procedure$\r\n\r\n$procedure$', procedureSql: '$procedure$\r\n\r\n$procedure$',
procedureContext: true, procedureContext: true,
procedureContextValues: ['IN', 'OUT', 'INOUT'],
procedureLanguage: true, procedureLanguage: true,
functionSql: '$function$\r\n\r\n$function$', functionSql: '$function$\r\n\r\n$function$',
triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$', triggerFunctionSql: '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$',

View File

@ -1,8 +1,21 @@
import { Customizations } from '../interfaces/customizations'; import { Customizations } from '../interfaces/customizations';
import { defaults } from './defaults'; import { defaults } from './defaults';
import sqliteTypes from '../data-types/sqlite';
export const customizations: Customizations = { export const customizations: Customizations = {
...defaults, ...defaults,
dataTypes: sqliteTypes,
indexTypes: [
'PRIMARY',
'INDEX',
'UNIQUE'
],
foreignActions: [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
],
// Core // Core
fileConnection: true, fileConnection: true,
// Structure // Structure
@ -14,6 +27,7 @@ export const customizations: Customizations = {
elementsWrapper: '"', elementsWrapper: '"',
stringsWrapper: '\'', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
tableDuplicate: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,
schemaEdit: false, schemaEdit: false,

View File

@ -0,0 +1,136 @@
import { TypesGroup } from 'common/interfaces/antares';
export default [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'INTEGER',
length: false,
collation: false,
unsigned: true,
zerofill: true
},
{
name: 'BIGINT',
length: false,
collation: false,
unsigned: true,
zerofill: true
}
]
},
{
group: 'float',
types: [
{
name: 'DECIMAL',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'NUMERIC',
length: true,
scale: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'FLOAT',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'DOUBLE PRECISION',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'string',
types: [
{
name: 'CHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'VARCHAR',
length: true,
collation: true,
unsigned: false,
zerofill: false
},
{
name: 'BLOB-TEXT',
length: false,
collation: true,
unsigned: false,
zerofill: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BLOB',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'CHAR-BINARY',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
},
{
group: 'time',
types: [
{
name: 'DATE',
length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIME',
length: true,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'TIMESTAMP',
length: false,
collation: false,
unsigned: false,
zerofill: false
}
]
}
] as TypesGroup[];

View File

@ -10,7 +10,8 @@ export const LONG_TEXT = [
'MEDIUMTEXT', 'MEDIUMTEXT',
'LONGTEXT', 'LONGTEXT',
'JSON', 'JSON',
'VARBINARY' 'VARBINARY',
'BLOB-TEXT'
]; ];
export const ARRAY = [ export const ARRAY = [
@ -35,7 +36,8 @@ export const NUMBER = [
'SERIAL', 'SERIAL',
'BIGSERIAL', 'BIGSERIAL',
'OID', 'OID',
'XID' 'XID',
'INT64'
]; ];
export const FLOAT = [ export const FLOAT = [
@ -84,7 +86,8 @@ export const BLOB = [
'MEDIUMBLOB', 'MEDIUMBLOB',
'LONGBLOB', 'LONGBLOB',
'LONG_BLOB', 'LONG_BLOB',
'BYTEA' 'BYTEA',
'CHAR-BINARY'
]; ];
export const BIT = [ export const BIT = [

View File

@ -1,6 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE',
'FULLTEXT'
];

View File

@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@ -1,5 +0,0 @@
export default [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@ -8,9 +8,10 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { MySQLClient } from '../../main/libs/clients/MySQLClient';
import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient';
import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; import { SQLiteClient } from '../../main/libs/clients/SQLiteClient';
import { FirebirdSQLClient } from 'src/main/libs/clients/FirebirdSQLClient';
export type Client = MySQLClient | PostgreSQLClient | SQLiteClient export type Client = MySQLClient | PostgreSQLClient | SQLiteClient | FirebirdSQLClient
export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' | 'firebird'
export type Exporter = MysqlExporter | PostgreSQLExporter export type Exporter = MysqlExporter | PostgreSQLExporter
export type Importer = MySQLImporter | PostgreSQLImporter export type Importer = MySQLImporter | PostgreSQLImporter

View File

@ -1,8 +1,13 @@
import { TypesGroup } from './antares';
export interface Customizations { export interface Customizations {
// Defaults // Defaults
defaultPort?: number; defaultPort?: number;
defaultUser?: string; defaultUser?: string;
defaultDatabase?: string; defaultDatabase?: string;
dataTypes?: TypesGroup[];
indexTypes?: string[];
foreignActions?: string[];
// Core // Core
database?: boolean; database?: boolean;
collations?: boolean; collations?: boolean;
@ -30,7 +35,7 @@ export interface Customizations {
stringsWrapper: string; stringsWrapper: string;
tableAdd?: boolean; tableAdd?: boolean;
tableSettings?: boolean; tableSettings?: boolean;
tableOptions?: boolean; tableDuplicate?: boolean;
tableArray?: boolean; tableArray?: boolean;
tableRealCount?: boolean; tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean; tableTruncateDisableFKCheck?: boolean;
@ -71,6 +76,7 @@ export interface Customizations {
procedureDataAccess?: boolean; procedureDataAccess?: boolean;
procedureSql?: string; procedureSql?: string;
procedureContext?: boolean; procedureContext?: boolean;
procedureContextValues?: string[];
procedureLanguage?: boolean; procedureLanguage?: boolean;
functionDeterministic?: boolean; functionDeterministic?: boolean;
functionDataAccess?: boolean; functionDataAccess?: boolean;

View File

@ -61,7 +61,11 @@ export default (connections: {[key: string]: antares.Client}) => {
}); });
await connection.connect(); await connection.connect();
await connection.select('1+1').run(); if (conn.client === 'firebird')
connection.raw('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') FROM rdb$database');
else
await connection.select('1+1').run();
connection.destroy(); connection.destroy();
return { status: 'success' }; return { status: 'success' };

View File

@ -97,7 +97,7 @@ export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('get-engines', async (event, uid) => { ipcMain.handle('get-engines', async (event, uid) => {
try { try {
const result = await connections[uid].getEngines(); const result: unknown = await connections[uid].getEngines();
return { status: 'success', response: result }; return { status: 'success', response: result };
} }

View File

@ -105,6 +105,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@ -124,6 +125,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
break; break;
case 'pg': case 'pg':
case 'firebird':
fileBlob = fs.readFileSync(params.content); fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break; break;
@ -141,6 +143,7 @@ export default (connections: {[key: string]: antares.Client}) => {
escapedParam = '\'\''; escapedParam = '\'\'';
break; break;
case 'pg': case 'pg':
case 'firebird':
escapedParam = 'decode(\'\', \'hex\')'; escapedParam = 'decode(\'\', \'hex\')';
break; break;
case 'sqlite': case 'sqlite':
@ -158,6 +161,7 @@ export default (connections: {[key: string]: antares.Client}) => {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
case 'pg': case 'pg':
case 'firebird':
escapedParam = params.content; escapedParam = params.content;
break; break;
case 'sqlite': case 'sqlite':
@ -223,10 +227,11 @@ export default (connections: {[key: string]: antares.Client}) => {
}).join(','); }).join(',');
try { try {
const result = await connections[params.uid] const result: unknown = await connections[params.uid]
.schema(params.schema) .schema(params.schema)
.delete(params.table) .delete(params.table)
.where({ [params.primary]: `IN (${idString})` }) .where({ [params.primary]: `IN (${idString})` })
.limit(params.rows.length)
.run(); .run();
return { status: 'success', response: result }; return { status: 'success', response: result };
@ -285,6 +290,7 @@ export default (connections: {[key: string]: antares.Client}) => {
break; break;
case 'pg': case 'pg':
case 'sqlite': case 'sqlite':
case 'firebird':
escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`;
break; break;
} }
@ -382,7 +388,20 @@ export default (connections: {[key: string]: antares.Client}) => {
if (description) if (description)
query.select(`LEFT(${description}, 20) AS foreign_description`); query.select(`LEFT(${description}, 20) AS foreign_description`);
const results = await query.run(); const results = await query.run<{[key: string]: string}>();
const parsedResults: {[key: string]: string}[] = [];
for (const row of results.rows) {
const remappedRow: {[key: string]: string} = {};
for (const key in row)
remappedRow[key.toLowerCase()] = row[key];// Thanks Firebird -.-
parsedResults.push(remappedRow);
}
results.rows = parsedResults;
return { status: 'success', response: results }; return { status: 'success', response: results };
} }

View File

@ -16,7 +16,7 @@ const queryLogger = ({ sql, cUid }: {sql: string; cUid: string}) => {
/** /**
* As Simple As Possible Query Builder Core * As Simple As Possible Query Builder Core
*/ */
export class AntaresCore { export abstract class AntaresCore {
_client: antares.ClientCode; _client: antares.ClientCode;
protected _cUid: string protected _cUid: string
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};

View File

@ -2,6 +2,7 @@ import * as antares from 'common/interfaces/antares';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient'; import { SQLiteClient } from './clients/SQLiteClient';
import { FirebirdSQLClient } from './clients/FirebirdSQLClient';
export class ClientsFactory { export class ClientsFactory {
static getClient (args: antares.ClientParams) { static getClient (args: antares.ClientParams) {
@ -13,6 +14,8 @@ export class ClientsFactory {
return new PostgreSQLClient(args); return new PostgreSQLClient(args);
case 'sqlite': case 'sqlite':
return new SQLiteClient(args); return new SQLiteClient(args);
case 'firebird':
return new FirebirdSQLClient(args);
default: default:
throw new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }

File diff suppressed because it is too large Load Diff

View File

@ -540,11 +540,7 @@ export class PostgreSQLClient extends AntaresCore {
return { return {
name: row.constraint_name, name: row.constraint_name,
column: row.column_name, column: row.column_name,
indexType: null as null, type: row.constraint_type
type: row.constraint_type,
cardinality: null as null,
comment: '',
indexComment: ''
}; };
}); });
} }

View File

@ -217,11 +217,7 @@ export class SQLiteClient extends AntaresCore {
remappedIndexes.push({ remappedIndexes.push({
name: 'PRIMARY', name: 'PRIMARY',
column: key.name, column: key.name,
indexType: null as never, type: 'PRIMARY'
type: 'PRIMARY',
cardinality: null as never,
comment: '',
indexComment: ''
}); });
} }

View File

@ -123,8 +123,8 @@ else {
if (isWindows) if (isWindows)
mainWindow.show(); mainWindow.show();
if (isDevelopment) // if (isDevelopment)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);

View File

@ -240,7 +240,9 @@ watch(() => tablesInQuery.value.length, () => {
}); });
fields.value = localFields; fields.value = localFields;
setCustomCompleter(); setTimeout(() => {
setCustomCompleter();
}, 100);
}); });
watch(editorTheme, () => { watch(editorTheme, () => {

View File

@ -415,7 +415,8 @@ const clients = [
{ name: 'MySQL', slug: 'mysql' }, { name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' }, { name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' }, { name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' } { name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
]; ];
const connection = ref({ const connection = ref({

View File

@ -428,7 +428,8 @@ const clients = [
{ name: 'MySQL', slug: 'mysql' }, { name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' }, { name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' }, { name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' } { name: 'SQLite', slug: 'sqlite' },
{ name: 'Firebird SQL (experimental)', slug: 'firebird' }
]; ];
const firstInput: Ref<HTMLInputElement> = ref(null); const firstInput: Ref<HTMLInputElement> = ref(null);

View File

@ -258,6 +258,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${localElement.value.name}(${params.join(',')})`; sql = `CALL ${localElement.value.name}(${params.join(',')})`;
break; break;
case 'firebird':
sql = `EXECUTE PROCEDURE "${localElement.value.name}"(${params.join(',')})`;
break;
// case 'mssql': // case 'mssql':
// sql = `EXEC ${localElement.value.name} ${params.join(',')}`; // sql = `EXEC ${localElement.value.name} ${params.join(',')}`;
// break; // break;

View File

@ -18,7 +18,7 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-tune-vertical-variant text-light pr-1" /> {{ t('word.settings') }}</span>
</div> </div>
<div <div
v-if="selectedTable && selectedTable.type === 'table'" v-if="selectedTable && selectedTable.type === 'table' && customizations.tableDuplicate"
class="context-element" class="context-element"
@click="duplicateTable" @click="duplicateTable"
> >

View File

@ -291,7 +291,7 @@ watch(consoleHeight, () => {
}); });
originalRoutine.value = { originalRoutine.value = {
sql: customizations.value.functionSql, sql: customizations.value.procedureSql,
language: customizations.value.languages ? customizations.value.languages[0] : null, language: customizations.value.languages ? customizations.value.languages[0] : null,
name: '', name: '',
definer: '', definer: '',

View File

@ -275,7 +275,13 @@ const saveContentListener = () => {
}; };
watch(() => props.isSelected, (val) => { watch(() => props.isSelected, (val) => {
if (val) changeBreadcrumbs({ schema: props.schema }); if (val) {
changeBreadcrumbs({ schema: props.schema });
setTimeout(() => {
resizeQueryEditor();
}, 50);
}
}); });
watch(isChanged, (val) => { watch(isChanged, (val) => {

View File

@ -351,6 +351,9 @@ const runRoutine = (params?: string[]) => {
case 'pg': case 'pg':
sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`; sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`;
break; break;
case 'firebird':
sql = `EXECUTE PROCEDURE "${originalRoutine.value.name}"(${params.join(',')})`;
break;
case 'mssql': case 'mssql':
sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`; sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`;
break; break;

View File

@ -118,29 +118,17 @@
{{ t('word.context') }} {{ t('word.context') }}
</label> </label>
<div class="column"> <div class="column">
<label class="form-radio"> <label
v-for="condext in customizations.procedureContextValues"
:key="condext"
class="form-radio"
>
<input <input
v-model="selectedParamObj.context" v-model="selectedParamObj.context"
type="radio" type="radio"
name="context" name="context"
value="IN" :value="condext"
> <i class="form-icon" /> IN > <i class="form-icon" /> {{ condext }}
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="OUT"
name="context"
> <i class="form-icon" /> OUT
</label>
<label class="form-radio">
<input
v-model="selectedParamObj.context"
type="radio"
value="INOUT"
name="context"
> <i class="form-icon" /> INOUT
</label> </label>
</div> </div>
</div> </div>

View File

@ -300,7 +300,7 @@ const getFieldsData = async () => {
field.defaultType = 'noval'; field.defaultType = 'noval';
else if (field.default === 'NULL') else if (field.default === 'NULL')
field.defaultType = 'null'; field.defaultType = 'null';
else if (isNaN(+field.default) && field.default.charAt(0) !== '\'') else if (typeof field.default === 'string' && isNaN(+field.default) && field.default.charAt(0) !== '\'')
field.defaultType = 'expression'; field.defaultType = 'expression';
else { else {
field.defaultType = 'custom'; field.defaultType = 'custom';
@ -323,11 +323,13 @@ const getFieldsData = async () => {
const { status, response } = await Tables.getTableIndexes(params); const { status, response } = await Tables.getTableIndexes(params);
if (status === 'success') { if (status === 'success') {
const indexesObj = response.reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => { const indexesObj = response
acc[curr.name] = acc[curr.name] || []; .filter((index: TableIndex) => index.type !== 'FOREIGN KEY')
acc[curr.name].push(curr); .reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => {
return acc; acc[curr.name] = acc[curr.name] || [];
}, {}); acc[curr.name].push(curr);
return acc;
}, {});
originalIndexes.value = Object.keys(indexesObj).map(index => { originalIndexes.value = Object.keys(indexesObj).map(index => {
return { return {
@ -529,9 +531,10 @@ const clearChanges = () => {
}; };
const addField = () => { const addField = () => {
const uid = uidGen();
localFields.value.push({ localFields.value.push({
_antares_id: uidGen(), _antares_id: uid,
name: `${t('word.field', 1)}_${++newFieldsCounter.value}`, name: `${t('word.field', 1)}_${uid.substring(0, 4)}`,
key: '', key: '',
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
type: (workspace.value.dataTypes[0] as any).types[0].name, type: (workspace.value.dataTypes[0] as any).types[0].name,

View File

@ -113,7 +113,7 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label col-3 pt-0"> <label class="form-label col-3">
{{ t('message.referenceTable') }} {{ t('message.referenceTable') }}
</label> </label>
<div class="column"> <div class="column">
@ -219,13 +219,8 @@ const foreignProxy = ref([]);
const selectedForeignID = ref(''); const selectedForeignID = ref('');
const modalInnerHeight = ref(400); const modalInnerHeight = ref(400);
const refFields = ref({} as {[key: string]: TableField[]}); const refFields = ref({} as {[key: string]: TableField[]});
const foreignActions = [
'RESTRICT',
'CASCADE',
'SET NULL',
'NO ACTION'
];
const foreignActions = computed(() => props.workspace.customizations.foreignActions);
const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value)); const selectedForeignObj = computed(() => foreignProxy.value.find(foreign => foreign._antares_id === selectedForeignID.value));
const isChanged = computed(() => JSON.stringify(props.localKeyUsage) !== JSON.stringify(foreignProxy.value)); const isChanged = computed(() => JSON.stringify(props.localKeyUsage) !== JSON.stringify(foreignProxy.value));
@ -254,16 +249,17 @@ const getModalInnerHeight = () => {
}; };
const addForeign = () => { const addForeign = () => {
const uid = uidGen();
foreignProxy.value = [...foreignProxy.value, { foreignProxy.value = [...foreignProxy.value, {
_antares_id: uidGen(), _antares_id: uid,
constraintName: `FK_${uidGen()}`, constraintName: `FK_${uid.substring(0, 4)}`,
refSchema: props.schema, refSchema: props.schema,
table: props.table, table: props.table,
refTable: '', refTable: '',
field: '', field: '',
refField: '', refField: '',
onUpdate: foreignActions[0], onUpdate: foreignActions.value[0],
onDelete: foreignActions[0] onDelete: foreignActions.value[0]
}]; }];
if (foreignProxy.value.length === 1) if (foreignProxy.value.length === 1)
@ -271,6 +267,7 @@ const addForeign = () => {
setTimeout(() => { setTimeout(() => {
indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60; indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60;
selectedForeignID.value = uid;
}, 20); }, 20);
}; };

View File

@ -92,7 +92,7 @@
<BaseSelect <BaseSelect
v-model="selectedIndexObj.type" v-model="selectedIndexObj.type"
:options="indexTypes" :options="indexTypes"
:option-disabled="(opt: any) => opt === 'PRIMARY'" :option-disabled="(opt: any) => opt === 'PRIMARY' && hasPrimary"
class="form-select" class="form-select"
/> />
</div> </div>
@ -160,6 +160,7 @@ const modalInnerHeight = ref(400);
const selectedIndexObj = computed(() => indexesProxy.value.find(index => index._antares_id === selectedIndexID.value)); const selectedIndexObj = computed(() => indexesProxy.value.find(index => index._antares_id === selectedIndexID.value));
const isChanged = computed(() => JSON.stringify(props.localIndexes) !== JSON.stringify(indexesProxy.value)); const isChanged = computed(() => JSON.stringify(props.localIndexes) !== JSON.stringify(indexesProxy.value));
const hasPrimary = computed(() => indexesProxy.value.some(index => ['PRIMARY', 'PRIMARY KEY'].includes(index.type)));
const confirmIndexesChange = () => { const confirmIndexesChange = () => {
indexesProxy.value = indexesProxy.value.filter(index => index.fields.length); indexesProxy.value = indexesProxy.value.filter(index => index.fields.length);
@ -179,15 +180,12 @@ const getModalInnerHeight = () => {
}; };
const addIndex = () => { const addIndex = () => {
const uid = uidGen();
indexesProxy.value = [...indexesProxy.value, { indexesProxy.value = [...indexesProxy.value, {
_antares_id: uidGen(), _antares_id: uid,
name: 'NEW_INDEX', name: `INDEX_${uid.substring(0, 4)}`,
fields: [], fields: [],
type: 'INDEX', type: props.workspace.customizations.primaryAsIndex ? props.indexTypes[0] : props.indexTypes[1]
comment: '',
indexType: 'BTREE',
indexComment: '',
cardinality: 0
}]; }];
if (indexesProxy.value.length === 1) if (indexesProxy.value.length === 1)
@ -195,6 +193,7 @@ const addIndex = () => {
setTimeout(() => { setTimeout(() => {
indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60; indexesPanel.value.scrollTop = indexesPanel.value.scrollHeight + 60;
selectedIndexID.value = uid;
}, 20); }, 20);
}; };

View File

@ -501,8 +501,8 @@ const editOFF = () => {
localRow.value.enumValues = ''; localRow.value.enumValues = '';
if (fieldType.value.length) { if (fieldType.value.length) {
if (['integer', 'float', 'binary', 'spatial'].includes(fieldType.value.group)) localRow.value.numLength = 11; if (['integer', 'float', 'binary', 'spatial'].includes(fieldType.value.group)) localRow.value.numLength = 10;
if (['string'].includes(fieldType.value.group)) localRow.value.charLength = 15; if (['string'].includes(fieldType.value.group)) localRow.value.charLength = 20;
if (['time'].includes(fieldType.value.group)) localRow.value.datePrecision = 0; if (['time'].includes(fieldType.value.group)) localRow.value.datePrecision = 0;
if (['other'].includes(fieldType.value.group)) localRow.value.enumValues = '\'valA\',\'valB\''; if (['other'].includes(fieldType.value.group)) localRow.value.enumValues = '\'valA\',\'valB\'';
} }

View File

@ -270,6 +270,8 @@ const keyName = (key: string) => {
return 'UNIQUE'; return 'UNIQUE';
case 'mul': case 'mul':
return 'INDEX'; return 'INDEX';
case 'fk':
return 'REFERENCES';
default: default:
return 'UNKNOWN ' + key; return 'UNKNOWN ' + key;
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -26,6 +26,8 @@
"macaddr8": $string-color, "macaddr8": $string-color,
"uuid": $string-color, "uuid": $string-color,
"regproc": $string-color, "regproc": $string-color,
"blob-text": $string-color,
"int": $number-color, "int": $number-color,
"tinyint": $number-color, "tinyint": $number-color,
"smallint": $number-color, "smallint": $number-color,
@ -44,6 +46,7 @@
"double_precision": $number-color, "double_precision": $number-color,
"oid": $number-color, "oid": $number-color,
"xid": $number-color, "xid": $number-color,
"money": $number-color, "money": $number-color,
"number": $number-color, "number": $number-color,
"datetime": $date-color, "datetime": $date-color,
@ -54,9 +57,12 @@
"timestamp": $date-color, "timestamp": $date-color,
"timestamp_without_time_zone": $date-color, "timestamp_without_time_zone": $date-color,
"timestamp_with_time_zone": $date-color, "timestamp_with_time_zone": $date-color,
"bit": $bit-color, "bit": $bit-color,
"bit_varying": $bit-color, "bit_varying": $bit-color,
"binary": $blob-color, "binary": $blob-color,
"char-binary": $blob-color,
"varbinary": $blob-color, "varbinary": $blob-color,
"blob": $blob-color, "blob": $blob-color,
"tinyblob": $blob-color, "tinyblob": $blob-color,
@ -65,10 +71,12 @@
"longblob": $blob-color, "longblob": $blob-color,
"long_blob": $blob-color, "long_blob": $blob-color,
"bytea": $blob-color, "bytea": $blob-color,
"enum": $enum-color, "enum": $enum-color,
"set": $enum-color, "set": $enum-color,
"bool": $enum-color, "bool": $enum-color,
"boolean": $enum-color, "boolean": $enum-color,
"interval": $array-color, "interval": $array-color,
"array": $array-color, "array": $array-color,
"anyarray": $array-color, "anyarray": $array-color,
@ -85,6 +93,7 @@
"geomcollection": $array-color, "geomcollection": $array-color,
"geometrycollection": $array-color, "geometrycollection": $array-color,
"aclitem": $array-color, "aclitem": $array-color,
"unknown": $unknown-color, "unknown": $unknown-color,
) )
); );

View File

@ -25,6 +25,10 @@
background-image: url("../images/svg/sqlite.svg"); background-image: url("../images/svg/sqlite.svg");
} }
&.dbi-firebird {
background-image: url("../images/svg/firebird.svg");
}
&.dbi-oracledb { &.dbi-oracledb {
background-image: url("../images/svg/oracledb.svg"); background-image: url("../images/svg/oracledb.svg");
} }

View File

@ -21,6 +21,10 @@
color: limegreen; color: limegreen;
} }
&.key-fk {
color: chocolate;
}
&.key-FULLTEXT { &.key-FULLTEXT {
color: mediumvioletred; color: mediumvioletred;
} }

View File

@ -176,8 +176,6 @@ export const useWorkspacesStore = defineStore('workspaces', {
: workspace); : workspace);
} }
else { else {
let dataTypes: TypesGroup[] = [];
let indexTypes: string[] = [];
let clientCustomizations: Customizations; let clientCustomizations: Customizations;
const { updateLastConnection } = connectionsStore; const { updateLastConnection } = connectionsStore;
@ -186,21 +184,20 @@ export const useWorkspacesStore = defineStore('workspaces', {
switch (connection.client) { switch (connection.client) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
dataTypes = require('common/data-types/mysql').default;
indexTypes = require('common/index-types/mysql').default;
clientCustomizations = customizations.mysql; clientCustomizations = customizations.mysql;
break; break;
case 'pg': case 'pg':
dataTypes = require('common/data-types/postgresql').default;
indexTypes = require('common/index-types/postgresql').default;
clientCustomizations = customizations.pg; clientCustomizations = customizations.pg;
break; break;
case 'sqlite': case 'sqlite':
dataTypes = require('common/data-types/sqlite').default;
indexTypes = require('common/index-types/sqlite').default;
clientCustomizations = customizations.sqlite; clientCustomizations = customizations.sqlite;
break; break;
case 'firebird':
clientCustomizations = customizations.firebird;
break;
} }
const dataTypes = clientCustomizations.dataTypes;
const indexTypes = clientCustomizations.indexTypes;
const { status, response: version } = await Schema.getVersion(connection.uid); const { status, response: version } = await Schema.getVersion(connection.uid);