(improvement)(headless) Parse sql variable (#763)

Co-authored-by: jolunoluo
This commit is contained in:
LXW
2024-02-26 21:59:55 +08:00
committed by GitHub
parent 0beb3cefd3
commit 0541614dad
14 changed files with 111 additions and 64 deletions

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.Filter;
import com.tencent.supersonic.common.pojo.Order; import com.tencent.supersonic.common.pojo.Order;
import com.tencent.supersonic.common.pojo.enums.QueryType; import com.tencent.supersonic.common.pojo.enums.QueryType;
import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import lombok.Data;
@Data @Data
public class QueryParam { public class QueryParam {
@@ -34,7 +33,6 @@ public class QueryParam {
// metric // metric
private List<String> metrics = new ArrayList(); private List<String> metrics = new ArrayList();
private List<String> dimensions; private List<String> dimensions;
private Map<String, String> variables;
private String where; private String where;
private List<ColumnOrder> order; private List<ColumnOrder> order;
private boolean nativeQuery = false; private boolean nativeQuery = false;

View File

@@ -5,7 +5,7 @@ package com.tencent.supersonic.headless.api.pojo.enums;
* sql_query : view sql begin as select * sql_query : view sql begin as select
* table_query: dbName.tableName * table_query: dbName.tableName
*/ */
public enum DatasourceQuery { public enum ModelDefineType {
SQL_QUERY("sql_query"), SQL_QUERY("sql_query"),
TABLE_QUERY("table_query"); TABLE_QUERY("table_query");
@@ -13,7 +13,7 @@ public enum DatasourceQuery {
private String name; private String name;
DatasourceQuery(String name) { ModelDefineType(String name) {
this.name = name; this.name = name;
} }

View File

@@ -1,10 +1,12 @@
package com.tencent.supersonic.headless.api.pojo.request; package com.tencent.supersonic.headless.api.pojo.request;
import javax.validation.constraints.NotBlank; import com.tencent.supersonic.headless.api.pojo.SqlVariable;
import javax.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data @Data
public class SqlExecuteReq { public class SqlExecuteReq {
@@ -16,6 +18,8 @@ public class SqlExecuteReq {
@NotBlank(message = "sql can not be blank") @NotBlank(message = "sql can not be blank")
private String sql; private String sql;
private List<SqlVariable> sqlVariables;
public String getSql() { public String getSql() {
if (StringUtils.isNotBlank(sql) && sql.endsWith(";")) { if (StringUtils.isNotBlank(sql) && sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1); sql = sql.substring(0, sql.length() - 1);

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.QueryParam;
import com.tencent.supersonic.headless.api.pojo.enums.AggOption; 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.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.Database;
import com.tencent.supersonic.headless.core.pojo.QueryStatement; 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 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; 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 * 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()); EngineType.fromString(database.getType().toUpperCase()), database.getVersion());
sqlCommend.setSql(viewQueryParam.getSql()); sqlCommend.setSql(viewQueryParam.getSql());
sqlCommend.setTables(viewQueryParam.getTables()); sqlCommend.setTables(viewQueryParam.getTables());
sqlCommend.setVariables(viewQueryParam.getVariables());
sqlCommend.setSupportWith(viewQueryParam.isSupportWith()); sqlCommend.setSupportWith(viewQueryParam.isSupportWith());
} }

View File

@@ -1,20 +1,20 @@
package com.tencent.supersonic.headless.core.parser.converter; package com.tencent.supersonic.headless.core.parser.converter;
import com.tencent.supersonic.common.pojo.ColumnOrder; 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.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.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.pojo.QueryStatement;
import com.tencent.supersonic.headless.core.utils.SqlGenerateUtils; 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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* HeadlessConverter default implement * HeadlessConverter default implement
*/ */
@@ -59,8 +59,6 @@ public class ParserDefaultConverter implements HeadlessConverter {
metricQueryParam.setWhere(where); metricQueryParam.setWhere(where);
metricQueryParam.setOrder(queryParam.getOrders().stream() metricQueryParam.setOrder(queryParam.getOrders().stream()
.map(order -> new ColumnOrder(order.getColumn(), order.getDirection())).collect(Collectors.toList())); .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()); metricQueryParam.setLimit(queryParam.getLimit());
// support detail query // 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; package com.tencent.supersonic.headless.core.pojo;
import com.tencent.supersonic.common.pojo.ColumnOrder; import com.tencent.supersonic.common.pojo.ColumnOrder;
import java.util.List;
import java.util.Map;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
public class MetricQueryParam { public class MetricQueryParam {
private List<String> metrics; private List<String> metrics;
private List<String> dimensions; private List<String> dimensions;
private Map<String, String> variables;
private String where; private String where;
private Long limit; private Long limit;
private List<ColumnOrder> order; private List<ColumnOrder> order;

View File

@@ -1,24 +1,14 @@
package com.tencent.supersonic.headless.core.pojo; package com.tencent.supersonic.headless.core.pojo;
import com.tencent.supersonic.headless.api.pojo.MetricTable; import com.tencent.supersonic.headless.api.pojo.MetricTable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
public class ViewQueryParam { public class ViewQueryParam {
private Map<String, String> variables;
private String sql = ""; private String sql = "";
private List<MetricTable> tables; private List<MetricTable> tables;
private boolean supportWith = true; private boolean supportWith = true;
private boolean withAlias = 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.common.util.ContextUtils;
import com.tencent.supersonic.headless.core.executor.JdbcExecutor; import com.tencent.supersonic.headless.core.executor.JdbcExecutor;
import com.tencent.supersonic.headless.core.executor.QueryExecutor; 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.DetailQueryOptimizer;
import com.tencent.supersonic.headless.core.planner.QueryOptimizer; import com.tencent.supersonic.headless.core.planner.QueryOptimizer;
import com.tencent.supersonic.headless.core.parser.converter.HeadlessConverter; import com.tencent.supersonic.headless.core.parser.converter.HeadlessConverter;
@@ -83,6 +84,7 @@ public class ComponentFactory {
private static void initSemanticConverter() { private static void initSemanticConverter() {
headlessConverters.add(getBean("DefaultDimValueConverter", DefaultDimValueConverter.class)); headlessConverters.add(getBean("DefaultDimValueConverter", DefaultDimValueConverter.class));
headlessConverters.add(getBean("SqlVariableParseConverter", SqlVariableParseConverter.class));
headlessConverters.add(getBean("CalculateAggConverter", CalculateAggConverter.class)); headlessConverters.add(getBean("CalculateAggConverter", CalculateAggConverter.class));
headlessConverters.add(getBean("ParserDefaultConverter", ParserDefaultConverter.class)); headlessConverters.add(getBean("ParserDefaultConverter", ParserDefaultConverter.class));
} }

View File

@@ -30,13 +30,13 @@ public class SqlVariableParseUtils {
private static final char delimiter = '$'; private static final char delimiter = '$';
public static String parse(String sql, List<SqlVariable> sqlVariables, List<Param> params) { public static String parse(String sql, List<SqlVariable> sqlVariables, List<Param> params) {
Map<String, Object> variables = new HashMap<>();
if (CollectionUtils.isEmpty(sqlVariables)) { if (CollectionUtils.isEmpty(sqlVariables)) {
return sql; return sql;
} }
Map<String, Object> queryParams = new HashMap<>();
//1. handle default variable value //1. handle default variable value
sqlVariables.forEach(variable -> { sqlVariables.forEach(variable -> {
queryParams.put(variable.getName().trim(), variables.put(variable.getName().trim(),
getValues(variable.getValueType(), variable.getDefaultValues())); getValues(variable.getValueType(), variable.getDefaultValues()));
}); });
@@ -49,21 +49,25 @@ public class SqlVariableParseUtils {
List<SqlVariable> list = map.get(p.getName()); List<SqlVariable> list = map.get(p.getName());
if (!CollectionUtils.isEmpty(list)) { if (!CollectionUtils.isEmpty(list)) {
SqlVariable v = list.get(list.size() - 1); 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) { if (v instanceof List && ((List) v).size() > 0) {
v = ((List) v).stream().collect(Collectors.joining(COMMA)).toString(); 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); ST st = new ST(sql, delimiter, delimiter);
if (!CollectionUtils.isEmpty(queryParams)) { if (!CollectionUtils.isEmpty(variables)) {
queryParams.forEach(st::add); variables.forEach(st::add);
} }
return st.render(); return st.render();
} }

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.Identify;
import com.tencent.supersonic.headless.api.pojo.Measure; import com.tencent.supersonic.headless.api.pojo.Measure;
import com.tencent.supersonic.headless.api.pojo.ModelDetail; 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.DatabaseResp;
import com.tencent.supersonic.headless.api.pojo.response.ModelResp; import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
import com.tencent.supersonic.headless.core.adaptor.db.DbAdaptor; import com.tencent.supersonic.headless.core.adaptor.db.DbAdaptor;
@@ -46,7 +46,7 @@ public class ModelYamlManager {
.collect(Collectors.toList())); .collect(Collectors.toList()));
dataModelYamlTpl.setName(modelResp.getBizName()); dataModelYamlTpl.setName(modelResp.getBizName());
dataModelYamlTpl.setSourceId(modelResp.getDatabaseId()); dataModelYamlTpl.setSourceId(modelResp.getDatabaseId());
if (modelDetail.getQueryType().equalsIgnoreCase(DatasourceQuery.SQL_QUERY.getName())) { if (modelDetail.getQueryType().equalsIgnoreCase(ModelDefineType.SQL_QUERY.getName())) {
dataModelYamlTpl.setSqlQuery(modelDetail.getSqlQuery()); dataModelYamlTpl.setSqlQuery(modelDetail.getSqlQuery());
} else { } else {
dataModelYamlTpl.setTableQuery(modelDetail.getTableQuery()); dataModelYamlTpl.setTableQuery(modelDetail.getTableQuery());

View File

@@ -73,7 +73,7 @@ public class DatabaseController {
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response) { HttpServletResponse response) {
User user = UserHolder.findUser(request, 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}") @RequestMapping("/getDbNames/{id}")

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.auth.api.authentication.pojo.User;
import com.tencent.supersonic.headless.api.pojo.request.DatabaseReq; 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.DatabaseResp;
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp; import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp;
import com.tencent.supersonic.headless.server.pojo.DatabaseParameter; 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, DatabaseResp databaseResp);
SemanticQueryResp executeSql(String sql, Long id, User user); SemanticQueryResp executeSql(SqlExecuteReq sqlExecuteReq, Long id, User user);
DatabaseResp getDatabase(Long id, User user); DatabaseResp getDatabase(Long id, User user);

View File

@@ -1,8 +1,9 @@
package com.tencent.supersonic.headless.server.service.impl; 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.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.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.DatabaseResp;
import com.tencent.supersonic.headless.api.pojo.response.ModelResp; import com.tencent.supersonic.headless.api.pojo.response.ModelResp;
import com.tencent.supersonic.headless.api.pojo.response.SemanticQueryResp; 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.pojo.Database;
import com.tencent.supersonic.headless.core.utils.JdbcDataSourceUtils; import com.tencent.supersonic.headless.core.utils.JdbcDataSourceUtils;
import com.tencent.supersonic.headless.core.utils.SqlUtils; 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.dataobject.DatabaseDO;
import com.tencent.supersonic.headless.server.persistence.repository.DatabaseRepository; import com.tencent.supersonic.headless.server.persistence.repository.DatabaseRepository;
import com.tencent.supersonic.headless.server.pojo.DatabaseParameter; import com.tencent.supersonic.headless.server.pojo.DatabaseParameter;
@@ -116,32 +118,19 @@ public class DatabaseServiceImpl implements DatabaseService {
@Override @Override
public DatabaseResp getDatabase(Long id, User user) { public DatabaseResp getDatabase(Long id, User user) {
DatabaseResp databaseResp = getDatabase(id); DatabaseResp databaseResp = getDatabase(id);
if (!databaseResp.getAdmins().contains(user.getName()) checkPermission(databaseResp, user);
&& !databaseResp.getViewers().contains(user.getName())
&& !databaseResp.getCreatedBy().equals(user.getName())) {
throw new InvalidPermissionException("您暂无查看该数据库详情的权限, 请联系创建人: "
+ databaseResp.getCreatedBy());
}
return databaseResp; return databaseResp;
} }
@Override @Override
public SemanticQueryResp executeSql(String sql, Long id, User user) { public SemanticQueryResp executeSql(SqlExecuteReq sqlExecuteReq, Long id, User user) {
DatabaseResp databaseResp = getDatabase(id); DatabaseResp databaseResp = getDatabase(id);
if (databaseResp == null) { if (databaseResp == null) {
return new SemanticQueryResp(); return new SemanticQueryResp();
} }
List<String> admins = databaseResp.getAdmins(); checkPermission(databaseResp, user);
List<String> viewers = databaseResp.getViewers(); String sql = sqlExecuteReq.getSql();
if (!admins.contains(user.getName()) sql = SqlVariableParseUtils.parse(sql, sqlExecuteReq.getSqlVariables(), Lists.newArrayList());
&& !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);
}
return executeSql(sql, databaseResp); return executeSql(sql, databaseResp);
} }
@@ -195,4 +184,18 @@ public class DatabaseServiceImpl implements DatabaseService {
return queryWithColumns(metaQuerySql, DatabaseConverter.convert(databaseResp)); 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);
}
}
} }