From 7ab84bde57053d43d7e67f7e3b8967de68415a9e Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Wed, 2 Nov 2022 14:18:50 +0100 Subject: [PATCH 01/14] initial firebird commit --- package-lock.json | 532 ++++++++++++- package.json | 1 + src/common/customizations/firebird.ts | 94 +++ src/common/customizations/index.ts | 5 +- src/common/interfaces/antares.ts | 5 +- src/main/ipc-handlers/connection.ts | 6 +- src/main/libs/ClientsFactory.ts | 3 + src/main/libs/clients/FirebirdSQLClient.ts | 703 ++++++++++++++++++ .../WorkspaceAddConnectionPanel.vue | 3 +- .../WorkspaceEditConnectionPanel.vue | 3 +- src/renderer/images/svg/firebird.svg | 1 + src/renderer/scss/_db-icons.scss | 4 + 12 files changed, 1351 insertions(+), 9 deletions(-) create mode 100644 src/common/customizations/firebird.ts create mode 100644 src/main/libs/clients/FirebirdSQLClient.ts create mode 100644 src/renderer/images/svg/firebird.svg diff --git a/package-lock.json b/package-lock.json index 36de32e7..b9d7a40c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", + "node-firebird": "~0.9.9", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", @@ -1937,6 +1938,15 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -1974,6 +1984,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -3825,6 +3841,15 @@ "node": ">=10" } }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4977,6 +5002,12 @@ "ms": "2.0.0" } }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/compression/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5042,6 +5073,11 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/conf/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/conf/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6070,6 +6106,15 @@ "node": ">=12" } }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/dmg-license": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", @@ -6414,6 +6459,15 @@ "node": ">=12" } }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/electron-builder/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -7236,6 +7290,15 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/eslint-plugin-promise": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", @@ -7699,6 +7762,12 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -10003,7 +10072,8 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-schema-typed": { "version": "7.0.3", @@ -10111,6 +10181,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -10244,6 +10323,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -10520,6 +10600,18 @@ "node": ">=8" } }, + "node_modules/meow/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/meow/node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -10535,6 +10627,42 @@ "node": ">=10" } }, + "node_modules/meow/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/meow/node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11147,6 +11275,14 @@ } } }, + "node_modules/node-firebird": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-0.9.9.tgz", + "integrity": "sha512-nH4zIaglr+/J0E8W9YTiJchpFbjO7gc0ExDtzZmFXp9iLpmyuq+9RFEJoePT2WE39kkx+DNYO7J7TTHBpOmitQ==", + "dependencies": { + "long": "^4.0.0" + } + }, "node_modules/node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -11485,6 +11621,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "dependencies": { "p-try": "^1.0.0" }, @@ -11496,6 +11633,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, "dependencies": { "p-limit": "^1.1.0" }, @@ -11556,6 +11694,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, "engines": { "node": ">=4" } @@ -11937,6 +12076,54 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pkg-dir/node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11968,6 +12155,51 @@ "node": ">=6" } }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/playwright": { "version": "1.21.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.21.1.tgz", @@ -12399,6 +12631,18 @@ "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/progress-webpack-plugin/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/progress-webpack-plugin/node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -12413,6 +12657,21 @@ "node": ">=4" } }, + "node_modules/progress-webpack-plugin/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/progress-webpack-plugin/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "node_modules/progress-webpack-plugin/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -13519,6 +13778,15 @@ "ms": "2.0.0" } }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index/node_modules/http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -14064,6 +14332,15 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/standard-version/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/standard-version/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -14696,6 +14973,12 @@ "node": ">=8" } }, + "node_modules/stylelint/node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "node_modules/stylelint/node_modules/read-pkg/node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -16327,6 +16610,15 @@ "node": ">=4" } }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -16439,6 +16731,12 @@ "ms": "2.0.0" } }, + "node_modules/xvfb-maybe/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/xvfb-maybe/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -17914,6 +18212,15 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, "globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -17939,6 +18246,12 @@ "esprima": "^4.0.0" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -19370,6 +19683,12 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true } } }, @@ -20252,6 +20571,12 @@ "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -20306,6 +20631,11 @@ "uri-js": "^4.2.2" } }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -21084,6 +21414,12 @@ "jsonfile": "^6.0.1", "universalify": "^2.0.0" } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true } } }, @@ -21418,6 +21754,12 @@ "universalify": "^2.0.0" } }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -22079,6 +22421,12 @@ "requires": { "eslint-visitor-keys": "^1.1.0" } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true } } }, @@ -22345,6 +22693,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true } } }, @@ -24062,7 +24416,8 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-schema-typed": { "version": "7.0.3", @@ -24151,6 +24506,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -24265,6 +24629,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -24476,6 +24841,15 @@ "path-exists": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, "normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", @@ -24488,6 +24862,30 @@ "validate-npm-package-license": "^3.0.1" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -24949,6 +25347,14 @@ "whatwg-url": "^5.0.0" } }, + "node-firebird": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-0.9.9.tgz", + "integrity": "sha512-nH4zIaglr+/J0E8W9YTiJchpFbjO7gc0ExDtzZmFXp9iLpmyuq+9RFEJoePT2WE39kkx+DNYO7J7TTHBpOmitQ==", + "requires": { + "long": "^4.0.0" + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -25193,6 +25599,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -25201,6 +25608,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -25244,7 +25652,8 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==" + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true }, "package-json": { "version": "6.5.0", @@ -25514,6 +25923,39 @@ "path-exists": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -25537,6 +25979,36 @@ "requires": { "locate-path": "^3.0.0" } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, @@ -25853,6 +26325,15 @@ "log-update": "^2.3.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -25864,6 +26345,21 @@ "supports-color": "^5.3.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -26689,6 +27185,12 @@ "ms": "2.0.0" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -27118,6 +27620,12 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -27486,6 +27994,12 @@ "type-fest": "^0.6.0" }, "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -28779,6 +29293,12 @@ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -28858,6 +29378,12 @@ "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 3f7c3bb1..ad426e4f 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", + "node-firebird": "~0.9.9", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts new file mode 100644 index 00000000..fe34d43a --- /dev/null +++ b/src/common/customizations/firebird.ts @@ -0,0 +1,94 @@ +import { Customizations } from '../interfaces/customizations'; + +export const customizations: Customizations = { + // Defaults + defaultPort: 3050, + defaultUser: 'SYSDBA', + defaultDatabase: null, + // 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: false, + views: false, + triggers: false, + triggerFunctions: false, + routines: false, + functions: false, + schedulers: false, + // Settings + elementsWrapper: '', + stringsWrapper: '"', + tableAdd: false, + tableTruncateDisableFKCheck: false, + viewAdd: false, + triggerAdd: false, + triggerFunctionAdd: false, + routineAdd: false, + functionAdd: false, + schedulerAdd: false, + databaseEdit: false, + schemaEdit: false, + schemaDrop: false, + schemaExport: false, + exportByChunks: false, + schemaImport: false, + tableSettings: false, + tableOptions: false, + tableArray: false, + tableRealCount: false, + viewSettings: false, + triggerSettings: false, + triggerFunctionSettings: false, + routineSettings: false, + functionSettings: false, + schedulerSettings: false, + indexes: false, + foreigns: false, + sortableFields: false, + unsigned: false, + nullable: false, + nullablePrimary: false, + zerofill: false, + autoIncrement: false, + comment: false, + collation: false, + definer: false, + onUpdate: false, + viewAlgorithm: false, + viewSqlSecurity: false, + viewUpdateOption: false, + procedureDeterministic: false, + procedureDataAccess: false, + procedureSql: null, + procedureContext: false, + procedureLanguage: false, + functionDeterministic: false, + functionDataAccess: false, + functionSql: null, + functionContext: false, + functionLanguage: false, + triggerSql: null, + triggerStatementInCreation: false, + triggerMultipleEvents: false, + triggerTableInName: false, + triggerUpdateColumns: false, + triggerOnlyRename: false, + triggerEnableDisable: false, + triggerFunctionSql: null, + triggerFunctionlanguages: null, + parametersLength: false, + languages: null, + readOnlyMode: false +}; diff --git a/src/common/customizations/index.ts b/src/common/customizations/index.ts index 52dbe6fc..6ccc8f1d 100644 --- a/src/common/customizations/index.ts +++ b/src/common/customizations/index.ts @@ -1,16 +1,19 @@ import * as mysql from 'common/customizations/mysql'; import * as postgresql from 'common/customizations/postgresql'; import * as sqlite from 'common/customizations/sqlite'; +import * as firebird from 'common/customizations/firebird'; import { Customizations } from 'common/interfaces/customizations'; export default { maria: mysql.customizations, mysql: mysql.customizations, pg: postgresql.customizations, - sqlite: sqlite.customizations + sqlite: sqlite.customizations, + firebird: firebird.customizations } as { maria: Customizations; mysql: Customizations; pg: Customizations; sqlite: Customizations; + firebird: Customizations; }; diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index b98ee5ef..2662b455 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -8,9 +8,10 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig'; import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; +import { FirebirdSQLClient } from 'src/main/libs/clients/FirebirdSQLClient'; -export type Client = MySQLClient | PostgreSQLClient | SQLiteClient -export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' +export type Client = MySQLClient | PostgreSQLClient | SQLiteClient | FirebirdSQLClient +export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' | 'firebird' export type Exporter = MysqlExporter | PostgreSQLExporter export type Importer = MySQLImporter | PostgreSQLImporter diff --git a/src/main/ipc-handlers/connection.ts b/src/main/ipc-handlers/connection.ts index 69630ec5..318a0e6d 100644 --- a/src/main/ipc-handlers/connection.ts +++ b/src/main/ipc-handlers/connection.ts @@ -61,7 +61,11 @@ export default (connections: {[key: string]: antares.Client}) => { }); 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(); return { status: 'success' }; diff --git a/src/main/libs/ClientsFactory.ts b/src/main/libs/ClientsFactory.ts index 73c29920..b421640b 100644 --- a/src/main/libs/ClientsFactory.ts +++ b/src/main/libs/ClientsFactory.ts @@ -2,6 +2,7 @@ import * as antares from 'common/interfaces/antares'; import { MySQLClient } from './clients/MySQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { SQLiteClient } from './clients/SQLiteClient'; +import { FirebirdSQLClient } from './clients/FirebirdSQLClient'; export class ClientsFactory { static getClient (args: antares.ClientParams) { @@ -13,6 +14,8 @@ export class ClientsFactory { return new PostgreSQLClient(args); case 'sqlite': return new SQLiteClient(args); + case 'firebird': + return new FirebirdSQLClient(args); default: throw new Error(`Unknown database client: ${args.client}`); } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts new file mode 100644 index 00000000..a06533b3 --- /dev/null +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -0,0 +1,703 @@ +import * as antares from 'common/interfaces/antares'; +import * as firebird from 'node-firebird'; +import { AntaresCore } from '../AntaresCore'; +import dataTypes from 'common/data-types/sqlite'; +import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; +import { promisify } from 'util'; + +export class FirebirdSQLClient extends AntaresCore { + private _schema?: string; + private _connectionsToCommit: Map; + protected _connection?: firebird.Database; + _params: firebird.Options; + + constructor (args: antares.ClientParams) { + super(args); + + this._schema = null; + this._connectionsToCommit = new Map(); + } + + getTypeInfo (type: string): antares.TypeInformations { + return dataTypes + .reduce((acc, group) => [...acc, ...group.types], []) + .filter(_type => _type.name === type.toUpperCase())[0]; + } + + async connect () { + this._connection = await this.getConnection(); + } + + async getConnection () { + return new Promise((resolve, reject) => { + firebird.attach(this._params, (err, db) => { + if (err) reject(err); + else resolve(db); + }); + }); + } + + destroy () { + return this._connection.detach(); + } + + use (): void { + return null; + } + + async getStructure (schemas: Set) { + /* eslint-disable camelcase */ + interface ShowTableResult { + Db?: string; + type: string; + name: string; + tbl_name: string; + rootpage:4; + sql: string; + } + + type ShowTriggersResult = ShowTableResult + /* eslint-enable camelcase */ + + const { rows: databases } = await this.raw>('SELECT * FROM pragma_database_list'); + + const filteredDatabases = databases; + + const tablesArr: ShowTableResult[] = []; + const triggersArr: ShowTriggersResult[] = []; + let schemaSize = 0; + + for (const db of filteredDatabases) { + if (!schemas.has(db.name)) continue; + + let { rows: tables } = await this.raw>(` + SELECT * + FROM "${db.name}".sqlite_master + WHERE type IN ('table', 'view') + AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' + ORDER BY name + `); + if (tables.length) { + tables = tables.map(table => { + table.Db = db.name; + return table; + }); + tablesArr.push(...tables); + } + + let { rows: triggers } = await this.raw>(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`); + if (triggers.length) { + triggers = triggers.map(trigger => { + trigger.Db = db.name; + return trigger; + }); + triggersArr.push(...triggers); + } + } + + return filteredDatabases.map(db => { + if (schemas.has(db.name)) { + // TABLES + const remappedTables = tablesArr.filter(table => table.Db === db.name).map(table => { + const tableSize = 0; + schemaSize += tableSize; + + return { + name: table.name, + type: table.type, + rows: false, + size: false + }; + }); + + // TRIGGERS + const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => { + return { + name: trigger.name, + table: trigger.tbl_name + }; + }); + + return { + name: db.name, + size: schemaSize, + tables: remappedTables, + functions: [], + procedures: [], + triggers: remappedTriggers, + schedulers: [] + }; + } + else { + return { + name: db.name, + size: 0, + tables: [], + functions: [], + procedures: [], + triggers: [], + schedulers: [] + }; + } + }); + } + + async getTableColumns ({ schema, table }: { schema: string; table: string }) { + interface TableColumnsResult { + cid: number; + name: string; + type: string; + notnull: 0 | 1; + // eslint-disable-next-line camelcase + dflt_value: string; + pk: 0 | 1; + } + const { rows: fields } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}')`); + + return fields.map(field => { + const [type, length]: [string, number?] = field.type.includes('(') + ? field.type.replace(')', '').split('(').map((el: string | number) => { + if (!isNaN(Number(el))) el = Number(el); + return el; + }) as [string, number?] + : [field.type, null]; + + return { + name: field.name, + key: null, + type: type.trim(), + schema: schema, + table: table, + numPrecision: [...NUMBER, ...FLOAT].includes(type) ? length : null, + datePrecision: null, + charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null, + nullable: !field.notnull, + unsigned: null, + zerofill: null, + order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1, + default: field.dflt_value, + charset: null, + collation: null, + autoIncrement: false, + onUpdate: null, + comment: '' + }; + }); + } + + async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise { + const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`); + + return rows.length ? rows[0].count : 0; + } + + async getTableOptions ({ table }: { table: string }) { + return { name: table }; + } + + async getTableIndexes ({ schema, table }: { schema: string; table: string }) { + interface TableColumnsResult { + type: string; + name: string; + // eslint-disable-next-line camelcase + tbl_name: string; + rootpage:4; + sql: string; + } + + interface ShowIndexesResult { + seq: number; + name: string; + unique: 0 | 1; + origin: string; + partial: 0 | 1; + } + + const remappedIndexes = []; + const { rows: primaryKeys } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`); + + for (const key of primaryKeys) { + remappedIndexes.push({ + name: 'PRIMARY', + column: key.name, + indexType: null as never, + type: 'PRIMARY', + cardinality: null as never, + comment: '', + indexComment: '' + }); + } + + const { rows: indexes } = await this.raw>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`); + + for (const index of indexes) { + const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`); + + for (const detail of details) { + remappedIndexes.push({ + name: index.name, + column: detail.name, + indexType: null as never, + type: index.unique === 1 ? 'UNIQUE' : 'INDEX', + cardinality: null as never, + comment: '', + indexComment: '' + }); + } + } + + return remappedIndexes; + } + + async getKeyUsage ({ schema, table }: { schema: string; table: string }) { + /* eslint-disable camelcase */ + interface KeyResult { + from: string; + id: number; + table: string; + to: string; + on_update: string; + on_delete: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`); + + return rows.map(field => { + return { + schema: schema, + table: table, + field: field.from, + position: field.id + 1, + constraintPosition: null, + constraintName: field.id, + refSchema: schema, + refTable: field.table, + refField: field.to, + onUpdate: field.on_update, + onDelete: field.on_delete + }; + }); + } + + async getUsers (): Promise { + return null; + } + + async createTable (params: antares.CreateTableParams) { + const { + schema, + fields, + foreigns, + indexes, + options + } = params; + const newColumns: string[] = []; + const newIndexes: string[] = []; + const manageIndexes: string[] = []; + const newForeigns: string[] = []; + + let sql = `CREATE TABLE "${schema}"."${options.name}"`; + + // ADD FIELDS + fields.forEach(field => { + const typeInfo = this.getTypeInfo(field.type); + const length = typeInfo?.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false; + + newColumns.push(`"${field.name}" + ${field.type.toUpperCase()}${length ? `(${length})` : ''} + ${field.unsigned ? 'UNSIGNED' : ''} + ${field.nullable ? 'NULL' : 'NOT NULL'} + ${field.autoIncrement ? 'AUTO_INCREMENT' : ''} + ${field.default !== null ? `DEFAULT ${field.default || '\'\''}` : ''} + ${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`); + }); + + // ADD INDEX + indexes.forEach(index => { + const fields = index.fields.map(field => `"${field}"`).join(','); + const type = index.type; + + if (type === 'PRIMARY') + newIndexes.push(`PRIMARY KEY (${fields})`); + else + manageIndexes.push(`CREATE ${type === 'UNIQUE' ? type : ''} INDEX "${index.name}" ON "${options.name}" (${fields})`); + }); + + // ADD FOREIGN KEYS + foreigns.forEach(foreign => { + newForeigns.push(`CONSTRAINT "${foreign.constraintName}" FOREIGN KEY ("${foreign.field}") REFERENCES "${foreign.refTable}" ("${foreign.refField}") ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`); + }); + + sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`; + if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`; + + return await this.raw(sql); + } + + async alterTable (params: antares.AlterTableParams) { + const { + table, + schema, + additions, + deletions, + changes, + tableStructure + } = params; + + try { + await this.raw('BEGIN TRANSACTION'); + await this.raw('PRAGMA foreign_keys = 0'); + + const tmpName = `Antares_${table}_tmp`; + await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); + await this.dropTable(params); + + const createTableParams = { + schema: schema, + fields: tableStructure.fields, + foreigns: tableStructure.foreigns, + indexes: tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')), + options: { name: tableStructure.name } + }; + await this.createTable(createTableParams); + const insertFields = createTableParams.fields + .filter(field => { + return ( + additions.every(add => add.name !== field.name) && + deletions.every(del => del.name !== field.name) + ); + }) + .reduce((acc, curr) => { + acc.push(`"${curr.name}"`); + return acc; + }, []); + + const selectFields = insertFields.map(field => { + const renamedField = changes.find(change => `"${change.name}"` === field); + if (renamedField) + return `"${renamedField.orgName}"`; + return field; + }); + + await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`); + + await this.dropTable({ schema: schema, table: tmpName }); + await this.raw('PRAGMA foreign_keys = 1'); + await this.raw('COMMIT'); + } + catch (err) { + await this.raw('ROLLBACK'); + return Promise.reject(err); + } + } + + async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy + const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`; + return await this.raw(sql); + } + + async truncateTable (params: { schema: string; table: string }) { + const sql = `DELETE FROM "${params.schema}"."${params.table}"`; + return await this.raw(sql); + } + + async dropTable (params: { schema: string; table: string }) { + const sql = `DROP TABLE "${params.schema}"."${params.table}"`; + return await this.raw(sql); + } + + async getViewInformations ({ schema, view }: { schema: string; view: string }) { + const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + sql: row.sql.match(/(?<=AS ).*?$/gs)[0], + name: view + }; + })[0]; + } + + async dropView (params: { schema: string; view: string }) { + const sql = `DROP VIEW "${params.schema}"."${params.view}"`; + return await this.raw(sql); + } + + async alterView ({ view }: { view: antares.AlterViewParams }) { + try { + await this.dropView({ schema: view.schema, view: view.oldName }); + await this.createView(view); + } + catch (err) { + return Promise.reject(err); + } + } + + async createView (params: antares.CreateViewParams) { + const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; + return await this.raw(sql); + } + + async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { + const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0], + name: trigger, + table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0], + activation: row.sql.match(/(BEFORE|AFTER)/gs)[0], + event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0] + }; + })[0]; + } + + async dropTrigger (params: { schema: string; trigger: string }) { + const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``; + return await this.raw(sql); + } + + async alterTrigger ({ trigger } : {trigger: antares.AlterTriggerParams}) { + const tempTrigger = Object.assign({}, trigger); + tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; + + try { + await this.createTrigger(tempTrigger); + await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name }); + await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName }); + await this.createTrigger(trigger); + } + catch (err) { + return Promise.reject(err); + } + } + + async createTrigger (params: antares.CreateTriggerParams) { + const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`; + return await this.raw(sql, { split: false }); + } + + async getEngines () { + return { + name: 'SQLite', + support: 'YES', + comment: '', + isDefault: true + }; + } + + async getVersion () { + const os = require('os'); + const sql = 'SELECT sqlite_version() AS version'; + const { rows } = await this.raw(sql); + + return { + number: rows[0].version, + name: 'SQLite', + arch: process.arch, + os: `${os.type()} ${os.release()}` + }; + } + + async getProcesses (): Promise { + return null; + } + + async killProcess (): Promise { + return null; + } + + async commitTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.prepare('COMMIT').run(); + return this.destroyConnectionToCommit(tabUid); + } + } + + async rollbackTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.prepare('ROLLBACK').run(); + return this.destroyConnectionToCommit(tabUid); + } + } + + destroyConnectionToCommit (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.close(); + this._connectionsToCommit.delete(tabUid); + } + } + + getSQL () { + // SELECT + const selectArray = this._query.select.reduce(this._reducer, []); + let selectRaw = ''; + + if (selectArray.length) + selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * '; + + // FROM + let fromRaw = ''; + + if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from) + fromRaw = 'FROM'; + else if (Object.keys(this._query.insert).length) + fromRaw = 'INTO'; + + fromRaw += this._query.from ? ` ${this._query.schema ? `"${this._query.schema}".` : ''}"${this._query.from}" ` : ''; + + // WHERE + const whereArray = this._query.where + .reduce(this._reducer, []) + ?.map(clausole => clausole.replace('= null', 'IS NULL')); + const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : ''; + + // UPDATE + const updateArray = this._query.update.reduce(this._reducer, []); + const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : ''; + + // INSERT + let insertRaw = ''; + + if (this._query.insert.length) { + const fieldsList = Object.keys(this._query.insert[0]); + const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`); + + insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `; + } + + // GROUP BY + const groupByArray = this._query.groupBy.reduce(this._reducer, []); + const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : ''; + + // ORDER BY + const orderByArray = this._query.orderBy.reduce(this._reducer, []); + const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; + + // LIMIT + const limitRaw = this._query.limit ? `LIMIT ${this._query.limit} ` : ''; + + // OFFSET + const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : ''; + + return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; + } + + async raw (sql: string, args?: antares.QueryParams) { + this._logger({ cUid: this._cUid, sql }); + + args = { + nest: false, + details: false, + split: true, + comments: true, + autocommit: true, + ...args + }; + + if (!args.comments) + sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments + + const resultsArr = []; + const paramsArr = []; + const queries = args.split + ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) + .filter(Boolean) + .map(q => q.trim()) + : [sql]; + + let connection: firebird.Database; + + if (!args.autocommit && args.tabUid) { // autocommit OFF + if (this._connectionsToCommit.has(args.tabUid)) + connection = this._connectionsToCommit.get(args.tabUid); + + else { + connection = await this.getConnection(); + await new Promise((resolve, reject) => { + connection.query('BEGIN TRANSACTION', [], (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + this._connectionsToCommit.set(args.tabUid, connection); + } + } + else// autocommit ON + connection = this._connection; + + for (const query of queries) { + if (!query) continue; + const timeStart = new Date(); + let timeStop; + const keysArr: antares.QueryForeign[] = []; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => { + (async () => { + let queryResult; + + try { + queryResult = await new Promise((resolve, reject) => { + connection.query(query, [], (err, res) => { + if (err) reject(err); + else { + const remappedResponse = []; + + for (const row of res) { + for (const key in row) { + if (Buffer.isBuffer(row[key])) + row[key] = row[key].toString('binary'); + } + + remappedResponse.push(row); + } + + resolve(remappedResponse); + } + }); + }); + } + catch (err) { + reject(err); + } + + timeStop = new Date(); + + const remappedFields = []; + + // if (args.details) { + + // } + + resolve({ + duration: timeStop.getTime() - timeStart.getTime(), + rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, + report: null, + fields: remappedFields, + keys: keysArr + }); + })(); + }); + + resultsArr.push({ rows, report, fields, keys, duration }); + } + + const result = resultsArr.length === 1 ? resultsArr[0] : resultsArr; + + return result as unknown as T; + } + + getVariables (): null[] { + return []; + } + + getCollations (): null[] { + return []; + } +} diff --git a/src/renderer/components/WorkspaceAddConnectionPanel.vue b/src/renderer/components/WorkspaceAddConnectionPanel.vue index 04a6892f..4f326647 100644 --- a/src/renderer/components/WorkspaceAddConnectionPanel.vue +++ b/src/renderer/components/WorkspaceAddConnectionPanel.vue @@ -415,7 +415,8 @@ const clients = [ { name: 'MySQL', slug: 'mysql' }, { name: 'MariaDB', slug: 'maria' }, { name: 'PostgreSQL', slug: 'pg' }, - { name: 'SQLite', slug: 'sqlite' } + { name: 'SQLite', slug: 'sqlite' }, + { name: 'FirebirdSQL', slug: 'firebird' } ]; const connection = ref({ diff --git a/src/renderer/components/WorkspaceEditConnectionPanel.vue b/src/renderer/components/WorkspaceEditConnectionPanel.vue index 2afd29f1..109545b8 100644 --- a/src/renderer/components/WorkspaceEditConnectionPanel.vue +++ b/src/renderer/components/WorkspaceEditConnectionPanel.vue @@ -428,7 +428,8 @@ const clients = [ { name: 'MySQL', slug: 'mysql' }, { name: 'MariaDB', slug: 'maria' }, { name: 'PostgreSQL', slug: 'pg' }, - { name: 'SQLite', slug: 'sqlite' } + { name: 'SQLite', slug: 'sqlite' }, + { name: 'FirebirdSQL', slug: 'firebird' } ]; const firstInput: Ref = ref(null); diff --git a/src/renderer/images/svg/firebird.svg b/src/renderer/images/svg/firebird.svg new file mode 100644 index 00000000..95e59c5d --- /dev/null +++ b/src/renderer/images/svg/firebird.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/scss/_db-icons.scss b/src/renderer/scss/_db-icons.scss index 98703f3c..db3bc7dc 100644 --- a/src/renderer/scss/_db-icons.scss +++ b/src/renderer/scss/_db-icons.scss @@ -25,6 +25,10 @@ background-image: url("../images/svg/sqlite.svg"); } + &.dbi-firebird { + background-image: url("../images/svg/firebird.svg"); + } + &.dbi-oracledb { background-image: url("../images/svg/oracledb.svg"); } From 95bb41e9db255a780aae1ae32ce4a53ee3bab20e Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Fri, 4 Nov 2022 16:31:10 +0100 Subject: [PATCH 02/14] feat(Firebird SQL): display table content and query results --- .vscode/settings.json | 1 + src/common/customizations/firebird.ts | 4 +- src/common/data-types/firebird.ts | 120 ++++++ src/common/index-types/firebird.ts | 5 + src/main/ipc-handlers/schema.ts | 2 +- src/main/libs/clients/FirebirdSQLClient.ts | 343 +++++++++++------- src/main/main.ts | 4 +- src/renderer/components/QueryEditor.vue | 4 +- .../WorkspaceAddConnectionPanel.vue | 2 +- .../WorkspaceEditConnectionPanel.vue | 2 +- src/renderer/stores/workspaces.ts | 5 + 11 files changed, 344 insertions(+), 148 deletions(-) create mode 100644 src/common/data-types/firebird.ts create mode 100644 src/common/index-types/firebird.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c0006a91..ebc16971 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "MySQL", "PostgreSQL", "SQLite", + "Firebird SQL", "Windows", "translation", "Linux", diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index fe34d43a..407a3990 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -20,7 +20,7 @@ export const customizations: Customizations = { variables: false, // Structure schemas: false, - tables: false, + tables: true, views: false, triggers: false, triggerFunctions: false, @@ -29,7 +29,7 @@ export const customizations: Customizations = { schedulers: false, // Settings elementsWrapper: '', - stringsWrapper: '"', + stringsWrapper: '\'', tableAdd: false, tableTruncateDisableFKCheck: false, viewAdd: false, diff --git a/src/common/data-types/firebird.ts b/src/common/data-types/firebird.ts new file mode 100644 index 00000000..d871286f --- /dev/null +++ b/src/common/data-types/firebird.ts @@ -0,0 +1,120 @@ +import { TypesGroup } from 'common/interfaces/antares'; + +export default [ + { + group: 'integer', + types: [ + { + name: 'SMALLINT', + length: true, + collation: false, + unsigned: true, + zerofill: true + }, + { + name: 'INTEGER', + length: true, + collation: false, + unsigned: true, + zerofill: true + }, + { + name: 'BIGINT', + length: true, + collation: false, + unsigned: true, + zerofill: true + } + ] + }, + { + group: 'float', + types: [ + { + name: 'DECIMAL', + length: true, + collation: false, + unsigned: false, + zerofill: false + }, + { + name: 'NUMERIC', + length: true, + collation: false, + unsigned: false, + zerofill: false + }, + { + name: 'FLOAT', + length: true, + collation: false, + unsigned: false, + zerofill: false + }, + { + name: 'DOUBLE PRECISION', + length: true, + 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 + } + ] + }, + { + group: 'binary', + types: [ + { + name: 'BLOB', + length: true, + 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: true, + collation: false, + unsigned: false, + zerofill: false + } + ] + } +] as TypesGroup[]; diff --git a/src/common/index-types/firebird.ts b/src/common/index-types/firebird.ts new file mode 100644 index 00000000..cb7fa05c --- /dev/null +++ b/src/common/index-types/firebird.ts @@ -0,0 +1,5 @@ +export default [ + 'PRIMARY', + 'INDEX', + 'UNIQUE' +]; diff --git a/src/main/ipc-handlers/schema.ts b/src/main/ipc-handlers/schema.ts index 8d45aa82..af5bc379 100644 --- a/src/main/ipc-handlers/schema.ts +++ b/src/main/ipc-handlers/schema.ts @@ -97,7 +97,7 @@ export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-engines', async (event, uid) => { try { - const result = await connections[uid].getEngines(); + const result: any = await connections[uid].getEngines(); return { status: 'success', response: result }; } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index a06533b3..bc350885 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -1,9 +1,9 @@ +import * as path from 'path'; import * as antares from 'common/interfaces/antares'; import * as firebird from 'node-firebird'; import { AntaresCore } from '../AntaresCore'; import dataTypes from 'common/data-types/sqlite'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; -import { promisify } from 'util'; export class FirebirdSQLClient extends AntaresCore { private _schema?: string; @@ -11,6 +11,25 @@ export class FirebirdSQLClient extends AntaresCore { protected _connection?: firebird.Database; _params: firebird.Options; + private types: {[key: number]: string} ={ + 452: 'CHAR', // Array of char + 448: 'VARCHAR', + 500: 'SMALLINT', + 496: 'INTEGER', + 482: 'FLOAT', + 480: 'DOUBLE', + 530: 'DOUBLE PRECISION', + 510: 'TIMESTAMP', + 520: 'BLOB', + 540: 'ARRAY', + 550: 'QUAD', + 560: 'TIME', + 570: 'DATE', + 580: 'BIGINT', + 32764: 'BOOLEAN', // >= 3.0 + 32766: 'NULL' // >= 2.5 + } + constructor (args: antares.ClientParams) { super(args); @@ -46,141 +65,151 @@ export class FirebirdSQLClient extends AntaresCore { } async getStructure (schemas: Set) { - /* eslint-disable camelcase */ interface ShowTableResult { - Db?: string; - type: string; - name: string; - tbl_name: string; - rootpage:4; - sql: string; + FORMAT: number; + NAME: string; + TYPE: string; + DESCRIPTION: string | null; } - type ShowTriggersResult = ShowTableResult - /* eslint-enable camelcase */ + // type ShowTriggersResult = ShowTableResult - const { rows: databases } = await this.raw>('SELECT * FROM pragma_database_list'); + const { rows: databases } = await this.raw>('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') as name FROM rdb$database'); - const filteredDatabases = databases; + const filteredDatabases = databases.map(db => { + return { name: path.basename(db.NAME) }; + }); const tablesArr: ShowTableResult[] = []; - const triggersArr: ShowTriggersResult[] = []; + // const triggersArr: ShowTriggersResult[] = []; let schemaSize = 0; for (const db of filteredDatabases) { - if (!schemas.has(db.name)) continue; + // if (!schemas.has(db.name)) continue; - let { rows: tables } = await this.raw>(` - SELECT * - FROM "${db.name}".sqlite_master - WHERE type IN ('table', 'view') - AND name NOT LIKE 'sqlite\\_%' ESCAPE '\\' - ORDER BY name + const { rows: tables } = await this.raw>(` + SELECT + rdb$relation_name AS name, + rdb$format AS format, + rdb$description AS description, + 'table' AS type + FROM RDB$RELATIONS a + WHERE COALESCE(RDB$SYSTEM_FLAG, 0) = 0 + AND RDB$RELATION_TYPE = 0 `); - if (tables.length) { - tables = tables.map(table => { - table.Db = db.name; - return table; - }); - tablesArr.push(...tables); - } - let { rows: triggers } = await this.raw>(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`); - if (triggers.length) { - triggers = triggers.map(trigger => { - trigger.Db = db.name; - return trigger; - }); - triggersArr.push(...triggers); - } + tablesArr.push(...tables); } return filteredDatabases.map(db => { - if (schemas.has(db.name)) { - // TABLES - const remappedTables = tablesArr.filter(table => table.Db === db.name).map(table => { - const tableSize = 0; - schemaSize += tableSize; - - return { - name: table.name, - type: table.type, - rows: false, - size: false - }; - }); - - // TRIGGERS - const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => { - return { - name: trigger.name, - table: trigger.tbl_name - }; - }); + // TABLES + const remappedTables = tablesArr.map(table => { + const tableSize = 0; + schemaSize += tableSize; return { - name: db.name, - size: schemaSize, - tables: remappedTables, - functions: [], - procedures: [], - triggers: remappedTriggers, - schedulers: [] + name: table.NAME.trim(), + type: table.TYPE.trim(), + rows: false, + size: false }; - } - else { - return { - name: db.name, - size: 0, - tables: [], - functions: [], - procedures: [], - triggers: [], - schedulers: [] - }; - } + }); + + // TRIGGERS + // const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => { + // return { + // name: trigger.name, + // table: trigger.tbl_name + // }; + // }); + + return { + name: db.name, + size: schemaSize, + tables: remappedTables, + functions: [], + procedures: [], + triggers: [], + schedulers: [] + }; }); } async getTableColumns ({ schema, table }: { schema: string; table: string }) { interface TableColumnsResult { - cid: number; - name: string; - type: string; - notnull: 0 | 1; - // eslint-disable-next-line camelcase - dflt_value: string; - pk: 0 | 1; + POSITION: number; + DESCRIPTION?: string; + /* eslint-disable camelcase */ + FIELD_NAME: string; + FIELD_TYPE: string; + NOT_NULL: 0 | 1; + DEFAULT_VALUE: string; + FIELD_LENGTH: number; + FIELD_PRECISION: number; + FIELD_SCALE: number; + /* eslint-enable camelcase */ + SUBTYPE: string; + COLLATION: string; + CHARSET: string; } - const { rows: fields } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}')`); + + const { rows: fields } = await this.raw>(` + SELECT + r.RDB$FIELD_NAME AS field_name, + r.RDB$DESCRIPTION AS description, + r.RDB$DEFAULT_VALUE AS default_value, + r.RDB$NULL_FLAG AS not_null, + f.RDB$FIELD_LENGTH AS field_length, + f.RDB$FIELD_PRECISION AS field_precision, + f.RDB$FIELD_SCALE AS field_scale, + CASE f.RDB$FIELD_TYPE + WHEN 261 THEN 'BLOB' + WHEN 14 THEN 'CHAR' + WHEN 40 THEN 'CSTRING' + WHEN 11 THEN 'D_FLOAT' + WHEN 27 THEN 'DOUBLE' + WHEN 10 THEN 'FLOAT' + WHEN 16 THEN 'INT64' + WHEN 8 THEN 'INTEGER' + WHEN 9 THEN 'QUAD' + WHEN 7 THEN 'SMALLINT' + WHEN 12 THEN 'DATE' + WHEN 13 THEN 'TIME' + WHEN 35 THEN 'TIMESTAMP' + WHEN 37 THEN 'VARCHAR' + ELSE 'UNKNOWN' + END AS field_type, + f.RDB$FIELD_SUB_TYPE AS subtype, + -- coll.RDB$COLLATION_NAME AS collation, + cset.RDB$CHARACTER_SET_NAME AS charset + FROM RDB$RELATION_FIELDS r + LEFT JOIN RDB$FIELDS f ON r.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME + -- LEFT JOIN RDB$COLLATIONS coll ON f.RDB$COLLATION_ID = coll.RDB$COLLATION_ID + LEFT JOIN RDB$CHARACTER_SETS cset ON f.RDB$CHARACTER_SET_ID = cset.RDB$CHARACTER_SET_ID + WHERE r.RDB$RELATION_NAME='${table}' + ORDER BY r.RDB$FIELD_POSITION; + `); return fields.map(field => { - const [type, length]: [string, number?] = field.type.includes('(') - ? field.type.replace(')', '').split('(').map((el: string | number) => { - if (!isNaN(Number(el))) el = Number(el); - return el; - }) as [string, number?] - : [field.type, null]; - return { - name: field.name, + name: field.FIELD_NAME.trim(), key: null, - type: type.trim(), + type: field.FIELD_TYPE.trim(), schema: schema, table: table, - numPrecision: [...NUMBER, ...FLOAT].includes(type) ? length : null, + numPrecision: field.FIELD_PRECISION, datePrecision: null, - charLength: ![...NUMBER, ...FLOAT].includes(type) ? length : null, - nullable: !field.notnull, + charLength: field.FIELD_LENGTH, + nullable: !field.NOT_NULL, unsigned: null, zerofill: null, - order: typeof field.cid === 'string' ? +field.cid + 1 : field.cid + 1, - default: field.dflt_value, - charset: null, + order: field.POSITION, + default: field.DEFAULT_VALUE, + charset: field.CHARSET, collation: null, autoIncrement: false, onUpdate: null, - comment: '' + comment: field.DESCRIPTION?.trim() }; }); } @@ -393,17 +422,17 @@ export class FirebirdSQLClient extends AntaresCore { } async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy - const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`; + const sql = `CREATE TABLE '${params.table}_copy' AS SELECT * FROM '${params.table}'`; return await this.raw(sql); } async truncateTable (params: { schema: string; table: string }) { - const sql = `DELETE FROM "${params.schema}"."${params.table}"`; + const sql = `DELETE FROM '${params.table}'`; return await this.raw(sql); } async dropTable (params: { schema: string; table: string }) { - const sql = `DROP TABLE "${params.schema}"."${params.table}"`; + const sql = `DROP TABLE '${params.table}'`; return await this.raw(sql); } @@ -420,7 +449,7 @@ export class FirebirdSQLClient extends AntaresCore { } async dropView (params: { schema: string; view: string }) { - const sql = `DROP VIEW "${params.schema}"."${params.view}"`; + const sql = `DROP VIEW '${params.view}'`; return await this.raw(sql); } @@ -435,7 +464,7 @@ export class FirebirdSQLClient extends AntaresCore { } async createView (params: antares.CreateViewParams) { - const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; + const sql = `CREATE VIEW '${params.name}' AS ${params.sql}`; return await this.raw(sql); } @@ -489,15 +518,19 @@ export class FirebirdSQLClient extends AntaresCore { } async getVersion () { - const os = require('os'); - const sql = 'SELECT sqlite_version() AS version'; + const sql = ` + SELECT + rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version, + rdb$get_context('SYSTEM', 'NETWORK_PROTOCOL') as protocol, + RDB$GET_CONTEXT('SYSTEM', 'CLIENT_ADDRESS') AS address + FROM rdb$database`; const { rows } = await this.raw(sql); return { number: rows[0].version, - name: 'SQLite', - arch: process.arch, - os: `${os.type()} ${os.release()}` + name: 'Firebird SQL', + arch: rows[0].protocol, + os: rows[0].address }; } @@ -509,37 +542,40 @@ export class FirebirdSQLClient extends AntaresCore { return null; } - async commitTab (tabUid: string) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.prepare('COMMIT').run(); - return this.destroyConnectionToCommit(tabUid); - } - } + // async commitTab (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.prepare('COMMIT').run(); + // return this.destroyConnectionToCommit(tabUid); + // } + // } - async rollbackTab (tabUid: string) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.prepare('ROLLBACK').run(); - return this.destroyConnectionToCommit(tabUid); - } - } + // async rollbackTab (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.prepare('ROLLBACK').run(); + // return this.destroyConnectionToCommit(tabUid); + // } + // } - destroyConnectionToCommit (tabUid: string) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.close(); - this._connectionsToCommit.delete(tabUid); - } - } + // destroyConnectionToCommit (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.close(); + // this._connectionsToCommit.delete(tabUid); + // } + // } getSQL () { + // LIMIT + const limitRaw = this._query.limit ? ` first ${this._query.limit}` : ''; + // SELECT const selectArray = this._query.select.reduce(this._reducer, []); let selectRaw = ''; if (selectArray.length) - selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * '; + selectRaw = selectArray.length ? `SELECT${limitRaw||''} ${selectArray.join(', ')} ` : `SELECT${limitRaw||''} * `; // FROM let fromRaw = ''; @@ -549,7 +585,7 @@ export class FirebirdSQLClient extends AntaresCore { else if (Object.keys(this._query.insert).length) fromRaw = 'INTO'; - fromRaw += this._query.from ? ` ${this._query.schema ? `"${this._query.schema}".` : ''}"${this._query.from}" ` : ''; + fromRaw += this._query.from ? ` ${this._query.from} ` : ''; // WHERE const whereArray = this._query.where @@ -579,16 +615,24 @@ export class FirebirdSQLClient extends AntaresCore { const orderByArray = this._query.orderBy.reduce(this._reducer, []); const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; - // LIMIT - const limitRaw = this._query.limit ? `LIMIT ${this._query.limit} ` : ''; - // OFFSET const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : ''; - return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; + return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${offsetRaw}${insertRaw}`; } async raw (sql: string, args?: antares.QueryParams) { + interface FieldData { + type: number; + nullable: boolean; + subType: number; + scale: number; + length: number; + field: string; + relation: string; + alias: string; + } + this._logger({ cUid: this._cUid, sql }); args = { @@ -604,7 +648,6 @@ export class FirebirdSQLClient extends AntaresCore { sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments const resultsArr = []; - const paramsArr = []; const queries = args.split ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) .filter(Boolean) @@ -641,16 +684,23 @@ export class FirebirdSQLClient extends AntaresCore { const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => { (async () => { let queryResult; + let remappedFields; try { queryResult = await new Promise((resolve, reject) => { - connection.query(query, [], (err, res) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (connection as any).query(query, [], (err: any, res: any, fields: FieldData[]) => { // <- fields is not natively typed or documented if (err) reject(err); else { const remappedResponse = []; for (const row of res) { for (const key in row) { + const fieldData = fields.find(({ alias }) => alias === key); + + if (fieldData.type === 520 && fieldData.subType === 1)// TODO: handle BLOB subType 1 + row[key] = row[key]?.toString(); + if (Buffer.isBuffer(row[key])) row[key] = row[key].toString('binary'); } @@ -658,6 +708,21 @@ export class FirebirdSQLClient extends AntaresCore { remappedResponse.push(row); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + remappedFields = fields.map((field: any) => { + return { + name: field.alias, + alias: field.alias, + orgName: field.field, + schema: args.schema, + table: field.relation, + tableAlias: field.relation, + orgTable: field.relation, + type: this.types[field.type], + length: field.length + }; + }); + resolve(remappedResponse); } }); @@ -669,8 +734,6 @@ export class FirebirdSQLClient extends AntaresCore { timeStop = new Date(); - const remappedFields = []; - // if (args.details) { // } diff --git a/src/main/main.ts b/src/main/main.ts index 66848df7..2a8dea17 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -123,8 +123,8 @@ else { if (isWindows) mainWindow.show(); - if (isDevelopment) - mainWindow.webContents.openDevTools(); + // if (isDevelopment) + // mainWindow.webContents.openDevTools(); process.on('uncaughtException', error => { mainWindow.webContents.send('unhandled-exception', error); diff --git a/src/renderer/components/QueryEditor.vue b/src/renderer/components/QueryEditor.vue index 1f92720d..c9360348 100644 --- a/src/renderer/components/QueryEditor.vue +++ b/src/renderer/components/QueryEditor.vue @@ -240,7 +240,9 @@ watch(() => tablesInQuery.value.length, () => { }); fields.value = localFields; - setCustomCompleter(); + setTimeout(() => { + setCustomCompleter(); + }, 100); }); watch(editorTheme, () => { diff --git a/src/renderer/components/WorkspaceAddConnectionPanel.vue b/src/renderer/components/WorkspaceAddConnectionPanel.vue index 4f326647..e63adf37 100644 --- a/src/renderer/components/WorkspaceAddConnectionPanel.vue +++ b/src/renderer/components/WorkspaceAddConnectionPanel.vue @@ -416,7 +416,7 @@ const clients = [ { name: 'MariaDB', slug: 'maria' }, { name: 'PostgreSQL', slug: 'pg' }, { name: 'SQLite', slug: 'sqlite' }, - { name: 'FirebirdSQL', slug: 'firebird' } + { name: 'Firebird SQL (experimental)', slug: 'firebird' } ]; const connection = ref({ diff --git a/src/renderer/components/WorkspaceEditConnectionPanel.vue b/src/renderer/components/WorkspaceEditConnectionPanel.vue index 109545b8..4a1c0047 100644 --- a/src/renderer/components/WorkspaceEditConnectionPanel.vue +++ b/src/renderer/components/WorkspaceEditConnectionPanel.vue @@ -429,7 +429,7 @@ const clients = [ { name: 'MariaDB', slug: 'maria' }, { name: 'PostgreSQL', slug: 'pg' }, { name: 'SQLite', slug: 'sqlite' }, - { name: 'FirebirdSQL', slug: 'firebird' } + { name: 'Firebird SQL (experimental)', slug: 'firebird' } ]; const firstInput: Ref = ref(null); diff --git a/src/renderer/stores/workspaces.ts b/src/renderer/stores/workspaces.ts index 5b3e14c7..9241f5dc 100644 --- a/src/renderer/stores/workspaces.ts +++ b/src/renderer/stores/workspaces.ts @@ -200,6 +200,11 @@ export const useWorkspacesStore = defineStore('workspaces', { indexTypes = require('common/index-types/sqlite').default; clientCustomizations = customizations.sqlite; break; + case 'firebird': + dataTypes = require('common/data-types/firebird').default; + indexTypes = require('common/index-types/firebird').default; + clientCustomizations = customizations.firebird; + break; } const { status, response: version } = await Schema.getVersion(connection.uid); From e6f6a022d1a5bbc3f5303f635a2115813601c61a Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Sat, 5 Nov 2022 10:22:12 +0100 Subject: [PATCH 03/14] feat: support to text blob fields --- package-lock.json | 27 ++++++++++++++----- package.json | 2 +- src/common/customizations/defaults.ts | 3 ++- src/common/customizations/firebird.ts | 3 ++- src/common/interfaces/customizations.ts | 1 + src/main/libs/clients/FirebirdSQLClient.ts | 18 +++++-------- .../components/WorkspaceTabQueryTable.vue | 2 ++ .../components/WorkspaceTabQueryTableRow.vue | 13 ++++----- 8 files changed, 42 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9d7a40c..e733ebf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", - "node-firebird": "~0.9.9", + "node-firebird": "github:Fabio286/node-firebird", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", @@ -4133,6 +4133,14 @@ "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": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -11276,10 +11284,10 @@ } }, "node_modules/node-firebird": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-0.9.9.tgz", - "integrity": "sha512-nH4zIaglr+/J0E8W9YTiJchpFbjO7gc0ExDtzZmFXp9iLpmyuq+9RFEJoePT2WE39kkx+DNYO7J7TTHBpOmitQ==", + "version": "1.1.2", + "resolved": "git+ssh://git@github.com/Fabio286/node-firebird.git#28059a4aff44fc4b9ce69699bb1ad14f837de692", "dependencies": { + "big-integer": "^1.6.48", "long": "^4.0.0" } }, @@ -19902,6 +19910,11 @@ "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": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -25348,10 +25361,10 @@ } }, "node-firebird": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-0.9.9.tgz", - "integrity": "sha512-nH4zIaglr+/J0E8W9YTiJchpFbjO7gc0ExDtzZmFXp9iLpmyuq+9RFEJoePT2WE39kkx+DNYO7J7TTHBpOmitQ==", + "version": "git+ssh://git@github.com/Fabio286/node-firebird.git#28059a4aff44fc4b9ce69699bb1ad14f837de692", + "from": "node-firebird@https://github.com/Fabio286/node-firebird", "requires": { + "big-integer": "^1.6.48", "long": "^4.0.0" } }, diff --git a/package.json b/package.json index ad426e4f..28061b34 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", - "node-firebird": "~0.9.9", + "node-firebird": "github:Fabio286/node-firebird", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", diff --git a/src/common/customizations/defaults.ts b/src/common/customizations/defaults.ts index 26d81697..453eed17 100644 --- a/src/common/customizations/defaults.ts +++ b/src/common/customizations/defaults.ts @@ -90,5 +90,6 @@ export const defaults: Customizations = { triggerFunctionlanguages: null, parametersLength: false, languages: null, - readOnlyMode: false + readOnlyMode: false, + blobAsText: false }; diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index 407a3990..1ba1ca8f 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -90,5 +90,6 @@ export const customizations: Customizations = { triggerFunctionlanguages: null, parametersLength: false, languages: null, - readOnlyMode: false + readOnlyMode: false, + blobAsText: true }; diff --git a/src/common/interfaces/customizations.ts b/src/common/interfaces/customizations.ts index 4a16ffbd..c0debd59 100644 --- a/src/common/interfaces/customizations.ts +++ b/src/common/interfaces/customizations.ts @@ -89,4 +89,5 @@ export interface Customizations { parametersLength?: boolean; languages?: string[]; readOnlyMode?: boolean; + blobAsText: boolean; } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index bc350885..2d4ca2a4 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -3,7 +3,6 @@ import * as antares from 'common/interfaces/antares'; import * as firebird from 'node-firebird'; import { AntaresCore } from '../AntaresCore'; import dataTypes from 'common/data-types/sqlite'; -import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; export class FirebirdSQLClient extends AntaresCore { private _schema?: string; @@ -49,7 +48,7 @@ export class FirebirdSQLClient extends AntaresCore { async getConnection () { return new Promise((resolve, reject) => { - firebird.attach(this._params, (err, db) => { + firebird.attach({ ...this._params, blobAsText: true }, (err, db) => { if (err) reject(err); else resolve(db); }); @@ -64,7 +63,7 @@ export class FirebirdSQLClient extends AntaresCore { return null; } - async getStructure (schemas: Set) { + async getStructure (_schemas: Set) { interface ShowTableResult { FORMAT: number; NAME: string; @@ -527,10 +526,10 @@ export class FirebirdSQLClient extends AntaresCore { const { rows } = await this.raw(sql); return { - number: rows[0].version, + number: rows[0].VERSION, name: 'Firebird SQL', - arch: rows[0].protocol, - os: rows[0].address + arch: rows[0].PROTOCOL, + os: rows[0].ADDRESS }; } @@ -696,13 +695,10 @@ export class FirebirdSQLClient extends AntaresCore { for (const row of res) { for (const key in row) { - const fieldData = fields.find(({ alias }) => alias === key); - - if (fieldData.type === 520 && fieldData.subType === 1)// TODO: handle BLOB subType 1 - row[key] = row[key]?.toString(); - if (Buffer.isBuffer(row[key])) row[key] = row[key].toString('binary'); + else if (typeof row[key] === 'function') + row[key] = row[key].toString('binary'); } remappedResponse.push(row); diff --git a/src/renderer/components/WorkspaceTabQueryTable.vue b/src/renderer/components/WorkspaceTabQueryTable.vue index b886d795..f204b1be 100644 --- a/src/renderer/components/WorkspaceTabQueryTable.vue +++ b/src/renderer/components/WorkspaceTabQueryTable.vue @@ -81,6 +81,7 @@ :fields="fieldsObj" :key-usage="keyUsage" :element-type="elementType" + :blob-as-text="blobAsText" :class="{'selected': selectedRows.includes(row._antares_id)}" :selected="selectedRows.includes(row._antares_id)" :selected-cell="selectedRows.length === 1 && selectedRows.includes(row._antares_id) ? selectedField : null" @@ -182,6 +183,7 @@ const isEditingRow = ref(false); const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema); const workspaceClient = computed(() => getWorkspace(props.connUid).client); +const blobAsText = computed(() => getWorkspace(props.connUid).customizations.blobAsText); const primaryField = computed(() => { const primaryFields = fields.value.filter(field => field.key === 'pri'); diff --git a/src/renderer/components/WorkspaceTabQueryTableRow.vue b/src/renderer/components/WorkspaceTabQueryTableRow.vue index 98af44ff..42cc1c56 100644 --- a/src/renderer/components/WorkspaceTabQueryTableRow.vue +++ b/src/renderer/components/WorkspaceTabQueryTableRow.vue @@ -243,7 +243,8 @@ const props = defineProps({ itemHeight: Number, elementType: { type: String, default: 'table' }, selected: { type: Boolean, default: false }, - selectedCell: { type: String, default: null } + selectedCell: { type: String, default: null }, + blobAsText: { type: Boolean, default: false } }); const emit = defineEmits(['update-field', 'select-row', 'contextmenu', 'start-editing', 'stop-editing']); @@ -318,7 +319,7 @@ const inputProps = computed(() => { return { type: 'text', mask: datetimeMask }; } - if (BLOB.includes(editingType.value)) + if (BLOB.includes(editingType.value) && !props.blobAsText) return { type: 'file', mask: false }; if (BOOLEAN.includes(editingType.value)) @@ -398,7 +399,7 @@ const editON = async (field: string) => { editingField.value = field; editingLength.value = props.fields[field].length; - if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type)) { + if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type) || (BLOB.includes(type) && props.blobAsText)) { isTextareaEditor.value = true; editingContent.value = typeFormat(content, type); emit('start-editing', field); @@ -415,7 +416,7 @@ const editON = async (field: string) => { return; } - if (BLOB.includes(type)) { + if (BLOB.includes(type) && !props.blobAsText) { isBlobEditor.value = true; editingContent.value = content || ''; fileToUpload.value = null; @@ -454,7 +455,7 @@ const editOFF = () => { isInlineEditor.value[editingField.value] = false; let content; - if (!BLOB.includes(editingType.value)) { + if (!BLOB.includes(editingType.value) || (BLOB.includes(editingType.value) && props.blobAsText)) { if ([...DATETIME, ...TIME].includes(editingType.value)) { if (editingContent.value !== null && editingContent.value.substring(editingContent.value.length - 1) === '.') editingContent.value = editingContent.value.slice(0, -1); @@ -588,7 +589,7 @@ const typeFormat = (val: string | number | Date | number[], type: string, precis return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val; } - if (BLOB.includes(type)) { + if (BLOB.includes(type) && !props.blobAsText) { const buff = Buffer.from(val as string); if (!buff.length) return ''; From 76df6319c242ea42f93d4e5d811d96ec2c282aa8 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Mon, 7 Nov 2022 09:49:36 +0100 Subject: [PATCH 04/14] feat(Firebird SQL): connections pool --- package-lock.json | 12 +++++++----- package.json | 2 +- src/main/libs/clients/FirebirdSQLClient.ts | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e733ebf5..b7d6fb35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", - "node-firebird": "github:Fabio286/node-firebird", + "node-firebird": "~1.1.3", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", @@ -11284,8 +11284,9 @@ } }, "node_modules/node-firebird": { - "version": "1.1.2", - "resolved": "git+ssh://git@github.com/Fabio286/node-firebird.git#28059a4aff44fc4b9ce69699bb1ad14f837de692", + "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" @@ -25361,8 +25362,9 @@ } }, "node-firebird": { - "version": "git+ssh://git@github.com/Fabio286/node-firebird.git#28059a4aff44fc4b9ce69699bb1ad14f837de692", - "from": "node-firebird@https://github.com/Fabio286/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" diff --git a/package.json b/package.json index 28061b34..d9a617a1 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "marked": "~4.0.19", "moment": "~2.29.4", "mysql2": "~2.3.2", - "node-firebird": "github:Fabio286/node-firebird", + "node-firebird": "~1.1.3", "pg": "~8.7.1", "pg-connection-string": "~2.5.0", "pg-query-stream": "~4.2.3", diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index 2d4ca2a4..2823fcfc 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -43,7 +43,10 @@ export class FirebirdSQLClient extends AntaresCore { } async connect () { - this._connection = await this.getConnection(); + if (!this._poolSize) + this._connection = await this.getConnection(); + else + this._connection = await this.getConnectionPool(); } async getConnection () { @@ -55,8 +58,18 @@ export class FirebirdSQLClient extends AntaresCore { }); } + async getConnectionPool () { + const pool = firebird.pool(this._poolSize, { ...this._params, blobAsText: true }); + return new Promise((resolve, reject) => { + pool.get((err, db) => { + if (err) reject(err); + else resolve(db); + }); + }); + } + destroy () { - return this._connection.detach(); + return (this._connection as firebird.Database).detach(); } use (): void { @@ -726,6 +739,7 @@ export class FirebirdSQLClient extends AntaresCore { } catch (err) { reject(err); + this._connection.detach(); } timeStop = new Date(); From 2c8509ff4173fbebeff92ab472a37edd3d80a2ac Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 8 Nov 2022 14:05:54 +0100 Subject: [PATCH 05/14] feat(Firebird SQL): support to indexes and foreign keys --- src/common/customizations/firebird.ts | 2 +- src/common/index-types/firebird.ts | 1 - src/main/ipc-handlers/tables.ts | 19 +- src/main/libs/clients/FirebirdSQLClient.ts | 291 ++++++++++++------ .../components/WorkspaceTabQueryTable.vue | 2 + src/renderer/scss/_table-keys.scss | 1 + 6 files changed, 213 insertions(+), 103 deletions(-) diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index 1ba1ca8f..293a3ec5 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -22,7 +22,7 @@ export const customizations: Customizations = { schemas: false, tables: true, views: false, - triggers: false, + triggers: true, triggerFunctions: false, routines: false, functions: false, diff --git a/src/common/index-types/firebird.ts b/src/common/index-types/firebird.ts index cb7fa05c..6f04d423 100644 --- a/src/common/index-types/firebird.ts +++ b/src/common/index-types/firebird.ts @@ -1,5 +1,4 @@ export default [ 'PRIMARY', - 'INDEX', 'UNIQUE' ]; diff --git a/src/main/ipc-handlers/tables.ts b/src/main/ipc-handlers/tables.ts index ea2bd77d..3c105548 100644 --- a/src/main/ipc-handlers/tables.ts +++ b/src/main/ipc-handlers/tables.ts @@ -105,6 +105,7 @@ export default (connections: {[key: string]: antares.Client}) => { break; case 'pg': case 'sqlite': + case 'firebird': escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; break; } @@ -124,6 +125,7 @@ export default (connections: {[key: string]: antares.Client}) => { escapedParam = `0x${fileBlob.toString('hex')}`; break; case 'pg': + case 'firebird': fileBlob = fs.readFileSync(params.content); escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; break; @@ -141,6 +143,7 @@ export default (connections: {[key: string]: antares.Client}) => { escapedParam = '\'\''; break; case 'pg': + case 'firebird': escapedParam = 'decode(\'\', \'hex\')'; break; case 'sqlite': @@ -158,6 +161,7 @@ export default (connections: {[key: string]: antares.Client}) => { case 'mysql': case 'maria': case 'pg': + case 'firebird': escapedParam = params.content; break; case 'sqlite': @@ -382,7 +386,20 @@ export default (connections: {[key: string]: antares.Client}) => { if (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 }; } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index 2823fcfc..6f4bc4eb 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -6,6 +6,7 @@ import dataTypes from 'common/data-types/sqlite'; export class FirebirdSQLClient extends AntaresCore { private _schema?: string; + private _runningConnections: Map; private _connectionsToCommit: Map; protected _connection?: firebird.Database; _params: firebird.Options; @@ -69,7 +70,7 @@ export class FirebirdSQLClient extends AntaresCore { } destroy () { - return (this._connection as firebird.Database).detach(); + return this._connection.detach(); } use (): void { @@ -84,7 +85,11 @@ export class FirebirdSQLClient extends AntaresCore { DESCRIPTION: string | null; } - // type ShowTriggersResult = ShowTableResult + interface ShowTriggersResult { + NAME: string; + RELATION: string; + SOURCE: string; + } const { rows: databases } = await this.raw>('SELECT rdb$get_context(\'SYSTEM\', \'DB_NAME\') as name FROM rdb$database'); @@ -93,10 +98,11 @@ export class FirebirdSQLClient extends AntaresCore { }); const tablesArr: ShowTableResult[] = []; - // const triggersArr: ShowTriggersResult[] = []; + const triggersArr: ShowTriggersResult[] = []; let schemaSize = 0; - for (const db of filteredDatabases) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const _db of filteredDatabases) { // if (!schemas.has(db.name)) continue; const { rows: tables } = await this.raw>(` @@ -111,6 +117,17 @@ export class FirebirdSQLClient extends AntaresCore { `); tablesArr.push(...tables); + + const { rows: triggers } = await this.raw>(` + SELECT + RDB$TRIGGER_NAME as name, + RDB$RELATION_NAME as relation, + RDB$TRIGGER_SOURCE as source + FROM RDB$TRIGGERS + WHERE RDB$SYSTEM_FLAG=0; + `); + + triggersArr.push(...triggers); } return filteredDatabases.map(db => { @@ -128,12 +145,13 @@ export class FirebirdSQLClient extends AntaresCore { }); // TRIGGERS - // const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.name).map(trigger => { - // return { - // name: trigger.name, - // table: trigger.tbl_name - // }; - // }); + const remappedTriggers = triggersArr.map(trigger => { + return { + name: trigger.NAME, + table: trigger.RELATION, + statement: trigger.SOURCE + }; + }); return { name: db.name, @@ -141,7 +159,7 @@ export class FirebirdSQLClient extends AntaresCore { tables: remappedTables, functions: [], procedures: [], - triggers: [], + triggers: remappedTriggers, schedulers: [] }; }); @@ -226,97 +244,111 @@ export class FirebirdSQLClient extends AntaresCore { }); } - async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise { - const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`); + async getTableApproximateCount ({ table }: { schema: string; table: string }): Promise { + const { rows } = await this.raw(`SELECT COUNT(*) AS nRows FROM "${table}"`); - return rows.length ? rows[0].count : 0; + return rows.length ? rows[0].NROWS : 0; } async getTableOptions ({ table }: { table: string }) { return { name: table }; } - async getTableIndexes ({ schema, table }: { schema: string; table: string }) { - interface TableColumnsResult { - type: string; - name: string; - // eslint-disable-next-line camelcase - tbl_name: string; - rootpage:4; - sql: string; - } - + async getTableIndexes ({ table }: { schema: string; table: string }) { interface ShowIndexesResult { - seq: number; - name: string; - unique: 0 | 1; - origin: string; - partial: 0 | 1; + INDEX_NAME: string; + FIELD_NAME: string; + TABLE_NAME: string; + INDEX_TYPE: string; + INDEX_UNIQUE: number; } const remappedIndexes = []; - const { rows: primaryKeys } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`); - for (const key of primaryKeys) { + const { rows: indexes } = await this.raw>(` + SELECT + ix.rdb$index_name AS INDEX_NAME, + sg.rdb$field_name AS FIELD_NAME, + rc.rdb$relation_name AS TABLE_NAME, + rc.rdb$constraint_type AS INDEX_TYPE, + ix.RDB$UNIQUE_FLAG AS INDEX_UNIQUE + FROM + rdb$indices ix + LEFT JOIN rdb$index_segments sg ON ix.rdb$index_name = sg.rdb$index_name + LEFT JOIN rdb$relation_constraints rc ON rc.rdb$index_name = ix.rdb$index_name + WHERE + rc.rdb$relation_name = '${table}' + `); + + for (const index of indexes) { remappedIndexes.push({ - name: 'PRIMARY', - column: key.name, + name: index.INDEX_NAME.trim(), + column: index.FIELD_NAME.trim(), indexType: null as never, - type: 'PRIMARY', + type: index.INDEX_TYPE.trim(), cardinality: null as never, comment: '', indexComment: '' }); } - const { rows: indexes } = await this.raw>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`); - - for (const index of indexes) { - const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`); - - for (const detail of details) { - remappedIndexes.push({ - name: index.name, - column: detail.name, - indexType: null as never, - type: index.unique === 1 ? 'UNIQUE' : 'INDEX', - cardinality: null as never, - comment: '', - indexComment: '' - }); - } - } - return remappedIndexes; } async getKeyUsage ({ schema, table }: { schema: string; table: string }) { /* eslint-disable camelcase */ interface KeyResult { - from: string; - id: number; - table: string; - to: string; - on_update: string; - on_delete: string; + PKTABLE_NAME: string; + PKCOLUMN_NAME: string; + FKTABLE_NAME: string; + FKCOLUMN_NAME: string; + KEY_SEQ: number; + UPDATE_RULE: string; + DELETE_RULE: string; + PK_NAME: string; + FK_NAME: string; } /* eslint-enable camelcase */ - const { rows } = await this.raw>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`); + const { rows } = await this.raw>(` + SELECT + PK.RDB$RELATION_NAME as PKTABLE_NAME, + ISP.RDB$FIELD_NAME as PKCOLUMN_NAME, + FK.RDB$RELATION_NAME as FKTABLE_NAME, + ISF.RDB$FIELD_NAME as FKCOLUMN_NAME, + (ISP.RDB$FIELD_POSITION + 1) as KEY_SEQ, + RC.RDB$UPDATE_RULE as UPDATE_RULE, + RC.RDB$DELETE_RULE as DELETE_RULE, + PK.RDB$CONSTRAINT_NAME as PK_NAME, + FK.RDB$CONSTRAINT_NAME as FK_NAME + FROM + RDB$RELATION_CONSTRAINTS PK, + RDB$RELATION_CONSTRAINTS FK, + RDB$REF_CONSTRAINTS RC, + RDB$INDEX_SEGMENTS ISP, + RDB$INDEX_SEGMENTS ISF + WHERE FK.RDB$RELATION_NAME = '${table}' + and FK.RDB$CONSTRAINT_NAME = RC.RDB$CONSTRAINT_NAME + and PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ + and ISP.RDB$INDEX_NAME = PK.RDB$INDEX_NAME + and ISF.RDB$INDEX_NAME = FK.RDB$INDEX_NAME + and ISP.RDB$FIELD_POSITION = ISF.RDB$FIELD_POSITION + ORDER BY 1, 5 + `); return rows.map(field => { return { schema: schema, table: table, - field: field.from, - position: field.id + 1, + field: field.FKCOLUMN_NAME.trim(), + position: field.KEY_SEQ, constraintPosition: null, - constraintName: field.id, + constraintName: field.FK_NAME.trim(), refSchema: schema, - refTable: field.table, - refField: field.to, - onUpdate: field.on_update, - onDelete: field.on_delete + refTable: field.PKTABLE_NAME.trim(), + refField: field.PKCOLUMN_NAME.trim(), + onUpdate: field.UPDATE_RULE.trim(), + onDelete: field.DELETE_RULE.trim() }; }); } @@ -582,12 +614,15 @@ export class FirebirdSQLClient extends AntaresCore { // LIMIT const limitRaw = this._query.limit ? ` first ${this._query.limit}` : ''; + // OFFSET + const offsetRaw = this._query.offset ? ` skip ${this._query.offset}` : ''; + // SELECT const selectArray = this._query.select.reduce(this._reducer, []); let selectRaw = ''; if (selectArray.length) - selectRaw = selectArray.length ? `SELECT${limitRaw||''} ${selectArray.join(', ')} ` : `SELECT${limitRaw||''} * `; + selectRaw = selectArray.length ? `SELECT${limitRaw||''}${offsetRaw||''} ${selectArray.join(', ')} ` : `SELECT${limitRaw||''}${offsetRaw||''} * `; // FROM let fromRaw = ''; @@ -627,10 +662,7 @@ export class FirebirdSQLClient extends AntaresCore { const orderByArray = this._query.orderBy.reduce(this._reducer, []); const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; - // OFFSET - const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : ''; - - return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${offsetRaw}${insertRaw}`; + return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${insertRaw}`; } async raw (sql: string, args?: antares.QueryParams) { @@ -660,6 +692,7 @@ export class FirebirdSQLClient extends AntaresCore { sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments const resultsArr = []; + let paramsArr = []; const queries = args.split ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) .filter(Boolean) @@ -690,52 +723,114 @@ export class FirebirdSQLClient extends AntaresCore { if (!query) continue; const timeStart = new Date(); let timeStop; - const keysArr: antares.QueryForeign[] = []; + let keysArr: antares.QueryForeign[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const { rows, report, fields, keys, duration }: any = await new Promise((resolve, reject) => { (async () => { let queryResult; - let remappedFields; + let remappedFields: { + name: string; + alias: string; + orgName: string; + schema: string; + table: string; + tableAlias: string; + orgTable: string; + type: string; + length: number; + key?: string; + }[]; try { queryResult = await new Promise((resolve, reject) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).query(query, [], (err: any, res: any, fields: FieldData[]) => { // <- fields is not natively typed or documented + (connection as any).query(query, [], async (err: any, res: any, fields: FieldData[]) => { // <- fields is not natively typed or documented if (err) reject(err); else { const remappedResponse = []; - for (const row of res) { - for (const key in row) { - if (Buffer.isBuffer(row[key])) - row[key] = row[key].toString('binary'); - else if (typeof row[key] === 'function') - row[key] = row[key].toString('binary'); - } + if (res) { + for (const row of res) { + for (const key in row) { + if (Buffer.isBuffer(row[key])) + row[key] = row[key].toString('binary'); + else if (typeof row[key] === 'function') + row[key] = row[key].toString('binary'); + } - remappedResponse.push(row); + remappedResponse.push(row); + } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - remappedFields = fields.map((field: any) => { - return { - name: field.alias, - alias: field.alias, - orgName: field.field, - schema: args.schema, - table: field.relation, - tableAlias: field.relation, - orgTable: field.relation, - type: this.types[field.type], - length: field.length - }; - }); + if (fields) { + remappedFields = fields.map(field => { + return { + name: field.alias, + alias: field.alias, + orgName: field.field, + schema: args.schema, + table: field.relation, + tableAlias: field.relation, + orgTable: field.relation, + type: this.types[field.type], + length: field.length, + key: undefined as string + }; + }); + } resolve(remappedResponse); } }); }); + + if (args.details) { + if (remappedFields.length) { + paramsArr = remappedFields.map(field => { + return { + table: field.orgTable, + schema: field.schema + }; + }).filter((val, i, arr) => arr.findIndex(el => el.table === val.table) === i); + + for (const paramObj of paramsArr) { + if (!paramObj.table || !paramObj.schema) continue; + + try { // Column details + const indexes = await this.getTableIndexes(paramObj); + remappedFields = remappedFields.map(field => { + const fieldIndex = indexes.find(i => i.column === field.name); + if (fieldIndex) { + const key = fieldIndex.type === 'PRIMARY KEY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'fk'; + field = { ...field, key }; + } + + return field; + }); + } + catch (err) { + if (args.autocommit) { + this._connection.detach(); + this._runningConnections.delete(args.tabUid); + } + reject(err); + } + + try { // Key usage (foreign keys) + const response = await this.getKeyUsage(paramObj); + keysArr = keysArr ? [...keysArr, ...response] : response; + } + catch (err) { + if (args.autocommit) { + this._connection.detach(); + this._runningConnections.delete(args.tabUid); + } + reject(err); + } + } + } + } } catch (err) { reject(err); @@ -744,10 +839,6 @@ export class FirebirdSQLClient extends AntaresCore { timeStop = new Date(); - // if (args.details) { - - // } - resolve({ duration: timeStop.getTime() - timeStart.getTime(), rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, diff --git a/src/renderer/components/WorkspaceTabQueryTable.vue b/src/renderer/components/WorkspaceTabQueryTable.vue index f204b1be..83e8c597 100644 --- a/src/renderer/components/WorkspaceTabQueryTable.vue +++ b/src/renderer/components/WorkspaceTabQueryTable.vue @@ -272,6 +272,8 @@ const keyName = (key: string) => { return 'UNIQUE'; case 'mul': return 'INDEX'; + case 'fk': + return 'FOREIGN KEY'; default: return 'UNKNOWN ' + key; } diff --git a/src/renderer/scss/_table-keys.scss b/src/renderer/scss/_table-keys.scss index 9157edb3..a57546b8 100644 --- a/src/renderer/scss/_table-keys.scss +++ b/src/renderer/scss/_table-keys.scss @@ -17,6 +17,7 @@ &.key-mul, &.key-INDEX, + &.key-fk, &.key-KEY { color: limegreen; } From 0827a04d61af75b4366394e5f0289df461d02c98 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 8 Nov 2022 15:53:21 +0100 Subject: [PATCH 06/14] feat(Firebird SQL): support to blob fields --- src/common/customizations/firebird.ts | 2 +- src/main/libs/clients/FirebirdSQLClient.ts | 71 ++++++++++++------- .../components/WorkspaceTabPropsTable.vue | 12 ++-- src/renderer/scss/_table-keys.scss | 5 +- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index 293a3ec5..e81c7abf 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -44,7 +44,7 @@ export const customizations: Customizations = { schemaExport: false, exportByChunks: false, schemaImport: false, - tableSettings: false, + tableSettings: true, tableOptions: false, tableArray: false, tableRealCount: false, diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index 6f4bc4eb..54895c52 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -77,6 +77,7 @@ export class FirebirdSQLClient extends AntaresCore { return null; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars async getStructure (_schemas: Set) { interface ShowTableResult { FORMAT: number; @@ -466,17 +467,17 @@ export class FirebirdSQLClient extends AntaresCore { } async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy - const sql = `CREATE TABLE '${params.table}_copy' AS SELECT * FROM '${params.table}'`; + const sql = `CREATE TABLE "${params.table}_copy" AS SELECT * FROM "${params.table}"`; return await this.raw(sql); } async truncateTable (params: { schema: string; table: string }) { - const sql = `DELETE FROM '${params.table}'`; + const sql = `DELETE FROM "${params.table}"`; return await this.raw(sql); } async dropTable (params: { schema: string; table: string }) { - const sql = `DROP TABLE '${params.table}'`; + const sql = `DROP TABLE "${params.table}"`; return await this.raw(sql); } @@ -586,29 +587,29 @@ export class FirebirdSQLClient extends AntaresCore { return null; } - // async commitTab (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.prepare('COMMIT').run(); - // return this.destroyConnectionToCommit(tabUid); - // } - // } + async commitTab (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.prepare('COMMIT').run(); + // return this.destroyConnectionToCommit(tabUid); + // } + } - // async rollbackTab (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.prepare('ROLLBACK').run(); - // return this.destroyConnectionToCommit(tabUid); - // } - // } + async rollbackTab (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.prepare('ROLLBACK').run(); + // return this.destroyConnectionToCommit(tabUid); + // } + } - // destroyConnectionToCommit (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.close(); - // this._connectionsToCommit.delete(tabUid); - // } - // } + destroyConnectionToCommit (tabUid: string) { + // const connection = this._connectionsToCommit.get(tabUid); + // if (connection) { + // connection.close(); + // this._connectionsToCommit.delete(tabUid); + // } + } getSQL () { // LIMIT @@ -755,8 +756,26 @@ export class FirebirdSQLClient extends AntaresCore { for (const key in row) { if (Buffer.isBuffer(row[key])) row[key] = row[key].toString('binary'); - else if (typeof row[key] === 'function') - row[key] = row[key].toString('binary'); + else if (typeof row[key] === 'function') { + const result = await new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + row[key]((err: any, _name: string, event: any) => { + if (err) + return reject(err); + + const buffArr: Buffer[] = []; + event.on('data', (chunk: Buffer) => { + buffArr.push(chunk); + }); + + event.on('end', () => { + resolve(Buffer.concat(buffArr)); + }); + }); + }); + + row[key] = result; + } } remappedResponse.push(row); diff --git a/src/renderer/components/WorkspaceTabPropsTable.vue b/src/renderer/components/WorkspaceTabPropsTable.vue index f97955ae..7db19f74 100644 --- a/src/renderer/components/WorkspaceTabPropsTable.vue +++ b/src/renderer/components/WorkspaceTabPropsTable.vue @@ -323,11 +323,13 @@ const getFieldsData = async () => { const { status, response } = await Tables.getTableIndexes(params); if (status === 'success') { - const indexesObj = response.reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => { - acc[curr.name] = acc[curr.name] || []; - acc[curr.name].push(curr); - return acc; - }, {}); + const indexesObj = response + .filter((index: TableIndex) => index.type !== 'FOREIGN KEY') + .reduce((acc: {[key: string]: TableIndex[]}, curr: TableIndex) => { + acc[curr.name] = acc[curr.name] || []; + acc[curr.name].push(curr); + return acc; + }, {}); originalIndexes.value = Object.keys(indexesObj).map(index => { return { diff --git a/src/renderer/scss/_table-keys.scss b/src/renderer/scss/_table-keys.scss index a57546b8..0c28866a 100644 --- a/src/renderer/scss/_table-keys.scss +++ b/src/renderer/scss/_table-keys.scss @@ -17,11 +17,14 @@ &.key-mul, &.key-INDEX, - &.key-fk, &.key-KEY { color: limegreen; } + &.key-fk { + color: chocolate; + } + &.key-FULLTEXT { color: mediumvioletred; } From 03777a2ea3b83b78f28df9778fe0d7f119db8d22 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Wed, 9 Nov 2022 17:41:31 +0100 Subject: [PATCH 07/14] refactor(Firebird SQL): improve fields metadata detection --- src/common/customizations/firebird.ts | 2 +- src/common/data-types/firebird.ts | 14 ++-- src/common/fieldTypes.ts | 3 +- src/main/libs/clients/FirebirdSQLClient.ts | 72 +++++++++++++++---- .../components/WorkspaceTabPropsTable.vue | 2 +- 5 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index e81c7abf..9f4c205a 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -58,7 +58,7 @@ export const customizations: Customizations = { foreigns: false, sortableFields: false, unsigned: false, - nullable: false, + nullable: true, nullablePrimary: false, zerofill: false, autoIncrement: false, diff --git a/src/common/data-types/firebird.ts b/src/common/data-types/firebird.ts index d871286f..8afd3fce 100644 --- a/src/common/data-types/firebird.ts +++ b/src/common/data-types/firebird.ts @@ -6,21 +6,21 @@ export default [ types: [ { name: 'SMALLINT', - length: true, + length: false, collation: false, unsigned: true, zerofill: true }, { name: 'INTEGER', - length: true, + length: false, collation: false, unsigned: true, zerofill: true }, { name: 'BIGINT', - length: true, + length: false, collation: false, unsigned: true, zerofill: true @@ -33,6 +33,7 @@ export default [ { name: 'DECIMAL', length: true, + scale: true, collation: false, unsigned: false, zerofill: false @@ -40,20 +41,21 @@ export default [ { name: 'NUMERIC', length: true, + scale: true, collation: false, unsigned: false, zerofill: false }, { name: 'FLOAT', - length: true, + length: false, collation: false, unsigned: false, zerofill: false }, { name: 'DOUBLE PRECISION', - length: true, + length: false, collation: false, unsigned: false, zerofill: false @@ -110,7 +112,7 @@ export default [ }, { name: 'TIMESTAMP', - length: true, + length: false, collation: false, unsigned: false, zerofill: false diff --git a/src/common/fieldTypes.ts b/src/common/fieldTypes.ts index d230740e..b8787ab6 100644 --- a/src/common/fieldTypes.ts +++ b/src/common/fieldTypes.ts @@ -35,7 +35,8 @@ export const NUMBER = [ 'SERIAL', 'BIGSERIAL', 'OID', - 'XID' + 'XID', + 'INT64' ]; export const FLOAT = [ diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index 54895c52..ba8e80e0 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -3,6 +3,7 @@ import * as antares from 'common/interfaces/antares'; import * as firebird from 'node-firebird'; import { AntaresCore } from '../AntaresCore'; import dataTypes from 'common/data-types/sqlite'; +import { FLOAT, NUMBER } from 'common/fieldTypes'; export class FirebirdSQLClient extends AntaresCore { private _schema?: string; @@ -168,39 +169,64 @@ export class FirebirdSQLClient extends AntaresCore { async getTableColumns ({ schema, table }: { schema: string; table: string }) { interface TableColumnsResult { - POSITION: number; DESCRIPTION?: string; /* eslint-disable camelcase */ FIELD_NAME: string; FIELD_TYPE: string; + FIELD_POSITION: number; NOT_NULL: 0 | 1; - DEFAULT_VALUE: string; + DEFAULT_VALUE: Buffer; + DEFAULT_SOURCE: string; FIELD_LENGTH: number; FIELD_PRECISION: number; FIELD_SCALE: number; /* eslint-enable camelcase */ - SUBTYPE: string; + SUBTYPE: number; + EXTERNAL_TYPE: number; COLLATION: string; CHARSET: string; } + /* + FIELD_SUB_TYPE + + BLOB + 0 - untyped + 1 - text + 2 - BLR + 3 - access control list + 4 - reserved for future use + 5 - encoded table metadata description + 6 - for storing the details of a cross-database transaction that ends abnormally + CHAR + 0 - untyped data + 1 - fixed binary data + NUMERIC FIELD + 0 or NULL - the data type matches the value in the RDB$FIELD_TYPE field + 1 - NUMERIC + 2 - DECIMAL + */ + const { rows: fields } = await this.raw>(` SELECT r.RDB$FIELD_NAME AS field_name, r.RDB$DESCRIPTION AS description, r.RDB$DEFAULT_VALUE AS default_value, r.RDB$NULL_FLAG AS not_null, + r.RDB$FIELD_POSITION AS field_position, f.RDB$FIELD_LENGTH AS field_length, f.RDB$FIELD_PRECISION AS field_precision, f.RDB$FIELD_SCALE AS field_scale, + f.RDB$EXTERNAL_TYPE AS external_type, + r.RDB$DEFAULT_SOURCE AS default_source, CASE f.RDB$FIELD_TYPE WHEN 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 40 THEN 'CSTRING' WHEN 11 THEN 'D_FLOAT' - WHEN 27 THEN 'DOUBLE' + WHEN 27 THEN 'DOUBLE PRECISION' WHEN 10 THEN 'FLOAT' - WHEN 16 THEN 'INT64' + WHEN 16 THEN 'BIGINT' WHEN 8 THEN 'INTEGER' WHEN 9 THEN 'QUAD' WHEN 7 THEN 'SMALLINT' @@ -222,20 +248,31 @@ export class FirebirdSQLClient extends AntaresCore { `); return fields.map(field => { + const defaultValue = field.DEFAULT_SOURCE ? field.DEFAULT_SOURCE.replace('DEFAULT ', '') : null; + let fieldType = field.FIELD_TYPE.trim(); + + if ([...NUMBER, ...FLOAT].includes(fieldType)) { + if (field.SUBTYPE === 1) + fieldType = 'NUMERIC'; + else if (field.SUBTYPE === 2) + fieldType = 'DECIMAL'; + } + return { name: field.FIELD_NAME.trim(), key: null, - type: field.FIELD_TYPE.trim(), + type: fieldType, schema: schema, table: table, - numPrecision: field.FIELD_PRECISION, - datePrecision: null, - charLength: field.FIELD_LENGTH, + numPrecision: field.FIELD_PRECISION ? field.FIELD_PRECISION : null, + numScale: Math.abs(field.FIELD_SCALE), + datePrecision: field.FIELD_NAME.trim() === 'TIMESTAMP' ? 4 : null, + charLength: ![...NUMBER, ...FLOAT].includes(fieldType) ? field.FIELD_LENGTH : null, nullable: !field.NOT_NULL, unsigned: null, zerofill: null, - order: field.POSITION, - default: field.DEFAULT_VALUE, + order: field.FIELD_POSITION+1, + default: defaultValue, charset: field.CHARSET, collation: null, autoIncrement: false, @@ -784,6 +821,15 @@ export class FirebirdSQLClient extends AntaresCore { if (fields) { remappedFields = fields.map(field => { + let fieldType = this.types[field.type]; + + if ([...NUMBER, ...FLOAT].includes(fieldType)) { + if (field.subType === 1) + fieldType = 'NUMERIC'; + else if (field.subType === 2) + fieldType = 'DECIMAL'; + } + return { name: field.alias, alias: field.alias, @@ -792,8 +838,8 @@ export class FirebirdSQLClient extends AntaresCore { table: field.relation, tableAlias: field.relation, orgTable: field.relation, - type: this.types[field.type], - length: field.length, + type: fieldType, + length: fieldType === 'TIMESTAMP' ? 4 : field.length, key: undefined as string }; }); diff --git a/src/renderer/components/WorkspaceTabPropsTable.vue b/src/renderer/components/WorkspaceTabPropsTable.vue index 7db19f74..1c070a57 100644 --- a/src/renderer/components/WorkspaceTabPropsTable.vue +++ b/src/renderer/components/WorkspaceTabPropsTable.vue @@ -300,7 +300,7 @@ const getFieldsData = async () => { field.defaultType = 'noval'; else if (field.default === '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'; else { field.defaultType = 'custom'; From 27566c1dfae55f72d3f870c50410e5ecda256037 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Thu, 10 Nov 2022 15:52:31 +0100 Subject: [PATCH 08/14] feat(Firebird SQL): manual commit mode --- src/common/customizations/defaults.ts | 3 +- src/common/customizations/firebird.ts | 3 +- src/common/data-types/firebird.ts | 16 ++- src/common/fieldTypes.ts | 6 +- src/common/interfaces/customizations.ts | 1 - src/main/libs/clients/FirebirdSQLClient.ts | 119 +++++++++--------- .../components/WorkspaceTabQueryTable.vue | 2 - .../components/WorkspaceTabQueryTableRow.vue | 13 +- src/renderer/scss/_data-types.scss | 9 ++ 9 files changed, 97 insertions(+), 75 deletions(-) diff --git a/src/common/customizations/defaults.ts b/src/common/customizations/defaults.ts index 453eed17..26d81697 100644 --- a/src/common/customizations/defaults.ts +++ b/src/common/customizations/defaults.ts @@ -90,6 +90,5 @@ export const defaults: Customizations = { triggerFunctionlanguages: null, parametersLength: false, languages: null, - readOnlyMode: false, - blobAsText: false + readOnlyMode: false }; diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index 9f4c205a..1d7452b2 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -90,6 +90,5 @@ export const customizations: Customizations = { triggerFunctionlanguages: null, parametersLength: false, languages: null, - readOnlyMode: false, - blobAsText: true + readOnlyMode: false }; diff --git a/src/common/data-types/firebird.ts b/src/common/data-types/firebird.ts index 8afd3fce..c40e9d4c 100644 --- a/src/common/data-types/firebird.ts +++ b/src/common/data-types/firebird.ts @@ -78,6 +78,13 @@ export default [ collation: true, unsigned: false, zerofill: false + }, + { + name: 'BLOB-TEXT', + length: false, + collation: true, + unsigned: false, + zerofill: false } ] }, @@ -86,7 +93,14 @@ export default [ types: [ { name: 'BLOB', - length: true, + length: false, + collation: false, + unsigned: false, + zerofill: false + }, + { + name: 'CHAR-BINARY', + length: false, collation: false, unsigned: false, zerofill: false diff --git a/src/common/fieldTypes.ts b/src/common/fieldTypes.ts index b8787ab6..27942871 100644 --- a/src/common/fieldTypes.ts +++ b/src/common/fieldTypes.ts @@ -10,7 +10,8 @@ export const LONG_TEXT = [ 'MEDIUMTEXT', 'LONGTEXT', 'JSON', - 'VARBINARY' + 'VARBINARY', + 'BLOB-TEXT' ]; export const ARRAY = [ @@ -85,7 +86,8 @@ export const BLOB = [ 'MEDIUMBLOB', 'LONGBLOB', 'LONG_BLOB', - 'BYTEA' + 'BYTEA', + 'CHAR-BINARY' ]; export const BIT = [ diff --git a/src/common/interfaces/customizations.ts b/src/common/interfaces/customizations.ts index c0debd59..4a16ffbd 100644 --- a/src/common/interfaces/customizations.ts +++ b/src/common/interfaces/customizations.ts @@ -89,5 +89,4 @@ export interface Customizations { parametersLength?: boolean; languages?: string[]; readOnlyMode?: boolean; - blobAsText: boolean; } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index ba8e80e0..c5f0420d 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -8,11 +8,11 @@ import { FLOAT, NUMBER } from 'common/fieldTypes'; export class FirebirdSQLClient extends AntaresCore { private _schema?: string; private _runningConnections: Map; - private _connectionsToCommit: Map; + private _connectionsToCommit: Map; protected _connection?: firebird.Database; _params: firebird.Options; - private types: {[key: number]: string} ={ + private _types: {[key: number]: string} ={ 452: 'CHAR', // Array of char 448: 'VARCHAR', 500: 'SMALLINT', @@ -22,7 +22,7 @@ export class FirebirdSQLClient extends AntaresCore { 530: 'DOUBLE PRECISION', 510: 'TIMESTAMP', 520: 'BLOB', - 540: 'ARRAY', + 540: 'VARCHAR', // ARRAY ??? 550: 'QUAD', 560: 'TIME', 570: 'DATE', @@ -38,6 +38,29 @@ export class FirebirdSQLClient extends AntaresCore { this._connectionsToCommit = new Map(); } + private _getType (type: string, subType?: number) { + let fieldType = type.trim(); + + if ([...NUMBER, ...FLOAT].includes(fieldType)) { + if (subType === 1) + fieldType = 'NUMERIC'; + else if (subType === 2) + fieldType = 'DECIMAL'; + } + + if (fieldType === 'BLOB') { + if (subType === 1) + fieldType = 'BLOB-TEXT'; + } + + if (fieldType === 'CHAR') { + if (subType === 1) + fieldType = 'CHAR-BINARY'; + } + + return fieldType; + } + getTypeInfo (type: string): antares.TypeInformations { return dataTypes .reduce((acc, group) => [...acc, ...group.types], []) @@ -170,7 +193,6 @@ export class FirebirdSQLClient extends AntaresCore { async getTableColumns ({ schema, table }: { schema: string; table: string }) { interface TableColumnsResult { DESCRIPTION?: string; - /* eslint-disable camelcase */ FIELD_NAME: string; FIELD_TYPE: string; FIELD_POSITION: number; @@ -180,10 +202,9 @@ export class FirebirdSQLClient extends AntaresCore { FIELD_LENGTH: number; FIELD_PRECISION: number; FIELD_SCALE: number; - /* eslint-enable camelcase */ - SUBTYPE: number; EXTERNAL_TYPE: number; - COLLATION: string; + SUBTYPE: number; + COLLATION?: string; CHARSET: string; } @@ -209,16 +230,16 @@ export class FirebirdSQLClient extends AntaresCore { const { rows: fields } = await this.raw>(` SELECT - r.RDB$FIELD_NAME AS field_name, - r.RDB$DESCRIPTION AS description, - r.RDB$DEFAULT_VALUE AS default_value, - r.RDB$NULL_FLAG AS not_null, - r.RDB$FIELD_POSITION AS field_position, - f.RDB$FIELD_LENGTH AS field_length, - f.RDB$FIELD_PRECISION AS field_precision, - f.RDB$FIELD_SCALE AS field_scale, - f.RDB$EXTERNAL_TYPE AS external_type, - r.RDB$DEFAULT_SOURCE AS default_source, + r.RDB$FIELD_NAME AS FIELD_NAME, + r.RDB$DESCRIPTION AS DESCRIPTION, + r.RDB$DEFAULT_VALUE AS DEFAULT_VALUE, + r.RDB$NULL_FLAG AS NOT_NULL, + r.RDB$FIELD_POSITION AS FIELD_POSITION, + f.RDB$FIELD_LENGTH AS FIELD_LENGTH, + f.RDB$FIELD_PRECISION AS FIELD_PRECISION, + f.RDB$FIELD_SCALE AS FIELD_SCALE, + f.RDB$EXTERNAL_TYPE AS EXTERNAL_TYPE, + r.RDB$DEFAULT_SOURCE AS DEFAULT_SOURCE, CASE f.RDB$FIELD_TYPE WHEN 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' @@ -235,10 +256,10 @@ export class FirebirdSQLClient extends AntaresCore { WHEN 35 THEN 'TIMESTAMP' WHEN 37 THEN 'VARCHAR' ELSE 'UNKNOWN' - END AS field_type, - f.RDB$FIELD_SUB_TYPE AS subtype, - -- coll.RDB$COLLATION_NAME AS collation, - cset.RDB$CHARACTER_SET_NAME AS charset + END AS FIELD_TYPE, + f.RDB$FIELD_SUB_TYPE AS SUBTYPE, + -- coll.RDB$COLLATION_NAME AS COLLATION, + cset.RDB$CHARACTER_SET_NAME AS CHARSET FROM RDB$RELATION_FIELDS r LEFT JOIN RDB$FIELDS f ON r.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME -- LEFT JOIN RDB$COLLATIONS coll ON f.RDB$COLLATION_ID = coll.RDB$COLLATION_ID @@ -249,14 +270,7 @@ export class FirebirdSQLClient extends AntaresCore { return fields.map(field => { const defaultValue = field.DEFAULT_SOURCE ? field.DEFAULT_SOURCE.replace('DEFAULT ', '') : null; - let fieldType = field.FIELD_TYPE.trim(); - - if ([...NUMBER, ...FLOAT].includes(fieldType)) { - if (field.SUBTYPE === 1) - fieldType = 'NUMERIC'; - else if (field.SUBTYPE === 2) - fieldType = 'DECIMAL'; - } + const fieldType = this._getType(field.FIELD_TYPE, field.SUBTYPE); return { name: field.FIELD_NAME.trim(), @@ -625,27 +639,23 @@ export class FirebirdSQLClient extends AntaresCore { } async commitTab (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.prepare('COMMIT').run(); - // return this.destroyConnectionToCommit(tabUid); - // } + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.commit(); + return this.destroyConnectionToCommit(tabUid); + } } async rollbackTab (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.prepare('ROLLBACK').run(); - // return this.destroyConnectionToCommit(tabUid); - // } + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.rollback(); + return this.destroyConnectionToCommit(tabUid); + } } destroyConnectionToCommit (tabUid: string) { - // const connection = this._connectionsToCommit.get(tabUid); - // if (connection) { - // connection.close(); - // this._connectionsToCommit.delete(tabUid); - // } + this._connectionsToCommit.delete(tabUid); } getSQL () { @@ -737,21 +747,21 @@ export class FirebirdSQLClient extends AntaresCore { .map(q => q.trim()) : [sql]; - let connection: firebird.Database; + let connection: firebird.Database | firebird.Transaction; if (!args.autocommit && args.tabUid) { // autocommit OFF if (this._connectionsToCommit.has(args.tabUid)) connection = this._connectionsToCommit.get(args.tabUid); - else { connection = await this.getConnection(); - await new Promise((resolve, reject) => { - connection.query('BEGIN TRANSACTION', [], (err, res) => { + const transaction = await new Promise((resolve, reject) => { + (connection as firebird.Database).transaction(firebird.ISOLATION_READ_COMMITED, (err, transaction) => { if (err) reject(err); - else resolve(res); + else resolve(transaction); }); }); - this._connectionsToCommit.set(args.tabUid, connection); + connection = transaction; + this._connectionsToCommit.set(args.tabUid, transaction); } } else// autocommit ON @@ -821,14 +831,7 @@ export class FirebirdSQLClient extends AntaresCore { if (fields) { remappedFields = fields.map(field => { - let fieldType = this.types[field.type]; - - if ([...NUMBER, ...FLOAT].includes(fieldType)) { - if (field.subType === 1) - fieldType = 'NUMERIC'; - else if (field.subType === 2) - fieldType = 'DECIMAL'; - } + const fieldType = this._getType(this._types[field.type], field.subType); return { name: field.alias, diff --git a/src/renderer/components/WorkspaceTabQueryTable.vue b/src/renderer/components/WorkspaceTabQueryTable.vue index 83e8c597..80120770 100644 --- a/src/renderer/components/WorkspaceTabQueryTable.vue +++ b/src/renderer/components/WorkspaceTabQueryTable.vue @@ -81,7 +81,6 @@ :fields="fieldsObj" :key-usage="keyUsage" :element-type="elementType" - :blob-as-text="blobAsText" :class="{'selected': selectedRows.includes(row._antares_id)}" :selected="selectedRows.includes(row._antares_id)" :selected-cell="selectedRows.length === 1 && selectedRows.includes(row._antares_id) ? selectedField : null" @@ -183,7 +182,6 @@ const isEditingRow = ref(false); const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema); const workspaceClient = computed(() => getWorkspace(props.connUid).client); -const blobAsText = computed(() => getWorkspace(props.connUid).customizations.blobAsText); const primaryField = computed(() => { const primaryFields = fields.value.filter(field => field.key === 'pri'); diff --git a/src/renderer/components/WorkspaceTabQueryTableRow.vue b/src/renderer/components/WorkspaceTabQueryTableRow.vue index 42cc1c56..98af44ff 100644 --- a/src/renderer/components/WorkspaceTabQueryTableRow.vue +++ b/src/renderer/components/WorkspaceTabQueryTableRow.vue @@ -243,8 +243,7 @@ const props = defineProps({ itemHeight: Number, elementType: { type: String, default: 'table' }, selected: { type: Boolean, default: false }, - selectedCell: { type: String, default: null }, - blobAsText: { type: Boolean, default: false } + selectedCell: { type: String, default: null } }); const emit = defineEmits(['update-field', 'select-row', 'contextmenu', 'start-editing', 'stop-editing']); @@ -319,7 +318,7 @@ const inputProps = computed(() => { return { type: 'text', mask: datetimeMask }; } - if (BLOB.includes(editingType.value) && !props.blobAsText) + if (BLOB.includes(editingType.value)) return { type: 'file', mask: false }; if (BOOLEAN.includes(editingType.value)) @@ -399,7 +398,7 @@ const editON = async (field: string) => { editingField.value = field; editingLength.value = props.fields[field].length; - if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type) || (BLOB.includes(type) && props.blobAsText)) { + if ([...LONG_TEXT, ...ARRAY, ...TEXT_SEARCH].includes(type)) { isTextareaEditor.value = true; editingContent.value = typeFormat(content, type); emit('start-editing', field); @@ -416,7 +415,7 @@ const editON = async (field: string) => { return; } - if (BLOB.includes(type) && !props.blobAsText) { + if (BLOB.includes(type)) { isBlobEditor.value = true; editingContent.value = content || ''; fileToUpload.value = null; @@ -455,7 +454,7 @@ const editOFF = () => { isInlineEditor.value[editingField.value] = false; let content; - if (!BLOB.includes(editingType.value) || (BLOB.includes(editingType.value) && props.blobAsText)) { + if (!BLOB.includes(editingType.value)) { if ([...DATETIME, ...TIME].includes(editingType.value)) { if (editingContent.value !== null && editingContent.value.substring(editingContent.value.length - 1) === '.') editingContent.value = editingContent.value.slice(0, -1); @@ -589,7 +588,7 @@ const typeFormat = (val: string | number | Date | number[], type: string, precis return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val; } - if (BLOB.includes(type) && !props.blobAsText) { + if (BLOB.includes(type)) { const buff = Buffer.from(val as string); if (!buff.length) return ''; diff --git a/src/renderer/scss/_data-types.scss b/src/renderer/scss/_data-types.scss index c1e65943..eae7b3ec 100644 --- a/src/renderer/scss/_data-types.scss +++ b/src/renderer/scss/_data-types.scss @@ -26,6 +26,8 @@ "macaddr8": $string-color, "uuid": $string-color, "regproc": $string-color, + "blob-text": $string-color, + "int": $number-color, "tinyint": $number-color, "smallint": $number-color, @@ -44,6 +46,7 @@ "double_precision": $number-color, "oid": $number-color, "xid": $number-color, + "money": $number-color, "number": $number-color, "datetime": $date-color, @@ -54,9 +57,12 @@ "timestamp": $date-color, "timestamp_without_time_zone": $date-color, "timestamp_with_time_zone": $date-color, + "bit": $bit-color, "bit_varying": $bit-color, + "binary": $blob-color, + "char-binary": $blob-color, "varbinary": $blob-color, "blob": $blob-color, "tinyblob": $blob-color, @@ -65,10 +71,12 @@ "longblob": $blob-color, "long_blob": $blob-color, "bytea": $blob-color, + "enum": $enum-color, "set": $enum-color, "bool": $enum-color, "boolean": $enum-color, + "interval": $array-color, "array": $array-color, "anyarray": $array-color, @@ -85,6 +93,7 @@ "geomcollection": $array-color, "geometrycollection": $array-color, "aclitem": $array-color, + "unknown": $unknown-color, ) ); From 1b5cc315dddca6b753fb6fe6e196e29441ffed79 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 15 Nov 2022 16:46:12 +0100 Subject: [PATCH 09/14] feat(Firebird SQL): table add/edit/delete support --- src/common/customizations/defaults.ts | 5 +- src/common/customizations/firebird.ts | 84 ++----- src/common/customizations/mysql.ts | 14 ++ src/common/customizations/postgresql.ts | 13 ++ src/common/customizations/sqlite.ts | 13 ++ src/common/index-types/firebird.ts | 4 - src/common/index-types/mysql.ts | 6 - src/common/index-types/postgresql.ts | 5 - src/common/index-types/sqlite.ts | 5 - src/common/interfaces/customizations.ts | 6 +- src/main/ipc-handlers/tables.ts | 4 +- src/main/libs/clients/FirebirdSQLClient.ts | 213 ++++++++++++------ src/main/libs/clients/PostgreSQLClient.ts | 6 +- src/main/libs/clients/SQLiteClient.ts | 6 +- .../components/WorkspaceTabPropsTable.vue | 5 +- .../WorkspaceTabPropsTableForeignModal.vue | 19 +- .../WorkspaceTabPropsTableIndexesModal.vue | 15 +- .../components/WorkspaceTabPropsTableRow.vue | 4 +- .../components/WorkspaceTabQueryTable.vue | 2 +- src/renderer/stores/workspaces.ts | 12 +- 20 files changed, 241 insertions(+), 200 deletions(-) delete mode 100644 src/common/index-types/firebird.ts delete mode 100644 src/common/index-types/mysql.ts delete mode 100644 src/common/index-types/postgresql.ts delete mode 100644 src/common/index-types/sqlite.ts diff --git a/src/common/customizations/defaults.ts b/src/common/customizations/defaults.ts index 26d81697..e9c9a496 100644 --- a/src/common/customizations/defaults.ts +++ b/src/common/customizations/defaults.ts @@ -1,10 +1,14 @@ import { Customizations } from '../interfaces/customizations'; +// Everything OFF export const defaults: Customizations = { // Defaults defaultPort: null, defaultUser: null, defaultDatabase: null, + dataTypes: [], + indexTypes: [], + foreignActions: [], // Core database: false, collations: false, @@ -45,7 +49,6 @@ export const defaults: Customizations = { exportByChunks: false, schemaImport: false, tableSettings: false, - tableOptions: false, tableArray: false, tableRealCount: false, viewSettings: false, diff --git a/src/common/customizations/firebird.ts b/src/common/customizations/firebird.ts index 1d7452b2..77e8fba7 100644 --- a/src/common/customizations/firebird.ts +++ b/src/common/customizations/firebird.ts @@ -1,10 +1,26 @@ 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, @@ -23,72 +39,14 @@ export const customizations: Customizations = { tables: true, views: false, triggers: true, - triggerFunctions: false, routines: false, functions: false, - schedulers: false, // Settings - elementsWrapper: '', + elementsWrapper: '"', stringsWrapper: '\'', - tableAdd: false, - tableTruncateDisableFKCheck: false, - viewAdd: false, - triggerAdd: false, - triggerFunctionAdd: false, - routineAdd: false, - functionAdd: false, - schedulerAdd: false, - databaseEdit: false, - schemaEdit: false, - schemaDrop: false, - schemaExport: false, - exportByChunks: false, - schemaImport: false, + tableAdd: true, tableSettings: true, - tableOptions: false, - tableArray: false, - tableRealCount: false, - viewSettings: false, - triggerSettings: false, - triggerFunctionSettings: false, - routineSettings: false, - functionSettings: false, - schedulerSettings: false, - indexes: false, - foreigns: false, - sortableFields: false, - unsigned: false, - nullable: true, - nullablePrimary: false, - zerofill: false, - autoIncrement: false, - comment: false, - collation: false, - definer: false, - onUpdate: false, - viewAlgorithm: false, - viewSqlSecurity: false, - viewUpdateOption: false, - procedureDeterministic: false, - procedureDataAccess: false, - procedureSql: null, - procedureContext: false, - procedureLanguage: false, - functionDeterministic: false, - functionDataAccess: false, - functionSql: null, - functionContext: false, - functionLanguage: false, - triggerSql: null, - triggerStatementInCreation: false, - triggerMultipleEvents: false, - triggerTableInName: false, - triggerUpdateColumns: false, - triggerOnlyRename: false, - triggerEnableDisable: false, - triggerFunctionSql: null, - triggerFunctionlanguages: null, - parametersLength: false, - languages: null, - readOnlyMode: false + indexes: true, + foreigns: true, + nullable: true }; diff --git a/src/common/customizations/mysql.ts b/src/common/customizations/mysql.ts index d84e93a6..619b1e7f 100644 --- a/src/common/customizations/mysql.ts +++ b/src/common/customizations/mysql.ts @@ -1,5 +1,6 @@ import { Customizations } from '../interfaces/customizations'; import { defaults } from './defaults'; +import mysqlTypes from '../data-types/mysql'; export const customizations: Customizations = { ...defaults, @@ -7,6 +8,19 @@ export const customizations: Customizations = { defaultPort: 3306, defaultUser: 'root', defaultDatabase: null, + dataTypes: mysqlTypes, + indexTypes: [ + 'PRIMARY', + 'INDEX', + 'UNIQUE', + 'FULLTEXT' + ], + foreignActions: [ + 'RESTRICT', + 'CASCADE', + 'SET NULL', + 'NO ACTION' + ], // Core connectionSchema: true, collations: true, diff --git a/src/common/customizations/postgresql.ts b/src/common/customizations/postgresql.ts index 7bf06e8f..39ca38e8 100644 --- a/src/common/customizations/postgresql.ts +++ b/src/common/customizations/postgresql.ts @@ -1,5 +1,6 @@ import { Customizations } from '../interfaces/customizations'; import { defaults } from './defaults'; +import postgresqlTypes from '../data-types/postgresql'; export const customizations: Customizations = { ...defaults, @@ -7,6 +8,18 @@ export const customizations: Customizations = { defaultPort: 5432, defaultUser: 'postgres', defaultDatabase: 'postgres', + dataTypes: postgresqlTypes, + indexTypes: [ + 'PRIMARY', + 'INDEX', + 'UNIQUE' + ], + foreignActions: [ + 'RESTRICT', + 'CASCADE', + 'SET NULL', + 'NO ACTION' + ], // Core database: true, sslConnection: true, diff --git a/src/common/customizations/sqlite.ts b/src/common/customizations/sqlite.ts index f9ac7f28..f31515ee 100644 --- a/src/common/customizations/sqlite.ts +++ b/src/common/customizations/sqlite.ts @@ -1,8 +1,21 @@ import { Customizations } from '../interfaces/customizations'; import { defaults } from './defaults'; +import sqliteTypes from '../data-types/sqlite'; export const customizations: Customizations = { ...defaults, + dataTypes: sqliteTypes, + indexTypes: [ + 'PRIMARY', + 'INDEX', + 'UNIQUE' + ], + foreignActions: [ + 'RESTRICT', + 'CASCADE', + 'SET NULL', + 'NO ACTION' + ], // Core fileConnection: true, // Structure diff --git a/src/common/index-types/firebird.ts b/src/common/index-types/firebird.ts deleted file mode 100644 index 6f04d423..00000000 --- a/src/common/index-types/firebird.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default [ - 'PRIMARY', - 'UNIQUE' -]; diff --git a/src/common/index-types/mysql.ts b/src/common/index-types/mysql.ts deleted file mode 100644 index 103179d3..00000000 --- a/src/common/index-types/mysql.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default [ - 'PRIMARY', - 'INDEX', - 'UNIQUE', - 'FULLTEXT' -]; diff --git a/src/common/index-types/postgresql.ts b/src/common/index-types/postgresql.ts deleted file mode 100644 index cb7fa05c..00000000 --- a/src/common/index-types/postgresql.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default [ - 'PRIMARY', - 'INDEX', - 'UNIQUE' -]; diff --git a/src/common/index-types/sqlite.ts b/src/common/index-types/sqlite.ts deleted file mode 100644 index cb7fa05c..00000000 --- a/src/common/index-types/sqlite.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default [ - 'PRIMARY', - 'INDEX', - 'UNIQUE' -]; diff --git a/src/common/interfaces/customizations.ts b/src/common/interfaces/customizations.ts index 4a16ffbd..03578e21 100644 --- a/src/common/interfaces/customizations.ts +++ b/src/common/interfaces/customizations.ts @@ -1,8 +1,13 @@ +import { TypesGroup } from './antares'; + export interface Customizations { // Defaults defaultPort?: number; defaultUser?: string; defaultDatabase?: string; + dataTypes?: TypesGroup[]; + indexTypes?: string[]; + foreignActions?: string[]; // Core database?: boolean; collations?: boolean; @@ -30,7 +35,6 @@ export interface Customizations { stringsWrapper: string; tableAdd?: boolean; tableSettings?: boolean; - tableOptions?: boolean; tableArray?: boolean; tableRealCount?: boolean; tableTruncateDisableFKCheck?: boolean; diff --git a/src/main/ipc-handlers/tables.ts b/src/main/ipc-handlers/tables.ts index 3c105548..2605eac7 100644 --- a/src/main/ipc-handlers/tables.ts +++ b/src/main/ipc-handlers/tables.ts @@ -227,10 +227,11 @@ export default (connections: {[key: string]: antares.Client}) => { }).join(','); try { - const result = await connections[params.uid] + const result: unknown = await connections[params.uid] .schema(params.schema) .delete(params.table) .where({ [params.primary]: `IN (${idString})` }) + .limit(params.rows.length) .run(); return { status: 'success', response: result }; @@ -289,6 +290,7 @@ export default (connections: {[key: string]: antares.Client}) => { break; case 'pg': case 'sqlite': + case 'firebird': escapedParam = `'${params.row[key].value.replaceAll('\'', '\'\'')}'`; break; } diff --git a/src/main/libs/clients/FirebirdSQLClient.ts b/src/main/libs/clients/FirebirdSQLClient.ts index c5f0420d..05e04606 100644 --- a/src/main/libs/clients/FirebirdSQLClient.ts +++ b/src/main/libs/clients/FirebirdSQLClient.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as antares from 'common/interfaces/antares'; import * as firebird from 'node-firebird'; import { AntaresCore } from '../AntaresCore'; -import dataTypes from 'common/data-types/sqlite'; +import dataTypes from 'common/data-types/firebird'; import { FLOAT, NUMBER } from 'common/fieldTypes'; export class FirebirdSQLClient extends AntaresCore { @@ -67,6 +67,27 @@ export class FirebirdSQLClient extends AntaresCore { .filter(_type => _type.name === type.toUpperCase())[0]; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected _reducer (acc: string[], curr: any) { + const type = typeof curr; + + switch (type) { + case 'number': + case 'string': + return [...acc, curr]; + case 'object': + if (Array.isArray(curr)) + return [...acc, ...curr]; + else { + const clausoles = []; + for (const key in curr) + clausoles.push(`"${key}" ${curr[key]}`); + + return clausoles; + } + } + } + async connect () { if (!this._poolSize) this._connection = await this.getConnection(); @@ -337,7 +358,7 @@ export class FirebirdSQLClient extends AntaresCore { name: index.INDEX_NAME.trim(), column: index.FIELD_NAME.trim(), indexType: null as never, - type: index.INDEX_TYPE.trim(), + type: index.INDEX_TYPE.trim() === 'PRIMARY KEY' ? 'PRIMARY' : index.INDEX_TYPE.trim(), cardinality: null as never, comment: '', indexComment: '' @@ -411,7 +432,6 @@ export class FirebirdSQLClient extends AntaresCore { async createTable (params: antares.CreateTableParams) { const { - schema, fields, foreigns, indexes, @@ -419,10 +439,9 @@ export class FirebirdSQLClient extends AntaresCore { } = params; const newColumns: string[] = []; const newIndexes: string[] = []; - const manageIndexes: string[] = []; const newForeigns: string[] = []; - let sql = `CREATE TABLE "${schema}"."${options.name}"`; + let sql = `CREATE TABLE "${options.name}"`; // ADD FIELDS fields.forEach(field => { @@ -431,11 +450,8 @@ export class FirebirdSQLClient extends AntaresCore { newColumns.push(`"${field.name}" ${field.type.toUpperCase()}${length ? `(${length})` : ''} - ${field.unsigned ? 'UNSIGNED' : ''} - ${field.nullable ? 'NULL' : 'NOT NULL'} - ${field.autoIncrement ? 'AUTO_INCREMENT' : ''} ${field.default !== null ? `DEFAULT ${field.default || '\'\''}` : ''} - ${field.onUpdate ? `ON UPDATE ${field.onUpdate}` : ''}`); + ${field.nullable ? '' : 'NOT NULL'}`); }); // ADD INDEX @@ -443,19 +459,23 @@ export class FirebirdSQLClient extends AntaresCore { const fields = index.fields.map(field => `"${field}"`).join(','); const type = index.type; - if (type === 'PRIMARY') - newIndexes.push(`PRIMARY KEY (${fields})`); - else - manageIndexes.push(`CREATE ${type === 'UNIQUE' ? type : ''} INDEX "${index.name}" ON "${options.name}" (${fields})`); + newIndexes.push(`CONSTRAINT "${index.name}" ${type === 'PRIMARY' ? 'PRIMARY KEY' : type} (${fields})`); }); // ADD FOREIGN KEYS foreigns.forEach(foreign => { - newForeigns.push(`CONSTRAINT "${foreign.constraintName}" FOREIGN KEY ("${foreign.field}") REFERENCES "${foreign.refTable}" ("${foreign.refField}") ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`); + newForeigns.push(` + ADD CONSTRAINT "${foreign.constraintName}" + FOREIGN KEY ("${foreign.field}") REFERENCES "${foreign.refTable}" ("${foreign.refField}") + ${foreign.onUpdate !== 'RESTRICT' ? `ON UPDATE ${foreign.onUpdate}` : ''} + ${foreign.onDelete !== 'RESTRICT' ? `ON DELETE ${foreign.onDelete}` : ''} + `); }); - sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`; - if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`; + sql = `${sql} (${[...newColumns, ...newIndexes].join(', ')})`; + + if (newForeigns.length) + sql = `${sql}; ALTER TABLE "${options.name}" ${newForeigns.join(';')}`; return await this.raw(sql); } @@ -463,58 +483,111 @@ export class FirebirdSQLClient extends AntaresCore { async alterTable (params: antares.AlterTableParams) { const { table, - schema, additions, deletions, changes, - tableStructure + indexChanges, + foreignChanges } = params; - try { - await this.raw('BEGIN TRANSACTION'); - await this.raw('PRAGMA foreign_keys = 0'); + let sql = `ALTER TABLE "${table}" `; + const alterColumns: string[] = []; + const newForeigns: string[] = []; - const tmpName = `Antares_${table}_tmp`; - await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); - await this.dropTable(params); + // OPTIONS + // if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`); + // if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`); + // if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`); + // if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`); - const createTableParams = { - schema: schema, - fields: tableStructure.fields, - foreigns: tableStructure.foreigns, - indexes: tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')), - options: { name: tableStructure.name } - }; - await this.createTable(createTableParams); - const insertFields = createTableParams.fields - .filter(field => { - return ( - additions.every(add => add.name !== field.name) && - deletions.every(del => del.name !== field.name) - ); - }) - .reduce((acc, curr) => { - acc.push(`"${curr.name}"`); - return acc; - }, []); + // ADD FIELDS + additions.forEach(addition => { + const typeInfo = this.getTypeInfo(addition.type); + const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false; - const selectFields = insertFields.map(field => { - const renamedField = changes.find(change => `"${change.name}"` === field); - if (renamedField) - return `"${renamedField.orgName}"`; - return field; - }); + alterColumns.push(`ADD "${addition.name}" + ${addition.type.toUpperCase()}${length ? `(${length})` : ''} + ${addition.default !== null ? `DEFAULT ${addition.default || '\'\''}` : ''} + ${addition.nullable ? '' : 'NOT NULL'}`); + }); - await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`); + // ADD INDEX + indexChanges.additions.forEach(addition => { + const fields = addition.fields.map(field => `"${field}"`).join(','); + const type = addition.type; - await this.dropTable({ schema: schema, table: tmpName }); - await this.raw('PRAGMA foreign_keys = 1'); - await this.raw('COMMIT'); - } - catch (err) { - await this.raw('ROLLBACK'); - return Promise.reject(err); - } + alterColumns.push(`ADD CONSTRAINT "${addition.name}" ${type === 'PRIMARY' ? 'PRIMARY KEY' : type} (${fields})`); + }); + + // ADD FOREIGN KEYS + foreignChanges.additions.forEach(foreign => { + newForeigns.push(` + ADD CONSTRAINT "${foreign.constraintName}" + FOREIGN KEY ("${foreign.field}") REFERENCES "${foreign.refTable}" ("${foreign.refField}") + ${foreign.onUpdate !== 'RESTRICT' ? `ON UPDATE ${foreign.onUpdate}` : ''} + ${foreign.onDelete !== 'RESTRICT' ? `ON DELETE ${foreign.onDelete}` : ''} + `); + }); + + // CHANGE FIELDS + changes.forEach(change => { + const typeInfo = this.getTypeInfo(change.type); + const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false; + + if (change.orgName !== change.name) + alterColumns.push(`ALTER COLUMN "${change.orgName}" TO "${change.name}"`); + + alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}`); + + if (change.default !== null) + alterColumns.push(`ALTER COLUMN "${change.name}" SET DEFAULT ${change.default || '\'\''}`); + + alterColumns.push(`ALTER COLUMN "${change.name}" ${!change.nullable ? 'SET ' : 'DROP '} NOT NULL`); + // TODO: position + }); + + // CHANGE INDEX + indexChanges.changes.forEach(change => { + alterColumns.push(`DROP CONSTRAINT "${change.oldName}"`); + const fields = change.fields.map(field => `"${field}"`).join(','); + const type = change.type; + + alterColumns.push(`ADD CONSTRAINT "${change.name}" ${type === 'PRIMARY' ? 'PRIMARY KEY' : type} (${fields})`); + }); + + // CHANGE FOREIGN KEYS + foreignChanges.changes.forEach(change => { + alterColumns.push(`DROP CONSTRAINT "${change.oldName}"`); + alterColumns.push(` + ADD CONSTRAINT "${change.constraintName}" + FOREIGN KEY ("${change.field}") REFERENCES "${change.refTable}" ("${change.refField}") + ${change.onUpdate !== 'RESTRICT' ? `ON UPDATE ${change.onUpdate}` : ''} + ${change.onDelete !== 'RESTRICT' ? `ON DELETE ${change.onDelete}` : ''} + `); + }); + + // DROP FIELDS + deletions.forEach(deletion => { + alterColumns.push(`DROP "${deletion.name}"`); + }); + + // DROP INDEX + indexChanges.deletions.forEach(deletion => { + alterColumns.push(`DROP CONSTRAINT "${deletion.name}"`); + }); + + // DROP FOREIGN KEYS + foreignChanges.deletions.forEach(deletion => { + alterColumns.push(`DROP CONSTRAINT "${deletion.constraintName}"`); + }); + + if (alterColumns.length) + sql += alterColumns.join(', '); + + if (newForeigns.length) + sql = `${sql}; ALTER TABLE "${table}" ${newForeigns.join(';')}`; + + return await this.raw(sql); } async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy @@ -660,10 +733,10 @@ export class FirebirdSQLClient extends AntaresCore { getSQL () { // LIMIT - const limitRaw = this._query.limit ? ` first ${this._query.limit}` : ''; + const limitRaw = this._query.limit ? ` FIRST ${this._query.limit}` : ''; // OFFSET - const offsetRaw = this._query.offset ? ` skip ${this._query.offset}` : ''; + const offsetRaw = this._query.offset ? ` SKIP ${this._query.offset}` : ''; // SELECT const selectArray = this._query.select.reduce(this._reducer, []); @@ -680,7 +753,7 @@ export class FirebirdSQLClient extends AntaresCore { else if (Object.keys(this._query.insert).length) fromRaw = 'INTO'; - fromRaw += this._query.from ? ` ${this._query.from} ` : ''; + fromRaw += this._query.from ? ` "${this._query.from}" ` : ''; // WHERE const whereArray = this._query.where @@ -696,7 +769,7 @@ export class FirebirdSQLClient extends AntaresCore { let insertRaw = ''; if (this._query.insert.length) { - const fieldsList = Object.keys(this._query.insert[0]); + const fieldsList = Object.keys(this._query.insert[0]).map(col => '"' + col + '"'); const rowsList = this._query.insert.map(el => `(${Object.values(el).join(', ')})`); insertRaw = `(${fieldsList.join(', ')}) VALUES ${rowsList.join(', ')} `; @@ -710,7 +783,7 @@ export class FirebirdSQLClient extends AntaresCore { const orderByArray = this._query.orderBy.reduce(this._reducer, []); const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; - return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${insertRaw}`; + return `${selectRaw}${updateRaw ? `UPDATE${' '+limitRaw||''}` : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${' '+(this._query.delete ? ` ROWS ${this._query.limit}` : '')||''}${insertRaw}`; } async raw (sql: string, args?: antares.QueryParams) { @@ -878,10 +951,10 @@ export class FirebirdSQLClient extends AntaresCore { }); } catch (err) { - if (args.autocommit) { - this._connection.detach(); + if (args.autocommit) this._runningConnections.delete(args.tabUid); - } + + this.destroy(); reject(err); } @@ -890,10 +963,10 @@ export class FirebirdSQLClient extends AntaresCore { keysArr = keysArr ? [...keysArr, ...response] : response; } catch (err) { - if (args.autocommit) { - this._connection.detach(); + if (args.autocommit) this._runningConnections.delete(args.tabUid); - } + + this.destroy(); reject(err); } } @@ -902,7 +975,7 @@ export class FirebirdSQLClient extends AntaresCore { } catch (err) { reject(err); - this._connection.detach(); + this.destroy(); } timeStop = new Date(); diff --git a/src/main/libs/clients/PostgreSQLClient.ts b/src/main/libs/clients/PostgreSQLClient.ts index 2c9168c9..1bac1037 100644 --- a/src/main/libs/clients/PostgreSQLClient.ts +++ b/src/main/libs/clients/PostgreSQLClient.ts @@ -540,11 +540,7 @@ export class PostgreSQLClient extends AntaresCore { return { name: row.constraint_name, column: row.column_name, - indexType: null as null, - type: row.constraint_type, - cardinality: null as null, - comment: '', - indexComment: '' + type: row.constraint_type }; }); } diff --git a/src/main/libs/clients/SQLiteClient.ts b/src/main/libs/clients/SQLiteClient.ts index 119bf5ab..7c5543be 100644 --- a/src/main/libs/clients/SQLiteClient.ts +++ b/src/main/libs/clients/SQLiteClient.ts @@ -217,11 +217,7 @@ export class SQLiteClient extends AntaresCore { remappedIndexes.push({ name: 'PRIMARY', column: key.name, - indexType: null as never, - type: 'PRIMARY', - cardinality: null as never, - comment: '', - indexComment: '' + type: 'PRIMARY' }); } diff --git a/src/renderer/components/WorkspaceTabPropsTable.vue b/src/renderer/components/WorkspaceTabPropsTable.vue index 1c070a57..ce8f0268 100644 --- a/src/renderer/components/WorkspaceTabPropsTable.vue +++ b/src/renderer/components/WorkspaceTabPropsTable.vue @@ -531,9 +531,10 @@ const clearChanges = () => { }; const addField = () => { + const uid = uidGen(); localFields.value.push({ - _antares_id: uidGen(), - name: `${t('word.field', 1)}_${++newFieldsCounter.value}`, + _antares_id: uid, + name: `${t('word.field', 1)}_${uid.substring(0, 4)}`, key: '', // eslint-disable-next-line @typescript-eslint/no-explicit-any type: (workspace.value.dataTypes[0] as any).types[0].name, diff --git a/src/renderer/components/WorkspaceTabPropsTableForeignModal.vue b/src/renderer/components/WorkspaceTabPropsTableForeignModal.vue index b8e5576c..5232d11f 100644 --- a/src/renderer/components/WorkspaceTabPropsTableForeignModal.vue +++ b/src/renderer/components/WorkspaceTabPropsTableForeignModal.vue @@ -113,7 +113,7 @@
-
diff --git a/src/renderer/components/WorkspaceTabNewRoutine.vue b/src/renderer/components/WorkspaceTabNewRoutine.vue index f648bd8e..e51859ef 100644 --- a/src/renderer/components/WorkspaceTabNewRoutine.vue +++ b/src/renderer/components/WorkspaceTabNewRoutine.vue @@ -291,7 +291,7 @@ watch(consoleHeight, () => { }); originalRoutine.value = { - sql: customizations.value.functionSql, + sql: customizations.value.procedureSql, language: customizations.value.languages ? customizations.value.languages[0] : null, name: '', definer: '', diff --git a/src/renderer/components/WorkspaceTabPropsRoutine.vue b/src/renderer/components/WorkspaceTabPropsRoutine.vue index 13718781..87ad1c3b 100644 --- a/src/renderer/components/WorkspaceTabPropsRoutine.vue +++ b/src/renderer/components/WorkspaceTabPropsRoutine.vue @@ -351,6 +351,9 @@ const runRoutine = (params?: string[]) => { case 'pg': sql = `CALL ${originalRoutine.value.name}(${params.join(',')})`; break; + case 'firebird': + sql = `EXECUTE PROCEDURE "${originalRoutine.value.name}"(${params.join(',')})`; + break; case 'mssql': sql = `EXEC ${originalRoutine.value.name} ${params.join(',')}`; break; diff --git a/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue b/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue index 85b7a036..e54d508e 100644 --- a/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue +++ b/src/renderer/components/WorkspaceTabPropsRoutineParamsModal.vue @@ -118,29 +118,17 @@ {{ t('word.context') }}
-
From 3580faebbaf6ee327e7dbd0defee54cabaeccb0a Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Thu, 17 Nov 2022 16:17:36 +0100 Subject: [PATCH 14/14] chore: update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fe012b5..b5b7143c 100644 --- a/README.md +++ b/README.md @@ -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. 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. 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] PostgreSQL - [x] SQLite -- [ ] MSSQL -- [ ] OracleDB +- [x] Firebird SQL +- [ ] SQL Server - [ ] More... ### Operating Systems