16 Commits

Author SHA1 Message Date
LXW
532a00518c (improvement)(chat) Fix agent filter rule query (#777)
Co-authored-by: jolunoluo
2024-02-29 12:45:11 +08:00
daikon
895f38b6f7 Dev 0.9 (#774) 2024-02-29 10:42:03 +08:00
lexluo09
eba3a8ad34 (improvement)(chat) In SchemaCorrector, removing filters from linkingValue that do not exist. (#775) 2024-02-29 10:25:14 +08:00
jerryjzhang
6813582ea0 [docs]Add core idea diagram to README 2024-02-28 20:48:51 +08:00
LXW
6b6e54e95f (improvement)(chat) Fix modelId to viewId in QueryReq (#773)
Co-authored-by: jolunoluo
2024-02-27 21:42:40 +08:00
tristanliu
26260c79f1 [improvement][headless-fe] Resolved the issue where users were unable to select all options for dimensions, metrics, and fields in the metric generation process. (#771)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality

* [improvement][semantic-fe] fix the dimension initialization issue in metric correlation

* [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source.

* [improvement][semantic-fe] Optimizing pagination logic and some CSS styles

* [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum"

* [improvement][semantic-fe] Fixing the default value setting for the indicator list

* [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models

* [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API

* [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators

* [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality

* [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component.

* [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not.

* [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas.

* [improvement][semantic-fe] Updating the datePicker component to use dayjs instead.

* [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market.

* [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model.

* [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market.

* [improvement][semantic-fe] Adding the ability to download and transpose data.

* [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity.

* [improvement][semantic-fe] Fixing the logic error in the dimension value setting.

* [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information.

* [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience.

* [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas.

* [improvement][semantic-fe] Changing the line type for canvas connections.

* [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless".

* [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model.

* [improvement][semantic-fe] Updating the logic for the fieldName.

* [improvement][semantic-fe] Adjusting the position of the metrics tab.

* [improvement][semantic-fe] Changing the 字段名称 to 英文名称.

* [improvement][semantic-fe] Fix metric measurement deletion.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI adjustment for metric details page.

* [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty.

* [improvement][semantic-fe] Added field type and metric type to the metric creation options.

* [improvement][semantic-fe] The organization structure selection feature has been added to the permission management.

* [improvement][semantic-fe] Improved user experience for the metric list.

* [improvement][semantic-fe] fix update the metric list.

* [improvement][headless-fe] Added view management functionality.

* [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system.

* [improvement][headless-fe] Added model editing side effect detection.

* [improvement][headless-fe] Fixed the logic error in view editing.

* [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings.

* [improvement][headless-fe] Added the ability to hide the Q&A settings entry point.

* [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation.

* [improvement][headless-fe] Added search functionality to the field list in model editing.

* [improvement][headless-fe] fix the field list in model editing

* [improvement][headless-fe] Restructured the data for the dimension value settings interface.

* [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts.

* [improvement][headless-fe] Added support for passing dynamic variables as parameters in the executeSql function.

* [improvement][headless-fe] Resolved the issue where users were unable to select all options for dimensions, metrics, and fields in the metric generation process.
2024-02-27 20:25:55 +08:00
jipeli
0a42932db2 (feature) (headless) tag api (#769) 2024-02-27 17:22:26 +08:00
lexluo09
3b4678d682 (improvement)(project) add mac、windows ci workflows (#767) 2024-02-27 16:14:48 +08:00
lexluo09
c0458ccf0e Merge pull request #766 from lexluo09/master 2024-02-27 14:12:55 +08:00
lexluo
c7c70208ff (improvement)(Headless) Adding SQL, view, tag, and metric APIs, along with the addition of GrammarCorrector. 2024-02-27 14:06:20 +08:00
LXW
3a38200448 (improvement)(headless) Fix automatically inject dimension default values into SQL (#765)
Co-authored-by: jolunoluo
2024-02-27 13:07:26 +08:00
daikon
b72e280990 Dict headless opt (#764)
* [improvement](headless) Enhance dictionary execution function,support clear dict data recording

* [fix](headless) fix dict value support space

---------

Co-authored-by: kanedai <kanedai@tencent.com>
2024-02-27 10:40:31 +08:00
LXW
0541614dad (improvement)(headless) Parse sql variable (#763)
Co-authored-by: jolunoluo
2024-02-26 21:59:55 +08:00
tristanliu
0beb3cefd3 [improvement][headless-fe] Added support for passing dynamic variables as parameters in the executeSql function. (#762)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality

* [improvement][semantic-fe] fix the dimension initialization issue in metric correlation

* [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source.

* [improvement][semantic-fe] Optimizing pagination logic and some CSS styles

* [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum"

* [improvement][semantic-fe] Fixing the default value setting for the indicator list

* [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models

* [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API

* [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators

* [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality

* [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component.

* [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not.

* [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas.

* [improvement][semantic-fe] Updating the datePicker component to use dayjs instead.

* [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market.

* [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model.

* [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market.

* [improvement][semantic-fe] Adding the ability to download and transpose data.

* [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity.

* [improvement][semantic-fe] Fixing the logic error in the dimension value setting.

* [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information.

* [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience.

* [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas.

* [improvement][semantic-fe] Changing the line type for canvas connections.

* [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless".

* [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model.

* [improvement][semantic-fe] Updating the logic for the fieldName.

* [improvement][semantic-fe] Adjusting the position of the metrics tab.

* [improvement][semantic-fe] Changing the 字段名称 to 英文名称.

* [improvement][semantic-fe] Fix metric measurement deletion.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI adjustment for metric details page.

* [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty.

* [improvement][semantic-fe] Added field type and metric type to the metric creation options.

* [improvement][semantic-fe] The organization structure selection feature has been added to the permission management.

* [improvement][semantic-fe] Improved user experience for the metric list.

* [improvement][semantic-fe] fix update the metric list.

* [improvement][headless-fe] Added view management functionality.

* [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system.

* [improvement][headless-fe] Added model editing side effect detection.

* [improvement][headless-fe] Fixed the logic error in view editing.

* [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings.

* [improvement][headless-fe] Added the ability to hide the Q&A settings entry point.

* [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation.

* [improvement][headless-fe] Added search functionality to the field list in model editing.

* [improvement][headless-fe] fix the field list in model editing

* [improvement][headless-fe] Restructured the data for the dimension value settings interface.

* [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts.

* [improvement][headless-fe] Added support for passing dynamic variables as parameters in the executeSql function.
2024-02-26 20:22:34 +08:00
tristanliu
afdf18398c [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts. (#761)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality

* [improvement][semantic-fe] fix the dimension initialization issue in metric correlation

* [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source.

* [improvement][semantic-fe] Optimizing pagination logic and some CSS styles

* [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum"

* [improvement][semantic-fe] Fixing the default value setting for the indicator list

* [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models

* [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API

* [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators

* [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality

* [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component.

* [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not.

* [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas.

* [improvement][semantic-fe] Updating the datePicker component to use dayjs instead.

* [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market.

* [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model.

* [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market.

* [improvement][semantic-fe] Adding the ability to download and transpose data.

* [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity.

* [improvement][semantic-fe] Fixing the logic error in the dimension value setting.

* [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information.

* [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience.

* [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas.

* [improvement][semantic-fe] Changing the line type for canvas connections.

* [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless".

* [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model.

* [improvement][semantic-fe] Updating the logic for the fieldName.

* [improvement][semantic-fe] Adjusting the position of the metrics tab.

* [improvement][semantic-fe] Changing the 字段名称 to 英文名称.

* [improvement][semantic-fe] Fix metric measurement deletion.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI adjustment for metric details page.

* [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty.

* [improvement][semantic-fe] Added field type and metric type to the metric creation options.

* [improvement][semantic-fe] The organization structure selection feature has been added to the permission management.

* [improvement][semantic-fe] Improved user experience for the metric list.

* [improvement][semantic-fe] fix update the metric list.

* [improvement][headless-fe] Added view management functionality.

* [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system.

* [improvement][headless-fe] Added model editing side effect detection.

* [improvement][headless-fe] Fixed the logic error in view editing.

* [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings.

* [improvement][headless-fe] Added the ability to hide the Q&A settings entry point.

* [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation.

* [improvement][headless-fe] Added search functionality to the field list in model editing.

* [improvement][headless-fe] fix the field list in model editing

* [improvement][headless-fe] Restructured the data for the dimension value settings interface.

* [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts.
2024-02-26 19:56:28 +08:00
tristanliu
bdf7df933b [improvement][headless-fe] Restructured the data for the dimension value settings interface. (#760)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab.
[improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions.
[improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager.

* [improvement][semantic-fe] Add time granularity setting in the data source configuration.

* [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility

* [improvement][semantic-fe] Modification of data source creation prompt wording"

* [improvement][semantic-fe] metric market experience optimization

* [improvement][semantic-fe] enhance the analysis of metric trends

* [improvement][semantic-fe] optimize the presentation of metric trend permissions

* [improvement][semantic-fe] add metric trend download functionality

* [improvement][semantic-fe] fix the dimension initialization issue in metric correlation

* [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source.

* [improvement][semantic-fe] Optimizing pagination logic and some CSS styles

* [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum"

* [improvement][semantic-fe] Fixing the default value setting for the indicator list

* [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models

* [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API

* [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators

* [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality

* [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component.

* [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not.

* [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas.

* [improvement][semantic-fe] Updating the datePicker component to use dayjs instead.

* [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market.

* [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model.

* [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market.

* [improvement][semantic-fe] Adding the ability to download and transpose data.

* [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity.

* [improvement][semantic-fe] Fixing the logic error in the dimension value setting.

* [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information.

* [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience.

* [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas.

* [improvement][semantic-fe] Changing the line type for canvas connections.

* [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless".

* [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model.

* [improvement][semantic-fe] Updating the logic for the fieldName.

* [improvement][semantic-fe] Adjusting the position of the metrics tab.

* [improvement][semantic-fe] Changing the 字段名称 to 英文名称.

* [improvement][semantic-fe] Fix metric measurement deletion.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI adjustment for metric details page.

* [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty.

* [improvement][semantic-fe] Added field type and metric type to the metric creation options.

* [improvement][semantic-fe] The organization structure selection feature has been added to the permission management.

* [improvement][semantic-fe] Improved user experience for the metric list.

* [improvement][semantic-fe] fix update the metric list.

* [improvement][headless-fe] Added view management functionality.

* [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system.

* [improvement][headless-fe] Added model editing side effect detection.

* [improvement][headless-fe] Fixed the logic error in view editing.

* [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings.

* [improvement][headless-fe] Added the ability to hide the Q&A settings entry point.

* [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation.

* [improvement][headless-fe] Added search functionality to the field list in model editing.

* [improvement][headless-fe] fix the field list in model editing

* [improvement][headless-fe] Restructured the data for the dimension value settings interface.
2024-02-26 18:40:23 +08:00
114 changed files with 2975 additions and 1210 deletions

35
.github/workflows/mac-ci.yml vendored Normal file
View 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

View File

@@ -1,4 +1,4 @@
name: supersonic CI
name: supersonic ubuntu CI
on:
push:

35
.github/workflows/windows-ci.yml vendored Normal file
View 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

View File

@@ -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).

View File

@@ -10,6 +10,8 @@
在我们看来,为了在实际场景发挥价值,有三个关键点:
1. 融合HeadlessBI通过统一语义层封装底层数据细节关联、键值、公式等降低SQL生成的**复杂度**。
<img src="./docs/images/supersonic_ideas.png" height="65%" width="65%" align="center"/>
2. 通过一前一后的模式映射器和语义修正器来缓解LLM常见的**幻觉**现象。
3. 设计启发式的规则,在一些特定场景提升语义解析的**效率**。

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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 = "_";

View File

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

View File

@@ -4,6 +4,7 @@ public enum TypeEnums {
METRIC,
DIMENSION,
TAG,
DOMAIN,
ENTITY,
VIEW,

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
package com.tencent.supersonic.headless.api.pojo;
import lombok.Data;
@Data
public class TagDefineParams {
private String expr;
}

View File

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

View File

@@ -0,0 +1,8 @@
package com.tencent.supersonic.headless.api.pojo.enums;
public enum TagDefineType {
FIELD,
DIMENSION,
Tag
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}")

View File

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

View File

@@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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());

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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, \

View File

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

View File

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

View File

@@ -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 ='标签表';

View File

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

View File

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

View File

@@ -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, \

View File

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

View File

@@ -1,22 +1,4 @@
export * from './models/base';
type ObjToArrayParams = Record<string, string>;
const keyTypeTran = {
string: String,
number: Number,
};
/**
* obj转成valuelabel的数组
* @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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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