diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalSource.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalSource.java index cc7e88b3694a..2d6fce56a41c 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalSource.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalSource.java @@ -22,12 +22,15 @@ import org.apache.calcite.plan.GraphOptCluster; import org.apache.calcite.rel.RelWriter; import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rex.RexNode; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.List; +import java.util.Objects; public class GraphLogicalSource extends AbstractBindableTableScan { private final GraphOpt.Source opt; + private @Nullable RexNode uniqueKeyFilters; protected GraphLogicalSource( GraphOptCluster cluster, @@ -54,6 +57,16 @@ public GraphOpt.Source getOpt() { @Override public RelWriter explainTerms(RelWriter pw) { - return super.explainTerms(pw).item("opt", getOpt()); + return super.explainTerms(pw) + .item("opt", getOpt()) + .itemIf("uniqueKeyFilters", uniqueKeyFilters, uniqueKeyFilters != null); + } + + public void setUniqueKeyFilters(RexNode uniqueKeyFilters) { + this.uniqueKeyFilters = Objects.requireNonNull(uniqueKeyFilters); + } + + public @Nullable RexNode getUniqueKeyFilters() { + return uniqueKeyFilters; } } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/RexNodeTypeRefresher.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/RexNodeTypeRefresher.java deleted file mode 100644 index 458adb42abc6..000000000000 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/RexNodeTypeRefresher.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020 Alibaba Group Holding Limited. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.alibaba.graphscope.common.ir.rex; - -import com.alibaba.graphscope.common.ir.tools.GraphRexBuilder; - -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rex.*; - -public class RexNodeTypeRefresher extends RexVisitorImpl { - private final RelDataType newType; - private final GraphRexBuilder rexBuilder; - - public RexNodeTypeRefresher(RelDataType newType, GraphRexBuilder rexBuilder) { - super(false); - this.newType = newType; - this.rexBuilder = rexBuilder; - } - - @Override - public RexNode visitCall(RexCall call) { - return call; - } - - @Override - public RexNode visitLiteral(RexLiteral literal) { - return literal; - } - - @Override - public RexNode visitInputRef(RexInputRef inputRef) { - return inputRef; - } - - @Override - public RexNode visitDynamicParam(RexDynamicParam dynamicParam) { - if (dynamicParam instanceof RexGraphDynamicParam) { - return visitGraphDynamicParam((RexGraphDynamicParam) dynamicParam); - } else { - return dynamicParam; - } - } - - private RexNode visitGraphDynamicParam(RexGraphDynamicParam graphDynamicParam) { - return rexBuilder.makeGraphDynamicParam( - newType, graphDynamicParam.getName(), graphDynamicParam.getIndex()); - } -} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java new file mode 100644 index 000000000000..9d4e18ea81be --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.common.ir.rex.operator; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.fun.SqlMultisetValueConstructor; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.type.SqlTypeUtil; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +public class SqlArrayValueConstructor extends SqlMultisetValueConstructor { + public SqlArrayValueConstructor() { + super("ARRAY", SqlKind.ARRAY_VALUE_CONSTRUCTOR); + } + + @Override + public RelDataType inferReturnType(SqlOperatorBinding opBinding) { + RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); + List argTypes = opBinding.collectOperandTypes(); + RelDataType componentType = getComponentType(typeFactory, argTypes); + return SqlTypeUtil.createArrayType(typeFactory, componentType, false); + } + + // operands of array value constructor can be any, even if empty, i.e [] + @Override + public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) { + return true; + } + + @Override + protected @Nullable RelDataType getComponentType( + RelDataTypeFactory typeFactory, List argTypes) { + try { + RelDataType componentType = typeFactory.leastRestrictive(argTypes); + return (componentType == null) + ? typeFactory.createSqlType(SqlTypeName.ANY) + : componentType; + } catch (AssertionError e) { + return typeFactory.createSqlType(SqlTypeName.ANY); + } + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java new file mode 100644 index 000000000000..e57103c4fc46 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.common.ir.rex.operator; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.fun.SqlMultisetValueConstructor; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.type.SqlTypeUtil; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.Static; +import org.apache.calcite.util.Util; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +public class SqlMapValueConstructor extends SqlMultisetValueConstructor { + public SqlMapValueConstructor() { + super("MAP", SqlKind.MAP_VALUE_CONSTRUCTOR); + } + + public RelDataType inferReturnType(SqlOperatorBinding opBinding) { + RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); + List argTypes = opBinding.collectOperandTypes(); + Pair type = + Pair.of( + getComponentType(typeFactory, Util.quotientList(argTypes, 2, 0)), + getComponentType(typeFactory, Util.quotientList(argTypes, 2, 1))); + return SqlTypeUtil.createMapType(opBinding.getTypeFactory(), type.left, type.right, false); + } + + @Override + public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) { + List argTypes = callBinding.collectOperandTypes(); + if (argTypes.size() % 2 > 0) { + throw callBinding.newValidationError(Static.RESOURCE.mapRequiresEvenArgCount()); + } + return true; + } + + @Override + protected @Nullable RelDataType getComponentType( + RelDataTypeFactory typeFactory, List argTypes) { + try { + RelDataType componentType = typeFactory.leastRestrictive(argTypes); + return (componentType == null) + ? typeFactory.createSqlType(SqlTypeName.ANY) + : componentType; + } catch (AssertionError e) { + return typeFactory.createSqlType(SqlTypeName.ANY); + } + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/FfiPhysicalBuilder.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/FfiPhysicalBuilder.java index 9216217d96da..b05ce9b46651 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/FfiPhysicalBuilder.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/FfiPhysicalBuilder.java @@ -68,7 +68,8 @@ public FfiPhysicalBuilder( Configs graphConfig, IrMeta irMeta, LogicalPlan logicalPlan, PlanPointer planPointer) { super( logicalPlan, - new GraphRelShuttleWrapper(new RelToFfiConverter(irMeta.getSchema().isColumnId()))); + new GraphRelShuttleWrapper( + new RelToFfiConverter(irMeta.getSchema().isColumnId(), graphConfig))); this.graphConfig = graphConfig; this.irMeta = irMeta; this.planPointer = Objects.requireNonNull(planPointer); diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/RelToFfiConverter.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/RelToFfiConverter.java index f9efd30c80c9..02332052ab5f 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/RelToFfiConverter.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/ffi/RelToFfiConverter.java @@ -16,6 +16,7 @@ package com.alibaba.graphscope.common.ir.runtime.ffi; +import com.alibaba.graphscope.common.config.Configs; import com.alibaba.graphscope.common.intermediate.ArgUtils; import com.alibaba.graphscope.common.ir.rel.GraphLogicalAggregate; import com.alibaba.graphscope.common.ir.rel.GraphLogicalProject; @@ -31,8 +32,10 @@ import com.alibaba.graphscope.common.ir.runtime.proto.RexToProtoConverter; import com.alibaba.graphscope.common.ir.runtime.type.PhysicalNode; import com.alibaba.graphscope.common.ir.tools.AliasInference; +import com.alibaba.graphscope.common.ir.tools.GraphPlanner; import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; import com.alibaba.graphscope.common.ir.type.GraphLabelType; +import com.alibaba.graphscope.common.ir.type.GraphNameOrId; import com.alibaba.graphscope.common.ir.type.GraphProperty; import com.alibaba.graphscope.common.ir.type.GraphSchemaType; import com.alibaba.graphscope.common.jna.IrCoreLibrary; @@ -49,17 +52,13 @@ import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalJoin; import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.rex.RexLiteral; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.rex.RexVariable; +import org.apache.calcite.rex.*; import org.apache.calcite.sql.SqlKind; import org.apache.commons.lang3.ObjectUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -72,9 +71,11 @@ public class RelToFfiConverter implements GraphRelShuttle { private static final Logger logger = LoggerFactory.getLogger(RelToFfiConverter.class); private static final IrCoreLibrary LIB = IrCoreLibrary.INSTANCE; private final boolean isColumnId; + private final RexBuilder rexBuilder; - public RelToFfiConverter(boolean isColumnId) { + public RelToFfiConverter(boolean isColumnId, Configs configs) { this.isColumnId = isColumnId; + this.rexBuilder = GraphPlanner.rexBuilderFactory.apply(configs); } @Override @@ -205,7 +206,9 @@ public RelNode visit(GraphLogicalMultiMatch match) { @Override public RelNode visit(LogicalFilter logicalFilter) { OuterExpression.Expression exprProto = - logicalFilter.getCondition().accept(new RexToProtoConverter(true, isColumnId)); + logicalFilter + .getCondition() + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)); Pointer ptrFilter = LIB.initSelectOperator(); checkFfiResult( LIB.setSelectPredicatePb( @@ -219,7 +222,9 @@ public PhysicalNode visit(GraphLogicalProject project) { List fields = project.getRowType().getFieldList(); for (int i = 0; i < project.getProjects().size(); ++i) { OuterExpression.Expression expression = - project.getProjects().get(i).accept(new RexToProtoConverter(true, isColumnId)); + project.getProjects() + .get(i) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)); int aliasId = fields.get(i).getIndex(); FfiAlias.ByValue ffiAlias = (aliasId == AliasInference.DEFAULT_ID) @@ -260,7 +265,7 @@ public PhysicalNode visit(GraphLogicalAggregate aggregate) { RexGraphVariable.class, var.getClass()); OuterExpression.Expression expr = - var.accept(new RexToProtoConverter(true, isColumnId)); + var.accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)); int aliasId; if (i >= fields.size() || (aliasId = fields.get(i).getIndex()) == AliasInference.DEFAULT_ID) { @@ -283,7 +288,7 @@ public PhysicalNode visit(GraphLogicalAggregate aggregate) { field.getName(), field.getType()); OuterExpression.Variable exprVar = - rexVar.accept(new RexToProtoConverter(true, isColumnId)) + rexVar.accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); checkFfiResult( @@ -305,7 +310,7 @@ public PhysicalNode visit(GraphLogicalAggregate aggregate) { OuterExpression.Variable var = groupKeys .get(i) - .accept(new RexToProtoConverter(true, isColumnId)) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); int aliasId = fields.get(i).getIndex(); @@ -339,7 +344,7 @@ public PhysicalNode visit(GraphLogicalAggregate aggregate) { operands.get(0).getClass()); OuterExpression.Variable var = operands.get(0) - .accept(new RexToProtoConverter(true, isColumnId)) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); checkFfiResult( @@ -369,7 +374,7 @@ public PhysicalNode visit(GraphLogicalSort sort) { for (int i = 0; i < collations.size(); ++i) { RexGraphVariable expr = ((GraphFieldCollation) collations.get(i)).getVariable(); OuterExpression.Variable var = - expr.accept(new RexToProtoConverter(true, isColumnId)) + expr.accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); checkFfiResult( @@ -405,13 +410,13 @@ public PhysicalNode visit(LogicalJoin join) { OuterExpression.Variable leftVar = leftRightVars .get(0) - .accept(new RexToProtoConverter(true, isColumnId)) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); OuterExpression.Variable rightVar = leftRightVars .get(1) - .accept(new RexToProtoConverter(true, isColumnId)) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)) .getOperators(0) .getVar(); checkFfiResult( @@ -451,7 +456,10 @@ private Pointer ffiQueryParams(AbstractBindableTableScan tableScan) { }); if (ObjectUtils.isNotEmpty(tableScan.getFilters())) { OuterExpression.Expression expression = - tableScan.getFilters().get(0).accept(new RexToProtoConverter(true, isColumnId)); + tableScan + .getFilters() + .get(0) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)); checkFfiResult( LIB.setParamsPredicatePb( params, new FfiPbPointer.ByValue(expression.toByteArray()))); @@ -460,46 +468,85 @@ private Pointer ffiQueryParams(AbstractBindableTableScan tableScan) { } private @Nullable Pointer ffiIndexPredicates(GraphLogicalSource source) { - ImmutableList filters = source.getFilters(); - if (ObjectUtils.isEmpty(filters)) return null; - // decomposed by OR - List disJunctions = RelOptUtil.disjunctions(filters.get(0)); - List literals = new ArrayList<>(); - for (RexNode rexNode : disJunctions) { - if (!isIdEqualsLiteral(rexNode, literals)) { - return null; - } - } + RexNode uniqueKeyFilters = source.getUniqueKeyFilters(); + if (uniqueKeyFilters == null) return null; + // 'within' operator in index predicate is unsupported in ir core, here just expand it to + // 'or' + // i.e. '~id within [1, 2]' -> '~id == 1 or ~id == 2' + RexNode expandSearch = RexUtil.expandSearch(this.rexBuilder, null, uniqueKeyFilters); + List disjunctions = RelOptUtil.disjunctions(expandSearch); Pointer ptrIndex = LIB.initIndexPredicate(); - for (RexNode literal : literals) { - FfiProperty.ByValue property = new FfiProperty.ByValue(); - property.opt = FfiPropertyOpt.Id; - checkFfiResult( - LIB.orEquivPredicate(ptrIndex, property, Utils.ffiConst((RexLiteral) literal))); + for (RexNode disjunction : disjunctions) { + if (disjunction instanceof RexCall) { + RexCall rexCall = (RexCall) disjunction; + switch (rexCall.getOperator().getKind()) { + case EQUALS: + RexNode left = rexCall.getOperands().get(0); + RexNode right = rexCall.getOperands().get(1); + if (left instanceof RexGraphVariable + && (right instanceof RexLiteral + || right instanceof RexDynamicParam)) { + LIB.orEquivPredicate( + ptrIndex, + getFfiProperty(((RexGraphVariable) left).getProperty()), + getFfiConst(right)); + break; + } else if (right instanceof RexGraphVariable + && (left instanceof RexLiteral + || left instanceof RexDynamicParam)) { + LIB.orEquivPredicate( + ptrIndex, + getFfiProperty(((RexGraphVariable) right).getProperty()), + getFfiConst(left)); + break; + } + default: + throw new IllegalArgumentException( + "can not convert unique key filter pattern=" + + rexCall + + " to ir core index predicate"); + } + } else { + throw new IllegalArgumentException( + "invalid unique key filter pattern=" + disjunction); + } } - // remove index predicates from filter conditions - source.setFilters(ImmutableList.of()); return ptrIndex; } - // i.e. a.~id == 10 - private boolean isIdEqualsLiteral(RexNode rexNode, List literal) { - if (rexNode.getKind() != SqlKind.EQUALS) return false; - List operands = ((RexCall) rexNode).getOperands(); - return isGlobalId(operands.get(0)) && isLiteral(operands.get(1), literal) - || isGlobalId(operands.get(1)) && isLiteral(operands.get(0), literal); + private FfiProperty.ByValue getFfiProperty(GraphProperty property) { + Preconditions.checkArgument(property != null, "unique key should not be null"); + FfiProperty.ByValue ffiProperty = new FfiProperty.ByValue(); + switch (property.getOpt()) { + case ID: + ffiProperty.opt = FfiPropertyOpt.Id; + break; + case KEY: + ffiProperty.opt = FfiPropertyOpt.Key; + ffiProperty.key = getFfiNameOrId(property.getKey()); + break; + default: + throw new IllegalArgumentException( + "can not convert property=" + property + " to ffi property"); + } + return ffiProperty; } - // i.e. a.~id - private boolean isGlobalId(RexNode rexNode) { - return rexNode instanceof RexGraphVariable - && ((RexGraphVariable) rexNode).getProperty().getOpt() == GraphProperty.Opt.ID; + private FfiNameOrId.ByValue getFfiNameOrId(GraphNameOrId nameOrId) { + switch (nameOrId.getOpt()) { + case NAME: + return ArgUtils.asNameOrId(nameOrId.getName()); + case ID: + default: + return ArgUtils.asNameOrId(nameOrId.getId()); + } } - private boolean isLiteral(RexNode rexNode, List literal) { - boolean isLiteral = rexNode.getKind() == SqlKind.LITERAL; - if (isLiteral) literal.add(rexNode); - return isLiteral; + private FfiConst.ByValue getFfiConst(RexNode rexNode) { + if (rexNode instanceof RexLiteral) { + return Utils.ffiConst((RexLiteral) rexNode); + } + throw new IllegalArgumentException("cannot convert rexNode=" + rexNode + " to ffi const"); } private List range(RexNode offset, RexNode fetch) { @@ -592,7 +639,8 @@ private void addFilterToFfiBinder(Pointer ptrSentence, AbstractBindableTableScan List filters = tableScan.getFilters(); if (ObjectUtils.isNotEmpty(filters)) { OuterExpression.Expression exprProto = - filters.get(0).accept(new RexToProtoConverter(true, isColumnId)); + filters.get(0) + .accept(new RexToProtoConverter(true, isColumnId, this.rexBuilder)); Pointer ptrFilter = LIB.initSelectOperator(); checkFfiResult( LIB.setSelectPredicatePb( diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java index 3e57e4e77d3f..220074c03e5d 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java @@ -27,6 +27,7 @@ import org.apache.calcite.rex.*; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.util.Sarg; import java.util.List; @@ -35,10 +36,12 @@ */ public class RexToProtoConverter extends RexVisitorImpl { private final boolean isColumnId; + private final RexBuilder rexBuilder; - public RexToProtoConverter(boolean deep, boolean isColumnId) { + public RexToProtoConverter(boolean deep, boolean isColumnId, RexBuilder rexBuilder) { super(deep); this.isColumnId = isColumnId; + this.rexBuilder = rexBuilder; } @Override @@ -166,6 +169,26 @@ private OuterExpression.Expression visitIsNullOperator(RexNode operand) { } private OuterExpression.Expression visitBinaryOperator(RexCall call) { + if (call.getOperator().getKind() == SqlKind.SEARCH) { + // ir core can not support continuous ranges in a search operator, here expand it to + // compositions of 'and' or 'or', + // i.e. a.age SEARCH [[1, 10]] -> a.age >= 1 and a.age <= 10 + RexNode left = call.getOperands().get(0); + RexNode right = call.getOperands().get(1); + RexLiteral literal = null; + if (left instanceof RexLiteral) { + literal = (RexLiteral) left; + } else if (right instanceof RexLiteral) { + literal = (RexLiteral) right; + } + if (literal != null && literal.getValue() instanceof Sarg) { + Sarg sarg = (Sarg) literal.getValue(); + // search continuous ranges + if (!sarg.isPoints()) { + call = (RexCall) RexUtil.expandSearch(this.rexBuilder, null, call); + } + } + } SqlOperator operator = call.getOperator(); OuterExpression.Expression.Builder exprBuilder = OuterExpression.Expression.newBuilder(); // left-associative diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java index 1245a82bb6d6..7345336353a0 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java @@ -17,7 +17,10 @@ package com.alibaba.graphscope.common.ir.runtime.proto; import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; -import com.alibaba.graphscope.common.ir.type.*; +import com.alibaba.graphscope.common.ir.type.GraphLabelType; +import com.alibaba.graphscope.common.ir.type.GraphNameOrId; +import com.alibaba.graphscope.common.ir.type.GraphProperty; +import com.alibaba.graphscope.common.ir.type.GraphSchemaType; import com.alibaba.graphscope.gaia.proto.Common; import com.alibaba.graphscope.gaia.proto.DataType; import com.alibaba.graphscope.gaia.proto.GraphAlgebra; @@ -31,6 +34,7 @@ import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.util.NlsString; +import org.apache.calcite.util.Sarg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,8 +73,54 @@ public static final Common.Value protoValue(RexLiteral literal) { return Common.Value.newBuilder() .setF64(((Number) literal.getValue()).doubleValue()) .build(); + case SARG: + Sarg sarg = literal.getValueAs(Sarg.class); + if (sarg.isPoints()) { + Common.Value.Builder valueBuilder = Common.Value.newBuilder(); + List values = + com.alibaba.graphscope.common.ir.tools.Utils.getValuesAsList(sarg); + if (values.isEmpty()) { + // return an empty string array to handle the case i.e. within [] + return valueBuilder + .setStrArray(Common.StringArray.newBuilder().build()) + .build(); + } + Comparable first = values.get(0); + if (first instanceof Integer) { + Common.I32Array.Builder i32Array = Common.I32Array.newBuilder(); + values.forEach(value -> i32Array.addItem((Integer) value)); + valueBuilder.setI32Array(i32Array); + } else if (first instanceof Number) { + Common.I64Array.Builder i64Array = Common.I64Array.newBuilder(); + values.forEach(value -> i64Array.addItem(((Number) value).longValue())); + valueBuilder.setI64Array(i64Array); + } else if (first instanceof Double || first instanceof Float) { + Common.DoubleArray.Builder doubleArray = Common.DoubleArray.newBuilder(); + values.forEach( + value -> + doubleArray.addItem( + (value instanceof Float) + ? (Float) value + : (Double) value)); + valueBuilder.setF64Array(doubleArray); + } else if (first instanceof String || first instanceof NlsString) { + Common.StringArray.Builder stringArray = Common.StringArray.newBuilder(); + values.forEach( + value -> + stringArray.addItem( + (value instanceof NlsString) + ? ((NlsString) value).getValue() + : (String) value)); + valueBuilder.setStrArray(stringArray); + } else { + throw new UnsupportedOperationException( + "can not convert value list=" + values + " to ir core array"); + } + return valueBuilder.build(); + } + throw new UnsupportedOperationException( + "can not convert continuous ranges to ir core structure, sarg=" + sarg); default: - // TODO: support int/double/string array throw new UnsupportedOperationException( "literal type " + literal.getTypeName() + " is unsupported yet"); } @@ -180,8 +230,11 @@ public static final OuterExpression.ExprOpr protoOperator(SqlOperator operator) return OuterExpression.ExprOpr.newBuilder() .setLogical(OuterExpression.Logical.ISNULL) .build(); + case SEARCH: + return OuterExpression.ExprOpr.newBuilder() + .setLogical(OuterExpression.Logical.WITHIN) + .build(); default: - // TODO: support IN and NOT_IN throw new UnsupportedOperationException( "operator type=" + operator.getKind() diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/AliasInference.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/AliasInference.java index 33eb338b677c..177f83d17b61 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/AliasInference.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/AliasInference.java @@ -176,7 +176,9 @@ public static final Set getUniqueAliasList(@Nullable RelNode input, bool while (!inputsQueue.isEmpty()) { RelNode cur = inputsQueue.remove(0); for (RelDataTypeField field : cur.getRowType().getFieldList()) { - uniqueNames.add(field.getName()); + if (field.getName() != null && field.getName() != DEFAULT_NAME) { + uniqueNames.add(field.getName()); + } } if (removeAlias(cur)) { break; diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphBuilder.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphBuilder.java index 6f3d9b3e2f98..d52d233838f6 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphBuilder.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphBuilder.java @@ -35,11 +35,9 @@ import com.alibaba.graphscope.common.ir.rex.*; import com.alibaba.graphscope.common.ir.rex.RexCallBinding; import com.alibaba.graphscope.common.ir.tools.config.*; -import com.alibaba.graphscope.common.ir.type.GraphNameOrId; -import com.alibaba.graphscope.common.ir.type.GraphPathType; -import com.alibaba.graphscope.common.ir.type.GraphProperty; -import com.alibaba.graphscope.common.ir.type.GraphSchemaType; +import com.alibaba.graphscope.common.ir.type.*; import com.alibaba.graphscope.gremlin.Utils; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; @@ -61,11 +59,13 @@ import org.apache.calcite.tools.RelBuilder; import org.apache.calcite.util.Litmus; import org.apache.calcite.util.Pair; +import org.apache.calcite.util.Sarg; import org.apache.commons.lang3.ObjectUtils; import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -581,7 +581,14 @@ private RexNode call_(SqlOperator operator, List operandList) { // derive return type RelDataType returnType = operator.inferReturnType(callBinding); // derive unknown types of operands - operandList = inferOperandTypes(operator, returnType, operandList); + operandList = + inferOperandTypes(operator, returnType, convertOperands(operator, operandList)); + final RexBuilder builder = cluster.getRexBuilder(); + return builder.makeCall(returnType, operator, operandList); + } + + // convert operands of the operator in some special cases + private List convertOperands(SqlOperator operator, List operandList) { if (operator.getKind() == SqlKind.EXTRACT) { RexNode intervalOperand = operandList.get(0); if (intervalOperand instanceof RexLiteral @@ -593,11 +600,11 @@ private RexNode call_(SqlOperator operator, List operandList) { getRexBuilder() .makeFlag(intervalType.getIntervalQualifier().getStartUnit())); newOperands.add(operandList.get(1)); - operandList = newOperands; + return newOperands; } } - final RexBuilder builder = cluster.getRexBuilder(); - return builder.makeCall(returnType, operator, operandList); + // todo: convert label names to ids + return operandList; } private List inferOperandTypes( @@ -612,10 +619,16 @@ private List inferOperandTypes( List typeInferredOperands = new ArrayList<>(operandList.size()); GraphRexBuilder rexBuilder = (GraphRexBuilder) this.getRexBuilder(); for (int i = 0; i < operandList.size(); ++i) { - typeInferredOperands.add( - operandList - .get(i) - .accept(new RexNodeTypeRefresher(newTypes[i], rexBuilder))); + RexNode rexNode = operandList.get(i); + if (rexNode instanceof RexGraphDynamicParam) { + RexGraphDynamicParam graphDynamicParam = (RexGraphDynamicParam) rexNode; + rexNode = + rexBuilder.makeGraphDynamicParam( + newTypes[i], + graphDynamicParam.getName(), + graphDynamicParam.getIndex()); + } + typeInferredOperands.add(rexNode); } return typeInferredOperands; } else { @@ -636,9 +649,12 @@ private boolean isCurrentSupported(SqlOperator operator) { || (sqlKind == SqlKind.PROCEDURE_CALL) || (sqlKind == SqlKind.NOT) || sqlKind == SqlKind.ARRAY_VALUE_CONSTRUCTOR + || sqlKind == SqlKind.MAP_VALUE_CONSTRUCTOR || sqlKind == SqlKind.IS_NULL || sqlKind == SqlKind.IS_NOT_NULL - || sqlKind == SqlKind.EXTRACT; + || sqlKind == SqlKind.EXTRACT + || sqlKind == SqlKind.SEARCH + || sqlKind == SqlKind.POSIX_REGEX_CASE_SENSITIVE; } @Override @@ -680,23 +696,245 @@ public GraphBuilder filter(Iterable conditions) { AliasInference.SIMPLE_NAME(AliasInference.DEFAULT_NAME), AliasInference.DEFAULT_ID)); // add condition into table scan - if (ObjectUtils.isEmpty(tableScan.getFilters())) { - tableScan.setFilters(ImmutableList.of(condition)); - } else { - ImmutableList.Builder listBuilder = new ImmutableList.Builder(); - listBuilder.addAll(tableScan.getFilters()).add(condition); - tableScan.setFilters( - ImmutableList.of( - RexUtil.composeConjunction( - this.getRexBuilder(), listBuilder.build()))); - } // pop the filter from the inner stack - replaceTop(tableScan); + replaceTop(fuseFilters(tableScan, condition)); } } return builder; } + private AbstractBindableTableScan fuseFilters( + AbstractBindableTableScan tableScan, RexNode condition) { + List labelValues = Lists.newArrayList(); + List uniqueKeyFilters = Lists.newArrayList(); + List extraFilters = Lists.newArrayList(); + classifyFilters(tableScan, condition, labelValues, uniqueKeyFilters, extraFilters); + if (!labelValues.isEmpty()) { + GraphLabelType labelType = + ((GraphSchemaType) tableScan.getRowType().getFieldList().get(0).getType()) + .getLabelType(); + List labelsToKeep = + labelType.getLabelsEntry().stream() + .filter(k -> labelValues.contains(k.getLabel())) + .map(k -> k.getLabel()) + .collect(Collectors.toList()); + Preconditions.checkArgument( + !labelsToKeep.isEmpty(), + "cannot find common labels between values= " + labelValues + " and label=", + labelType); + if (labelsToKeep.size() < labelType.getLabelsEntry().size()) { + GraphBuilder builder = + (GraphBuilder) + GraphPlanner.relBuilderFactory.create( + getCluster(), getRelOptSchema()); + LabelConfig newLabelConfig = new LabelConfig(false); + labelsToKeep.forEach(k -> newLabelConfig.addLabel(k)); + if (tableScan instanceof GraphLogicalSource) { + builder.source( + new SourceConfig( + ((GraphLogicalSource) tableScan).getOpt(), + newLabelConfig, + tableScan.getAliasName())); + } else if (tableScan instanceof GraphLogicalExpand) { + ((GraphBuilder) builder.push(tableScan.getInput(0))) + .expand( + new ExpandConfig( + ((GraphLogicalExpand) tableScan).getOpt(), + newLabelConfig, + tableScan.getAliasName())); + } else if (tableScan instanceof GraphLogicalGetV) { + ((GraphBuilder) builder.push(tableScan.getInput(0))) + .getV( + new GetVConfig( + ((GraphLogicalGetV) tableScan).getOpt(), + newLabelConfig, + tableScan.getAliasName())); + } + if (builder.size() > 0) { + // check if the property still exist after updating the label type + RexVisitor propertyChecker = + new RexVisitorImpl(true) { + @Override + public Void visitInputRef(RexInputRef inputRef) { + if (inputRef instanceof RexGraphVariable) { + RexGraphVariable variable = (RexGraphVariable) inputRef; + String[] splits = + variable.getName() + .split( + Pattern.quote( + AliasInference.DELIMITER)); + if (splits.length > 1) { + builder.variable(null, splits[1]); + } + } + return null; + } + }; + if (tableScan instanceof GraphLogicalSource) { + RexNode originalUniqueKeyFilters = + ((GraphLogicalSource) tableScan).getUniqueKeyFilters(); + if (originalUniqueKeyFilters != null) { + originalUniqueKeyFilters.accept(propertyChecker); + builder.filter(originalUniqueKeyFilters); + } + } + ImmutableList originalFilters = tableScan.getFilters(); + if (ObjectUtils.isNotEmpty(originalFilters)) { + originalFilters.forEach(k -> ((RexNode) k).accept(propertyChecker)); + builder.filter(originalFilters); + } + if (!extraFilters.isEmpty()) { + extraFilters.forEach(k -> k.accept(propertyChecker)); + } + tableScan = (AbstractBindableTableScan) builder.build(); + } + } + } + if (tableScan instanceof GraphLogicalSource && !uniqueKeyFilters.isEmpty()) { + GraphLogicalSource source = (GraphLogicalSource) tableScan; + Preconditions.checkArgument( + source.getUniqueKeyFilters() == null, + "can not add unique key filters if original is not empty"); + source.setUniqueKeyFilters( + RexUtil.composeDisjunction(this.getRexBuilder(), uniqueKeyFilters)); + } + if (!extraFilters.isEmpty()) { + ImmutableList originalFilters = tableScan.getFilters(); + if (ObjectUtils.isNotEmpty(originalFilters)) { + for (int i = 0; i < originalFilters.size(); ++i) { + extraFilters.add(i, (RexNode) originalFilters.get(i)); + } + } + tableScan.setFilters( + ImmutableList.of( + RexUtil.composeConjunction(this.getRexBuilder(), extraFilters))); + } + return tableScan; + } + + private void classifyFilters( + AbstractBindableTableScan tableScan, + RexNode condition, + List labelValues, + List uniqueKeyFilters, // unique key filters int the list are composed by 'OR' + List filters) { + List conjunctions = RelOptUtil.conjunctions(condition); + List filtersToRemove = Lists.newArrayList(); + for (RexNode conjunction : conjunctions) { + RexLiteral labelLiteral = isLabelEqualFilter(conjunction); + if (labelLiteral != null) { + filtersToRemove.add(conjunction); + labelValues.addAll( + com.alibaba.graphscope.common.ir.tools.Utils.getValuesAsList( + labelLiteral.getValueAs(Comparable.class))); + break; + } + } + if (tableScan instanceof GraphLogicalSource + && ((GraphLogicalSource) tableScan).getUniqueKeyFilters() == null) { + // try to extract unique key filters from the original condition + List disjunctions = RelOptUtil.disjunctions(condition); + for (RexNode disjunction : disjunctions) { + if (isUniqueKeyEqualFilter(disjunction)) { + filtersToRemove.add(disjunction); + uniqueKeyFilters.add(disjunction); + } + } + } + if (!filtersToRemove.isEmpty()) { + conjunctions.removeAll(filtersToRemove); + } + filters.addAll(conjunctions); + } + + // check the condition if it is the pattern of label equal filter, i.e. ~label = 'person' or + // ~label within ['person', 'software'] + // if it is then return the literal containing label values, otherwise null + private @Nullable RexLiteral isLabelEqualFilter(RexNode condition) { + if (condition instanceof RexCall) { + RexCall rexCall = (RexCall) condition; + SqlOperator operator = rexCall.getOperator(); + switch (operator.getKind()) { + case EQUALS: + case SEARCH: + RexNode left = rexCall.getOperands().get(0); + RexNode right = rexCall.getOperands().get(1); + if (left.getType() instanceof GraphLabelType && right instanceof RexLiteral) { + Comparable value = ((RexLiteral) right).getValue(); + // if Sarg is a continuous range then the filter is not the 'equal', i.e. + // ~label SEARCH [[1, 10]] which means ~label >= 1 and ~label <= 10 + if (value instanceof Sarg && !((Sarg) value).isPoints()) { + return null; + } + return (RexLiteral) right; + } else if (right.getType() instanceof GraphLabelType + && left instanceof RexLiteral) { + Comparable value = ((RexLiteral) left).getValue(); + if (value instanceof Sarg && !((Sarg) value).isPoints()) { + return null; + } + return (RexLiteral) left; + } + default: + return null; + } + } else { + return null; + } + } + + // check the condition if it is the pattern of unique key equal filter, i.e. ~id = 1 or ~id + // within [1, 2] + private boolean isUniqueKeyEqualFilter(RexNode condition) { + if (condition instanceof RexCall) { + RexCall rexCall = (RexCall) condition; + SqlOperator operator = rexCall.getOperator(); + switch (operator.getKind()) { + case EQUALS: + case SEARCH: + RexNode left = rexCall.getOperands().get(0); + RexNode right = rexCall.getOperands().get(1); + if (isUniqueKey(left) && isLiteralOrDynamicParams(right)) { + if (right instanceof RexLiteral) { + Comparable value = ((RexLiteral) right).getValue(); + // if Sarg is a continuous range then the filter is not the 'equal', + // i.e. ~id SEARCH [[1, 10]] which means ~id >= 1 and ~id <= 10 + if (value instanceof Sarg && !((Sarg) value).isPoints()) { + return false; + } + } + return true; + } else if (isUniqueKey(right) && isLiteralOrDynamicParams(left)) { + if (left instanceof RexLiteral) { + Comparable value = ((RexLiteral) left).getValue(); + if (value instanceof Sarg && !((Sarg) value).isPoints()) { + return false; + } + } + return true; + } + default: + return false; + } + } else { + return false; + } + } + + private boolean isUniqueKey(RexNode rexNode) { + // todo: support primary keys + if (rexNode instanceof RexGraphVariable) { + RexGraphVariable variable = (RexGraphVariable) rexNode; + return variable.getProperty() != null + && variable.getProperty().getOpt() == GraphProperty.Opt.ID; + } + return false; + } + + private boolean isLiteralOrDynamicParams(RexNode node) { + return node instanceof RexLiteral || node instanceof RexDynamicParam; + } + // return the top node if its type is Filter, otherwise null private Filter topFilter() { if (this.size() > 0 && this.peek() instanceof Filter) { diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphPlanner.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphPlanner.java index 80e85957ee58..3cba4882d53a 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphPlanner.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphPlanner.java @@ -61,6 +61,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; /** * A unified structure to build {@link PlannerInstance} which can further build logical and physical plan from an antlr tree @@ -70,18 +71,20 @@ public class GraphPlanner { private final Configs graphConfig; private final PlannerConfig plannerConfig; private final RelOptPlanner optPlanner; - private final RexBuilder rexBuilder; private final AtomicLong idGenerator; - private static final RelBuilderFactory relBuilderFactory = + private final RexBuilder rexBuilder; + public static final RelBuilderFactory relBuilderFactory = (RelOptCluster cluster, @Nullable RelOptSchema schema) -> GraphBuilder.create(null, (GraphOptCluster) cluster, schema); + public static final Function rexBuilderFactory = + (Configs configs) -> new GraphRexBuilder(new GraphTypeFactoryImpl(configs)); public GraphPlanner(Configs graphConfig) { this.graphConfig = graphConfig; this.plannerConfig = PlannerConfig.create(this.graphConfig); logger.debug("planner config: " + this.plannerConfig); this.optPlanner = createRelOptPlanner(this.plannerConfig); - this.rexBuilder = new GraphRexBuilder(new GraphTypeFactoryImpl(graphConfig)); + this.rexBuilder = rexBuilderFactory.apply(graphConfig); this.idGenerator = new AtomicLong(FrontendConfig.FRONTEND_SERVER_ID.get(graphConfig)); } @@ -258,6 +261,7 @@ public static void main(String[] args) throws Exception { // write physical plan to file try (PhysicalBuilder physicalBuilder = summary.getPhysicalBuilder()) { FileUtils.writeByteArrayToFile(new File(args[2]), physicalBuilder.build()); + physicalBuilder.explain(); } // write stored procedure meta to file LogicalPlan logicalPlan = summary.getLogicalPlan(); diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphStdOperatorTable.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphStdOperatorTable.java index 1959fa4571fb..93906e12c463 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphStdOperatorTable.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/GraphStdOperatorTable.java @@ -18,8 +18,11 @@ import com.alibaba.graphscope.common.ir.meta.procedure.StoredProcedureMeta; import com.alibaba.graphscope.common.ir.rex.operator.CaseOperator; +import com.alibaba.graphscope.common.ir.rex.operator.SqlArrayValueConstructor; +import com.alibaba.graphscope.common.ir.rex.operator.SqlMapValueConstructor; import org.apache.calcite.sql.*; +import org.apache.calcite.sql.fun.ExtSqlPosixRegexOperator; import org.apache.calcite.sql.fun.SqlMonotonicBinaryOperator; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.type.*; @@ -208,15 +211,7 @@ public static final SqlFunction USER_DEFINED_PROCEDURE(StoredProcedureMeta meta) } // combine multiple expressions into a list - public static final SqlOperator ARRAY_VALUE_CONSTRUCTOR = - new SqlSpecialOperator( - "ARRAY_VALUE_CONSTRUCTOR", - SqlKind.ARRAY_VALUE_CONSTRUCTOR, - 0, - false, - ReturnTypes.explicit(SqlTypeName.ANY).andThen(SqlTypeTransforms.TO_ARRAY), - GraphInferTypes.FIRST_KNOWN, - OperandTypes.ANY); + public static final SqlOperator ARRAY_VALUE_CONSTRUCTOR = new SqlArrayValueConstructor(); public static final SqlFunction EXTRACT = new SqlFunction( @@ -226,4 +221,10 @@ public static final SqlFunction USER_DEFINED_PROCEDURE(StoredProcedureMeta meta) null, GraphOperandTypes.INTERVALINTERVAL_INTERVALDATETIME, SqlFunctionCategory.SYSTEM); + + public static final SqlOperator MAP_VALUE_CONSTRUCTOR = new SqlMapValueConstructor(); + + public static final SqlOperator POSIX_REGEX_CASE_SENSITIVE = + new ExtSqlPosixRegexOperator( + "POSIX REGEX CASE SENSITIVE", SqlKind.POSIX_REGEX_CASE_SENSITIVE, true, false); } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/Utils.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/Utils.java index 701cc3ebb337..ea970934c9fd 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/Utils.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/Utils.java @@ -16,7 +16,9 @@ package com.alibaba.graphscope.common.ir.tools; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.collect.Range; import com.google.common.collect.Sets; import org.apache.calcite.rel.RelNode; @@ -24,6 +26,8 @@ import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.rel.type.StructKind; +import org.apache.calcite.util.NlsString; +import org.apache.calcite.util.Sarg; import java.util.List; import java.util.Set; @@ -53,4 +57,22 @@ public static RelDataType getOutputType(RelNode topNode) { .collect(Collectors.toList()); return new RelRecordType(StructKind.FULLY_QUALIFIED, dedup); } + + public static List getValuesAsList(Comparable value) { + ImmutableList.Builder valueBuilder = ImmutableList.builder(); + if (value instanceof NlsString) { + valueBuilder.add(((NlsString) value).getValue()); + } else if (value instanceof Sarg) { + Sarg sarg = (Sarg) value; + if (sarg.isPoints()) { + Set> rangeSets = sarg.rangeSet.asRanges(); + for (Range range : rangeSets) { + valueBuilder.addAll(getValuesAsList(range.lowerEndpoint())); + } + } + } else { + valueBuilder.add(value); + } + return valueBuilder.build(); + } } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/parser/GremlinAntlr4Parser.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/parser/GremlinAntlr4Parser.java new file mode 100644 index 000000000000..24c8c718427a --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/parser/GremlinAntlr4Parser.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x.parser; + +import com.alibaba.graphscope.common.antlr4.Antlr4Parser; +import com.alibaba.graphscope.common.antlr4.SyntaxErrorListener; +import com.alibaba.graphscope.grammar.GremlinGSLexer; +import com.alibaba.graphscope.grammar.GremlinGSParser; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.DefaultErrorStrategy; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.tree.ParseTree; + +/** + * parse gremlin DSL to antlr tree + */ +public class GremlinAntlr4Parser implements Antlr4Parser { + @Override + public ParseTree parse(String statement) { + GremlinGSLexer lexer = new GremlinGSLexer(CharStreams.fromString(statement)); + // reset error listeners on lexer + lexer.removeErrorListeners(); + lexer.addErrorListener(new SyntaxErrorListener()); + final GremlinGSParser parser = new GremlinGSParser(new CommonTokenStream(lexer)); + // setup error handler on parser + parser.setErrorHandler(new DefaultErrorStrategy()); + // reset error listeners on parser + parser.removeErrorListeners(); + parser.addErrorListener(new SyntaxErrorListener()); + parser.getInterpreter().setPredictionMode(PredictionMode.LL); + return parser.query(); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/ExpressionVisitor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/ExpressionVisitor.java new file mode 100644 index 000000000000..5ff8402684d1 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/ExpressionVisitor.java @@ -0,0 +1,265 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x.visitor; + +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.GraphStdOperatorTable; +import com.alibaba.graphscope.grammar.GremlinGSBaseVisitor; +import com.alibaba.graphscope.grammar.GremlinGSParser; +import com.alibaba.graphscope.gremlin.antlr4.GenericLiteralVisitor; +import com.alibaba.graphscope.gremlin.exception.UnsupportedEvalException; + +import org.apache.calcite.rex.RexNode; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ExpressionVisitor extends GremlinGSBaseVisitor { + private final RexNode propertyKey; + private final GraphBuilder builder; + + public ExpressionVisitor(GraphBuilder builder, RexNode propertyKey) { + this.builder = Objects.requireNonNull(builder); + this.propertyKey = Objects.requireNonNull(propertyKey); + } + + @Override + public RexNode visitTraversalPredicate(GremlinGSParser.TraversalPredicateContext ctx) { + switch (ctx.getChildCount()) { + case 1: + // handle simple predicate + return visitChildren(ctx); + case 6: + final int childIndexOfParameterOperator = 2; + final int childIndexOfCaller = 0; + final int childIndexOfArgument = 4; + + if (ctx.getChild(childIndexOfParameterOperator).getText().equals("or")) { + // handle or + return builder.call( + GraphStdOperatorTable.OR, + visit(ctx.getChild(childIndexOfCaller)), + visit(ctx.getChild(childIndexOfArgument))); + } else { + // handle and + return builder.call( + GraphStdOperatorTable.AND, + visit(ctx.getChild(childIndexOfCaller)), + visit(ctx.getChild(childIndexOfArgument))); + } + default: + throw new UnsupportedEvalException( + ctx.getClass(), + "unexpected number of children in TraversalPredicateContext " + + ctx.getChildCount()); + } + } + + @Override + public RexNode visitTraversalPredicate_eq(GremlinGSParser.TraversalPredicate_eqContext ctx) { + return builder.call( + GraphStdOperatorTable.EQUALS, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_neq(GremlinGSParser.TraversalPredicate_neqContext ctx) { + return builder.call( + GraphStdOperatorTable.NOT_EQUALS, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_lt(GremlinGSParser.TraversalPredicate_ltContext ctx) { + return builder.call( + GraphStdOperatorTable.LESS_THAN, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_lte(GremlinGSParser.TraversalPredicate_lteContext ctx) { + return builder.call( + GraphStdOperatorTable.LESS_THAN_OR_EQUAL, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_gt(GremlinGSParser.TraversalPredicate_gtContext ctx) { + return builder.call( + GraphStdOperatorTable.GREATER_THAN, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_gte(GremlinGSParser.TraversalPredicate_gteContext ctx) { + return builder.call( + GraphStdOperatorTable.GREATER_THAN_OR_EQUAL, + propertyKey, + builder.literal( + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral()))); + } + + @Override + public RexNode visitTraversalPredicate_within( + GremlinGSParser.TraversalPredicate_withinContext ctx) { + Object[] points = GenericLiteralVisitor.getGenericLiteralList(ctx.genericLiteralList()); + return builder.getRexBuilder() + .makeIn( + propertyKey, + Arrays.asList(points).stream() + .map(k -> builder.literal(k)) + .collect(Collectors.toList())); + } + + @Override + public RexNode visitTraversalPredicate_without( + GremlinGSParser.TraversalPredicate_withoutContext ctx) { + Object[] points = GenericLiteralVisitor.getGenericLiteralList(ctx.genericLiteralList()); + return builder.not( + builder.getRexBuilder() + .makeIn( + propertyKey, + Arrays.asList(points).stream() + .map(k -> builder.literal(k)) + .collect(Collectors.toList()))); + } + + @Override + public RexNode visitTraversalPredicate_not(GremlinGSParser.TraversalPredicate_notContext ctx) { + return builder.not(visitChildren(ctx)); + } + + @Override + public RexNode visitTraversalPredicate_inside( + GremlinGSParser.TraversalPredicate_insideContext ctx) { + Number lower = + (Number) + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral(0)); + Number upper = + (Number) + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral(1)); + return builder.getRexBuilder() + .makeBetween( + propertyKey, + builder.literal(new BigDecimal(lower.longValue() + 1)), + builder.literal(new BigDecimal(upper.longValue() - 1))); + } + + @Override + public RexNode visitTraversalPredicate_outside( + GremlinGSParser.TraversalPredicate_outsideContext ctx) { + Number lower = + (Number) + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral(0)); + Number upper = + (Number) + GenericLiteralVisitor.getInstance() + .visitGenericLiteral(ctx.genericLiteral(1)); + return builder.not( + builder.getRexBuilder() + .makeBetween( + propertyKey, + builder.literal(new BigDecimal(lower.longValue() + 1)), + builder.literal(new BigDecimal(upper.longValue() - 1)))); + } + + @Override + public RexNode visitTraversalPredicate_startingWith( + GremlinGSParser.TraversalPredicate_startingWithContext ctx) { + String posixRegex = "^" + GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()); + return builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex)); + } + + @Override + public RexNode visitTraversalPredicate_notStartingWith( + GremlinGSParser.TraversalPredicate_notStartingWithContext ctx) { + String posixRegex = "^" + GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()); + return builder.not( + builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex))); + } + + @Override + public RexNode visitTraversalPredicate_endingWith( + GremlinGSParser.TraversalPredicate_endingWithContext ctx) { + String posixRegex = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()) + "$"; + return builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex)); + } + + @Override + public RexNode visitTraversalPredicate_notEndingWith( + GremlinGSParser.TraversalPredicate_notEndingWithContext ctx) { + String posixRegex = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()) + "$"; + return builder.not( + builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex))); + } + + @Override + public RexNode visitTraversalPredicate_containing( + GremlinGSParser.TraversalPredicate_containingContext ctx) { + String posixRegex = + ".*" + GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()) + "*."; + return builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex)); + } + + @Override + public RexNode visitTraversalPredicate_notContaining( + GremlinGSParser.TraversalPredicate_notContainingContext ctx) { + String posixRegex = + ".*" + GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()) + "*."; + return builder.not( + builder.call( + GraphStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE, + propertyKey, + builder.literal(posixRegex))); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/GraphBuilderVisitor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/GraphBuilderVisitor.java new file mode 100644 index 000000000000..16128fa91b16 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/GraphBuilderVisitor.java @@ -0,0 +1,663 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x.visitor; + +import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalExpand; +import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalPathExpand; +import com.alibaba.graphscope.common.ir.rex.RexGraphVariable; +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.GraphStdOperatorTable; +import com.alibaba.graphscope.common.ir.tools.config.*; +import com.alibaba.graphscope.common.ir.type.GraphProperty; +import com.alibaba.graphscope.common.ir.type.GraphSchemaType; +import com.alibaba.graphscope.grammar.GremlinGSBaseVisitor; +import com.alibaba.graphscope.grammar.GremlinGSParser; +import com.alibaba.graphscope.gremlin.antlr4.GenericLiteralVisitor; +import com.alibaba.graphscope.gremlin.antlr4.TraversalEnumParser; +import com.alibaba.graphscope.gremlin.exception.InvalidGremlinScriptException; +import com.alibaba.graphscope.gremlin.exception.UnsupportedEvalException; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexNode; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.tinkerpop.gremlin.structure.Column; +import org.apache.tinkerpop.gremlin.structure.T; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GraphBuilderVisitor extends GremlinGSBaseVisitor { + private final GraphBuilder builder; + + public GraphBuilderVisitor(GraphBuilder builder) { + this.builder = builder; + } + + @Override + public GraphBuilder visitTraversalSourceSpawnMethod_V( + GremlinGSParser.TraversalSourceSpawnMethod_VContext ctx) { + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalSourceSpawnMethodContext) + ctx.getParent())))); + if (ctx.integerLiteralList() != null) { + Object[] ids = GenericLiteralVisitor.getIntegerLiteralList(ctx.integerLiteralList()); + if (ids.length == 1) { + return builder.filter( + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.ID_KEY), + builder.literal(ids[0]))); + } else if (ids.length > 1) { + List literals = + Arrays.asList(ids).stream() + .map(k -> builder.literal(k)) + .collect(Collectors.toList()); + return builder.filter( + builder.getRexBuilder() + .makeIn(builder.variable(null, GraphProperty.ID_KEY), literals)); + } + } + return builder; + } + + @Override + public GraphBuilder visitTraversalSourceSpawnMethod_E( + GremlinGSParser.TraversalSourceSpawnMethod_EContext ctx) { + builder.source( + new SourceConfig( + GraphOpt.Source.EDGE, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalSourceSpawnMethodContext) + ctx.getParent())))); + if (ctx.integerLiteralList() != null) { + Object[] ids = GenericLiteralVisitor.getIntegerLiteralList(ctx.integerLiteralList()); + if (ids.length == 1) { + return builder.filter( + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.ID_KEY), + builder.literal(ids[0]))); + } else if (ids.length > 1) { + List literals = + Arrays.asList(ids).stream() + .map(k -> builder.literal(k)) + .collect(Collectors.toList()); + return builder.filter( + builder.getRexBuilder() + .makeIn(builder.variable(null, GraphProperty.ID_KEY), literals)); + } + } + return builder; + } + + @Override + public GraphBuilder visitTraversalMethod_hasLabel( + GremlinGSParser.TraversalMethod_hasLabelContext ctx) { + if (ctx.stringLiteral() != null) { + String label = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()); + if (ObjectUtils.isEmpty(ctx.stringLiteralList())) { + return builder.filter( + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.LABEL_KEY), + builder.literal(label))); + } else { + List labelNodes = Lists.newArrayList(builder.literal(label)); + String[] otherLabels = + GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + for (String other : otherLabels) { + labelNodes.add(builder.literal(other)); + } + RexNode labelFilters = + builder.getRexBuilder() + .makeIn( + builder.variable(null, GraphProperty.LABEL_KEY), + labelNodes); + return builder.filter(labelFilters); + } + } + throw new UnsupportedEvalException( + ctx.getClass(), "supported pattern is [hasLabel('str')] or hasLabel('str1', ...)"); + } + + @Override + public GraphBuilder visitTraversalMethod_hasId( + GremlinGSParser.TraversalMethod_hasIdContext ctx) { + Object[] ids = + GenericLiteralVisitor.getIntegerLiteralExpr( + ctx.nonEmptyIntegerLiteralList().integerLiteralExpr()); + if (ids.length == 1) { + return builder.filter( + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.ID_KEY), + builder.literal(ids[0]))); + } else if (ids.length > 1) { + List idNodes = + Arrays.asList(ids).stream() + .map(k -> builder.literal(k)) + .collect(Collectors.toList()); + RexNode idFilters = + builder.getRexBuilder() + .makeIn(builder.variable(null, GraphProperty.ID_KEY), idNodes); + return builder.filter(idFilters); + } + throw new UnsupportedEvalException( + ctx.getClass(), "supported pattern is [hasId(1)] or hasId(1, 2, ...)"); + } + + @Override + public GraphBuilder visitTraversalMethod_has(GremlinGSParser.TraversalMethod_hasContext ctx) { + String notice = + "supported pattern is [has('key', 'value')] or [has('key', P)] or [has('label'," + + " 'key', 'value')] or [has('label', 'key', P)]"; + int childCount = ctx.getChildCount(); + if (childCount == 6 && ctx.genericLiteral() != null) { // g.V().has("name", "marko") + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(0)); + Object propertyValue = + GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral()); + return builder.filter( + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, propertyKey), + builder.literal(propertyValue))); + } else if (childCount == 6 + && ctx.traversalPredicate() != null) { // g.V().has("name", P.eq("marko")) + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(0)); + ExpressionVisitor exprVisitor = + new ExpressionVisitor(this.builder, builder.variable(null, propertyKey)); + return builder.filter(exprVisitor.visitTraversalPredicate(ctx.traversalPredicate())); + } else if (childCount == 8 + && ctx.genericLiteral() != null) { // g.V().has("person", "name", "marko") + String labelValue = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(0)); + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(1)); + Object propertyValue = + GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral()); + return builder.filter( + builder.call( + GraphStdOperatorTable.AND, + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.LABEL_KEY), + builder.literal(labelValue)), + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, propertyKey), + builder.literal(propertyValue)))); + } else if (childCount == 8 + && ctx.traversalPredicate() != null) { // g.V().has("person", "name", P.eq("marko")) + String labelValue = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(0)); + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(1)); + ExpressionVisitor exprVisitor = + new ExpressionVisitor(this.builder, builder.variable(null, propertyKey)); + return builder.filter( + builder.call( + GraphStdOperatorTable.AND, + builder.call( + GraphStdOperatorTable.EQUALS, + builder.variable(null, GraphProperty.LABEL_KEY), + builder.literal(labelValue)), + exprVisitor.visitTraversalPredicate(ctx.traversalPredicate()))); + } else if (childCount == 4 && ctx.stringLiteral() != null) { // g.V().has("name") + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral(0)); + return builder.filter( + builder.call( + GraphStdOperatorTable.IS_NOT_NULL, + builder.variable(null, propertyKey))); + } else { + throw new UnsupportedEvalException(ctx.getClass(), notice); + } + } + + @Override + public GraphBuilder visitTraversalMethod_hasNot( + GremlinGSParser.TraversalMethod_hasNotContext ctx) { + // g.V().hasNot("name") + String propertyKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()); + return builder.filter( + builder.call(GraphStdOperatorTable.IS_NULL, builder.variable(null, propertyKey))); + } + + @Override + public GraphBuilder visitTraversalMethod_outE(GremlinGSParser.TraversalMethod_outEContext ctx) { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + getLabelConfig(ctx.stringLiteralList()), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_inE(GremlinGSParser.TraversalMethod_inEContext ctx) { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.IN, + getLabelConfig(ctx.stringLiteralList()), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_bothE( + GremlinGSParser.TraversalMethod_bothEContext ctx) { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.BOTH, + getLabelConfig(ctx.stringLiteralList()), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_outV(GremlinGSParser.TraversalMethod_outVContext ctx) { + return builder.getV( + new GetVConfig( + GraphOpt.GetV.START, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_inV(GremlinGSParser.TraversalMethod_inVContext ctx) { + return builder.getV( + new GetVConfig( + GraphOpt.GetV.END, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_otherV( + GremlinGSParser.TraversalMethod_otherVContext ctx) { + return builder.getV( + new GetVConfig( + GraphOpt.GetV.OTHER, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + + @Override + public GraphBuilder visitTraversalMethod_endV(GremlinGSParser.TraversalMethod_endVContext ctx) { + RelNode peek = builder.peek(); + if (peek instanceof GraphLogicalPathExpand) { + GraphLogicalPathExpand pathExpand = (GraphLogicalPathExpand) peek; + GraphLogicalExpand expand = (GraphLogicalExpand) pathExpand.getExpand(); + String tag = + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + switch (expand.getOpt()) { + case OUT: + return builder.getV( + new GetVConfig(GraphOpt.GetV.END, new LabelConfig(true), tag)); + case IN: + return builder.getV( + new GetVConfig(GraphOpt.GetV.START, new LabelConfig(true), tag)); + case BOTH: + default: + return builder.getV( + new GetVConfig(GraphOpt.GetV.OTHER, new LabelConfig(true), tag)); + } + } + throw new InvalidGremlinScriptException("endV should follow with path expand"); + } + + @Override + public GraphBuilder visitTraversalMethod_out(GremlinGSParser.TraversalMethod_outContext ctx) { + if (pathExpandPattern(ctx.stringLiteralList())) { + return builder.pathExpand( + new PathExpandBuilderVisitor(this).visitTraversalMethod_out(ctx).build()); + } else { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.OUT, getLabelConfig(ctx.stringLiteralList()))) + .getV( + new GetVConfig( + GraphOpt.GetV.END, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + } + + @Override + public GraphBuilder visitTraversalMethod_in(GremlinGSParser.TraversalMethod_inContext ctx) { + if (pathExpandPattern(ctx.stringLiteralList())) { + return builder.pathExpand( + new PathExpandBuilderVisitor(this).visitTraversalMethod_in(ctx).build()); + } else { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.IN, getLabelConfig(ctx.stringLiteralList()))) + .getV( + new GetVConfig( + GraphOpt.GetV.START, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + } + + @Override + public GraphBuilder visitTraversalMethod_both(GremlinGSParser.TraversalMethod_bothContext ctx) { + if (pathExpandPattern(ctx.stringLiteralList())) { + return builder.pathExpand( + new PathExpandBuilderVisitor(this).visitTraversalMethod_both(ctx).build()); + } else { + return builder.expand( + new ExpandConfig( + GraphOpt.Expand.BOTH, getLabelConfig(ctx.stringLiteralList()))) + .getV( + new GetVConfig( + GraphOpt.GetV.OTHER, + new LabelConfig(true), + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) + ctx.getParent())))); + } + } + + @Override + public GraphBuilder visitTraversalMethod_with(GremlinGSParser.TraversalMethod_withContext ctx) { + return builder; + } + + @Override + public GraphBuilder visitTraversalMethod_as(GremlinGSParser.TraversalMethod_asContext ctx) { + return builder; + } + + @Override + public GraphBuilder visitTraversalMethod_valueMap( + GremlinGSParser.TraversalMethod_valueMapContext ctx) { + RexNode expr = + builder.call( + GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, + getProperties(ctx, null).stream() + .flatMap( + k -> + Stream.of( + builder.literal(k), + builder.variable(null, k))) + .collect(Collectors.toList())); + String tag = + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.project( + ImmutableList.of(expr), + tag == null ? ImmutableList.of() : ImmutableList.of(tag), + true); + } + + @Override + public GraphBuilder visitTraversalMethod_values( + GremlinGSParser.TraversalMethod_valuesContext ctx) { + if (ctx.getChildCount() == 4 && ctx.stringLiteral() != null) { + RexNode expr = + builder.variable( + null, GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral())); + String tag = + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.project( + ImmutableList.of(expr), + tag == null ? ImmutableList.of() : ImmutableList.of(tag), + true); + } + throw new UnsupportedEvalException(ctx.getClass(), "supported pattern is [values('..')]"); + } + + @Override + public GraphBuilder visitTraversalMethod_elementMap( + GremlinGSParser.TraversalMethod_elementMapContext ctx) { + RexNode expr = + builder.call( + GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, + getProperties(ctx, null).stream() + .flatMap( + k -> + Stream.of( + builder.literal(k), + builder.variable(null, k))) + .collect(Collectors.toList())); + String tag = + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.project( + ImmutableList.of(expr), + tag == null ? ImmutableList.of() : ImmutableList.of(tag), + true); + } + + @Override + public GraphBuilder visitTraversalMethod_select( + GremlinGSParser.TraversalMethod_selectContext ctx) { + String tag = + getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + RexNode expr; + if (ctx.stringLiteral() != null) { + List selectTags = + Lists.newArrayList(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral())); + if (ctx.stringLiteralList() != null) { + String[] tags = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + selectTags.addAll(Arrays.asList(tags)); + } + GremlinGSParser.TraversalMethod_selectby_listContext listCtx = + ctx.traversalMethod_selectby_list(); + List byCtxs = + listCtx == null + ? ImmutableList.of() + : listCtx.getRuleContexts( + GremlinGSParser.TraversalMethod_selectbyContext.class); + Map keyValueMap = Maps.newLinkedHashMap(); + for (int i = 0; i < selectTags.size(); ++i) { + String selectTag = selectTags.get(i); + keyValueMap.put(selectTag, convertSelectByCtx(byCtxs, i, selectTag)); + } + Preconditions.checkArgument( + !keyValueMap.isEmpty(), "keyValue should not be empty in select"); + + if (keyValueMap.size() == 1) { + expr = keyValueMap.entrySet().iterator().next().getValue(); + } else { + List mapParameters = Lists.newArrayList(); + keyValueMap.forEach( + (k, v) -> { + mapParameters.add(builder.literal(k)); + mapParameters.add(v); + }); + expr = builder.call(GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, mapParameters); + } + } else if (ctx.traversalColumn() != null) { + Column column = + TraversalEnumParser.parseTraversalEnumFromContext( + Column.class, ctx.traversalColumn()); + expr = builder.variable(column.name()); + } else { + throw new UnsupportedEvalException( + GremlinGSParser.TraversalMethod_selectbyContext.class, + ctx.getText() + " is unsupported yet"); + } + return builder.project( + ImmutableList.of(expr), + tag == null ? ImmutableList.of() : ImmutableList.of(tag), + true); + } + + public GraphBuilder getGraphBuilder() { + return this.builder; + } + + private RexNode convertSelectByCtx( + List byCtxs, int i, String tag) { + int ctxCnt = byCtxs.size(); + if (ctxCnt == 0) { + return builder.variable(tag); + } + GremlinGSParser.TraversalMethod_selectbyContext byCtx = byCtxs.get(i % ctxCnt); + int byChildCount = byCtx.getChildCount(); + if (byChildCount == 3) { // select(..).by() + return builder.variable(tag); + } else if (byChildCount == 4 && byCtx.stringLiteral() != null) { // select(..).by('name') + return builder.variable( + tag, GenericLiteralVisitor.getStringLiteral(byCtx.stringLiteral())); + } else if (byChildCount == 4 + && byCtx.traversalToken() != null) { // select(..).by(T.label/T.id) + T token = + TraversalEnumParser.parseTraversalEnumFromContext( + T.class, byCtx.traversalToken()); + return builder.variable(tag, token.getAccessor()); + } else if (byCtx.traversalMethod_valueMap() != null) { // select(..).by(valueMap('name')) + return builder.call( + GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, + getProperties(byCtx.traversalMethod_valueMap(), tag).stream() + .flatMap(k -> Stream.of(builder.literal(k), builder.variable(tag, k))) + .collect(Collectors.toList())); + } else if (byCtx.traversalMethod_elementMap() + != null) { // select(..).by(elementMap('name')) + return builder.call( + GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, + getProperties(byCtx.traversalMethod_elementMap(), tag).stream() + .flatMap(k -> Stream.of(builder.literal(k), builder.variable(tag, k))) + .collect(Collectors.toList())); + } + throw new UnsupportedEvalException( + GremlinGSParser.TraversalMethod_selectbyContext.class, + byCtx.getText() + " is unsupported yet in select"); + } + + private boolean pathExpandPattern(GremlinGSParser.StringLiteralListContext ctx) { + String[] labels = GenericLiteralVisitor.getStringLiteralList(ctx); + return labels != null && labels.length > 0 && rangeExpression(labels[0]); + } + + private boolean rangeExpression(String label) { + return label.matches("^\\d+\\.\\.\\d+"); + } + + private LabelConfig getLabelConfig(GremlinGSParser.StringLiteralListContext ctx) { + String[] labels = GenericLiteralVisitor.getStringLiteralList(ctx); + if (labels == null || labels.length == 0) { + return new LabelConfig(true); + } else { + LabelConfig labelConfig = new LabelConfig(false); + for (int i = 0; i < labels.length; ++i) { + labelConfig.addLabel(labels[i]); + } + return labelConfig; + } + } + + private List getProperties( + GremlinGSParser.TraversalMethod_valueMapContext ctx, @Nullable String tag) { + String[] properties = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + return (properties == null || properties.length == 0) + ? getAllProperties(tag) + : Arrays.asList(properties); + } + + private List getProperties( + GremlinGSParser.TraversalMethod_elementMapContext ctx, @Nullable String tag) { + String[] properties = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + List propertyList = + Lists.newArrayList(GraphProperty.LABEL_KEY, GraphProperty.ID_KEY); + if (properties == null || properties.length == 0) { + propertyList.addAll(getAllProperties(tag)); + } else { + propertyList.addAll(Arrays.asList(properties)); + } + return propertyList; + } + + private List getAllProperties(@Nullable String tag) { + RexGraphVariable curVar = builder.variable(tag); + RelDataType dataType = curVar.getType(); + Preconditions.checkArgument( + dataType instanceof GraphSchemaType, "can not get property from type=", dataType); + return dataType.getFieldList().stream().map(k -> k.getName()).collect(Collectors.toList()); + } + + protected @Nullable String getNextTag(TraversalMethodIterator methodIterator) { + List tags = Lists.newArrayList(); + while (methodIterator.hasNext()) { + GremlinGSParser.TraversalMethodContext next = methodIterator.next(); + if (next.traversalMethod_as() != null) { + tags.add( + GenericLiteralVisitor.getStringLiteral( + next.traversalMethod_as().stringLiteral())); + } else if (next.traversalMethod_has() != null + || next.traversalMethod_hasId() != null + || next.traversalMethod_hasLabel() != null + || next.traversalMethod_hasNot() != null + || next.traversalMethod_is() != null + || next.traversalMethod_where() != null + || next.traversalMethod_not() != null + || next.traversalMethod_identity() != null + || next.traversalMethod_limit() != null + || next.traversalMethod_order() != null + || next.traversalMethod_dedup() != null + || next.traversalMethod_with() != null) { + // continue + } else { + break; + } + } + return tags.isEmpty() ? null : tags.get(tags.size() - 1); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/PathExpandBuilderVisitor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/PathExpandBuilderVisitor.java new file mode 100644 index 000000000000..aec29f295d64 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/PathExpandBuilderVisitor.java @@ -0,0 +1,157 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x.visitor; + +import com.alibaba.graphscope.common.ir.tools.config.*; +import com.alibaba.graphscope.grammar.GremlinGSBaseVisitor; +import com.alibaba.graphscope.grammar.GremlinGSParser; +import com.alibaba.graphscope.gremlin.Utils; +import com.alibaba.graphscope.gremlin.antlr4.GenericLiteralVisitor; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Objects; + +public class PathExpandBuilderVisitor extends GremlinGSBaseVisitor { + private final GraphBuilderVisitor parent; + private final PathExpandConfig.Builder builder; + + public PathExpandBuilderVisitor(GraphBuilderVisitor parent) { + this.parent = Objects.requireNonNull(parent); + // PATH_OPT = ARBITRARY and RESULT_OPT = END_V are set by default + this.builder = PathExpandConfig.newBuilder(parent.getGraphBuilder()); + } + + @Override + public PathExpandConfig.Builder visitTraversalMethod_out( + GremlinGSParser.TraversalMethod_outContext ctx) { + String[] labels = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + Preconditions.checkArgument( + labels != null && labels.length > 0, "arguments can not be empty in path expand"); + String[] ranges = labels[0].split("\\.\\."); + int lower = Integer.valueOf(ranges[0]); + int upper = Integer.valueOf(ranges[1]); + // set path_opt and result_opt + List nextWith = + getNextWith((GremlinGSParser.TraversalMethodContext) ctx.getParent()); + nextWith.forEach(k -> visitTraversalMethod_with(k)); + // set expand config, getV config, range, alias + String alias = + parent.getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.expand(new ExpandConfig(GraphOpt.Expand.OUT, getExpandLabelConfig(labels))) + .getV(new GetVConfig(GraphOpt.GetV.END)) + .range(lower, upper - lower) + .alias(alias); + } + + @Override + public PathExpandConfig.Builder visitTraversalMethod_in( + GremlinGSParser.TraversalMethod_inContext ctx) { + String[] labels = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + Preconditions.checkArgument( + labels != null && labels.length > 0, "arguments can not be empty in path expand"); + String[] ranges = labels[0].split("\\.\\."); + int lower = Integer.valueOf(ranges[0]); + int upper = Integer.valueOf(ranges[1]); + // set path_opt and result_opt + List nextWith = + getNextWith((GremlinGSParser.TraversalMethodContext) ctx.getParent()); + nextWith.forEach(k -> visitTraversalMethod_with(k)); + // set expand config, getV config, range, alias + String alias = + parent.getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.expand(new ExpandConfig(GraphOpt.Expand.IN, getExpandLabelConfig(labels))) + .getV(new GetVConfig(GraphOpt.GetV.START)) + .range(lower, upper - lower) + .alias(alias); + } + + @Override + public PathExpandConfig.Builder visitTraversalMethod_both( + GremlinGSParser.TraversalMethod_bothContext ctx) { + String[] labels = GenericLiteralVisitor.getStringLiteralList(ctx.stringLiteralList()); + Preconditions.checkArgument( + labels != null && labels.length > 0, "arguments can not be empty in path expand"); + String[] ranges = labels[0].split("\\.\\."); + int lower = Integer.valueOf(ranges[0]); + int upper = Integer.valueOf(ranges[1]); + // set path_opt and result_opt + List nextWith = + getNextWith((GremlinGSParser.TraversalMethodContext) ctx.getParent()); + nextWith.forEach(k -> visitTraversalMethod_with(k)); + // set expand config, getV config, range, alias + String alias = + parent.getNextTag( + new TraversalMethodIterator( + (GremlinGSParser.TraversalMethodContext) ctx.getParent())); + return builder.expand(new ExpandConfig(GraphOpt.Expand.BOTH, getExpandLabelConfig(labels))) + .getV(new GetVConfig(GraphOpt.GetV.OTHER)) + .range(lower, upper - lower) + .alias(alias); + } + + @Override + public PathExpandConfig.Builder visitTraversalMethod_with( + GremlinGSParser.TraversalMethod_withContext ctx) { + String optKey = GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()); + Object optValue = + GenericLiteralVisitor.getInstance().visitGenericLiteral(ctx.genericLiteral()); + switch (optKey.toUpperCase()) { + case "PATH_OPT": + return builder.pathOpt( + GraphOpt.PathExpandPath.valueOf(String.valueOf(optValue).toUpperCase())); + case "RESULT_OPT": + return builder.resultOpt( + GraphOpt.PathExpandResult.valueOf(String.valueOf(optValue).toUpperCase())); + default: + return builder; + } + } + + private List getNextWith( + GremlinGSParser.TraversalMethodContext curCtx) { + List nextWith = Lists.newArrayList(); + TraversalMethodIterator methodIterator = new TraversalMethodIterator(curCtx); + while (methodIterator.hasNext()) { + GremlinGSParser.TraversalMethodContext next = methodIterator.next(); + if (next.traversalMethod_with() != null) { + nextWith.add(next.traversalMethod_with()); + } else { + break; + } + } + return nextWith; + } + + private LabelConfig getExpandLabelConfig(String[] labels) { + if (labels.length <= 1) { + return new LabelConfig(true); + } else { + labels = Utils.removeStringEle(0, labels); + LabelConfig expandLabels = new LabelConfig(false); + for (int i = 0; i < labels.length; ++i) { + expandLabels.addLabel(labels[i]); + } + return expandLabels; + } + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/TraversalMethodIterator.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/TraversalMethodIterator.java new file mode 100644 index 000000000000..c48a2a5b1e8b --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/gremlin/antlr4x/visitor/TraversalMethodIterator.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x.visitor; + +import com.alibaba.graphscope.grammar.GremlinGSParser; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Iterator; +import java.util.Objects; + +public class TraversalMethodIterator implements Iterator { + private GremlinGSParser.TraversalMethodContext next; + private GremlinGSParser.TraversalSourceSpawnMethodContext sourceNext; + + public TraversalMethodIterator(GremlinGSParser.TraversalMethodContext next) { + this.next = Objects.requireNonNull(next); + } + + public TraversalMethodIterator(GremlinGSParser.TraversalSourceSpawnMethodContext sourceNext) { + this.sourceNext = Objects.requireNonNull(sourceNext); + this.next = null; + } + + @Override + public boolean hasNext() { + if (this.next == null) { + GremlinGSParser.RootTraversalContext rootCtx = + (GremlinGSParser.RootTraversalContext) sourceNext.getParent(); + return rootCtx != null && rootCtx.chainedTraversal() != null; + } + ParseTree parent = getParent(getParent(next)); + return parent != null && parent.getChildCount() >= 3; + } + + @Override + public GremlinGSParser.TraversalMethodContext next() { + if (this.next == null) { + GremlinGSParser.RootTraversalContext rootCtx = + (GremlinGSParser.RootTraversalContext) sourceNext.getParent(); + if (rootCtx == null || rootCtx.chainedTraversal() == null) return null; + next = leftDfs(rootCtx.chainedTraversal()); + return next; + } + ParseTree parent = getParent(getParent(next)); + if (parent == null || parent.getChildCount() < 3) return null; + next = (GremlinGSParser.TraversalMethodContext) parent.getChild(2); + return next; + } + + private @Nullable ParseTree getParent(ParseTree child) { + Class parentClass = GremlinGSParser.ChainedTraversalContext.class; + while (child != null + && child.getParent() != null + && !child.getParent().getClass().equals(parentClass)) { + child = child.getParent(); + } + return (child != null + && child.getParent() != null + && child.getParent().getClass().equals(parentClass)) + ? child.getParent() + : null; + } + + private GremlinGSParser.TraversalMethodContext leftDfs( + GremlinGSParser.ChainedTraversalContext root) { + if (root.chainedTraversal() != null) { + return leftDfs(root.chainedTraversal()); + } + return root.traversalMethod(); + } +} diff --git a/interactive_engine/compiler/src/main/java/org/apache/calcite/sql/fun/ExtSqlPosixRegexOperator.java b/interactive_engine/compiler/src/main/java/org/apache/calcite/sql/fun/ExtSqlPosixRegexOperator.java new file mode 100644 index 000000000000..69806b258a99 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/org/apache/calcite/sql/fun/ExtSqlPosixRegexOperator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.calcite.sql.fun; + +import com.alibaba.graphscope.common.ir.rex.RexCallBinding; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.type.SqlTypeUtil; +import org.apache.calcite.util.Static; +import org.apache.calcite.util.Util; + +public class ExtSqlPosixRegexOperator extends SqlPosixRegexOperator { + public ExtSqlPosixRegexOperator( + String name, SqlKind kind, boolean caseSensitive, boolean negated) { + super(name, kind, caseSensitive, negated); + } + + @Override + public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) { + int operandCount = callBinding.getOperandCount(); + if (operandCount != 2) { + throw new AssertionError( + "Unexpected number of args to " + callBinding.getCall() + ": " + operandCount); + } else { + RelDataType op1Type = callBinding.getOperandType(0); + RelDataType op2Type = callBinding.getOperandType(1); + if (!SqlTypeUtil.isComparable(op1Type, op2Type)) { + throw new AssertionError( + "Incompatible first two operand types " + op1Type + " and " + op2Type); + } else { + if (!SqlTypeUtil.isCharTypeComparable(callBinding.collectOperandTypes())) { + if (throwOnFailure) { + String msg = + String.join( + ", ", + Util.transform( + ((RexCallBinding) callBinding).getRexOperands(), + String::valueOf)); + throw callBinding.newError(Static.RESOURCE.operandNotComparable(msg)); + } else { + return false; + } + } else { + return true; + } + } + } + } +} diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/runtime/RexToProtoTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/runtime/RexToProtoTest.java index 311f304f318a..191e112a274c 100644 --- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/runtime/RexToProtoTest.java +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/runtime/RexToProtoTest.java @@ -50,7 +50,7 @@ public void test_expression_with_brace() throws Exception { GraphStdOperatorTable.MINUS, builder.literal(1), builder.variable("a", "age"))); - RexToProtoConverter converter = new RexToProtoConverter(true, false); + RexToProtoConverter converter = new RexToProtoConverter(true, false, Utils.rexBuilder); Assert.assertEquals( FileUtils.readJsonFromResource("proto/expression_with_brace.json"), JsonFormat.printer().print(braceExpr.accept(converter))); diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/WithTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/WithTest.java index 6eb36f2acf51..9076848fdd25 100644 --- a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/WithTest.java +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/cypher/antlr4/WithTest.java @@ -211,8 +211,7 @@ public void with_12_test() { RelNode project = Utils.eval("Match (a:person)-[]-(b:person) Return [a.name, b.age, 1]").build(); Assert.assertEquals( - "GraphLogicalProject($f0=[ARRAY_VALUE_CONSTRUCTOR(a.name, b.age, 1)]," - + " isAppend=[false])\n" + "GraphLogicalProject($f0=[ARRAY(a.name, b.age, 1)], isAppend=[false])\n" + " GraphLogicalSingleMatch(input=[null]," + " sentence=[GraphLogicalGetV(tableConfig=[{isAll=false, tables=[person]}]," + " alias=[b], opt=[OTHER])\n" diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/antlr4x/GraphBuilderTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/antlr4x/GraphBuilderTest.java new file mode 100644 index 000000000000..6e718fd1fa8b --- /dev/null +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/gremlin/antlr4x/GraphBuilderTest.java @@ -0,0 +1,457 @@ +/* + * Copyright 2020 Alibaba Group Holding Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.graphscope.gremlin.antlr4x; + +import com.alibaba.graphscope.common.ir.Utils; +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.gremlin.antlr4x.parser.GremlinAntlr4Parser; +import com.alibaba.graphscope.gremlin.antlr4x.visitor.GraphBuilderVisitor; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.apache.calcite.rel.RelNode; +import org.junit.Assert; +import org.junit.Test; + +public class GraphBuilderTest { + public static RelNode eval(String query) { + GraphBuilder builder = Utils.mockGraphBuilder(); + GraphBuilderVisitor visitor = new GraphBuilderVisitor(builder); + ParseTree parseTree = new GremlinAntlr4Parser().parse(query); + return visitor.visit(parseTree).build(); + } + + @Test + public void g_V_test() { + RelNode node = eval("g.V()"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_id_test() { + RelNode node = eval("g.V(1, 2, 3)"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX], uniqueKeyFilters=[SEARCH(DEFAULT.~id," + + " Sarg[1, 2, 3])])", + node.explain().trim()); + } + + @Test + public void g_V_hasLabel_test() { + RelNode node = eval("g.V().hasLabel('person')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}], alias=[DEFAULT]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_hasLabel_as_test() { + RelNode node = eval("g.V().hasLabel('person').as('a')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}], alias=[a]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_hasLabels_as_test() { + RelNode node = eval("g.V().hasLabel('person', 'software').as('a')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_hasLabel_error_test() { + try { + RelNode node = eval("g.V().hasLabel('person').hasLabel('software')"); + } catch (IllegalArgumentException e) { + Assert.assertEquals( + "cannot find common labels between values= [software] and label=" + + " [[VertexLabel(person)]]", + e.getMessage()); + return; + } + Assert.fail(); + } + + @Test + public void g_V_hasId_test() { + RelNode node = eval("g.V().hasId(1)"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX], uniqueKeyFilters=[=(DEFAULT.~id, 1)])", + node.explain().trim()); + } + + @Test + public void g_V_hasIds_test() { + RelNode node = eval("g.V().hasId(1, 2, 3)"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX], uniqueKeyFilters=[SEARCH(DEFAULT.~id," + + " Sarg[1, 2, 3])])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_marko_test() { + RelNode node = eval("g.V().has('name', 'marko')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[=(DEFAULT.name, _UTF-8'marko')]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_eq_marko_test() { + RelNode node = eval("g.V().has('name', eq('marko'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[=(DEFAULT.name, _UTF-8'marko')]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_neq_marko_test() { + RelNode node = eval("g.V().has('name', neq('marko'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[<>(DEFAULT.name, _UTF-8'marko')]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_age_gt_17_test() { + RelNode node = eval("g.V().has('age', gt(17))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[>(DEFAULT.age, 17)]], opt=[VERTEX])", + node.explain().trim()); + } + + // hasNot('age') -> age is null + @Test + public void g_V_hasNot_age_test() { + RelNode node = eval("g.V().hasNot('age')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[IS NULL(DEFAULT.age)]], opt=[VERTEX])", + node.explain().trim()); + } + + // todo: convert SEARCH operator to ir core structure + @Test + public void g_V_has_age_inside_17_20_test() { + RelNode node = eval("g.V().has('age', inside(17, 20))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[SEARCH(DEFAULT.age, Sarg[[18..19]])]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_age_outside_17_20_test() { + RelNode node = eval("g.V().has('age', outside(17, 20))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[SEARCH(DEFAULT.age, Sarg[(-∞..18)," + + " (19..+∞)])]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_age_within_test() { + RelNode node = eval("g.V().has('age', within(17, 20))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[SEARCH(DEFAULT.age, Sarg[17, 20])]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_age_without_test() { + RelNode node = eval("g.V().has('age', without(17, 20))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[SEARCH(DEFAULT.age, Sarg[(-∞..17), (17..20)," + + " (20..+∞)])]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_endingWith_test() { + RelNode node = eval("g.V().has('name', endingWith('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'mar$')]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_not_endingWith_test() { + RelNode node = eval("g.V().has('name', notEndingWith('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[NOT(POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'mar$'))]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_containing_test() { + RelNode node = eval("g.V().has('name', containing('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'.*mar*.')]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_not_containing_test() { + RelNode node = eval("g.V().has('name', notContaining('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[NOT(POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'.*mar*.'))]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_startingWith_test() { + RelNode node = eval("g.V().has('name', startingWith('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'^mar')]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_not_startingWith_test() { + RelNode node = eval("g.V().has('name', notStartingWith('mar'))"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], fusedFilter=[[NOT(POSIX REGEX CASE SENSITIVE(DEFAULT.name," + + " _UTF-8'^mar'))]], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_marko_age_17_has_label_test() { + RelNode node = eval("g.V().has('name', 'marko').has('age', 17).hasLabel('person')"); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}], alias=[DEFAULT]," + + " fusedFilter=[[AND(=(DEFAULT.name, _UTF-8'marko'), =(DEFAULT.age, 17))]]," + + " opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_has_name_marko_age_17_has_label_error_test() { + try { + RelNode node = eval("g.V().has('name', 'marko').has('age', 17).hasLabel('software')"); + } catch (IllegalArgumentException e) { + Assert.assertEquals( + "{property=age} not found; expected properties are: [id, name, lang," + + " creationDate]", + e.getMessage()); + return; + } + Assert.fail(); + } + + @Test + public void g_V_out_test() { + RelNode node = eval("g.V().out()"); + Assert.assertEquals( + "GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[END])\n" + + " GraphLogicalExpand(tableConfig=[{isAll=true, tables=[created, knows]}]," + + " alias=[DEFAULT], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_out_label_test() { + RelNode node = eval("g.V().out('knows')"); + Assert.assertEquals( + "GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[END])\n" + + " GraphLogicalExpand(tableConfig=[{isAll=false, tables=[knows]}]," + + " alias=[DEFAULT], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_out_label_as_test() { + RelNode node = eval("g.V().as('a').out().as('b')"); + Assert.assertEquals( + "GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}], alias=[b]," + + " opt=[END])\n" + + " GraphLogicalExpand(tableConfig=[{isAll=true, tables=[created, knows]}]," + + " alias=[DEFAULT], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_path_expand_test() { + RelNode node = + eval( + "g.V().as('a').out('1..2', 'knows').with('path_opt'," + + " 'simple').with('result_opt', 'all_v').as('b').endV().as('c')"); + Assert.assertEquals( + "GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}], alias=[c]," + + " opt=[END])\n" + + " GraphLogicalPathExpand(expand=[GraphLogicalExpand(tableConfig=[{isAll=false," + + " tables=[knows]}], alias=[DEFAULT], opt=[OUT])\n" + + "], getV=[GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[DEFAULT], opt=[END])\n" + + "], offset=[1], fetch=[1], path_opt=[SIMPLE], result_opt=[ALL_V]," + + " alias=[b])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_values_name_test() { + RelNode node = eval("g.V().values('name').as('a')"); + Assert.assertEquals( + "GraphLogicalProject(a=[DEFAULT.name], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_valueMap_name_test() { + RelNode node = eval("g.V().valueMap('name').as('a')"); + Assert.assertEquals( + "GraphLogicalProject(a=[MAP(_UTF-8'name', DEFAULT.name)], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_valueMap_all_test() { + RelNode node = eval("g.V().valueMap().as('a')"); + Assert.assertEquals( + "GraphLogicalProject(a=[MAP(_UTF-8'id', DEFAULT.id, _UTF-8'name', DEFAULT.name," + + " _UTF-8'lang', DEFAULT.lang, _UTF-8'creationDate', DEFAULT.creationDate," + + " _UTF-8'age', DEFAULT.age)], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_elementMap_all_test() { + RelNode node = eval("g.V().elementMap().as('a')"); + Assert.assertEquals( + "GraphLogicalProject(a=[MAP(_UTF-8'~label', DEFAULT.~label, _UTF-8'~id'," + + " DEFAULT.~id, _UTF-8'id', DEFAULT.id, _UTF-8'name', DEFAULT.name," + + " _UTF-8'lang', DEFAULT.lang, _UTF-8'creationDate', DEFAULT.creationDate," + + " _UTF-8'age', DEFAULT.age)], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[DEFAULT], opt=[VERTEX])", + node.explain().trim()); + } + + // todo: optimize select('a'), provide a tag representing 'NONE' + @Test + public void g_V_select_a_test() { + RelNode node = eval("g.V().as('a').select('a')"); + Assert.assertEquals( + "GraphLogicalProject(a0=[a], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_select_a_by_name_test() { + RelNode node = eval("g.V().as('a').select('a').by('name')"); + Assert.assertEquals( + "GraphLogicalProject(name=[a.name], isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_select_a_by_valueMap_test() { + RelNode node = eval("g.V().as('a').select('a').by(valueMap())"); + Assert.assertEquals( + "GraphLogicalProject($f0=[MAP(_UTF-8'id', a.id, _UTF-8'name', a.name, _UTF-8'lang'," + + " a.lang, _UTF-8'creationDate', a.creationDate, _UTF-8'age', a.age)]," + + " isAppend=[true])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_select_a_b_test() { + RelNode node = eval("g.V().as('a').out().as('b').select('a', 'b')"); + Assert.assertEquals( + "GraphLogicalProject($f0=[MAP(_UTF-8'a', a, _UTF-8'b', b)], isAppend=[true])\n" + + " GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[b], opt=[END])\n" + + " GraphLogicalExpand(tableConfig=[{isAll=true, tables=[created, knows]}]," + + " alias=[DEFAULT], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[a], opt=[VERTEX])", + node.explain().trim()); + } + + @Test + public void g_V_select_a_b_by_name_valueMap_test() { + RelNode node = + eval("g.V().as('a').out().as('b').select('a', 'b').by('name').by(valueMap())"); + Assert.assertEquals( + "GraphLogicalProject($f0=[MAP(_UTF-8'a', a.name, _UTF-8'b', MAP(_UTF-8'id', b.id," + + " _UTF-8'name', b.name, _UTF-8'lang', b.lang, _UTF-8'creationDate'," + + " b.creationDate, _UTF-8'age', b.age))], isAppend=[true])\n" + + " GraphLogicalGetV(tableConfig=[{isAll=true, tables=[software, person]}]," + + " alias=[b], opt=[END])\n" + + " GraphLogicalExpand(tableConfig=[{isAll=true, tables=[created, knows]}]," + + " alias=[DEFAULT], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=true, tables=[software," + + " person]}], alias=[a], opt=[VERTEX])", + node.explain().trim()); + } +}