mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
support foreign identify (#111)
* [improvement][semantic] add get metric agg function * [improvement][semantic] support foreign identify
This commit is contained in:
@@ -221,7 +221,7 @@ public class ModelServiceImpl implements ModelService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, String> getModelFullPathMap() {
|
public Map<Long, String> getModelFullPathMap() {
|
||||||
return getModelList().stream().collect(Collectors.toMap(ModelResp::getId,
|
return getModelList().stream().filter(m -> m != null).collect(Collectors.toMap(ModelResp::getId,
|
||||||
ModelResp::getFullPath, (k1, k2) -> k1));
|
ModelResp::getFullPath, (k1, k2) -> k1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import lombok.NoArgsConstructor;
|
|||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class Identify {
|
public class Identify {
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
PRIMARY, FOREIGN
|
||||||
|
}
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
// primary or foreign
|
// primary or foreign
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tencent.supersonic.semantic.query.parser.calcite.sql;
|
package com.tencent.supersonic.semantic.query.parser.calcite.sql;
|
||||||
|
|
||||||
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.DataSource;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -25,6 +26,8 @@ public class TableView {
|
|||||||
private String alias;
|
private String alias;
|
||||||
private List<String> primary;
|
private List<String> primary;
|
||||||
|
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
public SqlNode build() {
|
public SqlNode build() {
|
||||||
measure.addAll(dimension);
|
measure.addAll(dimension);
|
||||||
SqlNodeList dimensionNodeList = null;
|
SqlNodeList dimensionNodeList = null;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package com.tencent.supersonic.semantic.query.parser.calcite.sql.node;
|
package com.tencent.supersonic.semantic.query.parser.calcite.sql.node;
|
||||||
|
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify;
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify;
|
||||||
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify.Type;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.apache.calcite.sql.SqlNode;
|
import org.apache.calcite.sql.SqlNode;
|
||||||
import org.apache.calcite.sql.validate.SqlValidatorScope;
|
import org.apache.calcite.sql.validate.SqlValidatorScope;
|
||||||
|
|
||||||
@@ -9,4 +14,28 @@ public class IdentifyNode extends SemanticNode {
|
|||||||
public static SqlNode build(Identify identify, SqlValidatorScope scope) throws Exception {
|
public static SqlNode build(Identify identify, SqlValidatorScope scope) throws Exception {
|
||||||
return parse(identify.getName(), scope);
|
return parse(identify.getName(), scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Set<String> getIdentifyNames(List<Identify> identifies, Identify.Type type) {
|
||||||
|
return identifies.stream().filter(i -> type.name().equalsIgnoreCase(i.getType())).map(i -> i.getName())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isForeign(String name, List<Identify> identifies) {
|
||||||
|
Optional<Identify> identify = identifies.stream().filter(i -> i.getName().equalsIgnoreCase(name))
|
||||||
|
.findFirst();
|
||||||
|
if (identify.isPresent()) {
|
||||||
|
return Type.FOREIGN.name().equalsIgnoreCase(identify.get().getType());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPrimary(String name, List<Identify> identifies) {
|
||||||
|
Optional<Identify> identify = identifies.stream().filter(i -> i.getName().equalsIgnoreCase(name))
|
||||||
|
.findFirst();
|
||||||
|
if (identify.isPresent()) {
|
||||||
|
return Type.PRIMARY.name().equalsIgnoreCase(identify.get().getType());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Constants;
|
|||||||
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.DataSource;
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.DataSource;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Dimension;
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Dimension;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify;
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify;
|
||||||
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Identify.Type;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Metric;
|
import com.tencent.supersonic.semantic.query.parser.calcite.dsl.Metric;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.schema.SemanticSchema;
|
import com.tencent.supersonic.semantic.query.parser.calcite.schema.SemanticSchema;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.Renderer;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.Renderer;
|
||||||
@@ -12,15 +13,20 @@ import com.tencent.supersonic.semantic.query.parser.calcite.sql.TableView;
|
|||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.AggFunctionNode;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.AggFunctionNode;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DataSourceNode;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.DataSourceNode;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.FilterNode;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.FilterNode;
|
||||||
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.IdentifyNode;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.MetricNode;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.MetricNode;
|
||||||
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.SemanticNode;
|
import com.tencent.supersonic.semantic.query.parser.calcite.sql.node.SemanticNode;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -33,6 +39,7 @@ import org.apache.calcite.sql.SqlNode;
|
|||||||
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
|
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
|
||||||
import org.apache.calcite.sql.parser.SqlParserPos;
|
import org.apache.calcite.sql.parser.SqlParserPos;
|
||||||
import org.apache.calcite.sql.validate.SqlValidatorScope;
|
import org.apache.calcite.sql.validate.SqlValidatorScope;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JoinRender extends Renderer {
|
public class JoinRender extends Renderer {
|
||||||
@@ -41,6 +48,7 @@ public class JoinRender extends Renderer {
|
|||||||
public void render(MetricReq metricCommand, List<DataSource> dataSources, SqlValidatorScope scope,
|
public void render(MetricReq metricCommand, List<DataSource> dataSources, SqlValidatorScope scope,
|
||||||
SemanticSchema schema, boolean nonAgg) throws Exception {
|
SemanticSchema schema, boolean nonAgg) throws Exception {
|
||||||
String queryWhere = metricCommand.getWhere();
|
String queryWhere = metricCommand.getWhere();
|
||||||
|
dataSources = getOrderSource(dataSources);
|
||||||
Set<String> whereFields = new HashSet<>();
|
Set<String> whereFields = new HashSet<>();
|
||||||
List<String> fieldWhere = new ArrayList<>();
|
List<String> fieldWhere = new ArrayList<>();
|
||||||
if (queryWhere != null && !queryWhere.isEmpty()) {
|
if (queryWhere != null && !queryWhere.isEmpty()) {
|
||||||
@@ -95,6 +103,7 @@ public class JoinRender extends Renderer {
|
|||||||
String alias = Constants.JOIN_TABLE_PREFIX + dataSource.getName();
|
String alias = Constants.JOIN_TABLE_PREFIX + dataSource.getName();
|
||||||
tableView.setAlias(alias);
|
tableView.setAlias(alias);
|
||||||
tableView.setPrimary(primary);
|
tableView.setPrimary(primary);
|
||||||
|
tableView.setDataSource(dataSource);
|
||||||
if (left == null) {
|
if (left == null) {
|
||||||
leftTable = tableView;
|
leftTable = tableView;
|
||||||
left = SemanticNode.buildAs(tableView.getAlias(), getTable(tableView, scope));
|
left = SemanticNode.buildAs(tableView.getAlias(), getTable(tableView, scope));
|
||||||
@@ -246,7 +255,7 @@ public class JoinRender extends Renderer {
|
|||||||
|
|
||||||
private SqlNode getCondition(TableView left, TableView right, DataSource dataSource, SemanticSchema schema,
|
private SqlNode getCondition(TableView left, TableView right, DataSource dataSource, SemanticSchema schema,
|
||||||
SqlValidatorScope scope) throws Exception {
|
SqlValidatorScope scope) throws Exception {
|
||||||
log.info(left.getClass().toString());
|
|
||||||
Set<String> selectLeft = SemanticNode.getSelect(left.getTable());
|
Set<String> selectLeft = SemanticNode.getSelect(left.getTable());
|
||||||
Set<String> selectRight = SemanticNode.getSelect(right.getTable());
|
Set<String> selectRight = SemanticNode.getSelect(right.getTable());
|
||||||
selectLeft.retainAll(selectRight);
|
selectLeft.retainAll(selectRight);
|
||||||
@@ -255,6 +264,16 @@ public class JoinRender extends Renderer {
|
|||||||
if (!SourceRender.isDimension(on, dataSource, schema)) {
|
if (!SourceRender.isDimension(on, dataSource, schema)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (IdentifyNode.isForeign(on, left.getDataSource().getIdentifiers())) {
|
||||||
|
if (!IdentifyNode.isPrimary(on, right.getDataSource().getIdentifiers())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (IdentifyNode.isForeign(on, right.getDataSource().getIdentifiers())) {
|
||||||
|
if (!IdentifyNode.isPrimary(on, left.getDataSource().getIdentifiers())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
List<SqlNode> ons = new ArrayList<>();
|
List<SqlNode> ons = new ArrayList<>();
|
||||||
ons.add(SemanticNode.parse(left.getAlias() + "." + on, scope));
|
ons.add(SemanticNode.parse(left.getAlias() + "." + on, scope));
|
||||||
ons.add(SemanticNode.parse(right.getAlias() + "." + on, scope));
|
ons.add(SemanticNode.parse(right.getAlias() + "." + on, scope));
|
||||||
@@ -276,4 +295,85 @@ public class JoinRender extends Renderer {
|
|||||||
}
|
}
|
||||||
return condition;
|
return condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<DataSource> getOrderSource(List<DataSource> dataSources) throws Exception {
|
||||||
|
if (CollectionUtils.isEmpty(dataSources) || dataSources.size() <= 2) {
|
||||||
|
return dataSources;
|
||||||
|
}
|
||||||
|
Map<String, Set<String>> next = new HashMap<>();
|
||||||
|
Map<String, Boolean> visited = new HashMap<>();
|
||||||
|
Map<String, List<Identify>> dataSourceIdentifies = new HashMap<>();
|
||||||
|
dataSources.stream().forEach(d -> {
|
||||||
|
next.put(d.getName(), new HashSet<>());
|
||||||
|
visited.put(d.getName(), false);
|
||||||
|
dataSourceIdentifies.put(d.getName(), d.getIdentifiers());
|
||||||
|
});
|
||||||
|
int cnt = dataSources.size();
|
||||||
|
List<Map.Entry<String, List<Identify>>> dataSourceIdentifyList = dataSourceIdentifies.entrySet().stream()
|
||||||
|
.collect(
|
||||||
|
Collectors.toList());
|
||||||
|
for (int i = 0; i < cnt; i++) {
|
||||||
|
for (int j = i + 1; j < cnt; j++) {
|
||||||
|
Set<String> primaries = IdentifyNode.getIdentifyNames(dataSourceIdentifyList.get(i).getValue(),
|
||||||
|
Type.PRIMARY);
|
||||||
|
Set<String> foreign = IdentifyNode.getIdentifyNames(dataSourceIdentifyList.get(i).getValue(),
|
||||||
|
Type.FOREIGN);
|
||||||
|
Set<String> nextPrimaries = IdentifyNode.getIdentifyNames(dataSourceIdentifyList.get(j).getValue(),
|
||||||
|
Type.PRIMARY);
|
||||||
|
Set<String> nextForeign = IdentifyNode.getIdentifyNames(dataSourceIdentifyList.get(j).getValue(),
|
||||||
|
Type.FOREIGN);
|
||||||
|
Set<String> nextAll = new HashSet<>();
|
||||||
|
nextAll.addAll(nextPrimaries);
|
||||||
|
nextAll.addAll(nextForeign);
|
||||||
|
primaries.retainAll(nextPrimaries);
|
||||||
|
foreign.retainAll(nextPrimaries);
|
||||||
|
if (primaries.size() > 0 || foreign.size() > 0) {
|
||||||
|
next.get(dataSourceIdentifyList.get(i).getKey()).add(dataSourceIdentifyList.get(j).getKey());
|
||||||
|
next.get(dataSourceIdentifyList.get(j).getKey()).add(dataSourceIdentifyList.get(i).getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Queue<String> paths = new ArrayDeque<>();
|
||||||
|
for (String id : visited.keySet()) {
|
||||||
|
if (!visited.get(id)) {
|
||||||
|
joinOrder(cnt, id, next, paths, visited);
|
||||||
|
if (paths.size() >= cnt) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paths.size() < cnt) {
|
||||||
|
throw new Exception("datasource cant join,pls check identify :" + dataSources.stream()
|
||||||
|
.map(d -> d.getName()).collect(
|
||||||
|
Collectors.joining(",")));
|
||||||
|
}
|
||||||
|
List<String> orderList = new ArrayList<>(paths);
|
||||||
|
Collections.sort(dataSources, new Comparator<DataSource>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DataSource o1, DataSource o2) {
|
||||||
|
return orderList.indexOf(o1.getName()) - orderList.indexOf(o2.getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dataSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void joinOrder(int cnt, String id, Map<String, Set<String>> next, Queue<String> orders,
|
||||||
|
Map<String, Boolean> visited) {
|
||||||
|
visited.put(id, true);
|
||||||
|
orders.add(id);
|
||||||
|
if (orders.size() >= cnt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String nextId : next.get(id)) {
|
||||||
|
if (!visited.get(nextId)) {
|
||||||
|
joinOrder(cnt, nextId, next, orders, visited);
|
||||||
|
if (orders.size() >= cnt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orders.poll();
|
||||||
|
visited.put(id, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import static com.tencent.supersonic.common.pojo.Constants.PARENTHESES_START;
|
|||||||
import static com.tencent.supersonic.common.pojo.Constants.SPACE;
|
import static com.tencent.supersonic.common.pojo.Constants.SPACE;
|
||||||
import static com.tencent.supersonic.common.pojo.Constants.SYS_VAR;
|
import static com.tencent.supersonic.common.pojo.Constants.SYS_VAR;
|
||||||
|
|
||||||
|
import com.tencent.supersonic.common.pojo.Constants;
|
||||||
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
|
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
|
||||||
import com.tencent.supersonic.semantic.api.query.pojo.Criterion;
|
import com.tencent.supersonic.semantic.api.query.pojo.Criterion;
|
||||||
import com.tencent.supersonic.semantic.api.query.pojo.Filter;
|
import com.tencent.supersonic.semantic.api.query.pojo.Filter;
|
||||||
import com.tencent.supersonic.common.pojo.Constants;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -25,6 +25,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
public class SqlFilterUtils {
|
public class SqlFilterUtils {
|
||||||
|
|
||||||
private static String pattern = "^'.*?'$";
|
private static String pattern = "^'.*?'$";
|
||||||
|
private static String numericPattern = "^[0-9]+$";
|
||||||
|
|
||||||
public List<String> getFiltersCol(List<Filter> filters) {
|
public List<String> getFiltersCol(List<Filter> filters) {
|
||||||
List<String> filterCols = new ArrayList<>();
|
List<String> filterCols = new ArrayList<>();
|
||||||
@@ -219,7 +220,7 @@ public class SqlFilterUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String valueApostropheLogic(String value) {
|
private String valueApostropheLogic(String value) {
|
||||||
if (Pattern.matches(pattern, value)) {
|
if (Pattern.matches(pattern, value) || Pattern.matches(numericPattern, value)) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
return Constants.APOSTROPHE + value + Constants.APOSTROPHE;
|
return Constants.APOSTROPHE + value + Constants.APOSTROPHE;
|
||||||
|
|||||||
Reference in New Issue
Block a user