mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532a00518c | ||
|
|
895f38b6f7 | ||
|
|
eba3a8ad34 | ||
|
|
6813582ea0 | ||
|
|
6b6e54e95f | ||
|
|
26260c79f1 | ||
|
|
0a42932db2 | ||
|
|
3b4678d682 | ||
|
|
c0458ccf0e | ||
|
|
c7c70208ff | ||
|
|
3a38200448 | ||
|
|
b72e280990 | ||
|
|
0541614dad | ||
|
|
0beb3cefd3 | ||
|
|
afdf18398c | ||
|
|
bdf7df933b |
35
.github/workflows/mac-ci.yml
vendored
Normal file
35
.github/workflows/mac-ci.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: supersonic mac CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest # Specify a macOS runner
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/Library/Caches/Maven # macOS Maven cache path
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
|
||||
- name: Test with Maven
|
||||
run: mvn test
|
||||
@@ -1,4 +1,4 @@
|
||||
name: supersonic CI
|
||||
name: supersonic ubuntu CI
|
||||
|
||||
on:
|
||||
push:
|
||||
35
.github/workflows/windows-ci.yml
vendored
Normal file
35
.github/workflows/windows-ci.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: supersonic windows CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest # Specify a Windows runner
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~\.m2 # Windows uses a backslash for paths
|
||||
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: ${{ runner.os }}-m2
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
|
||||
- name: Test with Maven
|
||||
run: mvn test
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# SuperSonic (超音数)
|
||||
|
||||
**SuperSonic is the next-generation LLM-powered data analytics platform that integrates ChatBI and HeadlessBI**. SuperSonic provides a chat interface that empowers users to query data using natural language and visualize the results with suitable charts. To enable such experience, the only thing necessary is to build logical semantic models (definition of entities/metrics/dimensions/tags, along with their meaning, context and relationships) on top of physical data models, and **no data modification or copying** is required. Meanwhile, SuperSonic is designed to be **highly extensible**, allowing custom functionalities to be added and configured with Java SPI.
|
||||
**SuperSonic is the next-generation LLM-powered data analytics platform that integrates ChatBI and HeadlessBI**. SuperSonic provides a chat interface that empowers users to query data using natural language and visualize the results with suitable charts. To enable such experience, the only thing necessary is to build logical semantic models (definition of entities/metrics/dimensions/tags, along with their meaning, context and relationships) with semantic layer, and **no data modification or copying** is required. Meanwhile, SuperSonic is designed to be **highly extensible**, allowing custom functionalities to be added and configured with Java SPI.
|
||||
|
||||
<img src="./docs/images/supersonic_demo.gif" height="100%" width="100%" align="center"/>
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
The emergence of Large Language Model (LLM) like ChatGPT is reshaping the way information is retrieved. In the field of data analytics, both academia and industry are primarily focused on leveraging LLM to convert natural language into SQL (so called Text2SQL or NL2SQL). While some approaches exhibit promising results, their **reliability** and **efficiency** are insufficient for real-world applications.
|
||||
|
||||
From our perspective, the key to filling the real-world gap lies in three aspects:
|
||||
1. Integrate ChatBI with HeadlessBI encapsulating underlying data context (joins, keys, formulas, etc) to **reduce complexity**.
|
||||
1. Integrate ChatBI with HeadlessBI encapsulating underlying data context (joins, keys, formulas, etc) to **reduce complexity**.
|
||||
<img src="./docs/images/supersonic_ideas.png" height="65%" width="65%" align="center"/>
|
||||
2. Augment the LLM with schema mappers(as a kind of preprocessor) and semantic correctors(as a kind of postprocessor) to **mitigate hallucination**.
|
||||
3. Utilize rule-based schema parsers when necessary to **improve efficiency**(in terms of latency and cost).
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
在我们看来,为了在实际场景发挥价值,有三个关键点:
|
||||
1. 融合HeadlessBI,通过统一语义层封装底层数据细节(关联、键值、公式等),降低SQL生成的**复杂度**。
|
||||
|
||||
<img src="./docs/images/supersonic_ideas.png" height="65%" width="65%" align="center"/>
|
||||
2. 通过一前一后的模式映射器和语义修正器,来缓解LLM常见的**幻觉**现象。
|
||||
3. 设计启发式的规则,在一些特定场景提升语义解析的**效率**。
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import lombok.Data;
|
||||
public class QueryReq {
|
||||
private String queryText;
|
||||
private Integer chatId;
|
||||
private Long modelId;
|
||||
private Long viewId;
|
||||
private User user;
|
||||
private QueryFilters queryFilters;
|
||||
private boolean saveAnswer = true;
|
||||
|
||||
@@ -140,4 +140,19 @@ public abstract class BaseSemanticCorrector implements SemanticCorrector {
|
||||
return semanticSchema.getMetrics(viewId);
|
||||
}
|
||||
|
||||
protected Set<String> getDimensions(Long viewId, SemanticSchema semanticSchema) {
|
||||
Set<String> dimensions = semanticSchema.getDimensions(viewId).stream()
|
||||
.flatMap(
|
||||
schemaElement -> {
|
||||
Set<String> elements = new HashSet<>();
|
||||
elements.add(schemaElement.getName());
|
||||
if (!CollectionUtils.isEmpty(schemaElement.getAlias())) {
|
||||
elements.addAll(schemaElement.getAlias());
|
||||
}
|
||||
return elements.stream();
|
||||
}
|
||||
).collect(Collectors.toSet());
|
||||
dimensions.add(TimeDimensionEnum.DAY.getChName());
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.tencent.supersonic.chat.core.corrector;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.core.pojo.QueryContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Correcting SQL syntax, primarily including fixes to select, where, groupBy, and Having clauses
|
||||
*/
|
||||
@Slf4j
|
||||
public class GrammarCorrector extends BaseSemanticCorrector {
|
||||
|
||||
private List<BaseSemanticCorrector> correctors;
|
||||
|
||||
public GrammarCorrector() {
|
||||
correctors = new ArrayList<>();
|
||||
correctors.add(new SelectCorrector());
|
||||
correctors.add(new WhereCorrector());
|
||||
correctors.add(new GroupByCorrector());
|
||||
correctors.add(new HavingCorrector());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doCorrect(QueryContext queryContext, SemanticParseInfo semanticParseInfo) {
|
||||
for (BaseSemanticCorrector corrector : correctors) {
|
||||
corrector.correct(queryContext, semanticParseInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,12 @@ import com.tencent.supersonic.headless.api.pojo.response.ViewResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.ViewService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Perform SQL corrections on the "Group by" section in S2SQL.
|
||||
@@ -82,22 +80,6 @@ public class GroupByCorrector extends BaseSemanticCorrector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<String> getDimensions(Long viewId, SemanticSchema semanticSchema) {
|
||||
Set<String> dimensions = semanticSchema.getDimensions(viewId).stream()
|
||||
.flatMap(
|
||||
schemaElement -> {
|
||||
Set<String> elements = new HashSet<>();
|
||||
elements.add(schemaElement.getName());
|
||||
if (!CollectionUtils.isEmpty(schemaElement.getAlias())) {
|
||||
elements.addAll(schemaElement.getAlias());
|
||||
}
|
||||
return elements.stream();
|
||||
}
|
||||
).collect(Collectors.toSet());
|
||||
dimensions.add(TimeDimensionEnum.DAY.getChName());
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
private void addGroupByFields(QueryContext queryContext, SemanticParseInfo semanticParseInfo) {
|
||||
Long viewId = semanticParseInfo.getViewId();
|
||||
//add dimension group by
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
package com.tencent.supersonic.chat.core.corrector;
|
||||
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticSchema;
|
||||
import com.tencent.supersonic.chat.api.pojo.response.SqlInfo;
|
||||
import com.tencent.supersonic.chat.core.parser.sql.llm.ParseResult;
|
||||
import com.tencent.supersonic.chat.core.pojo.QueryContext;
|
||||
import com.tencent.supersonic.chat.core.query.llm.s2sql.LLMReq.ElementValue;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.enums.FilterOperatorEnum;
|
||||
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||
import com.tencent.supersonic.common.util.DateUtils;
|
||||
import com.tencent.supersonic.common.util.JsonUtil;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.AggregateEnum;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.FieldExpression;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlRemoveHelper;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlReplaceHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlSelectHelper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Perform schema corrections on the Schema information in S2SQL.
|
||||
@@ -27,6 +35,8 @@ public class SchemaCorrector extends BaseSemanticCorrector {
|
||||
@Override
|
||||
public void doCorrect(QueryContext queryContext, SemanticParseInfo semanticParseInfo) {
|
||||
|
||||
removeFilterIfNotInLinkingValue(queryContext, semanticParseInfo);
|
||||
|
||||
correctAggFunction(semanticParseInfo);
|
||||
|
||||
replaceAlias(semanticParseInfo);
|
||||
@@ -105,4 +115,35 @@ public class SchemaCorrector extends BaseSemanticCorrector {
|
||||
String sql = SqlReplaceHelper.replaceValue(sqlInfo.getCorrectS2SQL(), filedNameToValueMap, false);
|
||||
sqlInfo.setCorrectS2SQL(sql);
|
||||
}
|
||||
|
||||
public void removeFilterIfNotInLinkingValue(QueryContext queryContext, SemanticParseInfo semanticParseInfo) {
|
||||
SqlInfo sqlInfo = semanticParseInfo.getSqlInfo();
|
||||
String correctS2SQL = sqlInfo.getCorrectS2SQL();
|
||||
List<FieldExpression> whereExpressionList = SqlSelectHelper.getWhereExpressions(correctS2SQL);
|
||||
if (CollectionUtils.isEmpty(whereExpressionList)) {
|
||||
return;
|
||||
}
|
||||
List<ElementValue> linkingValues = getLinkingValues(semanticParseInfo);
|
||||
SemanticSchema semanticSchema = queryContext.getSemanticSchema();
|
||||
Set<String> dimensions = getDimensions(semanticParseInfo.getViewId(), semanticSchema);
|
||||
|
||||
if (CollectionUtils.isEmpty(linkingValues)) {
|
||||
linkingValues = new ArrayList<>();
|
||||
}
|
||||
Set<String> linkingFieldNames = linkingValues.stream().map(linking -> linking.getFieldName())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> removeFieldNames = whereExpressionList.stream()
|
||||
.filter(fieldExpression -> StringUtils.isBlank(fieldExpression.getFunction()))
|
||||
.filter(fieldExpression -> !TimeDimensionEnum.containsTimeDimension(fieldExpression.getFieldName()))
|
||||
.filter(fieldExpression -> FilterOperatorEnum.EQUALS.getValue().equals(fieldExpression.getOperator()))
|
||||
.filter(fieldExpression -> dimensions.contains(fieldExpression.getFieldName()))
|
||||
.filter(fieldExpression -> !DateUtils.isAnyDateString(fieldExpression.getFieldValue().toString()))
|
||||
.filter(fieldExpression -> !linkingFieldNames.contains(fieldExpression.getFieldName()))
|
||||
.map(fieldExpression -> fieldExpression.getFieldName()).collect(Collectors.toSet());
|
||||
|
||||
String sql = SqlRemoveHelper.removeWhereCondition(correctS2SQL, removeFieldNames);
|
||||
sqlInfo.setCorrectS2SQL(sql);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,20 +23,22 @@ public class AgentCheckParser implements SemanticParser {
|
||||
@Override
|
||||
public void parse(QueryContext queryContext, ChatContext chatContext) {
|
||||
List<SemanticQuery> queries = queryContext.getCandidateQueries();
|
||||
agentCanSupport(queryContext, queries);
|
||||
log.info("query size before agent filter:{}", queryContext.getCandidateQueries().size());
|
||||
filterQueries(queryContext, queries);
|
||||
log.info("query size after agent filter: {}", queryContext.getCandidateQueries().size());
|
||||
}
|
||||
|
||||
private void agentCanSupport(QueryContext queryContext, List<SemanticQuery> queries) {
|
||||
private void filterQueries(QueryContext queryContext, List<SemanticQuery> queries) {
|
||||
Agent agent = queryContext.getAgent();
|
||||
if (agent == null) {
|
||||
return;
|
||||
}
|
||||
List<RuleParserTool> queryTools = getRuleTools(agent);
|
||||
if (CollectionUtils.isEmpty(queryTools)) {
|
||||
queries.clear();
|
||||
queryContext.setCandidateQueries(Lists.newArrayList());
|
||||
return;
|
||||
}
|
||||
log.info("queries resolved:{} {}", agent.getName(),
|
||||
log.info("agent name :{}, queries resolved: {}", agent.getName(),
|
||||
queries.stream().map(SemanticQuery::getQueryMode).collect(Collectors.toList()));
|
||||
queries.removeIf(query -> {
|
||||
for (RuleParserTool tool : queryTools) {
|
||||
@@ -46,10 +48,14 @@ public class AgentCheckParser implements SemanticParser {
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(tool.getQueryTypes())) {
|
||||
if (QueryManager.isTagQuery(query.getQueryMode())) {
|
||||
return !tool.getQueryTypes().contains(QueryType.TAG.name());
|
||||
if (!tool.getQueryTypes().contains(QueryType.TAG.name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (QueryManager.isMetricQuery(query.getQueryMode())) {
|
||||
return !tool.getQueryTypes().contains(QueryType.METRIC.name());
|
||||
if (!tool.getQueryTypes().contains(QueryType.METRIC.name())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(tool.getViewIds())) {
|
||||
@@ -62,7 +68,8 @@ public class AgentCheckParser implements SemanticParser {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
log.info("rule queries witch can be supported by agent :{} {}", agent.getName(),
|
||||
queryContext.setCandidateQueries(queries);
|
||||
log.info("agent name :{}, rule queries witch can be supported by agent :{}", agent.getName(),
|
||||
queries.stream().map(SemanticQuery::getQueryMode).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.tencent.supersonic.chat.core.corrector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
|
||||
import com.tencent.supersonic.chat.api.pojo.SemanticSchema;
|
||||
import com.tencent.supersonic.chat.api.pojo.ViewSchema;
|
||||
import com.tencent.supersonic.chat.api.pojo.response.SqlInfo;
|
||||
import com.tencent.supersonic.chat.core.parser.sql.llm.ParseResult;
|
||||
import com.tencent.supersonic.chat.core.pojo.QueryContext;
|
||||
import com.tencent.supersonic.chat.core.query.llm.s2sql.LLMReq.ElementValue;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryConfig;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaElement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SchemaCorrectorTest {
|
||||
|
||||
private String json = "{\n"
|
||||
+ " \"viewId\": 1,\n"
|
||||
+ " \"llmReq\": {\n"
|
||||
+ " \"queryText\": \"xxx2024年播放量最高的十首歌\",\n"
|
||||
+ " \"filterCondition\": {\n"
|
||||
+ " \"tableName\": null\n"
|
||||
+ " },\n"
|
||||
+ " \"schema\": {\n"
|
||||
+ " \"domainName\": \"歌曲\",\n"
|
||||
+ " \"viewName\": \"歌曲\",\n"
|
||||
+ " \"fieldNameList\": [\n"
|
||||
+ " \"商务组\",\n"
|
||||
+ " \"歌曲名\",\n"
|
||||
+ " \"播放量\",\n"
|
||||
+ " \"播放份额\",\n"
|
||||
+ " \"数据日期\"\n"
|
||||
+ " ]\n"
|
||||
+ " },\n"
|
||||
+ " \"linking\": [\n"
|
||||
+ "\n"
|
||||
+ " ],\n"
|
||||
+ " \"currentDate\": \"2024-02-24\",\n"
|
||||
+ " \"priorExts\": \"播放份额是小数; \",\n"
|
||||
+ " \"sqlGenerationMode\": \"2_pass_auto_cot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"request\": null,\n"
|
||||
+ " \"commonAgentTool\": {\n"
|
||||
+ " \"id\": \"y3LqVSRL\",\n"
|
||||
+ " \"name\": \"大模型语义解析\",\n"
|
||||
+ " \"type\": \"NL2SQL_LLM\",\n"
|
||||
+ " \"viewIds\": [\n"
|
||||
+ " 1\n"
|
||||
+ " ]\n"
|
||||
+ " },\n"
|
||||
+ " \"linkingValues\": [\n"
|
||||
+ "\n"
|
||||
+ " ]\n"
|
||||
+ "}";
|
||||
|
||||
@Test
|
||||
void doCorrect() throws JsonProcessingException {
|
||||
Long viewId = 1L;
|
||||
QueryContext queryContext = buildQueryContext(viewId);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
ParseResult parseResult = objectMapper.readValue(json, ParseResult.class);
|
||||
|
||||
|
||||
String sql = "select 歌曲名 from 歌曲 where 发行日期 >= '2024-01-01' "
|
||||
+ "and 商务组 = 'xxx' order by 播放量 desc limit 10";
|
||||
SemanticParseInfo semanticParseInfo = new SemanticParseInfo();
|
||||
SqlInfo sqlInfo = new SqlInfo();
|
||||
sqlInfo.setS2SQL(sql);
|
||||
sqlInfo.setCorrectS2SQL(sql);
|
||||
semanticParseInfo.setSqlInfo(sqlInfo);
|
||||
|
||||
SchemaElement schemaElement = new SchemaElement();
|
||||
schemaElement.setView(viewId);
|
||||
semanticParseInfo.setView(schemaElement);
|
||||
|
||||
|
||||
semanticParseInfo.getProperties().put(Constants.CONTEXT, parseResult);
|
||||
|
||||
SchemaCorrector schemaCorrector = new SchemaCorrector();
|
||||
schemaCorrector.removeFilterIfNotInLinkingValue(queryContext, semanticParseInfo);
|
||||
|
||||
assertEquals("SELECT 歌曲名 FROM 歌曲 WHERE 发行日期 >= '2024-01-01' "
|
||||
+ "ORDER BY 播放量 DESC LIMIT 10", semanticParseInfo.getSqlInfo().getCorrectS2SQL());
|
||||
|
||||
parseResult = objectMapper.readValue(json, ParseResult.class);
|
||||
|
||||
List<ElementValue> linkingValues = new ArrayList<>();
|
||||
ElementValue elementValue = new ElementValue();
|
||||
elementValue.setFieldName("商务组");
|
||||
elementValue.setFieldValue("xxx");
|
||||
linkingValues.add(elementValue);
|
||||
parseResult.setLinkingValues(linkingValues);
|
||||
semanticParseInfo.getProperties().put(Constants.CONTEXT, parseResult);
|
||||
|
||||
semanticParseInfo.getSqlInfo().setCorrectS2SQL(sql);
|
||||
semanticParseInfo.getSqlInfo().setS2SQL(sql);
|
||||
schemaCorrector.removeFilterIfNotInLinkingValue(queryContext, semanticParseInfo);
|
||||
assertEquals("SELECT 歌曲名 FROM 歌曲 WHERE 发行日期 >= '2024-01-01' "
|
||||
+ "AND 商务组 = 'xxx' ORDER BY 播放量 DESC LIMIT 10", semanticParseInfo.getSqlInfo().getCorrectS2SQL());
|
||||
|
||||
}
|
||||
|
||||
private QueryContext buildQueryContext(Long viewId) {
|
||||
QueryContext queryContext = new QueryContext();
|
||||
List<ViewSchema> viewSchemaList = new ArrayList<>();
|
||||
ViewSchema viewSchema = new ViewSchema();
|
||||
QueryConfig queryConfig = new QueryConfig();
|
||||
viewSchema.setQueryConfig(queryConfig);
|
||||
SchemaElement schemaElement = new SchemaElement();
|
||||
schemaElement.setView(viewId);
|
||||
viewSchema.setView(schemaElement);
|
||||
Set<SchemaElement> dimensions = new HashSet<>();
|
||||
SchemaElement element1 = new SchemaElement();
|
||||
element1.setView(1L);
|
||||
element1.setName("歌曲名");
|
||||
dimensions.add(element1);
|
||||
|
||||
SchemaElement element2 = new SchemaElement();
|
||||
element2.setView(1L);
|
||||
element2.setName("商务组");
|
||||
dimensions.add(element2);
|
||||
|
||||
SchemaElement element3 = new SchemaElement();
|
||||
element3.setView(1L);
|
||||
element3.setName("发行日期");
|
||||
dimensions.add(element3);
|
||||
|
||||
viewSchema.setDimensions(dimensions);
|
||||
viewSchemaList.add(viewSchema);
|
||||
|
||||
SemanticSchema semanticSchema = new SemanticSchema(viewSchemaList);
|
||||
queryContext.setSemanticSchema(semanticSchema);
|
||||
return queryContext;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public class SearchServiceImpl implements SearchService {
|
||||
List<S2Term> originals = knowledgeService.getTerms(queryText);
|
||||
log.info("hanlp parse result: {}", originals);
|
||||
MapperHelper mapperHelper = ContextUtils.getBean(MapperHelper.class);
|
||||
Set<Long> detectViewIds = mapperHelper.getViewIds(queryReq.getModelId(), agentService.getAgent(agentId));
|
||||
Set<Long> detectViewIds = mapperHelper.getViewIds(queryReq.getViewId(), agentService.getAgent(agentId));
|
||||
|
||||
QueryContext queryContext = new QueryContext();
|
||||
BeanUtils.copyProperties(queryReq, queryContext);
|
||||
@@ -123,7 +123,7 @@ public class SearchServiceImpl implements SearchService {
|
||||
Set<SearchResult> searchResults = new LinkedHashSet();
|
||||
ViewInfoStat modelStat = NatureHelper.getViewStat(originals);
|
||||
|
||||
List<Long> possibleModels = getPossibleModels(queryReq, originals, modelStat, queryReq.getModelId());
|
||||
List<Long> possibleModels = getPossibleModels(queryReq, originals, modelStat, queryReq.getViewId());
|
||||
|
||||
// 5.1 priority dimension metric
|
||||
boolean existMetricAndDimension = searchMetricAndDimension(new HashSet<>(possibleModels), modelToName,
|
||||
|
||||
@@ -11,6 +11,7 @@ public class Constants {
|
||||
public static final String AT_SYMBOL = "@";
|
||||
public static final String DOT = ".";
|
||||
public static String SPACE = " ";
|
||||
public static String POUND = "#";
|
||||
public static final String COLON = ":";
|
||||
public static final String MINUS = "-";
|
||||
public static final String UNDERLINE = "_";
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.tencent.supersonic.common.pojo.enums;
|
||||
|
||||
public enum TaskStatusEnum {
|
||||
|
||||
INITIAL("initial", -2),
|
||||
|
||||
ERROR("error", -1),
|
||||
|
||||
PENDING("pending", 0),
|
||||
@@ -10,7 +12,7 @@ public enum TaskStatusEnum {
|
||||
|
||||
SUCCESS("success", 2),
|
||||
|
||||
UNKNOWN("UNKNOWN", 3);
|
||||
UNKNOWN("unknown", 3);
|
||||
|
||||
private String status;
|
||||
private Integer code;
|
||||
|
||||
@@ -4,6 +4,7 @@ public enum TypeEnums {
|
||||
|
||||
METRIC,
|
||||
DIMENSION,
|
||||
TAG,
|
||||
DOMAIN,
|
||||
ENTITY,
|
||||
VIEW,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package com.tencent.supersonic.common.util;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@@ -166,4 +168,27 @@ public class DateUtils {
|
||||
return datesInRange;
|
||||
}
|
||||
|
||||
public static boolean isAnyDateString(String value) {
|
||||
List<String> formats = Arrays.asList("yyyy-MM-dd", "yyyy-MM", "yyyy/MM/dd");
|
||||
return isAnyDateString(value, formats);
|
||||
}
|
||||
|
||||
public static boolean isAnyDateString(String value, List<String> formats) {
|
||||
for (String format : formats) {
|
||||
if (isDateString(value, format)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isDateString(String value, String format) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
|
||||
LocalDate.parse(value, formatter);
|
||||
return true;
|
||||
} catch (DateTimeParseException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
docs/images/supersonic_ideas.png
Normal file
BIN
docs/images/supersonic_ideas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 KiB |
@@ -1,5 +1,6 @@
|
||||
package com.tencent.supersonic.headless.api.pojo;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.AggOption;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -9,8 +10,8 @@ import java.util.List;
|
||||
public class MetricTable {
|
||||
|
||||
private String alias;
|
||||
private List<String> metrics;
|
||||
private List<String> dimensions;
|
||||
private List<String> metrics = Lists.newArrayList();
|
||||
private List<String> dimensions = Lists.newArrayList();
|
||||
private String where;
|
||||
private AggOption aggOption = AggOption.DEFAULT;
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@ import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.Filter;
|
||||
import com.tencent.supersonic.common.pojo.Order;
|
||||
import com.tencent.supersonic.common.pojo.enums.QueryType;
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class QueryParam {
|
||||
@@ -34,7 +33,6 @@ public class QueryParam {
|
||||
// metric
|
||||
private List<String> metrics = new ArrayList();
|
||||
private List<String> dimensions;
|
||||
private Map<String, String> variables;
|
||||
private String where;
|
||||
private List<ColumnOrder> order;
|
||||
private boolean nativeQuery = false;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.tencent.supersonic.headless.api.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TagDefineParams {
|
||||
|
||||
private String expr;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ package com.tencent.supersonic.headless.api.pojo.enums;
|
||||
* sql_query : view sql begin as select
|
||||
* table_query: dbName.tableName
|
||||
*/
|
||||
public enum DatasourceQuery {
|
||||
public enum ModelDefineType {
|
||||
|
||||
SQL_QUERY("sql_query"),
|
||||
TABLE_QUERY("table_query");
|
||||
@@ -13,7 +13,7 @@ public enum DatasourceQuery {
|
||||
private String name;
|
||||
|
||||
|
||||
DatasourceQuery(String name) {
|
||||
ModelDefineType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.enums;
|
||||
|
||||
public enum TagDefineType {
|
||||
|
||||
FIELD,
|
||||
DIMENSION,
|
||||
Tag
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.enums;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public enum TagType {
|
||||
ATOMIC,
|
||||
DERIVED;
|
||||
|
||||
public static TagType of(String src) {
|
||||
for (TagType tagType : TagType.values()) {
|
||||
if (Objects.nonNull(src) && src.equalsIgnoreCase(tagType.name())) {
|
||||
return tagType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Boolean isDerived(String src) {
|
||||
TagType tagType = of(src);
|
||||
return Objects.nonNull(tagType) && tagType.equals(DERIVED);
|
||||
}
|
||||
|
||||
public static TagType getType(TagDefineType tagDefineType) {
|
||||
return Objects.nonNull(tagDefineType) && TagDefineType.Tag.equals(tagDefineType) ? TagType.DERIVED
|
||||
: TagType.ATOMIC;
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,11 @@ import com.tencent.supersonic.common.pojo.enums.TypeEnums;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class DictItemFilter {
|
||||
private Long id;
|
||||
private TypeEnums type;
|
||||
private Long itemId;
|
||||
@NotNull
|
||||
private StatusEnum status;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.Filter;
|
||||
import com.tencent.supersonic.common.pojo.Order;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Data
|
||||
@Slf4j
|
||||
@ToString
|
||||
public class QueryTagReq extends SemanticQueryReq {
|
||||
|
||||
private List<String> groups = new ArrayList<>();
|
||||
private List<Aggregator> aggregators = new ArrayList<>();
|
||||
private List<Filter> tagFilters = new ArrayList<>();
|
||||
private List<Order> orders = new ArrayList<>();
|
||||
|
||||
private Long limit = 20L;
|
||||
private Long offset = 0L;
|
||||
|
||||
private String tagFiltersDate;
|
||||
private DateConf dateInfo;
|
||||
|
||||
@Override
|
||||
public String toCustomizedString() {
|
||||
StringBuilder stringBuilder = new StringBuilder("{");
|
||||
stringBuilder.append("\"viewId\":")
|
||||
.append(viewId);
|
||||
stringBuilder.append("\"modelIds\":")
|
||||
.append(modelIds);
|
||||
stringBuilder.append(",\"groups\":")
|
||||
.append(groups);
|
||||
stringBuilder.append(",\"aggregators\":")
|
||||
.append(aggregators);
|
||||
stringBuilder.append(",\"orders\":")
|
||||
.append(orders);
|
||||
stringBuilder.append(",\"tagFilters\":")
|
||||
.append(tagFilters);
|
||||
stringBuilder.append(",\"dateInfo\":")
|
||||
.append(dateInfo);
|
||||
stringBuilder.append(",\"params\":")
|
||||
.append(params);
|
||||
stringBuilder.append(",\"limit\":")
|
||||
.append(limit);
|
||||
stringBuilder.append('}');
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public List<String> getMetrics() {
|
||||
List<String> metrics = Lists.newArrayList();
|
||||
if (!CollectionUtils.isEmpty(this.aggregators)) {
|
||||
metrics = aggregators.stream().map(Aggregator::getColumn).collect(Collectors.toList());
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||
import com.tencent.supersonic.common.pojo.DateConf;
|
||||
import com.tencent.supersonic.common.pojo.Filter;
|
||||
import com.tencent.supersonic.common.pojo.Order;
|
||||
import com.tencent.supersonic.common.pojo.enums.QueryType;
|
||||
import com.tencent.supersonic.headless.api.pojo.Cache;
|
||||
import com.tencent.supersonic.headless.api.pojo.Param;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public class QueryViewReq {
|
||||
|
||||
private Long viewId;
|
||||
private String viewName;
|
||||
private String sql;
|
||||
private boolean needAuth = true;
|
||||
private List<Param> params = new ArrayList<>();
|
||||
private Cache cacheInfo = new Cache();
|
||||
private List<String> groups = new ArrayList<>();
|
||||
private List<Aggregator> aggregators = new ArrayList<>();
|
||||
private List<Order> orders = new ArrayList<>();
|
||||
private List<Filter> dimensionFilters = new ArrayList<>();
|
||||
private List<Filter> metricFilters = new ArrayList<>();
|
||||
private DateConf dateInfo;
|
||||
private Long limit = 2000L;
|
||||
private QueryType queryType = QueryType.ID;
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import com.tencent.supersonic.headless.api.pojo.SqlVariable;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SqlExecuteReq {
|
||||
@@ -16,6 +18,8 @@ public class SqlExecuteReq {
|
||||
@NotBlank(message = "sql can not be blank")
|
||||
private String sql;
|
||||
|
||||
private List<SqlVariable> sqlVariables;
|
||||
|
||||
public String getSql() {
|
||||
if (StringUtils.isNotBlank(sql) && sql.endsWith(";")) {
|
||||
sql = sql.substring(0, sql.length() - 1);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.request;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.TagType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TagReq extends SchemaItem {
|
||||
|
||||
private Long modelId;
|
||||
private Map<String, Object> ext = new HashMap<>();
|
||||
private TagDefineType tagDefineType;
|
||||
private TagDefineParams tagDefineParams;
|
||||
|
||||
public String getTypeParamsJson() {
|
||||
return JSONObject.toJSONString(tagDefineParams);
|
||||
}
|
||||
|
||||
public String getExtJson() {
|
||||
return Objects.nonNull(ext) && ext.size() > 0 ? JSONObject.toJSONString(ext) : "";
|
||||
}
|
||||
|
||||
public TagType getType() {
|
||||
return TagType.getType(tagDefineType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class SemanticSchemaResp {
|
||||
private SchemaType schemaType;
|
||||
private List<MetricSchemaResp> metrics = Lists.newArrayList();
|
||||
private List<DimSchemaResp> dimensions = Lists.newArrayList();
|
||||
private List<TagResp> tags = Lists.newArrayList();
|
||||
private List<ModelRela> modelRelas = Lists.newArrayList();
|
||||
private List<ModelResp> modelResps = Lists.newArrayList();
|
||||
private ViewResp viewResp;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.tencent.supersonic.headless.api.pojo.response;
|
||||
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class TagResp extends SchemaItem {
|
||||
|
||||
private Long modelId;
|
||||
|
||||
private String type;
|
||||
|
||||
private Boolean isCollect;
|
||||
|
||||
private boolean hasAdminRes;
|
||||
|
||||
private Map<String, Object> ext = new HashMap<>();
|
||||
|
||||
private TagDefineType tagDefineType = TagDefineType.FIELD;
|
||||
|
||||
private TagDefineParams tagDefineParams;
|
||||
|
||||
public String getExpr() {
|
||||
return tagDefineParams.getExpr();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.tencent.supersonic.headless.core.file;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -17,9 +19,9 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FileHandlerImpl implements FileHandler {
|
||||
public static final String FILE_SPILT = File.separator;
|
||||
|
||||
private final LocalFileConfig localFileConfig;
|
||||
|
||||
public FileHandlerImpl(LocalFileConfig localFileConfig) {
|
||||
this.localFileConfig = localFileConfig;
|
||||
}
|
||||
@@ -31,8 +33,8 @@ public class FileHandlerImpl implements FileHandler {
|
||||
createDir(dictDirectoryBackup);
|
||||
}
|
||||
|
||||
String source = localFileConfig.getDictDirectoryLatest() + "/" + fileName;
|
||||
String target = dictDirectoryBackup + "/" + fileName;
|
||||
String source = localFileConfig.getDictDirectoryLatest() + FILE_SPILT + fileName;
|
||||
String target = dictDirectoryBackup + FILE_SPILT + fileName;
|
||||
Path sourcePath = Paths.get(source);
|
||||
Path targetPath = Paths.get(target);
|
||||
try {
|
||||
@@ -88,7 +90,7 @@ public class FileHandlerImpl implements FileHandler {
|
||||
if (!existPath(dictDirectoryLatest)) {
|
||||
createDir(dictDirectoryLatest);
|
||||
}
|
||||
String filePath = dictDirectoryLatest + "/" + fileName;
|
||||
String filePath = dictDirectoryLatest + FILE_SPILT + fileName;
|
||||
if (existPath(filePath)) {
|
||||
backupFile(fileName);
|
||||
}
|
||||
@@ -117,7 +119,7 @@ public class FileHandlerImpl implements FileHandler {
|
||||
@Override
|
||||
public Boolean deleteDictFile(String fileName) {
|
||||
backupFile(fileName);
|
||||
deleteFile(localFileConfig.getDictDirectoryLatest() + "/" + fileName);
|
||||
deleteFile(localFileConfig.getDictDirectoryLatest() + FILE_SPILT + fileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.tencent.supersonic.headless.core.file;
|
||||
|
||||
import com.tencent.supersonic.headless.core.knowledge.helper.HanlpHelper;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@@ -18,16 +21,21 @@ public class LocalFileConfig {
|
||||
private String dictDirectoryBackup;
|
||||
|
||||
public String getDictDirectoryLatest() {
|
||||
return getResourceDir() + dictDirectoryLatest;
|
||||
return getDictDirectoryPrefixDir() + dictDirectoryLatest;
|
||||
}
|
||||
|
||||
public String getDictDirectoryBackup() {
|
||||
return getResourceDir() + dictDirectoryBackup;
|
||||
return getDictDirectoryPrefixDir() + dictDirectoryBackup;
|
||||
}
|
||||
|
||||
private String getResourceDir() {
|
||||
//return hanlpPropertiesPath = HanlpHelper.getHanlpPropertiesPath();
|
||||
return ClassLoader.getSystemClassLoader().getResource("").getPath();
|
||||
private String getDictDirectoryPrefixDir() {
|
||||
try {
|
||||
return HanlpHelper.getHanlpPropertiesPath();
|
||||
} catch (FileNotFoundException e) {
|
||||
log.warn("getDictDirectoryPrefixDir error: " + e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,18 +9,19 @@ import com.tencent.supersonic.headless.api.pojo.MetricTable;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.AggOption;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.EngineType;
|
||||
import com.tencent.supersonic.headless.core.pojo.ViewQueryParam;
|
||||
import com.tencent.supersonic.headless.core.pojo.Database;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.pojo.ViewQueryParam;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlGenerateUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* supplement the QueryStatement when query with custom aggregation method
|
||||
@@ -110,7 +111,6 @@ public class CalculateAggConverter implements HeadlessConverter {
|
||||
EngineType.fromString(database.getType().toUpperCase()), database.getVersion());
|
||||
sqlCommend.setSql(viewQueryParam.getSql());
|
||||
sqlCommend.setTables(viewQueryParam.getTables());
|
||||
sqlCommend.setVariables(viewQueryParam.getVariables());
|
||||
sqlCommend.setSupportWith(viewQueryParam.isSupportWith());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
package com.tencent.supersonic.headless.core.parser.converter;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.Filter;
|
||||
import com.tencent.supersonic.common.pojo.enums.FilterOperatorEnum;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryParam;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.common.pojo.enums.TimeDimensionEnum;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlAddHelper;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlSelectHelper;
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricTable;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.Dimension;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.StringValue;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component("DefaultDimValueConverter")
|
||||
@@ -18,36 +27,43 @@ public class DefaultDimValueConverter implements HeadlessConverter {
|
||||
|
||||
@Override
|
||||
public boolean accept(QueryStatement queryStatement) {
|
||||
if (Objects.isNull(queryStatement.getQueryParam()) || queryStatement.getIsS2SQL()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return !Objects.isNull(queryStatement.getViewQueryParam())
|
||||
&& !StringUtils.isBlank(queryStatement.getViewQueryParam().getSql());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convert(QueryStatement queryStatement) {
|
||||
QueryParam queryParam = queryStatement.getQueryParam();
|
||||
List<Dimension> dimensions = queryStatement.getSemanticModel().getDimensions().stream()
|
||||
.filter(dimension -> !CollectionUtils.isEmpty(dimension.getDefaultValues()))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(dimensions)) {
|
||||
return;
|
||||
}
|
||||
log.info("dimension with default values:{}, queryStruct:{}", dimensions, queryParam);
|
||||
//add dimension default value to filter
|
||||
List<String> dimensionFilterBizName = queryParam.getDimensionFilters().stream()
|
||||
.map(Filter::getBizName).collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(dimensionFilterBizName)) {
|
||||
String sql = queryStatement.getViewQueryParam().getSql();
|
||||
List<String> whereFields = SqlSelectHelper.getWhereFields(sql)
|
||||
.stream().filter(field -> !TimeDimensionEnum.containsTimeDimension(field))
|
||||
.collect(Collectors.toList());
|
||||
if (!CollectionUtils.isEmpty(whereFields)) {
|
||||
return;
|
||||
}
|
||||
for (Dimension dimensionResp : dimensions) {
|
||||
Filter filter = new Filter();
|
||||
filter.setBizName(dimensionResp.getBizName());
|
||||
filter.setValue(dimensionResp.getDefaultValues());
|
||||
filter.setOperator(FilterOperatorEnum.IN);
|
||||
filter.setName(dimensionResp.getName());
|
||||
queryParam.getDimensionFilters().add(filter);
|
||||
MetricTable metricTable = queryStatement.getViewQueryParam()
|
||||
.getTables().stream().findFirst().orElse(null);
|
||||
List<Expression> expressions = Lists.newArrayList();
|
||||
for (Dimension dimension : dimensions) {
|
||||
ExpressionList expressionList = new ExpressionList();
|
||||
List<Expression> exprs = new ArrayList<>();
|
||||
dimension.getDefaultValues().forEach(value -> exprs.add(new StringValue(value)));
|
||||
expressionList.setExpressions(exprs);
|
||||
InExpression inExpression = new InExpression();
|
||||
inExpression.setLeftExpression(new Column(dimension.getBizName()));
|
||||
inExpression.setRightItemsList(expressionList);
|
||||
expressions.add(inExpression);
|
||||
if (metricTable != null) {
|
||||
metricTable.getDimensions().add(dimension.getBizName());
|
||||
}
|
||||
}
|
||||
sql = SqlAddHelper.addWhere(sql, expressions);
|
||||
queryStatement.getViewQueryParam().setSql(sql);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
package com.tencent.supersonic.headless.core.parser.converter;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.ColumnOrder;
|
||||
import com.tencent.supersonic.headless.api.pojo.Param;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryParam;
|
||||
import com.tencent.supersonic.headless.core.pojo.MetricQueryParam;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.DataSource;
|
||||
import com.tencent.supersonic.headless.core.pojo.MetricQueryParam;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlGenerateUtils;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* HeadlessConverter default implement
|
||||
*/
|
||||
@@ -59,8 +59,6 @@ public class ParserDefaultConverter implements HeadlessConverter {
|
||||
metricQueryParam.setWhere(where);
|
||||
metricQueryParam.setOrder(queryParam.getOrders().stream()
|
||||
.map(order -> new ColumnOrder(order.getColumn(), order.getDirection())).collect(Collectors.toList()));
|
||||
metricQueryParam.setVariables(queryParam.getParams().stream()
|
||||
.collect(Collectors.toMap(Param::getName, Param::getValue, (k1, k2) -> k1)));
|
||||
metricQueryParam.setLimit(queryParam.getLimit());
|
||||
|
||||
// support detail query
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tencent.supersonic.headless.core.parser.converter;
|
||||
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.ModelDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticSchemaResp;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.DataSource;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlVariableParseUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Component("SqlVariableParseConverter")
|
||||
public class SqlVariableParseConverter implements HeadlessConverter {
|
||||
|
||||
@Override
|
||||
public boolean accept(QueryStatement queryStatement) {
|
||||
if (Objects.isNull(queryStatement.getQueryParam())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convert(QueryStatement queryStatement) {
|
||||
SemanticSchemaResp semanticSchemaResp = queryStatement.getSemanticSchemaResp();
|
||||
List<ModelResp> modelResps = semanticSchemaResp.getModelResps();
|
||||
if (CollectionUtils.isEmpty(modelResps)) {
|
||||
return;
|
||||
}
|
||||
for (ModelResp modelResp : modelResps) {
|
||||
if (ModelDefineType.SQL_QUERY.getName()
|
||||
.equalsIgnoreCase(modelResp.getModelDetail().getQueryType())) {
|
||||
String sqlParsed = SqlVariableParseUtils.parse(
|
||||
modelResp.getModelDetail().getSqlQuery(),
|
||||
modelResp.getModelDetail().getSqlVariables(),
|
||||
queryStatement.getQueryParam().getParams()
|
||||
);
|
||||
DataSource dataSource = queryStatement.getSemanticModel()
|
||||
.getDatasourceMap().get(modelResp.getBizName());
|
||||
dataSource.setSqlQuery(sqlParsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
package com.tencent.supersonic.headless.core.pojo;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.ColumnOrder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class MetricQueryParam {
|
||||
|
||||
private List<String> metrics;
|
||||
private List<String> dimensions;
|
||||
private Map<String, String> variables;
|
||||
private String where;
|
||||
private Long limit;
|
||||
private List<ColumnOrder> order;
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
package com.tencent.supersonic.headless.core.pojo;
|
||||
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricTable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ViewQueryParam {
|
||||
|
||||
private Map<String, String> variables;
|
||||
private String sql = "";
|
||||
private List<MetricTable> tables;
|
||||
private boolean supportWith = true;
|
||||
private boolean withAlias = true;
|
||||
|
||||
public Map<String, String> getVariables() {
|
||||
if (variables == null) {
|
||||
variables = new HashMap<>();
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.tencent.supersonic.headless.core.utils;
|
||||
import com.tencent.supersonic.common.util.ContextUtils;
|
||||
import com.tencent.supersonic.headless.core.executor.JdbcExecutor;
|
||||
import com.tencent.supersonic.headless.core.executor.QueryExecutor;
|
||||
import com.tencent.supersonic.headless.core.parser.converter.SqlVariableParseConverter;
|
||||
import com.tencent.supersonic.headless.core.planner.DetailQueryOptimizer;
|
||||
import com.tencent.supersonic.headless.core.planner.QueryOptimizer;
|
||||
import com.tencent.supersonic.headless.core.parser.converter.HeadlessConverter;
|
||||
@@ -83,6 +84,7 @@ public class ComponentFactory {
|
||||
|
||||
private static void initSemanticConverter() {
|
||||
headlessConverters.add(getBean("DefaultDimValueConverter", DefaultDimValueConverter.class));
|
||||
headlessConverters.add(getBean("SqlVariableParseConverter", SqlVariableParseConverter.class));
|
||||
headlessConverters.add(getBean("CalculateAggConverter", CalculateAggConverter.class));
|
||||
headlessConverters.add(getBean("ParserDefaultConverter", ParserDefaultConverter.class));
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ public class SqlVariableParseUtils {
|
||||
private static final char delimiter = '$';
|
||||
|
||||
public static String parse(String sql, List<SqlVariable> sqlVariables, List<Param> params) {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
if (CollectionUtils.isEmpty(sqlVariables)) {
|
||||
return sql;
|
||||
}
|
||||
Map<String, Object> queryParams = new HashMap<>();
|
||||
//1. handle default variable value
|
||||
sqlVariables.forEach(variable -> {
|
||||
queryParams.put(variable.getName().trim(),
|
||||
variables.put(variable.getName().trim(),
|
||||
getValues(variable.getValueType(), variable.getDefaultValues()));
|
||||
});
|
||||
|
||||
@@ -49,21 +49,25 @@ public class SqlVariableParseUtils {
|
||||
List<SqlVariable> list = map.get(p.getName());
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
SqlVariable v = list.get(list.size() - 1);
|
||||
queryParams.put(p.getName().trim(), getValue(v.getValueType(), p.getValue()));
|
||||
variables.put(p.getName().trim(), getValue(v.getValueType(), p.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
queryParams.forEach((k, v) -> {
|
||||
variables.forEach((k, v) -> {
|
||||
if (v instanceof List && ((List) v).size() > 0) {
|
||||
v = ((List) v).stream().collect(Collectors.joining(COMMA)).toString();
|
||||
}
|
||||
queryParams.put(k, v);
|
||||
variables.put(k, v);
|
||||
});
|
||||
return parse(sql, variables);
|
||||
}
|
||||
|
||||
public static String parse(String sql, Map<String, Object> variables) {
|
||||
ST st = new ST(sql, delimiter, delimiter);
|
||||
if (!CollectionUtils.isEmpty(queryParams)) {
|
||||
queryParams.forEach(st::add);
|
||||
if (!CollectionUtils.isEmpty(variables)) {
|
||||
variables.forEach(st::add);
|
||||
}
|
||||
return st.render();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
public class SysTimeDimensionBuilder {
|
||||
|
||||
public static void addSysTimeDimension(List<Dim> dims, DbAdaptor engineAdaptor) {
|
||||
log.info("addSysTimeDimension before:{}, engineAdaptor:{}", dims, engineAdaptor);
|
||||
log.debug("addSysTimeDimension before:{}, engineAdaptor:{}", dims, engineAdaptor);
|
||||
Dim timeDim = getTimeDim(dims);
|
||||
if (timeDim == null) {
|
||||
timeDim = Dim.getDefault();
|
||||
|
||||
@@ -13,11 +13,19 @@ import com.tencent.supersonic.headless.api.pojo.DimValueMap;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryTagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DimensionResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
@@ -29,14 +37,6 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
@@ -63,9 +63,17 @@ public class DimValueAspect {
|
||||
if (queryReq instanceof QuerySqlReq) {
|
||||
return handleSqlDimValue(joinPoint);
|
||||
}
|
||||
|
||||
if (queryReq instanceof QueryTagReq) {
|
||||
return handleTagValue(joinPoint);
|
||||
}
|
||||
throw new InvalidArgumentException("queryReq is not Invalid:" + queryReq);
|
||||
}
|
||||
|
||||
public Object handleTagValue(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
return (SemanticQueryResp) joinPoint.proceed();
|
||||
}
|
||||
|
||||
private SemanticQueryResp handleStructDimValue(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
QueryStructReq queryStructReq = (QueryStructReq) args[0];
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.tencent.supersonic.headless.api.pojo.Dim;
|
||||
import com.tencent.supersonic.headless.api.pojo.Identify;
|
||||
import com.tencent.supersonic.headless.api.pojo.Measure;
|
||||
import com.tencent.supersonic.headless.api.pojo.ModelDetail;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.DatasourceQuery;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.ModelDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.core.adaptor.db.DbAdaptor;
|
||||
@@ -46,12 +46,13 @@ public class ModelYamlManager {
|
||||
.collect(Collectors.toList()));
|
||||
dataModelYamlTpl.setName(modelResp.getBizName());
|
||||
dataModelYamlTpl.setSourceId(modelResp.getDatabaseId());
|
||||
if (modelDetail.getQueryType().equalsIgnoreCase(DatasourceQuery.SQL_QUERY.getName())) {
|
||||
if (modelDetail.getQueryType().equalsIgnoreCase(ModelDefineType.SQL_QUERY.getName())) {
|
||||
dataModelYamlTpl.setSqlQuery(modelDetail.getSqlQuery());
|
||||
} else {
|
||||
dataModelYamlTpl.setTableQuery(modelDetail.getTableQuery());
|
||||
}
|
||||
dataModelYamlTpl.setFields(modelResp.getModelDetail().getFields());
|
||||
dataModelYamlTpl.setId(modelResp.getId());
|
||||
return dataModelYamlTpl;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.tencent.supersonic.common.pojo.enums.FilterOperatorEnum;
|
||||
import com.tencent.supersonic.headless.api.pojo.Field;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.Constants;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.DataSource;
|
||||
import com.tencent.supersonic.headless.core.parser.calcite.s2sql.DataType;
|
||||
@@ -29,11 +30,6 @@ import com.tencent.supersonic.headless.server.pojo.yaml.MetricTypeParamsYamlTpl;
|
||||
import com.tencent.supersonic.headless.server.pojo.yaml.MetricYamlTpl;
|
||||
import com.tencent.supersonic.headless.server.service.Catalog;
|
||||
import com.tencent.supersonic.headless.server.utils.DatabaseConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -44,11 +40,16 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SemanticSchemaManager {
|
||||
|
||||
private final Catalog catalog;
|
||||
|
||||
public SemanticSchemaManager(Catalog catalog) {
|
||||
@@ -87,6 +88,54 @@ public class SemanticSchemaManager {
|
||||
return semanticModel;
|
||||
}
|
||||
|
||||
public SemanticModel getTagSemanticModel(SemanticSchemaResp semanticSchemaResp) throws Exception {
|
||||
if (CollectionUtils.isEmpty(semanticSchemaResp.getTags())) {
|
||||
throw new Exception("semanticSchemaResp tag is empty");
|
||||
}
|
||||
SemanticModel semanticModel = getSemanticModel(semanticSchemaResp);
|
||||
//Map<String, List<Dimension>> dimensions = new HashMap<>();
|
||||
Map<Long, List<TagResp>> tagMap = new HashMap<>();
|
||||
for (TagResp tagResp : semanticSchemaResp.getTags()) {
|
||||
if (!tagMap.containsKey(tagResp.getModelId())) {
|
||||
tagMap.put(tagResp.getModelId(), new ArrayList<>());
|
||||
}
|
||||
tagMap.get(tagResp.getModelId()).add(tagResp);
|
||||
}
|
||||
if (Objects.nonNull(semanticModel.getDatasourceMap()) && !semanticModel.getDatasourceMap().isEmpty()) {
|
||||
for (Map.Entry<String, DataSource> entry : semanticModel.getDatasourceMap().entrySet()) {
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
List<String> tagNames = new ArrayList<>();
|
||||
if (tagMap.containsKey(entry.getValue().getId())) {
|
||||
for (TagResp tagResp : tagMap.get(entry.getValue().getId())) {
|
||||
tagNames.add(tagResp.getBizName());
|
||||
Dimension dimension = Dimension.builder().build();
|
||||
dimension.setType("");
|
||||
dimension.setExpr(tagResp.getExpr());
|
||||
dimension.setName(tagResp.getBizName());
|
||||
dimension.setOwners("");
|
||||
dimension.setBizName(tagResp.getBizName());
|
||||
if (Objects.isNull(dimension.getDataType())) {
|
||||
dimension.setDataType(DataType.UNKNOWN);
|
||||
}
|
||||
DimensionTimeTypeParams dimensionTimeTypeParams = new DimensionTimeTypeParams();
|
||||
dimension.setDimensionTimeTypeParams(dimensionTimeTypeParams);
|
||||
dimensions.add(dimension);
|
||||
}
|
||||
}
|
||||
if (semanticModel.getDimensionMap().containsKey(entry.getKey())) {
|
||||
semanticModel.getDimensionMap().get(entry.getKey()).stream()
|
||||
.filter(d -> !tagNames.contains(d.getBizName())).forEach(d -> {
|
||||
dimensions.add(d);
|
||||
});
|
||||
}
|
||||
semanticModel.getDimensionMap().put(entry.getKey(), dimensions);
|
||||
}
|
||||
}
|
||||
// metric ignored
|
||||
semanticModel.setMetrics(new ArrayList<>());
|
||||
return semanticModel;
|
||||
}
|
||||
|
||||
public static List<Metric> getMetrics(final List<MetricYamlTpl> t) {
|
||||
return getMetricsByMetricYamlTpl(t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.tencent.supersonic.headless.server.persistence.dataobject;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("s2_tag")
|
||||
public class TagDO {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 主体域ID
|
||||
*/
|
||||
private Long modelId;
|
||||
|
||||
/**
|
||||
* 指标名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 字段名称
|
||||
*/
|
||||
private String bizName;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 指标状态,0正常,1下架,2删除
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 敏感级别
|
||||
*/
|
||||
private Integer sensitiveLevel;
|
||||
|
||||
/**
|
||||
* 类型 DERIVED,ATOMIC
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createdAt;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updatedAt;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
|
||||
/**
|
||||
* 类型参数
|
||||
*/
|
||||
private String defineType;
|
||||
private String typeParams;
|
||||
private String ext;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.headless.server.persistence.mapper;
|
||||
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TagCustomMapper {
|
||||
List<TagDO> query(TagFilter tagFilter);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.tencent.supersonic.headless.server.persistence.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TagMapper extends BaseMapper<TagDO> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.tencent.supersonic.headless.server.persistence.repository;
|
||||
|
||||
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface TagRepository {
|
||||
|
||||
Long create(TagDO tagDO);
|
||||
|
||||
void update(TagDO tagDO);
|
||||
|
||||
TagDO getTagById(Long id);
|
||||
|
||||
List<TagDO> query(TagFilter tagFilter);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.tencent.supersonic.headless.server.persistence.repository.impl;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
|
||||
import com.tencent.supersonic.common.pojo.enums.TypeEnums;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DictItemFilter;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DictSingleTaskReq;
|
||||
@@ -17,16 +16,22 @@ import com.tencent.supersonic.headless.server.persistence.repository.DictReposit
|
||||
import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import com.tencent.supersonic.headless.server.utils.DictUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class DictRepositoryImpl implements DictRepository {
|
||||
|
||||
@Value("${dict.task.num:10}")
|
||||
private Integer dictTaskNum;
|
||||
|
||||
private final DictTaskMapper dictTaskMapper;
|
||||
private final DictConfMapper dictConfMapper;
|
||||
private final DictUtils dictConverter;
|
||||
@@ -83,7 +88,9 @@ public class DictRepositoryImpl implements DictRepository {
|
||||
QueryWrapper<DictTaskDO> wrapper = new QueryWrapper<>();
|
||||
wrapper.lambda().eq(DictTaskDO::getItemId, taskReq.getItemId());
|
||||
wrapper.lambda().eq(DictTaskDO::getType, taskReq.getType());
|
||||
List<DictTaskDO> dictTaskDOList = dictTaskMapper.selectList(wrapper);
|
||||
List<DictTaskDO> dictTaskDOList = dictTaskMapper.selectList(wrapper).stream()
|
||||
.sorted(Comparator.comparing(DictTaskDO::getCreatedAt).reversed())
|
||||
.limit(dictTaskNum).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(dictTaskDOList)) {
|
||||
return taskResp;
|
||||
}
|
||||
@@ -102,9 +109,9 @@ public class DictRepositoryImpl implements DictRepository {
|
||||
|
||||
@Override
|
||||
public Long editDictConf(DictConfDO dictConfDO) {
|
||||
DictItemFilter filter = DictItemFilter.builder().type(TypeEnums.valueOf(dictConfDO.getType()))
|
||||
DictItemFilter filter = DictItemFilter.builder()
|
||||
.type(TypeEnums.valueOf(dictConfDO.getType()))
|
||||
.itemId(dictConfDO.getItemId())
|
||||
.status(StatusEnum.ONLINE)
|
||||
.build();
|
||||
|
||||
List<DictConfDO> dictConfDOList = getDictConfDOList(filter);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.tencent.supersonic.headless.server.persistence.repository.impl;
|
||||
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.mapper.TagCustomMapper;
|
||||
import com.tencent.supersonic.headless.server.persistence.mapper.TagMapper;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.TagRepository;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class TagRepositoryImpl implements TagRepository {
|
||||
private final TagMapper mapper;
|
||||
private final TagCustomMapper tagCustomMapper;
|
||||
|
||||
public TagRepositoryImpl(TagMapper mapper,
|
||||
TagCustomMapper tagCustomMapper) {
|
||||
this.mapper = mapper;
|
||||
this.tagCustomMapper = tagCustomMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long create(TagDO tagDO) {
|
||||
mapper.insert(tagDO);
|
||||
return tagDO.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(TagDO tagDO) {
|
||||
mapper.updateById(tagDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagDO getTagById(Long id) {
|
||||
return mapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TagDO> query(TagFilter tagFilter) {
|
||||
return tagCustomMapper.query(tagFilter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tencent.supersonic.headless.server.pojo;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TagFilter extends MetaFilter {
|
||||
|
||||
private String type;
|
||||
private List<Integer> statusList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.supersonic.headless.server.pojo;
|
||||
|
||||
|
||||
import com.tencent.supersonic.headless.api.pojo.request.PageSchemaItemReq;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TagFilterPage extends PageSchemaItemReq {
|
||||
private String type;
|
||||
private List<Integer> statusList;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class DatabaseController {
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return databaseService.executeSql(sqlExecuteReq.getSql(), sqlExecuteReq.getId(), user);
|
||||
return databaseService.executeSql(sqlExecuteReq, sqlExecuteReq.getId(), user);
|
||||
}
|
||||
|
||||
@RequestMapping("/getDbNames/{id}")
|
||||
|
||||
@@ -42,7 +42,7 @@ public class KnowledgeController {
|
||||
* @param dictItemReq
|
||||
*/
|
||||
@PostMapping("/conf")
|
||||
public Long addDictConf(@RequestBody @Valid DictItemReq dictItemReq,
|
||||
public DictItemResp addDictConf(@RequestBody @Valid DictItemReq dictItemReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
@@ -56,7 +56,7 @@ public class KnowledgeController {
|
||||
* @param dictItemReq
|
||||
*/
|
||||
@PutMapping("/conf")
|
||||
public Long editDictConf(@RequestBody @Valid DictItemReq dictItemReq,
|
||||
public DictItemResp editDictConf(@RequestBody @Valid DictItemReq dictItemReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
|
||||
@@ -41,14 +41,6 @@ public class QueryController {
|
||||
@Autowired
|
||||
private DownloadService downloadService;
|
||||
|
||||
@PostMapping("/sql")
|
||||
public Object queryBySql(@RequestBody QuerySqlReq querySqlReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return queryService.queryByReq(querySqlReq, user);
|
||||
}
|
||||
|
||||
@PostMapping("/struct")
|
||||
public Object queryByStruct(@RequestBody QueryStructReq queryStructReq,
|
||||
HttpServletRequest request,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.tencent.supersonic.headless.server.rest;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilterPage;
|
||||
import com.tencent.supersonic.headless.server.service.TagService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/semantic/tag")
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
public TagController(TagService tagService) {
|
||||
this.tagService = tagService;
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
public TagResp create(@RequestBody TagReq tagReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return tagService.create(tagReq, user);
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
public TagResp update(@RequestBody TagReq tagReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return tagService.update(tagReq, user);
|
||||
}
|
||||
|
||||
@DeleteMapping("delete/{id}")
|
||||
public Boolean delete(@PathVariable("id") Long id,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
tagService.delete(id, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
@GetMapping("getTag/{id}")
|
||||
public TagResp getTag(@PathVariable("id") Long id,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
return tagService.getTag(id);
|
||||
}
|
||||
|
||||
@PostMapping("/queryTag")
|
||||
public PageInfo<TagResp> queryPage(@RequestBody TagFilterPage tagFilterPage,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return tagService.queryPage(tagFilterPage, user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tencent.supersonic.headless.server.rest.api;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/semantic/query")
|
||||
@Slf4j
|
||||
public class MetricQueryApiController {
|
||||
|
||||
@Autowired
|
||||
private QueryService queryService;
|
||||
|
||||
@Autowired
|
||||
private MetricService metricService;
|
||||
|
||||
@PostMapping("/metric")
|
||||
public Object queryByMetric(@RequestBody QueryMetricReq queryMetricReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
QueryStructReq queryStructReq = metricService.convert(queryMetricReq);
|
||||
return queryService.queryByReq(queryStructReq.convert(), user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.tencent.supersonic.headless.server.rest.api;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/semantic/query")
|
||||
@Slf4j
|
||||
public class SqlQueryApiController {
|
||||
|
||||
@Autowired
|
||||
private QueryService queryService;
|
||||
|
||||
@PostMapping("/sql")
|
||||
public Object queryBySql(@RequestBody QuerySqlReq querySqlReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return queryService.queryByReq(querySqlReq, user);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.tencent.supersonic.headless.server.rest;
|
||||
package com.tencent.supersonic.headless.server.rest.api;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryTagReq;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -16,17 +16,17 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/semantic/query")
|
||||
@Slf4j
|
||||
public class MetricApiController {
|
||||
public class TagQueryApiController {
|
||||
|
||||
@Autowired
|
||||
private QueryService queryService;
|
||||
|
||||
@PostMapping("/metric")
|
||||
public Object queryBySql(@RequestBody QueryMetricReq queryMetricReq,
|
||||
@PostMapping("/tag")
|
||||
public Object queryByTag(@RequestBody QueryTagReq queryTagReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
return queryService.queryByMetric(queryMetricReq, user);
|
||||
return queryService.queryByReq(queryTagReq, user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.tencent.supersonic.headless.server.rest.api;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryViewReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
import com.tencent.supersonic.headless.server.service.ViewService;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/semantic/query")
|
||||
@Slf4j
|
||||
public class ViewQueryApiController {
|
||||
|
||||
@Autowired
|
||||
private ViewService viewService;
|
||||
@Autowired
|
||||
private QueryService queryService;
|
||||
|
||||
@PostMapping("/view")
|
||||
public Object queryByView(@RequestBody QueryViewReq queryViewReq,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
User user = UserHolder.findUser(request, response);
|
||||
SemanticQueryReq queryReq = viewService.convert(queryViewReq);
|
||||
return queryService.queryByReq(queryReq, user);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.tencent.supersonic.headless.server.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DatabaseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SqlExecuteReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.DatabaseParameter;
|
||||
@@ -13,7 +14,7 @@ public interface DatabaseService {
|
||||
|
||||
SemanticQueryResp executeSql(String sql, DatabaseResp databaseResp);
|
||||
|
||||
SemanticQueryResp executeSql(String sql, Long id, User user);
|
||||
SemanticQueryResp executeSql(SqlExecuteReq sqlExecuteReq, Long id, User user);
|
||||
|
||||
DatabaseResp getDatabase(Long id, User user);
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import java.util.List;
|
||||
*/
|
||||
public interface DictConfService {
|
||||
|
||||
Long addDictConf(DictItemReq itemValueReq, User user);
|
||||
DictItemResp addDictConf(DictItemReq itemValueReq, User user);
|
||||
|
||||
Long editDictConf(DictItemReq itemValueReq, User user);
|
||||
DictItemResp editDictConf(DictItemReq itemValueReq, User user);
|
||||
|
||||
List<DictItemResp> queryDictConf(DictItemFilter dictItemFilter, User user);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.tencent.supersonic.headless.api.pojo.request.MetaBatchReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetricBaseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.PageMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.MetricResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetricsFilter;
|
||||
@@ -52,4 +54,6 @@ public interface MetricService {
|
||||
void sendMetricEventBatch(List<Long> modelIds, EventType eventType);
|
||||
|
||||
List<MetricResp> queryMetrics(MetricsFilter metricsFilter);
|
||||
|
||||
QueryStructReq convert(QueryMetricReq queryMetricReq);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.tencent.supersonic.headless.api.pojo.request.ExplainSqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.ItemUseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryDimValueReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryItemReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ExplainResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ItemQueryResultResp;
|
||||
@@ -28,6 +27,4 @@ public interface QueryService {
|
||||
@ApiHeaderCheck
|
||||
ItemQueryResultResp queryMetricDataById(QueryItemReq queryApiReq, HttpServletRequest request) throws Exception;
|
||||
|
||||
SemanticQueryResp queryByMetric(QueryMetricReq queryMetricReq, User user) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.tencent.supersonic.headless.server.service;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilterPage;
|
||||
import java.util.List;
|
||||
|
||||
public interface TagService {
|
||||
|
||||
TagResp create(TagReq tagReq, User user) throws Exception;
|
||||
|
||||
TagResp update(TagReq tagReq, User user) throws Exception;
|
||||
|
||||
void delete(Long id, User user) throws Exception;
|
||||
|
||||
TagResp getTag(Long id);
|
||||
|
||||
List<TagResp> query(TagFilter tagFilter);
|
||||
|
||||
PageInfo<TagResp> queryPage(TagFilterPage tagFilterPage, User user);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.tencent.supersonic.headless.server.service;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryViewReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.ViewReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ViewResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
@@ -25,4 +27,6 @@ public interface ViewService {
|
||||
List<ViewResp> getViews(User user);
|
||||
|
||||
List<ViewResp> getViewsInheritAuth(User user, Long domainId);
|
||||
|
||||
SemanticQueryReq convert(QueryViewReq queryViewReq);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.tencent.supersonic.headless.server.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.common.pojo.exception.InvalidPermissionException;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DatabaseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SqlExecuteReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
|
||||
@@ -11,6 +12,7 @@ import com.tencent.supersonic.headless.core.adaptor.db.DbAdaptorFactory;
|
||||
import com.tencent.supersonic.headless.core.pojo.Database;
|
||||
import com.tencent.supersonic.headless.core.utils.JdbcDataSourceUtils;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlUtils;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlVariableParseUtils;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.DatabaseDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.DatabaseRepository;
|
||||
import com.tencent.supersonic.headless.server.pojo.DatabaseParameter;
|
||||
@@ -116,32 +118,19 @@ public class DatabaseServiceImpl implements DatabaseService {
|
||||
@Override
|
||||
public DatabaseResp getDatabase(Long id, User user) {
|
||||
DatabaseResp databaseResp = getDatabase(id);
|
||||
if (!databaseResp.getAdmins().contains(user.getName())
|
||||
&& !databaseResp.getViewers().contains(user.getName())
|
||||
&& !databaseResp.getCreatedBy().equals(user.getName())) {
|
||||
throw new InvalidPermissionException("您暂无查看该数据库详情的权限, 请联系创建人: "
|
||||
+ databaseResp.getCreatedBy());
|
||||
}
|
||||
checkPermission(databaseResp, user);
|
||||
return databaseResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticQueryResp executeSql(String sql, Long id, User user) {
|
||||
public SemanticQueryResp executeSql(SqlExecuteReq sqlExecuteReq, Long id, User user) {
|
||||
DatabaseResp databaseResp = getDatabase(id);
|
||||
if (databaseResp == null) {
|
||||
return new SemanticQueryResp();
|
||||
}
|
||||
List<String> admins = databaseResp.getAdmins();
|
||||
List<String> viewers = databaseResp.getViewers();
|
||||
if (!admins.contains(user.getName())
|
||||
&& !viewers.contains(user.getName())
|
||||
&& !databaseResp.getCreatedBy().equalsIgnoreCase(user.getName())
|
||||
&& !user.isSuperAdmin()) {
|
||||
String message = String.format("您暂无当前数据库%s权限, 请联系数据库管理员%s开通",
|
||||
databaseResp.getName(),
|
||||
String.join(",", admins));
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
checkPermission(databaseResp, user);
|
||||
String sql = sqlExecuteReq.getSql();
|
||||
sql = SqlVariableParseUtils.parse(sql, sqlExecuteReq.getSqlVariables(), Lists.newArrayList());
|
||||
return executeSql(sql, databaseResp);
|
||||
}
|
||||
|
||||
@@ -195,4 +184,18 @@ public class DatabaseServiceImpl implements DatabaseService {
|
||||
return queryWithColumns(metaQuerySql, DatabaseConverter.convert(databaseResp));
|
||||
}
|
||||
|
||||
private void checkPermission(DatabaseResp databaseResp, User user) {
|
||||
List<String> admins = databaseResp.getAdmins();
|
||||
List<String> viewers = databaseResp.getViewers();
|
||||
if (!admins.contains(user.getName())
|
||||
&& !viewers.contains(user.getName())
|
||||
&& !databaseResp.getCreatedBy().equalsIgnoreCase(user.getName())
|
||||
&& !user.isSuperAdmin()) {
|
||||
String message = String.format("您暂无当前数据库%s权限, 请联系数据库创建人:%s开通",
|
||||
databaseResp.getName(),
|
||||
databaseResp.getCreatedBy());
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.tencent.supersonic.headless.server.service.impl;
|
||||
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DictItemFilter;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.DictItemReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DictItemResp;
|
||||
@@ -10,11 +9,15 @@ import com.tencent.supersonic.headless.server.persistence.dataobject.DictConfDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.DictRepository;
|
||||
import com.tencent.supersonic.headless.server.service.DictConfService;
|
||||
import com.tencent.supersonic.headless.server.utils.DictUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DictConfServiceImpl implements DictConfService {
|
||||
|
||||
private final DictRepository dictRepository;
|
||||
@@ -27,20 +30,49 @@ public class DictConfServiceImpl implements DictConfService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long addDictConf(DictItemReq itemValueReq, User user) {
|
||||
public DictItemResp addDictConf(DictItemReq itemValueReq, User user) {
|
||||
DictConfDO dictConfDO = dictConverter.generateDictConfDO(itemValueReq, user);
|
||||
return dictRepository.addDictConf(dictConfDO);
|
||||
Boolean exist = checkConfExist(itemValueReq, user);
|
||||
if (exist) {
|
||||
throw new RuntimeException("dictConf is existed");
|
||||
}
|
||||
Long id = dictRepository.addDictConf(dictConfDO);
|
||||
log.debug("dictConfDO:{}", dictConfDO);
|
||||
|
||||
DictItemFilter filter = DictItemFilter.builder()
|
||||
.id(id)
|
||||
.status(itemValueReq.getStatus())
|
||||
.build();
|
||||
Optional<DictItemResp> dictItemResp = queryDictConf(filter, user).stream().findFirst();
|
||||
if (dictItemResp.isPresent()) {
|
||||
return dictItemResp.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Boolean checkConfExist(DictItemReq itemValueReq, User user) {
|
||||
DictItemFilter filter = DictItemFilter.builder().build();
|
||||
BeanUtils.copyProperties(itemValueReq, filter);
|
||||
filter.setStatus(null);
|
||||
Optional<DictItemResp> dictItemResp = queryDictConf(filter, user).stream()
|
||||
.findFirst();
|
||||
if (dictItemResp.isPresent()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long editDictConf(DictItemReq itemValueReq, User user) {
|
||||
public DictItemResp editDictConf(DictItemReq itemValueReq, User user) {
|
||||
DictConfDO dictConfDO = dictConverter.generateDictConfDO(itemValueReq, user);
|
||||
dictRepository.editDictConf(dictConfDO);
|
||||
if (StatusEnum.DELETED.equals(itemValueReq.getStatus())) {
|
||||
// todo delete dict file and refresh
|
||||
|
||||
DictItemFilter filter = DictItemFilter.builder().build();
|
||||
BeanUtils.copyProperties(itemValueReq, filter);
|
||||
Optional<DictItemResp> dictItemResp = queryDictConf(filter, user).stream().findFirst();
|
||||
if (dictItemResp.isPresent()) {
|
||||
return dictItemResp.get();
|
||||
}
|
||||
return itemValueReq.getItemId();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,7 @@ public class DictTaskServiceImpl implements DictTaskService {
|
||||
}
|
||||
|
||||
private Long handleDictTaskByItemResp(DictItemResp dictItemResp, User user) {
|
||||
DictTaskDO dictTaskDO = dictConverter.generateDictTaskDO(dictItemResp, user);
|
||||
DictTaskDO dictTaskDO = dictConverter.generateDictTaskDO(dictItemResp, user, TaskStatusEnum.PENDING);
|
||||
log.info("[addDictTask] dictTaskDO:{}", dictTaskDO);
|
||||
dictRepository.addDictTask(dictTaskDO);
|
||||
Long idInDb = dictTaskDO.getId();
|
||||
@@ -95,14 +95,14 @@ public class DictTaskServiceImpl implements DictTaskService {
|
||||
dictTaskDO.setStatus(TaskStatusEnum.RUNNING.getStatus());
|
||||
dictRepository.editDictTask(dictTaskDO);
|
||||
|
||||
// 1.生成item字典数据
|
||||
// 1.Generate item dictionary data
|
||||
List<String> data = dictUtils.fetchItemValue(dictItemResp);
|
||||
|
||||
// 2.变更字典文件
|
||||
// 2.Change dictionary file
|
||||
String fileName = dictItemResp.fetchDictFileName() + Constants.DOT + dictFileType;
|
||||
fileHandler.writeFile(data, fileName, false);
|
||||
|
||||
// 3.实时变更内存中字典数据
|
||||
// 3.Change in-memory dictionary data in real time
|
||||
try {
|
||||
HanlpHelper.reloadCustomDictionary();
|
||||
dictTaskDO.setStatus(TaskStatusEnum.SUCCESS.getStatus());
|
||||
@@ -124,7 +124,10 @@ public class DictTaskServiceImpl implements DictTaskService {
|
||||
} catch (Exception e) {
|
||||
log.error("reloadCustomDictionary error", e);
|
||||
}
|
||||
|
||||
// Add a clear dictionary file record
|
||||
DictTaskDO dictTaskDO = dictConverter.generateDictTaskDO(dictItemResp, user, TaskStatusEnum.INITIAL);
|
||||
log.info("[addDictTask] dictTaskDO:{}", dictTaskDO);
|
||||
dictRepository.addDictTask(dictTaskDO);
|
||||
return 0L;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.github.pagehelper.PageInfo;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.common.pojo.Aggregator;
|
||||
import com.tencent.supersonic.common.pojo.Constants;
|
||||
import com.tencent.supersonic.common.pojo.DataEvent;
|
||||
import com.tencent.supersonic.common.pojo.DataItem;
|
||||
@@ -20,11 +21,15 @@ import com.tencent.supersonic.headless.api.pojo.DrillDownDimension;
|
||||
import com.tencent.supersonic.headless.api.pojo.MeasureParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricQueryDefaultConfig;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MetricDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetaBatchReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetricBaseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.PageMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DimensionResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.MetricResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ViewResp;
|
||||
@@ -32,23 +37,22 @@ import com.tencent.supersonic.headless.server.persistence.dataobject.CollectDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.MetricDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.MetricQueryDefaultConfigDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.MetricRepository;
|
||||
import com.tencent.supersonic.headless.server.pojo.DimensionsFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetricFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetricsFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.ModelCluster;
|
||||
import com.tencent.supersonic.headless.server.service.CollectService;
|
||||
import com.tencent.supersonic.headless.server.service.DomainService;
|
||||
import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.ViewService;
|
||||
import com.tencent.supersonic.headless.server.utils.MetricCheckUtils;
|
||||
import com.tencent.supersonic.headless.server.utils.MetricConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.tencent.supersonic.headless.server.utils.ModelClusterBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -56,6 +60,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -65,7 +74,7 @@ public class MetricServiceImpl implements MetricService {
|
||||
|
||||
private ModelService modelService;
|
||||
|
||||
private DomainService domainService;
|
||||
private DimensionService dimensionService;
|
||||
|
||||
private ChatGptHelper chatGptHelper;
|
||||
|
||||
@@ -77,18 +86,18 @@ public class MetricServiceImpl implements MetricService {
|
||||
|
||||
public MetricServiceImpl(MetricRepository metricRepository,
|
||||
ModelService modelService,
|
||||
DomainService domainService,
|
||||
ChatGptHelper chatGptHelper,
|
||||
CollectService collectService,
|
||||
ViewService viewService,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
this.domainService = domainService;
|
||||
ApplicationEventPublisher eventPublisher,
|
||||
DimensionService dimensionService) {
|
||||
this.metricRepository = metricRepository;
|
||||
this.modelService = modelService;
|
||||
this.chatGptHelper = chatGptHelper;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.collectService = collectService;
|
||||
this.viewService = viewService;
|
||||
this.dimensionService = dimensionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,7 +248,7 @@ public class MetricServiceImpl implements MetricService {
|
||||
}
|
||||
|
||||
private boolean filterByField(List<MetricResp> metricResps, MetricResp metricResp,
|
||||
List<String> fields, Set<MetricResp> metricRespFiltered) {
|
||||
List<String> fields, Set<MetricResp> metricRespFiltered) {
|
||||
if (MetricDefineType.METRIC.equals(metricResp.getMetricDefineType())) {
|
||||
List<Long> ids = metricResp.getMetricDefineByMetricParams().getMetrics()
|
||||
.stream().map(MetricParam::getId).collect(Collectors.toList());
|
||||
@@ -483,4 +492,107 @@ public class MetricServiceImpl implements MetricService {
|
||||
.type(TypeEnums.METRIC).defaultAgg(metricResp.getDefaultAgg()).build();
|
||||
}
|
||||
|
||||
public QueryStructReq convert(QueryMetricReq queryMetricReq) {
|
||||
//1. If a domainId exists, the modelIds obtained from the domainId.
|
||||
Set<Long> modelIdsByDomainId = getModelIdsByDomainId(queryMetricReq);
|
||||
|
||||
//2. get metrics and dimensions
|
||||
List<MetricResp> metricResps = getMetricResps(queryMetricReq, modelIdsByDomainId);
|
||||
|
||||
List<DimensionResp> dimensionResps = getDimensionResps(queryMetricReq, modelIdsByDomainId);
|
||||
|
||||
//3. choose ModelCluster
|
||||
Set<Long> modelIds = getModelIds(modelIdsByDomainId, metricResps, dimensionResps);
|
||||
ModelCluster modelCluster = getModelCluster(metricResps, modelIds);
|
||||
|
||||
//4. set groups
|
||||
List<String> dimensionBizNames = dimensionResps.stream()
|
||||
.filter(entry -> modelCluster.getModelIds().contains(entry.getModelId()))
|
||||
.map(entry -> entry.getBizName()).collect(Collectors.toList());
|
||||
|
||||
QueryStructReq queryStructReq = new QueryStructReq();
|
||||
if (org.apache.commons.collections.CollectionUtils.isNotEmpty(dimensionBizNames)) {
|
||||
queryStructReq.setGroups(dimensionBizNames);
|
||||
}
|
||||
//5. set aggregators
|
||||
List<String> metricBizNames = metricResps.stream()
|
||||
.filter(entry -> modelCluster.getModelIds().contains(entry.getModelId()))
|
||||
.map(entry -> entry.getBizName()).collect(Collectors.toList());
|
||||
if (org.apache.commons.collections.CollectionUtils.isEmpty(metricBizNames)) {
|
||||
throw new IllegalArgumentException("Invalid input parameters, unable to obtain valid metrics");
|
||||
}
|
||||
List<Aggregator> aggregators = new ArrayList<>();
|
||||
for (String metricBizName : metricBizNames) {
|
||||
Aggregator aggregator = new Aggregator();
|
||||
aggregator.setColumn(metricBizName);
|
||||
aggregators.add(aggregator);
|
||||
}
|
||||
queryStructReq.setAggregators(aggregators);
|
||||
queryStructReq.setLimit(queryMetricReq.getLimit());
|
||||
//6. set modelIds
|
||||
for (Long modelId : modelCluster.getModelIds()) {
|
||||
queryStructReq.addModelId(modelId);
|
||||
}
|
||||
//7. set dateInfo
|
||||
queryStructReq.setDateInfo(queryMetricReq.getDateInfo());
|
||||
return queryStructReq;
|
||||
}
|
||||
|
||||
private ModelCluster getModelCluster(List<MetricResp> metricResps, Set<Long> modelIds) {
|
||||
Map<String, ModelCluster> modelClusterMap = ModelClusterBuilder.buildModelClusters(new ArrayList<>(modelIds));
|
||||
|
||||
Map<String, List<SchemaItem>> modelClusterToMatchCount = new HashMap<>();
|
||||
for (ModelCluster modelCluster : modelClusterMap.values()) {
|
||||
for (MetricResp metricResp : metricResps) {
|
||||
if (modelCluster.getModelIds().contains(metricResp.getModelId())) {
|
||||
modelClusterToMatchCount.computeIfAbsent(modelCluster.getKey(), k -> new ArrayList<>())
|
||||
.add(metricResp);
|
||||
}
|
||||
}
|
||||
}
|
||||
String keyWithMaxSize = modelClusterToMatchCount.entrySet().stream()
|
||||
.max(Comparator.comparingInt(entry -> entry.getValue().size()))
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
|
||||
return modelClusterMap.get(keyWithMaxSize);
|
||||
}
|
||||
|
||||
private Set<Long> getModelIds(Set<Long> modelIdsByDomainId, List<MetricResp> metricResps,
|
||||
List<DimensionResp> dimensionResps) {
|
||||
Set<Long> result = new HashSet<>();
|
||||
if (org.apache.commons.collections.CollectionUtils.isNotEmpty(modelIdsByDomainId)) {
|
||||
result.addAll(modelIdsByDomainId);
|
||||
return result;
|
||||
}
|
||||
Set<Long> metricModelIds = metricResps.stream().map(entry -> entry.getModelId())
|
||||
.collect(Collectors.toSet());
|
||||
result.addAll(metricModelIds);
|
||||
|
||||
Set<Long> dimensionModelIds = dimensionResps.stream().map(entry -> entry.getModelId())
|
||||
.collect(Collectors.toSet());
|
||||
result.addAll(dimensionModelIds);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<DimensionResp> getDimensionResps(QueryMetricReq queryMetricReq, Set<Long> modelIds) {
|
||||
DimensionsFilter dimensionsFilter = new DimensionsFilter();
|
||||
BeanUtils.copyProperties(queryMetricReq, dimensionsFilter);
|
||||
dimensionsFilter.setModelIds(new ArrayList<>(modelIds));
|
||||
return dimensionService.queryDimensions(dimensionsFilter);
|
||||
}
|
||||
|
||||
private List<MetricResp> getMetricResps(QueryMetricReq queryMetricReq, Set<Long> modelIds) {
|
||||
MetricsFilter metricsFilter = new MetricsFilter();
|
||||
BeanUtils.copyProperties(queryMetricReq, metricsFilter);
|
||||
metricsFilter.setModelIds(new ArrayList<>(modelIds));
|
||||
return queryMetrics(metricsFilter);
|
||||
}
|
||||
|
||||
private Set<Long> getModelIdsByDomainId(QueryMetricReq queryMetricReq) {
|
||||
List<ModelResp> modelResps = modelService.getAllModelByDomainIds(
|
||||
Collections.singletonList(queryMetricReq.getDomainId()));
|
||||
return modelResps.stream().map(ModelResp::getId).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,16 +13,15 @@ import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
|
||||
import com.tencent.supersonic.headless.api.pojo.Dim;
|
||||
import com.tencent.supersonic.headless.api.pojo.Item;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
|
||||
import com.tencent.supersonic.headless.api.pojo.SingleItemQueryResult;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.ExplainSqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.ItemUseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryDimValueReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryItemReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMultiStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryTagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SchemaFilterReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.AppDetailResp;
|
||||
@@ -45,24 +44,14 @@ import com.tencent.supersonic.headless.server.annotation.S2DataPermission;
|
||||
import com.tencent.supersonic.headless.server.aspect.ApiHeaderCheckAspect;
|
||||
import com.tencent.supersonic.headless.server.manager.SemanticSchemaManager;
|
||||
import com.tencent.supersonic.headless.server.pojo.DimensionFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.DimensionsFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetricsFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.ModelCluster;
|
||||
import com.tencent.supersonic.headless.server.service.AppService;
|
||||
import com.tencent.supersonic.headless.server.service.Catalog;
|
||||
import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
import com.tencent.supersonic.headless.server.utils.ModelClusterBuilder;
|
||||
import com.tencent.supersonic.headless.server.utils.QueryReqConverter;
|
||||
import com.tencent.supersonic.headless.server.utils.QueryUtils;
|
||||
import com.tencent.supersonic.headless.server.utils.StatUtils;
|
||||
import com.tencent.supersonic.headless.server.utils.TagReqConverter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -72,7 +61,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
@@ -83,46 +71,34 @@ public class QueryServiceImpl implements QueryService {
|
||||
private StatUtils statUtils;
|
||||
private final QueryUtils queryUtils;
|
||||
private final QueryReqConverter queryReqConverter;
|
||||
private final TagReqConverter tagReqConverter;
|
||||
private final Catalog catalog;
|
||||
private final AppService appService;
|
||||
private final QueryCache queryCache;
|
||||
private final SemanticSchemaManager semanticSchemaManager;
|
||||
|
||||
private final QueryParser queryParser;
|
||||
|
||||
private final QueryPlanner queryPlanner;
|
||||
|
||||
private final MetricService metricService;
|
||||
|
||||
private final ModelService modelService;
|
||||
|
||||
private final DimensionService dimensionService;
|
||||
|
||||
public QueryServiceImpl(
|
||||
StatUtils statUtils,
|
||||
QueryUtils queryUtils,
|
||||
QueryReqConverter queryReqConverter,
|
||||
Catalog catalog,
|
||||
TagReqConverter tagReqConverter, Catalog catalog,
|
||||
AppService appService,
|
||||
QueryCache queryCache,
|
||||
SemanticSchemaManager semanticSchemaManager,
|
||||
DefaultQueryParser queryParser,
|
||||
QueryPlanner queryPlanner,
|
||||
MetricService metricService,
|
||||
ModelService modelService,
|
||||
DimensionService dimensionService) {
|
||||
QueryPlanner queryPlanner) {
|
||||
this.statUtils = statUtils;
|
||||
this.queryUtils = queryUtils;
|
||||
this.queryReqConverter = queryReqConverter;
|
||||
this.tagReqConverter = tagReqConverter;
|
||||
this.catalog = catalog;
|
||||
this.appService = appService;
|
||||
this.queryCache = queryCache;
|
||||
this.semanticSchemaManager = semanticSchemaManager;
|
||||
this.queryParser = queryParser;
|
||||
this.queryPlanner = queryPlanner;
|
||||
this.metricService = metricService;
|
||||
this.modelService = modelService;
|
||||
this.dimensionService = dimensionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,6 +161,9 @@ public class QueryServiceImpl implements QueryService {
|
||||
if (semanticQueryReq instanceof QueryMultiStructReq) {
|
||||
return buildMultiStructQueryStatement((QueryMultiStructReq) semanticQueryReq);
|
||||
}
|
||||
if (semanticQueryReq instanceof QueryTagReq) {
|
||||
return buildTagQueryStatement((QueryTagReq) semanticQueryReq);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -220,6 +199,21 @@ public class QueryServiceImpl implements QueryService {
|
||||
return queryUtils.sqlParserUnion(queryMultiStructReq, sqlParsers);
|
||||
}
|
||||
|
||||
private QueryStatement buildTagQueryStatement(QueryTagReq queryTagReq)
|
||||
throws Exception {
|
||||
SchemaFilterReq schemaFilterReq = new SchemaFilterReq();
|
||||
SchemaFilterReq filter = buildSchemaFilterReq(queryTagReq);
|
||||
schemaFilterReq.setModelIds(queryTagReq.getModelIds());
|
||||
SemanticSchemaResp semanticSchemaResp = catalog.fetchSemanticSchema(filter);
|
||||
QueryStatement queryStatement = tagReqConverter.convert(queryTagReq, semanticSchemaResp);
|
||||
queryStatement.setModelIds(queryTagReq.getModelIds());
|
||||
queryStatement.setEnableOptimize(queryUtils.enableOptimize());
|
||||
queryStatement.setSemanticSchemaResp(semanticSchemaResp);
|
||||
SemanticModel semanticModel = semanticSchemaManager.getTagSemanticModel(semanticSchemaResp);
|
||||
queryStatement.setSemanticModel(semanticModel);
|
||||
return queryStatement;
|
||||
}
|
||||
|
||||
private SchemaFilterReq buildSchemaFilterReq(SemanticQueryReq semanticQueryReq) {
|
||||
SchemaFilterReq schemaFilterReq = new SchemaFilterReq();
|
||||
schemaFilterReq.setViewId(semanticQueryReq.getViewId());
|
||||
@@ -265,58 +259,6 @@ public class QueryServiceImpl implements QueryService {
|
||||
return ItemQueryResultResp.builder().results(results).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticQueryResp queryByMetric(QueryMetricReq queryMetricReq, User user) {
|
||||
QueryStructReq queryStructReq = buildQueryStructReq(queryMetricReq);
|
||||
return queryByReq(queryStructReq.convert(), user);
|
||||
}
|
||||
|
||||
private QueryStructReq buildQueryStructReq(QueryMetricReq queryMetricReq) {
|
||||
//1. If a domainId exists, the modelIds obtained from the domainId.
|
||||
Set<Long> modelIdsByDomainId = getModelIdsByDomainId(queryMetricReq);
|
||||
|
||||
//2. get metrics and dimensions
|
||||
List<MetricResp> metricResps = getMetricResps(queryMetricReq, modelIdsByDomainId);
|
||||
|
||||
List<DimensionResp> dimensionResps = getDimensionResps(queryMetricReq, modelIdsByDomainId);
|
||||
|
||||
//3. choose ModelCluster
|
||||
Set<Long> modelIds = getModelIds(modelIdsByDomainId, metricResps, dimensionResps);
|
||||
ModelCluster modelCluster = getModelCluster(metricResps, modelIds);
|
||||
|
||||
//4. set groups
|
||||
List<String> dimensionBizNames = dimensionResps.stream()
|
||||
.filter(entry -> modelCluster.getModelIds().contains(entry.getModelId()))
|
||||
.map(entry -> entry.getBizName()).collect(Collectors.toList());
|
||||
|
||||
QueryStructReq queryStructReq = new QueryStructReq();
|
||||
if (CollectionUtils.isNotEmpty(dimensionBizNames)) {
|
||||
queryStructReq.setGroups(dimensionBizNames);
|
||||
}
|
||||
//5. set aggregators
|
||||
List<String> metricBizNames = metricResps.stream()
|
||||
.filter(entry -> modelCluster.getModelIds().contains(entry.getModelId()))
|
||||
.map(entry -> entry.getBizName()).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(metricBizNames)) {
|
||||
throw new IllegalArgumentException("Invalid input parameters, unable to obtain valid metrics");
|
||||
}
|
||||
List<Aggregator> aggregators = new ArrayList<>();
|
||||
for (String metricBizName : metricBizNames) {
|
||||
Aggregator aggregator = new Aggregator();
|
||||
aggregator.setColumn(metricBizName);
|
||||
aggregators.add(aggregator);
|
||||
}
|
||||
queryStructReq.setAggregators(aggregators);
|
||||
queryStructReq.setLimit(queryMetricReq.getLimit());
|
||||
//6. set modelIds
|
||||
for (Long modelId : modelCluster.getModelIds()) {
|
||||
queryStructReq.addModelId(modelId);
|
||||
}
|
||||
//7. set dateInfo
|
||||
queryStructReq.setDateInfo(queryMetricReq.getDateInfo());
|
||||
return queryStructReq;
|
||||
}
|
||||
|
||||
private QueryStructReq buildQueryStructReq(List<DimensionResp> dimensionResps,
|
||||
MetricResp metricResp, DateConf dateConf, Long limit) {
|
||||
Set<Long> modelIds = dimensionResps.stream().map(DimensionResp::getModelId).collect(Collectors.toSet());
|
||||
@@ -334,64 +276,6 @@ public class QueryServiceImpl implements QueryService {
|
||||
return queryStructReq;
|
||||
}
|
||||
|
||||
private ModelCluster getModelCluster(List<MetricResp> metricResps, Set<Long> modelIds) {
|
||||
Map<String, ModelCluster> modelClusterMap = ModelClusterBuilder.buildModelClusters(new ArrayList<>(modelIds));
|
||||
|
||||
Map<String, List<SchemaItem>> modelClusterToMatchCount = new HashMap<>();
|
||||
for (ModelCluster modelCluster : modelClusterMap.values()) {
|
||||
for (MetricResp metricResp : metricResps) {
|
||||
if (modelCluster.getModelIds().contains(metricResp.getModelId())) {
|
||||
modelClusterToMatchCount.computeIfAbsent(modelCluster.getKey(), k -> new ArrayList<>())
|
||||
.add(metricResp);
|
||||
}
|
||||
}
|
||||
}
|
||||
String keyWithMaxSize = modelClusterToMatchCount.entrySet().stream()
|
||||
.max(Comparator.comparingInt(entry -> entry.getValue().size()))
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
|
||||
return modelClusterMap.get(keyWithMaxSize);
|
||||
}
|
||||
|
||||
private Set<Long> getModelIds(Set<Long> modelIdsByDomainId, List<MetricResp> metricResps,
|
||||
List<DimensionResp> dimensionResps) {
|
||||
Set<Long> result = new HashSet<>();
|
||||
if (CollectionUtils.isNotEmpty(modelIdsByDomainId)) {
|
||||
result.addAll(modelIdsByDomainId);
|
||||
return result;
|
||||
}
|
||||
Set<Long> metricModelIds = metricResps.stream().map(entry -> entry.getModelId())
|
||||
.collect(Collectors.toSet());
|
||||
result.addAll(metricModelIds);
|
||||
|
||||
Set<Long> dimensionModelIds = dimensionResps.stream().map(entry -> entry.getModelId())
|
||||
.collect(Collectors.toSet());
|
||||
result.addAll(dimensionModelIds);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<DimensionResp> getDimensionResps(QueryMetricReq queryMetricReq, Set<Long> modelIds) {
|
||||
DimensionsFilter dimensionsFilter = new DimensionsFilter();
|
||||
BeanUtils.copyProperties(queryMetricReq, dimensionsFilter);
|
||||
dimensionsFilter.setModelIds(new ArrayList<>(modelIds));
|
||||
List<DimensionResp> dimensionResps = dimensionService.queryDimensions(dimensionsFilter);
|
||||
return dimensionResps;
|
||||
}
|
||||
|
||||
private List<MetricResp> getMetricResps(QueryMetricReq queryMetricReq, Set<Long> modelIds) {
|
||||
MetricsFilter metricsFilter = new MetricsFilter();
|
||||
BeanUtils.copyProperties(queryMetricReq, metricsFilter);
|
||||
metricsFilter.setModelIds(new ArrayList<>(modelIds));
|
||||
return metricService.queryMetrics(metricsFilter);
|
||||
}
|
||||
|
||||
private Set<Long> getModelIdsByDomainId(QueryMetricReq queryMetricReq) {
|
||||
List<ModelResp> modelResps = modelService.getAllModelByDomainIds(
|
||||
Collections.singletonList(queryMetricReq.getDomainId()));
|
||||
return modelResps.stream().map(ModelResp::getId).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private SingleItemQueryResult dataQuery(Integer appId, Item item, DateConf dateConf, Long limit) throws Exception {
|
||||
MetricResp metricResp = catalog.getMetric(item.getId());
|
||||
item.setCreatedBy(metricResp.getCreatedBy());
|
||||
|
||||
@@ -29,15 +29,18 @@ import com.tencent.supersonic.headless.api.pojo.response.MetricSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticSchemaResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ViewResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ViewSchemaResp;
|
||||
import com.tencent.supersonic.headless.server.pojo.MetaFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import com.tencent.supersonic.headless.server.service.DomainService;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelRelaService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.SchemaService;
|
||||
import com.tencent.supersonic.headless.server.service.TagService;
|
||||
import com.tencent.supersonic.headless.server.service.ViewService;
|
||||
import com.tencent.supersonic.headless.server.utils.DimensionConverter;
|
||||
import com.tencent.supersonic.headless.server.utils.MetricConverter;
|
||||
@@ -78,14 +81,15 @@ public class SchemaServiceImpl implements SchemaService {
|
||||
private final DomainService domainService;
|
||||
private final ViewService viewService;
|
||||
private final ModelRelaService modelRelaService;
|
||||
private final TagService tagService;
|
||||
|
||||
public SchemaServiceImpl(ModelService modelService,
|
||||
DimensionService dimensionService,
|
||||
MetricService metricService,
|
||||
DomainService domainService,
|
||||
ViewService viewService,
|
||||
ModelRelaService modelRelaService,
|
||||
StatUtils statUtils) {
|
||||
DimensionService dimensionService,
|
||||
MetricService metricService,
|
||||
DomainService domainService,
|
||||
ViewService viewService,
|
||||
ModelRelaService modelRelaService,
|
||||
StatUtils statUtils, TagService tagService) {
|
||||
this.modelService = modelService;
|
||||
this.dimensionService = dimensionService;
|
||||
this.metricService = metricService;
|
||||
@@ -93,6 +97,7 @@ public class SchemaServiceImpl implements SchemaService {
|
||||
this.viewService = viewService;
|
||||
this.modelRelaService = modelRelaService;
|
||||
this.statUtils = statUtils;
|
||||
this.tagService = tagService;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -301,6 +306,11 @@ public class SchemaServiceImpl implements SchemaService {
|
||||
.flatMap(Collection::stream).collect(Collectors.toList()));
|
||||
semanticSchemaResp.setModelResps(modelSchemaResps.stream().map(this::convert).collect(Collectors.toList()));
|
||||
semanticSchemaResp.setSchemaType(SchemaType.MODEL);
|
||||
// add tag info
|
||||
TagFilter tagFilter = new TagFilter();
|
||||
tagFilter.setModelIds(schemaFilterReq.getModelIds());
|
||||
List<TagResp> tagResps = tagService.query(tagFilter);
|
||||
semanticSchemaResp.setTags(tagResps);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(semanticSchemaResp.getModelIds())) {
|
||||
DatabaseResp databaseResp = modelService.getDatabaseByModelId(semanticSchemaResp.getModelIds().get(0));
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
package com.tencent.supersonic.headless.server.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.common.pojo.enums.AuthType;
|
||||
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
|
||||
import com.tencent.supersonic.common.pojo.enums.TypeEnums;
|
||||
import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
|
||||
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
|
||||
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.CollectDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.TagRepository;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilter;
|
||||
import com.tencent.supersonic.headless.server.pojo.TagFilterPage;
|
||||
import com.tencent.supersonic.headless.server.service.CollectService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.TagService;
|
||||
import com.tencent.supersonic.headless.server.utils.NameCheckUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class TagServiceImpl implements TagService {
|
||||
|
||||
private final TagRepository tagRepository;
|
||||
private final ModelService modelService;
|
||||
private final CollectService collectService;
|
||||
|
||||
public TagServiceImpl(TagRepository tagRepository, ModelService modelService,
|
||||
CollectService collectService) {
|
||||
this.tagRepository = tagRepository;
|
||||
this.modelService = modelService;
|
||||
this.collectService = collectService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagResp create(TagReq tagReq, User user) throws Exception {
|
||||
checkParam(tagReq);
|
||||
checkExit(tagReq);
|
||||
TagDO tagDO = convert(tagReq);
|
||||
tagDO.setCreatedBy(user.getName());
|
||||
tagDO.setCreatedAt(new Date());
|
||||
tagDO.setStatus(StatusEnum.ONLINE.getCode());
|
||||
tagRepository.create(tagDO);
|
||||
return convert(tagDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagResp update(TagReq tagReq, User user) throws Exception {
|
||||
if (Objects.isNull(tagReq.getId()) || tagReq.getId() <= 0) {
|
||||
throw new RuntimeException("id is empty");
|
||||
}
|
||||
TagDO tagDO = tagRepository.getTagById(tagReq.getId());
|
||||
if (Objects.nonNull(tagDO) && tagDO.getId() > 0) {
|
||||
if (Objects.nonNull(tagReq.getExt()) && !tagReq.getExt().isEmpty()) {
|
||||
tagDO.setExt(tagReq.getExtJson());
|
||||
}
|
||||
}
|
||||
if (Objects.nonNull(tagReq.getTagDefineType())) {
|
||||
tagDO.setDefineType(tagReq.getTagDefineType().name());
|
||||
}
|
||||
if (Objects.nonNull(tagReq.getTagDefineParams()) && !StringUtils.isBlank(
|
||||
tagReq.getTagDefineParams().getExpr())) {
|
||||
tagDO.setTypeParams(tagReq.getTypeParamsJson());
|
||||
}
|
||||
tagDO.setUpdatedBy(user.getName());
|
||||
tagDO.setUpdatedAt(new Date());
|
||||
tagRepository.update(tagDO);
|
||||
return convert(tagDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id, User user) throws Exception {
|
||||
TagDO tagDO = tagRepository.getTagById(id);
|
||||
if (Objects.isNull(tagDO)) {
|
||||
throw new RuntimeException("tag not found");
|
||||
}
|
||||
tagDO.setStatus(StatusEnum.DELETED.getCode());
|
||||
tagDO.setUpdatedBy(user.getName());
|
||||
tagDO.setUpdatedAt(new Date());
|
||||
tagRepository.update(tagDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagResp getTag(Long id) {
|
||||
return convert(tagRepository.getTagById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TagResp> query(TagFilter tagFilter) {
|
||||
List<TagDO> tagDOS = tagRepository.query(tagFilter);
|
||||
if (!CollectionUtils.isEmpty(tagDOS)) {
|
||||
return tagDOS.stream().map(tagDO -> convert(tagDO)).collect(Collectors.toList());
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageInfo<TagResp> queryPage(TagFilterPage tagFilterPage, User user) {
|
||||
TagFilter tagFilter = new TagFilter();
|
||||
BeanUtils.copyProperties(tagFilterPage, tagFilter);
|
||||
List<ModelResp> modelRespList = modelService.getAllModelByDomainIds(tagFilterPage.getDomainIds());
|
||||
List<Long> modelIds = modelRespList.stream().map(ModelResp::getId).collect(Collectors.toList());
|
||||
tagFilterPage.getModelIds().addAll(modelIds);
|
||||
tagFilter.setModelIds(tagFilterPage.getModelIds());
|
||||
|
||||
List<CollectDO> collectList = collectService.getCollectList(user.getName())
|
||||
.stream().filter(collectDO -> TypeEnums.TAG.name().equalsIgnoreCase(collectDO.getType()))
|
||||
.collect(Collectors.toList());
|
||||
List<Long> collectIds = collectList.stream().map(CollectDO::getCollectId).collect(Collectors.toList());
|
||||
if (tagFilterPage.isHasCollect()) {
|
||||
if (CollectionUtils.isEmpty(collectIds)) {
|
||||
tagFilter.setIds(Lists.newArrayList(-1L));
|
||||
} else {
|
||||
tagFilter.setIds(collectIds);
|
||||
}
|
||||
}
|
||||
|
||||
PageInfo<TagDO> tagDOPageInfo = PageHelper.startPage(tagFilterPage.getCurrent(),
|
||||
tagFilterPage.getPageSize())
|
||||
.doSelectPageInfo(() -> query(tagFilter));
|
||||
PageInfo<TagResp> pageInfo = new PageInfo<>();
|
||||
BeanUtils.copyProperties(tagDOPageInfo, pageInfo);
|
||||
List<TagResp> tagRespList = convertList(tagDOPageInfo.getList(), collectIds);
|
||||
fillAdminRes(tagRespList, user);
|
||||
pageInfo.setList(tagRespList);
|
||||
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
private void fillAdminRes(List<TagResp> tagRespList, User user) {
|
||||
List<ModelResp> modelRespList = modelService.getModelListWithAuth(user, null, AuthType.ADMIN);
|
||||
if (CollectionUtils.isEmpty(modelRespList)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> modelIdSet = modelRespList.stream().map(ModelResp::getId).collect(Collectors.toSet());
|
||||
for (TagResp tagResp : tagRespList) {
|
||||
if (modelIdSet.contains(tagResp.getModelId())) {
|
||||
tagResp.setHasAdminRes(true);
|
||||
} else {
|
||||
tagResp.setHasAdminRes(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<TagResp> convertList(List<TagDO> tagDOList, List<Long> collectIds) {
|
||||
List<TagResp> tagRespList = new ArrayList<>();
|
||||
if (CollectionUtils.isNotEmpty(tagDOList)) {
|
||||
tagDOList.stream().forEach(tagDO -> {
|
||||
TagResp tagResp = convert(tagDO);
|
||||
if (CollectionUtils.isNotEmpty(collectIds) && collectIds.contains(tagDO.getId())) {
|
||||
tagResp.setIsCollect(true);
|
||||
} else {
|
||||
tagResp.setIsCollect(false);
|
||||
}
|
||||
tagRespList.add(tagResp);
|
||||
});
|
||||
}
|
||||
return tagRespList;
|
||||
}
|
||||
|
||||
private void checkExit(TagReq tagReq) {
|
||||
TagFilter tagFilter = new TagFilter();
|
||||
tagFilter.setModelIds(Arrays.asList(tagReq.getModelId()));
|
||||
|
||||
List<TagResp> tagResps = query(tagFilter);
|
||||
if (!CollectionUtils.isEmpty(tagResps)) {
|
||||
Long bizNameSameCount = tagResps.stream().filter(tagResp -> !tagResp.getId().equals(tagReq.getId()))
|
||||
.filter(tagResp -> tagResp.getBizName().equalsIgnoreCase(tagReq.getBizName())).count();
|
||||
if (bizNameSameCount > 0) {
|
||||
throw new RuntimeException(String.format("the bizName %s is exit", tagReq.getBizName()));
|
||||
}
|
||||
Long nameSameCount = tagResps.stream().filter(tagResp -> !tagResp.getId().equals(tagReq.getId()))
|
||||
.filter(tagResp -> tagResp.getName().equalsIgnoreCase(tagReq.getName())).count();
|
||||
if (nameSameCount > 0) {
|
||||
throw new RuntimeException(String.format("the name %s is exit", tagReq.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkParam(TagReq tagReq) {
|
||||
if (Objects.isNull(tagReq.getModelId()) || tagReq.getModelId() <= 0) {
|
||||
throw new RuntimeException("the modelId is empty");
|
||||
}
|
||||
if (Objects.isNull(tagReq.getBizName()) || tagReq.getBizName().isEmpty() || Objects.isNull(tagReq.getName())
|
||||
|| tagReq.getName().isEmpty()) {
|
||||
throw new RuntimeException("the bizName or name is empty");
|
||||
}
|
||||
if (Objects.isNull(tagReq.getTagDefineType()) || Objects.isNull(tagReq.getTagDefineParams())
|
||||
|| StringUtils.isBlank(tagReq.getTagDefineParams().getExpr())) {
|
||||
throw new InvalidArgumentException("表达式不可为空");
|
||||
}
|
||||
|
||||
if (NameCheckUtils.containsSpecialCharacters(tagReq.getBizName())) {
|
||||
throw new InvalidArgumentException("名称包含特殊字符, 请修改");
|
||||
}
|
||||
}
|
||||
|
||||
private TagResp convert(TagDO tagDO) {
|
||||
TagResp tagResp = new TagResp();
|
||||
BeanUtils.copyProperties(tagDO, tagResp);
|
||||
if (Objects.nonNull(tagDO.getExt()) && !tagDO.getExt().isEmpty()) {
|
||||
Map<String, Object> ext = JSONObject.parseObject(tagDO.getExt(),
|
||||
Map.class);
|
||||
tagResp.setExt(ext);
|
||||
}
|
||||
tagResp.setTagDefineType(TagDefineType.valueOf(tagDO.getDefineType()));
|
||||
if (Objects.nonNull(tagDO.getTypeParams()) && !tagDO.getTypeParams().isEmpty()) {
|
||||
TagDefineParams tagDefineParams = JSONObject.parseObject(tagDO.getTypeParams(),
|
||||
TagDefineParams.class);
|
||||
tagResp.setTagDefineParams(tagDefineParams);
|
||||
}
|
||||
|
||||
return tagResp;
|
||||
}
|
||||
|
||||
private TagDO convert(TagReq tagReq) {
|
||||
TagDO tagDO = new TagDO();
|
||||
BeanUtils.copyProperties(tagReq, tagDO);
|
||||
tagDO.setDefineType(tagReq.getTagDefineType().name());
|
||||
tagDO.setType(tagReq.getType().name());
|
||||
tagDO.setTypeParams(tagReq.getTypeParamsJson());
|
||||
tagDO.setExt(tagReq.getExtJson());
|
||||
return tagDO;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
|
||||
import com.tencent.supersonic.common.util.BeanMapper;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryConfig;
|
||||
import com.tencent.supersonic.headless.api.pojo.ViewDetail;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryViewReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.ViewReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DimensionResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DomainResp;
|
||||
@@ -28,6 +32,7 @@ import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.ViewService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -174,6 +179,15 @@ public class ViewServiceImpl
|
||||
return viewDO;
|
||||
}
|
||||
|
||||
public SemanticQueryReq convert(QueryViewReq queryViewReq) {
|
||||
SemanticQueryReq queryReq = new QueryStructReq();
|
||||
if (StringUtils.isNotBlank(queryViewReq.getSql())) {
|
||||
queryReq = new QuerySqlReq();
|
||||
}
|
||||
BeanUtils.copyProperties(queryViewReq, queryReq);
|
||||
return queryReq;
|
||||
}
|
||||
|
||||
public static boolean checkAdminPermission(User user, ViewResp viewResp) {
|
||||
List<String> admins = viewResp.getAdmins();
|
||||
if (user.isSuperAdmin()) {
|
||||
@@ -239,5 +253,4 @@ public class ViewServiceImpl
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.tencent.supersonic.headless.server.utils;
|
||||
import static com.tencent.supersonic.common.pojo.Constants.AND_UPPER;
|
||||
import static com.tencent.supersonic.common.pojo.Constants.APOSTROPHE;
|
||||
import static com.tencent.supersonic.common.pojo.Constants.COMMA;
|
||||
import static com.tencent.supersonic.common.pojo.Constants.POUND;
|
||||
import static com.tencent.supersonic.common.pojo.Constants.SPACE;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -36,6 +37,7 @@ import com.tencent.supersonic.headless.server.service.DimensionService;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import com.tencent.supersonic.headless.server.service.QueryService;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
@@ -48,6 +50,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -78,9 +81,9 @@ public class DictUtils {
|
||||
private final ModelService modelService;
|
||||
|
||||
public DictUtils(DimensionService dimensionService,
|
||||
MetricService metricService,
|
||||
QueryService queryService,
|
||||
ModelService modelService) {
|
||||
MetricService metricService,
|
||||
QueryService queryService,
|
||||
ModelService modelService) {
|
||||
this.dimensionService = dimensionService;
|
||||
this.metricService = metricService;
|
||||
this.queryService = queryService;
|
||||
@@ -92,7 +95,7 @@ public class DictUtils {
|
||||
dictItemResp.getItemId());
|
||||
}
|
||||
|
||||
public DictTaskDO generateDictTaskDO(DictItemResp dictItemResp, User user) {
|
||||
public DictTaskDO generateDictTaskDO(DictItemResp dictItemResp, User user, TaskStatusEnum status) {
|
||||
DictTaskDO taskDO = new DictTaskDO();
|
||||
Date createAt = new Date();
|
||||
String name = dictItemResp.fetchDictFileName();
|
||||
@@ -100,7 +103,7 @@ public class DictUtils {
|
||||
taskDO.setType(dictItemResp.getType().name());
|
||||
taskDO.setItemId(dictItemResp.getItemId());
|
||||
taskDO.setConfig(JsonUtil.toString(dictItemResp.getConfig()));
|
||||
taskDO.setStatus(TaskStatusEnum.PENDING.getStatus());
|
||||
taskDO.setStatus(status.getStatus());
|
||||
taskDO.setCreatedAt(createAt);
|
||||
String creator = (Objects.isNull(user) || Strings.isNullOrEmpty(user.getName())) ? "" : user.getName();
|
||||
taskDO.setCreatedBy(creator);
|
||||
@@ -185,7 +188,12 @@ public class DictUtils {
|
||||
return;
|
||||
}
|
||||
List<String> whiteList = dictItemResp.getConfig().getWhiteList();
|
||||
whiteList.forEach(white -> lines.add(String.format("%s %s %s", white, nature, itemValueWhiteFrequency)));
|
||||
whiteList.forEach(white -> {
|
||||
if (!Strings.isNullOrEmpty(white)) {
|
||||
white = white.replace(SPACE, POUND);
|
||||
}
|
||||
lines.add(String.format("%s %s %s", white, nature, itemValueWhiteFrequency));
|
||||
});
|
||||
}
|
||||
|
||||
private void constructDictLines(Map<String, Long> valueAndFrequencyPair, List<String> lines, String nature) {
|
||||
@@ -194,6 +202,9 @@ public class DictUtils {
|
||||
}
|
||||
|
||||
valueAndFrequencyPair.forEach((value, frequency) -> {
|
||||
if (!Strings.isNullOrEmpty(value)) {
|
||||
value = value.replace(SPACE, POUND);
|
||||
}
|
||||
lines.add(String.format("%s %s %s", value, nature, frequency));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,22 +16,22 @@ import com.tencent.supersonic.headless.api.pojo.request.ItemUseReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMultiStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryTagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ItemUseResp;
|
||||
import com.tencent.supersonic.headless.server.persistence.repository.StatRepository;
|
||||
import com.tencent.supersonic.headless.server.service.ModelService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
||||
@Component
|
||||
@@ -96,6 +96,48 @@ public class StatUtils {
|
||||
QueryStructReq queryStructCmd = ((QueryMultiStructReq) semanticQueryReq).getQueryStructReqs().get(0);
|
||||
initStructStatInfo(queryStructCmd, facadeUser);
|
||||
}
|
||||
if (semanticQueryReq instanceof QueryTagReq) {
|
||||
initTagStatInfo((QueryTagReq) semanticQueryReq, facadeUser);
|
||||
}
|
||||
}
|
||||
|
||||
public void initTagStatInfo(QueryTagReq queryTagReq, User facadeUser) {
|
||||
QueryStat queryStatInfo = new QueryStat();
|
||||
String traceId = "";
|
||||
List<String> dimensions = queryTagReq.getGroups();
|
||||
|
||||
List<String> metrics = new ArrayList<>();
|
||||
queryTagReq.getAggregators().stream().forEach(aggregator -> metrics.add(aggregator.getColumn()));
|
||||
String user = getUserName(facadeUser);
|
||||
|
||||
try {
|
||||
queryStatInfo.setTraceId(traceId)
|
||||
.setViewId(queryTagReq.getViewId())
|
||||
.setUser(user)
|
||||
.setQueryType(QueryType.STRUCT.getValue())
|
||||
.setQueryTypeBack(QueryTypeBack.NORMAL.getState())
|
||||
.setQueryStructCmd(queryTagReq.toString())
|
||||
.setQueryStructCmdMd5(DigestUtils.md5Hex(queryTagReq.toString()))
|
||||
.setStartTime(System.currentTimeMillis())
|
||||
.setNativeQuery(CollectionUtils.isEmpty(queryTagReq.getAggregators()))
|
||||
.setGroupByCols(objectMapper.writeValueAsString(queryTagReq.getGroups()))
|
||||
.setAggCols(objectMapper.writeValueAsString(queryTagReq.getAggregators()))
|
||||
.setOrderByCols(objectMapper.writeValueAsString(queryTagReq.getOrders()))
|
||||
.setFilterCols(objectMapper.writeValueAsString(
|
||||
sqlFilterUtils.getFiltersCol(queryTagReq.getTagFilters())))
|
||||
.setUseResultCache(true)
|
||||
.setUseSqlCache(true)
|
||||
.setMetrics(objectMapper.writeValueAsString(metrics))
|
||||
.setDimensions(objectMapper.writeValueAsString(dimensions))
|
||||
.setQueryOptMode(QueryOptMode.NONE.name());
|
||||
if (!CollectionUtils.isEmpty(queryTagReq.getModelIds())) {
|
||||
queryStatInfo.setModelId(queryTagReq.getModelIds().get(0));
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
StatUtils.set(queryStatInfo);
|
||||
|
||||
}
|
||||
|
||||
public void initSqlStatInfo(QuerySqlReq querySqlReq, User facadeUser) {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.tencent.supersonic.headless.server.utils;
|
||||
|
||||
import com.tencent.supersonic.common.pojo.enums.QueryType;
|
||||
import com.tencent.supersonic.common.util.jsqlparser.SqlSelectHelper;
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricTable;
|
||||
import com.tencent.supersonic.headless.api.pojo.QueryParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.AggOption;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.EngineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QuerySqlReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryTagReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.DatabaseResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticSchemaResp;
|
||||
import com.tencent.supersonic.headless.core.pojo.QueryStatement;
|
||||
import com.tencent.supersonic.headless.core.pojo.ViewQueryParam;
|
||||
import com.tencent.supersonic.headless.core.utils.SqlGenerateUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TagReqConverter {
|
||||
|
||||
@Value("${query.sql.limitWrapper:true}")
|
||||
private Boolean limitWrapper;
|
||||
|
||||
@Autowired
|
||||
private QueryStructUtils queryStructUtils;
|
||||
|
||||
@Autowired
|
||||
private SqlGenerateUtils sqlGenerateUtils;
|
||||
|
||||
public QueryStatement convert(QueryTagReq queryTagReq,
|
||||
SemanticSchemaResp semanticSchemaResp) throws Exception {
|
||||
QueryStatement queryStatement = new QueryStatement();
|
||||
// covert to QueryReqConverter
|
||||
QueryStructReq queryStructReq = new QueryStructReq();
|
||||
BeanUtils.copyProperties(queryTagReq, queryStructReq);
|
||||
if (!CollectionUtils.isEmpty(queryTagReq.getTagFilters())) {
|
||||
queryStructReq.setDimensionFilters(queryTagReq.getTagFilters());
|
||||
}
|
||||
QuerySqlReq querySqlReq = queryStructReq.convert();
|
||||
if (Objects.nonNull(querySqlReq)) {
|
||||
log.info("convert to QuerySqlReq {}", querySqlReq);
|
||||
String tableName = SqlSelectHelper.getTableName(querySqlReq.getSql());
|
||||
MetricTable metricTable = new MetricTable();
|
||||
metricTable.setMetrics(new ArrayList<>());
|
||||
metricTable.getMetrics().add(sqlGenerateUtils.generateInternalMetricName(
|
||||
semanticSchemaResp.getModelResps().get(0).getBizName()));
|
||||
metricTable.setAggOption(AggOption.NATIVE);
|
||||
List<String> allFields = SqlSelectHelper.getAllFields(querySqlReq.getSql());
|
||||
metricTable.setDimensions(allFields);
|
||||
metricTable.setAlias(tableName.toLowerCase());
|
||||
List<MetricTable> tables = new ArrayList<>();
|
||||
tables.add(metricTable);
|
||||
//.build ParseSqlReq
|
||||
ViewQueryParam result = new ViewQueryParam();
|
||||
BeanUtils.copyProperties(querySqlReq, result);
|
||||
result.setTables(tables);
|
||||
DatabaseResp database = semanticSchemaResp.getDatabaseResp();
|
||||
if (!sqlGenerateUtils.isSupportWith(EngineType.fromString(database.getType().toUpperCase()),
|
||||
database.getVersion())) {
|
||||
result.setSupportWith(false);
|
||||
result.setWithAlias(false);
|
||||
}
|
||||
//.physicalSql by ParseSqlReq
|
||||
queryStructReq.setDateInfo(queryStructUtils.getDateConfBySql(querySqlReq.getSql()));
|
||||
queryStructReq.setViewId(querySqlReq.getViewId());
|
||||
queryStructReq.setQueryType(QueryType.TAG);
|
||||
QueryParam queryParam = new QueryParam();
|
||||
convert(queryTagReq, queryParam);
|
||||
queryStatement.setQueryParam(queryParam);
|
||||
queryStatement.setViewQueryParam(result);
|
||||
queryStatement.setIsS2SQL(true);
|
||||
queryStatement.setMinMaxTime(queryStructUtils.getBeginEndTime(queryStructReq));
|
||||
queryStatement.setViewId(queryTagReq.getViewId());
|
||||
queryStatement.setEnableLimitWrapper(limitWrapper);
|
||||
}
|
||||
return queryStatement;
|
||||
}
|
||||
|
||||
public void convert(QueryTagReq queryTagReq, QueryParam queryParam) {
|
||||
BeanUtils.copyProperties(queryTagReq, queryParam);
|
||||
queryParam.setOrders(queryTagReq.getOrders());
|
||||
queryParam.setMetrics(queryTagReq.getMetrics());
|
||||
queryParam.setGroups(queryTagReq.getGroups());
|
||||
queryParam.setDimensionFilters(queryTagReq.getTagFilters());
|
||||
queryParam.setQueryType(QueryType.TAG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.tencent.supersonic.headless.server.persistence.mapper.TagCustomMapper">
|
||||
<resultMap id="BaseResultMap" type="com.tencent.supersonic.headless.server.persistence.dataobject.TagDO">
|
||||
<id column="id" jdbcType="BIGINT" property="id" />
|
||||
<result column="model_id" jdbcType="BIGINT" property="modelId" />
|
||||
<result column="name" jdbcType="VARCHAR" property="name" />
|
||||
<result column="biz_name" jdbcType="VARCHAR" property="bizName" />
|
||||
<result column="description" jdbcType="VARCHAR" property="description" />
|
||||
<result column="status" jdbcType="INTEGER" property="status" />
|
||||
<result column="sensitive_level" jdbcType="INTEGER" property="sensitiveLevel" />
|
||||
<result column="type" jdbcType="VARCHAR" property="type" />
|
||||
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||
<result column="created_by" jdbcType="VARCHAR" property="createdBy" />
|
||||
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
|
||||
<result column="updated_by" jdbcType="VARCHAR" property="updatedBy" />
|
||||
<result column="define_type" jdbcType="VARCHAR" property="defineType" />
|
||||
</resultMap>
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tencent.supersonic.headless.server.persistence.dataobject.TagDO">
|
||||
<result column="type_params" jdbcType="LONGVARCHAR" property="typeParams" />
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
<foreach collection="oredCriteria" item="criteria" separator="or">
|
||||
<if test="criteria.valid">
|
||||
<trim prefix="(" prefixOverrides="and" suffix=")">
|
||||
<foreach collection="criteria.criteria" item="criterion">
|
||||
<choose>
|
||||
<when test="criterion.noValue">
|
||||
and ${criterion.condition}
|
||||
</when>
|
||||
<when test="criterion.singleValue">
|
||||
and ${criterion.condition} #{criterion.value}
|
||||
</when>
|
||||
<when test="criterion.betweenValue">
|
||||
and ${criterion.condition} #{criterion.value} and
|
||||
#{criterion.secondValue}
|
||||
</when>
|
||||
<when test="criterion.listValue">
|
||||
and ${criterion.condition}
|
||||
<foreach close=")" collection="criterion.value" item="listItem"
|
||||
open="(" separator=",">
|
||||
#{listItem}
|
||||
</foreach>
|
||||
</when>
|
||||
</choose>
|
||||
</foreach>
|
||||
</trim>
|
||||
</if>
|
||||
</foreach>
|
||||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
id, model_id, name, biz_name, description, status, sensitive_level, type, created_at,
|
||||
created_by, updated_at, updated_by, define_type
|
||||
</sql>
|
||||
<sql id="Blob_Column_List">
|
||||
type_params
|
||||
</sql>
|
||||
|
||||
|
||||
<select id="query" resultMap="ResultMapWithBLOBs">
|
||||
select *
|
||||
from s2_tag
|
||||
where status != 3
|
||||
<if test="type != null and type != ''">
|
||||
and type = #{type}
|
||||
</if>
|
||||
<if test="key != null and key != ''">
|
||||
and ( id like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
|
||||
name like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
|
||||
biz_name like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
|
||||
description like CONCAT('%',#{key , jdbcType=VARCHAR},'%'))
|
||||
</if>
|
||||
<if test="id != null">
|
||||
and id like CONCAT('%',#{id , jdbcType=VARCHAR},'%')
|
||||
</if>
|
||||
<if test="name != null and name != '' ">
|
||||
and name like CONCAT('%',#{name , jdbcType=VARCHAR},'%')
|
||||
</if>
|
||||
<if test="bizName != null and bizName != ''">
|
||||
and biz_name like CONCAT('%',#{bizName , jdbcType=VARCHAR},'%')
|
||||
</if>
|
||||
<if test="sensitiveLevel != null">
|
||||
and sensitive_level = #{sensitiveLevel}
|
||||
</if>
|
||||
<if test="status != null">
|
||||
and status = #{status}
|
||||
</if>
|
||||
<if test="modelIds != null and modelIds.size >0">
|
||||
and model_id in
|
||||
<foreach collection="modelIds" index="index" item="model" open="(" close=")"
|
||||
separator=",">
|
||||
#{model}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="ids != null and ids.size >0">
|
||||
and id in
|
||||
<foreach collection="ids" index="index" item="id" open="(" close=")"
|
||||
separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="createdBy != null">
|
||||
and created_by = #{createdBy}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.tencent.supersonic.headless.server.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.common.pojo.DataFormat;
|
||||
@@ -8,12 +11,12 @@ import com.tencent.supersonic.common.pojo.enums.SensitiveLevelEnum;
|
||||
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
|
||||
import com.tencent.supersonic.common.pojo.enums.TypeEnums;
|
||||
import com.tencent.supersonic.common.util.ChatGptHelper;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MetricDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MetricType;
|
||||
import com.tencent.supersonic.headless.api.pojo.DrillDownDimension;
|
||||
import com.tencent.supersonic.headless.api.pojo.MeasureParam;
|
||||
import com.tencent.supersonic.headless.api.pojo.MetricDefineByMeasureParams;
|
||||
import com.tencent.supersonic.headless.api.pojo.RelateDimension;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MetricDefineType;
|
||||
import com.tencent.supersonic.headless.api.pojo.enums.MetricType;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.MetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.MetricResp;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
|
||||
@@ -22,13 +25,11 @@ import com.tencent.supersonic.headless.server.persistence.repository.MetricRepos
|
||||
import com.tencent.supersonic.headless.server.service.impl.MetricServiceImpl;
|
||||
import com.tencent.supersonic.headless.server.service.impl.ViewServiceImpl;
|
||||
import com.tencent.supersonic.headless.server.utils.MetricConverter;
|
||||
import java.util.HashMap;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import java.util.HashMap;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MetricServiceImplTest {
|
||||
|
||||
@@ -61,14 +62,14 @@ public class MetricServiceImplTest {
|
||||
}
|
||||
|
||||
private MetricService mockMetricService(MetricRepository metricRepository,
|
||||
ModelService modelService) {
|
||||
DomainService domainService = Mockito.mock(DomainService.class);
|
||||
ModelService modelService) {
|
||||
ChatGptHelper chatGptHelper = Mockito.mock(ChatGptHelper.class);
|
||||
CollectService collectService = Mockito.mock(CollectService.class);
|
||||
ApplicationEventPublisher eventPublisher = Mockito.mock(ApplicationEventPublisher.class);
|
||||
ViewService viewService = Mockito.mock(ViewServiceImpl.class);
|
||||
return new MetricServiceImpl(metricRepository, modelService, domainService,
|
||||
chatGptHelper, collectService, viewService, eventPublisher);
|
||||
DimensionService dimensionService = Mockito.mock(DimensionService.class);
|
||||
return new MetricServiceImpl(metricRepository, modelService, chatGptHelper, collectService, viewService,
|
||||
eventPublisher, dimensionService);
|
||||
}
|
||||
|
||||
private MetricReq buildMetricReq() {
|
||||
@@ -96,7 +97,7 @@ public class MetricServiceImplTest {
|
||||
RelateDimension.builder().drillDownDimensions(Lists.newArrayList(
|
||||
new DrillDownDimension(1L),
|
||||
new DrillDownDimension(1L, false))
|
||||
).build());
|
||||
).build());
|
||||
metricReq.setSensitiveLevel(SensitiveLevelEnum.LOW.getCode());
|
||||
metricReq.setExt(new HashMap<>());
|
||||
return metricReq;
|
||||
|
||||
@@ -14,10 +14,7 @@ com.tencent.supersonic.chat.core.parser.SemanticParser=\
|
||||
com.tencent.supersonic.chat.core.corrector.SemanticCorrector=\
|
||||
com.tencent.supersonic.chat.core.corrector.SchemaCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.TimeCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.SelectCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.WhereCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.GroupByCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.HavingCorrector
|
||||
com.tencent.supersonic.chat.core.corrector.GrammarCorrector
|
||||
|
||||
com.tencent.supersonic.chat.core.query.semantic.SemanticInterpreter=\
|
||||
com.tencent.supersonic.chat.core.query.semantic.RemoteSemanticInterpreter
|
||||
|
||||
@@ -14,10 +14,7 @@ com.tencent.supersonic.chat.core.parser.SemanticParser=\
|
||||
com.tencent.supersonic.chat.core.corrector.SemanticCorrector=\
|
||||
com.tencent.supersonic.chat.core.corrector.SchemaCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.TimeCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.SelectCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.WhereCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.GroupByCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.HavingCorrector
|
||||
com.tencent.supersonic.chat.core.corrector.GrammarCorrector
|
||||
|
||||
com.tencent.supersonic.chat.server.processor.parse.ParseResultProcessor=\
|
||||
com.tencent.supersonic.chat.server.processor.parse.ParseInfoProcessor, \
|
||||
|
||||
@@ -192,4 +192,24 @@ CREATE TABLE s2_view(
|
||||
alter table s2_plugin change column model `view` varchar(100);
|
||||
alter table s2_view_info rename to s2_canvas;
|
||||
|
||||
alter table s2_query_stat_info add column `view_id` bigint(20) DEFAULT NULL after `model_id`;
|
||||
alter table s2_query_stat_info add column `view_id` bigint(20) DEFAULT NULL after `model_id`;
|
||||
|
||||
--20240221
|
||||
CREATE TABLE s2_tag(
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`model_id` INT NOT NULL ,
|
||||
`name` varchar(255) NOT NULL ,
|
||||
`biz_name` varchar(255) NOT NULL ,
|
||||
`description` varchar(500) DEFAULT NULL ,
|
||||
`status` INT NOT NULL ,
|
||||
`sensitive_level` INT NOT NULL ,
|
||||
`type` varchar(50) NOT NULL , -- ATOMIC, DERIVED
|
||||
`define_type` varchar(50) NOT NULL, -- FIELD, DIMENSION
|
||||
`type_params` LONGVARCHAR DEFAULT NULL ,
|
||||
`created_at` TIMESTAMP NOT NULL ,
|
||||
`created_by` varchar(100) NOT NULL ,
|
||||
`updated_at` TIMESTAMP DEFAULT NULL ,
|
||||
`updated_by` varchar(100) DEFAULT NULL ,
|
||||
`ext` LONGVARCHAR DEFAULT NULL ,
|
||||
PRIMARY KEY (`id`)
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -572,4 +572,24 @@ CREATE TABLE IF NOT EXISTS `s2_view` (
|
||||
query_config VARCHAR(3000),
|
||||
`admin` varchar(3000) DEFAULT NULL,
|
||||
`admin_org` varchar(3000) DEFAULT NULL
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `s2_tag` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`model_id` INT NOT NULL ,
|
||||
`name` varchar(255) NOT NULL ,
|
||||
`biz_name` varchar(255) NOT NULL ,
|
||||
`description` varchar(500) DEFAULT NULL ,
|
||||
`status` INT NOT NULL ,
|
||||
`sensitive_level` INT NOT NULL ,
|
||||
`type` varchar(50) NOT NULL , -- ATOMIC, DERIVED
|
||||
`define_type` varchar(50) NOT NULL, -- FIELD, DIMENSION
|
||||
`type_params` LONGVARCHAR DEFAULT NULL ,
|
||||
`created_at` TIMESTAMP NOT NULL ,
|
||||
`created_by` varchar(100) NOT NULL ,
|
||||
`updated_at` TIMESTAMP DEFAULT NULL ,
|
||||
`updated_by` varchar(100) DEFAULT NULL ,
|
||||
`ext` LONGVARCHAR DEFAULT NULL ,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
COMMENT ON TABLE s2_tag IS 'tag information';
|
||||
@@ -497,4 +497,25 @@ CREATE TABLE s2_view
|
||||
query_config VARCHAR(3000),
|
||||
`admin` varchar(3000) DEFAULT NULL,
|
||||
`admin_org` varchar(3000) DEFAULT NULL
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `s2_tag`
|
||||
(
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`model_id` bigint(20) DEFAULT NULL,
|
||||
`name` varchar(255) NOT NULL COMMENT '名称',
|
||||
`biz_name` varchar(255) NOT NULL COMMENT '英文名称',
|
||||
`description` varchar(500) DEFAULT NULL COMMENT '描述',
|
||||
`status` int(10) NOT NULL COMMENT '状态',
|
||||
`sensitive_level` int(10) NOT NULL COMMENT '敏感级别',
|
||||
`type` varchar(50) NOT NULL COMMENT '类型(DERIVED,ATOMIC)',
|
||||
`define_type` varchar(50) DEFAULT NULL, -- FIELD, DIMENSION
|
||||
`type_params` text NOT NULL COMMENT '类型参数',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间',
|
||||
`created_by` varchar(100) NOT NULL COMMENT '创建人',
|
||||
`updated_at` datetime NULL COMMENT '更新时间',
|
||||
`updated_by` varchar(100) NULL COMMENT '更新人',
|
||||
`ext` text DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8 COMMENT ='标签表';
|
||||
@@ -57,7 +57,7 @@ public class PluginRecognizeTest extends BasePluginTest {
|
||||
QueryFilter queryFilter = new QueryFilter();
|
||||
queryFilter.setElementID(2L);
|
||||
queryFilter.setValue("alice");
|
||||
queryRequest.setModelId(1L);
|
||||
// queryRequest.setModelId(1L);
|
||||
queryFilters.getFilters().add(queryFilter);
|
||||
queryRequest.setQueryFilters(queryFilters);
|
||||
|
||||
|
||||
@@ -4,19 +4,25 @@ import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.tencent.supersonic.auth.api.authentication.pojo.User;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryMetricReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.request.QueryStructReq;
|
||||
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
|
||||
import com.tencent.supersonic.headless.server.service.MetricService;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class QueryByMetricTest extends BaseTest {
|
||||
|
||||
@Autowired
|
||||
protected MetricService metricService;
|
||||
|
||||
@Test
|
||||
public void testWithMetricAndDimensionBizNames() throws Exception {
|
||||
QueryMetricReq queryMetricReq = new QueryMetricReq();
|
||||
queryMetricReq.setMetricNames(Arrays.asList("stay_hours", "pv"));
|
||||
queryMetricReq.setDimensionNames(Arrays.asList("user_name", "department"));
|
||||
SemanticQueryResp queryResp = queryService.queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
SemanticQueryResp queryResp = queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
Assert.assertNotNull(queryResp.getResultList());
|
||||
Assert.assertEquals(6, queryResp.getResultList().size());
|
||||
}
|
||||
@@ -26,7 +32,7 @@ public class QueryByMetricTest extends BaseTest {
|
||||
QueryMetricReq queryMetricReq = new QueryMetricReq();
|
||||
queryMetricReq.setMetricNames(Arrays.asList("停留时长", "访问次数"));
|
||||
queryMetricReq.setDimensionNames(Arrays.asList("用户", "部门"));
|
||||
SemanticQueryResp queryResp = queryService.queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
SemanticQueryResp queryResp = queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
Assert.assertNotNull(queryResp.getResultList());
|
||||
Assert.assertEquals(6, queryResp.getResultList().size());
|
||||
}
|
||||
@@ -37,7 +43,7 @@ public class QueryByMetricTest extends BaseTest {
|
||||
queryMetricReq.setDomainId(1L);
|
||||
queryMetricReq.setMetricNames(Arrays.asList("stay_hours", "pv"));
|
||||
queryMetricReq.setDimensionNames(Arrays.asList("user_name", "department"));
|
||||
SemanticQueryResp queryResp = queryService.queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
SemanticQueryResp queryResp = queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
Assert.assertNotNull(queryResp.getResultList());
|
||||
Assert.assertEquals(6, queryResp.getResultList().size());
|
||||
|
||||
@@ -45,7 +51,7 @@ public class QueryByMetricTest extends BaseTest {
|
||||
queryMetricReq.setMetricNames(Arrays.asList("stay_hours", "pv"));
|
||||
queryMetricReq.setDimensionNames(Arrays.asList("user_name", "department"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> queryService.queryByMetric(queryMetricReq, User.getFakeUser()));
|
||||
() -> queryByMetric(queryMetricReq, User.getFakeUser()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,9 +60,13 @@ public class QueryByMetricTest extends BaseTest {
|
||||
queryMetricReq.setDomainId(1L);
|
||||
queryMetricReq.setMetricIds(Arrays.asList(1L, 4L));
|
||||
queryMetricReq.setDimensionIds(Arrays.asList(1L, 2L));
|
||||
SemanticQueryResp queryResp = queryService.queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
SemanticQueryResp queryResp = queryByMetric(queryMetricReq, User.getFakeUser());
|
||||
Assert.assertNotNull(queryResp.getResultList());
|
||||
Assert.assertEquals(6, queryResp.getResultList().size());
|
||||
}
|
||||
|
||||
private SemanticQueryResp queryByMetric(QueryMetricReq queryMetricReq, User user) throws Exception {
|
||||
QueryStructReq convert = metricService.convert(queryMetricReq);
|
||||
return queryService.queryByReq(convert.convert(), user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,8 @@ com.tencent.supersonic.chat.core.parser.SemanticParser=\
|
||||
|
||||
com.tencent.supersonic.chat.core.corrector.SemanticCorrector=\
|
||||
com.tencent.supersonic.chat.core.corrector.SchemaCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.SelectCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.WhereCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.GroupByCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.HavingCorrector
|
||||
com.tencent.supersonic.chat.core.corrector.TimeCorrector, \
|
||||
com.tencent.supersonic.chat.core.corrector.GrammarCorrector
|
||||
|
||||
com.tencent.supersonic.chat.server.processor.parse.ParseResultProcessor=\
|
||||
com.tencent.supersonic.chat.server.processor.parse.ParseInfoProcessor, \
|
||||
|
||||
@@ -529,4 +529,24 @@ CREATE TABLE IF NOT EXISTS `s2_view` (
|
||||
query_config VARCHAR(3000),
|
||||
`admin` varchar(3000) DEFAULT NULL,
|
||||
`admin_org` varchar(3000) DEFAULT NULL
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `s2_tag` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`model_id` INT NOT NULL ,
|
||||
`name` varchar(255) NOT NULL ,
|
||||
`biz_name` varchar(255) NOT NULL ,
|
||||
`description` varchar(500) DEFAULT NULL ,
|
||||
`status` INT NOT NULL ,
|
||||
`sensitive_level` INT NOT NULL ,
|
||||
`type` varchar(50) NOT NULL , -- ATOMIC, DERIVED
|
||||
`define_type` varchar(50) NOT NULL, -- FIELD, DIMENSION
|
||||
`type_params` LONGVARCHAR DEFAULT NULL ,
|
||||
`created_at` TIMESTAMP NOT NULL ,
|
||||
`created_by` varchar(100) NOT NULL ,
|
||||
`updated_at` TIMESTAMP DEFAULT NULL ,
|
||||
`updated_by` varchar(100) DEFAULT NULL ,
|
||||
`ext` LONGVARCHAR DEFAULT NULL ,
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
COMMENT ON TABLE s2_tag IS 'tag information';
|
||||
@@ -1,22 +1,4 @@
|
||||
export * from './models/base';
|
||||
type ObjToArrayParams = Record<string, string>;
|
||||
|
||||
const keyTypeTran = {
|
||||
string: String,
|
||||
number: Number,
|
||||
};
|
||||
/**
|
||||
* obj转成value,label的数组
|
||||
* @param _obj
|
||||
*/
|
||||
export const objToArray = (_obj: ObjToArrayParams, keyType: string = 'string') => {
|
||||
return Object.keys(_obj).map((key) => {
|
||||
return {
|
||||
value: keyTypeTran[keyType](key),
|
||||
label: _obj[key],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
type EnumToArrayItem = {
|
||||
value: number;
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import OverviewContainer from '../OverviewContainer';
|
||||
import type { Dispatch } from 'umi';
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
const ChatSetting: React.FC<Props> = () => {
|
||||
return (
|
||||
<>
|
||||
<OverviewContainer mode={'chatSetting'} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ChatSetting);
|
||||
@@ -1,43 +0,0 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import ProCard from '@ant-design/pro-card';
|
||||
import EntitySection from '../components/Entity/EntitySection';
|
||||
import { ChatConfigType } from '../enum';
|
||||
import type { Dispatch } from 'umi';
|
||||
|
||||
type Props = {
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
const ChatSettingSection: React.FC<Props> = () => {
|
||||
const metricRef = useRef<any>();
|
||||
const tagRef = useRef<any>();
|
||||
return (
|
||||
<div style={{ width: 900, margin: '20px auto' }}>
|
||||
<ProCard bordered title="指标模式" style={{ marginBottom: 20 }}>
|
||||
<EntitySection
|
||||
ref={metricRef}
|
||||
chatConfigType={ChatConfigType.METRIC}
|
||||
onConfigSave={() => {
|
||||
tagRef.current.refreshConfigData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard bordered title="标签模式" style={{ marginBottom: 20 }}>
|
||||
<EntitySection
|
||||
ref={tagRef}
|
||||
chatConfigType={ChatConfigType.TAG}
|
||||
onConfigSave={() => {
|
||||
metricRef.current.refreshConfigData();
|
||||
}}
|
||||
/>
|
||||
</ProCard>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ChatSettingSection);
|
||||
@@ -1,102 +0,0 @@
|
||||
import { Tabs, Button } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'umi';
|
||||
|
||||
import styles from '../components/style.less';
|
||||
import type { StateType } from '../model';
|
||||
import { LeftOutlined } from '@ant-design/icons';
|
||||
import EntitySection from '../components/Entity/EntitySection';
|
||||
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
|
||||
import { ISemantic } from '../data';
|
||||
|
||||
import OverView from '../components/OverView';
|
||||
import { ChatConfigType } from '../enum';
|
||||
import type { Dispatch } from 'umi';
|
||||
|
||||
type Props = {
|
||||
isModel: boolean;
|
||||
activeKey: string;
|
||||
modelList: ISemantic.IModelItem[];
|
||||
handleModelChange: (model?: ISemantic.IModelItem) => void;
|
||||
onBackDomainBtnClick?: () => void;
|
||||
onMenuChange?: (menuKey: string) => void;
|
||||
domainManger: StateType;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
const ChatSetting: React.FC<Props> = ({
|
||||
isModel,
|
||||
activeKey,
|
||||
modelList,
|
||||
handleModelChange,
|
||||
onBackDomainBtnClick,
|
||||
onMenuChange,
|
||||
}) => {
|
||||
const defaultTabKey = 'metric';
|
||||
|
||||
const isModelItem = [
|
||||
{
|
||||
label: '指标模式',
|
||||
key: 'metric',
|
||||
children: <EntitySection chatConfigType={ChatConfigType.METRIC} />,
|
||||
},
|
||||
{
|
||||
label: '标签模式',
|
||||
key: 'dimenstion',
|
||||
children: <EntitySection chatConfigType={ChatConfigType.TAG} />,
|
||||
},
|
||||
{
|
||||
label: '推荐问题',
|
||||
key: 'recommendedQuestions',
|
||||
children: <RecommendedQuestionsSection />,
|
||||
},
|
||||
];
|
||||
|
||||
const tabItem = [
|
||||
{
|
||||
label: '模型',
|
||||
key: 'overview',
|
||||
children: (
|
||||
<OverView
|
||||
modelList={modelList}
|
||||
disabledEdit={true}
|
||||
onModelChange={(model) => {
|
||||
handleModelChange(model);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
className={styles.tab}
|
||||
items={isModel ? isModelItem : tabItem}
|
||||
activeKey={activeKey || defaultTabKey}
|
||||
destroyInactiveTabPane
|
||||
tabBarExtraContent={
|
||||
isModel ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<LeftOutlined />}
|
||||
onClick={() => {
|
||||
onBackDomainBtnClick?.();
|
||||
}}
|
||||
style={{ marginRight: 10 }}
|
||||
>
|
||||
返回主题域
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
onChange={(menuKey: string) => {
|
||||
onMenuChange?.(menuKey);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(ChatSetting);
|
||||
@@ -18,6 +18,7 @@ export type CreateFormProps = {
|
||||
dispatch: Dispatch;
|
||||
createModalVisible: boolean;
|
||||
sql?: string;
|
||||
sqlParams?: IDataSource.ISqlParamsItem[];
|
||||
databaseId?: number;
|
||||
modelItem: ISemantic.IModelItem;
|
||||
onCancel?: () => void;
|
||||
@@ -42,6 +43,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
||||
createModalVisible,
|
||||
scriptColumns,
|
||||
sql = '',
|
||||
sqlParams,
|
||||
onSubmit,
|
||||
modelItem,
|
||||
databaseId,
|
||||
@@ -249,6 +251,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
|
||||
queryType: basicInfoFormMode === 'fast' ? 'table_query' : 'sql_query',
|
||||
tableQuery: dbName && tableName ? `${dbName}.${tableName}` : '',
|
||||
sqlQuery: sql,
|
||||
sqlVariables: sqlParams,
|
||||
},
|
||||
};
|
||||
setQueryParamsState(queryParams);
|
||||
|
||||
@@ -12,17 +12,17 @@ import {
|
||||
SwapOutlined,
|
||||
PlayCircleOutlined,
|
||||
CloudServerOutlined,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { isFunction } from 'lodash';
|
||||
import FullScreen from '@/components/FullScreen';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import type { TaskResultItem, TaskResultColumn } from '../data';
|
||||
import { excuteSql } from '@/pages/SemanticModel/service';
|
||||
// import DataSourceCreateForm from './DataSourceCreateForm';
|
||||
import type { Dispatch } from 'umi';
|
||||
import type { StateType } from '../../model';
|
||||
import SqlParams from './SqlParams';
|
||||
import styles from '../style.less';
|
||||
|
||||
import 'ace-builds/src-min-noconflict/ext-searchbox';
|
||||
import 'ace-builds/src-min-noconflict/theme-sqlserver';
|
||||
import 'ace-builds/src-min-noconflict/theme-monokai';
|
||||
@@ -33,6 +33,7 @@ export type DataSourceSubmitData = {
|
||||
sql: string;
|
||||
databaseId: number;
|
||||
columns: any[];
|
||||
sqlParams: any[];
|
||||
};
|
||||
|
||||
type IProps = {
|
||||
@@ -77,7 +78,6 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
});
|
||||
const [dataBaseItems, setDataBaseItems] = useState<DatabaseItem[]>([]);
|
||||
const [currentDatabaseItem, setCurrentDatabaseItem] = useState<DatabaseItem>();
|
||||
// const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
|
||||
|
||||
const [tableScroll, setTableScroll] = useState({
|
||||
scrollToFirstRowOnChange: true,
|
||||
@@ -88,7 +88,7 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
const [runState, setRunState] = useState<boolean | undefined>();
|
||||
|
||||
const [taskLog, setTaskLog] = useState('');
|
||||
const [isSqlExcLocked, setIsSqlExcLocked] = useState(false);
|
||||
const [isSqlExcLocked, setIsSqlExcLocked] = useState<boolean>(false);
|
||||
const [screenSize, setScreenSize] = useState<ScreenSize>('middle');
|
||||
|
||||
const [isSqlIdeFullScreen, setIsSqlIdeFullScreen] = useState<boolean>(false);
|
||||
@@ -100,8 +100,11 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
const DEFAULT_FULLSCREEN_TOP = 0;
|
||||
|
||||
const [partialSql, setPartialSql] = useState('');
|
||||
const [isPartial, setIsPartial] = useState(false);
|
||||
const [isRight, setIsRight] = useState(false);
|
||||
const [isPartial, setIsPartial] = useState<boolean>(false);
|
||||
const [isRight, setIsRight] = useState<boolean>(false);
|
||||
|
||||
const [variableCollapsed, setVariableCollapsed] = useState<boolean>(true);
|
||||
const [sqlParams, setSqlParams] = useState<IDataSource.ISqlParamsItem[]>([]);
|
||||
|
||||
const [scriptColumns, setScriptColumns] = useState<any[]>([]);
|
||||
|
||||
@@ -125,6 +128,10 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
setCurrentDatabaseItem(targetDataBase);
|
||||
}, [dataSourceItem, databaseConfigList]);
|
||||
|
||||
useEffect(() => {
|
||||
setSqlParams(dataSourceItem?.modelDetail?.sqlVariables || []);
|
||||
}, [dataSourceItem]);
|
||||
|
||||
useEffect(() => {
|
||||
setRunState(undefined);
|
||||
}, [currentDatabaseItem, sql]);
|
||||
@@ -138,6 +145,11 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
return line;
|
||||
}
|
||||
|
||||
const handleVariable = () => {
|
||||
const collapsedValue = !variableCollapsed;
|
||||
setVariableCollapsed(collapsedValue);
|
||||
};
|
||||
|
||||
// 计算每列的宽度,通过容器插入文档中动态得到该列数据(包括表头)的最长宽度,设为列宽度,保证每列的数据都能一行展示完
|
||||
function getKeyWidthMap(list: TaskResultItem[]): TaskResultItem {
|
||||
const widthMap = {};
|
||||
@@ -236,6 +248,7 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
const { code, data, msg } = await excuteSql({
|
||||
sql: value,
|
||||
id: currentDatabaseItem.key,
|
||||
sqlVariables: sqlParams,
|
||||
});
|
||||
setResultTableLoading(false);
|
||||
if (code === 200) {
|
||||
@@ -417,6 +430,9 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
<Tooltip title="格式化SQL语句">
|
||||
<EditOutlined className={styles.sqlOprIcon} onClick={formatSQL} />
|
||||
</Tooltip>
|
||||
<Tooltip title="动态变量">
|
||||
<ApiOutlined className={styles.sqlOprIcon} onClick={handleVariable} />
|
||||
</Tooltip>
|
||||
<Tooltip title="改变主题">
|
||||
<SwapOutlined className={styles.sqlOprIcon} onClick={handleThemeChange} />
|
||||
</Tooltip>
|
||||
@@ -463,6 +479,17 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={variableCollapsed ? styles.hideSqlParams : styles.sqlParams}
|
||||
// style={{ height: sqlEditorHeight }}
|
||||
>
|
||||
<SqlParams
|
||||
value={sqlParams}
|
||||
onChange={(params) => {
|
||||
setSqlParams(params);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<div className={`${styles.sqlBottmWrap} ${screenSize}`}>
|
||||
@@ -476,6 +503,7 @@ const SqlDetail: React.FC<IProps> = ({
|
||||
columns: scriptColumns,
|
||||
databaseId: currentDatabaseItem?.key || 0,
|
||||
sql,
|
||||
sqlParams,
|
||||
});
|
||||
}}
|
||||
disabled={!runState}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import type { OprType } from '../data';
|
||||
import { IDataSource } from '../../data';
|
||||
import styles from '../style.less';
|
||||
import { AppstoreAddOutlined, DeleteTwoTone, EditTwoTone } from '@ant-design/icons';
|
||||
import SqlParamsDetailModal from './SqlParamsDetailModal';
|
||||
import { List } from 'antd';
|
||||
|
||||
type Props = {
|
||||
value?: IDataSource.ISqlParamsItem[];
|
||||
onChange?: (e: IDataSource.ISqlParamsItem[]) => void;
|
||||
};
|
||||
|
||||
const defalutItem: IDataSource.ISqlParamsItem = {
|
||||
name: '',
|
||||
defaultValues: [],
|
||||
valueType: 'STRING',
|
||||
};
|
||||
|
||||
const SqlParams: FC<Props> = ({ value, onChange }) => {
|
||||
const [oprType, setOprType] = useState<OprType>('add');
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [initValue, setInitValue] = useState<IDataSource.ISqlParamsItem>();
|
||||
const paramsChange = (params: IDataSource.ISqlParamsItem[]) => {
|
||||
if (onChange) {
|
||||
onChange(params);
|
||||
}
|
||||
};
|
||||
const handleAdd = () => {
|
||||
setOprType('add');
|
||||
setVisible(true);
|
||||
setInitValue(defalutItem);
|
||||
};
|
||||
const handleSave = async (values: IDataSource.ISqlParamsItem) => {
|
||||
const newValue = value ? [...value] : [];
|
||||
const { index, ...rest } = values;
|
||||
if (index || index === 0) {
|
||||
newValue[index] = rest;
|
||||
} else {
|
||||
newValue.push(rest);
|
||||
}
|
||||
setVisible(false);
|
||||
setInitValue(undefined);
|
||||
paramsChange(newValue);
|
||||
};
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const newValue = value ? [...value] : [];
|
||||
newValue.splice(index, 1);
|
||||
paramsChange(newValue);
|
||||
};
|
||||
const handleEdit = (index: number) => {
|
||||
const paramsItem = value ? value[index] : defalutItem;
|
||||
setInitValue({ ...paramsItem, index });
|
||||
setOprType('edit');
|
||||
setVisible(true);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className={styles.sqlParamsBody}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>变量</span>
|
||||
<AppstoreAddOutlined className={styles.icon} onClick={handleAdd} />
|
||||
</div>
|
||||
<List
|
||||
className={styles.paramsList}
|
||||
dataSource={value}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
className={styles.paramsItem}
|
||||
key={item.name}
|
||||
actions={[
|
||||
<>
|
||||
<EditTwoTone
|
||||
className={styles.icon}
|
||||
onClick={() => {
|
||||
handleEdit(index);
|
||||
}}
|
||||
/>
|
||||
<DeleteTwoTone
|
||||
className={styles.icon}
|
||||
onClick={() => {
|
||||
handleDelete(index);
|
||||
}}
|
||||
/>
|
||||
</>,
|
||||
]}
|
||||
>
|
||||
<div className={styles.name}>{item.name}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<SqlParamsDetailModal
|
||||
nameList={value?.map((item) => item.name)}
|
||||
oprType={oprType}
|
||||
modalVisible={visible}
|
||||
onSave={handleSave}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
initValue={initValue}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SqlParams;
|
||||
@@ -0,0 +1,190 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Modal, Form, Input, Select, Checkbox } from 'antd';
|
||||
import { isFunction } from 'lodash';
|
||||
import { objToArray } from '@/utils/utils';
|
||||
import type { ParamsItemProps, OprType } from '../data';
|
||||
import { IDataSource } from '../../data';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
import ParamsSqlEditor from './SqlParamsSqlEditor';
|
||||
|
||||
// const EnumSqlParamsType = {
|
||||
// auth: '权限变量',
|
||||
// query: '查询变量',
|
||||
// };
|
||||
|
||||
const EnumSqlValueType = {
|
||||
STRING: '字符串',
|
||||
NUMBER: '数字',
|
||||
EXPR: 'SQL表达式',
|
||||
};
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const ParamsTextArea: FC<ParamsItemProps> = ({ value, onChange }) => {
|
||||
return (
|
||||
<TextArea
|
||||
value={value && value[0]}
|
||||
onChange={(e) => {
|
||||
if (onChange) {
|
||||
onChange([e.target.value]);
|
||||
}
|
||||
}}
|
||||
placeholder="请输入表达式"
|
||||
rows={3}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type IProps = {
|
||||
oprType: OprType;
|
||||
modalVisible: boolean;
|
||||
onSave: (values: IDataSource.ISqlParamsItem) => Promise<any>;
|
||||
onCancel?: (oprType: OprType) => void;
|
||||
initValue?: IDataSource.ISqlParamsItem;
|
||||
nameList?: string[];
|
||||
};
|
||||
|
||||
const SqlParamsDetailModal: FC<IProps> = ({
|
||||
oprType = 'add',
|
||||
initValue = {} as IDataSource.ISqlParamsItem,
|
||||
modalVisible,
|
||||
onSave,
|
||||
onCancel,
|
||||
nameList,
|
||||
}) => {
|
||||
const [valueType, setValueType] = useState<IDataSource.ISqlParamsValueType>();
|
||||
const [oldName, setOldName] = useState<string>();
|
||||
|
||||
const formLayout = {
|
||||
labelCol: { span: 7 },
|
||||
wrapperCol: { span: 13 },
|
||||
};
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const submitSave = async () => {
|
||||
const fieldsValue = await form.validateFields();
|
||||
onSave({ ...fieldsValue, index: initValue.index });
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
if (onCancel && isFunction(onCancel)) {
|
||||
onCancel(oprType);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
...initValue,
|
||||
});
|
||||
setValueType(initValue.valueType);
|
||||
setOldName(initValue.name);
|
||||
}, [initValue]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
forceRender
|
||||
title={`${oprType === 'add' ? '新建' : '编辑'}sql参数`}
|
||||
open={modalVisible}
|
||||
onOk={submitSave}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form
|
||||
{...formLayout}
|
||||
initialValues={{
|
||||
...initValue,
|
||||
}}
|
||||
form={form}
|
||||
>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="参数名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入参数名称' },
|
||||
{
|
||||
validator(_, value, confirm) {
|
||||
if (
|
||||
nameList?.some((item) => {
|
||||
return item === value && value !== oldName;
|
||||
})
|
||||
) {
|
||||
confirm('名称不能重复');
|
||||
} else {
|
||||
confirm();
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入参数名称" />
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
name="type"
|
||||
label="参数类型"
|
||||
rules={[{ required: true, message: '请选择参数类型' }]}
|
||||
>
|
||||
<Select placeholder="请选择" disabled={true}>
|
||||
{objToArray(EnumSqlParamsType).map((d) => (
|
||||
<Option key={d.value} value={d.value}>
|
||||
{d.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
name="valueType"
|
||||
label="值类型"
|
||||
rules={[{ required: true, message: '请选择值类型' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择值类型"
|
||||
onChange={(e) => {
|
||||
setValueType(e as IDataSource.ISqlParamsValueType);
|
||||
if (e === 'EXPR') {
|
||||
form.setFieldsValue({
|
||||
defaultValues: undefined,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{objToArray(EnumSqlValueType).map((d) => (
|
||||
<Option key={d.value} value={d.value}>
|
||||
{d.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{/* <Form.Item name="alias" label="别名">
|
||||
<Input placeholder="请输入参数别名" />
|
||||
</Form.Item> */}
|
||||
{/* {valueType !== 'sql' && (
|
||||
<Form.Item name="udf" label="是否使用表达式">
|
||||
<Checkbox
|
||||
checked={udf}
|
||||
onChange={(e) => {
|
||||
setUdf(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
form.setFieldsValue({
|
||||
defaultValues: '',
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
)} */}
|
||||
{valueType === 'EXPR' ? (
|
||||
<Form.Item name="defaultValues" label="表达式">
|
||||
<ParamsTextArea />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item name="defaultValues" label="默认值">
|
||||
<ParamsSqlEditor />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SqlParamsDetailModal;
|
||||
@@ -0,0 +1,132 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { Input, Tag, Tooltip } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
import styles from '../style.less';
|
||||
|
||||
type ParamsItemProps = {
|
||||
value?: string[];
|
||||
onChange?: (e: string[]) => void;
|
||||
};
|
||||
const ParamsSqlEditor: FC<ParamsItemProps> = ({ value, onChange }) => {
|
||||
const [editInputValue, setEditInputValue] = useState<string>();
|
||||
const [inputValue, setInputValue] = useState<string>();
|
||||
const [editInputIndex, setEditInputIndex] = useState<number>(-1);
|
||||
const [inputVisible, setInputVisible] = useState<boolean>(false);
|
||||
const editInput = useRef<typeof Input>(null);
|
||||
const inputRef = useRef<typeof Input>(null);
|
||||
|
||||
const handleEditInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setEditInputValue(e.target.value);
|
||||
};
|
||||
const handleEditInputConfirm = () => {
|
||||
const newValues = value ? [...value] : [];
|
||||
newValues[editInputIndex] = editInputValue || '';
|
||||
if (onChange) {
|
||||
onChange(newValues);
|
||||
}
|
||||
setEditInputIndex(-1);
|
||||
setEditInputValue('');
|
||||
};
|
||||
const handleClose = (removedTag: string) => {
|
||||
const newValues = value ? value.filter((tag) => tag !== removedTag) : [];
|
||||
if (onChange) {
|
||||
onChange(newValues);
|
||||
}
|
||||
};
|
||||
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
const newValues = value ? [...value] : [];
|
||||
if (inputValue && !newValues.includes(inputValue)) {
|
||||
newValues.push(inputValue);
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(newValues);
|
||||
}
|
||||
setInputVisible(false);
|
||||
setInputValue('');
|
||||
};
|
||||
|
||||
const showInput = () => {
|
||||
setInputVisible(true);
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{value &&
|
||||
value.map((tag: any, index: number) => {
|
||||
if (editInputIndex === index) {
|
||||
return (
|
||||
<Input
|
||||
ref={editInput}
|
||||
key={tag}
|
||||
size="small"
|
||||
className={styles.tagInput}
|
||||
value={editInputValue}
|
||||
onChange={handleEditInputChange}
|
||||
onBlur={handleEditInputConfirm}
|
||||
onPressEnter={handleEditInputConfirm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const isLongTag = tag.length > 20;
|
||||
|
||||
const tagElem = (
|
||||
<Tag
|
||||
className={styles.editTag}
|
||||
key={tag}
|
||||
closable={true}
|
||||
onClose={() => handleClose(tag)}
|
||||
>
|
||||
<span
|
||||
onDoubleClick={(e) => {
|
||||
setEditInputIndex(index);
|
||||
setEditInputValue(tag);
|
||||
e.preventDefault();
|
||||
setTimeout(() => {
|
||||
editInput.current?.focus();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
tagElem
|
||||
);
|
||||
})}
|
||||
{inputVisible && (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
size="small"
|
||||
className={styles.tagInput}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onBlur={handleInputConfirm}
|
||||
onPressEnter={handleInputConfirm}
|
||||
/>
|
||||
)}
|
||||
{!inputVisible && (
|
||||
<Tag className={styles.siteTagPlus} onClick={showInput}>
|
||||
<PlusOutlined /> 增加默认值
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParamsSqlEditor;
|
||||
@@ -6,3 +6,10 @@ export type TaskResultColumn = {
|
||||
|
||||
// 任务查询结果
|
||||
export type TaskResultItem = Record<string, string | number>;
|
||||
|
||||
export type OprType = 'add' | 'edit';
|
||||
|
||||
export type ParamsItemProps = {
|
||||
value?: any;
|
||||
onChange?: (e: any) => void;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user