13 Commits

Author SHA1 Message Date
beat4ocean
9c10089707 [improvement][headless] Current mainstream large models are not as intelligent, and setting this restriction leads to excessive query failures. 2025-03-05 17:36:13 +08:00
beat4ocean
0e6050e8ce [fix][headless] Fix the issue where filterSql is not working. 2025-03-05 17:36:02 +08:00
beat4ocean
61685d31f3 Merge branch 'tencentmusic:master' into master 2025-03-05 17:21:44 +08:00
jerryjzhang
e0dc3fbf1a (improvement)(headless)Optimize compatibility and robustness in ontology query translation.
(improvement)(headless)Optimize compatibility and robustness in ontology query translation.
2025-03-05 17:11:59 +08:00
beat4ocean
738093bc88 [fix][chat]Fix dashboard display issue when categoryColumnName and dateColumnName are identical. 2025-03-05 15:01:11 +08:00
beat4ocean
b6d1525daa [Fix][Chat]Fix time granularity bug in model editing and dashboard display. 2025-03-05 15:01:11 +08:00
zyclove
efddf4cacf fix: https://github.com/tencentmusic/supersonic/issues/2132 (#2137) 2025-03-05 14:54:16 +08:00
jerryjzhang
732222ab98 (fix)(headless)Fix database permission check.
(fix)(headless)Fix database permission check.
2025-03-05 14:39:53 +08:00
jerryjzhang
5b994c4f8f (fix)(github)Upgrade actions/cache from 2 to 4. 2025-03-05 12:51:15 +08:00
Dack Wang
5d2ebdf680 (fix)(README) 404 docs link (#2133) 2025-03-05 12:36:56 +08:00
coosir
f1bc18ef65 Update docker-compose.yml (#2127)
Some checks are pending
supersonic CentOS CI / build (21) (push) Waiting to run
supersonic mac CI / build (21) (push) Waiting to run
supersonic ubuntu CI / build (21) (push) Waiting to run
supersonic windows CI / build (21) (push) Waiting to run
fix data persistence issue
2025-03-05 08:36:04 +08:00
jerryjzhang
8f361f9932 (improvement)(auth)Use interface in place of impl class.
Some checks are pending
supersonic CentOS CI / build (21) (push) Waiting to run
supersonic mac CI / build (21) (push) Waiting to run
supersonic ubuntu CI / build (21) (push) Waiting to run
supersonic windows CI / build (21) (push) Waiting to run
2025-03-04 17:56:41 +08:00
jerryjzhang
f532088e38 (feature)(releaes)Start 1.0.0-SNAPSHOT release.
Some checks are pending
supersonic CentOS CI / build (21) (push) Waiting to run
supersonic mac CI / build (21) (push) Waiting to run
supersonic ubuntu CI / build (21) (push) Waiting to run
supersonic windows CI / build (21) (push) Waiting to run
2025-03-04 10:11:00 +08:00
30 changed files with 166 additions and 84 deletions

View File

@@ -47,7 +47,7 @@ jobs:
mvn -version mvn -version
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@@ -26,7 +26,7 @@ jobs:
distribution: 'adopt' distribution: 'adopt'
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/Library/Caches/Maven # macOS Maven cache path path: ~/Library/Caches/Maven # macOS Maven cache path
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@@ -26,7 +26,7 @@ jobs:
distribution: 'adopt' distribution: 'adopt'
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.m2 path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@@ -26,7 +26,7 @@ jobs:
distribution: 'adopt' # You might need to change this if 'adopt' doesn't support JDK 21 distribution: 'adopt' # You might need to change this if 'adopt' doesn't support JDK 21
- name: Cache Maven packages - name: Cache Maven packages
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~\.m2 # Windows uses a backslash for paths path: ~\.m2 # Windows uses a backslash for paths
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@@ -75,7 +75,7 @@ SuperSonic comes with sample semantic models as well as chat conversations that
## Build and Development ## Build and Development
Please refer to project [Docs](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E7%BC%96%E8%AF%91%E6%9E%84%E5%BB%BA/). Please refer to project [Docs](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/).
## WeChat Contact ## WeChat Contact

View File

@@ -75,7 +75,7 @@ SuperSonic自带样例的语义模型和问答对话只需以下三步即可
## 如何构建和部署 ## 如何构建和部署
请参考项目[文档](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E7%BC%96%E8%AF%91%E6%9E%84%E5%BB%BA/)。 请参考项目[文档](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/)。
## 微信联系方式 ## 微信联系方式

View File

@@ -71,7 +71,7 @@ SuperSonicには、サンプルのセマンティックモデルとチャット
## ビルドと開発 ## ビルドと開発
プロジェクト[ドキュメント](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E7%BC%96%E8%AF%91%E6%9E%84%E5%BB%BA/)を参照してください。 プロジェクト[ドキュメント](https://supersonicbi.github.io/docs/%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2/%E6%BA%90%E7%A0%81%E7%BC%96%E8%AF%91%E9%83%A8%E7%BD%B2/)を参照してください。
## WeChat連絡先 ## WeChat連絡先

View File

@@ -1,7 +1,7 @@
package com.tencent.supersonic.auth.authentication.interceptor; package com.tencent.supersonic.auth.authentication.interceptor;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.authentication.service.UserServiceImpl; import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.authentication.utils.TokenService; import com.tencent.supersonic.auth.authentication.utils.TokenService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -16,7 +16,7 @@ public abstract class AuthenticationInterceptor implements HandlerInterceptor {
protected AuthenticationConfig authenticationConfig; protected AuthenticationConfig authenticationConfig;
protected UserServiceImpl userServiceImpl; protected UserService userService;
protected TokenService tokenService; protected TokenService tokenService;

View File

@@ -3,7 +3,7 @@ package com.tencent.supersonic.auth.authentication.interceptor;
import com.tencent.supersonic.auth.api.authentication.annotation.AuthenticationIgnore; import com.tencent.supersonic.auth.api.authentication.annotation.AuthenticationIgnore;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword; import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
import com.tencent.supersonic.auth.authentication.service.UserServiceImpl; import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.authentication.utils.TokenService; import com.tencent.supersonic.auth.authentication.utils.TokenService;
import com.tencent.supersonic.common.pojo.exception.AccessException; import com.tencent.supersonic.common.pojo.exception.AccessException;
import com.tencent.supersonic.common.util.ContextUtils; import com.tencent.supersonic.common.util.ContextUtils;
@@ -16,12 +16,7 @@ import org.springframework.web.method.HandlerMethod;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Optional; import java.util.Optional;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_IS_ADMIN; import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.*;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_DISPLAY_NAME;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_EMAIL;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_ID;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_NAME;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_PASSWORD;
@Slf4j @Slf4j
public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor { public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor {
@@ -30,7 +25,7 @@ public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws AccessException { Object handler) throws AccessException {
authenticationConfig = ContextUtils.getBean(AuthenticationConfig.class); authenticationConfig = ContextUtils.getBean(AuthenticationConfig.class);
userServiceImpl = ContextUtils.getBean(UserServiceImpl.class); userService = ContextUtils.getBean(UserService.class);
tokenService = ContextUtils.getBean(TokenService.class); tokenService = ContextUtils.getBean(TokenService.class);
if (!authenticationConfig.isEnabled()) { if (!authenticationConfig.isEnabled()) {
return true; return true;

View File

@@ -26,6 +26,16 @@ public class SqlDialectFactory {
.withLiteralQuoteString("'").withIdentifierQuoteString("\"") .withLiteralQuoteString("'").withIdentifierQuoteString("\"")
.withLiteralEscapedQuoteString("''").withUnquotedCasing(Casing.UNCHANGED) .withLiteralEscapedQuoteString("''").withUnquotedCasing(Casing.UNCHANGED)
.withQuotedCasing(Casing.UNCHANGED).withCaseSensitive(true); .withQuotedCasing(Casing.UNCHANGED).withCaseSensitive(true);
public static final Context PRESTO_CONTEXT =
SqlDialect.EMPTY_CONTEXT.withDatabaseProduct(DatabaseProduct.PRESTO)
.withLiteralQuoteString("'").withIdentifierQuoteString("\"")
.withLiteralEscapedQuoteString("''").withUnquotedCasing(Casing.UNCHANGED)
.withQuotedCasing(Casing.UNCHANGED).withCaseSensitive(true);
public static final Context KYUUBI_CONTEXT =
SqlDialect.EMPTY_CONTEXT.withDatabaseProduct(DatabaseProduct.BIG_QUERY)
.withLiteralQuoteString("'").withIdentifierQuoteString("`")
.withLiteralEscapedQuoteString("''").withUnquotedCasing(Casing.UNCHANGED)
.withQuotedCasing(Casing.UNCHANGED).withCaseSensitive(false);
private static Map<EngineType, SemanticSqlDialect> sqlDialectMap; private static Map<EngineType, SemanticSqlDialect> sqlDialectMap;
static { static {
@@ -35,6 +45,10 @@ public class SqlDialectFactory {
sqlDialectMap.put(EngineType.H2, new SemanticSqlDialect(DEFAULT_CONTEXT)); sqlDialectMap.put(EngineType.H2, new SemanticSqlDialect(DEFAULT_CONTEXT));
sqlDialectMap.put(EngineType.POSTGRESQL, new SemanticSqlDialect(POSTGRESQL_CONTEXT)); sqlDialectMap.put(EngineType.POSTGRESQL, new SemanticSqlDialect(POSTGRESQL_CONTEXT));
sqlDialectMap.put(EngineType.HANADB, new SemanticSqlDialect(HANADB_CONTEXT)); sqlDialectMap.put(EngineType.HANADB, new SemanticSqlDialect(HANADB_CONTEXT));
sqlDialectMap.put(EngineType.STARROCKS, new SemanticSqlDialect(DEFAULT_CONTEXT));
sqlDialectMap.put(EngineType.KYUUBI, new SemanticSqlDialect(KYUUBI_CONTEXT));
sqlDialectMap.put(EngineType.PRESTO, new SemanticSqlDialect(PRESTO_CONTEXT));
sqlDialectMap.put(EngineType.TRINO, new SemanticSqlDialect(PRESTO_CONTEXT));
} }
public static SemanticSqlDialect getSqlDialect(EngineType engineType) { public static SemanticSqlDialect getSqlDialect(EngineType engineType) {

View File

@@ -91,7 +91,8 @@ public class FiledFilterReplaceVisitor extends ExpressionVisitorAdapter {
} }
ExpressionList<?> leftFunctionParams = leftFunction.getParameters(); ExpressionList<?> leftFunctionParams = leftFunction.getParameters();
if (CollectionUtils.isEmpty(leftFunctionParams)) { if (CollectionUtils.isEmpty(leftFunctionParams)
|| !(leftFunctionParams.get(0) instanceof Column)) {
return result; return result;
} }

View File

@@ -3,6 +3,7 @@ package com.tencent.supersonic.common.pojo;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import static com.tencent.supersonic.common.pojo.Constants.ASC_UPPER; import static com.tencent.supersonic.common.pojo.Constants.ASC_UPPER;

View File

@@ -12,7 +12,7 @@ services:
ports: ports:
- "15432:5432" - "15432:5432"
# volumes: # volumes:
# - postgres_data:/var/lib/postgresql # - postgres_data:/var/lib/postgresql/data
networks: networks:
- supersonic_network - supersonic_network
dns: dns:
@@ -62,4 +62,4 @@ services:
# supersonic_data: # supersonic_data:
networks: networks:
supersonic_network: supersonic_network:

View File

@@ -24,6 +24,8 @@ public class ModelDetail {
private String tableQuery; private String tableQuery;
private String filterSql;
private List<Identify> identifiers = Lists.newArrayList(); private List<Identify> identifiers = Lists.newArrayList();
private List<Dimension> dimensions = Lists.newArrayList(); private List<Dimension> dimensions = Lists.newArrayList();

View File

@@ -19,6 +19,8 @@ public class ModelBuildReq {
private String sql; private String sql;
private String filterSql;
private String catalog; private String catalog;
private String db; private String db;

View File

@@ -1,6 +1,7 @@
package com.tencent.supersonic.headless.core.translator; package com.tencent.supersonic.headless.core.translator;
import com.tencent.supersonic.common.calcite.SqlMergeWithUtils; import com.tencent.supersonic.common.calcite.SqlMergeWithUtils;
import com.tencent.supersonic.common.jsqlparser.SqlSelectHelper;
import com.tencent.supersonic.common.pojo.enums.EngineType; import com.tencent.supersonic.common.pojo.enums.EngineType;
import com.tencent.supersonic.headless.api.pojo.response.QueryState; import com.tencent.supersonic.headless.api.pojo.response.QueryState;
import com.tencent.supersonic.headless.core.pojo.OntologyQuery; import com.tencent.supersonic.headless.core.pojo.OntologyQuery;
@@ -73,7 +74,7 @@ public class DefaultSemanticTranslator implements SemanticTranslator {
String finalSql = null; String finalSql = null;
if (sqlQuery.isSupportWith()) { if (sqlQuery.isSupportWith()) {
EngineType engineType = queryStatement.getOntology().getDatabaseType(); EngineType engineType = queryStatement.getOntology().getDatabaseType();
if (!SqlMergeWithUtils.hasWith(engineType, ontologyOuterSql)) { if (!SqlSelectHelper.hasWith(ontologyOuterSql)) {
finalSql = "with " + tables.stream() finalSql = "with " + tables.stream()
.map(t -> String.format("%s as (%s)", t.getLeft(), t.getRight())) .map(t -> String.format("%s as (%s)", t.getLeft(), t.getRight()))
.collect(Collectors.joining(",")) + "\n" + ontologyOuterSql; .collect(Collectors.joining(",")) + "\n" + ontologyOuterSql;

View File

@@ -50,19 +50,19 @@ public class SqlQueryParser implements QueryParser {
queryFields.removeAll(queryAliases); queryFields.removeAll(queryAliases);
Ontology ontology = queryStatement.getOntology(); Ontology ontology = queryStatement.getOntology();
OntologyQuery ontologyQuery = buildOntologyQuery(ontology, queryFields); OntologyQuery ontologyQuery = buildOntologyQuery(ontology, queryFields);
// check if there are fields not matched with any metric or dimension // // check if there are fields not matched with any metric or dimension
if (queryFields.size() > ontologyQuery.getMetrics().size() // if (queryFields.size() > ontologyQuery.getMetrics().size()
+ ontologyQuery.getDimensions().size()) { // + ontologyQuery.getDimensions().size()) {
List<String> semanticFields = Lists.newArrayList(); // List<String> semanticFields = Lists.newArrayList();
ontologyQuery.getMetrics().forEach(m -> semanticFields.add(m.getName())); // ontologyQuery.getMetrics().forEach(m -> semanticFields.add(m.getName()));
ontologyQuery.getDimensions().forEach(d -> semanticFields.add(d.getName())); // ontologyQuery.getDimensions().forEach(d -> semanticFields.add(d.getName()));
String errMsg = // String errMsg =
String.format("Querying columns[%s] not matched with semantic fields[%s].", // String.format("Querying columns[%s] not matched with semantic fields[%s].",
queryFields, semanticFields); // queryFields, semanticFields);
queryStatement.setErrMsg(errMsg); // queryStatement.setErrMsg(errMsg);
queryStatement.setStatus(QueryState.INVALID); // queryStatement.setStatus(QueryState.INVALID);
return; // return;
} // }
queryStatement.setOntologyQuery(ontologyQuery); queryStatement.setOntologyQuery(ontologyQuery);
AggOption sqlQueryAggOption = getAggOption(sqlQuery.getSql(), ontologyQuery.getMetrics()); AggOption sqlQueryAggOption = getAggOption(sqlQuery.getSql(), ontologyQuery.getMetrics());

View File

@@ -40,11 +40,23 @@ public class DataModelNode extends SemanticNode {
.equalsIgnoreCase(EngineType.POSTGRESQL.getName())) { .equalsIgnoreCase(EngineType.POSTGRESQL.getName())) {
String fullTableName = String.join(".public.", String fullTableName = String.join(".public.",
dataModel.getModelDetail().getTableQuery().split("\\.")); dataModel.getModelDetail().getTableQuery().split("\\."));
sqlTable = "select * from " + fullTableName; sqlTable = "SELECT * FROM " + fullTableName;
} else { } else {
sqlTable = "select * from " + dataModel.getModelDetail().getTableQuery(); sqlTable = "SELECT * FROM " + dataModel.getModelDetail().getTableQuery();
} }
} }
// String filterSql = dataModel.getFilterSql();
String filterSql = dataModel.getModelDetail().getFilterSql();
if (filterSql != null && !filterSql.isEmpty()) {
boolean sqlContainWhere = sqlTable.toUpperCase().matches("(?s).*\\bWHERE\\b.*");
if (sqlContainWhere) {
sqlTable = String.format("%s AND %s", sqlTable, filterSql);
} else {
sqlTable = String.format("%s WHERE %s", sqlTable, filterSql);
}
}
if (sqlTable.isEmpty()) { if (sqlTable.isEmpty()) {
throw new Exception("DataModelNode build error [tableSqlNode not found]"); throw new Exception("DataModelNode build error [tableSqlNode not found]");
} }
@@ -69,7 +81,7 @@ public class DataModelNode extends SemanticNode {
} }
private static void addSchemaTable(SqlValidatorScope scope, ModelResp dataModel, String db, private static void addSchemaTable(SqlValidatorScope scope, ModelResp dataModel, String db,
String tb, Set<String> fields) throws Exception { String tb, Set<String> fields) throws Exception {
Set<String> dateInfo = new HashSet<>(); Set<String> dateInfo = new HashSet<>();
Set<String> dimensions = new HashSet<>(); Set<String> dimensions = new HashSet<>();
Set<String> metrics = new HashSet<>(); Set<String> metrics = new HashSet<>();
@@ -106,7 +118,7 @@ public class DataModelNode extends SemanticNode {
} }
public static List<SqlNode> getExtendField(Map<String, String> exprList, public static List<SqlNode> getExtendField(Map<String, String> exprList,
SqlValidatorScope scope, EngineType engineType) throws Exception { SqlValidatorScope scope, EngineType engineType) throws Exception {
List<SqlNode> sqlNodeList = new ArrayList<>(); List<SqlNode> sqlNodeList = new ArrayList<>();
for (String expr : exprList.keySet()) { for (String expr : exprList.keySet()) {
sqlNodeList.add(parse(expr, scope, engineType)); sqlNodeList.add(parse(expr, scope, engineType));
@@ -127,7 +139,7 @@ public class DataModelNode extends SemanticNode {
} }
public static List<ModelResp> getQueryDataModels(Ontology ontology, public static List<ModelResp> getQueryDataModels(Ontology ontology,
OntologyQuery ontologyQuery) { OntologyQuery ontologyQuery) {
// get query measures and dimensions // get query measures and dimensions
Set<String> queryMeasures = new HashSet<>(); Set<String> queryMeasures = new HashSet<>();
Set<String> queryDimensions = new HashSet<>(); Set<String> queryDimensions = new HashSet<>();
@@ -162,7 +174,7 @@ public class DataModelNode extends SemanticNode {
} }
public static void getQueryDimensionMeasure(Ontology ontology, OntologyQuery ontologyQuery, public static void getQueryDimensionMeasure(Ontology ontology, OntologyQuery ontologyQuery,
Set<String> queryDimensions, Set<String> queryMeasures) { Set<String> queryDimensions, Set<String> queryMeasures) {
ontologyQuery.getMetrics().forEach(m -> { ontologyQuery.getMetrics().forEach(m -> {
if (Objects.nonNull(m.getMetricDefineByMeasureParams())) { if (Objects.nonNull(m.getMetricDefineByMeasureParams())) {
m.getMetricDefineByMeasureParams().getMeasures() m.getMetricDefineByMeasureParams().getMeasures()
@@ -215,7 +227,7 @@ public class DataModelNode extends SemanticNode {
} }
private static boolean checkMatch(ModelResp baseDataModel, Set<String> queryMeasures, private static boolean checkMatch(ModelResp baseDataModel, Set<String> queryMeasures,
Set<String> queryDimension) { Set<String> queryDimension) {
boolean isAllMatch = true; boolean isAllMatch = true;
Set<String> baseMeasures = baseDataModel.getMeasures().stream().map(Measure::getName) Set<String> baseMeasures = baseDataModel.getMeasures().stream().map(Measure::getName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@@ -248,8 +260,8 @@ public class DataModelNode extends SemanticNode {
} }
private static List<ModelResp> findRelatedModelsByRelation(Ontology ontology, private static List<ModelResp> findRelatedModelsByRelation(Ontology ontology,
OntologyQuery ontologyQuery, ModelResp baseDataModel, Set<String> queryDimensions, OntologyQuery ontologyQuery, ModelResp baseDataModel, Set<String> queryDimensions,
Set<String> queryMeasures) { Set<String> queryMeasures) {
Set<String> joinDataModelNames = new HashSet<>(); Set<String> joinDataModelNames = new HashSet<>();
List<ModelResp> joinDataModels = new ArrayList<>(); List<ModelResp> joinDataModels = new ArrayList<>();
Set<String> before = new HashSet<>(); Set<String> before = new HashSet<>();
@@ -333,7 +345,7 @@ public class DataModelNode extends SemanticNode {
} }
private static void sortJoinRelation(List<JoinRelation> joinRelations, String next, private static void sortJoinRelation(List<JoinRelation> joinRelations, String next,
Set<Long> visited, List<JoinRelation> sortedJoins) { Set<Long> visited, List<JoinRelation> sortedJoins) {
for (JoinRelation link : joinRelations) { for (JoinRelation link : joinRelations) {
if (!visited.contains(link.getId())) { if (!visited.contains(link.getId())) {
if (link.getLeft().equals(next) || link.getRight().equals(next)) { if (link.getLeft().equals(next) || link.getRight().equals(next)) {
@@ -348,7 +360,7 @@ public class DataModelNode extends SemanticNode {
} }
private static List<ModelResp> findRelatedModelsByIdentifier(Ontology ontology, private static List<ModelResp> findRelatedModelsByIdentifier(Ontology ontology,
ModelResp baseDataModel, Set<String> queryDimension, Set<String> measures) { ModelResp baseDataModel, Set<String> queryDimension, Set<String> measures) {
Set<String> baseIdentifiers = baseDataModel.getModelDetail().getIdentifiers().stream() Set<String> baseIdentifiers = baseDataModel.getModelDetail().getIdentifiers().stream()
.map(Identify::getName).collect(Collectors.toSet()); .map(Identify::getName).collect(Collectors.toSet());
if (baseIdentifiers.isEmpty()) { if (baseIdentifiers.isEmpty()) {

View File

@@ -78,7 +78,8 @@ public abstract class SemanticNode {
scope.getValidator().getCatalogReader().getRootSchema(), engineType); scope.getValidator().getCatalogReader().getRootSchema(), engineType);
if (Configuration.getSqlAdvisor(sqlValidatorWithHints, engineType).getReservedAndKeyWords() if (Configuration.getSqlAdvisor(sqlValidatorWithHints, engineType).getReservedAndKeyWords()
.contains(expression.toUpperCase())) { .contains(expression.toUpperCase())) {
if (engineType == EngineType.HANADB) { if (engineType == EngineType.HANADB || engineType == EngineType.PRESTO
|| engineType == EngineType.TRINO) {
expression = String.format("\"%s\"", expression); expression = String.format("\"%s\"", expression);
} else { } else {
expression = String.format("`%s`", expression); expression = String.format("`%s`", expression);
@@ -166,9 +167,9 @@ public abstract class SemanticNode {
if (sqlNode instanceof SqlBasicCall) { if (sqlNode instanceof SqlBasicCall) {
SqlBasicCall sqlBasicCall = (SqlBasicCall) sqlNode; SqlBasicCall sqlBasicCall = (SqlBasicCall) sqlNode;
if (sqlBasicCall.getOperator().getKind().equals(SqlKind.AS)) { if (sqlBasicCall.getOperator().getKind().equals(SqlKind.AS)) {
if (sqlBasicCall.getOperandList().get(0) instanceof SqlSelect) { SqlNode innerQuery = sqlBasicCall.getOperandList().get(0);
SqlSelect table = (SqlSelect) sqlBasicCall.getOperandList().get(0); if (innerQuery instanceof SqlCall) {
return table; return innerQuery;
} }
} }
} }

View File

@@ -69,7 +69,13 @@ public class SqlBuilder {
SqlNode parserNode = tableView.build(); SqlNode parserNode = tableView.build();
DatabaseResp database = queryStatement.getOntology().getDatabase(); DatabaseResp database = queryStatement.getOntology().getDatabase();
EngineType engineType = EngineType.fromString(database.getType()); EngineType engineType = EngineType.fromString(database.getType());
parserNode = optimizeParseNode(parserNode, engineType); try {
parserNode = optimizeParseNode(parserNode, engineType);
} catch (Exception e) {
// failure in optimization phase doesn't affect the query result,
// just ignore it
log.error("optimizeParseNode error", e);
}
return SemanticNode.getSql(parserNode, engineType); return SemanticNode.getSql(parserNode, engineType);
} }

View File

@@ -36,6 +36,7 @@ public class ModelYamlManager {
} else { } else {
dataModelYamlTpl.setTableQuery(modelDetail.getTableQuery()); dataModelYamlTpl.setTableQuery(modelDetail.getTableQuery());
} }
dataModelYamlTpl.setFilterSql(modelDetail.getFilterSql());
dataModelYamlTpl.setFields(modelResp.getModelDetail().getFields()); dataModelYamlTpl.setFields(modelResp.getModelDetail().getFields());
dataModelYamlTpl.setId(modelResp.getId()); dataModelYamlTpl.setId(modelResp.getId());
return dataModelYamlTpl; return dataModelYamlTpl;

View File

@@ -97,6 +97,7 @@ public class SemanticSchemaManager {
modelDetail.setDbType(d.getType()); modelDetail.setDbType(d.getType());
modelDetail.setSqlQuery(d.getSqlQuery()); modelDetail.setSqlQuery(d.getSqlQuery());
modelDetail.setTableQuery(d.getTableQuery()); modelDetail.setTableQuery(d.getTableQuery());
modelDetail.setFilterSql(d.getFilterSql());
modelDetail.getIdentifiers().addAll(getIdentify(d.getIdentifiers())); modelDetail.getIdentifiers().addAll(getIdentify(d.getIdentifiers()));
modelDetail.getMeasures().addAll(getMeasureParams(d.getMeasures())); modelDetail.getMeasures().addAll(getMeasureParams(d.getMeasures()));
modelDetail.getDimensions().addAll(getDimensions(d.getDimensions())); modelDetail.getDimensions().addAll(getDimensions(d.getDimensions()));

View File

@@ -21,6 +21,8 @@ public class DataModelYamlTpl {
private String tableQuery; private String tableQuery;
private String filterSql;
private List<IdentifyYamlTpl> identifiers; private List<IdentifyYamlTpl> identifiers;
private List<DimensionYamlTpl> dimensions; private List<DimensionYamlTpl> dimensions;

View File

@@ -2,6 +2,7 @@ package com.tencent.supersonic.headless.server.rest;
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder; import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
import com.tencent.supersonic.common.pojo.User; import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
import com.tencent.supersonic.headless.api.pojo.MetaFilter; import com.tencent.supersonic.headless.api.pojo.MetaFilter;
import com.tencent.supersonic.headless.api.pojo.request.DataSetReq; import com.tencent.supersonic.headless.api.pojo.request.DataSetReq;
import com.tencent.supersonic.headless.api.pojo.response.DataSetResp; import com.tencent.supersonic.headless.api.pojo.response.DataSetResp;
@@ -51,6 +52,7 @@ public class DataSetController {
public List<DataSetResp> getDataSetList(@RequestParam("domainId") Long domainId) { public List<DataSetResp> getDataSetList(@RequestParam("domainId") Long domainId) {
MetaFilter metaFilter = new MetaFilter(); MetaFilter metaFilter = new MetaFilter();
metaFilter.setDomainId(domainId); metaFilter.setDomainId(domainId);
metaFilter.setStatus(StatusEnum.ONLINE.getCode());
return dataSetService.getDataSetList(metaFilter); return dataSetService.getDataSetList(metaFilter);
} }

View File

@@ -56,9 +56,9 @@ public class DataSetServiceImpl extends ServiceImpl<DataSetDOMapper, DataSetDO>
public DataSetResp save(DataSetReq dataSetReq, User user) { public DataSetResp save(DataSetReq dataSetReq, User user) {
dataSetReq.createdBy(user.getName()); dataSetReq.createdBy(user.getName());
DataSetDO dataSetDO = convert(dataSetReq); DataSetDO dataSetDO = convert(dataSetReq);
dataSetDO.setStatus(StatusEnum.ONLINE.getCode()); dataSetDO.setStatus(dataSetReq.getStatus() != null ? dataSetReq.getStatus()
: StatusEnum.ONLINE.getCode());
DataSetResp dataSetResp = convert(dataSetDO); DataSetResp dataSetResp = convert(dataSetDO);
// conflictCheck(dataSetResp);
save(dataSetDO); save(dataSetDO);
dataSetResp.setId(dataSetDO.getId()); dataSetResp.setId(dataSetDO.getId());
return dataSetResp; return dataSetResp;

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.tencent.supersonic.common.pojo.QueryColumn; import com.tencent.supersonic.common.pojo.QueryColumn;
import com.tencent.supersonic.common.pojo.User; import com.tencent.supersonic.common.pojo.User;
import com.tencent.supersonic.common.pojo.enums.AuthType;
import com.tencent.supersonic.common.pojo.enums.EngineType; import com.tencent.supersonic.common.pojo.enums.EngineType;
import com.tencent.supersonic.headless.api.pojo.DBColumn; import com.tencent.supersonic.headless.api.pojo.DBColumn;
import com.tencent.supersonic.headless.api.pojo.enums.DataType; import com.tencent.supersonic.headless.api.pojo.enums.DataType;
@@ -79,8 +80,9 @@ public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseD
@Override @Override
public List<DatabaseResp> getDatabaseList(User user) { public List<DatabaseResp> getDatabaseList(User user) {
List<DatabaseResp> databaseResps = List<DatabaseResp> databaseResps = list().stream().map(DatabaseConverter::convert)
list().stream().map(DatabaseConverter::convert).collect(Collectors.toList()); .filter(database -> filterByAuth(database, user, AuthType.VIEWER))
.collect(Collectors.toList());
fillPermission(databaseResps, user); fillPermission(databaseResps, user);
return databaseResps; return databaseResps;
} }
@@ -100,6 +102,43 @@ public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseD
}); });
} }
private boolean filterByAuth(DatabaseResp database, User user, AuthType authType) {
if (user.isSuperAdmin() || user.getName().equals(database.getCreatedBy())) {
return true;
}
authType = authType == null ? AuthType.VIEWER : authType;
switch (authType) {
case ADMIN:
return checkAdminPermission(user, database);
case VIEWER:
default:
return checkViewPermission(user, database);
}
}
private boolean checkAdminPermission(User user, DatabaseResp database) {
List<String> admins = database.getAdmins();
if (user.isSuperAdmin()) {
return true;
}
if (admins.contains(user.getName()) || database.getCreatedBy().equals(user.getName())) {
return true;
}
return false;
}
private boolean checkViewPermission(User user, DatabaseResp database) {
if (checkAdminPermission(user, database)) {
return true;
}
List<String> viewers = database.getViewers();
if (viewers.contains(user.getName())) {
return true;
}
return false;
}
@Override @Override
public void deleteDatabase(Long databaseId) { public void deleteDatabase(Long databaseId) {
ModelFilter modelFilter = new ModelFilter(); ModelFilter modelFilter = new ModelFilter();

View File

@@ -157,6 +157,7 @@ public class ModelConverter {
modelDetail.setQueryType(ModelDefineType.TABLE_QUERY.getName()); modelDetail.setQueryType(ModelDefineType.TABLE_QUERY.getName());
modelDetail.setTableQuery(String.format("%s.%s", modelBuildReq.getDb(), tableName)); modelDetail.setTableQuery(String.format("%s.%s", modelBuildReq.getDb(), tableName));
} }
modelDetail.setFilterSql(modelBuildReq.getFilterSql());
for (ColumnSchema columnSchema : modelSchema.getColumnSchemas()) { for (ColumnSchema columnSchema : modelSchema.getColumnSchemas()) {
FieldType fieldType = columnSchema.getFiledType(); FieldType fieldType = columnSchema.getFiledType();
if (getIdentifyType(fieldType) != null) { if (getIdentifyType(fieldType) != null) {

View File

@@ -26,7 +26,7 @@
</parent> </parent>
<properties> <properties>
<revision>0.9.10</revision> <revision>1.0.0-SNAPSHOT</revision>
<java.source.version>21</java.source.version> <java.source.version>21</java.source.version>
<java.target.version>21</java.target.version> <java.target.version>21</java.target.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>

View File

@@ -48,12 +48,13 @@ const MetricTrend: React.FC<Props> = ({
const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data; const { queryColumns, queryResults, aggregateInfo, entityInfo, chatContext } = data;
const [chartType, setChartType] = useState('line'); const [chartType, setChartType] = useState('line');
const dateField: any = queryColumns?.find( const dateField = queryColumns?.find(
(column: any) => column.showType === 'DATE' || column.type === 'DATE' (column: any) => column.showType === 'DATE' || column.type === 'DATE'
); );
const dateColumnName = dateField?.bizName || ''; const dateColumnName = dateField?.bizName || '';
const categoryColumnName = let categoryColumnName =
queryColumns?.find((column: any) => column.showType === 'CATEGORY')?.bizName || ''; queryColumns?.find((column: any) => column.showType === 'CATEGORY')?.bizName || '';
categoryColumnName = categoryColumnName === dateColumnName ? '' : categoryColumnName;
const metricFields = queryColumns?.filter((column: any) => column.showType === 'NUMBER'); const metricFields = queryColumns?.filter((column: any) => column.showType === 'NUMBER');
const currentMetricField = queryColumns?.find((column: any) => column.showType === 'NUMBER'); const currentMetricField = queryColumns?.find((column: any) => column.showType === 'NUMBER');
@@ -80,11 +81,11 @@ const MetricTrend: React.FC<Props> = ({
<MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} /> <MetricInfo aggregateInfo={aggregateInfo} currentMetricField={currentMetricField} />
)} )}
<div className={`${prefixCls}-select-options`}> <div className={`${prefixCls}-select-options`}>
<DateOptions {/*<DateOptions*/}
chatContext={chatContext} {/* chatContext={chatContext}*/}
currentDateOption={currentDateOption} {/* currentDateOption={currentDateOption}*/}
onSelectDateOption={onSelectDateOption} {/* onSelectDateOption={onSelectDateOption}*/}
/> {/*/>*/}
<div> <div>
<Select <Select
defaultValue="line" defaultValue="line"

View File

@@ -8,7 +8,7 @@ import { ISemantic } from '../../data';
import { import {
AGG_OPTIONS, AGG_OPTIONS,
DATE_FORMATTER, DATE_FORMATTER,
DATE_OPTIONS, // DATE_OPTIONS,
DIM_OPTIONS, DIM_OPTIONS,
EnumDataSourceType, EnumDataSourceType,
EnumModelDataType, EnumModelDataType,
@@ -275,7 +275,7 @@ const ModelFieldForm: React.FC<Props> = ({
} }
} }
if ([EnumDataSourceType.TIME, EnumDataSourceType.PARTITION_TIME].includes(type)) { if ([EnumDataSourceType.TIME, EnumDataSourceType.PARTITION_TIME].includes(type)) {
const { dateFormat, timeGranularity } = record; const { dateFormat } = record;
const dateFormatterOptions = const dateFormatterOptions =
type === EnumDataSourceType.PARTITION_TIME ? PARTITION_TIME_FORMATTER : DATE_FORMATTER; type === EnumDataSourceType.PARTITION_TIME ? PARTITION_TIME_FORMATTER : DATE_FORMATTER;
@@ -302,25 +302,25 @@ const ModelFieldForm: React.FC<Props> = ({
<ExclamationCircleOutlined /> <ExclamationCircleOutlined />
</Tooltip> </Tooltip>
</Space> </Space>
<Space> {/*<Space>*/}
<span>:</span> {/* <span>时间粒度:</span>*/}
<Select {/* <Select*/}
placeholder="时间粒度" {/* placeholder="时间粒度"*/}
value={timeGranularity === '' ? undefined : timeGranularity} {/* value={timeGranularity === '' ? undefined : timeGranularity}*/}
onChange={(value) => { {/* onChange={(value) => {*/}
handleFieldChange(record, 'timeGranularity', value); {/* handleFieldChange(record, 'timeGranularity', value);*/}
}} {/* }}*/}
defaultValue={timeGranularity === '' ? undefined : DATE_OPTIONS[0]} {/* defaultValue={timeGranularity === '' ? undefined : DATE_OPTIONS[0]}*/}
style={{ minWidth: 180 }} {/* style={{ minWidth: 180 }}*/}
allowClear {/* allowClear*/}
> {/* >*/}
{DATE_OPTIONS.map((item) => ( {/* {DATE_OPTIONS.map((item) => (*/}
<Option key={item} value={item}> {/* <Option key={item} value={item}>*/}
{item} {/* {item}*/}
</Option> {/* </Option>*/}
))} {/* ))}*/}
</Select> {/* </Select>*/}
</Space> {/*</Space>*/}
</Space> </Space>
); );
} }