summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorliuyongqiang <[email protected]>2020-11-11 10:47:51 +0800
committerliuyongqiang <[email protected]>2020-11-11 10:47:51 +0800
commit40df6d5e11d23cce29e921ca492613cd167331c0 (patch)
tree685a9600e3cae27b3ec9e3e10928ae279cc58ee9
parente412d42c40fba667c1187c8ef91230a0772e43da (diff)
基于DSL的单表查询迁移完成
-rw-r--r--galaxy-admin-server/pom.xml8
-rw-r--r--galaxy-auth-center/pom.xml12
-rw-r--r--galaxy-auth-center/src/test/java/com/mesalab/api/test/JwtAuthorizeServiceTest.java31
-rw-r--r--galaxy-common/pom.xml20
-rw-r--r--galaxy-gateway/pom.xml12
-rw-r--r--galaxy-gateway/src/main/java/com/mesalab/api/gateway/GalaxyGatewayApp.java12
-rw-r--r--galaxy-query-engine/config/application.properties23
-rw-r--r--galaxy-query-engine/pom.xml114
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/GalaxyQueryEngine.java12
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/constant/SqlKeywords.java71
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/dto/ClickHouseResult.java31
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/EngineTypeEnum.java36
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/IntervalTypeEnum.java28
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/MatchTypeEnum.java33
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/RangeTypeEnum.java34
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultCodeEnum.java24
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultStatusEnum.java29
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/exception/BusinessException.java52
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/BaseResult.java51
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/ClickHouseParser.java122
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLObject.java61
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLParser.java285
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DruidParser.java82
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/BaseResultUtil.java208
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/HttpClientUtil.java270
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLRouter.java46
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLValidate.java367
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/controller/DslQueryController.java47
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/ClickHouseEngineService.java10
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/CommonEngineService.java47
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/DruidEngineService.java11
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/EngineExecuteService.java31
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/ClickHouseEngineServiceImpl.java91
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/DruidEngineServiceImpl.java97
-rw-r--r--galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/EngineExecuteServiceImpl.java62
-rw-r--r--pom.xml12
36 files changed, 2378 insertions, 104 deletions
diff --git a/galaxy-admin-server/pom.xml b/galaxy-admin-server/pom.xml
index bd5f13f..e800ddb 100644
--- a/galaxy-admin-server/pom.xml
+++ b/galaxy-admin-server/pom.xml
@@ -23,14 +23,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
diff --git a/galaxy-auth-center/pom.xml b/galaxy-auth-center/pom.xml
index 8667385..c7622a3 100644
--- a/galaxy-auth-center/pom.xml
+++ b/galaxy-auth-center/pom.xml
@@ -14,10 +14,6 @@
<dependencies>
<dependency>
- <groupId>com.mesalab</groupId>
- <artifactId>galaxy-common</artifactId>
- </dependency>
- <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
@@ -30,14 +26,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
diff --git a/galaxy-auth-center/src/test/java/com/mesalab/api/test/JwtAuthorizeServiceTest.java b/galaxy-auth-center/src/test/java/com/mesalab/api/test/JwtAuthorizeServiceTest.java
deleted file mode 100644
index 953ec17..0000000
--- a/galaxy-auth-center/src/test/java/com/mesalab/api/test/JwtAuthorizeServiceTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.mesalab.api.test;
-
-import com.mesalab.auth.GalaxyAuthCenterApp;
-import com.mesalab.auth.service.AuthorizeService;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-/**
- * @Date: 2020-08-17 14:33
- * @Author : liuyongqiang
- * @ClassName : JwtAuthorizeServiceTest
- * @Description : JWT授权服务测试
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest(classes = {GalaxyAuthCenterApp.class})// 指定启动类
-@Slf4j
-public class JwtAuthorizeServiceTest {
-
- @Autowired
- AuthorizeService jwtAuthorizeService;
-
- //ArangoDBJwt登录测试
- @Test
- public void arangoJwtLoginTest() {
- log.info(jwtAuthorizeService.arangoJwtLogin());
- }
-}
diff --git a/galaxy-common/pom.xml b/galaxy-common/pom.xml
deleted file mode 100644
index 72d01ca..0000000
--- a/galaxy-common/pom.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>galaxy-data-platform</artifactId>
- <groupId>com.mesalab</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
-
- <modelVersion>4.0.0</modelVersion>
- <artifactId>galaxy-common</artifactId>
- <packaging>jar</packaging>
-
-
- <build>
- <finalName>galaxy-common</finalName>
- </build>
-
-</project> \ No newline at end of file
diff --git a/galaxy-gateway/pom.xml b/galaxy-gateway/pom.xml
index e5fe68a..a7d8781 100644
--- a/galaxy-gateway/pom.xml
+++ b/galaxy-gateway/pom.xml
@@ -14,18 +14,6 @@
<dependencies>
<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
diff --git a/galaxy-gateway/src/main/java/com/mesalab/api/gateway/GalaxyGatewayApp.java b/galaxy-gateway/src/main/java/com/mesalab/api/gateway/GalaxyGatewayApp.java
index 661d03a..0f72ed3 100644
--- a/galaxy-gateway/src/main/java/com/mesalab/api/gateway/GalaxyGatewayApp.java
+++ b/galaxy-gateway/src/main/java/com/mesalab/api/gateway/GalaxyGatewayApp.java
@@ -1,19 +1,11 @@
package com.mesalab.api.gateway;
import org.springframework.boot.SpringApplication;
-import org.springframework.cloud.client.SpringCloudApplication;
-import org.springframework.cloud.client.loadbalancer.LoadBalanced;
-import org.springframework.context.annotation.Bean;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
-@SpringCloudApplication
+@SpringBootApplication
public class GalaxyGatewayApp {
- @Bean
- @LoadBalanced
- RestTemplate restTemplate() {
- return new RestTemplate();
- }
public static void main(String[] args) {
SpringApplication.run(GalaxyGatewayApp.class);
diff --git a/galaxy-query-engine/config/application.properties b/galaxy-query-engine/config/application.properties
index d884eee..014575d 100644
--- a/galaxy-query-engine/config/application.properties
+++ b/galaxy-query-engine/config/application.properties
@@ -1,8 +1,29 @@
#application config
server.port=8803
spring.application.name=galaxy-query-engine
+
#management config
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
+
#log file path config
-logging.config=./config/logback-spring.xml \ No newline at end of file
+logging.config=classpath:logback-spring.xml
+
+#engine config
+engine.maxCacheNum=500000
+engine.defaultResultNum=300000
+
+#druid config
+druid.address=192.168.40.152:8082
+druid.dbname=druid
+druid.sqlTimeZone=Asia/Shanghai
+druid.query.url=http://${druid.address}/druid/v2/sql
+
+#clickhouse config
+clickhouse.address=192.168.44.12:8123
+clickhouse.query.url=http://${clickhouse.address}
+clickhouse.dbname=tsg_galaxy_v3
+clickhouse.real.time.username=tsg_query
+clickhouse.real.time.password=ceiec2018
+clickhouse.long.term.username=tsg_report
+clickhouse.long.term.password=ceiec2019 \ No newline at end of file
diff --git a/galaxy-query-engine/pom.xml b/galaxy-query-engine/pom.xml
index caea719..b7d8c32 100644
--- a/galaxy-query-engine/pom.xml
+++ b/galaxy-query-engine/pom.xml
@@ -12,4 +12,118 @@
<artifactId>galaxy-query-engine</artifactId>
<packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.hutool</groupId>
+ <artifactId>hutool-all</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.avro</groupId>
+ <artifactId>avro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.github.java-json-tools</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.zdjizhi</groupId>
+ <artifactId>galaxy</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.6</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <finalName>galaxy-query-engine</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>2.0.2.RELEASE</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <includeSystemScope>true</includeSystemScope>
+ <mainClass>com.mesalab.engine.GalaxyDataEngineApp</mainClass>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <serverId>153-docker-repo</serverId>
+ <registryUrl>${docker.registry}:${docker.registry.port}</registryUrl>
+ <pushImage>true</pushImage>
+ <imageName>${docker.registry}:${docker.registry.port}/${docker.image.prefix}/${project.artifactId}
+ </imageName>
+ <forceTags>true</forceTags>
+ <dockerHost>http://192.168.40.153:2375</dockerHost>
+ <dockerDirectory>docker</dockerDirectory>
+ <buildArgs>
+ <JDK_IMAGE>192.168.40.153:9080/common/jdk:1.8.0_73</JDK_IMAGE>
+ <JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
+ </buildArgs>
+ <resources>
+ <resource>
+ <targetPath>/</targetPath>
+ <directory>${project.build.directory}</directory>
+ <include>${project.build.finalName}.jar</include>
+ </resource>
+ <resource>
+ <targetPath>/mmdb</targetPath>
+ <directory>mmdb</directory>
+ </resource>
+ <resource>
+ <targetPath>/config</targetPath>
+ <directory>config</directory>
+ </resource>
+ <resource>
+ <targetPath>/schema</targetPath>
+ <directory>schema</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
</project> \ No newline at end of file
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/GalaxyQueryEngine.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/GalaxyQueryEngine.java
new file mode 100644
index 0000000..ade63bd
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/GalaxyQueryEngine.java
@@ -0,0 +1,12 @@
+package com.mesalab.engine;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GalaxyQueryEngine {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GalaxyQueryEngine.class);
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/constant/SqlKeywords.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/constant/SqlKeywords.java
new file mode 100644
index 0000000..f5ff188
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/constant/SqlKeywords.java
@@ -0,0 +1,71 @@
+package com.mesalab.engine.common.constant;
+
+/**
+ * @Date: 2020-07-22 19:41
+ * @Author : liuyongqiang
+ * @ClassName : SqlKeywords
+ * @Description : SQL关键字常量
+ */
+public class SqlKeywords {
+
+ public static final String SELECT = " select ";
+
+ public static final String AND = " and ";
+
+ public static final String WHERE = " where ";
+
+ public static final String LIKE = " like ";
+
+ public static final String OR = " or ";
+
+ public static final String FROM = " from ";
+
+ public static final String BWTWEEN = " between ";
+
+ public static final String ORDER_BY = " order by ";
+
+ public static final String LIMIT = " limit ";
+
+ public static final String D_TIME = " __time ";
+
+ public static final String D_TIME_FLOOR = " time_floor(__time,''{0}'') ";
+
+ public static final String C_TO_DATE_TIME = "toDateTime(''{0}'')";
+
+ public static final String A_SORT ="SORT";
+
+ public static final String A_TIME = "_TIME";
+
+ public static final String A_FILTER = "FILTER";
+
+ public static final String A_AND = "AND";
+
+ public static final String A_LIKE = "LIKE";
+
+ public static final String A_OR = "OR";
+
+ public static final String A_SPACE = " ";
+
+ public static final String A_SPOT = ".";
+
+ public static final String A_LIMIT = "LIMIT" ;
+
+ public static final String A_RETURN = "RETURN";
+
+ public static final String C_SECOND = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} SECOND)))) as granularity";
+
+ public static final String C_MINUTE = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} MINUTE)))) as granularity";
+
+ public static final String C_HOUR = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} HOUR)))) as granularity";
+
+ public static final String C_DAY = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} DAY)))) as granularity";
+
+ public static final String C_WEEK = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} WEEK)))) as granularity";
+
+ public static final String C_MONTH = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} MONTH)))) as granularity";
+
+ public static final String C_YEAR = "toDateTime(toUnixTimestamp(toDateTime(toStartOfInterval(toDateTime(common_recv_time),INTERVAL {0} YEAR)))) as granularity";
+
+
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/dto/ClickHouseResult.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/dto/ClickHouseResult.java
new file mode 100644
index 0000000..51786da
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/dto/ClickHouseResult.java
@@ -0,0 +1,31 @@
+package com.mesalab.engine.common.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Date: 2020-07-23 16:21
+ * @Author : liuyongqiang
+ * @ClassName : ClickHouseResult
+ * @Description : ClickHouse查询结果封装
+ */
+@Data
+@AllArgsConstructor
+public class ClickHouseResult implements Serializable {
+
+ private int rows;
+ private List<MetaBean> meta;
+ private List<Map<String, Object>> data;
+ private Map<String, Object> statistics;
+
+ @Data
+ public static class MetaBean {
+ private String name;
+ private String type;
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/EngineTypeEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/EngineTypeEnum.java
new file mode 100644
index 0000000..979716a
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/EngineTypeEnum.java
@@ -0,0 +1,36 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Date: 2020-07-20 18:45
+ * @Author : liuyongqiang
+ * @ClassName : EngineTypeEnum
+ * @Description : 查询引擎映射枚举类
+ */
+@Getter
+@AllArgsConstructor
+public enum EngineTypeEnum {
+
+ //AnalysisEngine-AnangoDB 分析、学习引擎
+ //CaculationEngine-Druid 保存、计算引擎
+ //BusinessEngine-ClickHouse 业务数据引擎
+
+ ANALYSIS_ENGINE("AnalysisEngine", "AnangoDB"),
+ CACULATION_ENGINE("CaculationEngine", "Druid"),
+ BUSINESS_ENGINE("BusinessEngine", "ClickHouse");
+
+ private String engine;//引擎名称
+ private String dbtype;//数据库类型
+
+ public static EngineTypeEnum getByEngine(String engine) {
+ for (EngineTypeEnum constants : values()) {
+ if (constants.engine.equalsIgnoreCase(engine)) {
+ return constants;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/IntervalTypeEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/IntervalTypeEnum.java
new file mode 100644
index 0000000..8be621f
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/IntervalTypeEnum.java
@@ -0,0 +1,28 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Date: 2020-07-29 15:45
+ * @Author : wangwei
+ * @ClassName : IntervalTypeEnum
+ * @Description : 区间查询类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum IntervalTypeEnum {
+ EXACTLY("BETWEEN", "闭区间间隔");
+
+ private String type;
+ private String name;
+
+ public static IntervalTypeEnum getByType(String type) {
+ for (IntervalTypeEnum constants : values()) {
+ if (constants.type.equalsIgnoreCase(type)) {
+ return constants;
+ }
+ }
+ return null;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/MatchTypeEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/MatchTypeEnum.java
new file mode 100644
index 0000000..36b7e01
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/MatchTypeEnum.java
@@ -0,0 +1,33 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Date: 2020-07-22 17:45
+ * @Author : liuyongqiang
+ * @ClassName : MatchTypeEnum
+ * @Description : 匹配查询类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum MatchTypeEnum {
+
+ EXACTLY("exactly", "完全匹配"),
+ PREFIX("prefix", "前缀匹配"),
+ SUFFIX("suffix", "后缀匹配"),
+ SUBSTRING("substring", "字串匹配"),
+ REGEX("regex", "正则匹配");
+
+ private String type;
+ private String name;
+
+ public static MatchTypeEnum getByType(String type) {
+ for (MatchTypeEnum constants : values()) {
+ if (constants.type.equalsIgnoreCase(type)) {
+ return constants;
+ }
+ }
+ return null;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/RangeTypeEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/RangeTypeEnum.java
new file mode 100644
index 0000000..a9c5fba
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/RangeTypeEnum.java
@@ -0,0 +1,34 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Date: 2020-07-22 17:44
+ * @Author : liuyongqiang
+ * @ClassName : RangeTypeEnum
+ * @Description : 范围查询类型枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum RangeTypeEnum {
+
+ GT("gt", " > "),
+ LT("lt", " < "),
+ EQ("eq", " = "),
+ GE("ge", " >= "),
+ LE("le", " <= "),
+ NE("ne", " != ");
+
+ private String type;
+ private String expr;
+
+ public static RangeTypeEnum getByType(String type) {
+ for (RangeTypeEnum constants : values()) {
+ if (constants.type.equalsIgnoreCase(type)) {
+ return constants;
+ }
+ }
+ return null;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultCodeEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultCodeEnum.java
new file mode 100644
index 0000000..ab5fe58
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultCodeEnum.java
@@ -0,0 +1,24 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BaseResult的业务状态码
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultCodeEnum {
+
+ EXECUTE_SUCCESS("200666", "成功"),
+ PARAM_SYNTAX_ERROR("400001", "参数检查异常"),
+ SQL_SYNTAX_ERROR("400010", "SQL语句检查异常"),
+ SQL_EXECUTION_ERROR("500001", "SQL 执行异常"),
+ ENGINE_STATISTICS_ERROR("500010", "引擎计算异常"),
+ UNKNOW_ERROR("500999", "执行失败");
+
+ private String code;
+ private String message;
+
+}
+
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultStatusEnum.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultStatusEnum.java
new file mode 100644
index 0000000..64cb304
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/enums/ResultStatusEnum.java
@@ -0,0 +1,29 @@
+package com.mesalab.engine.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BaseResult的http状态编码枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultStatusEnum {
+
+ /**
+ * SUCCESS: 200 成功
+ * FAIL: 400 失败
+ * NOT_FOUND: 404 不存在
+ * SERVER_ERROR: 500 网络服务异常
+ */
+ SUCCESS(200, "成功"),
+ FAIL(400, "失败"),
+ NOT_FOUND(404, "不存在"),
+ REQ_FORBIDDEN(403, "重复请求"),
+ SERVER_ERROR(500, "服务异常");
+
+ private int code;
+ private String message;
+
+}
+
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/exception/BusinessException.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/exception/BusinessException.java
new file mode 100644
index 0000000..2f5c476
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/exception/BusinessException.java
@@ -0,0 +1,52 @@
+package com.mesalab.engine.common.exception;
+
+import com.mesalab.engine.common.enums.ResultCodeEnum;
+import com.mesalab.engine.common.enums.ResultStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/**
+ * 业务异常
+ *
+ * @author dazzlzy
+ * @date 2018/3/22
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class BusinessException extends RuntimeException {
+
+ /**
+ * 返回HTTP状态码
+ */
+ private int errorStatus = ResultStatusEnum.SERVER_ERROR.getCode();
+
+ /**
+ * 内部执行错误码
+ */
+ private String errorCode = ResultCodeEnum.UNKNOW_ERROR.getCode();
+
+ /**
+ * 异常信息
+ */
+ private String errorMessage;
+
+
+ public BusinessException(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+
+ public BusinessException(String errorMessage, Throwable e) {
+ super(errorMessage, e);
+ }
+
+ public BusinessException(int errorStatus, String errorCode, String errorMessage, Throwable e) {
+ super(errorMessage, e);
+ this.errorStatus = errorStatus;
+ this.errorCode = errorCode;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/BaseResult.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/BaseResult.java
new file mode 100644
index 0000000..3784e6d
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/BaseResult.java
@@ -0,0 +1,51 @@
+package com.mesalab.engine.common.pojo;
+
+import com.mesalab.engine.common.enums.ResultStatusEnum;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 响应结果
+ *
+ * @author dazzlzy
+ * @date 2018/3/21
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseResult<T> implements Serializable {
+
+ private Integer status;
+
+ private String code;
+
+ private boolean success;
+
+ private String message;
+
+ private Map<String, Object> statistics;
+
+ private String formatType;
+
+ private T meta;
+
+ private T data;
+
+
+ /**
+ * 判断是否是成功结果
+ * JsonIgnore使之不在json序列化结果当中
+ *
+ * @return 是否为成功结果
+ */
+ public boolean isSuccess() {
+ return ResultStatusEnum.SUCCESS.getCode() == this.status;
+ }
+
+} \ No newline at end of file
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/ClickHouseParser.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/ClickHouseParser.java
new file mode 100644
index 0000000..a2b4c32
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/ClickHouseParser.java
@@ -0,0 +1,122 @@
+package com.mesalab.engine.common.pojo;
+
+import cn.hutool.core.util.StrUtil;
+import com.mesalab.engine.common.constant.SqlKeywords;
+import com.mesalab.engine.service.ClickHouseEngineService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Date: 2020-07-22 18:00
+ * @Author : liuyongqiang
+ * @ClassName : ClickHouseDSLParser
+ * @Description : ClickHouse DSL语法解析器
+ */
+@Slf4j
+@Component
+public class ClickHouseParser extends DSLParser {
+
+ @Autowired
+ ClickHouseEngineService clickHouseEngineService;
+
+ //表字段和数据类型查询SQL
+ public final static String SCHEAM_QUERY = " desc {0} ";
+
+ //查询所有表
+ public final static String TABLES_QUERY = " show tables ";
+
+ public static Pattern periodOfPT = Pattern.compile("PT(\\d+)(\\w+)", Pattern.CASE_INSENSITIVE);
+
+ public static Pattern periodOfP = Pattern.compile("P(\\d+)(\\w+)", Pattern.CASE_INSENSITIVE);
+
+
+ @Override
+ public String parseGranularity(DSLObject dslObject, String sql) {
+ if (StrUtil.isEmpty(dslObject.getQuery().getGranularity())) {
+ return sql;
+ }
+ String granularity = dslObject.getQuery().getGranularity();
+ Matcher matcherPT = periodOfPT.matcher(granularity);
+ Matcher matcherP = periodOfP.matcher(granularity);
+ String format = "";
+
+ if (matcherPT.find()) {
+ String PTnum = matcherPT.group(1);
+ String PTunit = matcherPT.group(2);
+ if ("S".equalsIgnoreCase(PTunit)) {
+ format = MessageFormat.format(SqlKeywords.C_SECOND, PTnum);
+ } else if ("M".equalsIgnoreCase(PTunit)) {
+ format = MessageFormat.format(SqlKeywords.C_MINUTE, PTnum);
+ } else if ("H".equalsIgnoreCase(PTunit)) {
+ format = MessageFormat.format(SqlKeywords.C_HOUR, PTnum);
+ }
+ }
+
+ if (matcherP.find()) {
+ String Pnum = matcherP.group(1);
+ String Punit = matcherP.group(2);
+ if ("D".equalsIgnoreCase(Punit)) {
+ format = MessageFormat.format(SqlKeywords.C_DAY, Pnum);
+ } else if ("W".equalsIgnoreCase(Punit)) {
+ format = MessageFormat.format(SqlKeywords.C_WEEK, Pnum);
+ } else if ("M".equalsIgnoreCase(Punit)) {
+ format = MessageFormat.format(SqlKeywords.C_MONTH, Pnum);
+ } else if ("Y".equalsIgnoreCase(Punit)) {
+ format = MessageFormat.format(SqlKeywords.C_YEAR, Pnum);
+ }
+ }
+ sql = sql.replace(SqlKeywords.SELECT, SqlKeywords.SELECT.concat(format).concat(","));
+ return sql;
+ }
+
+ @Override
+ public String parseSort(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getSort())) {
+ return sql;
+ }
+ String sortType = dslObject.getQuery().getSort().get(0).getType();
+ sql = sql.concat(SqlKeywords.ORDER_BY
+ .concat(dslObject.getQuery().getSort().get(0).getFieldKey()))
+ .concat(" ")
+ .concat(sortType);
+ if (dslObject.getQuery().getSort().size() > 1) {
+ List<DSLObject.QueryBean.SortBean> sortBeans = dslObject.getQuery().getSort();
+ for (int i = 1; i < sortBeans.size(); i++) {
+ sql = sql.concat(" , ")
+ .concat(sortBeans.get(i).getFieldKey())
+ .concat(" ")
+ .concat(sortBeans.get(i).getType());
+ }
+ }
+ return sql;
+ }
+
+ @Override
+ public String parseIntervals(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getIntervals())) {
+ return sql;
+ }
+
+ List<DSLObject.QueryBean.IntervalsBean> intervalsBeanList = dslObject.getQuery().getIntervals();
+ for (DSLObject.QueryBean.IntervalsBean intervalsBean : intervalsBeanList) {
+ String fieldKey = intervalsBean.getFieldKey();
+ List<String> values = intervalsBean.getFieldValues();
+ if (CollectionUtils.isEmpty(values)) break;
+ sql = sql.concat(sql.contains(SqlKeywords.WHERE) ? SqlKeywords.AND : SqlKeywords.WHERE)
+ .concat(fieldKey)
+ .concat(SqlKeywords.BWTWEEN)
+ .concat(MessageFormat.format(SqlKeywords.C_TO_DATE_TIME, values.get(0)))
+ .concat(SqlKeywords.AND)
+ .concat(MessageFormat.format(SqlKeywords.C_TO_DATE_TIME, values.get(1)));
+ }
+ return sql;
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLObject.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLObject.java
new file mode 100644
index 0000000..8afcc6d
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLObject.java
@@ -0,0 +1,61 @@
+package com.mesalab.engine.common.pojo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Date: 2020-07-20 09:48
+ * @Author : liuyongqiang
+ * @ClassName : DSLObject
+ * @Description : 标准服务接口模型
+ */
+
+@Data
+public class DSLObject implements Serializable {
+
+ private static final long serialVersionUID = 4956257906217545258L;
+
+ private String limit;//数据条数
+ private String dataEngine;//数据引擎
+ private String dataSource;//数据来源
+ private QueryBean query;//查询结构
+
+ @Data
+ public static class QueryBean {
+ private String granularity;//时间粒度
+ private List<MatchBean> match;//匹配查询
+ private List<RangeBean> range;//区间查询
+ private List<IntervalsBean> intervals;//时间范围
+ private List<SortBean> sort;//排序方式
+
+ @Data
+ public static class MatchBean {
+ private String type;
+ private String fieldKey;
+ private List<String> fieldValues;
+ }
+
+ @Data
+ public static class RangeBean {
+ private String type;
+ private String fieldKey;
+ private List<String> fieldValues;
+ }
+
+ @Data
+ public static class IntervalsBean {
+ private String type;
+ private String fieldKey;
+ private List<String> fieldValues;
+ }
+
+ @Data
+ public static class SortBean {
+ private String type;
+ private String fieldKey;
+ }
+ }
+}
+
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLParser.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLParser.java
new file mode 100644
index 0000000..cabf0fa
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DSLParser.java
@@ -0,0 +1,285 @@
+package com.mesalab.engine.common.pojo;
+
+import cn.hutool.core.util.StrUtil;
+import com.mesalab.engine.common.constant.SqlKeywords;
+import com.mesalab.engine.common.enums.EngineTypeEnum;
+import com.mesalab.engine.common.enums.MatchTypeEnum;
+import com.mesalab.engine.common.enums.RangeTypeEnum;
+import com.mesalab.engine.service.ClickHouseEngineService;
+import com.mesalab.engine.service.DruidEngineService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @Date: 2020-07-22 17:35
+ * @Author : liuyongqiang
+ * @ClassName : DslParser
+ * @Description : DSLParse解析抽象类
+ */
+@Slf4j
+@Component
+public abstract class DSLParser {
+
+ //默认数据大小
+ private final static String DEFAULT_LIMIT = "100000";
+
+ @Autowired
+ DruidEngineService druidEngine;
+
+ @Autowired
+ ClickHouseEngineService clickHouseEngine;
+
+
+ /**
+ * 解析granularity字段
+ */
+ public abstract String parseGranularity(DSLObject dslObject, String sql);
+
+ /**
+ * 解析 Sort对象
+ */
+ public abstract String parseSort(DSLObject dslObject, String sql);
+
+ /**
+ * 解析 Intervals对象
+ */
+ public abstract String parseIntervals(DSLObject dslObject, String sql);
+
+ /**
+ * @param dslObject:
+ * @Description: 单表全列查询
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 18:23
+ * @return: java.lang.String
+ **/
+ public String allQuery(DSLObject dslObject) {
+ String selectSql = this.allColumn(dslObject);
+ selectSql = parseQuery(dslObject, selectSql);
+ return selectSql;
+ }
+
+ /**
+ * @param dslObject:
+ * @Description: 查询表中的所有列
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 17:24
+ * @return: java.lang.String
+ **/
+ public String allColumn(DSLObject dslObject) {
+ BaseResult baseResult = null;
+
+ switch (EngineTypeEnum.getByEngine(dslObject.getDataEngine())) {
+ case CACULATION_ENGINE:
+ baseResult = druidEngine.schemaQuery(dslObject);
+ break;
+ case BUSINESS_ENGINE:
+ baseResult = clickHouseEngine.schemaQuery(dslObject);
+ break;
+ }
+
+ List<Map<String, Object>> results = (List<Map<String, Object>>) baseResult.getData();
+ String column = "";
+ for (Map<String, Object> map : results) {
+ String name = String.valueOf(map.get("name"));
+ //ClickHouse数据表中的时间字段做日期转换
+ if (dslObject.getDataEngine().equals(EngineTypeEnum.BUSINESS_ENGINE.getEngine())) {
+ if (name.lastIndexOf("_time") > 0) {
+ name = MessageFormat.format(" toDateTime({0}) as c_{1} ", name, name);
+ }
+ }
+ //Druid数据表中的时间字段做日期转换
+ if (dslObject.getDataEngine().equals(EngineTypeEnum.CACULATION_ENGINE.getEngine())) {
+ if (name.equalsIgnoreCase("__time")) {
+ name = "TIME_FORMAT(__time,'yyyy-MM-dd HH:mm:ss') as c_time";
+ }
+ }
+ //拼接表中的列名称
+ column = column.concat(name).concat(",");
+ }
+ //去掉最后一个逗号
+ column = column.substring(0, column.length() - 1);
+ return SqlKeywords.SELECT
+ .concat(column)
+ .concat(SqlKeywords.FROM)
+ .concat(dslObject.getDataSource());
+ }
+
+
+ /**
+ * @param dslObject:
+ * @param sql:
+ * @Description: 解析query对象
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 14:29
+ * @return: java.lang.String
+ **/
+ public String parseQuery(DSLObject dslObject, String sql) {
+ if (Objects.nonNull(dslObject.getQuery())) {
+ sql = parseGranularity(dslObject, sql);//解析Granularity
+ sql = parseMatch(dslObject, sql);//解析Match
+ sql = parseRange(dslObject, sql);//range解析
+ sql = parseIntervals(dslObject, sql);//解析Intervals
+ sql = parseSort(dslObject, sql);//解析Sort
+ }
+ sql = parseLimit(dslObject, sql);//limit解析
+ log.info("Parse Query SQL:{}", sql);
+ return sql;
+ }
+
+ /**
+ * @param dslObject:
+ * @param sql:
+ * @Description: 解析 Range对象
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 17:47
+ * @return: java.lang.String
+ **/
+ public String parseRange(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getRange())) {
+ return sql;
+ }
+ List<DSLObject.QueryBean.RangeBean> rangeBeanList = dslObject.getQuery().getRange();
+ for (DSLObject.QueryBean.RangeBean rangeBean : rangeBeanList) {
+ String type = rangeBean.getType();
+ String fieldKey = rangeBean.getFieldKey();
+ List<String> values = rangeBean.getFieldValues();
+
+ if (CollectionUtils.isEmpty(values)) break;
+
+ String join = sql.contains(SqlKeywords.WHERE) ? SqlKeywords.AND : SqlKeywords.WHERE;
+ String expr = RangeTypeEnum.getByType(type).getExpr();
+ sql = sql.concat(join).concat(fieldKey).concat(expr).concat(values.get(0));
+
+ if (values.size() > 1) {
+ for (int i = 1; i < values.size(); i++) {
+ sql = sql.concat(SqlKeywords.OR)
+ .concat(fieldKey)
+ .concat(expr)
+ .concat(values.get(i));
+ }
+ }
+ }
+ return sql;
+ }
+
+ /**
+ * @param dslObject:
+ * @param sql:
+ * @Description: 解析 Match对象
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 17:45
+ * @return: java.lang.String
+ **/
+ public String parseMatch(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getMatch())) {
+ return sql;
+ }
+ List<DSLObject.QueryBean.MatchBean> matchBeanList = dslObject.getQuery().getMatch();
+ int index = 0;
+ for (DSLObject.QueryBean.MatchBean matchBean : matchBeanList) {
+ index++;
+ String type = matchBean.getType();
+ String fieldKey = matchBean.getFieldKey();
+ List<String> values = matchBean.getFieldValues();
+
+ if (CollectionUtils.isEmpty(values)) break;
+
+ String join = index == 1 ? SqlKeywords.WHERE : SqlKeywords.AND;
+ sql = sql.concat(join)
+ .concat(fieldKey)
+ .concat(SqlKeywords.LIKE)
+ .concat(parseLike(values.get(0), type));
+
+ if (values.size() > 1) {
+ for (int i = 1; i < values.size(); i++) {
+ sql = sql.concat(SqlKeywords.OR)
+ .concat(fieldKey)
+ .concat(SqlKeywords.LIKE)
+ .concat(parseLike(values.get(i), type));
+ }
+ }
+ }
+ return sql;
+ }
+
+
+ /**
+ * @param dslObject:
+ * @param sql:
+ * @Description: 解析Limit对象
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 17:44
+ * @return: java.lang.String
+ **/
+ public String parseLimit(DSLObject dslObject, String sql) {
+ sql = sql.concat(SqlKeywords.LIMIT);
+ if (StrUtil.isNotEmpty(dslObject.getLimit())) {
+ sql = sql.concat(dslObject.getLimit());
+ } else {
+ sql = sql.concat(DEFAULT_LIMIT);
+ }
+ return sql;
+ }
+
+ /**
+ * @param value:
+ * @param type:
+ * @Description: match.type转换
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 11:47
+ * @return: java.lang.String
+ **/
+ public String parseLike(String value, String type) {
+ switch (MatchTypeEnum.getByType(type)) {
+ case REGEX:
+ return parseRegex(value);//正则匹配
+ case PREFIX:
+ return "'" + value + "%'";//前缀匹配
+ case SUFFIX:
+ return "'%" + value + "'";//后缀匹配
+ case EXACTLY:
+ return "'" + value + "'";//完全匹配
+ case SUBSTRING:
+ return "'%" + value + "%'";//子串匹配
+ }
+ return "";
+ }
+
+ /**
+ * @param value:
+ * @Description: parseRegex
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 11:49
+ * @return: java.lang.String
+ **/
+ public String parseRegex(String value) {
+ if (value.startsWith("$")) {
+ //以$开头,不以*结尾,无论关键字其他位置是否包含表示匹配方式的字符*和$,即表示完整匹配
+ value = value.replaceAll("\\$", "");
+ return "'" + value + "'";
+ } else if (value.startsWith("*") && value.endsWith("*")) {
+ //以*开始,以*结尾,无论关键字其他位置是否包含表示匹配方式的字符*和$,即表示子串匹配
+ value = value.replaceAll("\\*", "");
+ return "'%" + value + "%'";
+ } else if (value.startsWith("*")) {
+ //以*开始,不以*结尾,无论关键字其他位置是否包含表示匹配方式的字符*和$,即表示右匹配(后缀匹配)
+ value = value.replaceAll("\\*", "");
+ return "'%" + value + "'";
+ } else if (value.endsWith("*")) {
+ //以*结尾,不以*或者$开始,无论关键字其他位置是否包含表示匹配方式的字符*和$,即表示左匹配(前缀匹配)
+ value = value.replaceAll("\\*", "");
+ return "'" + value + "%'";
+ } else {
+ //不以*或者$开头,不以*结尾,无论关键字其他位置是否包含表示匹配方式的字符*和$,即表示子串匹配
+ return "'%" + value + "%'";
+ }
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DruidParser.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DruidParser.java
new file mode 100644
index 0000000..04728dd
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/pojo/DruidParser.java
@@ -0,0 +1,82 @@
+package com.mesalab.engine.common.pojo;
+
+import cn.hutool.core.util.StrUtil;
+import com.mesalab.engine.common.constant.SqlKeywords;
+import com.mesalab.engine.service.DruidEngineService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.text.MessageFormat;
+import java.util.List;
+
+/**
+ * @Date: 2020-07-22 17:00
+ * @Author : liuyongqiang
+ * @ClassName : DruidDSLParse
+ * @Description : Druid DSL语法解析器
+ */
+@Slf4j
+@Component
+public class DruidParser extends DSLParser {
+
+ @Autowired
+ DruidEngineService druidEngineService;
+
+ //表字段和数据类型查询SQL
+ public final static String SCHEAM_QUERY = "SELECT COLUMN_NAME as name, DATA_TYPE as type FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ''{0}''";
+
+ //查询所有表
+ public final static String TABLES_QUERY = "SELECT TABLE_NAME AS name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'TABLE'";
+
+
+ @Override
+ public String parseGranularity(DSLObject dslObject, String sql) {
+ if (StrUtil.isEmpty(dslObject.getQuery().getGranularity())) {
+ return sql;
+ }
+ String granularity = dslObject.getQuery().getGranularity();
+ sql = sql.replace(SqlKeywords.SELECT, SqlKeywords.SELECT
+ .concat(MessageFormat.format(SqlKeywords.D_TIME_FLOOR, granularity))
+ .concat(","));
+ return sql;
+ }
+
+
+ @Override
+ public String parseSort(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getSort())) {
+ return sql;
+ }
+ String sortType = dslObject.getQuery().getSort().get(0).getType();
+ sql = sql.concat(SqlKeywords.ORDER_BY
+ .concat(SqlKeywords.D_TIME))
+ .concat(sortType);
+ return sql;
+ }
+
+ @Override
+ public String parseIntervals(DSLObject dslObject, String sql) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getIntervals())) {
+ return sql;
+ }
+ List<DSLObject.QueryBean.IntervalsBean> intervalsBeanList = dslObject.getQuery().getIntervals();
+ for (DSLObject.QueryBean.IntervalsBean intervalsBean : intervalsBeanList) {
+ String fieldKey = intervalsBean.getFieldKey();
+ List<String> values = intervalsBean.getFieldValues();
+
+ if (CollectionUtils.isEmpty(values)) break;
+
+ String join = sql.contains(SqlKeywords.WHERE) ? SqlKeywords.AND : SqlKeywords.WHERE;
+
+ sql = sql.concat(join).concat(fieldKey)
+ .concat(SqlKeywords.BWTWEEN)
+ .concat("'" + values.get(0) + "'")
+ .concat(SqlKeywords.AND)
+ .concat("'" + values.get(1) + "'");
+ }
+ return sql;
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/BaseResultUtil.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/BaseResultUtil.java
new file mode 100644
index 0000000..80f6020
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/BaseResultUtil.java
@@ -0,0 +1,208 @@
+package com.mesalab.engine.common.util;
+
+
+import com.mesalab.engine.common.enums.ResultCodeEnum;
+import com.mesalab.engine.common.enums.ResultStatusEnum;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+
+import java.util.Map;
+
+/**
+ * BaseResult生成器
+ *
+ * @author dazzlzy
+ * @date 2018/4/1
+ */
+public class BaseResultUtil {
+
+ /**
+ * 生成返回结果
+ *
+ * @param status 返回HTTP响应码
+ * @param code 返回业务编码
+ * @param message 返回消息
+ * @param data 返回数据
+ * @param formatType 返回数据格式 JSON/CSV
+ * @param <T> 返回数据类型
+ * @return 返回结果
+ */
+ public static <T> BaseResult<T> generate(final int status, final String code, final String message, T data, T meta, final Map<String, Object> statistics, final String formatType) {
+ return new BaseResult<>(status, code, false, message, statistics, formatType, meta, data);
+ }
+
+ /**
+ * 操作成功响应结果, 默认结果
+ *
+ * @return 操作成功的默认响应结果
+ */
+ public static <T> BaseResult<T> success() {
+ return new BaseResult<>(ResultStatusEnum.SUCCESS.getCode(), ResultCodeEnum.EXECUTE_SUCCESS.getCode(),
+ true, ResultCodeEnum.EXECUTE_SUCCESS.getMessage(), null, null, null, null);
+ }
+
+ /**
+ * 操作成功响应结果, 自定义数据及信息
+ *
+ * @param message 自定义信息
+ * @param data 自定义数据
+ * @param <T> 自定义数据类型
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> success(final String message, final T data) {
+ return new BaseResult<>(ResultStatusEnum.SUCCESS.getCode(), ResultCodeEnum.EXECUTE_SUCCESS.getCode(),
+ true, message, null, null, null, data);
+ }
+
+ /**
+ * 操作成功响应结果, 自定义数据及信息, 统计结果
+ *
+ * @param message 自定义信息
+ * @param data 自定义数据
+ * @param <T> 自定义数据类型
+ * @param statistics 统计结果
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> success(final String message, final T data, final Map<String, Object> statistics) {
+ return new BaseResult<>(ResultStatusEnum.SUCCESS.getCode(), ResultCodeEnum.EXECUTE_SUCCESS.getCode(),
+ true, message, statistics, null, null, data);
+ }
+
+ /**
+ * 操作成功响应结果,自定义数据,默认信息
+ *
+ * @param data 自定义数据
+ * @param <T> 自定义数据类型
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> success(final T data) {
+ return new BaseResult<>(ResultStatusEnum.SUCCESS.getCode(), ResultCodeEnum.EXECUTE_SUCCESS.getCode(), true,
+ ResultCodeEnum.EXECUTE_SUCCESS.getMessage(), null, null, null, data);
+ }
+
+ /**
+ * 操作成功响应结果,自定义信息,无数据
+ *
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> success4Message(final String message) {
+ return new BaseResult<>(ResultStatusEnum.SUCCESS.getCode(), ResultCodeEnum.EXECUTE_SUCCESS.getCode(),
+ true, message, null, null, null, null);
+ }
+
+ /**
+ * 操作失败响应结果, 默认结果
+ *
+ * @return 操作成功的默认响应结果
+ */
+ public static <T> BaseResult<T> failure() {
+ return new BaseResult<>(ResultStatusEnum.FAIL.getCode(),
+ ResultCodeEnum.UNKNOW_ERROR.getCode(), false, ResultCodeEnum.UNKNOW_ERROR.getMessage(), null, null, null, null);
+ }
+
+ /**
+ * 操作失败响应结果, 自定义错误编码及信息
+ *
+ * @param status HTTP状态码
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> failure(final int status, final String message) {
+ return new BaseResult<>(status, ResultCodeEnum.UNKNOW_ERROR.getCode(), false, message, null, null, null, null);
+ }
+
+ /**
+ * 操作失败响应结果, 自定义错误编码及信息
+ *
+ * @param status HTTP 状态码
+ * @param code 返回业务编码
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> failure(final int status, final String code, final String message) {
+ return new BaseResult<>(status, code, false, message, null, null, null, null);
+ }
+
+ /**
+ * 操作失败响应结果, 自定义错误编码及信息
+ *
+ * @param status HTTP 状态码
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> failure(final int status, final String message, T data) {
+ return new BaseResult<>(status, ResultCodeEnum.UNKNOW_ERROR.getCode(), false, message, null, null, null, data);
+ }
+
+ /**
+ * 操作失败响应结果,自定义错误编码
+ *
+ * @param resultStatusEnum 自定义错误编码枚举
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> failure(final ResultStatusEnum resultStatusEnum) {
+ return new BaseResult<>(resultStatusEnum.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, resultStatusEnum.getMessage(), null, null, null, null);
+ }
+
+ /**
+ * 操作失败响应结果,自定义信息
+ *
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> failure(final String message) {
+ return new BaseResult<>(ResultStatusEnum.FAIL.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, message, null, null, null, null);
+ }
+
+ /**
+ * 异常响应结果, 默认结果
+ *
+ * @return 操作成功的默认响应结果
+ */
+ public static <T> BaseResult<T> error() {
+ return new BaseResult<>(ResultStatusEnum.SERVER_ERROR.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, ResultStatusEnum.SERVER_ERROR.getMessage(), null, null, null, null);
+ }
+
+ /**
+ * 异常响应结果, 自定义错误编码及信息
+ *
+ * @param code 自定义错误编码
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> error(final int code, final String message) {
+ return new BaseResult<>(code, ResultCodeEnum.UNKNOW_ERROR.getCode(), false, message, null, null, null, null);
+ }
+
+ /**
+ * 异常响应结果,自定义错误编码
+ *
+ * @param resultStatusEnum 自定义错误编码枚举
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> error(final ResultStatusEnum resultStatusEnum) {
+ return new BaseResult<>(resultStatusEnum.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, resultStatusEnum.getMessage(), null, null, null, null);
+ }
+
+ /**
+ * 业务异常响应结果
+ *
+ * @param be 业务异常
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> error(final BusinessException be) {
+ return new BaseResult<>(ResultStatusEnum.SERVER_ERROR.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, be.getErrorMessage(), null, null, null, null);
+ }
+
+ /**
+ * 异常响应结果,自定义信息
+ *
+ * @param message 自定义信息
+ * @return 响应结果
+ */
+ public static <T> BaseResult<T> error(final String message) {
+ return new BaseResult<>(ResultStatusEnum.SERVER_ERROR.getCode(), ResultCodeEnum.UNKNOW_ERROR.getCode(), false, message, null, null, null, null);
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/HttpClientUtil.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/HttpClientUtil.java
new file mode 100644
index 0000000..b76ceae
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/common/util/HttpClientUtil.java
@@ -0,0 +1,270 @@
+package com.mesalab.engine.common.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.mesalab.engine.common.exception.BusinessException;
+import org.apache.http.*;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.ConnectionKeepAliveStrategy;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicHeaderElementIterator;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+public class HttpClientUtil {
+ /**
+ * 全局连接池对象
+ */
+ private static final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
+
+ private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
+
+ /**
+ * 静态代码块配置连接池信息
+ */
+ static {
+
+ // 设置最大连接数
+ connManager.setMaxTotal(400);
+ // 设置每个连接的路由数
+ connManager.setDefaultMaxPerRoute(80);
+
+ }
+
+ /**
+ * 获取Http客户端连接对象
+ *
+ * @return Http客户端连接对象
+ */
+ public static CloseableHttpClient getHttpClient() {
+ // 创建Http请求配置参数
+ RequestConfig requestConfig = RequestConfig.custom()
+ // 获取连接超时时间
+ .setConnectionRequestTimeout(30000)
+ // 请求超时时间
+ .setConnectTimeout(10000)
+ // 响应超时时间
+ .setSocketTimeout(10000)
+ .build();
+
+ /**
+ * 测出超时重试机制为了防止超时不生效而设置
+ * 如果直接放回false,不重试
+ * 这里会根据情况进行判断是否重试
+ */
+ HttpRequestRetryHandler retry = (exception, executionCount, context) -> {
+ if (executionCount >= 3) {// 如果已经重试了3次,就放弃
+ return false;
+ }
+ if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试
+ return true;
+ }
+ if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
+ return false;
+ }
+ if (exception instanceof InterruptedIOException) {// 超时
+ return true;
+ }
+ if (exception instanceof UnknownHostException) {// 目标服务器不可达
+ return false;
+ }
+ if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
+ return false;
+ }
+ if (exception instanceof SSLException) {// ssl握手异常
+ return false;
+ }
+ HttpClientContext clientContext = HttpClientContext.adapt(context);
+ HttpRequest request = clientContext.getRequest();
+ // 如果请求是幂等的,就再次尝试
+ if (!(request instanceof HttpEntityEnclosingRequest)) {
+ return true;
+ }
+ return false;
+ };
+
+
+ ConnectionKeepAliveStrategy myStrategy = (response, context) -> {
+ HeaderElementIterator it = new BasicHeaderElementIterator
+ (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
+ while (it.hasNext()) {
+ HeaderElement he = it.nextElement();
+ String param = he.getName();
+ String value = he.getValue();
+ if (value != null && param.equalsIgnoreCase
+ ("timeout")) {
+ return Long.parseLong(value) * 1000;
+ }
+ }
+ return 60 * 1000;//如果没有约定,则默认定义时长为60s
+ };
+
+ // 创建httpClient
+ return HttpClients.custom()
+ // 把请求相关的超时信息设置到连接客户端
+ .setDefaultRequestConfig(requestConfig)
+ // 把请求重试设置到连接客户端
+ .setRetryHandler(retry)
+ .setKeepAliveStrategy(myStrategy)
+ // 配置连接池管理对象
+ .setConnectionManager(connManager)
+ .build();
+ }
+
+
+ /**
+ * GET请求
+ *
+ * @param url 请求地
+ * @return
+ */
+ public static String httpGet(String url, Header... headers) throws BusinessException {
+ String msg = "-1";
+
+ // 获取客户端连接对象
+ CloseableHttpClient httpClient = getHttpClient();
+ CloseableHttpResponse response = null;
+
+ try {
+
+ URL ul = new URL(url);
+
+ URI uri = new URI(ul.getProtocol(), null, ul.getHost(), ul.getPort(), ul.getPath(), ul.getQuery(), null);
+ logger.info("http get uri {}", uri);
+ // 创建GET请求对象
+ HttpGet httpGet = new HttpGet(uri);
+ if (Objects.nonNull(headers)) {
+ for (Header h : headers) {
+ httpGet.addHeader(h);
+ }
+ }
+ // 执行请求
+ response = httpClient.execute(httpGet);
+ int statusCode = response.getStatusLine().getStatusCode();
+ // 获取响应实体
+ HttpEntity entity = response.getEntity();
+ // 获取响应信息
+ msg = EntityUtils.toString(entity, "UTF-8");
+
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new BusinessException("Http get content is :" + msg);
+ }
+
+ } catch (URISyntaxException e) {
+ logger.error("URI 转换错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (ClientProtocolException e) {
+ logger.error("协议错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (ParseException e) {
+ logger.error("解析错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (IOException e) {
+ logger.error("IO错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } finally {
+ if (null != response) {
+ try {
+ EntityUtils.consume(response.getEntity());
+ response.close();
+ } catch (IOException e) {
+ logger.error("释放链接错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ }
+ }
+ }
+
+ return msg;
+ }
+
+ /**
+ * POST 请求
+ *
+ * @param url
+ * @param requestBody
+ * @return
+ */
+ public static String httpPost(String url, String requestBody, Header... headers) throws BusinessException {
+ String msg = "-1";
+ // 获取客户端连接对象
+ CloseableHttpClient httpClient = getHttpClient();
+ // 创建POST请求对象
+ CloseableHttpResponse response = null;
+ try {
+
+ URL ul = new URL(url);
+
+ URI uri = new URI(ul.getProtocol(), null, ul.getHost(), ul.getPort(), ul.getPath(), ul.getQuery(), null);
+
+ logger.info("http post uri:{}, http post body:{}", uri, requestBody);
+
+ HttpPost httpPost = new HttpPost(uri);
+ httpPost.setHeader("Content-Type", "application/json");
+ if (Objects.nonNull(headers)) {
+ for (Header h : headers) {
+ httpPost.addHeader(h);
+ }
+ }
+
+ if (StrUtil.isNotBlank(requestBody)) {
+ httpPost.setEntity(new ByteArrayEntity(requestBody.getBytes("utf-8")));
+ }
+
+ response = httpClient.execute(httpPost);
+ int statusCode = response.getStatusLine().getStatusCode();
+ // 获取响应实体
+ HttpEntity entity = response.getEntity();
+ // 获取响应信息
+ msg = EntityUtils.toString(entity, "UTF-8");
+
+ if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_CREATED) {
+ throw new BusinessException(msg);
+ }
+ } catch (URISyntaxException e) {
+ logger.error("URI 转换错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (ClientProtocolException e) {
+ logger.error("协议错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (ParseException e) {
+ logger.error("解析错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } catch (IOException e) {
+ logger.error("IO错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ } finally {
+ if (null != response) {
+ try {
+ EntityUtils.consumeQuietly(response.getEntity());
+ response.close();
+ } catch (IOException e) {
+ logger.error("释放链接错误: {}", e.getMessage());
+ throw new BusinessException(e.getMessage());
+ }
+ }
+ }
+ return msg;
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLRouter.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLRouter.java
new file mode 100644
index 0000000..f112311
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLRouter.java
@@ -0,0 +1,46 @@
+package com.mesalab.engine.component;
+
+
+import com.mesalab.engine.common.enums.EngineTypeEnum;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.DSLObject;
+import com.mesalab.engine.service.ClickHouseEngineService;
+import com.mesalab.engine.service.DruidEngineService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Date: 2020-07-20 18:11
+ * @Author : liuyongqiang
+ * @ClassName : DSLRouter
+ * @Description : DSL路由执行,负责调用查询引擎执行查询
+ */
+@Component
+public class DSLRouter {
+
+ @Autowired
+ private ClickHouseEngineService clickHouseEngineService;
+
+ @Autowired
+ private DruidEngineService druidEngineService;
+
+ /**
+ * @param dslObject:
+ * @Description: 查询引擎路由
+ * @Author: liuyongqiang
+ * @Date: 2020/7/22 15:59
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ public BaseResult executeRouter(DSLObject dslObject) throws BusinessException {
+ switch (EngineTypeEnum.getByEngine(dslObject.getDataEngine())) {
+ case CACULATION_ENGINE:
+ return druidEngineService.dslQuery(dslObject);
+ case BUSINESS_ENGINE:
+ return clickHouseEngineService.dslQuery(dslObject);
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLValidate.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLValidate.java
new file mode 100644
index 0000000..3d5cde7
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/component/DSLValidate.java
@@ -0,0 +1,367 @@
+package com.mesalab.engine.component;
+
+import cn.hutool.core.util.StrUtil;
+import com.mesalab.engine.common.enums.*;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.DSLObject;
+import com.mesalab.engine.service.ClickHouseEngineService;
+import com.mesalab.engine.service.DruidEngineService;
+import org.apache.http.HttpStatus;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * @Date: 2020-07-20 18:10
+ * @Author : wangwei
+ * @ClassName : DSLValidate
+ * @Description : DSL格式校验
+ */
+@Component
+public class DSLValidate {
+
+ public static Pattern periodOfPT = Pattern.compile("PT(\\d+)[SMH]", Pattern.CASE_INSENSITIVE);
+ public static Pattern periodOfP = Pattern.compile("P(\\d+)[DWMY]", Pattern.CASE_INSENSITIVE);
+ public static Pattern strFormatDateTime = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}", Pattern.CASE_INSENSITIVE);
+
+ @Autowired
+ DruidEngineService druidEngineService;
+ @Autowired
+ ClickHouseEngineService clickHouseEngineService;
+ @Value("${engine.maxCacheNum}")
+ int maxCacheNum;
+
+
+ /**
+ * 1.dbType字段必须在支持的范围之内(ArangoDB、ClickHouse、Druid)
+ * 2.dataSource数据表是否在库中存在
+ * 3.limit不能大于100000
+ * 4.时间格式校验
+ * 5.intervals.fieldValues长度必须==2
+ * 6.intervals.type暂时只支持between方式
+ * 7.sort.type支持desc和asc
+ * 8.match.type在:["exactly"|"prefix"|"suffix"|"substring"]内
+ * 9.range.type在:["gt"|"lt"|"eq"|"ge"|"le"|"ne"]
+ * 10.对表中的字段做是否存在的验证
+ * 11.match.fieldValues中不允许出现以*开头$结束的值
+ * 12.match.fieldKey字段类型不能为数值型
+ */
+ public void executeValidate(DSLObject dslObject) throws BusinessException {
+ if (Objects.isNull(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "DSLObject is invalid");
+ }
+ if (isValidEngine(dslObject.getDataEngine())) {
+ if (!isValidDataSource(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "dataSource is invalid");
+ }
+ } else {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "dataEngine is invalid");
+ }
+ if (Objects.isNull(dslObject.getQuery())) {
+ return;
+ }
+ if (!isValidLimit(dslObject.getLimit())) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "Limit is invalid");
+ }
+ if (!isValidGranularity(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "query.Granularity value is invalid");
+ }
+ if (!isValidMatch(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "query.Match type is invalid");
+ }
+ if (!isValidRange(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "query.Range type is invalid");
+ }
+ if (!isValidIntervals(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "query.Intervals is invalid");
+ }
+ if (!isValidSort(dslObject)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(),
+ "query.Sort type is invalid");
+ }
+ }
+
+ /**
+ * 校验dataSource
+ * 1.判定是否在{@link EngineTypeEnum}相应数据库中
+ *
+ * @param dslObject
+ * @return
+ */
+ private boolean isValidDataSource(DSLObject dslObject) {
+ if (StrUtil.isBlank(dslObject.getDataSource())) {
+ return false;
+ }
+ if (EngineTypeEnum.BUSINESS_ENGINE.getEngine().equals(dslObject.getDataEngine())) {
+ BaseResult baseResult = clickHouseEngineService.tablesQuery(dslObject);
+ return isDataSourceInResult(dslObject.getDataSource(), baseResult);
+ } else if (EngineTypeEnum.CACULATION_ENGINE.getEngine().equals(dslObject.getDataEngine())) {
+ BaseResult baseResult = druidEngineService.tablesQuery(dslObject);
+ return isDataSourceInResult(dslObject.getDataSource(), baseResult);
+ }
+ return false;
+ }
+
+
+ /**
+ * 获取表中所有列
+ *
+ * @param dslObject
+ * @return
+ */
+ private List getFields(DSLObject dslObject) {
+ if (EngineTypeEnum.BUSINESS_ENGINE.getEngine().equalsIgnoreCase(dslObject.getDataEngine())) {
+ BaseResult baseResult = clickHouseEngineService.schemaQuery(dslObject);
+ return fieldsInDataSource(baseResult);
+ } else if (EngineTypeEnum.CACULATION_ENGINE.getEngine().equalsIgnoreCase(dslObject.getDataEngine())) {
+ BaseResult baseResult = druidEngineService.schemaQuery(dslObject);
+ return fieldsInDataSource(baseResult);
+ } else if (EngineTypeEnum.ANALYSIS_ENGINE.getEngine().equalsIgnoreCase(dslObject.getDataEngine())) {
+ return new ArrayList();
+
+ }
+ throw new BusinessException(HttpStatus.SC_INTERNAL_SERVER_ERROR, ResultCodeEnum.UNKNOW_ERROR.getCode(), "field is invalid");
+ }
+
+ /**
+ * 表中所有字段
+ *
+ * @param baseResult
+ * @return
+ */
+ private List fieldsInDataSource(BaseResult baseResult) {
+ if (baseResult.isSuccess()) {
+ List<Map> data = (List<Map>) baseResult.getData();
+ List fields = new ArrayList<>();
+ data.forEach(o -> {
+ fields.add(o.get("name"));
+ }
+ );
+ return fields;
+ } else {
+ throw new BusinessException(HttpStatus.SC_INTERNAL_SERVER_ERROR, ResultCodeEnum.UNKNOW_ERROR.getCode(), "get fields error: " + baseResult);
+ }
+ }
+
+ /**
+ * 判断表是否在对应dataEngine对应库
+ *
+ * @param dataSource
+ * @param baseResult
+ * @return
+ */
+ private boolean isDataSourceInResult(String dataSource, BaseResult baseResult) {
+ if (baseResult.isSuccess()) {
+ List<Map> data = (List<Map>) baseResult.getData();
+ for (Map datum : data) {
+ if (datum.get("name").equals(dataSource)) {
+ return true;
+ }
+ }
+ } else {
+ throw new BusinessException(HttpStatus.SC_INTERNAL_SERVER_ERROR, ResultCodeEnum.UNKNOW_ERROR.getCode(), "get datasource error: " + baseResult);
+ }
+ return false;
+ }
+
+ /**
+ * 时间粒度校验, 遵循ISO8601 durations 定义
+ *
+ * @param dslObject
+ * @return
+ */
+ private boolean isValidGranularity(DSLObject dslObject) {
+ String granularity = dslObject.getQuery().getGranularity();
+ if (StrUtil.isBlank(granularity)) {
+ return true;
+ }
+ if (periodOfP.matcher(granularity).find() || periodOfPT.matcher(granularity).find()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 校验range:
+ * 1.是否属于{@link RangeTypeEnum}支持的类型
+ *
+ * @param dslObject
+ * @return
+ */
+ private boolean isValidRange(DSLObject dslObject) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getRange())) {
+ return true;
+ }
+ for (DSLObject.QueryBean.RangeBean rangeBean : dslObject.getQuery().getRange()) {
+ if (RangeTypeEnum.getByType(rangeBean.getType()) == null) {
+ return false;
+ }
+ checkField(dslObject, rangeBean.getFieldKey());
+ }
+ return true;
+ }
+
+ /**
+ * 校验match:
+ * 1.是否属于{@link MatchTypeEnum}限定类型
+ * 2.不能以*开始、或$结尾
+ *
+ * @param dslObject
+ * @return
+ */
+ private boolean isValidMatch(DSLObject dslObject) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getMatch())) {
+ return true;
+ }
+ for (DSLObject.QueryBean.MatchBean matchBean : dslObject.getQuery().getMatch()) {
+ if (MatchTypeEnum.getByType(matchBean.getType()) == null) {
+ return false;
+ }
+ for (String fieldValue : matchBean.getFieldValues()) {
+ if (fieldValue.startsWith("*") || fieldValue.endsWith("$")) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), "Match fieldValues cannot startWith '*' or endWith '$'");
+ }
+ }
+ checkField(dslObject, matchBean.getFieldKey());
+ }
+ return true;
+ }
+
+ /**
+ * 校验sort:
+ * 1.排序类型只能是DESC或ASC
+ * 2.不限大小写
+ *
+ * @param dslObject
+ * @return
+ */
+ private boolean isValidSort(DSLObject dslObject) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getSort())) {
+ return true;
+ }
+ for (DSLObject.QueryBean.SortBean sortBean : dslObject.getQuery().getSort()) {
+ if (!"DESC".equalsIgnoreCase(sortBean.getType()) && !"ASC".equalsIgnoreCase(sortBean.getType())) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), "sort type is illegal, must be asc or desc");
+ }
+ checkField(dslObject, sortBean.getFieldKey());
+ }
+ return true;
+ }
+
+ /**
+ * 校验limit:
+ * 1.limit范围[0,100000]
+ *
+ * @param limitStr
+ * @return
+ */
+ private boolean isValidLimit(String limitStr) {
+ if (StrUtil.isBlank(limitStr)) {
+ return true;
+ }
+ int limit;
+ String[] split = limitStr.split(",");
+ if (split.length > 2) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), "Limit format error");
+ } else if (split.length == 2) {
+ limit = Integer.parseInt(split[0]) + Integer.parseInt(split[1]);
+ } else {
+ limit = Integer.parseInt(split[split.length - 1]);
+ }
+ if (0 > limit || maxCacheNum < limit) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), "Limit must be in the range [0, " + maxCacheNum + "]");
+ }
+ return true;
+ }
+
+ /**
+ * 校验Interval:
+ * 1.目前只支持between类型
+ * 2.时间区间必须是 开始时间 < 结束时间
+ *
+ * @param dslObject
+ */
+ private boolean isValidIntervals(DSLObject dslObject) {
+ if (CollectionUtils.isEmpty(dslObject.getQuery().getIntervals())) {
+ return true;
+ }
+ for (DSLObject.QueryBean.IntervalsBean interval : dslObject.getQuery().getIntervals()) {
+ if (Objects.isNull(IntervalTypeEnum.getByType(interval.getType().toUpperCase()))) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), "Interval type not support " + interval.getType());
+ }
+ if (interval.getFieldValues().size() != 2) {
+ return false;
+ }
+ for (String fieldValue : interval.getFieldValues()) {
+ if (!strFormatDateTime.matcher(fieldValue).find()) {
+ return false;
+ }
+ }
+
+ try {
+ DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
+ if (dateTimeFormatter.parseMillis(interval.getFieldValues().get(1)) - dateTimeFormatter.parseMillis(interval.getFieldValues().get(0)) < 0) {
+ throw new RuntimeException("Interval fieldValue should be [start, end]");
+ }
+ } catch (Exception e) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), e.getMessage());
+ }
+ checkField(dslObject, interval.getFieldKey());
+ }
+ return true;
+ }
+
+ /**
+ * 校验dataEngine:
+ * 1.是否属于{@link EngineTypeEnum}支持的类型
+ *
+ * @param dataEngine
+ * @return
+ */
+ private boolean isValidEngine(String dataEngine) {
+ if (StrUtil.isBlank(dataEngine)) {
+ return false;
+ }
+ for (EngineTypeEnum engineTypeEnum : EngineTypeEnum.values()) {
+ if (engineTypeEnum.getEngine().equals(dataEngine)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 校验field是否是相应表字段
+ *
+ * @param dslObject
+ * @param fieldKey
+ */
+ private void checkField(DSLObject dslObject, String fieldKey) {
+ //AnangoDB暂不校验
+ if (EngineTypeEnum.ANALYSIS_ENGINE.getEngine().equalsIgnoreCase(dslObject.getDataEngine())) {
+ return;
+ }
+ if (!getFields(dslObject).contains(fieldKey)) {
+ throw new BusinessException(HttpStatus.SC_BAD_REQUEST, ResultCodeEnum.PARAM_SYNTAX_ERROR.getCode(), fieldKey + " is invalid");
+ }
+ }
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/controller/DslQueryController.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/controller/DslQueryController.java
new file mode 100644
index 0000000..620073a
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/controller/DslQueryController.java
@@ -0,0 +1,47 @@
+package com.mesalab.engine.controller;
+
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.DSLObject;
+import com.mesalab.engine.common.util.BaseResultUtil;
+import com.mesalab.engine.component.DSLRouter;
+import com.mesalab.engine.component.DSLValidate;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @Date: 2020-08-17 10:55
+ * @Author : liuyongqiang
+ * @ClassName : DslQueryController
+ * @Description : 基于DSL语言的单表数据查询
+ */
+@Slf4j
+@RestController
+@RequestMapping("/v1/dsl/")
+public class DslQueryController {
+
+ @Autowired
+ protected DSLRouter dslRouter;
+
+ @Autowired
+ protected DSLValidate dslValidate;
+
+ @PostMapping("query")
+ public BaseResult query(@RequestBody DSLObject dslObject) {
+ BaseResult baseResult;
+ try {
+ dslValidate.executeValidate(dslObject);
+ baseResult = dslRouter.executeRouter(dslObject);
+ } catch (BusinessException e) {
+ return BaseResultUtil.failure(
+ e.getErrorStatus(),
+ e.getErrorCode(),
+ e.getErrorMessage());
+ }
+ return baseResult;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/ClickHouseEngineService.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/ClickHouseEngineService.java
new file mode 100644
index 0000000..d82cca0
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/ClickHouseEngineService.java
@@ -0,0 +1,10 @@
+package com.mesalab.engine.service;
+
+/**
+ * @Date: 2020-07-20 10:00
+ * @Author : liuyongqiang
+ * @ClassName : ClickHouseEngineService
+ * @Description : ClickHouse数据查询引擎接口
+ */
+public interface ClickHouseEngineService extends CommonEngineService {
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/CommonEngineService.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/CommonEngineService.java
new file mode 100644
index 0000000..5abeac6
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/CommonEngineService.java
@@ -0,0 +1,47 @@
+package com.mesalab.engine.service;
+
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.DSLObject;
+
+/**
+ * @Date: 2020-07-20 18:39
+ * @Author : liuyongqiang
+ * @ClassName : CommonEngineService
+ * @Description : 公共查询接口定义
+ */
+public interface CommonEngineService {
+
+
+ /**
+ * @param dslObject: DSL查询对象
+ * @Description: 单表数据查询
+ * @Author: liuyongqiang
+ * @Date: 2020/7/21 9:41
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ BaseResult dslQuery(DSLObject dslObject) throws BusinessException;
+
+
+ /**
+ * @param dslObject:
+ * @Description: 查询数据源下的所有表
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 15:16
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ BaseResult tablesQuery(DSLObject dslObject) throws BusinessException;
+
+
+ /**
+ * @param dslObject:
+ * @Description: 查询指定表中的所有字段
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 11:08
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ BaseResult schemaQuery(DSLObject dslObject) throws BusinessException;
+
+
+}
+
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/DruidEngineService.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/DruidEngineService.java
new file mode 100644
index 0000000..d4ce603
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/DruidEngineService.java
@@ -0,0 +1,11 @@
+package com.mesalab.engine.service;
+
+/**
+ * @Date: 2020-07-20 10:02
+ * @Author : liuyongqiang
+ * @ClassName : DruidEngineService
+ * @Description : Druid数据查询引擎接口
+ */
+public interface DruidEngineService extends CommonEngineService {
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/EngineExecuteService.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/EngineExecuteService.java
new file mode 100644
index 0000000..ae87d2c
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/EngineExecuteService.java
@@ -0,0 +1,31 @@
+package com.mesalab.engine.service;
+
+import com.mesalab.engine.common.exception.BusinessException;
+
+/**
+ * @Date: 2020-07-22 16:11
+ * @Author : liuyongqiang
+ * @ClassName : EngineExecuteService
+ * @Description : 引擎执行查询接口
+ */
+public interface EngineExecuteService {
+
+ /**
+ * @param sql:
+ * @Description: 执行Druid查询语句
+ * @Author: liuyongqiang
+ * @Date: 2020/7/22 16:16
+ * @return: java.lang.String
+ **/
+ String executeDruidSql(String sql) throws BusinessException;
+
+ /**
+ * @param sql:
+ * @Description: 执行ClickHouse查询语句
+ * @Author: liuyongqiang
+ * @Date: 2020/7/22 16:16
+ * @return: java.lang.String
+ **/
+ String executeClickHouseSql(String sql) throws BusinessException;
+
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/ClickHouseEngineServiceImpl.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/ClickHouseEngineServiceImpl.java
new file mode 100644
index 0000000..25e6106
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/ClickHouseEngineServiceImpl.java
@@ -0,0 +1,91 @@
+package com.mesalab.engine.service.impl;
+
+
+import com.google.gson.Gson;
+import com.mesalab.engine.common.dto.ClickHouseResult;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.ClickHouseParser;
+import com.mesalab.engine.common.pojo.DSLObject;
+import com.mesalab.engine.common.util.BaseResultUtil;
+import com.mesalab.engine.service.ClickHouseEngineService;
+import com.mesalab.engine.service.EngineExecuteService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.MessageFormat;
+
+/**
+ * @Date: 2020-07-20 10:04
+ * @Author : liuyongqiang
+ * @ClassName : ClickHouseEngineServiceImpl
+ * @Description : ClickHouse数据查询引擎实现
+ */
+@Slf4j
+@Service("clickHouseEngineService")
+public class ClickHouseEngineServiceImpl implements ClickHouseEngineService {
+
+ @Autowired
+ ClickHouseParser clickHouseDSLParser;
+
+ @Autowired
+ EngineExecuteService executeService;
+
+ /**
+ * @param dslObject: DSL查询对象
+ * @Description: 单表数据查询
+ * @Author: liuyongqiang
+ * @Date: 2020/7/21 9:41
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult dslQuery(DSLObject dslObject) throws BusinessException {
+ String executeSql = clickHouseDSLParser.allQuery(dslObject);
+ String jsonResult = executeService.executeClickHouseSql(executeSql);
+ return assemble(jsonResult);
+ }
+
+ /**
+ * @param dslObject:
+ * @Description: 查询数据源下的所有表
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 15:16
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult tablesQuery(DSLObject dslObject) throws BusinessException {
+ String executeSql = ClickHouseParser.TABLES_QUERY;
+ String jsonResult = executeService.executeClickHouseSql(executeSql);
+ return assemble(jsonResult);
+ }
+
+ /**
+ * @param dslObject:
+ * @Description: 查询指定表中的所有字段
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 11:08
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult schemaQuery(DSLObject dslObject) throws BusinessException {
+ String dataSource = dslObject.getDataSource();
+ String executeSql = MessageFormat.format(ClickHouseParser.SCHEAM_QUERY, dataSource);
+ return assemble(executeService.executeClickHouseSql(executeSql));
+ }
+
+ /**
+ * @param jsonResult:
+ * @Description: 组装返回结果
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 16:52
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ private BaseResult assemble(String jsonResult) {
+ ClickHouseResult clickHouseResultDTO = new Gson().fromJson(jsonResult, ClickHouseResult.class);
+ BaseResult baseResult = BaseResultUtil.success(clickHouseResultDTO.getData());
+ baseResult.setStatistics(clickHouseResultDTO.getStatistics());
+ baseResult.setMeta(clickHouseResultDTO.getMeta());
+ return baseResult;
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/DruidEngineServiceImpl.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/DruidEngineServiceImpl.java
new file mode 100644
index 0000000..d9e7df0
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/DruidEngineServiceImpl.java
@@ -0,0 +1,97 @@
+package com.mesalab.engine.service.impl;
+
+import com.google.gson.Gson;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.pojo.BaseResult;
+import com.mesalab.engine.common.pojo.DSLObject;
+import com.mesalab.engine.common.pojo.DruidParser;
+import com.mesalab.engine.common.util.BaseResultUtil;
+import com.mesalab.engine.service.DruidEngineService;
+import com.mesalab.engine.service.EngineExecuteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Date: 2020-07-20 10:05
+ * @Author : liuyongqiang
+ * @ClassName : DruidEngineServiceImpl
+ * @Description : Druid数据查询引擎实现
+ */
+@Service("druidEngineService")
+public class DruidEngineServiceImpl implements DruidEngineService {
+
+ @Autowired
+ DruidParser druidDSLParse;
+
+ @Autowired
+ EngineExecuteService executeService;
+
+ /**
+ * @param dslObject: DSL查询对象
+ * @Description: 单表数据查询
+ * @Author: liuyongqiang
+ * @Date: 2020/7/21 9:41
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult dslQuery(DSLObject dslObject) throws BusinessException {
+ String executeSql = druidDSLParse.allQuery(dslObject);
+ String jsonResult = executeService.executeDruidSql(executeSql);
+ List<Map<String, Object>> results = new Gson().fromJson(jsonResult, List.class);
+ dataFormat(results);
+ return BaseResultUtil.success(results);
+ }
+
+
+ /**
+ * @param dslObject:
+ * @Description: 查询数据源下的所有表
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 15:16
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult tablesQuery(DSLObject dslObject) throws BusinessException {
+ String executeSql = DruidParser.TABLES_QUERY;
+ String jsonResult = executeService.executeDruidSql(executeSql);
+ List<Map<String, Object>> results = new Gson().fromJson(jsonResult, List.class);
+ return BaseResultUtil.success(results);
+ }
+
+ /**
+ * @param dslObject:
+ * @Description: 查询指定表中的所有字段
+ * @Author: liuyongqiang
+ * @Date: 2020/7/23 11:08
+ * @return: com.mesalab.common.base.BaseResult
+ **/
+ @Override
+ public BaseResult schemaQuery(DSLObject dslObject) throws BusinessException {
+ String dataSource = dslObject.getDataSource();
+ String executeSql = MessageFormat.format(DruidParser.SCHEAM_QUERY, dataSource);
+ String jsonResult = executeService.executeDruidSql(executeSql);
+ List<Map<String, Object>> results = new Gson().fromJson(jsonResult, List.class);
+ return BaseResultUtil.success(results);
+ }
+
+ /**
+ * @param results:
+ * @Description: 数据格式化
+ * @Author: liuyongqiang
+ * @Date: 2020/7/28 16:56
+ * @return: java.util.List<java.util.Map < java.lang.String, java.lang.Object>>
+ **/
+ private void dataFormat(List<Map<String, Object>> results) {
+ for (Map<String, Object> map : results) {
+ for (String key : map.keySet()) {
+ if (map.get(key) instanceof Double) {
+ map.put(key, ((Double) map.get(key)).longValue());
+ }
+ }
+ }
+ }
+}
diff --git a/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/EngineExecuteServiceImpl.java b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/EngineExecuteServiceImpl.java
new file mode 100644
index 0000000..67627b8
--- /dev/null
+++ b/galaxy-query-engine/src/main/java/com/mesalab/engine/service/impl/EngineExecuteServiceImpl.java
@@ -0,0 +1,62 @@
+package com.mesalab.engine.service.impl;
+
+
+import com.google.gson.Gson;
+import com.mesalab.engine.common.exception.BusinessException;
+import com.mesalab.engine.common.util.HttpClientUtil;
+import com.mesalab.engine.service.EngineExecuteService;
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @Date: 2020-07-22 16:15
+ * @Author : liuyongqiang
+ * @ClassName : EngineExecuteServiceImpl
+ * @Description : 引擎执行查询实现
+ */
+@Service("engineExecuteService")
+public class EngineExecuteServiceImpl implements EngineExecuteService {
+
+ final Header JSON_HEADER = new BasicHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+
+ @Value("${druid.query.url}")
+ String druidQueryUrl;
+ @Value("${clickhouse.query.url}")
+ String clickHouseQueryUrl;
+ @Value("${clickhouse.dbname}")
+ String clickHouseDbName;
+ @Value("${clickhouse.real.time.username}")
+ String ckRealTimeUsername;
+ @Value("${clickhouse.real.time.password}")
+ String ckRealTimePassword;
+
+ @Override
+ public String executeDruidSql(String sql) throws BusinessException {
+ Map<String, String> queryParams = new HashMap<>();
+ queryParams.put("query", sql);
+ String jsonParam = new Gson().toJson(queryParams);
+ return HttpClientUtil.httpPost(druidQueryUrl, jsonParam, JSON_HEADER);
+ }
+
+ @Override
+ public String executeClickHouseSql(String sql) throws BusinessException {
+ String url = clickHouseQueryUrl
+ .concat("/?user=")
+ .concat(ckRealTimeUsername)
+ .concat("&password=")
+ .concat(ckRealTimePassword)
+ .concat("&database=")
+ .concat(clickHouseDbName)
+ .concat("&query=").concat(sql)
+ .concat(" FORMAT JSON");
+ return HttpClientUtil.httpGet(url);
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index fd951f9..8f32874 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,8 +31,6 @@
</distributionManagement>
<modules>
- <!--公共模块-->
- <module>galaxy-common</module>
<!--查询网关-->
<module>galaxy-gateway</module>
<!--认证中心-->
@@ -51,6 +49,7 @@
<module>galaxy-job-executor</module>
<!--定时任务核心模块-->
<module>galaxy-job-core</module>
+ <!--数据查询引擎-->
<module>galaxy-query-engine</module>
</modules>
@@ -71,7 +70,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<commons.lang3.version>3.5</commons.lang3.version>
<calcite.core.version>1.22.0</calcite.core.version>
- <spring.cloud.version>Hoxton.SR1</spring.cloud.version>
+ <spring.cloud.version>Finchley.RC2</spring.cloud.version>
<spring.boot.version>2.0.2.RELEASE</spring.boot.version>
<galaxy.common.version>1.0-SNAPSHOT</galaxy.common.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -84,13 +83,6 @@
<dependencyManagement>
<dependencies>
<dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring.cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>