diff --git a/.all-contributorsrc b/.all-contributorsrc
index 39e1aa91..531e03bd 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1,5 +1,5 @@
{
- "projectName": "Antares",
+ "projectName": "antares",
"projectOwner": "Fabio286",
"repoType": "github",
"repoHost": "https://github.com",
@@ -111,7 +111,35 @@
"contributions": [
"platform"
]
+ },
+ {
+ "login": "kilianstallz",
+ "name": "Kilian Stallinger",
+ "avatar_url": "https://avatars.githubusercontent.com/u/5290318?v=4",
+ "profile": "https://kilianstallinger.com",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "wenj91",
+ "name": "文杰",
+ "avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
+ "profile": "https://github.com/wenj91",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "goYou",
+ "name": "goYou",
+ "avatar_url": "https://avatars.githubusercontent.com/u/62732795?v=4",
+ "profile": "https://github.com/goYou",
+ "contributions": [
+ "translation"
+ ]
}
],
- "contributorsPerLine": 7
+ "contributorsPerLine": 7,
+ "skipCi": true
}
diff --git a/.eslintignore b/.eslintignore
index c87fbd4d..d3405a7a 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,4 @@
-/node_modules
-/assets/vendor
-/out
-/dist
\ No newline at end of file
+node_modules
+assets
+out
+dist
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
index 5c86654c..54031e45 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -45,6 +45,7 @@
"no-console": "off",
"no-undef": "off",
"vue/no-side-effects-in-computed-properties": "off",
+ "vue/multi-word-component-names": "off",
"vue/require-default-prop": "off",
"vue/comment-directive": "off",
"vue/no-v-html": "off",
@@ -61,10 +62,11 @@
"vue/max-attributes-per-line": [
"error",
{
- "singleline": 2,
+ "singleline": {
+ "max": 2
+ },
"multiline": {
- "max": 1,
- "allowFirstLine": false
+ "max": 1
}
}
]
diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml
index d0a63f9c..edb77ac1 100644
--- a/.github/workflows/build-linux.yml
+++ b/.github/workflows/build-linux.yml
@@ -12,12 +12,18 @@ jobs:
steps:
- name: Check out Git repository
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
+
+ - name: Install dependencies
+ run: npm i
+
+ - name: Run tests
+ run: npm run test
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml
index 95679142..9981e0a8 100644
--- a/.github/workflows/build-mac.yml
+++ b/.github/workflows/build-mac.yml
@@ -12,12 +12,18 @@ jobs:
steps:
- name: Check out Git repository
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
+
+ - name: Install dependencies
+ run: npm i
+
+ - name: Run tests
+ run: npm run test
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml
index 7495faa5..747902d8 100644
--- a/.github/workflows/build-win.yml
+++ b/.github/workflows/build-win.yml
@@ -12,13 +12,19 @@ jobs:
steps:
- name: Check out Git repository
- uses: actions/checkout@v1
+ uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 14
+ - name: Install dependencies
+ run: npm i
+
+ - name: Run tests
+ run: npm run test
+
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5b0c8130..768edbfb 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,7 +3,9 @@
"UI",
"core",
"MySQL",
- "PostgreSQL"
+ "PostgreSQL",
+ "SQLite",
+ "Windows"
],
"svg.preview.background": "transparent"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0402fdd1..eef86af4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,128 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.4.3](https://github.com/Fabio286/antares/compare/v0.4.2...v0.4.3) (2022-01-30)
+
+
+### Features
+
+* add Simplified Chinese translation ([6ef565c](https://github.com/Fabio286/antares/commit/6ef565cf078cb3f5b7bcdc226894cddeb6239db9))
+* **MySQL:** spatial fields support ([#165](https://github.com/Fabio286/antares/issues/165)) ([48ebf23](https://github.com/Fabio286/antares/commit/48ebf23bd1574408f429f2e1200ce878352007f6))
+
+
+### Bug Fixes
+
+* cell copy returns "undefined" in some conditions, closes [#170](https://github.com/Fabio286/antares/issues/170) ([8fb1f08](https://github.com/Fabio286/antares/commit/8fb1f0803efd9df0b66521e73bb6e1a229cf9691))
+* indexes and foreign keys not cleared after deletion of related field, closes [#182](https://github.com/Fabio286/antares/issues/182) ([9f033fb](https://github.com/Fabio286/antares/commit/9f033fb994916b4fb165e81e55e86127ca817791))
+* **PostgreSQL:** schema different than public not automatically selected, closes [#172](https://github.com/Fabio286/antares/issues/172) ([46b45c8](https://github.com/Fabio286/antares/commit/46b45c8ab64fb6837a532c4f8342167e4fd794bb))
+* scale on numeric fields that doesn't support it ([0cfd793](https://github.com/Fabio286/antares/commit/0cfd7938ee7d607dbad66ae452d0200223a6bab2))
+* **Windows:** temporary fix to Windows 7 style frame on app startup, closes [#169](https://github.com/Fabio286/antares/issues/169) ([1356011](https://github.com/Fabio286/antares/commit/1356011ba3b7dd72e12cb252a8787ce48a364fd4))
+
+
+### Improvements
+
+* support of scale in field's length setting ([eef7c1d](https://github.com/Fabio286/antares/commit/eef7c1dcecc6593ab0e69ed678187a57fe0a4fb6))
+
+### [0.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
+
+
+### Features
+
+* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
+* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
+* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
+* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
+
+
+### Bug Fixes
+
+* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
+
+
+### Improvements
+
+* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
+* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
+
+### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)
+
+
+### Features
+
+* language format detection for text fields ([a5fdcc1](https://github.com/Fabio286/antares/commit/a5fdcc1a85aa188ff1b9a15b1a768aced026f360))
+
+
+### Bug Fixes
+
+* cell disappear on edit in one column tables ([aaa5549](https://github.com/Fabio286/antares/commit/aaa5549609664665bd4513632d621cb249b379c1))
+* false positive with Windows Defender ([992a033](https://github.com/Fabio286/antares/commit/992a033cb2bede3d1eb52e19482d810f6692de1e))
+* **MySQL:** wrong datetime fields default in table filler in some cases ([8da0224](https://github.com/Fabio286/antares/commit/8da022487650039b7f34a9c86a7bd9045eba65e2))
+* **MySQL:** wrong value for fields "on update" in some conditions ([359e14a](https://github.com/Fabio286/antares/commit/359e14a9ebd48f86069ba7762fe00a7056f52d47))
+* select all rows with ctrl+a when editing a cell ([35cb7e1](https://github.com/Fabio286/antares/commit/35cb7e1dc48d3a74e9d106cb1a37f454c1b4a4d1))
+* **SQLite:** update rows with a text primary key ([d7f1aa9](https://github.com/Fabio286/antares/commit/d7f1aa97af32a4c51fc7022498bd47e15fa08430))
+
+
+### Improvements
+
+* **UI:** avoid columns size change when editing cells or scrolling results ([813aa32](https://github.com/Fabio286/antares/commit/813aa320d9ab799efea38a7110b7c0bdf7549123))
+* **UI:** disable save button in table creation when no fields are added ([e8af2d2](https://github.com/Fabio286/antares/commit/e8af2d24a869f7667c069936648808952d2062ab))
+
+## [0.4.0](https://github.com/Fabio286/antares/compare/v0.3.9...v0.4.0) (2021-11-24)
+
+
+### Features
+
+* **MySQL:** read-only mode ([4437d44](https://github.com/Fabio286/antares/commit/4437d44486c4f20b0bec4bf89d56016b08e36e79))
+* **PostgreSQL:** read-only mode ([5d48fe0](https://github.com/Fabio286/antares/commit/5d48fe08c77755ed18b3f7a9ea834268e317e7ef))
+* **SQLite:** cell update in data tabs ([604b371](https://github.com/Fabio286/antares/commit/604b3719204f7473ce4846624f08f8be9eec8b8f))
+* **SQLite:** connection add/edit masks ([c54438d](https://github.com/Fabio286/antares/commit/c54438d6d3bad38bc76dfcd61f58929fe30279cb))
+* **SQLite:** keys support ([fd321be](https://github.com/Fabio286/antares/commit/fd321beece075d3ad23fdd8541f9beb5727045a5))
+* **SQLite:** readonly mode ([3fc227d](https://github.com/Fabio286/antares/commit/3fc227d2de53aae115226ad3c965bfb6e9f3eca6))
+* **SQLite:** table data visualization ([f2fcc98](https://github.com/Fabio286/antares/commit/f2fcc9883972402eab4d51ef2a9796638dde2d3d))
+* **SQLite:** tables management ([3efeb45](https://github.com/Fabio286/antares/commit/3efeb45c460f178b794de72367f8d542fd8ddd56))
+* **SQLite:** triggers management ([f40e9c5](https://github.com/Fabio286/antares/commit/f40e9c592eeffd204aba21a0a0767a0c523fca49))
+* **SQLite:** views management ([7671c58](https://github.com/Fabio286/antares/commit/7671c585f5f8049bd863db190d4fc60d8f0c6c66))
+
+
+### Bug Fixes
+
+* **SQLite:** hide schema creation ([98165ca](https://github.com/Fabio286/antares/commit/98165cacaa158c85ead0490d3caf579e2a17319f))
+* **UI:** hide tools menu if no tools available ([da1947e](https://github.com/Fabio286/antares/commit/da1947e4efa7f0a26d6a231fadf750be055fbdd5))
+* **UI:** notifications timeout anomalies ([cc99491](https://github.com/Fabio286/antares/commit/cc99491fe4a15812368f6c928b8c7801d7b255aa))
+
+
+### Improvements
+
+* **SQLite:** improvements in data visualization ([94c899e](https://github.com/Fabio286/antares/commit/94c899eb8288b41a5962ac3d24365227e1f9f485))
+* **SQLite:** improvements in field length detection ([93b4a70](https://github.com/Fabio286/antares/commit/93b4a7063beeb5a7001cb06a74f05b23105212f5))
+* update italian traslation ([9fe3680](https://github.com/Fabio286/antares/commit/9fe3680bbb17c192cffa85348e68794ab49beb81))
+
+### [0.3.9](https://github.com/Fabio286/antares/compare/v0.3.8...v0.3.9) (2021-11-14)
+
+
+### Features
+
+* added macos basic shortcusts and menu ([430490a](https://github.com/Fabio286/antares/commit/430490ad93f3148962ced1f13a5330c79cd86b3b))
+* **MySQL:** enable/disable schedulers from contextual menu ([5ca3a22](https://github.com/Fabio286/antares/commit/5ca3a22dc538b27a4bf6402f1288c4b9f5bc5a90))
+* **MySQL:** scheduler status indicator in explore bar ([5c66824](https://github.com/Fabio286/antares/commit/5c668249cf102cd9d601f9f7b4943c7155775217))
+* **PostgreSQL:** enable/disable triggers from contextual menu ([534659f](https://github.com/Fabio286/antares/commit/534659f9aee12eb5ac477f91bfe5d764387dc17e))
+* schema size in explore bar ([fd25f88](https://github.com/Fabio286/antares/commit/fd25f881f95779709156cbad93a41d6b391f1a45))
+* **UI:** double click on the title bar will toggle window fullscreen size ([a35566f](https://github.com/Fabio286/antares/commit/a35566f273322602abe434b8bd30817ba8885900))
+* **UI:** improved topbar look&feel on MacOS ([7657d05](https://github.com/Fabio286/antares/commit/7657d05edfbeaed6a14eb337fc562da5126e6ba0))
+
+
+### Bug Fixes
+
+* copy&paste and basic usability on macOS ([1ddf8f0](https://github.com/Fabio286/antares/commit/1ddf8f0dbe22f94d6bffddf70636706d2d142ecf))
+* **PostgreSQL:** bigint fetched as string instead of number, closes [#134](https://github.com/Fabio286/antares/issues/134) ([39b9a59](https://github.com/Fabio286/antares/commit/39b9a59143b457a96f0711a3b8588c92dd80e28d))
+* row selection problems after a deletion fail, closes [#128](https://github.com/Fabio286/antares/issues/128) ([89fdd21](https://github.com/Fabio286/antares/commit/89fdd210ca48fc9ae399b195ea796c8523619627))
+* temporary solution on MacOS for unsigned app updates ([c00fd13](https://github.com/Fabio286/antares/commit/c00fd1381f451ba7aace7047b28b904ddcaf18f0))
+
+
+### Improvements
+
+* **UI:** improved function and routine parameters modals ([d19f475](https://github.com/Fabio286/antares/commit/d19f475fc28c0367ada569cb634769fa618b48b4))
+
### [0.3.8](https://github.com/Fabio286/antares/compare/v0.3.7...v0.3.8) (2021-10-23)
diff --git a/README.md b/README.md
index af1fe470..c0aa199b 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 and PostgreSQL.
+**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, 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.
@@ -31,10 +31,9 @@ We are actively working on it, hoping to provide new cool features, improvements
- Query suggestions and auto complete.
- Query history: search through the last 1000 queries.
- SSH tunnel support.
+- Manual commit mode.
- Dark and light theme.
- Editor themes.
-- Scratchpad.
-- Secure password storage.
## Philosophy
@@ -68,12 +67,12 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
This is a roadmap with major features will come in near future.
-- Support for other databases.
- Database tools.
- Users management (add/edit/delete).
- More context menu shortcuts.
- More keyboard shortcuts.
- Import/export and migration.
+- Support for other databases.
- Apple Silicon distribution
## Currently supported
@@ -82,7 +81,7 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB
- [x] PostgreSQL
-- [ ] SQLite
+- [x] SQLite
- [ ] MSSQL
- [ ] OracleDB
- [ ] More...
@@ -116,9 +115,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
diff --git a/assets/appx/LargeTile.png b/assets/appx/LargeTile.png
deleted file mode 100644
index afef8cac..00000000
Binary files a/assets/appx/LargeTile.png and /dev/null differ
diff --git a/assets/appx/SmallTile.png b/assets/appx/SmallTile.png
deleted file mode 100644
index 91b41331..00000000
Binary files a/assets/appx/SmallTile.png and /dev/null differ
diff --git a/assets/appx/Square150x150Logo.png b/assets/appx/Square150x150Logo.png
index 730eb946..007497bb 100644
Binary files a/assets/appx/Square150x150Logo.png and b/assets/appx/Square150x150Logo.png differ
diff --git a/assets/appx/Square44x44Logo.png b/assets/appx/Square44x44Logo.png
index 444becef..6cfb69b7 100644
Binary files a/assets/appx/Square44x44Logo.png and b/assets/appx/Square44x44Logo.png differ
diff --git a/assets/appx/StoreLogo.png b/assets/appx/StoreLogo.png
index 0ba72c45..6b03a448 100644
Binary files a/assets/appx/StoreLogo.png and b/assets/appx/StoreLogo.png differ
diff --git a/assets/appx/Wide310x150Logo.png b/assets/appx/Wide310x150Logo.png
index fbe0e56b..4ffec399 100644
Binary files a/assets/appx/Wide310x150Logo.png and b/assets/appx/Wide310x150Logo.png differ
diff --git a/package.json b/package.json
index 08617900..cf93c684 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "antares",
"productName": "Antares",
- "version": "0.3.8",
- "description": "A cross-platform easy to use SQL client.",
+ "version": "0.4.3",
+ "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT",
"repository": "https://github.com/Fabio286/antares.git",
"scripts": {
@@ -14,11 +14,11 @@
"build": "cross-env NODE_ENV=production npm run compile",
"build:local": "npm run build && electron-builder",
"build:appx": "npm run build:local -- --win appx",
- "rebuild:electron": "npm run postinstall && electron-rebuild",
+ "rebuild:electron": "npm run postinstall",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"postinstall": "electron-builder install-app-deps",
- "test": "npm run lint",
+ "test": "npm run compile && node tests/app.spec.js",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add",
@@ -30,6 +30,7 @@
"appId": "com.fabio286.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
"asar": true,
+ "buildDependenciesFromSource": true,
"directories": {
"output": "build",
"buildResources": "assets"
@@ -50,7 +51,8 @@
"target": {
"target": "default",
"arch": [
- "x64"
+ "x64",
+ "arm64"
]
}
},
@@ -79,7 +81,9 @@
"artifactName": "${productName}-${version}-portable.exe"
},
"appx": {
- "displayName": "Antares SQL Client",
+ "displayName": "Antares SQL",
+ "backgroundColor": "transparent",
+ "showNameOnTiles": true,
"identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient"
@@ -102,12 +106,17 @@
"dependencies": {
"@electron/remote": "^2.0.1",
"@mdi/font": "^6.1.95",
+ "@turf/helpers": "^6.5.0",
+ "@vscode/vscode-languagedetection": "^1.0.21",
"ace-builds": "^1.4.13",
+ "better-sqlite3": "^7.4.4",
"electron-log": "^4.4.1",
"electron-store": "^8.0.1",
- "electron-updater": "^4.3.9",
+ "electron-updater": "^4.6.1",
+ "electron-window-state": "^5.0.3",
"faker": "^5.5.3",
- "marked": "^3.0.4",
+ "leaflet": "^1.7.1",
+ "marked": "^4.0.0",
"moment": "^2.29.1",
"mysql2": "^2.3.2",
"pg": "^8.7.1",
@@ -130,23 +139,23 @@
"clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.2",
"css-loader": "^6.5.0",
- "electron": "^15.3.0",
- "electron-builder": "^22.13.1",
+ "electron": "^17.0.1",
+ "electron-builder": "^22.14.11",
"electron-devtools-installer": "^3.2.0",
- "electron-rebuild": "^3.2.3",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
- "eslint-plugin-promise": "^5.1.0",
- "eslint-plugin-vue": "^7.18.0",
+ "eslint-plugin-promise": "^5.2.0",
+ "eslint-plugin-vue": "^8.0.3",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
- "mini-css-extract-plugin": "^2.4.3",
+ "mini-css-extract-plugin": "~2.4.5",
"node-loader": "^2.0.0",
+ "playwright": "^1.18.1",
"progress-webpack-plugin": "^1.0.12",
"sass": "^1.42.1",
- "sass-loader": "^10.2.0",
+ "sass-loader": "^12.3.0",
"standard-version": "^9.3.1",
"style-loader": "^3.3.1",
"stylelint": "^13.13.1",
@@ -158,6 +167,6 @@
"vue-template-compiler": "^2.6.14",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1",
- "webpack-dev-server": "^3.11.2"
+ "webpack-dev-server": "^4.4.0"
}
-}
\ No newline at end of file
+}
diff --git a/scripts/devRunner.js b/scripts/devRunner.js
index bfbea89d..ed3eff40 100644
--- a/scripts/devRunner.js
+++ b/scripts/devRunner.js
@@ -113,14 +113,15 @@ function startRenderer (callback) {
});
const server = new WebpackDevServer(compiler, {
- contentBase: path.join(__dirname, '../'),
hot: true,
- noInfo: true,
- overlay: true,
- clientLogLevel: 'warning'
+ port: 9080,
+ client: {
+ overlay: true,
+ logging: 'warn'
+ }
});
- server.listen(9080, '', err => {
+ server.startCallback(err => {
if (err) console.error(chalk.red(err));
callback();
diff --git a/src/common/FakerMethods.js b/src/common/FakerMethods.js
index fe7c7ec4..e401fa11 100644
--- a/src/common/FakerMethods.js
+++ b/src/common/FakerMethods.js
@@ -134,7 +134,7 @@ export default class {
{ name: 'phoneNumberFormat', group: 'phone', types: ['string'] },
{ name: 'phoneFormats', group: 'phone', types: ['string'] },
- { name: 'number', group: 'random', types: ['string', 'number'], params: ['min', 'max'] },
+ { name: 'number', group: 'datatype', types: ['string', 'number'], params: ['min', 'max'] },
{ name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] },
{ name: 'arrayElement', group: 'random', types: ['string'] },
{ name: 'arrayElements', group: 'random', types: ['string'] },
diff --git a/src/common/customizations/defaults.js b/src/common/customizations/defaults.js
index 1cc0fe47..ad5788df 100644
--- a/src/common/customizations/defaults.js
+++ b/src/common/customizations/defaults.js
@@ -8,6 +8,10 @@ module.exports = {
collations: false,
engines: false,
connectionSchema: false,
+ sslConnection: false,
+ sshConnection: false,
+ fileConnection: false,
+ cancelQueries: false,
// Tools
processesList: false,
usersManagement: false,
@@ -33,7 +37,12 @@ module.exports = {
schedulerAdd: false,
databaseEdit: false,
schemaEdit: false,
+ schemaDrop: false,
+ schemaExport: false,
tableSettings: false,
+ tableOptions: false,
+ tableArray: false,
+ tableRealCount: false,
viewSettings: false,
triggerSettings: false,
triggerFunctionSettings: false,
@@ -45,14 +54,13 @@ module.exports = {
sortableFields: false,
unsigned: false,
nullable: false,
+ nullablePrimary: false,
zerofill: false,
- tableOptions: false,
autoIncrement: false,
comment: false,
collation: false,
definer: false,
onUpdate: false,
- tableArray: false,
viewAlgorithm: false,
viewSqlSecurity: false,
viewUpdateOption: false,
@@ -72,8 +80,10 @@ module.exports = {
triggerTableInName: false,
triggerUpdateColumns: false,
triggerOnlyRename: false,
+ triggerEnableDisable: false,
triggerFunctionSql: false,
triggerFunctionlanguages: false,
parametersLength: false,
- languages: false
+ languages: false,
+ readOnlyMode: false
};
diff --git a/src/common/customizations/index.js b/src/common/customizations/index.js
index 304889a4..931e18f2 100644
--- a/src/common/customizations/index.js
+++ b/src/common/customizations/index.js
@@ -1,5 +1,6 @@
module.exports = {
maria: require('./mysql'),
mysql: require('./mysql'),
- pg: require('./postgresql')
+ pg: require('./postgresql'),
+ sqlite: require('./sqlite')
};
diff --git a/src/common/customizations/mysql.js b/src/common/customizations/mysql.js
index 79fae6bc..d037d26c 100644
--- a/src/common/customizations/mysql.js
+++ b/src/common/customizations/mysql.js
@@ -10,6 +10,9 @@ module.exports = {
connectionSchema: true,
collations: true,
engines: true,
+ sslConnection: true,
+ sshConnection: true,
+ cancelQueries: true,
// Tools
processesList: true,
// Structure
@@ -30,6 +33,7 @@ module.exports = {
functionAdd: true,
schedulerAdd: true,
schemaEdit: true,
+ schemaDrop: true,
schemaExport: true,
tableSettings: true,
viewSettings: true,
@@ -60,5 +64,6 @@ module.exports = {
functionDeterministic: true,
functionDataAccess: true,
functionSql: 'BEGIN\r\n\r\nEND',
- parametersLength: true
+ parametersLength: true,
+ readOnlyMode: true
};
diff --git a/src/common/customizations/postgresql.js b/src/common/customizations/postgresql.js
index 655a716b..3d827c11 100644
--- a/src/common/customizations/postgresql.js
+++ b/src/common/customizations/postgresql.js
@@ -8,9 +8,13 @@ module.exports = {
defaultDatabase: 'postgres',
// Core
database: true,
+ sslConnection: true,
+ sshConnection: true,
+ cancelQueries: true,
// Tools
processesList: true,
// Structure
+ schemas: true,
tables: true,
views: true,
triggers: true,
@@ -26,6 +30,7 @@ module.exports = {
triggerFunctionAdd: true,
routineAdd: true,
functionAdd: true,
+ schemaDrop: true,
databaseEdit: false,
schemaExport: true,
tableSettings: true,
@@ -51,5 +56,7 @@ module.exports = {
triggerMultipleEvents: true,
triggerTableInName: true,
triggerOnlyRename: false,
- languages: ['sql', 'plpgsql', 'c', 'internal']
+ triggerEnableDisable: true,
+ languages: ['sql', 'plpgsql', 'c', 'internal'],
+ readOnlyMode: true
};
diff --git a/src/common/customizations/sqlite.js b/src/common/customizations/sqlite.js
new file mode 100644
index 00000000..83c6cdb3
--- /dev/null
+++ b/src/common/customizations/sqlite.js
@@ -0,0 +1,27 @@
+module.exports = {
+ // Core
+ fileConnection: true,
+ // Structure
+ schemas: false,
+ tables: true,
+ views: true,
+ triggers: true,
+ // Settings
+ elementsWrapper: '"',
+ stringsWrapper: '\'',
+ tableAdd: true,
+ viewAdd: true,
+ triggerAdd: true,
+ schemaEdit: false,
+ tableSettings: true,
+ tableRealCount: true,
+ viewSettings: true,
+ triggerSettings: true,
+ indexes: true,
+ foreigns: true,
+ sortableFields: true,
+ nullable: true,
+ nullablePrimary: true,
+ triggerSql: 'BEGIN\r\n\r\nEND',
+ readOnlyMode: true
+};
diff --git a/src/common/data-types/mysql.js b/src/common/data-types/mysql.js
index 78e3d2c9..14b61229 100644
--- a/src/common/data-types/mysql.js
+++ b/src/common/data-types/mysql.js
@@ -66,6 +66,7 @@ module.exports = [
{
name: 'DECIMAL',
length: true,
+ scale: true,
collation: false,
unsigned: false,
zerofill: false
@@ -120,7 +121,7 @@ module.exports = [
{
name: 'JSON',
length: false,
- collation: true,
+ collation: false,
unsigned: false,
zerofill: false
}
@@ -218,56 +219,56 @@ module.exports = [
types: [
{
name: 'POINT',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'LINESTRING',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'POLYGON',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'GEOMETRY',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTIPOINT',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTILINESTRING',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
name: 'MULTIPOLYGON',
- length: true,
+ length: false,
collation: false,
unsigned: false,
zerofill: false
},
{
- name: 'GEOMETRYCOLLECTION',
- length: true,
+ name: 'GEOMCOLLECTION',
+ length: false,
collation: false,
unsigned: false,
zerofill: false
diff --git a/src/common/data-types/postgresql.js b/src/common/data-types/postgresql.js
index bfe9e50d..e666229b 100644
--- a/src/common/data-types/postgresql.js
+++ b/src/common/data-types/postgresql.js
@@ -22,11 +22,6 @@ module.exports = [
length: false,
unsigned: true
},
- {
- name: 'NUMERIC',
- length: true,
- unsigned: true
- },
{
name: 'SMALLSERIAL',
length: false,
@@ -52,6 +47,12 @@ module.exports = [
length: false,
unsigned: true
},
+ {
+ name: 'NUMERIC',
+ length: true,
+ unsigned: true,
+ scale: true
+ },
{
name: 'DOUBLE PRECISION',
length: false,
diff --git a/src/common/data-types/sqlite.js b/src/common/data-types/sqlite.js
new file mode 100644
index 00000000..c7ac6b32
--- /dev/null
+++ b/src/common/data-types/sqlite.js
@@ -0,0 +1,137 @@
+module.exports = [
+ {
+ group: 'integer',
+ types: [
+ {
+ name: 'INT',
+ 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
+ },
+ {
+ name: 'NUMERIC',
+ length: true,
+ collation: false,
+ unsigned: true,
+ zerofill: true
+ },
+ {
+ name: 'BOOLEAN',
+ length: false,
+ collation: false,
+ unsigned: true,
+ zerofill: true
+ }
+ ]
+ },
+ {
+ group: 'float',
+ types: [
+ {
+ name: 'FLOAT',
+ length: true,
+ collation: false,
+ unsigned: false,
+ zerofill: false
+ },
+ {
+ name: 'REAL',
+ 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
+ },
+ {
+ name: 'TEXT',
+ 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: 'DATETIME',
+ length: true,
+ collation: false,
+ unsigned: false,
+ zerofill: false
+ }
+ ]
+ },
+ {
+ group: 'other',
+ types: [
+ {
+ name: 'NONE',
+ length: false,
+ collation: false,
+ unsigned: false,
+ zerofill: false
+ }
+ ]
+ }
+];
diff --git a/src/common/fieldTypes.js b/src/common/fieldTypes.js
index d3a8a1db..a7d3399f 100644
--- a/src/common/fieldTypes.js
+++ b/src/common/fieldTypes.js
@@ -8,7 +8,9 @@ export const TEXT = [
export const LONG_TEXT = [
'TEXT',
'MEDIUMTEXT',
- 'LONGTEXT'
+ 'LONGTEXT',
+ 'JSON',
+ 'VARBINARY'
];
export const ARRAY = [
@@ -82,3 +84,24 @@ export const BIT = [
'BIT',
'BIT VARYING'
];
+
+export const SPATIAL = [
+ 'POINT',
+ 'LINESTRING',
+ 'POLYGON',
+ 'GEOMETRY',
+ 'MULTIPOINT',
+ 'MULTILINESTRING',
+ 'MULTIPOLYGON',
+ 'GEOMCOLLECTION',
+ 'GEOMETRYCOLLECTION'
+];
+
+// Used to check multi spatial fields only
+export const IS_MULTI_SPATIAL = [
+ 'MULTIPOINT',
+ 'MULTILINESTRING',
+ 'MULTIPOLYGON',
+ 'GEOMCOLLECTION',
+ 'GEOMETRYCOLLECTION'
+];
diff --git a/src/common/index-types/sqlite.js b/src/common/index-types/sqlite.js
new file mode 100644
index 00000000..edc2f2a3
--- /dev/null
+++ b/src/common/index-types/sqlite.js
@@ -0,0 +1,5 @@
+module.exports = [
+ 'PRIMARY',
+ 'INDEX',
+ 'UNIQUE'
+];
diff --git a/src/common/libs/getArrayDepth.js b/src/common/libs/getArrayDepth.js
new file mode 100644
index 00000000..04e62e77
--- /dev/null
+++ b/src/common/libs/getArrayDepth.js
@@ -0,0 +1,10 @@
+/**
+ *
+ * @param {any[]} array
+ * @returns {number}
+ */
+export function getArrayDepth (array) {
+ return Array.isArray(array)
+ ? 1 + Math.max(0, ...array.map(getArrayDepth))
+ : 0;
+}
diff --git a/src/main/ipc-handlers/connection.js b/src/main/ipc-handlers/connection.js
index aa2c1378..c539f75a 100644
--- a/src/main/ipc-handlers/connection.js
+++ b/src/main/ipc-handlers/connection.js
@@ -9,12 +9,16 @@ export default connections => {
port: +conn.port,
user: conn.user,
password: conn.password,
- application_name: 'Antares SQL'
+ application_name: 'Antares SQL',
+ readonly: conn.readonly
};
if (conn.database)
params.database = conn.database;
+ if (conn.databasePath)
+ params.databasePath = conn.databasePath;
+
if (conn.ssl) {
params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -48,7 +52,7 @@ export default connections => {
return { status: 'success' };
}
catch (err) {
- return { status: 'error', response: err };
+ return { status: 'error', response: err.toString() };
}
});
@@ -62,12 +66,16 @@ export default connections => {
port: +conn.port,
user: conn.user,
password: conn.password,
- application_name: 'Antares SQL'
+ application_name: 'Antares SQL',
+ readonly: conn.readonly
};
if (conn.database)
params.database = conn.database;
+ if (conn.databasePath)
+ params.databasePath = conn.databasePath;
+
if (conn.schema)
params.schema = conn.schema;
diff --git a/src/main/ipc-handlers/schedulers.js b/src/main/ipc-handlers/schedulers.js
index 4133fbed..f270beb3 100644
--- a/src/main/ipc-handlers/schedulers.js
+++ b/src/main/ipc-handlers/schedulers.js
@@ -40,4 +40,17 @@ export default (connections) => {
return { status: 'error', response: err.toString() };
}
});
+
+ ipcMain.handle('toggle-scheduler', async (event, params) => {
+ try {
+ if (!params.enabled)
+ await connections[params.uid].enableEvent({ ...params });
+ else
+ await connections[params.uid].disableEvent({ ...params });
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
};
diff --git a/src/main/ipc-handlers/schema.js b/src/main/ipc-handlers/schema.js
index c59d9e0f..f161a5ae 100644
--- a/src/main/ipc-handlers/schema.js
+++ b/src/main/ipc-handlers/schema.js
@@ -149,7 +149,7 @@ export default connections => {
}
});
- ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
+ ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid, autocommit }) => {
if (!query) return;
try {
@@ -157,6 +157,8 @@ export default connections => {
nest: true,
details: true,
schema,
+ tabUid,
+ autocommit,
comments: false
});
@@ -263,4 +265,51 @@ export default connections => {
return { status: 'success', response: { willAbort } };
});
+ ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
+ if (!tabUid) return;
+
+ try {
+ await connections[uid].killTabQuery(tabUid);
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
+
+ ipcMain.handle('commit-tab', async (event, { uid, tabUid }) => {
+ if (!tabUid) return;
+
+ try {
+ await connections[uid].commitTab(tabUid);
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
+
+ ipcMain.handle('rollback-tab', async (event, { uid, tabUid }) => {
+ if (!tabUid) return;
+
+ try {
+ await connections[uid].rollbackTab(tabUid);
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
+
+ ipcMain.handle('destroy-connection-to-commit', async (event, { uid, tabUid }) => {
+ if (!tabUid) return;
+
+ try {
+ await connections[uid].destroyConnectionToCommit(tabUid);
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
};
diff --git a/src/main/ipc-handlers/tables.js b/src/main/ipc-handlers/tables.js
index 47ef823f..7791aa5b 100644
--- a/src/main/ipc-handlers/tables.js
+++ b/src/main/ipc-handlers/tables.js
@@ -3,6 +3,7 @@ import faker from 'faker';
import moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
+import * as customizations from 'common/customizations';
import fs from 'fs';
export default (connections) => {
@@ -84,12 +85,13 @@ export default (connections) => {
});
ipcMain.handle('update-table-cell', async (event, params) => {
- delete params.row._id;
+ delete params.row._antares_id;
+ const { stringsWrapper: sw } = customizations[connections[params.uid]._client];
try { // TODO: move to client classes
let escapedParam;
let reload = false;
- const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
+ const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
if ([...NUMBER, ...FLOAT].includes(params.type))
escapedParam = params.content;
@@ -102,6 +104,9 @@ export default (connections) => {
case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
+ case 'sqlite':
+ escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
+ break;
}
}
else if (ARRAY.includes(params.type))
@@ -122,6 +127,10 @@ export default (connections) => {
fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break;
+ case 'sqlite':
+ fileBlob = fs.readFileSync(params.content);
+ escapedParam = `X'${fileBlob.toString('hex')}'`;
+ break;
}
reload = true;
}
@@ -134,6 +143,9 @@ export default (connections) => {
case 'pg':
escapedParam = 'decode(\'\', \'hex\')';
break;
+ case 'sqlite':
+ escapedParam = 'X\'\'';
+ break;
}
}
}
@@ -188,7 +200,7 @@ export default (connections) => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string'
- ? `"${row[fieldName]}"`
+ ? `'${row[fieldName]}'`
: row[fieldName];
}).join(',');
diff --git a/src/main/ipc-handlers/triggers.js b/src/main/ipc-handlers/triggers.js
index b7edaeab..89df9a15 100644
--- a/src/main/ipc-handlers/triggers.js
+++ b/src/main/ipc-handlers/triggers.js
@@ -40,4 +40,17 @@ export default (connections) => {
return { status: 'error', response: err.toString() };
}
});
+
+ ipcMain.handle('toggle-trigger', async (event, params) => {
+ try {
+ if (!params.enabled)
+ await connections[params.uid].enableTrigger(params);
+ else
+ await connections[params.uid].disableTrigger(params);
+ return { status: 'success' };
+ }
+ catch (err) {
+ return { status: 'error', response: err.toString() };
+ }
+ });
};
diff --git a/src/main/ipc-handlers/updates.js b/src/main/ipc-handlers/updates.js
index ff72702e..19636b52 100644
--- a/src/main/ipc-handlers/updates.js
+++ b/src/main/ipc-handlers/updates.js
@@ -2,6 +2,7 @@ import { ipcMain } from 'electron';
import { autoUpdater } from 'electron-updater';
import Store from 'electron-store';
const persistentStore = new Store({ name: 'settings' });
+const isMacOS = process.platform === 'darwin';
let mainWindow;
autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true);
@@ -11,6 +12,9 @@ export default () => {
mainWindow = event;
if (process.windowsStore || (process.platform === 'linux' && !process.env.APPIMAGE))
mainWindow.reply('no-auto-update');
+ else if (isMacOS) { // Temporary solution on MacOS for unsigned app updates
+ autoUpdater.autoDownload = false;
+ }
else {
autoUpdater.checkForUpdatesAndNotify().catch(() => {
mainWindow.reply('check-failed');
@@ -28,7 +32,10 @@ export default () => {
});
autoUpdater.on('update-available', () => {
- mainWindow.reply('update-available');
+ if (isMacOS)
+ mainWindow.reply('link-to-download');
+ else
+ mainWindow.reply('update-available');
});
autoUpdater.on('update-not-available', () => {
diff --git a/src/main/libs/ClientsFactory.js b/src/main/libs/ClientsFactory.js
index 54211908..6543eaff 100644
--- a/src/main/libs/ClientsFactory.js
+++ b/src/main/libs/ClientsFactory.js
@@ -1,6 +1,7 @@
'use strict';
import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient';
+import { SQLiteClient } from './clients/SQLiteClient';
const queryLogger = sql => {
// Remove comments, newlines and multiple spaces
@@ -37,6 +38,8 @@ export class ClientsFactory {
return new MySQLClient(args);
case 'pg':
return new PostgreSQLClient(args);
+ case 'sqlite':
+ return new SQLiteClient(args);
default:
throw new Error(`Unknown database client: ${args.client}`);
}
diff --git a/src/main/libs/clients/MySQLClient.js b/src/main/libs/clients/MySQLClient.js
index 1fce40f9..d31e1be6 100644
--- a/src/main/libs/clients/MySQLClient.js
+++ b/src/main/libs/clients/MySQLClient.js
@@ -9,6 +9,8 @@ export class MySQLClient extends AntaresCore {
super(args);
this._schema = null;
+ this._runningConnections = new Map();
+ this._connectionsToCommit = new Map();
this.types = {
0: 'DECIMAL',
@@ -100,9 +102,11 @@ export class MySQLClient extends AntaresCore {
}
/**
+ *
+ * @returns dbConfig
* @memberof MySQLClient
*/
- async connect () {
+ async getDbConfig () {
delete this._params.application_name;
const dbConfig = {
@@ -133,20 +137,17 @@ export class MySQLClient extends AntaresCore {
}
}
+ return dbConfig;
+ }
+
+ /**
+ * @memberof MySQLClient
+ */
+ async connect () {
if (!this._poolSize)
- this._connection = await mysql.createConnection(dbConfig);
- else {
- this._connection = mysql.createPool({
- ...dbConfig,
- connectionLimit: this._poolSize,
- typeCast: (field, next) => {
- if (field.type === 'DATETIME')
- return field.string();
- else
- return next();
- }
- });
- }
+ this._connection = await this.getConnection();
+ else
+ this._connection = await this.getConnectionPool();
}
/**
@@ -157,6 +158,64 @@ export class MySQLClient extends AntaresCore {
if (this._ssh) this._ssh.close();
}
+ async getConnection () {
+ const dbConfig = await this.getDbConfig();
+ const connection = await mysql.createConnection({
+ ...dbConfig,
+ typeCast: (field, next) => {
+ if (field.type === 'DATETIME')
+ return field.string();
+ else
+ return next();
+ }
+ });
+
+ // ANSI_QUOTES check
+ const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
+ const sqlMode = res[0]?.Variable_name?.split(',');
+ const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
+
+ if (this._params.readonly)
+ await connection.query('SET SESSION TRANSACTION READ ONLY');
+
+ if (hasAnsiQuotes)
+ await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
+
+ return connection;
+ }
+
+ async getConnectionPool () {
+ const dbConfig = await this.getDbConfig();
+ const connection = mysql.createPool({
+ ...dbConfig,
+ connectionLimit: this._poolSize,
+ typeCast: (field, next) => {
+ if (field.type === 'DATETIME')
+ return field.string();
+ else
+ return next();
+ }
+ });
+
+ // ANSI_QUOTES check
+ const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\'');
+ const sqlMode = res[0]?.Variable_name?.split(',');
+ const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
+
+ if (hasAnsiQuotes)
+ await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
+
+ connection.on('connection', conn => {
+ if (this._params.readonly)
+ conn.query('SET SESSION TRANSACTION READ ONLY');
+
+ if (hasAnsiQuotes)
+ conn.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
+ });
+
+ return connection;
+ }
+
/**
* Executes an USE query
*
@@ -187,6 +246,7 @@ export class MySQLClient extends AntaresCore {
const tablesArr = [];
const triggersArr = [];
+ let schemaSize = 0;
for (const db of filteredDatabases) {
if (!schemas.has(db.Database)) continue;
@@ -224,6 +284,9 @@ export class MySQLClient extends AntaresCore {
break;
}
+ const tableSize = table.Data_length + table.Index_length;
+ schemaSize += tableSize;
+
return {
name: table.Name,
type: tableType,
@@ -232,7 +295,7 @@ export class MySQLClient extends AntaresCore {
updated: table.Update_time,
engine: table.Engine,
comment: table.Comment,
- size: table.Data_length + table.Index_length,
+ size: tableSize,
autoIncrement: table.Auto_increment,
collation: table.Collation
};
@@ -276,7 +339,7 @@ export class MySQLClient extends AntaresCore {
body: scheduler.EVENT_BODY,
starts: scheduler.STARTS,
ends: scheduler.ENDS,
- status: scheduler.STATUS,
+ enabled: scheduler.STATUS === 'ENABLED',
executeAt: scheduler.EXECUTE_AT,
intervalField: scheduler.INTERVAL_FIELD,
intervalValue: scheduler.INTERVAL_VALUE,
@@ -309,6 +372,7 @@ export class MySQLClient extends AntaresCore {
return {
name: db.Database,
+ size: schemaSize,
tables: remappedTables,
functions: remappedFunctions,
procedures: remappedProcedures,
@@ -319,6 +383,7 @@ export class MySQLClient extends AntaresCore {
else {
return {
name: db.Database,
+ size: 0,
tables: [],
functions: [],
procedures: [],
@@ -360,7 +425,7 @@ export class MySQLClient extends AntaresCore {
return acc;
}, '')
.replaceAll('\n', '')
- .split(',')
+ .split(/,\s?(?![^(]*\))/)
.map(f => {
try {
const fieldArr = f.trim().split(' ');
@@ -400,18 +465,25 @@ export class MySQLClient extends AntaresCore {
return rows.map(field => {
let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/);
- numLength = numLength ? +numLength.pop() : null;
+ numLength = numLength ? +numLength.pop() : field.NUMERIC_PRECISION || null;
const enumValues = /(enum|set)/.test(field.COLUMN_TYPE)
? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1)
: null;
+ const defaultValue = (remappedFields && remappedFields[field.COLUMN_NAME])
+ ? remappedFields[field.COLUMN_NAME].default
+ : field.COLUMN_DEFAULT;
+
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
- type: remappedFields ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
+ type: (remappedFields && remappedFields[field.COLUMN_NAME])
+ ? remappedFields[field.COLUMN_NAME].type
+ : field.DATA_TYPE.toUpperCase(),
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION,
+ numScale: field.NUMERIC_SCALE,
numLength,
enumValues,
datePrecision: field.DATETIME_PRECISION,
@@ -420,11 +492,13 @@ export class MySQLClient extends AntaresCore {
unsigned: field.COLUMN_TYPE.includes('unsigned'),
zerofill: field.COLUMN_TYPE.includes('zerofill'),
order: field.ORDINAL_POSITION,
- default: remappedFields ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
+ default: defaultValue,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'),
- onUpdate: field.EXTRA.toLowerCase().includes('on update') ? field.EXTRA.replace('on update', '') : '',
+ onUpdate: field.EXTRA.toLowerCase().includes('on update')
+ ? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
+ : '',
comment: field.COLUMN_COMMENT
};
});
@@ -565,7 +639,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE DATABASE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createSchema (params) {
@@ -575,7 +649,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER DATABASE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterSchema (params) {
@@ -585,7 +659,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP DATABASE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropSchema (params) {
@@ -625,7 +699,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropView (params) {
@@ -636,7 +710,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterView (params) {
@@ -657,7 +731,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createView (params) {
@@ -690,7 +764,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropTrigger (params) {
@@ -701,7 +775,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterTrigger (params) {
@@ -723,7 +797,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createTrigger (params) {
@@ -797,7 +871,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP PROCEDURE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropRoutine (params) {
@@ -808,7 +882,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER PROCEDURE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterRoutine (params) {
@@ -830,7 +904,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE PROCEDURE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createRoutine (params) {
@@ -924,7 +998,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP FUNCTION
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropFunction (params) {
@@ -935,7 +1009,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER FUNCTION
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterFunction (params) {
@@ -957,7 +1031,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE FUNCTION
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createFunction (params) {
@@ -1018,7 +1092,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP EVENT
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropEvent (params) {
@@ -1029,7 +1103,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER EVENT
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterEvent (params) {
@@ -1055,7 +1129,7 @@ export class MySQLClient extends AntaresCore {
/**
* CREATE EVENT
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createEvent (params) {
@@ -1072,6 +1146,16 @@ export class MySQLClient extends AntaresCore {
return await this.raw(sql, { split: false });
}
+ async enableEvent ({ schema, scheduler }) {
+ const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` ENABLE`;
+ return await this.raw(sql, { split: false });
+ }
+
+ async disableEvent ({ schema, scheduler }) {
+ const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` DISABLE`;
+ return await this.raw(sql, { split: false });
+ }
+
/**
* SHOW COLLATION
*
@@ -1111,6 +1195,26 @@ export class MySQLClient extends AntaresCore {
});
}
+ /**
+ * SHOW VARIABLES LIKE %variable%
+ *
+ * @param {String} variable
+ * @param {'global'|'session'|null} level
+ * @returns {Object} variable
+ * @memberof MySQLClient
+ */
+ async getVariable (variable, level) {
+ const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`;
+ const results = await this.raw(sql);
+
+ if (results.rows.length) {
+ return {
+ name: results.rows[0].Variable_name,
+ value: results.rows[0].Value
+ };
+ }
+ }
+
/**
* SHOW ENGINES
*
@@ -1182,14 +1286,60 @@ export class MySQLClient extends AntaresCore {
});
}
+ /**
+ *
+ * @param {number} id
+ * @returns {Promise}
+ */
async killProcess (id) {
return await this.raw(`KILL ${id}`);
}
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async killTabQuery (tabUid) {
+ const id = this._runningConnections.get(tabUid);
+ if (id)
+ return await this.killProcess(id);
+ }
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async commitTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection)
+ return await connection.query('COMMIT');
+ }
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async rollbackTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection)
+ return await connection.query('ROLLBACK');
+ }
+
+ destroyConnectionToCommit (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ connection.destroy();
+ this._connectionsToCommit.delete(tabUid);
+ }
+ }
+
/**
* CREATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createTable (params) {
@@ -1212,7 +1362,7 @@ export class MySQLClient extends AntaresCore {
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`\`${field.name}\`
- ${field.type.toUpperCase()}${length ? `(${length})` : ''}
+ ${field.type.toUpperCase()}${length ? `(${length}${field.numScale ? `,${field.numScale}` : ''})` : ''}
${field.unsigned ? 'UNSIGNED' : ''}
${field.zerofill ? 'ZEROFILL' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'}
@@ -1251,7 +1401,7 @@ export class MySQLClient extends AntaresCore {
/**
* ALTER TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterTable (params) {
@@ -1281,7 +1431,7 @@ export class MySQLClient extends AntaresCore {
const length = typeInfo.length ? addition.enumValues || addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\`
- ${addition.type.toUpperCase()}${length ? `(${length})` : ''}
+ ${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale ? `,${addition.numScale}` : ''})` : ''}
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'}
@@ -1319,7 +1469,7 @@ export class MySQLClient extends AntaresCore {
const length = typeInfo.length ? change.enumValues || change.numLength || change.charLength || change.datePrecision : false;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\`
- ${change.type.toUpperCase()}${length ? `(${length})` : ''}
+ ${change.type.toUpperCase()}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}
${change.unsigned ? 'UNSIGNED' : ''}
${change.zerofill ? 'ZEROFILL' : ''}
${change.nullable ? 'NULL' : 'NOT NULL'}
@@ -1386,7 +1536,7 @@ export class MySQLClient extends AntaresCore {
/**
* DUPLICATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async duplicateTable (params) {
@@ -1397,7 +1547,7 @@ export class MySQLClient extends AntaresCore {
/**
* TRUNCATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async truncateTable (params) {
@@ -1408,7 +1558,7 @@ export class MySQLClient extends AntaresCore {
/**
* DROP TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropTable (params) {
@@ -1490,6 +1640,7 @@ export class MySQLClient extends AntaresCore {
details: false,
split: true,
comments: true,
+ autocommit: true,
...args
};
@@ -1504,8 +1655,24 @@ export class MySQLClient extends AntaresCore {
.filter(Boolean)
.map(q => q.trim())
: [sql];
+
+ let connection;
const isPool = typeof this._connection.getConnection === 'function';
- const connection = isPool ? await this._connection.getConnection() : this._connection;
+
+ 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 connection.query('SET SESSION autocommit=0');
+ this._connectionsToCommit.set(args.tabUid, connection);
+ }
+ }
+ else// autocommit ON
+ connection = isPool ? await this._connection.getConnection() : this._connection;
+
+ if (args.tabUid && isPool)
+ this._runningConnections.set(args.tabUid, connection.connection.connectionId);
if (args.schema)
await connection.query(`USE \`${args.schema}\``);
@@ -1567,7 +1734,10 @@ export class MySQLClient extends AntaresCore {
});
}
catch (err) {
- if (isPool) connection.release();
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
reject(err);
}
@@ -1576,7 +1746,10 @@ export class MySQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
- if (isPool) connection.release();
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
reject(err);
}
}
@@ -1591,7 +1764,10 @@ export class MySQLClient extends AntaresCore {
keys: keysArr
});
}).catch((err) => {
- if (isPool) connection.release();
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
reject(err);
});
});
@@ -1599,7 +1775,10 @@ export class MySQLClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration });
}
- if (isPool) connection.release();
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}
diff --git a/src/main/libs/clients/PostgreSQLClient.js b/src/main/libs/clients/PostgreSQLClient.js
index 7d9125e7..af9a4a56 100644
--- a/src/main/libs/clients/PostgreSQLClient.js
+++ b/src/main/libs/clients/PostgreSQLClient.js
@@ -9,6 +9,7 @@ function pgToString (value) {
return value.toString();
}
+types.setTypeParser(20, a => parseInt(a));// bigint string to number
types.setTypeParser(1082, pgToString); // date
types.setTypeParser(1083, pgToString); // time
types.setTypeParser(1114, pgToString); // timestamp
@@ -20,6 +21,8 @@ export class PostgreSQLClient extends AntaresCore {
super(args);
this._schema = null;
+ this._runningConnections = new Map();
+ this._connectionsToCommit = new Map();
this.types = {};
for (const key in types.builtins)
@@ -69,9 +72,11 @@ export class PostgreSQLClient extends AntaresCore {
}
/**
+ *
+ * @returns dbConfig
* @memberof PostgreSQLClient
*/
- async connect () {
+ async getDbConfig () {
const dbConfig = {
host: this._params.host,
port: this._params.port,
@@ -100,15 +105,43 @@ export class PostgreSQLClient extends AntaresCore {
}
}
- if (!this._poolSize) {
- const client = new Client(dbConfig);
- await client.connect();
- this._connection = client;
- }
- else {
- const pool = new Pool({ ...dbConfig, max: this._poolSize });
- this._connection = pool;
+ return dbConfig;
+ }
+
+ /**
+ * @memberof PostgreSQLClient
+ */
+ async connect () {
+ if (!this._poolSize)
+ this._connection = await this.getConnection();
+ else
+ this._connection = await this.getConnectionPool();
+ }
+
+ async getConnection () {
+ const dbConfig = await this.getDbConfig();
+ const client = new Client(dbConfig);
+ await client.connect();
+ const connection = client;
+
+ if (this._params.readonly)
+ await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
+
+ return connection;
+ }
+
+ async getConnectionPool () {
+ const dbConfig = await this.getDbConfig();
+ const pool = new Pool({ ...dbConfig, max: this._poolSize });
+ const connection = pool;
+
+ if (this._params.readonly) {
+ connection.on('connect', conn => {
+ conn.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
+ });
}
+
+ return connection;
}
/**
@@ -120,15 +153,23 @@ export class PostgreSQLClient extends AntaresCore {
}
/**
- * Executes an "USE" query
+ * Executes an 'SET search_path TO "${schema}"' query
*
* @param {String} schema
+ * @param {Object?} connection optional
* @memberof PostgreSQLClient
*/
- use (schema) {
+ use (schema, connection) {
this._schema = schema;
- if (schema)
- return this.raw(`SET search_path TO "${schema}"`);
+
+ if (schema) {
+ const sql = `SET search_path TO "${schema}"`;
+
+ if (connection === undefined)
+ return this.raw(sql);
+ else
+ return connection.query(sql);
+ }
}
/**
@@ -143,6 +184,7 @@ export class PostgreSQLClient extends AntaresCore {
const tablesArr = [];
const triggersArr = [];
+ let schemaSize = 0;
for (const db of databases) {
if (!schemas.has(db.database)) continue;
@@ -168,19 +210,20 @@ export class PostgreSQLClient extends AntaresCore {
}
let { rows: triggers } = await this.raw(`
- SELECT event_object_schema AS table_schema,
- event_object_table AS table_name,
- trigger_schema,
- trigger_name,
- string_agg(event_manipulation, ',') AS event,
- action_timing AS activation,
- action_condition AS condition,
- action_statement AS definition
- FROM information_schema.triggers
+ SELECT
+ pg_class.relname AS table_name,
+ pg_trigger.tgname AS trigger_name,
+ pg_namespace.nspname AS trigger_schema,
+ (pg_trigger.tgenabled != 'D')::bool AS enabled
+ FROM pg_trigger
+ JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid
+ JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ JOIN information_schema.triggers ON information_schema.triggers.trigger_schema = pg_namespace.nspname
+ AND information_schema.triggers.event_object_table = pg_class.relname
+ AND information_schema.triggers.trigger_name = pg_trigger.tgname
WHERE trigger_schema = '${db.database}'
- GROUP BY 1,2,3,4,6,7,8
- ORDER BY table_schema,
- table_name
+ GROUP BY 1, 2, 3, 4
+ ORDER BY table_name
`);
if (triggers.length) {
@@ -196,11 +239,14 @@ export class PostgreSQLClient extends AntaresCore {
if (schemas.has(db.database)) {
// TABLES
const remappedTables = tablesArr.filter(table => table.Db === db.database).map(table => {
+ const tableSize = +table.data_length + table.index_length;
+ schemaSize += tableSize;
+
return {
name: table.table_name,
type: table.table_type === 'VIEW' ? 'view' : 'table',
rows: table.reltuples,
- size: +table.data_length + +table.index_length,
+ size: tableSize,
collation: table.Collation,
comment: table.comment,
engine: ''
@@ -239,17 +285,16 @@ export class PostgreSQLClient extends AntaresCore {
return {
name: `${trigger.table_name}.${trigger.trigger_name}`,
orgName: trigger.trigger_name,
- timing: trigger.activation,
definer: '',
- definition: trigger.definition,
- event: trigger.event,
table: trigger.table_name,
- sqlMode: ''
+ sqlMode: '',
+ enabled: trigger.enabled
};
});
return {
name: db.database,
+ size: schemaSize,
tables: remappedTables,
functions: remappedFunctions,
procedures: remappedProcedures,
@@ -261,6 +306,7 @@ export class PostgreSQLClient extends AntaresCore {
else {
return {
name: db.database,
+ size: 0,
tables: [],
functions: [],
procedures: [],
@@ -302,6 +348,7 @@ export class PostgreSQLClient extends AntaresCore {
isArray,
schema: field.table_schema,
table: field.table_name,
+ numScale: field.numeric_scale,
numPrecision: field.numeric_precision,
datePrecision: field.datetime_precision,
charLength: field.character_maximum_length,
@@ -500,7 +547,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* CREATE SCHEMA
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async createSchema (params) {
@@ -510,7 +557,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* ALTER DATABASE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async alterSchema (params) {
@@ -520,7 +567,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* DROP DATABASE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof MySQLClient
*/
async dropSchema (params) {
@@ -552,7 +599,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* DROP VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async dropView (params) {
@@ -563,7 +610,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* ALTER VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async alterView (params) {
@@ -579,7 +626,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* CREATE VIEW
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async createView (params) {
@@ -597,19 +644,25 @@ export class PostgreSQLClient extends AntaresCore {
const [table, triggerName] = trigger.split('.');
const results = await this.raw(`
- SELECT event_object_schema AS table_schema,
- event_object_table AS table_name,
- trigger_schema,
- trigger_name,
- string_agg(event_manipulation, ',') AS event,
+ SELECT
+ information_schema.triggers.event_object_schema AS table_schema,
+ information_schema.triggers.event_object_table AS table_name,
+ information_schema.triggers.trigger_schema,
+ information_schema.triggers.trigger_name,
+ string_agg(event_manipulation, ',') AS EVENT,
action_timing AS activation,
action_condition AS condition,
- action_statement AS definition
- FROM information_schema.triggers
+ action_statement AS definition,
+ (pg_trigger.tgenabled != 'D')::bool AS enabled
+ FROM pg_trigger
+ JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid
+ JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
+ JOIN information_schema.triggers ON pg_namespace.nspname = information_schema.triggers.trigger_schema
+ AND pg_class.relname = information_schema.triggers.event_object_table
WHERE trigger_schema = '${schema}'
AND trigger_name = '${triggerName}'
AND event_object_table = '${table}'
- GROUP BY 1,2,3,4,6,7,8
+ GROUP BY 1,2,3,4,6,7,8,9
ORDER BY table_schema,
table_name
`);
@@ -619,7 +672,7 @@ export class PostgreSQLClient extends AntaresCore {
sql: row.definition,
name: row.trigger_name,
table: row.table_name,
- event: row.event.split(','),
+ event: [...new Set(row.event.split(','))],
activation: row.activation
};
})[0];
@@ -628,7 +681,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* DROP TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async dropTrigger (params) {
@@ -640,7 +693,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* ALTER TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async alterTrigger (params) {
@@ -662,7 +715,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* CREATE TRIGGER
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async createTrigger (params) {
@@ -671,6 +724,18 @@ export class PostgreSQLClient extends AntaresCore {
return await this.raw(sql, { split: false });
}
+ async enableTrigger ({ schema, trigger }) {
+ const [table, triggerName] = trigger.split('.');
+ const sql = `ALTER TABLE "${schema}"."${table}" ENABLE TRIGGER "${triggerName}"`;
+ return await this.raw(sql, { split: false });
+ }
+
+ async disableTrigger ({ schema, trigger }) {
+ const [table, triggerName] = trigger.split('.');
+ const sql = `ALTER TABLE "${schema}"."${table}" DISABLE TRIGGER "${triggerName}"`;
+ return await this.raw(sql, { split: false });
+ }
+
/**
* SHOW CREATE PROCEDURE
*
@@ -1055,14 +1120,64 @@ export class PostgreSQLClient extends AntaresCore {
});
}
+ /**
+ *
+ * @param {number} id
+ * @returns {Promise}
+ */
async killProcess (id) {
return await this.raw(`SELECT pg_terminate_backend(${id})`);
}
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async killTabQuery (tabUid) {
+ const id = this._runningConnections.get(tabUid);
+ if (id)
+ return await this.raw(`SELECT pg_cancel_backend(${id})`);
+ }
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async commitTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ await connection.query('COMMIT');
+ return this.destroyConnectionToCommit(tabUid);
+ }
+ }
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async rollbackTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ await connection.query('ROLLBACK');
+ return this.destroyConnectionToCommit(tabUid);
+ }
+ }
+
+ destroyConnectionToCommit (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ connection.end();
+ this._connectionsToCommit.delete(tabUid);
+ }
+ }
+
/**
* CREATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async createTable (params) {
@@ -1086,7 +1201,7 @@ export class PostgreSQLClient extends AntaresCore {
const length = typeInfo.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`"${field.name}"
- ${field.type.toUpperCase()}${length ? `(${length})` : ''}
+ ${field.type.toUpperCase()}${length ? `(${length}${field.numScale !== null ? `,${field.numScale}` : ''})` : ''}
${field.unsigned ? 'UNSIGNED' : ''}
${field.zerofill ? 'ZEROFILL' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'}
@@ -1120,7 +1235,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* ALTER TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async alterTable (params) {
@@ -1150,7 +1265,7 @@ export class PostgreSQLClient extends AntaresCore {
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN "${addition.name}"
- ${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
+ ${addition.type.toUpperCase()}${length ? `(${length}${addition.numScale !== null ? `,${addition.numScale}` : ''})` : ''}${addition.isArray ? '[]' : ''}
${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''}
${addition.nullable ? 'NULL' : 'NOT NULL'}
@@ -1196,7 +1311,7 @@ export class PostgreSQLClient extends AntaresCore {
localType = change.type.toLowerCase();
}
- alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
+ alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length}${change.numScale ? `,${change.numScale}` : ''})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
@@ -1266,7 +1381,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* DUPLICATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async duplicateTable (params) {
@@ -1277,7 +1392,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* TRUNCATE TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async truncateTable (params) {
@@ -1288,7 +1403,7 @@ export class PostgreSQLClient extends AntaresCore {
/**
* DROP TABLE
*
- * @returns {Array.} parameters
+ * @returns {Promise}
* @memberof PostgreSQLClient
*/
async dropTable (params) {
@@ -1363,17 +1478,17 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async raw (sql, args) {
+ if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
+
args = {
nest: false,
details: false,
split: true,
comments: true,
+ autocommit: true,
...args
};
- if (args.schema && args.schema !== 'public')
- await this.use(args.schema);
-
if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
@@ -1385,7 +1500,26 @@ export class PostgreSQLClient extends AntaresCore {
.map(q => q.trim())
: [sql];
- if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
+ let connection;
+ const isPool = this._connection instanceof Pool;
+
+ 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 connection.query('START TRANSACTION');
+ this._connectionsToCommit.set(args.tabUid, connection);
+ }
+ }
+ else// autocommit ON
+ connection = isPool ? await this._connection.connect() : this._connection;
+
+ if (args.tabUid && isPool)
+ this._runningConnections.set(args.tabUid, connection.processID);
+
+ if (args.schema && args.schema !== 'public')
+ await this.use(args.schema, connection);
for (const query of queries) {
if (!query) continue;
@@ -1395,15 +1529,12 @@ export class PostgreSQLClient extends AntaresCore {
let keysArr = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
- this._connection.query({
- rowMode: args.nest ? 'array' : null,
- text: query
- }, async (err, res) => {
- timeStop = new Date();
+ (async () => {
+ try {
+ const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });
+
+ timeStop = new Date();
- if (err)
- reject(err);
- else {
let ast;
try {
@@ -1492,6 +1623,10 @@ export class PostgreSQLClient extends AntaresCore {
});
}
catch (err) {
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
reject(err);
}
@@ -1500,6 +1635,10 @@ export class PostgreSQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response;
}
catch (err) {
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
reject(err);
}
}
@@ -1514,12 +1653,24 @@ export class PostgreSQLClient extends AntaresCore {
keys: keysArr
});
}
- });
+ catch (err) {
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
+ reject(err);
+ }
+ })();
});
resultsArr.push({ rows, report, fields, keys, duration });
}
+ if (isPool && args.autocommit) {
+ connection.release();
+ this._runningConnections.delete(args.tabUid);
+ }
+
return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
}
}
diff --git a/src/main/libs/clients/SQLiteClient.js b/src/main/libs/clients/SQLiteClient.js
new file mode 100644
index 00000000..1323434e
--- /dev/null
+++ b/src/main/libs/clients/SQLiteClient.js
@@ -0,0 +1,859 @@
+'use strict';
+import sqlite from 'better-sqlite3';
+import { AntaresCore } from '../AntaresCore';
+import dataTypes from 'common/data-types/sqlite';
+import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
+
+export class SQLiteClient extends AntaresCore {
+ constructor (args) {
+ super(args);
+
+ this._schema = null;
+ this._connectionsToCommit = new Map();
+ }
+
+ _getTypeInfo (type) {
+ return dataTypes
+ .reduce((acc, group) => [...acc, ...group.types], [])
+ .filter(_type => _type.name === type.toUpperCase())[0];
+ }
+
+ /**
+ * @memberof SQLiteClient
+ */
+ async connect () {
+ this._connection = this.getConnection();
+ }
+
+ getConnection () {
+ return sqlite(this._params.databasePath, {
+ fileMustExist: true,
+ readonly: this._params.readonly
+ });
+ }
+
+ /**
+ * @memberof SQLiteClient
+ */
+ destroy () {}
+
+ /**
+ * Executes an USE query
+ *
+ * @memberof SQLiteClient
+ */
+ use () {}
+
+ /**
+ * @param {Array} schemas list
+ * @returns {Array.} databases scructure
+ * @memberof SQLiteClient
+ */
+ async getStructure (schemas) {
+ const { rows: databases } = await this.raw('SELECT * FROM pragma_database_list');
+
+ const filteredDatabases = databases;
+
+ const tablesArr = [];
+ const triggersArr = [];
+ 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_%'
+ 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: []
+ };
+ }
+ });
+ }
+
+ /**
+ * @param {Object} params
+ * @param {String} params.schema
+ * @param {String} params.table
+ * @returns {Object} table scructure
+ * @memberof SQLiteClient
+ */
+ async getTableColumns ({ schema, table }) {
+ const { rows: fields } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}')`);
+
+ return fields.map(field => {
+ const [type, length] = field.type.includes('(')
+ ? field.type.replace(')', '').split('(').map(el => {
+ if (!isNaN(el)) el = +el;
+ return el;
+ })
+ : [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: field.cid + 1,
+ default: field.dflt_value,
+ charset: null,
+ collation: null,
+ autoIncrement: false,
+ onUpdate: null,
+ comment: ''
+ };
+ });
+ }
+
+ /**
+ * @param {Object} params
+ * @param {String} params.schema
+ * @param {String} params.table
+ * @returns {Object} table row count
+ * @memberof SQLiteClient
+ */
+ async getTableApproximateCount ({ schema, table }) {
+ const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`);
+
+ return rows.length ? rows[0].count : 0;
+ }
+
+ /**
+ * @param {Object} params
+ * @param {String} params.schema
+ * @param {String} params.table
+ * @returns {Object} table options
+ * @memberof SQLiteClient
+ */
+ async getTableOptions ({ schema, table }) {
+ return { name: table };
+ }
+
+ /**
+ * @param {Object} params
+ * @param {String} params.schema
+ * @param {String} params.table
+ * @returns {Object} table indexes
+ * @memberof SQLiteClient
+ */
+ async getTableIndexes ({ schema, table }) {
+ 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,
+ type: 'PRIMARY',
+ cardinality: null,
+ 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,
+ type: index.unique === 1 ? 'UNIQUE' : 'INDEX',
+ cardinality: null,
+ comment: '',
+ indexComment: ''
+ });
+ }
+ }
+
+ return remappedIndexes;
+ }
+
+ /**
+ * @param {Object} params
+ * @param {String} params.schema
+ * @param {String} params.table
+ * @returns {Object} table key usage
+ * @memberof SQLiteClient
+ */
+ async getKeyUsage ({ schema, table }) {
+ 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 () {}
+
+ /**
+ * SHOW CREATE VIEW
+ *
+ * @returns {Array.} view informations
+ * @memberof SQLiteClient
+ */
+ async getViewInformations ({ schema, view }) {
+ 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];
+ }
+
+ /**
+ * DROP VIEW
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async dropView (params) {
+ const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
+ return await this.raw(sql);
+ }
+
+ /**
+ * ALTER VIEW
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async alterView (params) {
+ const { view } = params;
+ try {
+ await this.dropView({ schema: view.schema, view: view.oldName });
+ await this.createView(view);
+ }
+ catch (err) {
+ return Promise.reject(err);
+ }
+ }
+
+ /**
+ * CREATE VIEW
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async createView (params) {
+ const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`;
+ return await this.raw(sql);
+ }
+
+ /**
+ * SHOW CREATE TRIGGER
+ *
+ * @returns {Array.} view informations
+ * @memberof SQLiteClient
+ */
+ async getTriggerInformations ({ schema, trigger }) {
+ 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];
+ }
+
+ /**
+ * DROP TRIGGER
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async dropTrigger (params) {
+ const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
+ return await this.raw(sql);
+ }
+
+ /**
+ * ALTER TRIGGER
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async alterTrigger (params) {
+ const { trigger } = params;
+ 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);
+ }
+ }
+
+ /**
+ * CREATE TRIGGER
+ *
+ * @returns {Array.} parameters
+ * @memberof SQLiteClient
+ */
+ async createTrigger (params) {
+ 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 });
+ }
+
+ /**
+ * SHOW COLLATION
+ *
+ * @returns {Array.} collations list
+ * @memberof SQLiteClient
+ */
+ async getCollations () {
+ return [];
+ }
+
+ /**
+ * SHOW VARIABLES
+ *
+ * @returns {Array.} variables list
+ * @memberof SQLiteClient
+ */
+ async getVariables () {
+ return [];
+ }
+
+ /**
+ * SHOW ENGINES
+ *
+ * @returns {Array.} engines list
+ * @memberof SQLiteClient
+ */
+ async getEngines () {
+ return {
+ name: 'SQLite',
+ support: 'YES',
+ comment: '',
+ isDefault: true
+ };
+ }
+
+ /**
+ * SHOW VARIABLES LIKE '%vers%'
+ *
+ * @returns {Array.} version parameters
+ * @memberof SQLiteClient
+ */
+ 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 () {}
+
+ async killProcess () {}
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async commitTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ connection.prepare('COMMIT').run();
+ return this.destroyConnectionToCommit(tabUid);
+ }
+ }
+
+ /**
+ *
+ * @param {string} tabUid
+ * @returns {Promise}
+ */
+ async rollbackTab (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ connection.prepare('ROLLBACK').run();
+ return this.destroyConnectionToCommit(tabUid);
+ }
+ }
+
+ destroyConnectionToCommit (tabUid) {
+ const connection = this._connectionsToCommit.get(tabUid);
+ if (connection) {
+ connection.close();
+ this._connectionsToCommit.delete(tabUid);
+ }
+ }
+
+ /**
+ * CREATE TABLE
+ *
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async createTable (params) {
+ const {
+ schema,
+ fields,
+ foreigns,
+ indexes,
+ options
+ } = params;
+ const newColumns = [];
+ const newIndexes = [];
+ const manageIndexes = [];
+ const newForeigns = [];
+
+ 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 !== true ? `(${length})` : ''}
+ ${field.unsigned ? 'UNSIGNED' : ''}
+ ${field.nullable ? 'NULL' : 'NOT NULL'}
+ ${field.autoIncrement ? 'AUTO_INCREMENT' : ''}
+ ${field.default ? `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);
+ }
+
+ /**
+ * ALTER TABLE
+ *
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async alterTable (params) {
+ try {
+ await this.raw('BEGIN TRANSACTION');
+ await this.raw('PRAGMA foreign_keys = 0');
+
+ const tmpName = `Antares_${params.table}_tmp`;
+ await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${params.table}"`);
+ await this.dropTable(params);
+
+ const createTableParams = {
+ schema: params.schema,
+ fields: params.tableStructure.fields,
+ foreigns: params.tableStructure.foreigns,
+ indexes: params.tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')),
+ options: { name: params.tableStructure.name }
+ };
+ await this.createTable(createTableParams);
+ const insertFields = createTableParams.fields
+ .filter(field => {
+ return (
+ params.additions.every(add => add.name !== field.name) &&
+ params.deletions.every(del => del.name !== field.name)
+ );
+ })
+ .reduce((acc, curr) => {
+ acc.push(`"${curr.name}"`);
+ return acc;
+ }, []);
+
+ const selectFields = insertFields.map(field => {
+ const renamedField = params.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: params.schema, table: tmpName });
+ await this.raw('PRAGMA foreign_keys = 1');
+ await this.raw('COMMIT');
+ }
+ catch (err) {
+ await this.raw('ROLLBACK');
+ return Promise.reject(err);
+ }
+ }
+
+ /**
+ * DUPLICATE TABLE
+ *
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async duplicateTable (params) { // 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);
+ }
+
+ /**
+ * TRUNCATE TABLE
+ *
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async truncateTable (params) {
+ const sql = `DELETE FROM "${params.schema}"."${params.table}"`;
+ return await this.raw(sql);
+ }
+
+ /**
+ * DROP TABLE
+ *
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async dropTable (params) {
+ const sql = `DROP TABLE "${params.schema}"."${params.table}"`;
+ return await this.raw(sql);
+ }
+
+ /**
+ * @returns {String} SQL string
+ * @memberof SQLiteClient
+ */
+ 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.length ? `LIMIT ${this._query.limit.join(', ')} ` : '';
+
+ // OFFSET
+ const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : '';
+
+ return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
+ }
+
+ /**
+ * @param {string} sql raw SQL query
+ * @param {object} args
+ * @param {boolean} args.nest
+ * @param {boolean} args.details
+ * @param {boolean} args.split
+ * @returns {Promise}
+ * @memberof SQLiteClient
+ */
+ async raw (sql, args) {
+ if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
+
+ 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 = [];
+ let paramsArr = [];
+ const queries = args.split
+ ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm)
+ .filter(Boolean)
+ .map(q => q.trim())
+ : [sql];
+
+ let connection;
+
+ if (!args.autocommit && args.tabUid) { // autocommit OFF
+ if (this._connectionsToCommit.has(args.tabUid))
+ connection = this._connectionsToCommit.get(args.tabUid);
+ else {
+ connection = this.getConnection();
+ connection.prepare('BEGIN TRANSACTION').run();
+ 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 = [];
+
+ const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
+ (async () => {
+ let queryResult;
+ let affectedRows;
+ let fields;
+ const detectedTypes = {};
+
+ try {
+ const stmt = connection.prepare(query);
+
+ if (stmt.reader) {
+ queryResult = stmt.all();
+ fields = stmt.columns();
+
+ if (queryResult.length) {
+ fields.forEach(field => {
+ detectedTypes[field.name] = typeof queryResult[0][field.name];
+ });
+ }
+ }
+ else {
+ const info = queryResult = stmt.run();
+ affectedRows = info.changes;
+ }
+ }
+ catch (err) {
+ reject(err);
+ }
+
+ timeStop = new Date();
+
+ let remappedFields = fields
+ ? fields.map(field => {
+ let [parsedType, length] = field.type?.includes('(')
+ ? field.type.replace(')', '').split('(').map(el => {
+ if (!isNaN(el))
+ el = +el;
+ else
+ el = el.trim();
+ return el;
+ })
+ : [field.type, null];
+
+ if ([...TIME, ...DATETIME].includes(parsedType)) {
+ const firstNotNull = queryResult.find(res => res[field.name] !== null);
+ if (firstNotNull && firstNotNull[field.name].includes('.'))
+ length = firstNotNull[field.name].split('.').pop().length;
+ }
+
+ return {
+ name: field.name,
+ alias: field.name,
+ orgName: field.column,
+ schema: field.database,
+ table: field.table,
+ tableAlias: field.table,
+ orgTable: field.table,
+ type: field.type !== null ? parsedType : detectedTypes[field.name],
+ length
+ };
+ }).filter(Boolean)
+ : [];
+
+ if (args.details) {
+ paramsArr = remappedFields.map(field => {
+ return {
+ table: field.table,
+ schema: field.schema
+ };
+ }).filter((val, i, arr) => arr.findIndex(el => el.schema === val.schema && el.table === val.table) === i);
+
+ for (const paramObj of paramsArr) {
+ if (!paramObj.table || !paramObj.schema) continue;
+
+ try {
+ const indexes = await this.getTableIndexes(paramObj);
+
+ remappedFields = remappedFields.map(field => {
+ // const detailedField = columns.find(f => f.name === field.name);
+ const fieldIndex = indexes.find(i => i.column === field.name);
+ if (field.table === paramObj.table && field.schema === paramObj.schema) {
+ // if (detailedField) {
+ // const length = detailedField.numPrecision || detailedField.charLength || detailedField.datePrecision || null;
+ // field = { ...field, ...detailedField, length };
+ // }
+
+ if (fieldIndex) {
+ const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul';
+ field = { ...field, key };
+ };
+ }
+
+ return field;
+ });
+ }
+ catch (err) {
+ reject(err);
+ }
+ }
+ }
+
+ resolve({
+ duration: timeStop - timeStart,
+ rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false,
+ report: affectedRows !== undefined ? { affectedRows } : null,
+ fields: remappedFields,
+ keys: keysArr
+ });
+ })();
+ });
+
+ resultsArr.push({ rows, report, fields, keys, duration });
+ }
+
+ return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
+ }
+}
diff --git a/src/main/main.js b/src/main/main.js
index 570f5b54..87c8ca67 100644
--- a/src/main/main.js
+++ b/src/main/main.js
@@ -3,6 +3,7 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path';
import Store from 'electron-store';
+import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers';
@@ -18,15 +19,18 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow;
+let mainWindowState;
async function createMainWindow () {
const icon = require('../renderer/images/logo-32.png');
const window = new BrowserWindow({
- width: 1024,
- height: 800,
+ width: mainWindowState.width,
+ height: mainWindowState.height,
+ x: mainWindowState.x,
+ y: mainWindowState.y,
minWidth: 900,
minHeight: 550,
- title: 'Antares',
+ title: 'Antares SQL',
autoHideMenuBar: true,
icon: nativeImage.createFromDataURL(icon.default),
webPreferences: {
@@ -41,6 +45,9 @@ async function createMainWindow () {
backgroundColor: '#1d1d1d'
});
+ mainWindowState.manage(window);
+ window.on('moved', saveWindowState);
+
remoteMain.enable(window.webContents);
try {
@@ -70,16 +77,10 @@ async function createMainWindow () {
}
window.on('closed', () => {
+ window.removeListener('moved', saveWindowState);
mainWindow = null;
});
- window.webContents.on('devtools-opened', () => {
- window.focus();
- setImmediate(() => {
- window.focus();
- });
- });
-
return window;
}
@@ -104,17 +105,22 @@ else {
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
+ mainWindowState = windowStateKeeper({
+ defaultWidth: 1024,
+ defaultHeight: 800
+ });
+
mainWindow = await createMainWindow();
createAppMenu();
- if (isDevelopment)
- mainWindow.webContents.openDevTools();
+ // if (isDevelopment)
+ // mainWindow.webContents.openDevTools();
- process.on('uncaughtException', (error) => {
+ process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error);
});
- process.on('unhandledRejection', (error) => {
+ process.on('unhandledRejection', error => {
mainWindow.webContents.send('unhandled-exception', error);
});
});
@@ -160,3 +166,7 @@ function createAppMenu () {
Menu.setApplicationMenu(menu);
}
+
+function saveWindowState () {
+ mainWindowState.saveState(mainWindow);
+}
diff --git a/src/renderer/components/BaseMap.vue b/src/renderer/components/BaseMap.vue
new file mode 100644
index 00000000..c1cbb836
--- /dev/null
+++ b/src/renderer/components/BaseMap.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
diff --git a/src/renderer/components/FakerSelect.vue b/src/renderer/components/FakerSelect.vue
index 53e7a6b9..fc1627b0 100644
--- a/src/renderer/components/FakerSelect.vue
+++ b/src/renderer/components/FakerSelect.vue
@@ -89,7 +89,7 @@
:type="inputProps().type"
:disabled="!isChecked"
>
-
+
-
+
{{ $t('word.parameters') }}: {{ localRoutine.name }}
-
+
+
diff --git a/src/renderer/components/ModalDiscardChanges.vue b/src/renderer/components/ModalDiscardChanges.vue
index 2b66ba67..de9f8461 100644
--- a/src/renderer/components/ModalDiscardChanges.vue
+++ b/src/renderer/components/ModalDiscardChanges.vue
@@ -5,16 +5,16 @@
@confirm="$emit('confirm')"
@hide="$emit('close')"
>
-
+
{{ $t('message.unsavedChanges') }}
-
+
{{ $t('message.discardUnsavedChanges') }}
-
+
diff --git a/src/renderer/components/ModalExportSchema.vue b/src/renderer/components/ModalExportSchema.vue
index 627efae9..ac96a12e 100644
--- a/src/renderer/components/ModalExportSchema.vue
+++ b/src/renderer/components/ModalExportSchema.vue
@@ -170,7 +170,7 @@
KiB
- {{ $t('word.rows') }}
+ {{ $tc('word.row', 2) }}
diff --git a/src/renderer/components/ModalFakerRows.vue b/src/renderer/components/ModalFakerRows.vue
index e12dbaff..b7785537 100644
--- a/src/renderer/components/ModalFakerRows.vue
+++ b/src/renderer/components/ModalFakerRows.vue
@@ -6,7 +6,7 @@
- {{ $t('message.tableFiller') }}
+ {{ $tc('message.insertRow', 2) }}
@@ -41,7 +41,7 @@
@@ -264,7 +264,7 @@ export default {
else if (BIT.includes(field.type))
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) {
- if (field.default && ['current_timestamp', 'now()'].includes(field.default.toLowerCase())) {
+ if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
@@ -281,7 +281,7 @@ export default {
rowObj[field.name] = { value: fieldDefault };
- if (field.autoIncrement)// Disable by default auto increment fields
+ if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
}
diff --git a/src/renderer/components/ModalHistory.vue b/src/renderer/components/ModalHistory.vue
index 4dd57ea5..6fdd83c4 100644
--- a/src/renderer/components/ModalHistory.vue
+++ b/src/renderer/components/ModalHistory.vue
@@ -253,6 +253,7 @@ export default {
font-size: 100%;
// color: $primary-color;
opacity: 0.8;
+ font-weight: 600;
}
.tile-subtitle {
diff --git a/src/renderer/components/ModalProcessesListRow.vue b/src/renderer/components/ModalProcessesListRow.vue
index b5b88e63..3c40f002 100644
--- a/src/renderer/components/ModalProcessesListRow.vue
+++ b/src/renderer/components/ModalProcessesListRow.vue
@@ -22,12 +22,12 @@
:hide-footer="true"
@hide="hideInfoModal"
>
-
+
{{ $t('message.processInfo') }}
-
+
diff --git a/src/renderer/components/ModalSettings.vue b/src/renderer/components/ModalSettings.vue
index 7ea9feac..8c1f5f39 100644
--- a/src/renderer/components/ModalSettings.vue
+++ b/src/renderer/components/ModalSettings.vue
@@ -396,7 +396,7 @@ export default {
return locales;
},
hasUpdates () {
- return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
+ return ['available', 'downloading', 'downloaded', 'link'].includes(this.updateStatus);
},
workspace () {
return this.getWorkspace(this.selectedWorkspace);
diff --git a/src/renderer/components/ModalSettingsChangelog.vue b/src/renderer/components/ModalSettingsChangelog.vue
index c524b48e..bfbf9527 100644
--- a/src/renderer/components/ModalSettingsChangelog.vue
+++ b/src/renderer/components/ModalSettingsChangelog.vue
@@ -16,7 +16,7 @@
diff --git a/src/renderer/components/WorkspaceTabQueryTable.vue b/src/renderer/components/WorkspaceTabQueryTable.vue
index 1c13d49e..fbd33279 100644
--- a/src/renderer/components/WorkspaceTabQueryTable.vue
+++ b/src/renderer/components/WorkspaceTabQueryTable.vue
@@ -5,7 +5,7 @@
tabindex="0"
:style="{'height': resultsSize+'px'}"
@keyup.46="showDeleteConfirmModal"
- @keydown.ctrl.65="selectAllRows"
+ @keydown.ctrl.65="selectAllRows($event)"
@keydown.esc="deselectRows"
>
+
@@ -62,7 +63,7 @@
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
ref="resultTable"
:items="sortedResults"
- :item-height="22"
+ :item-height="rowHeight"
class="tbody"
:visible-height="resultsSize"
:scroll-element="scrollElement"
@@ -70,13 +71,14 @@
@@ -89,17 +91,17 @@
@confirm="deleteSelected"
@hide="hideDeleteConfirmModal"
>
-
+
{{ $tc('message.deleteRows', selectedRows.length) }}
-
+
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
-
+
@@ -142,7 +144,8 @@ export default {
currentSort: '',
currentSortDir: 'asc',
resultsetIndex: 0,
- scrollElement: null
+ scrollElement: null,
+ rowHeight: 23
};
},
computed: {
@@ -196,7 +199,7 @@ export default {
if (this.sortedResults.length) {
const fieldsObj = {};
for (const key in this.sortedResults[0]) {
- if (key === '_id') continue;
+ if (key === '_antares_id') continue;
const fieldObj = this.fields.find(field => {
let fieldNames = [
@@ -242,6 +245,11 @@ export default {
if (this.$refs.tableWrapper)
this.scrollElement = this.$refs.tableWrapper;
+
+ document.querySelectorAll('.column-resizable').forEach(element => {
+ if (element.clientWidth !== 0)
+ element.style.width = element.clientWidth + 'px';
+ });
},
mounted () {
window.addEventListener('resize', this.resizeResults);
@@ -272,6 +280,7 @@ export default {
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
else if (TEXT.includes(field.type)) return field.charLength;
+ else if (field.numScale) return `${field.numPrecision}, ${field.numScale}`;
return field.length;
},
keyName (key) {
@@ -310,7 +319,7 @@ export default {
setLocalResults () {
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
- return { ...item, _id: uidGen() };
+ return { ...item, _antares_id: uidGen() };
})
: [];
},
@@ -330,7 +339,7 @@ export default {
this.resizeResults();
},
updateField (payload, row) {
- const orgRow = this.localResults.find(lr => lr._id === row._id);
+ const orgRow = this.localResults.find(lr => lr._antares_id === row._antares_id);
Object.keys(orgRow).forEach(key => { // remap the row
if (orgRow[key] instanceof Date && moment(orgRow[key]).isValid()) { // if datetime
@@ -367,8 +376,8 @@ export default {
},
deleteSelected () {
this.closeContext();
- const rows = JSON.parse(JSON.stringify(this.localResults)).filter(row => this.selectedRows.includes(row._id)).map(row => {
- delete row._id;
+ const rows = JSON.parse(JSON.stringify(this.localResults)).filter(row => this.selectedRows.includes(row._antares_id)).map(row => {
+ delete row._antares_id;
return row;
});
@@ -381,7 +390,7 @@ export default {
this.$emit('delete-selected', params);
},
setNull () {
- const row = this.localResults.find(row => this.selectedRows.includes(row._id));
+ const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
const params = {
primary: this.primaryField.name,
@@ -396,19 +405,22 @@ export default {
this.$emit('update-field', params);
},
copyCell () {
- const row = this.localResults.find(row => this.selectedRows.includes(row._id));
+ const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
const cellName = Object.keys(row).find(prop => [
this.selectedCell.field,
+ this.selectedCell.orgField,
`${this.fields[0].table}.${this.selectedCell.field}`,
`${this.fields[0].tableAlias}.${this.selectedCell.field}`
].includes(prop));
- const valueToCopy = row[cellName];
+ let valueToCopy = row[cellName];
+ if (typeof valueToCopy === 'object')
+ valueToCopy = JSON.stringify(valueToCopy);
navigator.clipboard.writeText(valueToCopy);
},
copyRow () {
- const row = this.localResults.find(row => this.selectedRows.includes(row._id));
+ const row = this.localResults.find(row => this.selectedRows.includes(row._antares_id));
const rowToCopy = JSON.parse(JSON.stringify(row));
- delete rowToCopy._id;
+ delete rowToCopy._antares_id;
navigator.clipboard.writeText(JSON.stringify(rowToCopy));
},
applyUpdate (params) {
@@ -435,24 +447,26 @@ export default {
this.selectedRows.push(row);
else {
const lastID = this.selectedRows.slice(-1)[0];
- const lastIndex = this.sortedResults.findIndex(el => el._id === lastID);
- const clickedIndex = this.sortedResults.findIndex(el => el._id === row);
+ const lastIndex = this.sortedResults.findIndex(el => el._antares_id === lastID);
+ const clickedIndex = this.sortedResults.findIndex(el => el._antares_id === row);
if (lastIndex > clickedIndex) {
for (let i = clickedIndex; i < lastIndex; i++)
- this.selectedRows.push(this.sortedResults[i]._id);
+ this.selectedRows.push(this.sortedResults[i]._antares_id);
}
else if (lastIndex < clickedIndex) {
for (let i = clickedIndex; i > lastIndex; i--)
- this.selectedRows.push(this.sortedResults[i]._id);
+ this.selectedRows.push(this.sortedResults[i]._antares_id);
}
}
}
else
this.selectedRows = [row];
},
- selectAllRows () {
+ selectAllRows (e) {
+ if (e.target.classList.contains('editable-field')) return;
+
this.selectedRows = this.localResults.reduce((acc, curr) => {
- acc.push(curr._id);
+ acc.push(curr._antares_id);
return acc;
}, []);
},
@@ -501,7 +515,7 @@ export default {
if (!this.sortedResults) return;
const rows = JSON.parse(JSON.stringify(this.sortedResults)).map(row => {
- delete row._id;
+ delete row._antares_id;
return row;
});
diff --git a/src/renderer/components/WorkspaceTabQueryTableContext.vue b/src/renderer/components/WorkspaceTabQueryTableContext.vue
index ba7e3f11..f8d20a56 100644
--- a/src/renderer/components/WorkspaceTabQueryTableContext.vue
+++ b/src/renderer/components/WorkspaceTabQueryTableContext.vue
@@ -61,8 +61,6 @@ export default {
selectedRows: Array,
selectedCell: Object
},
- computed: {
- },
methods: {
showConfirmModal () {
this.$emit('show-delete-modal');
diff --git a/src/renderer/components/WorkspaceTabQueryTableRow.vue b/src/renderer/components/WorkspaceTabQueryTableRow.vue
index 462a7edc..6fe51f13 100644
--- a/src/renderer/components/WorkspaceTabQueryTableRow.vue
+++ b/src/renderer/components/WorkspaceTabQueryTableRow.vue
@@ -1,14 +1,18 @@
-
+
-
+
-
+
{{ $t('word.edit') }} "{{ editingField }}"
-
+
-
- TEXT
-
-
- HTML
-
-
- XML
-
-
- JSON
-
-
- SVG
-
-
- YAML
+
+ {{ language.name }}
@@ -128,7 +121,22 @@
-
+
+
+
+
+
+ "{{ editingField }}"
+
+
+
+
+
-
+
{{ $t('word.edit') }} "{{ editingField }}"
-
+