1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

Compare commits

...

27 Commits

Author SHA1 Message Date
76dc66c14d chore(release): 0.3.10 2021-11-24 17:26:42 +01:00
cc99491fe4 fix(UI): notifications timeout anomalies 2021-11-24 16:59:07 +01:00
fe8435531e Merge pull request #143 from Fabio286/feat/read-only-mode-on-connections
feat: read-only mode
2021-11-24 14:41:31 +01:00
5d48fe08c7 feat(PostgreSQL): read-only mode 2021-11-24 14:24:52 +01:00
4437d44486 feat(MySQL): read-only mode 2021-11-24 13:04:14 +01:00
9fe3680bbb perf: update italian traslation 2021-11-24 09:52:10 +01:00
da1947e4ef fix(UI): hide tools menu if no tools available 2021-11-24 09:19:42 +01:00
37a848df9d Merge pull request #142 from Fabio286/dependabot/npm_and_yarn/electron-16.0.1
build(deps-dev): bump electron from 15.3.2 to 16.0.1
2021-11-23 17:38:21 +01:00
dependabot[bot]
9816965e18 build(deps-dev): bump electron from 15.3.2 to 16.0.1
Bumps [electron](https://github.com/electron/electron) from 15.3.2 to 16.0.1.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v15.3.2...v16.0.1)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 19:05:55 +00:00
98165cacaa fix(SQLite): hide schema creation 2021-11-19 16:50:15 +01:00
7ad3096b4e chore: update README.md 2021-11-19 16:14:11 +01:00
7d345cf795 Merge pull request #141 from Fabio286/feat/sqlite-implementation
feat:sqlite implementation
2021-11-19 16:11:00 +01:00
f40e9c592e feat(SQLite): triggers management 2021-11-19 15:36:07 +01:00
7671c585f5 feat(SQLite): views management 2021-11-19 13:13:35 +01:00
93b4a7063b perf(SQLite): improvements in field length detection 2021-11-18 19:43:08 +01:00
3efeb45c46 feat(SQLite): tables management 2021-11-18 11:36:46 +01:00
3fc227d2de feat(SQLite): readonly mode 2021-11-16 13:21:33 +01:00
604b371920 feat(SQLite): cell update in data tabs 2021-11-16 12:56:03 +01:00
fd321beece feat(SQLite): keys support 2021-11-16 12:27:51 +01:00
9fe4e6b9e3 Merge pull request #140 from Fabio286/all-contributors/add-kilianstallz
docs: add kilianstallz as a contributor for code
2021-11-15 22:38:24 +01:00
allcontributors[bot]
57bf90481b docs: update .all-contributorsrc [skip ci] 2021-11-15 21:37:49 +00:00
allcontributors[bot]
6062a32c1c docs: update README.md [skip ci] 2021-11-15 21:37:48 +00:00
94c899eb82 perf(SQLite): improvements in data visualization 2021-11-15 18:09:34 +01:00
44a4ca75bd Merge pull request #139 from kilianstallz/kilianstallz-patch-table-rows
Hotfix quote style on delete-table-rows handler
2021-11-15 17:01:10 +01:00
Kilian Stallinger
c8e1605b08 Hotfix quote style on delete-table-rows handler
Use single quote as intended for ID in delete query.
2021-11-15 16:21:09 +01:00
f2fcc98839 feat(SQLite): table data visualization 2021-11-13 23:00:53 +01:00
c54438d6d3 feat(SQLite): connection add/edit masks 2021-11-13 11:34:30 +01:00
37 changed files with 1333 additions and 130 deletions

View File

@@ -1,5 +1,5 @@
{ {
"projectName": "Antares", "projectName": "antares",
"projectOwner": "Fabio286", "projectOwner": "Fabio286",
"repoType": "github", "repoType": "github",
"repoHost": "https://github.com", "repoHost": "https://github.com",
@@ -111,7 +111,17 @@
"contributions": [ "contributions": [
"platform" "platform"
] ]
},
{
"login": "kilianstallz",
"name": "Kilian Stallinger",
"avatar_url": "https://avatars.githubusercontent.com/u/5290318?v=4",
"profile": "https://kilianstallinger.com",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7 "contributorsPerLine": 7,
"skipCi": true
} }

View File

@@ -3,7 +3,8 @@
"UI", "UI",
"core", "core",
"MySQL", "MySQL",
"PostgreSQL" "PostgreSQL",
"SQLite"
], ],
"svg.preview.background": "transparent" "svg.preview.background": "transparent"
} }

View File

@@ -2,6 +2,36 @@
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. 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.3.10](https://github.com/Fabio286/antares/compare/v0.3.9...v0.3.10) (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) ### [0.3.9](https://github.com/Fabio286/antares/compare/v0.3.8...v0.3.9) (2021-11-14)

View File

@@ -12,7 +12,7 @@
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers. Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
Our target is to support as many databases as possible, and all major operating systems, including the ARM versions. Our target is to support as many databases as possible, and all major operating systems, including the ARM versions.
**At the moment this application is in development state, many features will come in future updates**, and supports only MySQL/MariaDB 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. 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. We are actively working on it, hoping to provide new cool features, improvements and fixes as soon as possible.
@@ -68,12 +68,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. This is a roadmap with major features will come in near future.
- Support for other databases.
- Database tools. - Database tools.
- Users management (add/edit/delete). - Users management (add/edit/delete).
- More context menu shortcuts. - More context menu shortcuts.
- More keyboard shortcuts. - More keyboard shortcuts.
- Import/export and migration. - Import/export and migration.
- Support for other databases.
- Apple Silicon distribution - Apple Silicon distribution
## Currently supported ## Currently supported
@@ -82,7 +82,7 @@ This is a roadmap with major features will come in near future.
- [x] MySQL/MariaDB - [x] MySQL/MariaDB
- [x] PostgreSQL - [x] PostgreSQL
- [ ] SQLite - [x] SQLite
- [ ] MSSQL - [ ] MSSQL
- [ ] OracleDB - [ ] OracleDB
- [ ] More... - [ ] More...
@@ -116,9 +116,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <tr>
<td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/Fabio286/Antares/commits?author=Fabio286" title="Documentation">📖</a></td> <td align="center"><a href="https://fabiodistasio.it/"><img src="https://avatars.githubusercontent.com/u/31471771?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fabio Di Stasio</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Code">💻</a> <a href="#translation-Fabio286" title="Translation">🌍</a> <a href="https://github.com/Fabio286/antares/commits?author=Fabio286" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=toriphes" title="Code">💻</a></td> <td align="center"><a href="https://www.linkedin.com/in/giulioganci/"><img src="https://avatars.githubusercontent.com/u/4192159?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giulio Ganci</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=toriphes" title="Code">💻</a></td>
<td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/Fabio286/Antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td> <td align="center"><a href="https://christianratz.de/"><img src="https://avatars.githubusercontent.com/u/2630316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Christian Ratz</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=digitalgopnik" title="Code">💻</a> <a href="#translation-digitalgopnik" title="Translation">🌍</a></td>
<td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td> <td align="center"><a href="https://reverb6821.github.io/"><img src="https://avatars.githubusercontent.com/u/55198803?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Giuseppe Gigliotti</b></sub></a><br /><a href="#translation-reverb6821" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/Mohd-PH"><img src="https://avatars.githubusercontent.com/u/9362157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mohd-PH</b></sub></a><br /><a href="#translation-Mohd-PH" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/hongkfui"><img src="https://avatars.githubusercontent.com/u/37477191?v=4?s=100" width="100px;" alt=""/><br /><sub><b>hongkfui</b></sub></a><br /><a href="#translation-hongkfui" title="Translation">🌍</a></td>
@@ -129,6 +129,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td> <td align="center"><a href="https://ngoquocdat.com/"><img src="https://avatars.githubusercontent.com/u/56961917?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ngô Quốc Đạt</b></sub></a><br /><a href="#translation-datlechin" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td> <td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.3.9", "version": "0.3.10",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/Fabio286/antares.git", "repository": "https://github.com/Fabio286/antares.git",
@@ -14,7 +14,7 @@
"build": "cross-env NODE_ENV=production npm run compile", "build": "cross-env NODE_ENV=production npm run compile",
"build:local": "npm run build && electron-builder", "build:local": "npm run build && electron-builder",
"build:appx": "npm run build:local -- --win appx", "build:appx": "npm run build:local -- --win appx",
"rebuild:electron": "npm run postinstall && electron-rebuild", "rebuild:electron": "npm run postinstall",
"release": "standard-version", "release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha", "release:pre": "npm run release -- --prerelease alpha",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
@@ -30,6 +30,7 @@
"appId": "com.fabio286.antares", "appId": "com.fabio286.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}", "artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
"asar": true, "asar": true,
"buildDependenciesFromSource": true,
"directories": { "directories": {
"output": "build", "output": "build",
"buildResources": "assets" "buildResources": "assets"
@@ -104,6 +105,7 @@
"@electron/remote": "^2.0.1", "@electron/remote": "^2.0.1",
"@mdi/font": "^6.1.95", "@mdi/font": "^6.1.95",
"ace-builds": "^1.4.13", "ace-builds": "^1.4.13",
"better-sqlite3": "^7.4.4",
"electron-log": "^4.4.1", "electron-log": "^4.4.1",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
@@ -131,10 +133,9 @@
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"css-loader": "^6.5.0", "css-loader": "^6.5.0",
"electron": "^15.3.0", "electron": "^16.0.1",
"electron-builder": "^22.13.1", "electron-builder": "^22.13.1",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-rebuild": "^3.2.3",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2", "eslint-plugin-import": "^2.24.2",

View File

@@ -8,6 +8,9 @@ module.exports = {
collations: false, collations: false,
engines: false, engines: false,
connectionSchema: false, connectionSchema: false,
sslConnection: false,
sshConnection: false,
fileConnection: false,
// Tools // Tools
processesList: false, processesList: false,
usersManagement: false, usersManagement: false,
@@ -33,7 +36,11 @@ module.exports = {
schedulerAdd: false, schedulerAdd: false,
databaseEdit: false, databaseEdit: false,
schemaEdit: false, schemaEdit: false,
schemaDrop: false,
tableSettings: false, tableSettings: false,
tableOptions: false,
tableArray: false,
tableRealCount: false,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
triggerFunctionSettings: false, triggerFunctionSettings: false,
@@ -45,14 +52,13 @@ module.exports = {
sortableFields: false, sortableFields: false,
unsigned: false, unsigned: false,
nullable: false, nullable: false,
nullablePrimary: false,
zerofill: false, zerofill: false,
tableOptions: false,
autoIncrement: false, autoIncrement: false,
comment: false, comment: false,
collation: false, collation: false,
definer: false, definer: false,
onUpdate: false, onUpdate: false,
tableArray: false,
viewAlgorithm: false, viewAlgorithm: false,
viewSqlSecurity: false, viewSqlSecurity: false,
viewUpdateOption: false, viewUpdateOption: false,
@@ -76,5 +82,6 @@ module.exports = {
triggerFunctionSql: false, triggerFunctionSql: false,
triggerFunctionlanguages: false, triggerFunctionlanguages: false,
parametersLength: false, parametersLength: false,
languages: false languages: false,
readOnlyMode: false
}; };

View File

@@ -1,5 +1,6 @@
module.exports = { module.exports = {
maria: require('./mysql'), maria: require('./mysql'),
mysql: require('./mysql'), mysql: require('./mysql'),
pg: require('./postgresql') pg: require('./postgresql'),
sqlite: require('./sqlite')
}; };

View File

@@ -10,6 +10,8 @@ module.exports = {
connectionSchema: true, connectionSchema: true,
collations: true, collations: true,
engines: true, engines: true,
sslConnection: true,
sshConnection: true,
// Tools // Tools
processesList: true, processesList: true,
// Structure // Structure
@@ -30,6 +32,7 @@ module.exports = {
functionAdd: true, functionAdd: true,
schedulerAdd: true, schedulerAdd: true,
schemaEdit: true, schemaEdit: true,
schemaDrop: true,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
triggerSettings: true, triggerSettings: true,
@@ -59,5 +62,6 @@ module.exports = {
functionDeterministic: true, functionDeterministic: true,
functionDataAccess: true, functionDataAccess: true,
functionSql: 'BEGIN\r\n\r\nEND', functionSql: 'BEGIN\r\n\r\nEND',
parametersLength: true parametersLength: true,
readOnlyMode: true
}; };

View File

@@ -8,9 +8,12 @@ module.exports = {
defaultDatabase: 'postgres', defaultDatabase: 'postgres',
// Core // Core
database: true, database: true,
sslConnection: true,
sshConnection: true,
// Tools // Tools
processesList: true, processesList: true,
// Structure // Structure
schemas: true,
tables: true, tables: true,
views: true, views: true,
triggers: true, triggers: true,
@@ -26,6 +29,7 @@ module.exports = {
triggerFunctionAdd: true, triggerFunctionAdd: true,
routineAdd: true, routineAdd: true,
functionAdd: true, functionAdd: true,
schemaDrop: true,
databaseEdit: false, databaseEdit: false,
tableSettings: true, tableSettings: true,
viewSettings: true, viewSettings: true,
@@ -51,5 +55,6 @@ module.exports = {
triggerTableInName: true, triggerTableInName: true,
triggerOnlyRename: false, triggerOnlyRename: false,
triggerEnableDisable: true, triggerEnableDisable: true,
languages: ['sql', 'plpgsql', 'c', 'internal'] languages: ['sql', 'plpgsql', 'c', 'internal'],
readOnlyMode: true
}; };

View File

@@ -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
};

View File

@@ -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
}
]
}
];

View File

@@ -0,0 +1,5 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@@ -9,12 +9,16 @@ export default connections => {
port: +conn.port, port: +conn.port,
user: conn.user, user: conn.user,
password: conn.password, password: conn.password,
application_name: 'Antares SQL' application_name: 'Antares SQL',
readonly: conn.readonly
}; };
if (conn.database) if (conn.database)
params.database = conn.database; params.database = conn.database;
if (conn.databasePath)
params.databasePath = conn.databasePath;
if (conn.ssl) { if (conn.ssl) {
params.ssl = { params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null, key: conn.key ? fs.readFileSync(conn.key) : null,
@@ -62,12 +66,16 @@ export default connections => {
port: +conn.port, port: +conn.port,
user: conn.user, user: conn.user,
password: conn.password, password: conn.password,
application_name: 'Antares SQL' application_name: 'Antares SQL',
readonly: conn.readonly
}; };
if (conn.database) if (conn.database)
params.database = conn.database; params.database = conn.database;
if (conn.databasePath)
params.databasePath = conn.databasePath;
if (conn.schema) if (conn.schema)
params.schema = conn.schema; params.schema = conn.schema;

View File

@@ -102,6 +102,9 @@ export default (connections) => {
case 'pg': case 'pg':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`; escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break; break;
case 'sqlite':
escapedParam = `'${params.content.replaceAll('\'', '\'\'')}'`;
break;
} }
} }
else if (ARRAY.includes(params.type)) else if (ARRAY.includes(params.type))
@@ -122,6 +125,10 @@ export default (connections) => {
fileBlob = fs.readFileSync(params.content); fileBlob = fs.readFileSync(params.content);
escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`; escapedParam = `decode('${fileBlob.toString('hex')}', 'hex')`;
break; break;
case 'sqlite':
fileBlob = fs.readFileSync(params.content);
escapedParam = `X'${fileBlob.toString('hex')}'`;
break;
} }
reload = true; reload = true;
} }
@@ -134,6 +141,9 @@ export default (connections) => {
case 'pg': case 'pg':
escapedParam = 'decode(\'\', \'hex\')'; escapedParam = 'decode(\'\', \'hex\')';
break; break;
case 'sqlite':
escapedParam = 'X\'\'';
break;
} }
} }
} }
@@ -188,7 +198,7 @@ export default (connections) => {
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary; const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
return typeof row[fieldName] === 'string' return typeof row[fieldName] === 'string'
? `"${row[fieldName]}"` ? `'${row[fieldName]}'`
: row[fieldName]; : row[fieldName];
}).join(','); }).join(',');

View File

@@ -1,6 +1,7 @@
'use strict'; 'use strict';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient'; import { PostgreSQLClient } from './clients/PostgreSQLClient';
import { SQLiteClient } from './clients/SQLiteClient';
const queryLogger = sql => { const queryLogger = sql => {
// Remove comments, newlines and multiple spaces // Remove comments, newlines and multiple spaces
@@ -37,6 +38,8 @@ export class ClientsFactory {
return new MySQLClient(args); return new MySQLClient(args);
case 'pg': case 'pg':
return new PostgreSQLClient(args); return new PostgreSQLClient(args);
case 'sqlite':
return new SQLiteClient(args);
default: default:
throw new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }

View File

@@ -133,8 +133,12 @@ export class MySQLClient extends AntaresCore {
} }
} }
if (!this._poolSize) if (!this._poolSize) {
this._connection = await mysql.createConnection(dbConfig); this._connection = await mysql.createConnection(dbConfig);
if (this._params.readonly)
await this.raw('SET SESSION TRANSACTION READ ONLY');
}
else { else {
this._connection = mysql.createPool({ this._connection = mysql.createPool({
...dbConfig, ...dbConfig,
@@ -146,6 +150,12 @@ export class MySQLClient extends AntaresCore {
return next(); return next();
} }
}); });
if (this._params.readonly) {
this._connection.on('connection', connection => {
connection.query('SET SESSION TRANSACTION READ ONLY');
});
}
} }
} }
@@ -571,7 +581,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE DATABASE * CREATE DATABASE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createSchema (params) { async createSchema (params) {
@@ -581,7 +591,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER DATABASE * ALTER DATABASE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterSchema (params) { async alterSchema (params) {
@@ -591,7 +601,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP DATABASE * DROP DATABASE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropSchema (params) { async dropSchema (params) {
@@ -631,7 +641,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP VIEW * DROP VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropView (params) { async dropView (params) {
@@ -642,7 +652,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER VIEW * ALTER VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterView (params) { async alterView (params) {
@@ -663,7 +673,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE VIEW * CREATE VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createView (params) { async createView (params) {
@@ -696,7 +706,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP TRIGGER * DROP TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropTrigger (params) { async dropTrigger (params) {
@@ -707,7 +717,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER TRIGGER * ALTER TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterTrigger (params) { async alterTrigger (params) {
@@ -729,7 +739,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE TRIGGER * CREATE TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createTrigger (params) { async createTrigger (params) {
@@ -803,7 +813,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP PROCEDURE * DROP PROCEDURE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropRoutine (params) { async dropRoutine (params) {
@@ -814,7 +824,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER PROCEDURE * ALTER PROCEDURE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterRoutine (params) { async alterRoutine (params) {
@@ -836,7 +846,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE PROCEDURE * CREATE PROCEDURE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createRoutine (params) { async createRoutine (params) {
@@ -930,7 +940,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP FUNCTION * DROP FUNCTION
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropFunction (params) { async dropFunction (params) {
@@ -941,7 +951,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER FUNCTION * ALTER FUNCTION
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterFunction (params) { async alterFunction (params) {
@@ -963,7 +973,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE FUNCTION * CREATE FUNCTION
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createFunction (params) { async createFunction (params) {
@@ -1024,7 +1034,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP EVENT * DROP EVENT
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropEvent (params) { async dropEvent (params) {
@@ -1035,7 +1045,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER EVENT * ALTER EVENT
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterEvent (params) { async alterEvent (params) {
@@ -1061,7 +1071,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE EVENT * CREATE EVENT
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createEvent (params) { async createEvent (params) {
@@ -1205,7 +1215,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* CREATE TABLE * CREATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createTable (params) { async createTable (params) {
@@ -1267,7 +1277,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* ALTER TABLE * ALTER TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterTable (params) { async alterTable (params) {
@@ -1402,7 +1412,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DUPLICATE TABLE * DUPLICATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async duplicateTable (params) { async duplicateTable (params) {
@@ -1413,7 +1423,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* TRUNCATE TABLE * TRUNCATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async truncateTable (params) { async truncateTable (params) {
@@ -1424,7 +1434,7 @@ export class MySQLClient extends AntaresCore {
/** /**
* DROP TABLE * DROP TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropTable (params) { async dropTable (params) {

View File

@@ -105,10 +105,19 @@ export class PostgreSQLClient extends AntaresCore {
const client = new Client(dbConfig); const client = new Client(dbConfig);
await client.connect(); await client.connect();
this._connection = client; this._connection = client;
if (this._params.readonly)
await this.raw('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
} }
else { else {
const pool = new Pool({ ...dbConfig, max: this._poolSize }); const pool = new Pool({ ...dbConfig, max: this._poolSize });
this._connection = pool; this._connection = pool;
if (this._params.readonly) {
this._connection.on('connect', connection => {
connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
});
}
} }
} }
@@ -506,7 +515,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* CREATE SCHEMA * CREATE SCHEMA
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async createSchema (params) { async createSchema (params) {
@@ -516,7 +525,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* ALTER DATABASE * ALTER DATABASE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async alterSchema (params) { async alterSchema (params) {
@@ -526,7 +535,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* DROP DATABASE * DROP DATABASE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof MySQLClient * @memberof MySQLClient
*/ */
async dropSchema (params) { async dropSchema (params) {
@@ -558,7 +567,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* DROP VIEW * DROP VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropView (params) { async dropView (params) {
@@ -569,7 +578,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* ALTER VIEW * ALTER VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async alterView (params) { async alterView (params) {
@@ -585,7 +594,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* CREATE VIEW * CREATE VIEW
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createView (params) { async createView (params) {
@@ -640,7 +649,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* DROP TRIGGER * DROP TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropTrigger (params) { async dropTrigger (params) {
@@ -652,7 +661,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* ALTER TRIGGER * ALTER TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async alterTrigger (params) { async alterTrigger (params) {
@@ -674,7 +683,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* CREATE TRIGGER * CREATE TRIGGER
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTrigger (params) { async createTrigger (params) {
@@ -1086,7 +1095,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* CREATE TABLE * CREATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async createTable (params) { async createTable (params) {
@@ -1144,7 +1153,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* ALTER TABLE * ALTER TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async alterTable (params) { async alterTable (params) {
@@ -1290,7 +1299,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* DUPLICATE TABLE * DUPLICATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async duplicateTable (params) { async duplicateTable (params) {
@@ -1301,7 +1310,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* TRUNCATE TABLE * TRUNCATE TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async truncateTable (params) { async truncateTable (params) {
@@ -1312,7 +1321,7 @@ export class PostgreSQLClient extends AntaresCore {
/** /**
* DROP TABLE * DROP TABLE
* *
* @returns {Array.<Object>} parameters * @returns {Promise<null>}
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async dropTable (params) { async dropTable (params) {

View File

@@ -0,0 +1,806 @@
'use strict';
import sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore {
constructor (args) {
super(args);
this._schema = null;
}
_getTypeInfo (type) {
return dataTypes
.reduce((acc, group) => [...acc, ...group.types], [])
.filter(_type => _type.name === type.toUpperCase())[0];
}
/**
* @memberof SQLiteClient
*/
async connect () {
this._connection = 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.<Object>} 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.<Object>} 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.<Object>} parameters
* @memberof SQLiteClient
*/
async dropView (params) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
/**
* ALTER VIEW
*
* @returns {Array.<Object>} 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.<Object>} 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.<Object>} 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.<Object>} parameters
* @memberof SQLiteClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
/**
* ALTER TRIGGER
*
* @returns {Array.<Object>} 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.<Object>} 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.<Object>} collations list
* @memberof SQLiteClient
*/
async getCollations () {
return [];
}
/**
* SHOW VARIABLES
*
* @returns {Array.<Object>} variables list
* @memberof SQLiteClient
*/
async getVariables () {
return [];
}
/**
* SHOW ENGINES
*
* @returns {Array.<Object>} engines list
* @memberof SQLiteClient
*/
async getEngines () {
return {
name: 'SQLite',
support: 'YES',
comment: '',
isDefault: true
};
}
/**
* SHOW VARIABLES LIKE '%vers%'
*
* @returns {Array.<Object>} 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 () {}
/**
* CREATE TABLE
*
* @returns {Promise<null>}
* @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<null>}
* @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<null>}
* @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<null>}
* @memberof SQLiteClient
*/
async truncateTable (params) {
const sql = `DELETE FROM "${params.schema}"."${params.table}"`;
return await this.raw(sql);
}
/**
* DROP TABLE
*
* @returns {Promise<null>}
* @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,
...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];
const 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[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;
}
}

View File

@@ -107,14 +107,14 @@ else {
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
createAppMenu(); createAppMenu();
if (isDevelopment) // if (isDevelopment)
mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
process.on('uncaughtException', (error) => { process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
}); });
process.on('unhandledRejection', (error) => { process.on('unhandledRejection', error => {
mainWindow.webContents.send('unhandled-exception', error); mainWindow.webContents.send('unhandled-exception', error);
}); });
}); });

View File

@@ -40,15 +40,13 @@ export default {
} }
}, },
watch: { watch: {
notifications: { 'notifications.length': function (val) {
deep: true, if (val > 0) {
handler: function (notification) { const nUid = this.notifications[0].uid;
if (notification.length) { this.timeouts[nUid] = setTimeout(() => {
this.timeouts[notification[0].uid] = setTimeout(() => { this.removeNotification(nUid);
this.removeNotification(notification[0].uid); delete this.timeouts[nUid];
delete this.timeouts[notification.uid]; }, this.notificationsTimeout * 1000);
}, this.notificationsTimeout * 1000);
}
} }
} }
}, },
@@ -63,11 +61,14 @@ export default {
} }
}, },
rearmTimeouts () { rearmTimeouts () {
const delay = 50;
let i = this.notifications.length * delay;
for (const notification of this.notifications) { for (const notification of this.notifications) {
this.timeouts[notification.uid] = setTimeout(() => { this.timeouts[notification.uid] = setTimeout(() => {
this.removeNotification(notification.uid); this.removeNotification(notification.uid);
delete this.timeouts[notification.uid]; delete this.timeouts[notification.uid];
}, this.notificationsTimeout * 1000); }, (this.notificationsTimeout * 1000) + i);
i = i > delay ? i - delay : 0;
} }
} }
} }

View File

@@ -256,7 +256,11 @@
</span> </span>
</a> </a>
</li> </li>
<li slot="header" class="tab-item dropdown tools-dropdown"> <li
v-if="workspace.customizations.processesList"
slot="header"
class="tab-item dropdown tools-dropdown"
>
<a <a
class="tab-link workspace-tools-link dropdown-toggle" class="tab-link workspace-tools-link dropdown-toggle"
tabindex="0" tabindex="0"
@@ -264,8 +268,8 @@
> >
<i class="mdi mdi-24px mdi-tools" /> <i class="mdi mdi-24px mdi-tools" />
</a> </a>
<ul class="menu text-left text-uppercase"> <ul v-if="hasTools" class="menu text-left text-uppercase">
<li v-if="workspace.customizations.processesList" class="menu-item"> <li class="menu-item">
<a class="c-hand p-vcentered" @click="showProcessesModal"> <a class="c-hand p-vcentered" @click="showProcessesModal">
<i class="mdi mdi-memory mr-1 tool-icon" /> <i class="mdi mdi-memory mr-1 tool-icon" />
<span>{{ $t('message.processesList') }}</span> <span>{{ $t('message.processesList') }}</span>
@@ -569,6 +573,11 @@ export default {
if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key]; if (this.workspace.breadcrumbs[key]) return this.workspace.breadcrumbs[key];
} }
return false; return false;
},
hasTools () {
return this.workspace.customizations.processesList ||
this.workspace.customizations.usersManagement ||
this.workspace.customizations.variables;
} }
}, },
watch: { watch: {

View File

@@ -11,6 +11,7 @@
<a class="tab-link">{{ $t('word.general') }}</a> <a class="tab-link">{{ $t('word.general') }}</a>
</li> </li>
<li <li
v-if="customizations.sslConnection"
class="tab-item c-hand" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}" :class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')" @click="selectTab('ssl')"
@@ -18,6 +19,7 @@
<a class="tab-link">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
<li <li
v-if="customizations.sshConnection"
class="tab-item c-hand" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}" :class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')" @click="selectTab('ssh')"
@@ -49,25 +51,17 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<select v-model="connection.client" class="form-select"> <select v-model="connection.client" class="form-select">
<option value="mysql"> <option
MySQL v-for="client in clients"
:key="client.slug"
:value="client.slug"
>
{{ client.name }}
</option> </option>
<option value="maria">
MariaDB
</option>
<option value="pg">
PostgreSQL
</option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb">
Oracle DB
</option> -->
</select> </select>
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label> <label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div> </div>
@@ -79,7 +73,20 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="connection.databasePath"
:message="$t('word.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
</div>
</div>
<div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label> <label class="form-label">{{ $t('word.port') }}</label>
</div> </div>
@@ -105,7 +112,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label> <label class="form-label">{{ $t('word.user') }}</label>
</div> </div>
@@ -118,7 +125,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label> <label class="form-label">{{ $t('word.password') }}</label>
</div> </div>
@@ -144,7 +151,15 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="customizations.readOnlyMode" class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }}
</label>
</div>
</div>
<div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12" /> <div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline">
@@ -369,15 +384,23 @@ export default {
}, },
data () { data () {
return { return {
clients: [
{ name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' }
],
connection: { connection: {
name: '', name: '',
client: 'mysql', client: 'mysql',
host: '127.0.0.1', host: '127.0.0.1',
database: null, database: null,
databasePath: '',
port: null, port: null,
user: null, user: null,
password: '', password: '',
ask: false, ask: false,
readonly: false,
uid: uidGen('C'), uid: uidGen('C'),
ssl: false, ssl: false,
cert: '', cert: '',

View File

@@ -11,6 +11,7 @@
<a class="tab-link">{{ $t('word.general') }}</a> <a class="tab-link">{{ $t('word.general') }}</a>
</li> </li>
<li <li
v-if="customizations.sslConnection"
class="tab-item c-hand" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssl'}" :class="{'active': selectedTab === 'ssl'}"
@click="selectTab('ssl')" @click="selectTab('ssl')"
@@ -18,6 +19,7 @@
<a class="tab-link">{{ $t('word.ssl') }}</a> <a class="tab-link">{{ $t('word.ssl') }}</a>
</li> </li>
<li <li
v-if="customizations.sshConnection"
class="tab-item c-hand" class="tab-item c-hand"
:class="{'active': selectedTab === 'ssh'}" :class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')" @click="selectTab('ssh')"
@@ -49,19 +51,17 @@
</div> </div>
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<select v-model="localConnection.client" class="form-select"> <select v-model="localConnection.client" class="form-select">
<option value="mysql"> <option
MySQL v-for="client in clients"
</option> :key="client.slug"
<option value="maria"> :value="client.slug"
MariaDB >
</option> {{ client.name }}
<option value="pg">
PostgreSQL
</option> </option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label> <label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div> </div>
@@ -73,7 +73,20 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.database') }}</label>
</div>
<div class="column col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.databasePath"
:message="$t('word.browse')"
@clear="pathClear('databasePath')"
@change="pathSelection($event, 'databasePath')"
/>
</div>
</div>
<div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label> <label class="form-label">{{ $t('word.port') }}</label>
</div> </div>
@@ -99,7 +112,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label> <label class="form-label">{{ $t('word.user') }}</label>
</div> </div>
@@ -112,7 +125,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12"> <div class="column col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label> <label class="form-label">{{ $t('word.password') }}</label>
</div> </div>
@@ -138,7 +151,15 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns"> <div v-if="customizations.readOnlyMode" class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.readonly" type="checkbox"><i class="form-icon" /> {{ $t('message.readOnlyMode') }}
</label>
</div>
</div>
<div v-if="!customizations.fileConnection" class="form-group columns">
<div class="column col-4 col-sm-12" /> <div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12"> <div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline"> <label class="form-checkbox form-inline">
@@ -374,6 +395,12 @@ export default {
}, },
data () { data () {
return { return {
clients: [
{ name: 'MySQL', slug: 'mysql' },
{ name: 'MariaDB', slug: 'maria' },
{ name: 'PostgreSQL', slug: 'pg' },
{ name: 'SQLite', slug: 'sqlite' }
],
isConnecting: false, isConnecting: false,
isTesting: false, isTesting: false,
isAsking: false, isAsking: false,
@@ -383,7 +410,7 @@ export default {
}, },
computed: { computed: {
customizations () { customizations () {
return customizations[this.connection.client]; return customizations[this.localConnection.client];
}, },
isBusy () { isBusy () {
return this.isConnecting || this.isTesting; return this.isConnecting || this.isTesting;

View File

@@ -13,6 +13,7 @@
<span class="workspace-explorebar-title">{{ connectionName }}</span> <span class="workspace-explorebar-title">{{ connectionName }}</span>
<span v-if="workspace.connectionStatus === 'connected'" class="workspace-explorebar-tools"> <span v-if="workspace.connectionStatus === 'connected'" class="workspace-explorebar-tools">
<i <i
v-if="customizations.schemas"
class="mdi mdi-18px mdi-database-plus c-hand mr-2" class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewSchema')" :title="$t('message.createNewSchema')"
@click="showNewDBModal" @click="showNewDBModal"

View File

@@ -43,7 +43,7 @@
<span v-html="highlightWord(table.name)" /> <span v-html="highlightWord(table.name)" />
</a> </a>
<div <div
v-if="table.type === 'table'" v-if="table.type === 'table' && table.size !== false"
class="table-size tooltip tooltip-left mr-1" class="table-size tooltip tooltip-left mr-1"
:data-tooltip="formatBytes(table.size)" :data-tooltip="formatBytes(table.size)"
> >

View File

@@ -65,7 +65,11 @@
> >
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
</div> </div>
<div class="context-element" @click="showDeleteModal"> <div
v-if="workspace.customizations.schemaDrop"
class="context-element"
@click="showDeleteModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
</div> </div>

View File

@@ -506,6 +506,12 @@ export default {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.schema, schema: this.schema,
table: this.table, table: this.table,
tableStructure: {
name: this.localOptions.name,
fields: this.localFields,
foreigns: this.localKeyUsage,
indexes: this.localIndexes
},
additions, additions,
changes, changes,
deletions, deletions,

View File

@@ -367,7 +367,8 @@ export default {
getWorkspace: 'workspaces/getWorkspace' getWorkspace: 'workspaces/getWorkspace'
}), }),
localLength () { localLength () {
return this.localRow.numLength || this.localRow.charLength || this.localRow.datePrecision || this.localRow.numPrecision || 0; const localLength = this.localRow.numLength || this.localRow.charLength || this.localRow.datePrecision || this.localRow.numPrecision || 0;
return localLength === true ? null : localLength;
}, },
fieldType () { fieldType () {
const fieldType = this.dataTypes.reduce((acc, group) => [...acc, ...group.types], []).filter(type => const fieldType = this.dataTypes.reduce((acc, group) => [...acc, ...group.types], []).filter(type =>
@@ -391,7 +392,7 @@ export default {
return this.indexes.some(index => ['PRIMARY', 'UNIQUE'].includes(index.type)); return this.indexes.some(index => ['PRIMARY', 'UNIQUE'].includes(index.type));
}, },
isNullable () { isNullable () {
return !this.indexes.some(index => ['PRIMARY'].includes(index.type)); return this.customizations.nullablePrimary || !this.indexes.some(index => ['PRIMARY'].includes(index.type));
}, },
isInDataTypes () { isInDataTypes () {
let typeNames = []; let typeNames = [];

View File

@@ -93,7 +93,7 @@
<div v-if="resultsCount"> <div v-if="resultsCount">
{{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b> {{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b>
</div> </div>
<div v-if="affectedCount"> <div v-if="affectedCount !== null">
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b> {{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
</div> </div>
<div class="input-group" :title="$t('word.schema')"> <div class="input-group" :title="$t('word.schema')">
@@ -170,7 +170,7 @@ export default {
selectedSchema: null, selectedSchema: null,
resultsCount: 0, resultsCount: 0,
durationsCount: 0, durationsCount: 0,
affectedCount: 0, affectedCount: null,
editorHeight: 200, editorHeight: 200,
isHistoryOpen: false isHistoryOpen: false
}; };
@@ -255,9 +255,14 @@ export default {
if (status === 'success') { if (status === 'success') {
this.results = Array.isArray(response) ? response : [response]; this.results = Array.isArray(response) ? response : [response];
this.resultsCount += this.results.reduce((acc, curr) => acc + (curr.rows ? curr.rows.length : 0), 0); this.resultsCount = this.results.reduce((acc, curr) => acc + (curr.rows ? curr.rows.length : 0), 0);
this.durationsCount += this.results.reduce((acc, curr) => acc + curr.duration, 0); this.durationsCount = this.results.reduce((acc, curr) => acc + curr.duration, 0);
this.affectedCount += this.results.reduce((acc, curr) => acc + (curr.report ? curr.report.affectedRows : 0), 0); this.affectedCount = this.results
.filter(result => result.report !== null)
.reduce((acc, curr) => {
if (acc === null) acc = 0;
return acc + (curr.report ? curr.report.affectedRows : 0);
}, null);
this.updateTabContent({ this.updateTabContent({
uid: this.connection.uid, uid: this.connection.uid,
@@ -285,7 +290,7 @@ export default {
this.results = []; this.results = [];
this.resultsCount = 0; this.resultsCount = 0;
this.durationsCount = 0; this.durationsCount = 0;
this.affectedCount = 0; this.affectedCount = null;
}, },
resize (e) { resize (e) {
const el = this.$refs.queryEditor.$el; const el = this.$refs.queryEditor.$el;

View File

@@ -120,7 +120,12 @@
{{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b> {{ $t('word.results') }}: <b>{{ results[0].rows.length | localeString }}</b>
</div> </div>
<div v-if="hasApproximately || (page > 1 && approximateCount)"> <div v-if="hasApproximately || (page > 1 && approximateCount)">
{{ $t('word.total') }}: <b :title="$t('word.approximately')"> {{ approximateCount | localeString }}</b> {{ $t('word.total') }}: <b
:title="!customizations.tableRealCount ? $t('word.approximately') : ''"
>
<span v-if="!customizations.tableRealCount"></span>
{{ approximateCount | localeString }}
</b>
</div> </div>
<div class="d-flex" :title="$t('word.schema')"> <div class="d-flex" :title="$t('word.schema')">
<i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b> <i class="mdi mdi-18px mdi-database mr-1" /><b>{{ schema }}</b>
@@ -231,6 +236,9 @@ export default {
workspace () { workspace () {
return this.getWorkspace(this.connection.uid); return this.getWorkspace(this.connection.uid);
}, },
customizations () {
return this.workspace.customizations;
},
isTable () { isTable () {
return !!this.workspace.breadcrumbs.table; return !!this.workspace.breadcrumbs.table;
}, },

View File

@@ -250,7 +250,8 @@ module.exports = {
searchForQueries: 'Search for queries', searchForQueries: 'Search for queries',
killProcess: 'Kill process', killProcess: 'Kill process',
closeTab: 'Close tab', closeTab: 'Close tab',
goToDownloadPage: 'Go to download page' goToDownloadPage: 'Go to download page',
readOnlyMode: 'Read-only mode'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@@ -121,7 +121,10 @@ module.exports = {
history: 'Cronologia', history: 'Cronologia',
select: 'Seleziona', select: 'Seleziona',
passphrase: 'Passphrase', passphrase: 'Passphrase',
filter: 'Filtra' filter: 'Filtra',
disabled: 'Disabilitato',
enable: 'Abilita',
disable: 'Disabilita'
}, },
message: { message: {
appWelcome: 'Benvenuto in Antares SQL Client!', appWelcome: 'Benvenuto in Antares SQL Client!',
@@ -233,7 +236,22 @@ module.exports = {
duplicateTable: 'Duplica tabella', duplicateTable: 'Duplica tabella',
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:', noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
noSchema: 'Nessuno schema', noSchema: 'Nessuno schema',
restorePreviourSession: 'Ripristina sessione precedente' restorePreviourSession: 'Ripristina sessione precedente',
runQuery: 'Esegui query',
thereAreNoTableFields: 'Non ci sono campi della tabella',
newTable: 'Nuova tabella',
newView: 'Nuova vista',
newTrigger: 'Nuovo trigger',
newRoutine: 'Nuova routine',
newFunction: 'Nuova funzione',
newScheduler: 'Nuovo scheduler',
newTriggerFunction: 'Nuova funzione di trigger',
thereIsNoQueriesYet: 'Non ci sono ancora query',
searchForQueries: 'Cerca query',
killProcess: 'Uccidi processo',
closeTab: 'Chiudi tab',
goToDownloadPage: 'Vai alla pagina di download',
readOnlyMode: 'Modalità sola lettura'
}, },
faker: { faker: {
address: 'Indirizzo', address: 'Indirizzo',

View File

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 1004 B

View File

@@ -17,10 +17,12 @@
( (
"char": $string-color, "char": $string-color,
"varchar": $string-color, "varchar": $string-color,
"longvarchar": $string-color,
"text": $string-color, "text": $string-color,
"tinytext": $string-color, "tinytext": $string-color,
"mediumtext": $string-color, "mediumtext": $string-color,
"longtext": $string-color, "longtext": $string-color,
"string": $string-color,
"json": $string-color, "json": $string-color,
"name": $string-color, "name": $string-color,
"character": $string-color, "character": $string-color,
@@ -50,6 +52,7 @@
"oid": $number-color, "oid": $number-color,
"xid": $number-color, "xid": $number-color,
"money": $number-color, "money": $number-color,
"number": $number-color,
"datetime": $date-color, "datetime": $date-color,
"date": $date-color, "date": $date-color,
"time": $date-color, "time": $date-color,

View File

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

View File

@@ -24,12 +24,24 @@ export default {
getConnections: state => state.connections, getConnections: state => state.connections,
getConnectionName: state => uid => { getConnectionName: state => uid => {
const connection = state.connections.filter(connection => connection.uid === uid)[0]; const connection = state.connections.filter(connection => connection.uid === uid)[0];
if (!connection) return ''; let connectionName = '';
return connection.name
? connection.name if (connection.name)
: connection.ask connectionName = connection.name;
? `${connection.host}:${connection.port}` else if (connection.ask)
: `${connection.user + '@'}${connection.host}:${connection.port}`; connectionName = `${connection.host}:${connection.port}`;
else if (connection.databasePath) {
let string = connection.databasePath.split(/[/\\]+/).pop();
if (string.length >= 30)
string = `...${string.slice(-30)}`;
connectionName = string;
}
else
connectionName = `${connection.user + '@'}${connection.host}:${connection.port}`;
return connectionName;
} }
}, },
mutations: { mutations: {

View File

@@ -412,6 +412,11 @@ export default {
indexTypes = require('common/index-types/postgresql'); indexTypes = require('common/index-types/postgresql');
customizations = require('common/customizations/postgresql'); customizations = require('common/customizations/postgresql');
break; break;
case 'sqlite':
dataTypes = require('common/data-types/sqlite');
indexTypes = require('common/index-types/sqlite');
customizations = require('common/customizations/sqlite');
break;
} }
const { status, response: version } = await Schema.getVersion(connection.uid); const { status, response: version } = await Schema.getVersion(connection.uid);