Skip to content

Commit

Permalink
fix(interactive): Fix bug in ExpandGetVFusionRule, and optimize whe…
Browse files Browse the repository at this point in the history
…n `GetV` has imprecise types (#3804)

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

## What do these changes do?

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

As titled. This pr:
1. Fix a bug in `ExpandGetVFusionRule`. For example, assume we have edge
types of `person-likes-comment`, `person-likes-post`,
`person-knows-person` in schema. Then in queries, if we want to expand
`person-likes-comment`, we would generate a
`Expand(likes)+GetV(comment)` (before this fix, we generate a
`Expand(likes)` only, which is a bug); And if we want to expand
`person-knows-person`, we simply generate a `Expand(knows)`.
2. Optimize cases when the types in GetV is imprecise, to avoid
unnecessary filtering in Runtime.

## Related issue number

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

Fixes #3732 #3802

---------

Co-authored-by: Xiaoli Zhou <yihe.zxl@alibaba-inc.com>
  • Loading branch information
BingqingLyu and shirly121 authored May 20, 2024
1 parent 46e3559 commit f1a4901
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,31 @@

package com.alibaba.graphscope.common.ir.planner.rules;

import com.alibaba.graphscope.common.ir.meta.schema.GraphOptTable;
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalExpand;
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalGetV;
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalPathExpand;
import com.alibaba.graphscope.common.ir.rel.graph.GraphPhysicalExpand;
import com.alibaba.graphscope.common.ir.rel.graph.GraphPhysicalGetV;
import com.alibaba.graphscope.common.ir.tools.AliasInference;
import com.alibaba.graphscope.common.ir.tools.config.GraphOpt;
import com.alibaba.graphscope.common.ir.type.GraphLabelType;
import com.google.common.collect.ImmutableList;

import org.apache.calcite.plan.GraphOptCluster;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.rules.TransformationRule;
import org.apache.calcite.tools.RelBuilderFactory;
import org.apache.commons.lang3.ObjectUtils;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

// This rule try to fuse GraphLogicalExpand and GraphLogicalGetV if GraphLogicalExpand has no alias
// (that it won't be visited individually later):
// 1. if GraphLogicalGetV has no filters, then:
Expand All @@ -57,7 +64,7 @@ protected RelNode transform(GraphLogicalGetV getV, GraphLogicalExpand expand, Re
&& getV.getOpt().equals(GraphOpt.GetV.START)
|| expand.getOpt().equals(GraphOpt.Expand.BOTH)
&& getV.getOpt().equals(GraphOpt.GetV.OTHER)) {
if (ObjectUtils.isEmpty(getV.getFilters())) {
if (canFuse(getV, expand)) {
GraphPhysicalExpand physicalExpand =
GraphPhysicalExpand.create(
expand.getCluster(),
Expand Down Expand Up @@ -95,6 +102,107 @@ protected RelNode transform(GraphLogicalGetV getV, GraphLogicalExpand expand, Re
}
}

private boolean canFuse(GraphLogicalGetV getV, GraphLogicalExpand expand) {
// If GraphLogicalGetV has filters, then we cannot fuse them directly. Instead, we create a
// EdgeExpand(V) with an Auxilia for the filters.
// If GraphLogicalGetV has no filters, then:
// 1. if we want to expand "person-knows->person", and the type in getV is "person",
// we can fuse them into one EdgeExpand with type "knows" (where "knows" will be given in
// EdgeExpand's QueryParam in PhysicalPlan)
// 2. if we want to expand "person-create->post", while in schema, a "create" actually
// consists of "person-create->post" and "person-create->comment",
// we do not fuse them directly. Instead, we create a EdgeExpand(V) with type "create" and
// an Auxilia with type "post" as the filter.
// 3. if we want to expand "person-islocatedin->city", while in schema, a "islocatedin"
// actually
// consists of "person-islocatedin->city" and "post-islocatedin->country".
// Thought the edge type of "islocatedin" may generate "city" and "country", we can still
// fuse them into a single EdgeExpand(V) with type "islocatedin" directly if we can confirm
// that the expand starts from "person".
// 4. a special case is that, currently, for gremlin query like g.V().out("create"), we have
// not infer precise types for getV yet (getV may contain all vertex types).
// In this case, if getV's types contains all the types that expand will generate, we can
// fuse them.

Set<Integer> edgeExpandedVLabels = new HashSet<>();
// the optTables in expand preserves the full schema information for the edges,
// that is, for edge type "create", it contains both "person-create->post" and
// "person-create->comment", "user-create->post" etc.
List<RelOptTable> optTables = expand.getTableConfig().getTables();
// the edgeParamLabels in expand preserves the inferred schema information for the edges,
// that is, for edge type "create", it contains only "person-create->post" if user queries
// like g.V().hasLabel("person").out("create").hasLabel("post")
GraphLabelType edgeParamType =
com.alibaba.graphscope.common.ir.tools.Utils.getGraphLabels(expand.getRowType());
List<GraphLabelType.Entry> edgeParamLabels = edgeParamType.getLabelsEntry();
GraphOpt.Expand direction = expand.getOpt();
// First, we get all the source vertex types where the edge will be expanded from.
// e.g., expand from "person"
Set<Integer> edgeExpandedSrcVLabels = new HashSet<>();
for (GraphLabelType.Entry edgeLabel : edgeParamLabels) {
switch (direction) {
case OUT:
edgeExpandedSrcVLabels.add(edgeLabel.getSrcLabelId());
break;
case IN:
edgeExpandedSrcVLabels.add(edgeLabel.getDstLabelId());
break;
case BOTH:
edgeExpandedSrcVLabels.add(edgeLabel.getDstLabelId());
edgeExpandedSrcVLabels.add(edgeLabel.getSrcLabelId());
break;
}
}
// Then, we get all the destination vertex types where the edge will be expanded to.
// e.g., expand "likes"
for (RelOptTable optTable : optTables) {
if (optTable instanceof GraphOptTable) {
GraphOptTable graphOptTable = (GraphOptTable) optTable;
List<GraphLabelType.Entry> edgeUserGivenParamLabels =
com.alibaba.graphscope.common.ir.tools.Utils.getGraphLabels(
graphOptTable.getRowType())
.getLabelsEntry();
for (GraphLabelType.Entry edgeLabel : edgeUserGivenParamLabels) {
switch (direction) {
case OUT:
if (edgeExpandedSrcVLabels.contains(edgeLabel.getSrcLabelId())) {
edgeExpandedVLabels.add(edgeLabel.getDstLabelId());
}
break;
case IN:
if (edgeExpandedSrcVLabels.contains(edgeLabel.getDstLabelId())) {
edgeExpandedVLabels.add(edgeLabel.getSrcLabelId());
}
break;
case BOTH:
if (edgeExpandedSrcVLabels.contains(edgeLabel.getSrcLabelId())) {
edgeExpandedVLabels.add(edgeLabel.getDstLabelId());
}
if (edgeExpandedSrcVLabels.contains(edgeLabel.getDstLabelId())) {
edgeExpandedVLabels.add(edgeLabel.getSrcLabelId());
}
break;
}
}
}
}

// Finally, we check if the vertex types in getV to see if the type filter for the expanded
// vertex is necessary.
// e.g., if getV is "post" and expand type is "likes", then we cannot fuse them directly.
// Instead, we should create an EdgeExpand(V) with type "likes" and an Auxilia with type
// "post" as the filter.
List<GraphLabelType.Entry> vertexParamLabels =
com.alibaba.graphscope.common.ir.tools.Utils.getGraphLabels(getV.getRowType())
.getLabelsEntry();
Set<Integer> vertexExpandedVLabels = new HashSet<>();
for (GraphLabelType.Entry vertexLabel : vertexParamLabels) {
vertexExpandedVLabels.add(vertexLabel.getLabelId());
}
return ObjectUtils.isEmpty(getV.getFilters())
&& vertexExpandedVLabels.containsAll(edgeExpandedVLabels);
}

// transform expande + getv to GraphPhysicalExpandGetV
public static class BasicExpandGetVFusionRule
extends ExpandGetVFusionRule<BasicExpandGetVFusionRule.Config> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import com.alibaba.graphscope.common.ir.tools.config.GraphOpt.PhysicalGetVOpt;
import com.alibaba.graphscope.common.ir.type.GraphLabelType;
import com.alibaba.graphscope.common.ir.type.GraphNameOrId;
import com.alibaba.graphscope.common.ir.type.GraphSchemaType;
import com.alibaba.graphscope.gaia.proto.GraphAlgebra;
import com.alibaba.graphscope.gaia.proto.GraphAlgebraPhysical;
import com.alibaba.graphscope.gaia.proto.OuterExpression;
Expand Down Expand Up @@ -170,7 +169,10 @@ public RelNode visit(GraphLogicalGetV getV) {
Utils.protoGetVOpt(PhysicalGetVOpt.valueOf(getV.getOpt().name())));
// 1. build adjV without filter
GraphAlgebra.QueryParams.Builder adjParamsBuilder = defaultQueryParams();
addQueryTables(adjParamsBuilder, getGraphLabels(getV).getLabelsEntry());
addQueryTables(
adjParamsBuilder,
com.alibaba.graphscope.common.ir.tools.Utils.getGraphLabels(getV.getRowType())
.getLabelsEntry());
adjVertexBuilder.setParams(adjParamsBuilder);
if (getV.getStartAlias().getAliasId() != AliasInference.DEFAULT_ID) {
adjVertexBuilder.setTag(Utils.asAliasId(getV.getStartAlias().getAliasId()));
Expand Down Expand Up @@ -975,16 +977,6 @@ private GraphAlgebra.IndexPredicate buildIndexPredicates(RexNode uniqueKeyFilter
return indexPredicate;
}

private GraphLabelType getGraphLabels(AbstractBindableTableScan tableScan) {
List<RelDataTypeField> fields = tableScan.getRowType().getFieldList();
Preconditions.checkArgument(
!fields.isEmpty() && fields.get(0).getType() instanceof GraphSchemaType,
"data type of graph operators should be %s ",
GraphSchemaType.class);
GraphSchemaType schemaType = (GraphSchemaType) fields.get(0).getType();
return schemaType.getLabelType();
}

private GraphAlgebra.QueryParams.Builder defaultQueryParams() {
GraphAlgebra.QueryParams.Builder paramsBuilder = GraphAlgebra.QueryParams.newBuilder();
// TODO: currently no sample rate fused into tableScan, so directly set 1.0 as default.
Expand Down Expand Up @@ -1022,7 +1014,10 @@ private void addQueryColumns(

private GraphAlgebra.QueryParams.Builder buildQueryParams(AbstractBindableTableScan tableScan) {
GraphAlgebra.QueryParams.Builder paramsBuilder = defaultQueryParams();
addQueryTables(paramsBuilder, getGraphLabels(tableScan).getLabelsEntry());
addQueryTables(
paramsBuilder,
com.alibaba.graphscope.common.ir.tools.Utils.getGraphLabels(tableScan.getRowType())
.getLabelsEntry());
addQueryFilters(paramsBuilder, tableScan.getFilters());
return paramsBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import com.alibaba.graphscope.common.ir.meta.schema.CommonOptTable;
import com.alibaba.graphscope.common.ir.rel.CommonTableScan;
import com.alibaba.graphscope.common.ir.type.GraphLabelType;
import com.alibaba.graphscope.common.ir.type.GraphSchemaType;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
Expand Down Expand Up @@ -81,6 +84,20 @@ public static List<Comparable> getValuesAsList(Comparable value) {
return values;
}

public static GraphLabelType getGraphLabels(RelDataType rowType) {
if (rowType instanceof GraphSchemaType) {
return ((GraphSchemaType) rowType).getLabelType();
} else {
List<RelDataTypeField> fields = rowType.getFieldList();
Preconditions.checkArgument(
!fields.isEmpty() && fields.get(0).getType() instanceof GraphSchemaType,
"data type of graph operators should be %s ",
GraphSchemaType.class);
GraphSchemaType schemaType = (GraphSchemaType) fields.get(0).getType();
return schemaType.getLabelType();
}
}

/**
* print root {@code RelNode} and nested {@code RelNode}s in each {@code CommonTableScan}
* @param node
Expand Down
Loading

0 comments on commit f1a4901

Please sign in to comment.