diff --git a/README.md b/README.md index 09229070..6982e75a 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,14 @@ DBM can query data from any SQL-speaking datastore or data engine (ClickHouse an Here are some of the major database solutions that are supported:

- ClickHouse - Trino - Presto - MySQL - PostgreSQL - Druid + ClickHouse + Trino + Presto + MySQL + PostgreSQL + Druid + ElasticSearch + Hologres

## Features diff --git a/docs/docs/assets/images/others/management/datasource/elasticsearch/img.png b/docs/docs/assets/images/others/management/datasource/elasticsearch/img.png new file mode 100644 index 00000000..7ebd2ad7 Binary files /dev/null and b/docs/docs/assets/images/others/management/datasource/elasticsearch/img.png differ diff --git a/docs/docs/assets/images/others/management/datasource/elasticsearch/img_1.png b/docs/docs/assets/images/others/management/datasource/elasticsearch/img_1.png new file mode 100644 index 00000000..16b60fd4 Binary files /dev/null and b/docs/docs/assets/images/others/management/datasource/elasticsearch/img_1.png differ diff --git a/docs/docs/assets/images/others/management/datasource/hologres/img.png b/docs/docs/assets/images/others/management/datasource/hologres/img.png new file mode 100644 index 00000000..cd4b3047 Binary files /dev/null and b/docs/docs/assets/images/others/management/datasource/hologres/img.png differ diff --git a/docs/docs/assets/images/others/management/datasource/hologres/img_1.png b/docs/docs/assets/images/others/management/datasource/hologres/img_1.png new file mode 100644 index 00000000..1a9ab8c0 Binary files /dev/null and b/docs/docs/assets/images/others/management/datasource/hologres/img_1.png differ diff --git a/docs/docs/assets/integrate/ElasticSearch.svg b/docs/docs/assets/integrate/ElasticSearch.svg new file mode 100644 index 00000000..b95507cd --- /dev/null +++ b/docs/docs/assets/integrate/ElasticSearch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/docs/assets/integrate/Hologres.svg b/docs/docs/assets/integrate/Hologres.svg new file mode 100644 index 00000000..0bf2084c --- /dev/null +++ b/docs/docs/assets/integrate/Hologres.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/docs/download.md b/docs/docs/download.md index 82dc38ab..f2fa86dd 100644 --- a/docs/docs/download.md +++ b/docs/docs/download.md @@ -91,11 +91,11 @@ The current Trino release is version = `1.22.0` + +### Supported Versions + +--- + +| Version | Tested? | +|---------|---------------------------------------------| +| `1.0.x` | :material-checkbox-marked-circle:{.success} | + +!!! note "Supported versions" + + Most versions have been tested, please submit issues for non-adapted versions. + +### Created a Source + +--- + +After entering the data source management page, click the Add data source button. + +![img.png](../../../assets/images/others/management/datasource/elasticsearch/img.png) + +Select the ElasticSearch icon in the `Experimental` type (the fourth). + +After selecting the type, click the `Next` button at the bottom to configure the relevant information. + +![img.png](../../../assets/images/others/management/datasource/elasticsearch/img_1.png) + +!!! note "Supported protocols" + + - [x] `TCP` + +#### TCP Protocol + +--- + +!!! note "TCP Protocol" + + Use the TCP interface provided by ElasticSearch to connect to the service. + +| Parameter | Description | Required | Unique | Default | +|-----------|-----------------------------------------------------------------------------------------------------|----------|--------|---------| +| `Alias` | The alias of the data source, which will be displayed later in the selected data source on the page | Yes | Yes | | +| `Host` | The host of the ElasticSearch server | Yes | Yes | | +| `Port` | The port of the ElasticSearch server | Yes | Yes | `5443` | + +When we have configured the above parameters, click the `Test` button at the bottom. If the service can be accessed normally, the `OK` button can be used. Click it and it will be saved. diff --git a/docs/docs/reference/management/datasource/hologres.md b/docs/docs/reference/management/datasource/hologres.md new file mode 100644 index 00000000..23d427a3 --- /dev/null +++ b/docs/docs/reference/management/datasource/hologres.md @@ -0,0 +1,60 @@ +--- +template: overrides/main.html +--- + +!!! note "Hologres" + + It is mainly used to describe how the software builds the Hologres data source for subsequent operations. + +!!! warning "System requirements" + + \>= `1.22.0` + +### Supported Versions + +--- + +| Version | Tested? | +|---------|---------------------------------------------| +| `1.0.x` | :material-checkbox-marked-circle:{.success} | + +!!! note "Supported versions" + + Most versions have been tested, please submit issues for non-adapted versions. + +### Created a Source + +--- + +After entering the data source management page, click the Add data source button. + +![img.png](../../../assets/images/others/management/datasource/hologres/img.png) + +Select the Hologres icon in the `Experimental` type (the fourth). + +After selecting the type, click the `Next` button at the bottom to configure the relevant information. + +![img.png](../../../assets/images/others/management/datasource/hologres/img_1.png) + +!!! note "Supported protocols" + + - [x] `TCP` + +#### TCP Protocol + +--- + +!!! note "TCP Protocol" + + Use the TCP interface provided by Hologres to connect to the service. + +| Parameter | Description | Required | Unique | Default | +|--------------------|-----------------------------------------------------------------------------------------------------|----------|--------|---------| +| `Alias` | The alias of the data source, which will be displayed later in the selected data source on the page | Yes | Yes | | +| `Host` | The host of the Hologres server | Yes | Yes | | +| `Port` | The port of the Hologres server | Yes | Yes | `5443` | +| `AccessKey ID` | The user name of the Hologres server | No | Yes | | +| `AccessKey Secret` | The password of the Hologres server | No | Yes | | +| `Database` | The database of the Hologres server | No | Yes | | + +When we have configured the above parameters, click the `Test` button at the bottom. If the service can be accessed normally, the `OK` button can be used. Click it and it will be saved. diff --git a/docs/docs/release/1.22.0-20220812.md b/docs/docs/release/1.22.0-20220812.md new file mode 100644 index 00000000..a98ddd24 --- /dev/null +++ b/docs/docs/release/1.22.0-20220812.md @@ -0,0 +1,82 @@ +--- +template: overrides/main.html +icon: material/gesture-tap-button +--- + +DBM Version for `1.22.0` is released! + +Release Time: `20220812` + +#### General + +--- + +- Rebuild the table configuration layout +- Fix the query result is not displayed in the column data under certain circumstances +- Optimize the display type icon of the data source list + +#### Editor + +--- + +- Support the new editor to delete the data table shortcut menu (new editor) +- Display and query additional information (new editor) + +#### Security + +--- + +- Upgrade electron `16.2.0` to `20.0.1` + + +#### Dependencies + +--- + +- Bump wait-on from `5.3.0` to `6.0.1` + +#### ClickHouse + +--- + +- Support `MySQL` table engine +- Support `Hive` table engine +- Support `PostgreSQL` database engine +- Support https protocol [issues-247](https://github.com/EdurtIO/dbm/issues/247) + +#### MySQL + +--- + +- Support metadata management, build a database, specify character sets & collation [issues-180](https://github.com/EdurtIO/dbm/issues/180) +- Fix only_full_group_by could not obtain metadata [issues-180](https://github.com/EdurtIO/dbm/issues/180) + +#### PostgreSQL + +--- + +- Fix the exception of the metadata management build table +- Support rename database +- Fix failure to query when database is empty + +#### Apache Druid + +--- + +- Support quick query + +#### ElasticSearch + +--- + +- Support ElasticSearch (query and data source management) + +#### Hologres + +--- + +- Support Hologres (query and data source management) + +--- + +- @qianmoQ diff --git a/docs/material/overrides/home.html b/docs/material/overrides/home.html index acac4d51..4ab71212 100644 --- a/docs/material/overrides/home.html +++ b/docs/material/overrides/home.html @@ -136,6 +136,28 @@

Supported Databases

Druid is a high performance real-time analytics database. Druid's main value add is to reduce time to insight and action. +
  • +
    + + + + ElasticSearch +
    +
    + Elasticsearch is the distributed, RESTful search and analytics engine at the heart of the Elastic Stack. +
    +
  • +
  • +
    + + + + Hologres +
    +
    + Hologres is an all-in-one real-time data warehouse engine that is compatible with PostgreSQL. It supports online analytical processing (OLAP) and ad hoc analysis of PB-scale data. +
    +
  • diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 4c708467..74efadea 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -133,6 +133,8 @@ plugins: Datasource_ClickHouse: ClickHouse Datasource_PostgreSQL: PostgreSQL (Experimental) Datasource_Druid: Druid (Experimental) + Datasource_Hologres: Hologres (Experimental) + Datasource_ElasticSearch: ElasticSearch (Experimental) zh: Home: 主页 Documentation: 文档 @@ -156,9 +158,11 @@ plugins: Datasource_ClickHouse: ClickHouse Datasource_PostgreSQL: PostgreSQL (Experimental) Datasource_Druid: Druid (Experimental) + Datasource_Hologres: Hologres (Experimental) + Datasource_ElasticSearch: ElasticSearch (Experimental) - redirects: redirect_maps: - release-latest.md: release/1.20.0-20220706.md + release-latest.md: release/1.22.0-20220812.md nav: - Home: index.md @@ -175,13 +179,16 @@ nav: - Datasource_MySQL: reference/management/datasource/mysql.md - Datasource_PostgreSQL: reference/management/datasource/postgresql.md - Datasource_Druid: reference/management/datasource/druid.md + - Datasource_ElasticSearch: reference/management/datasource/elasticsearch.md + - Datasource_Hologres: reference/management/datasource/hologres.md - Monitor: - Processor: reference/monitor/monitor-processor.md - Connection: reference/monitor/monitor_connection.md - Mutations: reference/monitor/monitor_mutations.md - Query: reference/monitor/monitor_query.md - Release Note: - - 1.21.0 (latest): release/1.21.0-20220725.md + - 1.22.0 (latest): release/1.22.0-20220812.md + - 1.21.0: release/1.21.0-20220725.md - 1.20.0: release/1.20.0-20220706.md - 1.19.0: release/1.19.0-20220623.md - 1.18.0: release/1.18.0-20220610.md diff --git a/electron-builder.yml b/electron-builder.yml index 4ee9855a..6f766284 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -18,41 +18,71 @@ releaseInfo: --- - - Support custom file names for downloading result data - - Add multiple editor theme + - Rebuild the table configuration layout + - Fix the query result is not displayed in the column data under certain circumstances + - Optimize the display type icon of the data source list + + #### Editor + + --- + + - Support the new editor to delete the data table shortcut menu (new editor) + - Display and query additional information (new editor) + + #### Security + + --- + + - Upgrade electron `16.2.0` to `20.0.1` + #### Dependencies --- - - Bump eslint-plugin-jsdoc from 35.3.2 to 39.3.3 - - Bump angular-highcharts from 13.0.1 to 14.1.5 - - Bump karma-jasmine-html-reporter from 1.7.0 to 2.0.0 + - Bump wait-on from `5.3.0` to `6.0.1` + + #### ClickHouse + + --- + + - Support `MySQL` table engine + - Support `Hive` table engine + - Support `PostgreSQL` database engine + - Support https protocol [issues-247](https://github.com/EdurtIO/dbm/issues/247) #### MySQL --- - - Support metadata management to build data tables [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to filter tables [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to show database ddl [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to delete database [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to preview table [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to show table ddl [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to delete table [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to rename table, truncate table [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to preview column [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to create column, delete column [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to rename column [issues-180](https://github.com/EdurtIO/dbm/issues/180) - - Support metadata management to add column comment [issues-180](https://github.com/EdurtIO/dbm/issues/180) + - Support metadata management, build a database, specify character sets & collation [issues-180](https://github.com/EdurtIO/dbm/issues/180) + - Fix only_full_group_by could not obtain metadata [issues-180](https://github.com/EdurtIO/dbm/issues/180) #### PostgreSQL --- - - Supports quick query - - Support metadata management menu server related operations - - Supports metadata management of disk usage + - Fix the exception of the metadata management build table + - Support rename database + - Fix failure to query when database is empty + + #### Apache Druid + + --- + + - Support quick query + + #### ElasticSearch + + --- + + - Support ElasticSearch (query and data source management) + + #### Hologres + + --- + + - Support Hologres (query and data source management) directories: output: ./release diff --git a/package.json b/package.json index 94d84179..8802486f 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "dbm", - "version": "1.21.0", + "version": "1.22.0", "author": "qianmoQ ", - "description": "DataBase GUI", + "description": "Full platform database management tool, supports ClickHouse, Presto, Trino, MySQL, PostgreSQL...", "github": "https://github.com/EdurtIO/dbm.git", "homepage": "https://dbm.edurt.io", "keywords": [ "angular", "angular 12", + "angular 14", "electron", "typescript", "windows", @@ -17,7 +18,9 @@ "clickhouse", "trino", "presto", - "mysql" + "mysql", + "apache druid", + "postgresql" ], "publish": [ { @@ -106,7 +109,7 @@ "cfonts": "^3.1.0", "chalk": "^4.1.1", "cross-env": "^7.0.3", - "electron": "16.2.0", + "electron": "20.0.1", "electron-builder": "23.0.3", "electron-reload": "1.5.0", "eslint": "7.29.0", @@ -121,7 +124,7 @@ "karma-jasmine-html-reporter": "^2.0.0", "ts-loader": "^9.2.3", "typescript": "^4.2.4", - "wait-on": "^5.3.0", + "wait-on": "6.0.1", "webpack": "5.73.0", "webpack-cli": "4.7.2" }, diff --git a/src/renderer/app/layout/layout.module.ts b/src/renderer/app/layout/layout.module.ts index 3dbad47f..5afd5520 100644 --- a/src/renderer/app/layout/layout.module.ts +++ b/src/renderer/app/layout/layout.module.ts @@ -17,6 +17,17 @@ import { PrestoService } from "@renderer/services/presto.service"; import { FactoryService } from "@renderer/services/factory.service"; import { MySQLService } from "@renderer/services/plugin/mysql.service"; import { PostgresqlService } from "@renderer/services/plugin/postgresql.service"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; +import { PluginToken } from "@renderer/token/plugin.token"; +import { ClickHousePlugin } from "@renderer/plugin/clickhouse.plugin"; +import { ElasticsearchPlugin } from "@renderer/plugin/elasticsearch.plugin"; +import { MysqlPlugin } from "@renderer/plugin/mysql.plugin"; +import { ConfigFactory } from "@renderer/factory/config.factory"; +import { ConfigToken } from "@renderer/token/config.token"; +import { MysqlConfig } from "@renderer/config/mysql.config"; +import { ClickHouseConfig } from "@renderer/config/clickhouse.config"; +import { PostgreSQLConfig } from "@renderer/config/postgresql.config"; +import { PostgreSQLPlugin } from "@renderer/plugin/postgresql.plugin"; const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './renderer/assets/i18n/', '.json'); @@ -48,7 +59,18 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => PrestoService, FactoryService, MySQLService, - PostgresqlService + PostgresqlService, + /* Plugin */ + PluginFactory, + {provide: PluginToken, useClass: ClickHousePlugin, multi: true}, + {provide: PluginToken, useClass: ElasticsearchPlugin, multi: true}, + {provide: PluginToken, useClass: MysqlPlugin, multi: true}, + {provide: PluginToken, useClass: PostgreSQLPlugin, multi: true}, + /* Config */ + ConfigFactory, + {provide: ConfigToken, useClass: MysqlConfig, multi: true}, + {provide: ConfigToken, useClass: ClickHouseConfig, multi: true}, + {provide: ConfigToken, useClass: PostgreSQLConfig, multi: true}, ] }) export class LayoutModule { diff --git a/src/renderer/app/pages/management/datasource/datasource.component.html b/src/renderer/app/pages/management/datasource/datasource.component.html index 242eb596..f56c79b5 100644 --- a/src/renderer/app/pages/management/datasource/datasource.component.html +++ b/src/renderer/app/pages/management/datasource/datasource.component.html @@ -11,7 +11,7 @@ - + {{'common.alias' | translate}} @@ -31,13 +31,15 @@ - {{data.alias}} - {{data.name ? data.name : '-'}} - {{data.host}} - {{data.protocol}} - {{data.type}} - {{data.username ? data.username : '-'}} - {{data.version ? data.version : '-'}} + {{data.alias}} + {{data.name ? data.name : '-'}} + {{data.host}} + {{data.protocol}} + + + + {{data.username ? data.username : '-'}} + {{data.version ? data.version : '-'}} +
    + + {{applyResult.columns.length}} + + + {{applyResult.statistics.elapsed}} + + + {{applyResult.statistics.rows_read}} + + + {{applyResult.statistics.bytes_read}} + +
    diff --git a/src/renderer/app/pages/query/beta/query.beta.component.ts b/src/renderer/app/pages/query/beta/query.beta.component.ts index 2ed01e45..7b9d10a2 100644 --- a/src/renderer/app/pages/query/beta/query.beta.component.ts +++ b/src/renderer/app/pages/query/beta/query.beta.component.ts @@ -14,12 +14,10 @@ import { ColumnService } from "@renderer/services/management/column.service"; import { DatabaseService } from "@renderer/services/management/database.service"; import { DatasourceService } from "@renderer/services/management/datasource.service"; import { TableService } from "@renderer/services/management/table.service"; -import { QueryService } from "@renderer/services/query/query.service"; import { ObjectUtils } from "@renderer/utils/object.utils"; import { SqlUtils } from "@renderer/utils/sql.utils"; import { StringUtils } from "@renderer/utils/string.utils"; import { TreeUtils } from "@renderer/utils/tree.utils"; -import { NzContextMenuService } from "ng-zorro-antd/dropdown"; import { NzModalService } from "ng-zorro-antd/modal"; import { NzFormatEmitEvent } from "ng-zorro-antd/tree"; import { DefaultConfig } from "ngx-easy-table"; @@ -64,6 +62,7 @@ export class QueryBetaComponent implements AfterViewInit, AfterViewChecked { configuration: {...DefaultConfig}, headers: [], columns: [], + statistics: null, message: null, status: false, height: 0, @@ -208,7 +207,7 @@ export class QueryBetaComponent implements AfterViewInit, AfterViewChecked { configure.table = this.selectData.table; configure.value = this.selectData.currentValue.key; configure.request = this.requestConfig; - this.menuCommonService.applySqlForOperation(command, configure); + this.applyEditor.value = this.menuCommonService.applySqlForOperation(command, configure); } handlerExecute() { @@ -233,6 +232,7 @@ export class QueryBetaComponent implements AfterViewInit, AfterViewChecked { this.applyResult.headers.push({key: column.name, title: column.name}); }); this.applyResult.columns = response.data.columns; + this.applyResult.statistics = response.data.statistics; if (this.applyResult.headers.length > 0) { this.applyResult.columns = response.data.columns; if (this.applyResult.columns.length > 0) { diff --git a/src/renderer/app/pages/query/beta/query.beta.module.ts b/src/renderer/app/pages/query/beta/query.beta.module.ts index ce1017a1..f0c0acaf 100644 --- a/src/renderer/app/pages/query/beta/query.beta.module.ts +++ b/src/renderer/app/pages/query/beta/query.beta.module.ts @@ -21,6 +21,7 @@ import { AceModule } from "ngx-ace-wrapper"; import { TableModule } from "ngx-easy-table"; import { EllipsisModule } from "ngx-ellipsis"; import { QueryBetaComponent } from './query.beta.component'; +import { ElasticsearchPlugin } from "@renderer/plugin/elasticsearch.plugin"; const QUERY_ROUTES: Routes = [ {path: '', component: QueryBetaComponent} @@ -54,7 +55,8 @@ const QUERY_ROUTES: Routes = [ EditorService, QueryService, PluginFactory, - { provide: PluginToken, useClass: ClickHousePlugin, multi: true } + {provide: PluginToken, useClass: ClickHousePlugin, multi: true}, + {provide: PluginToken, useClass: ElasticsearchPlugin, multi: true} ] }) export class QueryBetaModule { diff --git a/src/renderer/app/pages/query/query/query.module.ts b/src/renderer/app/pages/query/query/query.module.ts index 5edbe4ef..9de5bd62 100644 --- a/src/renderer/app/pages/query/query/query.module.ts +++ b/src/renderer/app/pages/query/query/query.module.ts @@ -18,6 +18,10 @@ import { QuoteSnippetComponent } from '@renderer/components/snippet/quote/quote. import { SnippetService } from '@renderer/services/snippet/snippet.service'; import { TableModule } from 'ngx-easy-table'; import { EllipsisModule } from 'ngx-ellipsis'; +import { PluginFactory } from "@renderer/factory/plugin.factory"; +import { PluginToken } from "@renderer/token/plugin.token"; +import { ClickHousePlugin } from "@renderer/plugin/clickhouse.plugin"; +import { ElasticsearchPlugin } from "@renderer/plugin/elasticsearch.plugin"; const QUERY_ROUTES: Routes = [ {path: '', component: QueryComponent} @@ -48,7 +52,10 @@ const QUERY_ROUTES: Routes = [ QueryService, QueryHistoryService, QueryQuickService, - SnippetService + SnippetService, + PluginFactory, + {provide: PluginToken, useClass: ClickHousePlugin, multi: true}, + {provide: PluginToken, useClass: ElasticsearchPlugin, multi: true} ] }) export class QueryModule { diff --git a/src/renderer/assets/i18n/en.json b/src/renderer/assets/i18n/en.json index da6fac2c..4742c2fd 100644 --- a/src/renderer/assets/i18n/en.json +++ b/src/renderer/assets/i18n/en.json @@ -24,9 +24,7 @@ "cancel": "Cancel", "select": "Select", "quick": "Quick", - "result": "Result", "editor": "Editor", - "id": "ID", "server": "Server", "state": "State", "time": "Time", @@ -74,7 +72,6 @@ "atomic": "Atomic", "expiration": "Expiration", "lazy": "Lazy", - "mysql": "MySQL", "materialized_mysql": "MaterializeMySQL", "github": "GitHub", "drop": "Drop", @@ -143,8 +140,6 @@ "comment": "Comment", "snippet": "Snippet", "id": "ID", - "name": "Name", - "description": "Description", "code": "Code", "created": "Created", "updated": "Updated", @@ -171,7 +166,13 @@ "location": "Location", "failed": "Failed", "druid": "Druid", - "materialized_postgresql": "MaterializePostgreSQL" + "materialized_postgresql": "MaterializePostgreSQL", + "elasticsearch": "ElasticSearch", + "character": "Character", + "collation": "Collation", + "hologres": "Hologres", + "accesskey_id": "AccessKey ID", + "accesskey_secret": "AccessKey Secret" }, "language": { "english": "English", @@ -228,7 +229,8 @@ "lazy": "Keeps tables in RAM only expiration_time_in_seconds seconds after last access. Can be used only with *Log tables.", "mysql": "Allows to connect to databases on a remote MySQL server and perform INSERT and SELECT queries to exchange data between ClickHouse and MySQL.", "materialized_mysql": "Creates ClickHouse database with all the tables existing in MySQL, and all the data in those tables. ClickHouse server works as MySQL replica. It reads binlog and performs DDL and DML queries.", - "materialized_postgresql": "Creates a ClickHouse database with tables from PostgreSQL database. Firstly, database with engine MaterializedPostgreSQL creates a snapshot of PostgreSQL database and loads required tables. Required tables can include any subset of tables from any subset of schemas from specified database. Along with the snapshot database engine acquires LSN and once initial dump of tables is performed - it starts pulling updates from WAL. After database is created, newly added tables to PostgreSQL database are not automatically added to replication. They have to be added manually with ATTACH TABLE db.table query." + "materialized_postgresql": "Creates a ClickHouse database with tables from PostgreSQL database. Firstly, database with engine MaterializedPostgreSQL creates a snapshot of PostgreSQL database and loads required tables. Required tables can include any subset of tables from any subset of schemas from specified database. Along with the snapshot database engine acquires LSN and once initial dump of tables is performed - it starts pulling updates from WAL. After database is created, newly added tables to PostgreSQL database are not automatically added to replication. They have to be added manually with ATTACH TABLE db.table query.", + "postgresql": "Allows to connect to databases on a remote PostgreSQL server. Supports read and write operations (SELECT and INSERT queries) to exchange data between ClickHouse and PostgreSQL." }, "table": { "log": "Lightweight engines with minimum functionality. They’re the most effective when you need to quickly write many small tables (up to approximately 1 million rows) and read them later as a whole.", @@ -239,7 +241,8 @@ "sqlite": "The engine allows to import and export data to SQLite and supports queries to SQLite tables directly from ClickHouse.", "odbc": "Allows ClickHouse to connect to external databases via ODBC", "mongodb": "MongoDB engine is read-only table engine which allows to read data (SELECT queries) from remote MongoDB collection. Engine supports only non-nested data types. INSERT queries are not supported.", - "default": "Default table engine" + "default": "Default table engine", + "mysql": "The MySQL engine allows you to perform SELECT and INSERT queries on data that is stored on a remote MySQL server." }, "property": { "timeSeconds": "Retention time in RAM (unit per second)", @@ -282,7 +285,9 @@ "presto": "Integrate Presto data sources", "mysql": "Integrate MySQL data sources", "postgresql": "Integrate PostgreSQL data sources", - "druid": "Integrate Druid data sources" + "druid": "Integrate Druid data sources", + "elasticsearch": "Integrate ElasticSearch data sources", + "hologres": "Integrate Alibaba Hologres data sources" } }, "alert": { @@ -308,5 +313,15 @@ }, "formatter": { "migrate_data": "Need to migrate {0} data, please confirm whether to migrate?" + }, + "engine": { + "table": { + "hive": { + "description": "The Hive engine allows you to perform SELECT quries on HDFS Hive table.", + "uri": "Hive Metastore address, egg: thrift://host:port", + "database": "Remote database name", + "table": "Remote table name" + } + } } } diff --git a/src/renderer/assets/i18n/zh.json b/src/renderer/assets/i18n/zh.json index 972a6dbc..bfb905b9 100644 --- a/src/renderer/assets/i18n/zh.json +++ b/src/renderer/assets/i18n/zh.json @@ -16,7 +16,6 @@ "action": "操作", "delete": "删除", "edit": "修改", - "name": "名称", "query": "查询", "execute": "执行", "history": "历史", @@ -24,9 +23,7 @@ "cancel": "取消", "select": "选择", "quick": "Quick", - "result": "结果", "editor": "编辑器", - "id": "ID", "server": "服务", "state": "状态", "time": "时间", @@ -74,7 +71,6 @@ "atomic": "Atomic", "expiration": "过期", "lazy": "Lazy", - "mysql": "MySQL", "materialized_mysql": "MaterializeMySQL", "github": "GitHub", "drop": "Drop", @@ -138,7 +134,6 @@ "remove": "移除", "status": "状态", "success": "成功", - "description": "描述", "comment": "评论", "snippet": "片段", "id": "标记", @@ -170,7 +165,13 @@ "location": "位置", "failed": "失败", "druid": "Druid", - "materialized_postgresql": "MaterializePostgreSQL" + "materialized_postgresql": "MaterializePostgreSQL", + "elasticsearch": "ElasticSearch", + "character": "字符集", + "collation": "排序规则", + "hologres": "Hologres", + "accesskey_id": "AccessKey ID", + "accesskey_secret": "AccessKey Secret" }, "language": { "english": "英语", @@ -229,7 +230,8 @@ "lazy": "Keeps tables in RAM only expiration_time_in_seconds seconds after last access. Can be used only with *Log tables.", "mysql": "Allows to connect to databases on a remote MySQL server and perform INSERT and SELECT queries to exchange data between ClickHouse and MySQL.", "materialized_mysql": "Creates ClickHouse database with all the tables existing in MySQL, and all the data in those tables. ClickHouse server works as MySQL replica. It reads binlog and performs DDL and DML queries.", - "materialized_postgresql": "Creates a ClickHouse database with tables from PostgreSQL database. Firstly, database with engine MaterializedPostgreSQL creates a snapshot of PostgreSQL database and loads required tables. Required tables can include any subset of tables from any subset of schemas from specified database. Along with the snapshot database engine acquires LSN and once initial dump of tables is performed - it starts pulling updates from WAL. After database is created, newly added tables to PostgreSQL database are not automatically added to replication. They have to be added manually with ATTACH TABLE db.table query." + "materialized_postgresql": "Creates a ClickHouse database with tables from PostgreSQL database. Firstly, database with engine MaterializedPostgreSQL creates a snapshot of PostgreSQL database and loads required tables. Required tables can include any subset of tables from any subset of schemas from specified database. Along with the snapshot database engine acquires LSN and once initial dump of tables is performed - it starts pulling updates from WAL. After database is created, newly added tables to PostgreSQL database are not automatically added to replication. They have to be added manually with ATTACH TABLE db.table query.", + "postgresql": "Allows to connect to databases on a remote PostgreSQL server. Supports read and write operations (SELECT and INSERT queries) to exchange data between ClickHouse and PostgreSQL." }, "table": { "log": "轻量级引擎。当您需要快速地构建多个小表(最多100万行),然后将它们作为一个整体读取时,它们是最有效的。", @@ -240,7 +242,8 @@ "sqlite": "The engine allows to import and export data to SQLite and supports queries to SQLite tables directly from ClickHouse.", "odbc": "Allows ClickHouse to connect to external databases via ODBC", "mongodb": "MongoDB engine is read-only table engine which allows to read data (SELECT queries) from remote MongoDB collection. Engine supports only non-nested data types. INSERT queries are not supported.", - "default": "默认表引擎" + "default": "默认表引擎", + "mysql": "The MySQL engine allows you to perform SELECT and INSERT queries on data that is stored on a remote MySQL server." }, "property": { "timeSeconds": "Retention time in RAM (unit per second)", @@ -283,7 +286,9 @@ "presto": "整合Presto数据源", "mysql": "整合MySQL数据源", "postgresql": "整合PostgreSQL数据源", - "druid": "整合Druid数据源" + "druid": "整合Druid数据源", + "elasticsearch": "整合ElasticSearch数据源", + "hologres": "整合Alibaba Hologres数据源" } }, "alert": { @@ -309,5 +314,15 @@ }, "formatter": { "migrate_data": "需要迁移{0}条数据, 请确认是否迁移?" + }, + "engine": { + "table": { + "hive": { + "description": "The Hive engine allows you to perform SELECT quries on HDFS Hive table.", + "uri": "Hive Metastore address, egg: thrift://host:port", + "database": "Remote database name", + "table": "Remote table name" + } + } } } diff --git a/src/renderer/assets/icon/source/ElasticSearch.svg b/src/renderer/assets/icon/source/ElasticSearch.svg new file mode 100644 index 00000000..b95507cd --- /dev/null +++ b/src/renderer/assets/icon/source/ElasticSearch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/icon/source/Hologres.svg b/src/renderer/assets/icon/source/Hologres.svg new file mode 100644 index 00000000..0bf2084c --- /dev/null +++ b/src/renderer/assets/icon/source/Hologres.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/components/database/basic/database.basic.component.html b/src/renderer/components/database/basic/database.basic.component.html index 5e9643e6..76e230fe 100644 --- a/src/renderer/components/database/basic/database.basic.component.html +++ b/src/renderer/components/database/basic/database.basic.component.html @@ -41,27 +41,65 @@
    - - - {{'common.engine'|translate}} - - {{configure.type}} - - - - {{'common.name'|translate}} - - - - - - - - - + + + + + + {{'common.basic'|translate}} + + + {{'common.engine'|translate}} + + {{configure.type}} + + + + {{'common.name'|translate}} + + + + + + + + + + + + + + + {{'common.character'|translate}} & {{'common.collation'|translate}} + + + {{'common.character'|translate}} + + + + + + + + {{'common.collation'|translate}} + + + + + + + +
    diff --git a/src/renderer/components/database/basic/database.basic.component.ts b/src/renderer/components/database/basic/database.basic.component.ts index c8c28086..f1adf36f 100644 --- a/src/renderer/components/database/basic/database.basic.component.ts +++ b/src/renderer/components/database/basic/database.basic.component.ts @@ -13,6 +13,7 @@ import * as cloneDeep from 'lodash/cloneDeep'; import { PropertyModel } from '@renderer/model/property.model'; import { NzTreeNode } from 'ng-zorro-antd/core/tree/nz-tree-base-node'; import { MenuModel } from '@renderer/model/menu.model'; +import { CollationService } from "@renderer/services/management/collation.service"; @Component({ selector: 'app-component-database', @@ -35,9 +36,15 @@ export class DatabaseBasicComponent extends BaseComponent implements AfterViewIn configure: DatabaseModel; databaseType = DatabaseEnum; properties: PropertyModel[]; + collationConfigure = { + elements: [], + characters: [], + collations: [] + }; constructor(private dataSourceService: DatasourceService, private metadataService: MetadataService, + private collationService: CollationService, private messageService: NzMessageService) { super(); this.configure = new DatabaseModel(); @@ -97,19 +104,53 @@ export class DatabaseBasicComponent extends BaseComponent implements AfterViewIn this.handlerValidate(); } - async handlerComplete() { - const request = new RequestModel(); - request.config = await this.dataSourceService.getByAliasAsync(this.config.value); - this.metadataService.createDatabase(request, this.configure).then(response => { - if (response.status) { - this.messageService.success(response.message); - this.config.status = true; - this.config.menu = this.menu; - this.config.currentNode = this.node; - this.emitter.emit(this.config); - } else { - this.messageService.error(response.message); - } - }); + handlerComplete() { + this.dataSourceService.getByAliasAsync(this.config.value) + .then(dataSource => { + const request = new RequestModel(); + request.config = dataSource; + this.metadataService.createDatabase(request, this.configure) + .then(response => { + if (response.status) { + this.messageService.success(response.message); + this.config.status = true; + this.config.menu = this.menu; + this.config.currentNode = this.node; + this.emitter.emit(this.config); + } else { + this.messageService.error(response.message); + } + }); + }); + + } + + handlerLoadCharacterAndCollation() { + if (this.collationConfigure.characters.length <= 0) { + this.dataSourceService.getByAliasAsync(this.config.value) + .then(dataSource => { + const request = new RequestModel(); + request.config = dataSource; + this.collationService.getCharacterAndCollation(request) + .then(response => { + response.data.columns.forEach(v => { + const collation = { + name: v['name'], + values: v['values'].split(',') + }; + this.collationConfigure.characters.push(v['name']); + this.collationConfigure.elements.push(collation); + }); + }) + }); + } + } + + handlerChangeCharacter(value: string) { + this.configure.characterAndCollationConfigure.collationConfigure.value = null; + if (this.configure.characterAndCollationConfigure.collationConfigure.enable) { + this.collationConfigure.collations = this.collationConfigure.elements + .filter(item => item.name === value)[0].values; + } } } diff --git a/src/renderer/components/database/rename/database.rename.component.ts b/src/renderer/components/database/rename/database.rename.component.ts index 24a5b4cb..b9c632f1 100644 --- a/src/renderer/components/database/rename/database.rename.component.ts +++ b/src/renderer/components/database/rename/database.rename.component.ts @@ -6,6 +6,7 @@ import { DatasourceService } from '@renderer/services/management/datasource.serv import { StringUtils } from '@renderer/utils/string.utils'; import { NzMessageService } from 'ng-zorro-antd/message'; import { DatabaseService } from '@renderer/services/management/database.service'; +import { DatabaseEnum } from "@renderer/enum/database.enum"; @Component({ selector: 'app-component-rename-database', @@ -37,14 +38,16 @@ export class DatabaseRenameComponent extends BaseComponent implements AfterViewI this.checkStatus = true; const request = new RequestModel(); request.config = await this.dataSourceService.getByAliasAsync(this.config.value); - this.databaseService.getDatabase(request, this.value) - .then(response => { - if (response.status) { - this.checkStatus = response.data?.columns[0]?.isSupport; - } else { - this.messageService.error(response.message); - } - }); + if (request.config.type === DatabaseEnum.clickhosue) { + this.databaseService.getDatabase(request, this.value) + .then(response => { + if (response.status) { + this.checkStatus = response.data?.columns[0]?.isSupport; + } else { + this.messageService.error(response.message); + } + }); + } } handlerValidate() { @@ -60,15 +63,15 @@ export class DatabaseRenameComponent extends BaseComponent implements AfterViewI const request = new RequestModel(); request.config = await this.dataSourceService.getByAliasAsync(this.config.value); this.databaseService.rename(request, this.value, this.inputValue) - .then(response => { - if (response.status) { - this.messageService.success(response.message); - this.config.status = true; - this.emitter.emit(this.config); - } else { - this.messageService.error(response.message); - } - this.loading.button = false; - }); + .then(response => { + if (response.status) { + this.messageService.success(response.message); + this.config.status = true; + this.emitter.emit(this.config); + } else { + this.messageService.error(response.message); + } + this.loading.button = false; + }); } } diff --git a/src/renderer/components/datasource/clickhouse/datasource.clickhouse.component.html b/src/renderer/components/datasource/clickhouse/datasource.clickhouse.component.html index a503d06e..139b9c86 100644 --- a/src/renderer/components/datasource/clickhouse/datasource.clickhouse.component.html +++ b/src/renderer/components/datasource/clickhouse/datasource.clickhouse.component.html @@ -23,6 +23,7 @@ + diff --git a/src/renderer/components/datasource/common/datasource.common.component.html b/src/renderer/components/datasource/common/datasource.common.component.html index e358d616..6452f5f4 100644 --- a/src/renderer/components/datasource/common/datasource.common.component.html +++ b/src/renderer/components/datasource/common/datasource.common.component.html @@ -13,3 +13,9 @@ + + + + diff --git a/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.html b/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.html new file mode 100644 index 00000000..9ee8651c --- /dev/null +++ b/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.html @@ -0,0 +1,41 @@ +
    + + +
    +
    +
    +
    + + + {{'common.alias'|translate}} + + + + + + + + + {{'common.host'| translate}} + + + + + + + + {{'common.port'| translate}} + + + + + + +
    +
    +
    +
    +
    diff --git a/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.ts b/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.ts new file mode 100644 index 00000000..816f8ba1 --- /dev/null +++ b/src/renderer/components/datasource/elasticsearch/datasource.elasticsearch.component.ts @@ -0,0 +1,45 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { BaseComponent } from '@renderer/app/base.component'; +import { DatasourceModel } from "@renderer/model/datasource.model"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; + +@Component({ + selector: 'app-component-datasource-elasticsearch', + templateUrl: './datasource.elasticsearch.component.html' +}) +export class DatasourceElasticSearchComponent extends BaseComponent { + @Input() + configure: DatasourceModel; + @Output() + emitterValue = new EventEmitter(); + validateForm!: FormGroup; + + constructor(private formBuilder: FormBuilder) { + super(); + this.validateForm = this.formBuilder.group({ + alias: [null, [Validators.required]], + host: [null, [Validators.required]], + port: [null, [Validators.required]] + }); + } + + handlerValidate() { + if (this.validateForm.valid) { + this.configure.validate = true; + this.configure.url = '_xpack/sql'; + this.emitterValue.emit(this.configure); + } else { + this.configure.validate = false; + Object.values(this.validateForm.controls).forEach(control => { + if (control.invalid) { + control.markAsDirty(); + control.updateValueAndValidity({onlySelf: true}); + } + }); + } + } + + handlerEmitterValue() { + this.handlerValidate(); + } +} diff --git a/src/renderer/components/datasource/hologres/datasource.hologres.component.html b/src/renderer/components/datasource/hologres/datasource.hologres.component.html new file mode 100644 index 00000000..24bb6482 --- /dev/null +++ b/src/renderer/components/datasource/hologres/datasource.hologres.component.html @@ -0,0 +1,85 @@ + diff --git a/src/renderer/components/datasource/hologres/datasource.hologres.component.ts b/src/renderer/components/datasource/hologres/datasource.hologres.component.ts new file mode 100644 index 00000000..e5fe437b --- /dev/null +++ b/src/renderer/components/datasource/hologres/datasource.hologres.component.ts @@ -0,0 +1,63 @@ +import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; +import { BaseComponent } from '@renderer/app/base.component'; +import { DatasourceModel } from "@renderer/model/datasource.model"; +import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; + +@Component({ + selector: 'app-component-datasource-hologres', + templateUrl: './datasource.hologres.component.html' +}) +export class DatasourceHologresComponent extends BaseComponent implements AfterViewInit { + @Input() + configure: DatasourceModel; + @Output() + emitterValue = new EventEmitter(); + validateForm!: FormGroup; + + constructor(private formBuilder: FormBuilder) { + super(); + this.validateForm = this.formBuilder.group({ + alias: [null, [Validators.required]], + host: [null, [Validators.required]], + port: [null, [Validators.required]], + authorization: [null, [Validators.required]], + catalog: [null, []], + database: [null, []] + }); + } + + ngAfterViewInit(): void { + setTimeout(() => { + if (this.configure?.port === 8123) { + this.configure.port = 5443; + } + this.handlerAuthorization(); + }, 0); + } + + handlerValidate() { + if (this.validateForm.valid) { + this.configure.validate = true; + this.emitterValue.emit(this.configure); + } else { + this.configure.validate = false; + Object.values(this.validateForm.controls).forEach(control => { + if (control.invalid) { + control.markAsDirty(); + control.updateValueAndValidity({onlySelf: true}); + } + }); + } + } + + handlerAuthorization() { + if (this.configure.authorization) { + this.validateForm.addControl('username', new FormControl(null, [Validators.required])); + this.validateForm.addControl('password', new FormControl(null, [Validators.required])); + } else { + this.validateForm.removeControl('username'); + this.validateForm.removeControl('password'); + } + this.handlerValidate(); + } +} diff --git a/src/renderer/components/query/quick/quick.query.component.ts b/src/renderer/components/query/quick/quick.query.component.ts index 639488a0..6748546b 100644 --- a/src/renderer/components/query/quick/quick.query.component.ts +++ b/src/renderer/components/query/quick/quick.query.component.ts @@ -47,7 +47,12 @@ export class QuickQueryComponent extends BaseComponent { super(); this.quickCommands = this.queryQuickService.getQuickAll(); this.dataSourceService.getAll().then(response => { - this.dataSourceSet = response; + this.dataSourceSet = response.map(item => { + if (item.type === DatabaseEnum.elasticsearch) { + item.status = false; + } + return item; + }); }); } diff --git a/src/renderer/components/table/basic/basic.table.component.html b/src/renderer/components/table/basic/basic.table.component.html index 3a469585..9498077b 100644 --- a/src/renderer/components/table/basic/basic.table.component.html +++ b/src/renderer/components/table/basic/basic.table.component.html @@ -2,13 +2,14 @@ - - - - - - + + + + {{row[header.key]}} + + diff --git a/src/renderer/components/table/basic/basic.table.component.ts b/src/renderer/components/table/basic/basic.table.component.ts index 0305c9bd..197ccd13 100644 --- a/src/renderer/components/table/basic/basic.table.component.ts +++ b/src/renderer/components/table/basic/basic.table.component.ts @@ -5,6 +5,8 @@ import { ExportToCsv } from 'export-to-csv'; import { Md5 } from 'ts-md5'; import { TableExportModel } from "@renderer/components/table/basic/table.export.model"; import { StringUtils } from "@renderer/utils/string.utils"; +import { TranslateService } from "@ngx-translate/core"; +import { NzModalService } from "ng-zorro-antd/modal"; @Component({ selector: 'app-component-basic-table', @@ -18,7 +20,8 @@ export class BasicTableComponent extends BaseComponent implements AfterViewInit public id: string; exportInfo: TableExportModel; - constructor() { + constructor(private translateService: TranslateService, + private modalService: NzModalService) { super(); this.configuration = {...DefaultConfig}; this.configuration.horizontalScroll = true; @@ -68,4 +71,14 @@ export class BasicTableComponent extends BaseComponent implements AfterViewInit const csvExporter = new ExportToCsv(options); csvExporter.generateCsv(this.value.columns); } + + handlerShowMoreEllipsis(value: any): void { + this.modalService.info({ + nzWidth: '80%', + nzKeyboard: false, + nzMaskClosable: false, + nzOkText: this.translateService.instant('common.ok'), + nzContent: value.toString() + }); + } } diff --git a/src/renderer/components/table/create/table.create.component.html b/src/renderer/components/table/create/table.create.component.html index 27bde476..434fe439 100644 --- a/src/renderer/components/table/create/table.create.component.html +++ b/src/renderer/components/table/create/table.create.component.html @@ -37,78 +37,119 @@
    -
    - - - {{'common.engine'|translate}} - - {{configure.type}} - - - - {{'common.name'|translate}} - - - - - - -
    - - -
    -
    - +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + -
    -
    - -
    -
    - - - -
    -
    - -
    -
    - - -
    -
    - - - - - - - - - -
    - - - -
    - - - + + + + + + + + + {{'common.property'|translate}} + + + + + + + + + {{'common.property'|translate}} + - - + + + + + + {{'common.partition'|translate}} + + + {{'common.column'|translate}} + + + + + + + + diff --git a/src/renderer/components/table/create/table.create.component.ts b/src/renderer/components/table/create/table.create.component.ts index 39360acb..17c74f85 100644 --- a/src/renderer/components/table/create/table.create.component.ts +++ b/src/renderer/components/table/create/table.create.component.ts @@ -28,6 +28,11 @@ export class CreateTableComponent extends BaseComponent implements AfterViewInit selectValue: string; columns: ColumnModel[] = new Array(); columnTypes: string[] = new Array(); + validated = { + basic: false, + column: false, + property: false + } constructor(private tableService: TableService, private dataSourceService: DatasourceService, @@ -71,6 +76,30 @@ export class CreateTableComponent extends BaseComponent implements AfterViewInit this.configure = cloneDeep(value); } + handlerValidateStep(step: string) { + switch (step) { + case 'Basic': + if (StringUtils.isNotEmpty(this.configure.targetName)) { + this.validated.basic = true; + } else { + this.validated.basic = false; + } + break; + case 'Column': + const emptyCount = this.columns.filter(column => StringUtils.isEmpty(column.name)).length; + if (emptyCount === 0) { + this.validated.column = true; + } else { + this.validated.column = false; + } + break; + case 'Property': + this.validated.property = this.configure.validate; + break; + } + this.handlerValidate(); + } + handlerValidate() { let flag; if (this.configure.validate != undefined) { @@ -107,7 +136,7 @@ export class CreateTableComponent extends BaseComponent implements AfterViewInit } else { this.configure.optionalProperties = $event.properties; } - this.handlerValidate(); + this.handlerValidateStep('Property'); } handlerPrevious(): void { @@ -122,6 +151,7 @@ export class CreateTableComponent extends BaseComponent implements AfterViewInit const request = new RequestModel(); request.config = await this.dataSourceService.getByAliasAsync(this.config.value); this.configure.database = this.value; + request.config.database = this.value; this.tableService.createTable(request, this.configure, this.columns).then(response => { if (response.status) { this.messageService.success(response.message); diff --git a/src/renderer/config/base.config.ts b/src/renderer/config/base.config.ts index d87d7077..206c0888 100644 --- a/src/renderer/config/base.config.ts +++ b/src/renderer/config/base.config.ts @@ -25,4 +25,7 @@ export interface BaseConfig { stopProcessor: string; showCreateDatabase: string; showTableWithSize: string; + getCharacterAndCollation: string; + // database + databaseRename: string; } diff --git a/src/renderer/config/clickhouse.config.ts b/src/renderer/config/clickhouse.config.ts new file mode 100644 index 00000000..0fbbacca --- /dev/null +++ b/src/renderer/config/clickhouse.config.ts @@ -0,0 +1,22 @@ +import { ConfigInterface } from "@renderer/interfaces/config.interface"; +import { Injectable } from "@angular/core"; +import { Factory } from "@renderer/factory"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { ClickhouseConfig } from "@renderer/config/plugin/clickhouse.config"; + +@Injectable() +export class ClickHouseConfig implements ConfigInterface { + private readonly config: ClickhouseConfig; + + constructor() { + this.config = Factory.create(ClickhouseConfig); + } + + getName(): DatabaseEnum { + return DatabaseEnum.clickhosue; + } + + getStatement(key: string): string { + return this.config[key]; + } +} diff --git a/src/renderer/config/database.config.ts b/src/renderer/config/database.config.ts index ed1f6cb5..b7af8925 100644 --- a/src/renderer/config/database.config.ts +++ b/src/renderer/config/database.config.ts @@ -4,6 +4,8 @@ import { DatabaseModel } from '@renderer/model/database.model'; import { StringUtils } from '@renderer/utils/string.utils'; import { TranslateUtils } from '@renderer/utils/translate.utils'; import { PropertyModel } from '@renderer/model/property.model'; +import { DefaultEngine } from "@renderer/config/engine/database/mysql/engine.database.mysql.default.config"; +import { PostgreSQLDatabaseEngine } from "@renderer/config/engine/database/engine.database.postgresql"; @Injectable() export class DatabaseConfig { @@ -21,8 +23,12 @@ export class DatabaseConfig { TranslateUtils.getValue('tooltip.database.default'), DatabaseEnum.none, null); - defaultEngine.supportedSource.push(DatabaseEnum.presto, DatabaseEnum.trino, DatabaseEnum.mysql, DatabaseEnum.postgresql); + defaultEngine.supportedSource.push(DatabaseEnum.presto, DatabaseEnum.trino, DatabaseEnum.postgresql); defaultEngines.push(defaultEngine); + + // MySQL + defaultEngines.push(DefaultEngine); + defaultEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.atomic'), TranslateUtils.getValue('tooltip.database.atomic'), DatabaseEnum.atomic, @@ -31,6 +37,7 @@ export class DatabaseConfig { TranslateUtils.getValue('tooltip.database.lazy'), DatabaseEnum.lazy, null)); + // mysql const properties = new Array(); properties.push(PropertyModel.builder('host', @@ -57,6 +64,10 @@ export class DatabaseConfig { TranslateUtils.getValue('tooltip.database.mysql'), DatabaseEnum.mysql, properties)); + + // PostgreSQL + defaultEngines.push(PostgreSQLDatabaseEngine); + basicDatabase.engines = defaultEngines; databaseEngines.push(basicDatabase); /** diff --git a/src/renderer/config/engine/database/engine.database.postgresql.ts b/src/renderer/config/engine/database/engine.database.postgresql.ts new file mode 100644 index 00000000..ac273b26 --- /dev/null +++ b/src/renderer/config/engine/database/engine.database.postgresql.ts @@ -0,0 +1,36 @@ +import { DatabaseModel } from "@renderer/model/database.model"; +import { TranslateUtils } from "@renderer/utils/translate.utils"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { PropertyModel } from "@renderer/model/property.model"; + +const properties = new Array(); +properties.push(PropertyModel.builder('host', + TranslateUtils.getValue('common.host'), + TranslateUtils.getValue('placeholder.host'), + TranslateUtils.getValue('tooltip.property.host'))); +properties.push(PropertyModel.builder('port', + TranslateUtils.getValue('common.port'), + TranslateUtils.getValue('placeholder.port'), + TranslateUtils.getValue('tooltip.property.port'))); +properties.push(PropertyModel.builder('database', + TranslateUtils.getValue('common.database'), + TranslateUtils.getValue('placeholder.database'), + TranslateUtils.getValue('tooltip.property.database'))); +properties.push(PropertyModel.builder('username', + TranslateUtils.getValue('common.username'), + TranslateUtils.getValue('placeholder.username'), + TranslateUtils.getValue('tooltip.property.username'))); +properties.push(PropertyModel.builder('password', + TranslateUtils.getValue('common.password'), + TranslateUtils.getValue('placeholder.password'), + TranslateUtils.getValue('tooltip.property.password'))); + +const PostgreSQLDatabaseEngine = DatabaseModel.builder(TranslateUtils.getValue('common.postgresql'), + TranslateUtils.getValue('tooltip.database.postgresql'), + DatabaseEnum.postgresql, + properties); +PostgreSQLDatabaseEngine.supportedSource = [DatabaseEnum.clickhosue]; + +export { + PostgreSQLDatabaseEngine +} diff --git a/src/renderer/config/engine/database/mysql/engine.database.mysql.default.config.ts b/src/renderer/config/engine/database/mysql/engine.database.mysql.default.config.ts new file mode 100644 index 00000000..06673ea1 --- /dev/null +++ b/src/renderer/config/engine/database/mysql/engine.database.mysql.default.config.ts @@ -0,0 +1,17 @@ +import { DatabaseModel } from "@renderer/model/database.model"; +import { TranslateUtils } from "@renderer/utils/translate.utils"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; + +const DefaultEngine = DatabaseModel.builder(TranslateUtils.getValue('common.default'), + TranslateUtils.getValue('tooltip.database.default'), + DatabaseEnum.none, + null); +DefaultEngine.supportedSource = [DatabaseEnum.mysql]; + +DefaultEngine.characterAndCollationConfigure.enable = true; +DefaultEngine.characterAndCollationConfigure.characterSetConfigure.enable = true; +DefaultEngine.characterAndCollationConfigure.collationConfigure.enable = true; + +export { + DefaultEngine +} diff --git a/src/renderer/config/engine/table/engine.table.hive.config.ts b/src/renderer/config/engine/table/engine.table.hive.config.ts new file mode 100644 index 00000000..91c933c4 --- /dev/null +++ b/src/renderer/config/engine/table/engine.table.hive.config.ts @@ -0,0 +1,37 @@ +import { PropertyModel } from "@renderer/model/property.model"; +import { TranslateUtils } from "@renderer/utils/translate.utils"; +import { DatabaseModel } from "@renderer/model/database.model"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { PropertyEnum } from "@renderer/enum/property.enum"; + +const hiveProperties = new Array(); +hiveProperties.push(PropertyModel.builder('uri', + TranslateUtils.getValue('common.uri'), + TranslateUtils.getValue('engine.table.hive.uri'), + TranslateUtils.getValue('engine.table.hive.uri'), + null, + false, + true)); +hiveProperties.push(PropertyModel.builder('database', + TranslateUtils.getValue('common.database'), + TranslateUtils.getValue('engine.table.hive.database'), + TranslateUtils.getValue('engine.table.hive.database'), + null, + false)); +hiveProperties.push(PropertyModel.builder('table', + TranslateUtils.getValue('common.table'), + TranslateUtils.getValue('engine.table.hive.table'), + TranslateUtils.getValue('engine.table.hive.table'), + null, + false)); +const HiveTableEngine = DatabaseModel.builder(DatabaseEnum.hive.toString(), + TranslateUtils.getValue('engine.table.hive.description'), + DatabaseEnum.hive, + hiveProperties, + false, + PropertyEnum.name); + +HiveTableEngine.partitionConfigure.enable = true; +HiveTableEngine.supportedSource = [DatabaseEnum.clickhosue]; + +export { HiveTableEngine }; diff --git a/src/renderer/config/mysql.config.ts b/src/renderer/config/mysql.config.ts new file mode 100644 index 00000000..f7b8b93a --- /dev/null +++ b/src/renderer/config/mysql.config.ts @@ -0,0 +1,22 @@ +import { ConfigInterface } from "@renderer/interfaces/config.interface"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { Injectable } from "@angular/core"; +import { Factory } from "@renderer/factory"; +import { MySQLConfig } from "@renderer/config/plugin/mysql.config"; + +@Injectable() +export class MysqlConfig implements ConfigInterface { + private readonly config: MySQLConfig; + + constructor() { + this.config = Factory.create(MySQLConfig); + } + + getName(): DatabaseEnum { + return DatabaseEnum.mysql; + } + + getStatement(key: string): string { + return this.config[key]; + } +} diff --git a/src/renderer/config/operation.config.ts b/src/renderer/config/operation.config.ts index 9e198e0c..f347f36b 100644 --- a/src/renderer/config/operation.config.ts +++ b/src/renderer/config/operation.config.ts @@ -51,7 +51,11 @@ export class OperationConfig { actions: [OperationEnum.structure], supportedSource: [DatabaseEnum.clickhosue, DatabaseEnum.mysql] }, - {type: TypeEnum.database, actions: [OperationEnum.rename], supportedSource: [DatabaseEnum.clickhosue]} + { + type: TypeEnum.database, + actions: [OperationEnum.rename], + supportedSource: [DatabaseEnum.clickhosue, DatabaseEnum.postgresql] + } ]; opertions.push(database); const table = new OperationModel(); diff --git a/src/renderer/config/plugin/clickhouse.config.ts b/src/renderer/config/plugin/clickhouse.config.ts index 14caed3f..6746f7b8 100644 --- a/src/renderer/config/plugin/clickhouse.config.ts +++ b/src/renderer/config/plugin/clickhouse.config.ts @@ -171,4 +171,6 @@ ALTER TABLE {0} RENAME COLUMN {1} TO {2} columnAddComment = ` ALTER TABLE {0} COMMENT COLUMN {1} '{2}' `; + getCharacterAndCollation: string; + databaseRename = 'RENAME DATABASE `{0}` TO `{1}`'; } diff --git a/src/renderer/config/plugin/druid.config.ts b/src/renderer/config/plugin/druid.config.ts index 2478ef16..95d1b077 100644 --- a/src/renderer/config/plugin/druid.config.ts +++ b/src/renderer/config/plugin/druid.config.ts @@ -8,7 +8,10 @@ export class DruidConfig implements BaseConfig { connectionFetchAll: string; databaseCreate: string; databaseDiskUsedRatio: string; - databaseFetchAll: string; + databaseFetchAll = ` + SELECT SCHEMA_NAME AS name + FROM INFORMATION_SCHEMA.SCHEMATA + `; databaseItems: string; databaseItemsFilterFuzzy: string; databaseItemsFilterPrecise: string; @@ -21,10 +24,16 @@ export class DruidConfig implements BaseConfig { slowQueryFetchAll: string; stopProcessor: string; tableDiskUsedRatio: string; - tableFetchAll: string; + tableFetchAll = ` + SELECT TABLE_NAME AS name + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = '{0}' + `; tableItems: string; tableItemsFilterFuzzy: string; tableItemsFilterPrecise: string; tableSchemaFetchAll: string; version = `SELECT '-' AS version`; + getCharacterAndCollation: string; + databaseRename: string; } diff --git a/src/renderer/config/plugin/elasticsearch.config.ts b/src/renderer/config/plugin/elasticsearch.config.ts new file mode 100644 index 00000000..792b3822 --- /dev/null +++ b/src/renderer/config/plugin/elasticsearch.config.ts @@ -0,0 +1,32 @@ +import { BaseConfig } from "@renderer/config/base.config"; + +export class ElasticsearchConfig implements BaseConfig { + columnAddComment: string; + columnDiskUsedRatio: string; + columnItems: string; + columnRename: string; + connectionFetchAll: string; + databaseCreate: string; + databaseDiskUsedRatio: string; + databaseFetchAll: string; + databaseItems: string; + databaseItemsFilterFuzzy: string; + databaseItemsFilterPrecise: string; + diskUsedRatio: string; + processesFetchAll: string; + schemaFetchAll: string; + serverInfo: string; + showCreateDatabase: string; + showTableWithSize: string; + slowQueryFetchAll: string; + stopProcessor: string; + tableDiskUsedRatio: string; + tableFetchAll: string; + tableItems: string; + tableItemsFilterFuzzy: string; + tableItemsFilterPrecise: string; + tableSchemaFetchAll: string; + version = `SELECT 1 AS version`; + getCharacterAndCollation: string; + databaseRename: string; +} diff --git a/src/renderer/config/plugin/mysql.config.ts b/src/renderer/config/plugin/mysql.config.ts index ccfa4e97..81b3bf32 100644 --- a/src/renderer/config/plugin/mysql.config.ts +++ b/src/renderer/config/plugin/mysql.config.ts @@ -3,10 +3,9 @@ import { BaseConfig } from "@renderer/config/base.config"; export class MySQLConfig implements BaseConfig { columnDiskUsedRatio = `SELECT '{0}' AS db, '{1}' AS name`; columnItems = ` -SELECT - TABLE_SCHEMA AS "database", TABLE_NAME AS tableName, COLUMN_NAME AS name, +SELECT TABLE_SCHEMA AS "database", TABLE_NAME AS tableName, COLUMN_NAME AS name -- DATA_TYPE AS type - concat(DATA_TYPE, '(', CHARACTER_MAXIMUM_LENGTH, ')') AS type +-- concat(DATA_TYPE, '(', CHARACTER_MAXIMUM_LENGTH, ')') AS type FROM information_schema.columns WHERE table_schema = '{0}' AND table_name = '{1}' GROUP BY COLUMN_NAME @@ -87,9 +86,7 @@ FROM information_schema.tables WHERE table_schema = '{0}' `; tableItems = ` -SELECT - table_schema AS "database", TABLE_NAME AS name, - ENGINE AS value, MAX_DATA_LENGTH AS total_rows +SELECT table_schema AS "database", TABLE_NAME AS name FROM information_schema.tables WHERE table_schema = '{0}' GROUP BY TABLE_NAME @@ -122,4 +119,13 @@ ALTER TABLE {0} CHANGE COLUMN {1} {2} {3} columnAddComment = ` ALTER TABLE {0} CHANGE COLUMN {1} {2} {3} COMMENT '{4}' `; + getCharacterAndCollation = ` + SELECT + CHARACTER_SET_NAME AS 'name', + GROUP_CONCAT(COLLATION_NAME ORDER BY COLLATION_NAME ASC) AS 'values' + FROM information_schema.COLLATIONS + GROUP BY CHARACTER_SET_NAME + ORDER BY CHARACTER_SET_NAME ASC + `; + databaseRename: string; } diff --git a/src/renderer/config/plugin/postgresql.config.ts b/src/renderer/config/plugin/postgresql.config.ts index 2642b77f..4bce7aec 100644 --- a/src/renderer/config/plugin/postgresql.config.ts +++ b/src/renderer/config/plugin/postgresql.config.ts @@ -7,7 +7,7 @@ export class PostgresqlConfig implements BaseConfig { table_schema AS "database", table_name AS tableName, column_name AS name, data_type AS type FROM information_schema.columns - WHERE table_schema = '{0}' AND table_name = '{1}' + WHERE table_name = '{0}' `; connectionFetchAll: string; databaseCreate = `CREATE DATABASE {0}`; @@ -68,9 +68,15 @@ export class PostgresqlConfig implements BaseConfig { AND table_schema = 'public' `; tableItems = ` - SELECT table_schema AS "database", TABLE_NAME AS name +-- SELECT table_schema AS "database", TABLE_NAME AS name +-- FROM information_schema.tables +-- WHERE table_catalog = '{0}' +-- AND table_type = 'BASE TABLE' +-- AND table_schema = 'public' + SELECT table_name AS name FROM information_schema.tables - WHERE table_schema = '{0}' + WHERE table_type = 'BASE TABLE' + AND table_schema = 'public' `; tableItemsFilterFuzzy: string; tableItemsFilterPrecise: string; @@ -89,4 +95,6 @@ export class PostgresqlConfig implements BaseConfig { `; columnRename: string; columnAddComment: string; + getCharacterAndCollation: string; + databaseRename = `ALTER DATABASE "{0}" RENAME TO "{1}"`; } diff --git a/src/renderer/config/plugin/presto.config.ts b/src/renderer/config/plugin/presto.config.ts index 03879422..c4eea542 100644 --- a/src/renderer/config/plugin/presto.config.ts +++ b/src/renderer/config/plugin/presto.config.ts @@ -1,6 +1,7 @@ import { BaseConfig } from "@renderer/config/base.config"; export class PrestoConfig implements BaseConfig { + getCharacterAndCollation: string; version = ` SELECT node_version AS version FROM system.runtime.nodes LIMIT 1 @@ -66,4 +67,5 @@ export class PrestoConfig implements BaseConfig { showTableWithSize: string; columnRename: string; columnAddComment: string; + databaseRename: string; } diff --git a/src/renderer/config/postgresql.config.ts b/src/renderer/config/postgresql.config.ts new file mode 100644 index 00000000..4ab9d369 --- /dev/null +++ b/src/renderer/config/postgresql.config.ts @@ -0,0 +1,21 @@ +import { ConfigInterface } from "@renderer/interfaces/config.interface"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { Injectable } from "@angular/core"; +import { PostgresqlConfig } from "@renderer/config/plugin/postgresql.config"; + +@Injectable() +export class PostgreSQLConfig implements ConfigInterface { + private readonly config: PostgresqlConfig; + + constructor() { + this.config = new PostgresqlConfig(); + } + + getName(): DatabaseEnum { + return DatabaseEnum.postgresql; + } + + getStatement(key: string): string { + return this.config[key]; + } +} diff --git a/src/renderer/config/source.type.config.ts b/src/renderer/config/source.type.config.ts index 422bc298..16b4ef1f 100644 --- a/src/renderer/config/source.type.config.ts +++ b/src/renderer/config/source.type.config.ts @@ -3,6 +3,7 @@ import {DatabaseEnum} from '@renderer/enum/database.enum'; import {DatabaseModel} from '@renderer/model/database.model'; import {StringUtils} from '@renderer/utils/string.utils'; import {TranslateUtils} from '@renderer/utils/translate.utils'; +import { HologresDataSource } from "@renderer/config/source/source.hologres"; @Injectable() export class SourceTypeConfig { @@ -69,6 +70,18 @@ export class SourceTypeConfig { true, null, './renderer/assets/icon/source/Druid.svg')); + // ElasticSearch + experimentalEngines.push(DatabaseModel.builder(TranslateUtils.getValue('common.elasticsearch'), + TranslateUtils.getValue('tooltip.source.elasticsearch'), + DatabaseEnum.elasticsearch, + null, + true, + null, + './renderer/assets/icon/source/ElasticSearch.svg')); + + // Hologres + experimentalEngines.push(HologresDataSource); + experimentalType.engines = experimentalEngines; typeEngines.push(basicType, experimentalType); diff --git a/src/renderer/config/source/source.hologres.ts b/src/renderer/config/source/source.hologres.ts new file mode 100644 index 00000000..9fd4d762 --- /dev/null +++ b/src/renderer/config/source/source.hologres.ts @@ -0,0 +1,13 @@ +import { DatabaseModel } from "@renderer/model/database.model"; +import { TranslateUtils } from "@renderer/utils/translate.utils"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; + +const HologresDataSource = DatabaseModel.builder(TranslateUtils.getValue('common.hologres'), + TranslateUtils.getValue('tooltip.source.hologres'), + DatabaseEnum.hologres, + null, + true, + null, + './renderer/assets/icon/source/Hologres.svg'); + +export { HologresDataSource }; diff --git a/src/renderer/config/table.config.ts b/src/renderer/config/table.config.ts index d610777c..edb23407 100644 --- a/src/renderer/config/table.config.ts +++ b/src/renderer/config/table.config.ts @@ -5,6 +5,7 @@ import { StringUtils } from '@renderer/utils/string.utils'; import { TranslateUtils } from '@renderer/utils/translate.utils'; import { PropertyModel } from '@renderer/model/property.model'; import { PropertyEnum } from '@renderer/enum/property.enum'; +import { HiveTableEngine } from "@renderer/config/engine/table/engine.table.hive.config"; @Injectable() export class TableConfig { @@ -258,6 +259,53 @@ export class TableConfig { mongodb.supportedSource = [DatabaseEnum.clickhosue]; integrationEngines.push(mongodb); + // MySQL + const mysqlProperties = new Array(); + mysqlProperties.push(PropertyModel.builder('uri', + TranslateUtils.getValue('common.uri'), + TranslateUtils.getValue('placeholder.uri'), + TranslateUtils.getValue('tooltip.property.mongodb.uri'), + null, + false, + true)); + mysqlProperties.push(PropertyModel.builder('database', + TranslateUtils.getValue('common.database'), + TranslateUtils.getValue('placeholder.database'), + TranslateUtils.getValue('tooltip.property.database'), + null, + false)); + mysqlProperties.push(PropertyModel.builder('table', + TranslateUtils.getValue('common.table'), + TranslateUtils.getValue('placeholder.table'), + TranslateUtils.getValue('tooltip.property.table'), + null, + false)); + mysqlProperties.push(PropertyModel.builder('username', + TranslateUtils.getValue('common.username'), + TranslateUtils.getValue('tooltip.property.username'), + TranslateUtils.getValue('tooltip.property.username'), + null, + false, + true)); + mysqlProperties.push(PropertyModel.builder('password', + TranslateUtils.getValue('common.password'), + TranslateUtils.getValue('tooltip.property.password'), + TranslateUtils.getValue('tooltip.property.password'), + null, + false, + true)); + const mysql = DatabaseModel.builder(DatabaseEnum.mysql.toString(), + TranslateUtils.getValue('tooltip.table.mysql'), + DatabaseEnum.mysql, + mysqlProperties, + false, + PropertyEnum.name); + mysql.supportedSource = [DatabaseEnum.clickhosue]; + integrationEngines.push(mysql); + + // Hive + integrationEngines.push(HiveTableEngine); + integrationTable.engines = integrationEngines; tableEngines.push(integrationTable); return tableEngines; diff --git a/src/renderer/enum/database.enum.ts b/src/renderer/enum/database.enum.ts index 900ba4b6..f773e23a 100644 --- a/src/renderer/enum/database.enum.ts +++ b/src/renderer/enum/database.enum.ts @@ -16,11 +16,14 @@ export enum DatabaseEnum { sqlite = 'SQLite', odbc = 'ODBC', mongodb = 'MongoDB', + hive = 'Hive', // DataSource Type clickhosue = 'ClickHouse', trino = 'Trino', presto = 'Presto', postgresql = 'PostgreSQL', - druid = 'Druid' + druid = 'Druid', + elasticsearch = 'ElasticSearch', + hologres = 'Hologres' } diff --git a/src/renderer/factory/config.factory.ts b/src/renderer/factory/config.factory.ts new file mode 100644 index 00000000..c0fddb04 --- /dev/null +++ b/src/renderer/factory/config.factory.ts @@ -0,0 +1,14 @@ +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { Inject, Injectable } from "@angular/core"; +import { ConfigToken } from "@renderer/token/config.token"; +import { ConfigInterface } from "@renderer/interfaces/config.interface"; + +@Injectable() +export class ConfigFactory { + constructor(@Inject(ConfigToken) private configs: ConfigInterface[]) { + } + + createConfig(name: DatabaseEnum) { + return this.configs.filter(item => item.getName() === name)[0]; + } +} diff --git a/src/renderer/interfaces/config.interface.ts b/src/renderer/interfaces/config.interface.ts new file mode 100644 index 00000000..c326b14c --- /dev/null +++ b/src/renderer/interfaces/config.interface.ts @@ -0,0 +1,11 @@ +import { DatabaseEnum } from "@renderer/enum/database.enum"; + +export interface ConfigInterface { + getName(): DatabaseEnum; + + /** + * Get SQL statement + * @param key statement key + */ + getStatement(key: string): string; +} diff --git a/src/renderer/model/database.model.ts b/src/renderer/model/database.model.ts index 5caf8872..2ec67a17 100644 --- a/src/renderer/model/database.model.ts +++ b/src/renderer/model/database.model.ts @@ -30,6 +30,29 @@ export class DatabaseModel { optionalProperties: PropertyModel[]; supportedSource: DatabaseEnum[] = [DatabaseEnum.clickhosue]; + /** + * Optional parameter. Whether zone configuration is supported + */ + partitionConfigure = { + enable: false, + columns: [] + } + + /** + * Database character set and collation rules + */ + characterAndCollationConfigure = { + enable: false, + characterSetConfigure: { + enable: false, + value: null + }, + collationConfigure: { + enable: false, + value: null + } + } + public static builder(name: string, description: string, type: DatabaseEnum, diff --git a/src/renderer/plugin/clickhouse.plugin.ts b/src/renderer/plugin/clickhouse.plugin.ts index 225caced..6c75afce 100644 --- a/src/renderer/plugin/clickhouse.plugin.ts +++ b/src/renderer/plugin/clickhouse.plugin.ts @@ -14,8 +14,9 @@ import { UrlUtils } from "@renderer/utils/url.utils"; export class ClickHousePlugin implements PluginInterface { constructor(private httpService: HttpService, - private sshService: SshService, - private basicService: BasicService) { } + private sshService: SshService, + private basicService: BasicService) { + } getName(): DatabaseEnum { return DatabaseEnum.clickhosue; @@ -29,6 +30,7 @@ export class ClickHousePlugin implements PluginInterface { const configure = request.config; switch (configure.protocol) { case 'HTTP': + case 'HTTPS': return this.httpService.post(UrlUtils.formatUrl(request), sql); case 'SSH': const basicConfig = this.getConfig(); @@ -49,4 +51,4 @@ export class ClickHousePlugin implements PluginInterface { return Promise.reject(new Error('Unsupported protocol')); } } -} \ No newline at end of file +} diff --git a/src/renderer/plugin/elasticsearch.plugin.ts b/src/renderer/plugin/elasticsearch.plugin.ts new file mode 100644 index 00000000..ed5913ee --- /dev/null +++ b/src/renderer/plugin/elasticsearch.plugin.ts @@ -0,0 +1,128 @@ +import { PluginInterface } from "@renderer/interfaces/plugin.interface"; +import { Injectable } from "@angular/core"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { HttpService } from "@renderer/services/http.service"; +import { SshService } from "@renderer/services/ssh.service"; +import { BasicService } from "@renderer/services/system/basic.service"; +import { ResponseDataModel, ResponseModel } from "@renderer/model/response.model"; +import { RequestModel } from "@renderer/model/request.model"; +import { SystemBasicModel } from "@renderer/model/system.model"; +import { timeout, TimeoutError } from 'promise-timeout'; + +const http = require('http'); + +@Injectable() +export class ElasticsearchPlugin implements PluginInterface { + constructor(private httpService: HttpService, + private sshService: SshService, + private basicService: BasicService) { + } + + getName(): DatabaseEnum { + return DatabaseEnum.elasticsearch; + } + + getResponse(configure: RequestModel, sql?: string): Promise { + const network = this.getConfig().network * 1000; + const response = new ResponseModel(); + const start = new Date().getTime(); + const somePromise = new Promise((resolve) => { + const body = {query: sql}; + const options = { + hostname: configure.config.host, + port: configure.config.port, + path: configure.config.url, + method: 'POST', + headers: {'Content-Type': 'application/json'} + } + const req = http.request(options, (response) => { + const bodyArray = []; + response.on('data', (chunk) => { + bodyArray.push(chunk); + }) + .on('error', function (err) { + console.log('HTTP :: ERROR: ' + err); + response.status = false; + response.message = err; + resolve(response); + }) + .on('end', function () { + if (response.statusCode === 200) { + const body = JSON.parse(Buffer.concat(bodyArray).toString()); + const responseData = new ResponseDataModel(); + responseData.headers = body['columns']; + const rows = new Array(); + /** Parse Elasticsearch to return the original data, the original format is + { + "columns": [ + { + "name": "name", + "type": "keyword" + }, + { + "name": "type", + "type": "keyword" + } + ], + "rows": [ + [ + "AVG", + "AGGREGATE" + ] + ] + } + */ + const keys = Object.keys(body['columns']); + body['rows'].forEach(row => { + const column = {}; + keys.forEach(index => { + const title = responseData.headers[index].name; + column[title] = row[index]; + }); + rows.push(column); + }); + responseData.columns = rows; + responseData.rows = responseData.columns.length; + const end = new Date().getTime(); + const statistics = { + elapsed: end - start + }; + responseData.statistics = statistics; + response.data = responseData; + response.status = true; + } else { + response.status = false; + const errorJson = JSON.parse(Buffer.concat(bodyArray).toString()); + response.message = errorJson['error']['reason']; + } + resolve(response); + }); + }) + + req.on('error', error => { + const response = new ResponseModel(); + response.status = false; + response.message = error; + resolve(response); + }) + req.end(JSON.stringify(body)); + }); + return timeout(somePromise, network) + .then((thing) => { + return thing; + }) + .catch((err) => { + response.status = false; + if (err instanceof TimeoutError) { + response.message = `Promise timed out after ${this.getConfig().network} ms`; + } else { + response.message = err; + } + return response; + }); + } + + private getConfig(): SystemBasicModel { + return this.basicService.get() === null ? new SystemBasicModel() : this.basicService.get(); + } +} diff --git a/src/renderer/plugin/mysql.plugin.ts b/src/renderer/plugin/mysql.plugin.ts new file mode 100644 index 00000000..db369f04 --- /dev/null +++ b/src/renderer/plugin/mysql.plugin.ts @@ -0,0 +1,77 @@ +import { PluginInterface } from "@renderer/interfaces/plugin.interface"; +import { Injectable } from "@angular/core"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { ResponseDataModel, ResponseModel } from "@renderer/model/response.model"; +import { RequestModel } from "@renderer/model/request.model"; +import { SystemBasicModel } from "@renderer/model/system.model"; +import { BasicService } from "@renderer/services/system/basic.service"; +import { timeout, TimeoutError } from 'promise-timeout'; + +const mysql = require('mysql'); + +@Injectable() +export class MysqlPlugin implements PluginInterface { + + constructor(private basicService: BasicService) { + } + + getName(): DatabaseEnum { + return DatabaseEnum.mysql; + } + + private getConfig(): SystemBasicModel { + return this.basicService.get() === null ? new SystemBasicModel() : this.basicService.get(); + } + + getResponse(request: RequestModel, sql?: string): Promise { + const configure = request.config; + const start = new Date().getTime(); + const network = this.getConfig().network * 1000; + const connection = mysql.createConnection({ + host: configure.host, + port: configure.port, + user: configure.username, + password: configure.password, + database: configure.database + }); + const response = new ResponseModel(); + const responseData = new ResponseDataModel(); + const somePromise = new Promise((resolve) => { + connection.connect(); + connection.query(sql, function (error, results, fields) { + if (error) { + response.status = false; + response.message = error?.message; + resolve(response); + } else { + responseData.headers = fields; + responseData.columns = results; + const end = new Date().getTime(); + const statistics = { + elapsed: end - start + }; + responseData.statistics = statistics; + responseData.rows = results.length; + response.status = true; + response.data = responseData; + resolve(response); + } + connection.end(); + console.log('Close connection'); + }); + }); + return timeout(somePromise, network) + .then((thing) => { + return thing; + }) + .catch((err) => { + response.status = false; + if (err instanceof TimeoutError) { + response.message = `Promise timed out after ${this.getConfig().network} ms`; + } else { + response.message = err; + } + return response; + }); + } +} diff --git a/src/renderer/plugin/postgresql.plugin.ts b/src/renderer/plugin/postgresql.plugin.ts new file mode 100644 index 00000000..09d3344f --- /dev/null +++ b/src/renderer/plugin/postgresql.plugin.ts @@ -0,0 +1,91 @@ +import { PluginInterface } from "@renderer/interfaces/plugin.interface"; +import { DatabaseEnum } from "@renderer/enum/database.enum"; +import { Injectable } from "@angular/core"; +import { BasicService } from "@renderer/services/system/basic.service"; +import { SystemBasicModel } from "@renderer/model/system.model"; +import { RequestModel } from "@renderer/model/request.model"; +import { ResponseDataModel, ResponseModel } from "@renderer/model/response.model"; +import { StringUtils } from "@renderer/utils/string.utils"; +import { timeout, TimeoutError } from 'promise-timeout'; + +const {Client} = require('pg'); + +@Injectable() +export class PostgreSQLPlugin implements PluginInterface { + constructor(private basicService: BasicService) { + } + + private getConfig(): SystemBasicModel { + return this.basicService.get() === null ? new SystemBasicModel() : this.basicService.get(); + } + + getName(): DatabaseEnum { + return DatabaseEnum.postgresql; + } + + getResponse(request: RequestModel, sql?: string): Promise { + const configure = request.config; + const start = new Date().getTime(); + const network = this.getConfig().network * 1000; + const hasAuthentication = (StringUtils.isNotEmpty(configure.username) && StringUtils.isNotEmpty(configure.password)); + let connection; + if (hasAuthentication) { + connection = new Client({ + host: configure.host, + port: configure.port, + user: configure.username, + password: configure.password, + database: configure.database + }); + } else { + connection = new Client({ + host: configure.host, + port: configure.port, + database: configure.database + }); + } + const response = new ResponseModel(); + const responseData = new ResponseDataModel(); + const somePromise = new Promise((resolve) => { + connection.connect().catch(error => { + response.status = false; + response.message = error?.message; + resolve(response); + }) + connection.query(sql, function (error, results) { + if (error) { + response.status = false; + response.message = error?.message; + resolve(response); + } else { + responseData.headers = results?.fields; + responseData.columns = results?.rows; + const end = new Date().getTime(); + const statistics = { + elapsed: end - start + }; + responseData.statistics = statistics; + responseData.rows = results?.rowCount; + response.status = true; + response.data = responseData; + resolve(response); + } + connection.end(); + console.log('Connection ', configure.host, ' is closed'); + }); + }); + return timeout(somePromise, network) + .then((thing) => { + return thing; + }) + .catch((err) => { + response.status = false; + if (err instanceof TimeoutError) { + response.message = `Promise timed out after ${this.getConfig().network} ms`; + } else { + response.message = err; + } + return response; + }); + } +} diff --git a/src/renderer/services/builder/base.builder.ts b/src/renderer/services/builder/base.builder.ts index b7d6f70f..1cc976a7 100644 --- a/src/renderer/services/builder/base.builder.ts +++ b/src/renderer/services/builder/base.builder.ts @@ -1,6 +1,46 @@ import { DatabaseModel } from "@renderer/model/database.model"; import { ColumnModel } from "@renderer/model/column.model"; +import { StringUtils } from "@renderer/utils/string.utils"; +import { SqlUtils } from "@renderer/utils/sql.utils"; -export interface BaseBuilder { - builder(configure: DatabaseModel, columns: ColumnModel[]): string; +export abstract class BaseBuilder { + abstract builder(configure: DatabaseModel, columns: ColumnModel[]): string; + + builderPrefix(configure: DatabaseModel): string { + return StringUtils.format('CREATE TABLE {0} (\n', + [SqlUtils.getTableName(configure.database, configure.targetName)]); + } + + builderColumns(columns: ColumnModel[]): string { + let columnStr = ''; + columns.forEach((value, index) => { + if (index !== columns.length - 1) { + columnStr += this.builderColumnToString(value, true); + } else { + columnStr += this.builderColumnToString(value, false); + } + }); + return columnStr; + } + + builderColumnToString(value: ColumnModel, end: boolean): string { + let column: string; + let dStr: string; + if (value.empty) { + dStr = StringUtils.format(' {0} {1} NOT NULL', [value.name, value.type]); + } else { + dStr = StringUtils.format(' {0} {1}', [value.name, value.type]); + } + const endStr = end ? ',\n' : ''; + if (StringUtils.isNotEmpty(value.description)) { + column = StringUtils.format(` {0} COMMENT '{1}' {2}`, [dStr, value.description, endStr]); + } else { + column = StringUtils.format(' {0} {1}', [dStr, endStr]); + } + return column; + } + + builderSuffix(): string { + return ')'; + } } diff --git a/src/renderer/services/builder/clickhouse.builder.ts b/src/renderer/services/builder/clickhouse.builder.ts index 061fb673..ca6823f0 100644 --- a/src/renderer/services/builder/clickhouse.builder.ts +++ b/src/renderer/services/builder/clickhouse.builder.ts @@ -6,13 +6,16 @@ import { PropertyModel } from "@renderer/model/property.model"; import { DatabaseModel } from "@renderer/model/database.model"; import { PropertyEnum } from "@renderer/enum/property.enum"; -export class ClickhouseBuilder implements BaseBuilder { +export class ClickhouseBuilder extends BaseBuilder { public builder(configure: DatabaseModel, columns: ColumnModel[]): string { let sql = StringUtils.format('CREATE TABLE {0} (\n', [SqlUtils.getTableName(configure.database, configure.targetName)]); sql += StringUtils.format('{0}\n', [this.builderColumnsToString(columns)]); sql += StringUtils.format(') {0}\n', [this.builderEngine(configure)]); const mergeProperties = this.mergeProperties(configure); sql += this.builderProperties(mergeProperties); + if (configure.partitionConfigure.enable) { + sql += this.builderPartition(configure); + } return sql; } @@ -121,4 +124,10 @@ export class ClickhouseBuilder implements BaseBuilder { } return applyArray; } + + private builderPartition(configure: DatabaseModel): string { + let partition = configure.partitionConfigure.columns.join(' , '); + const partitionStr = StringUtils.format('\nPARTITION BY ({0})', [partition]); + return partitionStr; + } } diff --git a/src/renderer/services/builder/default.builder.ts b/src/renderer/services/builder/default.builder.ts index d2975118..efb0f17e 100644 --- a/src/renderer/services/builder/default.builder.ts +++ b/src/renderer/services/builder/default.builder.ts @@ -1,52 +1,12 @@ import { BaseBuilder } from "@renderer/services/builder/base.builder"; import { DatabaseModel } from "@renderer/model/database.model"; import { ColumnModel } from "@renderer/model/column.model"; -import { StringUtils } from "@renderer/utils/string.utils"; -import { SqlUtils } from "@renderer/utils/sql.utils"; -export class DefaultBuilder implements BaseBuilder { +export class DefaultBuilder extends BaseBuilder { builder(configure: DatabaseModel, columns: ColumnModel[]): string { let sql = this.builderPrefix(configure); sql += this.builderColumns(columns); sql += this.builderSuffix(); return sql; } - - builderPrefix(configure: DatabaseModel): string { - return StringUtils.format('CREATE TABLE {0} (\n', - [SqlUtils.getTableName(configure.database, configure.targetName)]); - } - - builderColumns(columns: ColumnModel[]): string { - let columnStr = ''; - columns.forEach((value, index) => { - if (index !== columns.length - 1) { - columnStr += this.builderColumnToString(value, true); - } else { - columnStr += this.builderColumnToString(value, false); - } - }); - return columnStr; - } - - builderColumnToString(value: ColumnModel, end: boolean): string { - let column: string; - let dStr: string; - if (value.empty) { - dStr = StringUtils.format(' {0} {1} NOT NULL', [value.name, value.type]); - } else { - dStr = StringUtils.format(' {0} {1}', [value.name, value.type]); - } - const endStr = end ? ',\n' : ''; - if (StringUtils.isNotEmpty(value.description)) { - column = StringUtils.format(` {0} COMMENT '{1}' {2}`, [dStr, value.description, endStr]); - } else { - column = StringUtils.format(' {0} {1}', [dStr, endStr]); - } - return column; - } - - builderSuffix(): string { - return ')'; - } } diff --git a/src/renderer/services/builder/postgresql.builder.ts b/src/renderer/services/builder/postgresql.builder.ts new file mode 100644 index 00000000..a1de5baf --- /dev/null +++ b/src/renderer/services/builder/postgresql.builder.ts @@ -0,0 +1,17 @@ +import { BaseBuilder } from "@renderer/services/builder/base.builder"; +import { DatabaseModel } from "@renderer/model/database.model"; +import { ColumnModel } from "@renderer/model/column.model"; +import { StringUtils } from "@renderer/utils/string.utils"; + +export class PostgresqlBuilder extends BaseBuilder { + builder(configure: DatabaseModel, columns: ColumnModel[]): string { + let sql = this.builderPrefix(configure); + sql += this.builderColumns(columns); + sql += this.builderSuffix(); + return sql; + } + + builderPrefix(configure: DatabaseModel): string { + return StringUtils.format('CREATE TABLE {0} (\n', [configure.targetName]); + } +} diff --git a/src/renderer/services/common/menu.common.service.ts b/src/renderer/services/common/menu.common.service.ts index b299442d..01f23405 100644 --- a/src/renderer/services/common/menu.common.service.ts +++ b/src/renderer/services/common/menu.common.service.ts @@ -6,17 +6,20 @@ import { OperationEnum } from "@renderer/enum/operation.enum"; import { DatabaseEnum } from "@renderer/enum/database.enum"; import { ConfigModel } from "@renderer/model/config.model"; import { Injectable } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; import { ClipboardComService } from "@renderer/services/other/clipboard.service"; +import { SqlUtils } from "@renderer/utils/sql.utils"; @Injectable() export class MenuCommonService { - constructor(private clipboardComService: ClipboardComService) { + constructor(private clipboardComService: ClipboardComService, + private translateService: TranslateService) { } public applyContextMenu(type: TypeEnum, configure: ConfigModel): MenuModel[] { const menus = new Array(); const operationOfCopy = new OperationModel(); - operationOfCopy.name = StringUtils.format('Copy: {0}', [configure.value]); + operationOfCopy.name = StringUtils.format(this.translateService.instant('common.copy') + ': {0}', [configure.value]); operationOfCopy.type = type; operationOfCopy.icon = 'fa-files-o'; operationOfCopy.operations = [{ @@ -24,6 +27,22 @@ export class MenuCommonService { actions: [OperationEnum.copy], supportedSource: [DatabaseEnum.clickhosue] }]; + + switch (type) { + case TypeEnum.table: + const operationOfDrop = new OperationModel(); + operationOfDrop.name = StringUtils.format('{0} {1}', [this.translateService.instant('common.delete'), configure.value]); + operationOfDrop.type = type; + operationOfDrop.icon = 'fa-trash'; + operationOfDrop.operations = [{ + type: type, + actions: [OperationEnum.delete], + supportedSource: [DatabaseEnum.clickhosue] + }]; + menus.push(operationOfDrop); + break; + } + menus.push(operationOfCopy); return menus; } @@ -34,6 +53,9 @@ export class MenuCommonService { case OperationEnum.copy: this.clipboardComService.copy(configure.value); break; + case OperationEnum.delete: + sql = StringUtils.format('DROP TABLE {0}', [SqlUtils.getTableName(configure.database, configure.value)]); + break; } return sql; } diff --git a/src/renderer/services/factory.service.ts b/src/renderer/services/factory.service.ts index 209df0f8..47b77f64 100644 --- a/src/renderer/services/factory.service.ts +++ b/src/renderer/services/factory.service.ts @@ -7,6 +7,8 @@ import { ClickhouseBuilder } from "@renderer/services/builder/clickhouse.builder import { PrestoConfig } from "@renderer/config/plugin/presto.config"; import { ClickhouseConfig } from "@renderer/config/plugin/clickhouse.config"; import { DruidConfig } from "@renderer/config/plugin/druid.config"; +import { ElasticsearchConfig } from "@renderer/config/plugin/elasticsearch.config"; +import { PostgresqlBuilder } from "@renderer/services/builder/postgresql.builder"; export class FactoryService { public forward(type: string) { @@ -19,9 +21,12 @@ export class FactoryService { case DatabaseEnum.mysql: return Factory.create(MySQLConfig); case DatabaseEnum.postgresql: + case DatabaseEnum.hologres: return Factory.create(PostgresqlConfig); case DatabaseEnum.druid: return Factory.create(DruidConfig); + case DatabaseEnum.elasticsearch: + return Factory.create(ElasticsearchConfig); default: new Error("Unsupported database type"); return null; @@ -32,6 +37,8 @@ export class FactoryService { switch (type) { case DatabaseEnum.clickhosue: return Factory.create(ClickhouseBuilder); + case DatabaseEnum.postgresql: + return Factory.create(PostgresqlBuilder); default: return Factory.create(DefaultBuilder); } diff --git a/src/renderer/services/forward.service.ts b/src/renderer/services/forward.service.ts index ab008a93..b7be4093 100644 --- a/src/renderer/services/forward.service.ts +++ b/src/renderer/services/forward.service.ts @@ -11,6 +11,8 @@ import { DatabaseEnum } from "@renderer/enum/database.enum"; import { FactoryService } from "@renderer/services/factory.service"; import { MySQLService } from "@renderer/services/plugin/mysql.service"; import { PostgresqlService } from "@renderer/services/plugin/postgresql.service"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; +import { ConfigFactory } from "@renderer/factory/config.factory"; export class ForwardService { constructor( @@ -20,7 +22,9 @@ export class ForwardService { protected sshService: SshService, protected prestoService?: PrestoService, protected mysqlService?: MySQLService, - protected postgresqlService?: PostgresqlService + protected postgresqlService?: PostgresqlService, + protected pluginFactory?: PluginFactory, + protected configFactory?: ConfigFactory ) { } @@ -32,6 +36,7 @@ export class ForwardService { const configure = request.config; switch (configure.protocol) { case 'HTTP': + case 'HTTPS': let response; switch (configure.type) { case DatabaseEnum.clickhosue: @@ -45,11 +50,15 @@ export class ForwardService { response = this.mysqlService.execute(configure, sql); break case DatabaseEnum.postgresql: + case DatabaseEnum.hologres: response = this.postgresqlService.execute(configure, sql); break case DatabaseEnum.druid: response = this.httpService.post(UrlUtils.formatUrl(request), sql, true); break + case DatabaseEnum.elasticsearch: + response = this.pluginFactory.createService(configure.type).getResponse(request, sql); + break; } return response; case 'SSH': diff --git a/src/renderer/services/management/collation.service.ts b/src/renderer/services/management/collation.service.ts new file mode 100644 index 00000000..b960f93f --- /dev/null +++ b/src/renderer/services/management/collation.service.ts @@ -0,0 +1,23 @@ +import { BaseService } from "@renderer/services/base.service"; +import { ResponseModel } from "@renderer/model/response.model"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; +import { Injectable } from "@angular/core"; +import { RequestModel } from "@renderer/model/request.model"; +import { ConfigFactory } from "@renderer/factory/config.factory"; + +@Injectable() +export class CollationService implements BaseService { + + constructor(private pluginFactory: PluginFactory, + private configFactory: ConfigFactory) { + } + + getResponse(request: RequestModel, sql?: string): Promise { + return this.pluginFactory.createService(request.config.type).getResponse(request, sql); + } + + getCharacterAndCollation(request: RequestModel): Promise { + const source = this.configFactory.createConfig(request.config.type).getStatement('getCharacterAndCollation'); + return this.getResponse(request, source); + } +} diff --git a/src/renderer/services/management/database.service.ts b/src/renderer/services/management/database.service.ts index 43e063a9..e35f84b1 100644 --- a/src/renderer/services/management/database.service.ts +++ b/src/renderer/services/management/database.service.ts @@ -10,6 +10,8 @@ import { ForwardService } from '@renderer/services/forward.service'; import { FactoryService } from "@renderer/services/factory.service"; import { MySQLService } from "@renderer/services/plugin/mysql.service"; import { PostgresqlService } from "@renderer/services/plugin/postgresql.service"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; +import { ConfigFactory } from "@renderer/factory/config.factory"; @Injectable() export class DatabaseService extends ForwardService implements BaseService { @@ -18,12 +20,14 @@ export class DatabaseService extends ForwardService implements BaseService { sshService: SshService, basicService: BasicService, mysqlService: MySQLService, - postgresqlService: PostgresqlService) { - super(basicService, factoryService, httpService, sshService, null, mysqlService, postgresqlService); + postgresqlService: PostgresqlService, + pluginFactory: PluginFactory, + configFactory: ConfigFactory) { + super(basicService, factoryService, httpService, sshService, null, mysqlService, postgresqlService, pluginFactory, configFactory); } getResponse(request: RequestModel, sql?: string): Promise { - return this.forward(request, sql); + return this.pluginFactory.createService(request.config.type).getResponse(request, sql); } getAll(request: RequestModel): Promise { @@ -48,7 +52,8 @@ export class DatabaseService extends ForwardService implements BaseService { } rename(request: RequestModel, source: string, target: string): Promise { - const sql = StringUtils.format('RENAME DATABASE `{0}` TO `{1}`', [source, target]); + const sourceSql = this.configFactory.createConfig(request.config.type).getStatement('databaseRename'); + const sql = StringUtils.format(sourceSql, [source, target]); return this.getResponse(request, sql); } } diff --git a/src/renderer/services/management/datasource.service.ts b/src/renderer/services/management/datasource.service.ts index 1c7e703c..6234fa98 100644 --- a/src/renderer/services/management/datasource.service.ts +++ b/src/renderer/services/management/datasource.service.ts @@ -13,6 +13,7 @@ import { PrestoService } from "@renderer/services/presto.service"; import { FactoryService } from "@renderer/services/factory.service"; import { MySQLService } from "@renderer/services/plugin/mysql.service"; import { PostgresqlService } from "@renderer/services/plugin/postgresql.service"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; @Injectable() export class DatasourceService extends PersistenceService implements BaseService { @@ -25,8 +26,9 @@ export class DatasourceService extends PersistenceService implements BaseService sshService: SshService, prestoService: PrestoService, mysqlService: MySQLService, - postgresqlService: PostgresqlService) { - super(basicService, factoryService, httpService, sshService, prestoService, mysqlService, postgresqlService); + postgresqlService: PostgresqlService, + pluginFactory: PluginFactory) { + super(basicService, factoryService, httpService, sshService, prestoService, mysqlService, postgresqlService, pluginFactory); this.db = new DexieDb(); } diff --git a/src/renderer/services/management/metadata.service.ts b/src/renderer/services/management/metadata.service.ts index f25808ed..c49b30fa 100644 --- a/src/renderer/services/management/metadata.service.ts +++ b/src/renderer/services/management/metadata.service.ts @@ -112,9 +112,26 @@ export class MetadataService extends ForwardService implements BaseService { case DatabaseEnum.mysql: case DatabaseEnum.materialized_mysql: case DatabaseEnum.materialized_postgresql: + case DatabaseEnum.postgresql: suffix = this.builderDatabaseMySQL(database); break; } + + if (request.config.type === DatabaseEnum.mysql) { + if (database.characterAndCollationConfigure.enable) { + if (database.characterAndCollationConfigure.characterSetConfigure.enable + && StringUtils.isNotEmpty(database.characterAndCollationConfigure.characterSetConfigure.value)) { + suffix += StringUtils.format(` CHARACTER SET '{0}'`, + [database.characterAndCollationConfigure.characterSetConfigure.value]); + } + if (database.characterAndCollationConfigure.collationConfigure.enable + && StringUtils.isNotEmpty(database.characterAndCollationConfigure.collationConfigure.value)) { + suffix += StringUtils.format(` COLLATE '{0}'`, + [database.characterAndCollationConfigure.collationConfigure.value]); + } + } + } + return this.getResponse(request, StringUtils.format('{0} {1}', [prefix, suffix])); } diff --git a/src/renderer/services/query/query.service.ts b/src/renderer/services/query/query.service.ts index 3edcc37e..75117aa0 100644 --- a/src/renderer/services/query/query.service.ts +++ b/src/renderer/services/query/query.service.ts @@ -11,6 +11,7 @@ import { FactoryService } from "@renderer/services/factory.service"; import { PrestoService } from "@renderer/services/presto.service"; import { MySQLService } from "@renderer/services/plugin/mysql.service"; import { PostgresqlService } from "@renderer/services/plugin/postgresql.service"; +import { PluginFactory } from "@renderer/factory/plugin.factory"; const {Parser} = require('node-sql-parser'); @@ -22,8 +23,9 @@ export class QueryService extends ForwardService implements BaseService { factoryService: FactoryService, prestoService: PrestoService, mysqlService: MySQLService, - postgresqlService: PostgresqlService) { - super(basicService, factoryService, httpService, sshService, prestoService, mysqlService, postgresqlService); + postgresqlService: PostgresqlService, + pluginFactory: PluginFactory) { + super(basicService, factoryService, httpService, sshService, prestoService, mysqlService, postgresqlService, pluginFactory); } getResponse(request: RequestModel, sql?: string): Promise { diff --git a/src/renderer/token/config.token.ts b/src/renderer/token/config.token.ts new file mode 100644 index 00000000..aaaddc15 --- /dev/null +++ b/src/renderer/token/config.token.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core'; +import { ConfigInterface } from "@renderer/interfaces/config.interface"; + +export const ConfigToken = new InjectionToken(''); diff --git a/src/renderer/utils/url.utils.ts b/src/renderer/utils/url.utils.ts index 0ca47c0b..388716e0 100644 --- a/src/renderer/utils/url.utils.ts +++ b/src/renderer/utils/url.utils.ts @@ -38,4 +38,9 @@ export class UrlUtils { } return remoteUrl; } + + public static formatUrlWithHostAndPort(request: RequestModel): string { + const protocol = StringUtils.getValue(request.config.protocol, 'http'); + return StringUtils.format('{0}://{1}:{2}', [protocol, request.config.host, request.config.port]); + } } diff --git a/src/shared/assets/integrate/elasticsearch.svg b/src/shared/assets/integrate/elasticsearch.svg new file mode 100644 index 00000000..bb9a3a12 --- /dev/null +++ b/src/shared/assets/integrate/elasticsearch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/shared/assets/integrate/hologres.svg b/src/shared/assets/integrate/hologres.svg new file mode 100644 index 00000000..0bf2084c --- /dev/null +++ b/src/shared/assets/integrate/hologres.svg @@ -0,0 +1 @@ + \ No newline at end of file