Skip to content

Commit

Permalink
feat(interactive): Support Optional Match and Where Not(subquery) in …
Browse files Browse the repository at this point in the history
…Compiler (#3088)

<!--
Thanks for your contribution! please review
https://github.com/alibaba/GraphScope/blob/main/CONTRIBUTING.md before
opening an issue.
-->

## What do these changes do?
1. support optional match as left outer join 
2. support where(not(subquery)) which is converted to anti join by
`NotExistToAntiJoinRule`
3. support `FilterIntoJoinRule` to push down filters into join (using
calcite standard implementation)

<!-- Please give a short brief about these changes. -->

## Related issue number

<!-- Are there any issues opened that will be resolved by merging this
change? -->

Fixes

---------

Co-authored-by: Longbin Lai <[email protected]>
  • Loading branch information
shirly121 and longbinlai authored Sep 1, 2023
1 parent c766793 commit 9de14a9
Show file tree
Hide file tree
Showing 43 changed files with 1,371 additions and 298 deletions.
4 changes: 2 additions & 2 deletions docs/interactive_engine/neo4j/supported_cypher.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ RETURN a, b;
| Keyword | Comments | Supported | Todo
|:---|---|:---:|:---|
| MATCH | only one Match clause is allowed | <input type="checkbox" disabled checked /> |
| OPTIONAL MATCH | implements as left outer join | <input type="checkbox" disabled /> | planned |
| OPTIONAL MATCH | implements as left outer join | <input type="checkbox" checked /> | |
| RETURN .. [AS] | | <input type="checkbox" disabled checked /> | |
| WITH .. [AS] | project, aggregate, distinct | <input type="checkbox" disabled checked /> | |
| WHERE | | <input type="checkbox" disabled checked /> | |
| NOT EXIST (an edge/path) | implements as anti join | <input type="checkbox" disabled />| |
| WHERE NOT EXIST (an edge/path) | implements as anti join | <input type="checkbox" checked />| |
| ORDER BY | | <input type="checkbox" disabled checked /> | |
| LIMIT | | <input type="checkbox" disabled checked /> | |

2 changes: 1 addition & 1 deletion interactive_engine/compiler/conf/ir.compiler.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ graph.store: exp

graph.planner.is.on: true
graph.planner.opt: RBO
graph.planner.rules: FilterMatchRule
graph.planner.rules: FilterIntoJoinRule, FilterMatchRule, NotMatchToAntiJoinRule

# set stored procedures directory path
# graph.stored.procedures: <your stored procedures directory path>
Expand Down
27 changes: 19 additions & 8 deletions interactive_engine/compiler/src/main/antlr4/CypherGS.g4
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ CALL : ( 'C' | 'c' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ( 'L' | 'l' ) ;
YIELD : ( 'Y' | 'y' ) ( 'I' | 'i' ) ( 'E' | 'e' ) ( 'L' | 'l' ) ( 'D' | 'd' ) ;

oC_RegularQuery
: oC_Match ( SP? oC_With )* ( SP oC_Return ) ;
: oC_Match ( SP? ( oC_Match | oC_With ) )* ( SP oC_Return ) ;

oC_Match
: MATCH SP? oC_Pattern ( SP? oC_Where )? ;
: ( OPTIONAL SP )? MATCH SP? oC_Pattern ( SP? oC_Where )? ;

MATCH : ( 'M' | 'm' ) ( 'A' | 'a' ) ( 'T' | 't' ) ( 'C' | 'c' ) ( 'H' | 'h' ) ;

OPTIONAL : ( 'O' | 'o' ) ( 'P' | 'p' ) ( 'T' | 't' ) ( 'I' | 'i' ) ( 'O' | 'o' ) ( 'N' | 'n' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ;

// multiple sentences
oC_Pattern
: oC_PatternPart ( SP? ',' SP? oC_PatternPart )* ;
Expand All @@ -77,6 +79,9 @@ oC_PatternElement
| ( '(' oC_PatternElement ')' )
;

oC_RelationshipsPattern
: oC_NodePattern ( SP? oC_PatternElementChain )+ ;

// (n)
// (n:Person)
// (n:Person {name : 'marko'})
Expand Down Expand Up @@ -217,10 +222,13 @@ OR : ( 'O' | 'o' ) ( 'R' | 'r' ) ;
XOR : ( 'X' | 'x' ) ( 'O' | 'o' ) ( 'R' | 'r' ) ;

oC_AndExpression
: oC_ComparisonExpression ( SP AND SP oC_ComparisonExpression )* ;
: oC_NotExpression ( SP AND SP oC_NotExpression )* ;

AND : ( 'A' | 'a' ) ( 'N' | 'n' ) ( 'D' | 'd' ) ;

oC_NotExpression
: ( NOT SP? )* oC_ComparisonExpression ;

NOT : ( 'N' | 'n' ) ( 'O' | 'o' ) ( 'T' | 't' ) ;

oC_ComparisonExpression
Expand Down Expand Up @@ -269,13 +277,16 @@ oC_PropertyLookup

oC_Atom
: oC_Literal
| oC_ParenthesizedExpression
| oC_Variable
| oC_FunctionInvocation
| oC_CountAny
| oC_Parameter
| oC_CaseExpression
;
| oC_CountAny
| oC_PatternPredicate
| oC_ParenthesizedExpression
| oC_FunctionInvocation
| oC_Variable ;

oC_PatternPredicate
: oC_RelationshipsPattern ;

oC_Parameter
: '$' ( oC_SymbolicName ) ;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
import com.alibaba.graphscope.common.ir.tools.GraphBuilder;
import com.google.common.collect.ImmutableList;

import org.apache.calcite.plan.*;
import org.apache.calcite.plan.GraphOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.rules.TransformationRule;
Expand Down Expand Up @@ -139,11 +140,7 @@ public static class Config implements RelRule.Config {
b1.operand(
AbstractLogicalMatch
.class)
.anyInputs()))
.withRelBuilderFactory(
(RelOptCluster cluster, @Nullable RelOptSchema schema) ->
GraphBuilder.create(
null, (GraphOptCluster) cluster, schema));
.anyInputs()));

private RelRule.OperandTransform operandSupplier;
private @Nullable String description;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.common.ir.planner.rules;

import com.alibaba.graphscope.common.ir.tools.GraphBuilder;
import com.alibaba.graphscope.common.ir.tools.config.GraphOpt;
import com.google.common.collect.Lists;

import org.apache.calcite.plan.*;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.rules.TransformationRule;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexSubQuery;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.tools.RelBuilderFactory;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;

public class NotMatchToAntiJoinRule<C extends NotMatchToAntiJoinRule.Config> extends RelRule<C>
implements TransformationRule {

protected NotMatchToAntiJoinRule(C config) {
super(config);
}

@Override
public void onMatch(RelOptRuleCall call) {
Filter filter = call.rel(0);
List<RexNode> conditions = RelOptUtil.conjunctions(filter.getCondition());
List<RexNode> conditionsToRemove = Lists.newArrayList();
RelNode input = filter.getInput();
GraphBuilder graphBuilder = (GraphBuilder) call.builder();
for (RexNode condition : conditions) {
if (foundNotExistSubQuery(condition)) {
input = convertToAntiJoin(input, condition, graphBuilder);
conditionsToRemove.add(condition);
}
}
conditions.removeAll(conditionsToRemove);
if (!conditionsToRemove.isEmpty() && !conditions.isEmpty()) {
filter = (Filter) graphBuilder.push(input).filter(conditions).build();
}
RelNode newNode = (conditions.isEmpty()) ? input : filter;
call.transformTo(newNode);
}

private RelNode convertToAntiJoin(
RelNode input, RexNode notExistCondition, GraphBuilder builder) {
RelNode leftChild = input;
RexSubQuery existSubQuery = (RexSubQuery) ((RexCall) notExistCondition).operands.get(0);
RelNode rightChild = builder.match(existSubQuery.rel, GraphOpt.Match.INNER).build();
return builder.push(leftChild)
.push(rightChild)
.antiJoin(builder.getJoinCondition(leftChild, rightChild))
.build();
}

public static class Config implements RelRule.Config {
public static NotMatchToAntiJoinRule.Config DEFAULT =
new NotMatchToAntiJoinRule.Config()
.withOperandSupplier(
b0 ->
b0.operand(Filter.class)
.predicate(
k -> {
List<RexNode> conditions =
RelOptUtil.conjunctions(
k.getCondition());
for (RexNode condition : conditions) {
if (foundNotExistSubQuery(
condition)) {
return true;
}
}
return false;
})
.anyInputs())
.withDescription("NotExistToAntiJoinRule");

private RelRule.OperandTransform operandSupplier;
private @Nullable String description;
private RelBuilderFactory builderFactory;

@Override
public RelRule toRule() {
return new NotMatchToAntiJoinRule(this);
}

@Override
public NotMatchToAntiJoinRule.Config withRelBuilderFactory(
RelBuilderFactory relBuilderFactory) {
this.builderFactory = relBuilderFactory;
return this;
}

@Override
public NotMatchToAntiJoinRule.Config withDescription(
@org.checkerframework.checker.nullness.qual.Nullable String s) {
this.description = s;
return this;
}

@Override
public NotMatchToAntiJoinRule.Config withOperandSupplier(
RelRule.OperandTransform operandTransform) {
this.operandSupplier = operandTransform;
return this;
}

@Override
public RelRule.OperandTransform operandSupplier() {
return this.operandSupplier;
}

@Override
public @org.checkerframework.checker.nullness.qual.Nullable String description() {
return this.description;
}

@Override
public RelBuilderFactory relBuilderFactory() {
return this.builderFactory;
}
}

private static boolean foundNotExistSubQuery(RexNode condition) {
if (condition instanceof RexCall) {
RexCall call = (RexCall) condition;
if (call.getOperator().getKind() == SqlKind.NOT) {
RexNode operand = ((RexCall) condition).operands.get(0);
return operand instanceof RexSubQuery
&& ((RexSubQuery) operand).getOperator().getKind() == SqlKind.EXISTS;
} else {
return false;
}
} else {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalJoin;

/**
* interface to visit each {@code RelNode}
Expand All @@ -49,4 +50,6 @@ public interface GraphRelShuttle {
RelNode visit(GraphLogicalSingleMatch match);

RelNode visit(GraphLogicalMultiMatch match);

RelNode visit(LogicalJoin join);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.calcite.rel.RelShuttleImpl;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalJoin;

/**
* a wrapper of {@code GraphRelShuttle} and re-implement {@code RelShuttleImpl} to visit our self-defined operators ({@code GraphLogicalXX})
Expand Down Expand Up @@ -55,6 +56,11 @@ public RelNode visit(LogicalFilter filter) {
return relShuttle.visit(filter);
}

@Override
public RelNode visit(LogicalJoin join) {
return relShuttle.visit(join);
}

@Override
public RelNode visit(RelNode relNode) {
if (relNode instanceof GraphLogicalProject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@
import org.apache.calcite.rel.RelVisitor;
import org.apache.calcite.rel.SingleRel;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.*;
import java.util.List;

/**
* A wrapper structure for all related graph operators
Expand All @@ -40,23 +39,26 @@ protected AbstractLogicalMatch(
super(cluster, RelTraitSet.createEmpty(), input);
}

// Join or FlatMap or Intersect
public RelNode toPhysical() {
throw new UnsupportedOperationException("will implement in physical layer");
}

@Override
public List<RelNode> getInputs() {
return this.input == null ? ImmutableList.of() : ImmutableList.of(this.input);
}

protected void addFields(List<RelDataTypeField> addTo, RelDataType rowType) {
List<RelDataTypeField> fields = rowType.getFieldList();
for (RelDataTypeField field : fields) {
if (!field.getName().equals(AliasInference.DEFAULT_NAME)) {
addTo.add(field);
}
}
protected void addFields(List<RelDataTypeField> addTo, RelNode topNode) {
RelVisitor visitor =
new RelVisitor() {
@Override
public void visit(RelNode node, int ordinal, @Nullable RelNode parent) {
super.visit(node, ordinal, parent);
List<RelDataTypeField> fields = node.getRowType().getFieldList();
for (RelDataTypeField field : fields) {
if (!field.getName().equals(AliasInference.DEFAULT_NAME)) {
addTo.add(field);
}
}
}
};
visitor.go(topNode);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.alibaba.graphscope.common.ir.rel.graph.match;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;

import org.apache.calcite.plan.GraphOptCluster;
import org.apache.calcite.plan.RelOptUtil;
Expand Down Expand Up @@ -75,13 +76,18 @@ public RelWriter explainTerms(RelWriter pw) {
public RelDataType deriveRowType() {
List<RelDataTypeField> fields = new ArrayList<>();
for (RelNode node : sentences) {
addFields(fields, node.getRowType());
while (ObjectUtils.isNotEmpty(node.getInputs())) {
node = node.getInput(0);
addFields(fields, node.getRowType());
}
addFields(fields, node);
}
List<RelDataTypeField> dedup = fields.stream().distinct().collect(Collectors.toList());
Set<String> fieldNames = Sets.newHashSet();
List<RelDataTypeField> dedup =
fields.stream()
.filter(
k -> {
boolean notExist = !fieldNames.contains(k.getName());
fieldNames.add(k.getName());
return notExist;
})
.collect(Collectors.toList());
return new RelRecordType(StructKind.FULLY_QUALIFIED, dedup);
}

Expand Down
Loading

0 comments on commit 9de14a9

Please sign in to comment.