diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/AbstractBindableTableScan.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/AbstractBindableTableScan.java new file mode 100644 index 000000000000..7cc1f1bd987f --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/AbstractBindableTableScan.java @@ -0,0 +1,147 @@ +/* + * 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.rel.graph; + +import com.alibaba.graphscope.common.ir.rel.type.TableConfig; +import com.alibaba.graphscope.common.ir.type.GraphSchemaType; +import com.alibaba.graphscope.common.ir.type.GraphSchemaTypeList; +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.core.TableScan; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.util.ImmutableIntList; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A basic structure of graph operators + */ +public abstract class AbstractBindableTableScan extends TableScan { + // for filter fusion + protected ImmutableList filters; + // for field trimmer + protected ImmutableIntList project; + + protected @Nullable RelNode input; + + protected TableConfig tableConfig; + + protected AbstractBindableTableScan( + GraphOptCluster cluster, + List hints, + @Nullable RelNode input, + TableConfig tableConfig) { + super( + cluster, + RelTraitSet.createEmpty(), + hints, + (tableConfig == null || ObjectUtils.isEmpty(tableConfig.getTables())) + ? null + : tableConfig.getTables().get(0)); + this.input = input; + this.tableConfig = Objects.requireNonNull(tableConfig); + } + + protected AbstractBindableTableScan( + GraphOptCluster cluster, List hints, TableConfig tableConfig) { + this(cluster, hints, null, tableConfig); + } + + @Override + public RelDataType deriveRowType() { + List tableTypes = new ArrayList<>(); + List tables = ObjectUtils.requireNonEmpty(this.tableConfig.getTables()); + for (RelOptTable table : tables) { + GraphSchemaType type = (GraphSchemaType) table.getRowType(); + // flat fuzzy labels to the list + if (type instanceof GraphSchemaTypeList) { + tableTypes.addAll((GraphSchemaTypeList) type); + } else { + tableTypes.add(type); + } + } + ObjectUtils.requireNonEmpty(tableTypes); + GraphSchemaType graphType = + (tableTypes.size() == 1) + ? tableTypes.get(0) + : GraphSchemaTypeList.create(tableTypes); + RelRecordType rowType = + new RelRecordType( + ImmutableList.of( + new RelDataTypeFieldImpl(getAliasName(), getAliasId(), graphType))); + return rowType; + } + + public String getAliasName() { + Objects.requireNonNull(hints); + if (hints.size() < 2) { + throw new IllegalArgumentException( + "should have put alias config in the index 1 of the hints list"); + } + RelHint aliasHint = hints.get(1); + Objects.requireNonNull(aliasHint.kvOptions); + String aliasName = aliasHint.kvOptions.get("name"); + Objects.requireNonNull(aliasName); + return aliasName; + } + + public int getAliasId() { + Objects.requireNonNull(hints); + if (hints.size() < 2) { + throw new IllegalArgumentException( + "should have put alias config in the index 1 of the hints list"); + } + RelHint aliasHint = hints.get(1); + Objects.requireNonNull(aliasHint.kvOptions); + String aliasId = aliasHint.kvOptions.get("id"); + Objects.requireNonNull(aliasId); + return Integer.valueOf(aliasId); + } + + // toString + + @Override + public RelWriter explainTerms(RelWriter pw) { + return pw.itemIf("input", input, !Objects.isNull(input)) + .item("tableConfig", tableConfig) + .item("alias", getAliasName()) + .itemIf("fusedProject", project, !ObjectUtils.isEmpty(project)) + .itemIf("fusedFilter", filters, !ObjectUtils.isEmpty(filters)); + } + + @Override + public List getInputs() { + return this.input == null ? ImmutableList.of() : ImmutableList.of(this.input); + } + + public void setFilters(ImmutableList filters) { + this.filters = filters; + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalExpand.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalExpand.java new file mode 100644 index 000000000000..f987d4abd8d3 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalExpand.java @@ -0,0 +1,59 @@ +/* + * 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.rel.graph; + +import com.alibaba.graphscope.common.ir.rel.type.TableConfig; +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.List; + +public class GraphLogicalExpand extends AbstractBindableTableScan { + private GraphOpt.Expand opt; + + protected GraphLogicalExpand( + GraphOptCluster cluster, List hints, RelNode input, TableConfig tableConfig) { + super(cluster, hints, input, tableConfig); + this.opt = directionOpt(); + } + + public static GraphLogicalExpand create( + GraphOptCluster cluster, List hints, RelNode input, TableConfig tableConfig) { + return new GraphLogicalExpand(cluster, hints, input, tableConfig); + } + + private GraphOpt.Expand directionOpt() { + ObjectUtils.requireNonEmpty(hints); + RelHint optHint = hints.get(0); + ObjectUtils.requireNonEmpty(optHint.listOptions); + return GraphOpt.Expand.valueOf(optHint.listOptions.get(0)); + } + + public GraphOpt.Expand getOpt() { + return opt; + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + return super.explainTerms(pw).item("opt", opt); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalGetV.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalGetV.java new file mode 100644 index 000000000000..807d96655f05 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalGetV.java @@ -0,0 +1,59 @@ +/* + * 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.rel.graph; + +import com.alibaba.graphscope.common.ir.rel.type.TableConfig; +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.List; + +public class GraphLogicalGetV extends AbstractBindableTableScan { + private GraphOpt.GetV opt; + + protected GraphLogicalGetV( + GraphOptCluster cluster, List hints, RelNode input, TableConfig tableConfig) { + super(cluster, hints, input, tableConfig); + this.opt = getVOpt(); + } + + public static GraphLogicalGetV create( + GraphOptCluster cluster, List hints, RelNode input, TableConfig tableConfig) { + return new GraphLogicalGetV(cluster, hints, input, tableConfig); + } + + private GraphOpt.GetV getVOpt() { + ObjectUtils.requireNonEmpty(hints); + RelHint optHint = hints.get(0); + ObjectUtils.requireNonEmpty(optHint.listOptions); + return GraphOpt.GetV.valueOf(optHint.listOptions.get(0)); + } + + public GraphOpt.GetV getOpt() { + return opt; + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + return super.explainTerms(pw).item("opt", opt); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java new file mode 100644 index 000000000000..118221a1db65 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalPathExpand.java @@ -0,0 +1,128 @@ +/* + * 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.rel.graph; + +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.SingleRel; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexNode; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; + +public class GraphLogicalPathExpand extends SingleRel { + private final RelNode expand; + private final RelNode getV; + + public final @Nullable RexNode offset; + public final @Nullable RexNode fetch; + + private final List hints; + + protected GraphLogicalPathExpand( + GraphOptCluster cluster, + @Nullable List hints, + RelNode input, + RelNode expand, + RelNode getV, + @Nullable RexNode offset, + @Nullable RexNode fetch) { + super(cluster, RelTraitSet.createEmpty(), input); + this.hints = hints; + this.expand = Objects.requireNonNull(expand); + this.getV = Objects.requireNonNull(getV); + this.offset = offset; + this.fetch = fetch; + } + + public static GraphLogicalPathExpand create( + GraphOptCluster cluster, + List hints, + RelNode input, + RelNode expand, + RelNode getV, + @Nullable RexNode offset, + @Nullable RexNode fetch) { + return new GraphLogicalPathExpand(cluster, hints, input, expand, getV, offset, fetch); + } + + @Override + public RelDataType deriveRowType() { + return getV.getRowType(); + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + return super.explainTerms(pw) + .item("expand", RelOptUtil.toString(expand)) + .item("getV", RelOptUtil.toString(getV)) + .itemIf("offset", offset, offset != null) + .itemIf("fetch", fetch, fetch != null) + .item("path_opt", pathOpt()) + .item("result_opt", resultOpt()) + .item("alias", getAliasName()); + } + + public String getAliasName() { + Objects.requireNonNull(hints); + if (hints.size() < 2) { + throw new IllegalArgumentException( + "should have put alias config in the index 1 of the hints list"); + } + RelHint aliasHint = hints.get(1); + Objects.requireNonNull(aliasHint.kvOptions); + String aliasName = aliasHint.kvOptions.get("name"); + Objects.requireNonNull(aliasName); + return aliasName; + } + + public int getAliasId() { + Objects.requireNonNull(hints); + if (hints.size() < 2) { + throw new IllegalArgumentException( + "should have put alias config in the index 1 of the hints list"); + } + RelHint aliasHint = hints.get(1); + Objects.requireNonNull(aliasHint.kvOptions); + String aliasId = aliasHint.kvOptions.get("id"); + Objects.requireNonNull(aliasId); + return Integer.valueOf(aliasId); + } + + private GraphOpt.PathExpandPath pathOpt() { + ObjectUtils.requireNonEmpty(hints); + RelHint optHint = hints.get(0); + ObjectUtils.requireNonEmpty(optHint.kvOptions); + return GraphOpt.PathExpandPath.valueOf(optHint.kvOptions.get("path")); + } + + private GraphOpt.PathExpandResult resultOpt() { + ObjectUtils.requireNonEmpty(hints); + RelHint optHint = hints.get(0); + ObjectUtils.requireNonEmpty(optHint.kvOptions); + return GraphOpt.PathExpandResult.valueOf(optHint.kvOptions.get("result")); + } +} 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 new file mode 100644 index 000000000000..1b5412eea98b --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/GraphLogicalSource.java @@ -0,0 +1,58 @@ +/* + * 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.rel.graph; + +import com.alibaba.graphscope.common.ir.rel.type.TableConfig; +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.List; + +public class GraphLogicalSource extends AbstractBindableTableScan { + private GraphOpt.Source opt; + + protected GraphLogicalSource( + GraphOptCluster cluster, List hints, TableConfig tableConfig) { + super(cluster, hints, tableConfig); + this.opt = scanOpt(); + } + + public static GraphLogicalSource create( + GraphOptCluster cluster, List hints, TableConfig tableConfig) { + return new GraphLogicalSource(cluster, hints, tableConfig); + } + + private GraphOpt.Source scanOpt() { + ObjectUtils.requireNonEmpty(hints); + RelHint optHint = hints.get(0); + ObjectUtils.requireNonEmpty(optHint.listOptions); + return GraphOpt.Source.valueOf(optHint.listOptions.get(0)); + } + + public GraphOpt.Source getOpt() { + return opt; + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + return super.explainTerms(pw).item("opt", opt); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/AbstractLogicalMatch.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/AbstractLogicalMatch.java new file mode 100644 index 000000000000..71047a3e5ff9 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/AbstractLogicalMatch.java @@ -0,0 +1,60 @@ +/* + * 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.rel.graph.match; + +import com.alibaba.graphscope.common.ir.tools.AliasInference; +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +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.*; + +/** + * A wrapper structure for all related graph operators + */ +public abstract class AbstractLogicalMatch extends SingleRel { + protected AbstractLogicalMatch( + GraphOptCluster cluster, @Nullable List hints, @Nullable RelNode input) { + super(cluster, RelTraitSet.createEmpty(), input); + } + + // Join or FlatMap + public RelNode toPhysical() { + throw new UnsupportedOperationException("will implement in physical layer"); + } + + @Override + public List getInputs() { + return this.input == null ? ImmutableList.of() : ImmutableList.of(this.input); + } + + protected void addFields(List addTo, RelDataType rowType) { + List fields = rowType.getFieldList(); + for (RelDataTypeField field : fields) { + if (!field.getName().equals(AliasInference.DEFAULT_NAME)) { + addTo.add(field); + } + } + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalMultiMatch.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalMultiMatch.java new file mode 100644 index 000000000000..8178d9ce05e1 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalMultiMatch.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.common.ir.rel.graph.match; + +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rel.type.StructKind; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +public class GraphLogicalMultiMatch extends AbstractLogicalMatch { + // sentences should >= 2 + private List sentences; + + protected GraphLogicalMultiMatch( + GraphOptCluster cluster, + @Nullable List hints, + @Nullable RelNode input, + RelNode firstSentence, + List otherSentences) { + super(cluster, hints, input); + ImmutableList.Builder builder = ImmutableList.builder(); + this.sentences = + builder.add(Objects.requireNonNull(firstSentence)) + .addAll(ObjectUtils.requireNonEmpty(otherSentences)) + .build(); + } + + public static GraphLogicalMultiMatch create( + GraphOptCluster cluster, + @Nullable List hints, + RelNode input, + RelNode firstSentence, + List otherSentences) { + return new GraphLogicalMultiMatch(cluster, hints, input, firstSentence, otherSentences); + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + Map strMap = new HashMap<>(); + for (int i = 0; i < sentences.size(); ++i) { + strMap.put( + String.format("s%d", i), + String.format("[%s]", RelOptUtil.toString(sentences.get(i)))); + } + return super.explainTerms(pw).itemIf("sentences", strMap, !ObjectUtils.isEmpty(strMap)); + } + + @Override + public RelDataType deriveRowType() { + List fields = new ArrayList<>(); + for (RelNode node : sentences) { + addFields(fields, node.getRowType()); + while (ObjectUtils.isNotEmpty(node.getInputs())) { + node = node.getInput(0); + addFields(fields, node.getRowType()); + } + } + List dedup = fields.stream().distinct().collect(Collectors.toList()); + return new RelRecordType(StructKind.FULLY_QUALIFIED, dedup); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalSingleMatch.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalSingleMatch.java new file mode 100644 index 000000000000..b7d666898ce8 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/graph/match/GraphLogicalSingleMatch.java @@ -0,0 +1,77 @@ +/* + * 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.rel.graph.match; + +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rel.type.StructKind; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; + +public class GraphLogicalSingleMatch extends AbstractLogicalMatch { + private final RelNode sentence; + private final GraphOpt.Match matchOpt; + + protected GraphLogicalSingleMatch( + GraphOptCluster cluster, + @Nullable List hints, + @Nullable RelNode input, + RelNode sentence, + GraphOpt.Match matchOpt) { + super(cluster, hints, input); + this.sentence = Objects.requireNonNull(sentence); + this.matchOpt = matchOpt; + } + + public static GraphLogicalSingleMatch create( + GraphOptCluster cluster, + @Nullable List hints, + RelNode input, + RelNode sentence, + GraphOpt.Match matchOpt) { + return new GraphLogicalSingleMatch(cluster, hints, input, sentence, matchOpt); + } + + @Override + public RelWriter explainTerms(RelWriter pw) { + return super.explainTerms(pw) + .item("sentence", RelOptUtil.toString(sentence)) + .item("matchOpt", matchOpt); + } + + @Override + public RelDataType deriveRowType() { + List fields = new ArrayList<>(); + RelNode node = this.sentence; + addFields(fields, node.getRowType()); + while (ObjectUtils.isNotEmpty(node.getInputs())) { + node = node.getInput(0); + addFields(fields, node.getRowType()); + } + return new RelRecordType(StructKind.FULLY_QUALIFIED, fields); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/type/TableConfig.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/type/TableConfig.java new file mode 100644 index 000000000000..a693859c70d3 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rel/type/TableConfig.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.rel.type; + +import org.apache.calcite.plan.RelOptTable; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * For each vertex or edge, we derive all the table(s) it requests depending on its query given label(s) + */ +public class TableConfig { + // indicate whether all labels are requested, i.e. g.V() + private boolean isAll; + // we have derived the table type(s) for the query given label(s), should not be empty + private List tables; + + public TableConfig(List tables) { + this.tables = ObjectUtils.requireNonEmpty(tables); + this.isAll = false; + } + + public TableConfig isAll(boolean isAll) { + this.isAll = isAll; + return this; + } + + public boolean isAll() { + return isAll; + } + + public List getTables() { + return Collections.unmodifiableList(this.tables); + } + + @Override + public String toString() { + List labelNames = + tables.stream() + .filter(k -> ObjectUtils.isNotEmpty(k.getQualifiedName())) + .map(k -> k.getQualifiedName().get(0)) + .collect(Collectors.toList()); + return "{" + "isAll=" + isAll + ", tables=" + labelNames + '}'; + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptSchema.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptSchema.java new file mode 100644 index 000000000000..7f322d7d71b6 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptSchema.java @@ -0,0 +1,91 @@ +/* + * 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.schema; + +import com.alibaba.graphscope.compiler.api.exception.GraphElementNotFoundException; +import com.alibaba.graphscope.compiler.api.schema.GraphElement; + +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelOptSchema; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.schema.Statistic; +import org.apache.calcite.util.Static; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; + +/** + * Maintain a set of {@link RelOptTable} objects per query in compilation phase + */ +public class GraphOptSchema implements RelOptSchema { + private RelOptCluster optCluster; + private StatisticSchema rootSchema; + + public GraphOptSchema(@Nullable RelOptCluster optCluster, StatisticSchema rootSchema) { + this.optCluster = optCluster; + this.rootSchema = Objects.requireNonNull(rootSchema); + } + + /** + * @param tableName name of an entity or a relation, + * i.e. the name of an entity can be denoted by ["person"] + * and the name of a relation can be denoted by ["knows"] + * @return + * @throws Exception - if the given table is not found + */ + @Override + public RelOptTable getTableForMember(List tableName) { + ObjectUtils.requireNonEmpty(tableName); + String labelName = tableName.get(0); + try { + GraphElement element = rootSchema.getElement(labelName); + return createRelOptTable(tableName, element, rootSchema.getStatistic(tableName)); + } catch (GraphElementNotFoundException e) { + throw Static.RESOURCE.tableNotFound(labelName).ex(); + } + } + + private RelOptTable createRelOptTable( + List tableName, GraphElement element, Statistic statistic) { + return new GraphOptTable(this, tableName, element, statistic); + } + + /** + * {@link RelDataTypeFactory} provides interfaces to create {@link org.apache.calcite.rel.type.RelDataType} in Calcite + * + * @return + */ + @Override + public RelDataTypeFactory getTypeFactory() { + return this.optCluster.getTypeFactory(); + } + + public StatisticSchema getRootSchema() { + return this.rootSchema; + } + + // used in optimizer + + @Override + public void registerRules(RelOptPlanner relOptPlanner) { + throw new UnsupportedOperationException("registerRules is unsupported yet"); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptTable.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptTable.java new file mode 100644 index 000000000000..0cadc2842c96 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphOptTable.java @@ -0,0 +1,208 @@ +/* + * 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.schema; + +import static java.util.Objects.requireNonNull; + +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; +import com.alibaba.graphscope.common.ir.type.GraphSchemaType; +import com.alibaba.graphscope.common.ir.type.GraphSchemaTypeList; +import com.alibaba.graphscope.common.ir.type.LabelType; +import com.alibaba.graphscope.compiler.api.schema.*; + +import org.apache.calcite.linq4j.tree.Expression; +import org.apache.calcite.plan.RelOptSchema; +import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelReferentialConstraint; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.schema.ColumnStrategy; +import org.apache.calcite.schema.Statistic; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.ImmutableBitSet; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Maintain {@link RelDataType} and {@link Statistic} per entity or per relation + */ +public class GraphOptTable implements RelOptTable { + private List tableName; + private RelOptSchema schema; + private RelDataType dataType; + private Statistic statistic; + + protected GraphOptTable( + RelOptSchema schema, + List tableName, + GraphElement element, + Statistic statistic) { + this.schema = schema; + this.tableName = tableName; + this.statistic = statistic; + this.dataType = deriveType(element); + } + + private RelDataType deriveType(GraphElement element) { + List properties = element.getPropertyList(); + List fields = new ArrayList<>(); + boolean isColumnId = + (this.schema instanceof GraphOptSchema) + ? ((GraphOptSchema) this.schema).getRootSchema().isColumnId() + : false; + for (int i = 0; i < properties.size(); ++i) { + GraphProperty property = properties.get(i); + fields.add( + new RelDataTypeFieldImpl( + property.getName(), + isColumnId ? property.getId() : -1, + deriveType(property))); + } + if (element instanceof GraphVertex) { + LabelType labelType = + (new LabelType()).label(element.getLabel()).labelId(element.getLabelId()); + return new GraphSchemaType(GraphOpt.Source.VERTEX, labelType, fields); + } else if (element instanceof GraphEdge) { + GraphEdge edge = (GraphEdge) element; + List relations = edge.getRelationList(); + List fuzzyTypes = new ArrayList<>(); + for (EdgeRelation relation : relations) { + LabelType labelType = + (new LabelType()).label(element.getLabel()).labelId(element.getLabelId()); + GraphVertex src = relation.getSource(); + GraphVertex dst = relation.getTarget(); + labelType.srcLabel(src.getLabel()).dstLabel(dst.getLabel()); + labelType.srcLabelId(src.getLabelId()).dstLabelId(dst.getLabelId()); + fuzzyTypes.add(new GraphSchemaType(GraphOpt.Source.EDGE, labelType, fields)); + } + ObjectUtils.requireNonEmpty(fuzzyTypes); + return (fuzzyTypes.size() == 1) + ? fuzzyTypes.get(0) + : GraphSchemaTypeList.create(fuzzyTypes); + } else { + throw new IllegalArgumentException("element should be vertex or edge"); + } + } + + private RelDataType deriveType(GraphProperty property) { + RelDataTypeFactory typeFactory = this.schema.getTypeFactory(); + requireNonNull(typeFactory, "typeFactory"); + switch (property.getDataType()) { + case BOOL: + return typeFactory.createSqlType(SqlTypeName.BOOLEAN); + case STRING: + return typeFactory.createSqlType(SqlTypeName.CHAR); + case INT: + return typeFactory.createSqlType(SqlTypeName.INTEGER); + case LONG: + return typeFactory.createSqlType(SqlTypeName.BIGINT); + case DOUBLE: + return typeFactory.createSqlType(SqlTypeName.DOUBLE); + default: + throw new UnsupportedOperationException( + "type " + property.getDataType().name() + " not supported"); + } + } + + @Override + public List getQualifiedName() { + return this.tableName; + } + + @Override + public RelDataType getRowType() { + return this.dataType; + } + + @Override + public @Nullable RelOptSchema getRelOptSchema() { + return this.schema; + } + + @Override + public @Nullable C unwrap(Class clazz) { + if (clazz.isInstance(this)) { + return clazz.cast(this); + } + return null; + } + + // statistics + + @Override + public boolean isKey(ImmutableBitSet immutableBitSet) { + return this.statistic.isKey(immutableBitSet); + } + + @Override + public @Nullable List getKeys() { + return this.statistic.getKeys(); + } + + @Override + public double getRowCount() { + throw new UnsupportedOperationException("row count is unsupported yet in statistics"); + } + + @Override + public @Nullable RelDistribution getDistribution() { + throw new UnsupportedOperationException("distribution is unsupported yet in statistics"); + } + + @Override + public @Nullable List getCollationList() { + throw new UnsupportedOperationException("collations is unsupported yet in statistics"); + } + + // not used currently + + @Override + public RelNode toRel(ToRelContext toRelContext) { + throw new UnsupportedOperationException("toRel is unsupported for it will never be used"); + } + + @Override + public @Nullable List getReferentialConstraints() { + throw new UnsupportedOperationException( + "referentialConstraints is unsupported for it will never be used"); + } + + @Override + public @Nullable Expression getExpression(Class aClass) { + throw new UnsupportedOperationException( + "expression is unsupported for it will never be used"); + } + + @Override + public RelOptTable extend(List list) { + throw new UnsupportedOperationException("extend is unsupported for it will never be used"); + } + + @Override + public List getColumnStrategies() { + throw new UnsupportedOperationException( + "columnStrategies is unsupported for it will never be used"); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphSchemaWrapper.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphSchemaWrapper.java new file mode 100644 index 000000000000..4884ea0e4e60 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/GraphSchemaWrapper.java @@ -0,0 +1,140 @@ +/* + * 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.schema; + +import com.alibaba.graphscope.compiler.api.exception.GraphElementNotFoundException; +import com.alibaba.graphscope.compiler.api.exception.GraphPropertyNotFoundException; +import com.alibaba.graphscope.compiler.api.schema.*; +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.schema.Statistic; +import org.apache.calcite.util.ImmutableBitSet; +import org.apache.calcite.util.Static; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * A wrapper class for {@link GraphSchema} + */ +public class GraphSchemaWrapper implements StatisticSchema { + private final GraphSchema graphSchema; + private final boolean isColumnId; + + public GraphSchemaWrapper(GraphSchema graphSchema, boolean isColumnId) { + this.graphSchema = graphSchema; + this.isColumnId = isColumnId; + } + + @Override + public Statistic getStatistic(List tableName) { + ObjectUtils.requireNonEmpty(tableName); + String labelName = tableName.get(0); + try { + return new DefaultStatistic(this.graphSchema.getElement(labelName)); + } catch (GraphElementNotFoundException e) { + throw Static.RESOURCE.tableNotFound(labelName).ex(); + } + } + + @Override + public boolean isColumnId() { + return this.isColumnId; + } + + @Override + public GraphElement getElement(String s) throws GraphElementNotFoundException { + return this.graphSchema.getElement(s); + } + + @Override + public GraphElement getElement(int i) throws GraphElementNotFoundException { + return this.graphSchema.getElement(i); + } + + @Override + public List getVertexList() { + return this.graphSchema.getVertexList(); + } + + @Override + public List getEdgeList() { + return this.graphSchema.getEdgeList(); + } + + @Override + public Integer getPropertyId(String s) throws GraphPropertyNotFoundException { + return this.graphSchema.getPropertyId(s); + } + + @Override + public String getPropertyName(int i) throws GraphPropertyNotFoundException { + return this.graphSchema.getPropertyName(i); + } + + @Override + public Map getPropertyList(String s) { + return this.graphSchema.getPropertyList(s); + } + + @Override + public Map getPropertyList(int i) { + return this.graphSchema.getPropertyList(i); + } + + @Override + public int getVersion() { + return this.getVersion(); + } + + /** + * An inner class to implement interfaces related to primary keys from {@code Statistic} + */ + private class DefaultStatistic implements Statistic { + private GraphElement element; + private ImmutableBitSet primaryBitSet; + + public DefaultStatistic(GraphElement element) { + Objects.requireNonNull(element); + this.element = element; + List primaryKeys = this.element.getPrimaryKeyList(); + if (ObjectUtils.isEmpty(primaryKeys)) { + this.primaryBitSet = ImmutableBitSet.of(); + } else { + this.primaryBitSet = + ImmutableBitSet.of( + primaryKeys.stream() + .map(k -> k.getId()) + .collect(Collectors.toList())); + } + } + + @Override + public boolean isKey(ImmutableBitSet columns) { + return this.primaryBitSet.isEmpty() ? false : this.primaryBitSet.equals(columns); + } + + @Override + public @Nullable List getKeys() { + return ImmutableList.of(primaryBitSet); + } + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/StatisticSchema.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/StatisticSchema.java new file mode 100644 index 000000000000..1fb1dcab3103 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/schema/StatisticSchema.java @@ -0,0 +1,34 @@ +/* + * 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.schema; + +import com.alibaba.graphscope.compiler.api.schema.GraphSchema; + +import org.apache.calcite.schema.Statistic; + +import java.util.List; + +/** + * Extends {@link GraphSchema} to add {@link Statistic} + */ +public interface StatisticSchema extends GraphSchema { + // get meta for CBO + Statistic getStatistic(List tableName); + + // if the property name need to be converted to id + boolean isColumnId(); +} 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 new file mode 100644 index 000000000000..bcb3bb5e4a34 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/AliasInference.java @@ -0,0 +1,155 @@ +/* + * 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.tools; + +import static org.apache.calcite.linq4j.Nullness.castNonNull; + +import com.alibaba.graphscope.common.ir.rex.RexGraphVariable; + +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.validate.SqlValidatorUtil; +import org.apache.calcite.util.NlsString; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * infer a new alias name from the query given one, and validate if the query given alias is duplicated. + */ +public abstract class AliasInference { + public static final String DEFAULT_NAME = "~DEFAULT"; + public static final int DEFAULT_ID = -1; + public static final String DELIMITER = "."; + + public static final String SIMPLE_NAME(String alias) { + return alias == DEFAULT_NAME ? "DEFAULT" : alias; + } + + /** + * infer alias for graph operators, + * throw exceptions if fieldName has existed in {@code uniqueNameList}, otherwise return fieldName itself or {@link #DEFAULT_NAME} + * @param fieldName + * @param uniqueNameList alias names have stored by previous operators + * @return + * @throws IllegalArgumentException - if fieldName has existed in {@code uniqueNameList} + */ + public static final String inferDefault(@Nullable String fieldName, Set uniqueNameList) + throws IllegalArgumentException { + if (fieldName == null) return DEFAULT_NAME; + if (uniqueNameList.contains(fieldName)) { + throw new IllegalArgumentException( + "alias=" + fieldName + " exists in " + uniqueNameList); + } else { + return fieldName; + } + } + + /** + * infer aliases for project or group expressions + * @param exprList + * @param fieldNameList + * @param uniqueNameList + * @return + * @throws IllegalArgumentException - if fieldNameList has duplicated aliases or some exist in uniqueNameList + */ + public static final List inferProject( + List exprList, + List<@Nullable String> fieldNameList, + Set uniqueNameList) + throws IllegalArgumentException { + ObjectUtils.requireNonEmpty(exprList); + Objects.requireNonNull(fieldNameList); + Objects.requireNonNull(uniqueNameList); + while (fieldNameList.size() < exprList.size()) { + fieldNameList.add(null); + } + for (int i = 0; i < fieldNameList.size(); ++i) { + if (fieldNameList.get(i) == null) { + fieldNameList.set(i, innerInfer(exprList, exprList.get(i), i)); + } else { + String field = fieldNameList.get(i); + if (fieldNameList.lastIndexOf(field) != i || uniqueNameList.contains(field)) { + throw new IllegalArgumentException( + "alias=" + + field + + " exists in " + + CollectionUtils.union(fieldNameList, uniqueNameList)); + } + } + } + for (int i = 0; i < fieldNameList.size(); ++i) { + String name = fieldNameList.get(i); + String originalName = name; + if (name == null || uniqueNameList.contains(name)) { + int j = 0; + if (name == null) { + j = i; + } + do { + name = SqlValidatorUtil.F_SUGGESTER.apply(originalName, j, j++); + } while (uniqueNameList.contains(name)); + fieldNameList.set(i, name); + } + uniqueNameList.add(name); + } + return fieldNameList; + } + + /** + * infer alias for some specific expressions, + * i.e. a -> a, a.name -> name, a.name as b -> b + * @param exprList + * @param expr + * @param i + * @return + */ + private static @Nullable String innerInfer(List exprList, RexNode expr, int i) { + if (expr instanceof RexGraphVariable) { + String name = ((RexGraphVariable) expr).getName(); + String[] nameArray = name.split(Pattern.quote(DELIMITER)); + if (ObjectUtils.isEmpty(nameArray)) { + return null; + } else if (nameArray.length == 1) { + return nameArray[0]; + } else { + return nameArray[1]; + } + } else { + switch (expr.getKind()) { + case CAST: + return innerInfer(exprList, ((RexCall) expr).getOperands().get(0), -1); + case AS: + final RexCall call = (RexCall) expr; + if (i >= 0) { + exprList.set(i, call.getOperands().get(0)); + } + NlsString value = + (NlsString) ((RexLiteral) call.getOperands().get(1)).getValue(); + return castNonNull(value).getValue(); + default: + return null; + } + } + } +} 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 fe43942a1d49..5041db903c71 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 @@ -16,19 +16,31 @@ package com.alibaba.graphscope.common.ir.tools; +import static java.util.Objects.requireNonNull; + +import com.alibaba.graphscope.common.ir.rel.graph.*; +import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalMultiMatch; +import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalSingleMatch; +import com.alibaba.graphscope.common.ir.rel.type.TableConfig; import com.alibaba.graphscope.common.ir.rex.RexGraphVariable; +import com.alibaba.graphscope.common.ir.schema.GraphOptSchema; +import com.alibaba.graphscope.common.ir.schema.StatisticSchema; import com.alibaba.graphscope.common.ir.tools.config.*; import com.google.common.collect.ImmutableList; import org.apache.calcite.plan.*; import org.apache.calcite.rel.*; +import org.apache.calcite.rel.hint.RelHint; +import org.apache.calcite.rel.type.*; import org.apache.calcite.rex.*; import org.apache.calcite.sql.SqlAggFunction; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.tools.RelBuilder; +import org.apache.commons.lang3.ObjectUtils; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.*; +import java.util.stream.Collectors; /** * Integrate interfaces to build algebra structures, @@ -65,12 +77,20 @@ public static GraphBuilder create( * if exist then derive the {@code GraphSchemaType} of the given labels and keep the type in {@link RelNode#getRowType()}, * otherwise throw exceptions * - * 2. validate the existence of the given alias in config, if exist throw exceptions + * 2. validate the existence of the given alias in config, if exist throw duplication exceptions * * @param config * @return */ public GraphBuilder source(SourceConfig config) { + String aliasName = AliasInference.inferDefault(config.getAlias(), new HashSet<>()); + int aliasId = generateAliasId(aliasName, null); + RelNode source = + GraphLogicalSource.create( + (GraphOptCluster) cluster, + getHints(config.getOpt().name(), aliasName, aliasId), + getTableConfig(config.getLabels(), config.getOpt())); + push(source); return this; } @@ -81,6 +101,17 @@ public GraphBuilder source(SourceConfig config) { * @return */ public GraphBuilder expand(ExpandConfig config) { + RelNode input = requireNonNull(peek(), "frame stack is empty"); + String aliasName = + AliasInference.inferDefault(config.getAlias(), uniqueNameList(input, false, true)); + int aliasId = generateAliasId(aliasName, input); + RelNode expand = + GraphLogicalExpand.create( + (GraphOptCluster) cluster, + getHints(config.getOpt().name(), aliasName, aliasId), + input, + getTableConfig(config.getLabels(), GraphOpt.Source.EDGE)); + replaceTop(expand); return this; } @@ -91,9 +122,141 @@ public GraphBuilder expand(ExpandConfig config) { * @return */ public GraphBuilder getV(GetVConfig config) { + RelNode input = requireNonNull(peek(), "frame stack is empty"); + String aliasName = + AliasInference.inferDefault(config.getAlias(), uniqueNameList(input, false, true)); + int aliasId = generateAliasId(aliasName, input); + RelNode getV = + GraphLogicalGetV.create( + (GraphOptCluster) cluster, + getHints(config.getOpt().name(), aliasName, aliasId), + input, + getTableConfig(config.getLabels(), GraphOpt.Source.VERTEX)); + replaceTop(getV); + return this; + } + + /** + * build an algebra structure of {@code GraphLogicalPathExpand} + * + * @param config + * @return + */ + public GraphBuilder pathExpand(PathExpandConfig config) { + RelNode input = requireNonNull(peek(), "frame stack is empty"); + String aliasName = + AliasInference.inferDefault(config.getAlias(), uniqueNameList(input, false, true)); + RexNode offsetNode = config.getOffset() <= 0 ? null : literal(config.getOffset()); + RexNode fetchNode = config.getFetch() < 0 ? null : literal(config.getFetch()); + RelNode expand = Objects.requireNonNull(config.getExpand()); + RelNode getV = Objects.requireNonNull(config.getGetV()); + RelNode pathExpand = + GraphLogicalPathExpand.create( + (GraphOptCluster) cluster, + getHints( + config.getPathOpt().name(), + config.getResultOpt().name(), + aliasName, + generateAliasId(aliasName, input)), + input, + expand, + getV, + offsetNode, + fetchNode); + replaceTop(pathExpand); return this; } + /** + * convert user-given config to {@code TableConfig}, + * derive all table types (labels with properties) depending on the user given labels + * @param labelConfig + * @return + */ + public TableConfig getTableConfig(LabelConfig labelConfig, GraphOpt.Source opt) { + List relOptTables = new ArrayList<>(); + if (!labelConfig.isAll()) { + ObjectUtils.requireNonEmpty(labelConfig.getLabels()); + for (String label : labelConfig.getLabels()) { + relOptTables.add(relOptSchema.getTableForMember(ImmutableList.of(label))); + } + } else if (relOptSchema instanceof GraphOptSchema) { // get all labels + List> allLabels = + getTableNames(opt, ((GraphOptSchema) relOptSchema).getRootSchema()); + for (List label : allLabels) { + relOptTables.add(relOptSchema.getTableForMember(label)); + } + } else { + throw new IllegalArgumentException( + "cannot infer label types from the query given config"); + } + return new TableConfig(relOptTables).isAll(labelConfig.isAll()); + } + + /** + * get all table names for a specific {@code opt} to handle fuzzy conditions, i.e. g.V() + * @param opt + * @return + */ + private List> getTableNames(GraphOpt.Source opt, StatisticSchema rootSchema) { + switch (opt) { + case VERTEX: + return rootSchema.getVertexList().stream() + .map(k -> ImmutableList.of(k.getLabel())) + .collect(Collectors.toList()); + case EDGE: + default: + return rootSchema.getEdgeList().stream() + .map(k -> ImmutableList.of(k.getLabel())) + .collect(Collectors.toList()); + } + } + + public List getHints(String optName, String aliasName, int aliasId) { + RelHint optHint = RelHint.builder("opt").hintOption(optName).build(); + RelHint aliasHint = + RelHint.builder("alias") + .hintOption("name", Objects.requireNonNull(aliasName)) + .hintOption("id", String.valueOf(aliasId)) + .build(); + return ImmutableList.of(optHint, aliasHint); + } + + private List getHints( + String pathOptName, String resultOptName, String aliasName, int aliasId) { + RelHint optHint = + RelHint.builder("opt") + .hintOption("path", pathOptName) + .hintOption("result", resultOptName) + .build(); + RelHint aliasHint = + RelHint.builder("alias") + .hintOption("name", aliasName) + .hintOption("id", String.valueOf(aliasId)) + .build(); + return ImmutableList.of(optHint, aliasHint); + } + + /** + * get all aliases stored by previous operators, to avoid duplicated alias creation + * @param input the input operator + * @param containsAll if the input operator contains all stored alias + * @param isAppend if the current operator need to keep the history + * @return + */ + private Set uniqueNameList( + @Nullable RelNode input, boolean containsAll, boolean isAppend) { + Set uniqueNames = new HashSet<>(); + if (!isAppend || input == null) return uniqueNames; + for (RelDataTypeField field : input.getRowType().getFieldList()) { + uniqueNames.add(field.getName()); + } + if (!containsAll && ObjectUtils.isNotEmpty(input.getInputs())) { + uniqueNames.addAll(uniqueNameList(input.getInput(0), containsAll, isAppend)); + } + return uniqueNames; + } + /** * generate a new alias id for the given alias name * @@ -118,6 +281,11 @@ private int generateAliasId(@Nullable String alias, @Nullable RelNode input) { * @param opt anti or optional */ public GraphBuilder match(RelNode single, GraphOpt.Match opt) { + RelNode input = size() > 0 ? peek() : null; + RelNode match = + GraphLogicalSingleMatch.create((GraphOptCluster) cluster, null, input, single, opt); + if (size() > 0) pop(); + push(match); return this; } @@ -132,6 +300,16 @@ public GraphBuilder match(RelNode single, GraphOpt.Match opt) { * @return */ public GraphBuilder match(RelNode first, Iterable others) { + RelNode input = size() > 0 ? peek() : null; + RelNode match = + GraphLogicalMultiMatch.create( + (GraphOptCluster) cluster, + null, + input, + first, + ImmutableList.copyOf(others)); + if (size() > 0) pop(); + push(match); return this; } @@ -266,4 +444,13 @@ public RelBuilder sortLimit( Iterable nodes) { return this; } + + protected void pop() { + this.build(); + } + + protected void replaceTop(RelNode node) { + pop(); + push(node); + } } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/GraphOpt.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/GraphOpt.java index f3816e589765..be3c0612ac75 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/GraphOpt.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/GraphOpt.java @@ -42,4 +42,14 @@ public enum Match { // by // any others } + + public enum PathExpandPath { + ARBITRARY, + SIMPLE + } + + public enum PathExpandResult { + EndV, + AllV + } } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/PathExpandConfig.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/PathExpandConfig.java index f6153238209a..121844efd240 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/PathExpandConfig.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/tools/config/PathExpandConfig.java @@ -16,10 +16,12 @@ package com.alibaba.graphscope.common.ir.tools.config; +import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalExpand; +import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalGetV; +import com.alibaba.graphscope.common.ir.tools.AliasInference; import com.alibaba.graphscope.common.ir.tools.GraphBuilder; -import com.alibaba.graphscope.common.jna.type.PathOpt; -import com.alibaba.graphscope.common.jna.type.ResultOpt; +import org.apache.calcite.plan.GraphOptCluster; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rex.RexNode; import org.checkerframework.checker.nullness.qual.Nullable; @@ -37,8 +39,8 @@ public class PathExpandConfig { private final int offset; private final int fetch; - private final PathOpt pathOpt; - private final ResultOpt resultOpt; + private final GraphOpt.PathExpandPath pathOpt; + private final GraphOpt.PathExpandResult resultOpt; @Nullable private final String alias; @@ -47,8 +49,8 @@ protected PathExpandConfig( RelNode getV, int offset, int fetch, - ResultOpt resultOpt, - PathOpt pathOpt, + GraphOpt.PathExpandResult resultOpt, + GraphOpt.PathExpandPath pathOpt, @Nullable String alias) { this.expand = Objects.requireNonNull(expand); this.getV = Objects.requireNonNull(getV); @@ -67,11 +69,11 @@ public static Builder newBuilder(GraphBuilder innerBuilder) { return alias; } - public PathOpt getPathOpt() { + public GraphOpt.PathExpandPath getPathOpt() { return pathOpt; } - public ResultOpt getResultOpt() { + public GraphOpt.PathExpandResult getResultOpt() { return resultOpt; } @@ -100,24 +102,46 @@ public static final class Builder { private int offset; private int fetch; - private PathOpt pathOpt; - private ResultOpt resultOpt; + private GraphOpt.PathExpandPath pathOpt; + private GraphOpt.PathExpandResult resultOpt; @Nullable private String alias; protected Builder(GraphBuilder innerBuilder) { this.innerBuilder = innerBuilder; - this.pathOpt = PathOpt.Arbitrary; - this.resultOpt = ResultOpt.EndV; + this.pathOpt = GraphOpt.PathExpandPath.ARBITRARY; + this.resultOpt = GraphOpt.PathExpandResult.EndV; } - // TODO: build expand from config public Builder expand(ExpandConfig config) { + if (this.getV == null && this.expand == null) { + this.expand = + GraphLogicalExpand.create( + (GraphOptCluster) innerBuilder.getCluster(), + innerBuilder.getHints( + config.getOpt().name(), + AliasInference.DEFAULT_NAME, + AliasInference.DEFAULT_ID), + null, + innerBuilder.getTableConfig( + config.getLabels(), GraphOpt.Source.EDGE)); + } return this; } - // TODO: build getV from config public Builder getV(GetVConfig config) { + if (this.expand != null && this.getV == null) { + this.getV = + GraphLogicalGetV.create( + (GraphOptCluster) innerBuilder.getCluster(), + innerBuilder.getHints( + config.getOpt().name(), + AliasInference.DEFAULT_NAME, + AliasInference.DEFAULT_ID), + null, + innerBuilder.getTableConfig( + config.getLabels(), GraphOpt.Source.VERTEX)); + } return this; } @@ -132,12 +156,12 @@ public Builder range(int offset, int fetch) { return this; } - public Builder pathOpt(PathOpt pathOpt) { + public Builder pathOpt(GraphOpt.PathExpandPath pathOpt) { this.pathOpt = pathOpt; return this; } - public Builder resultOpt(ResultOpt resultOpt) { + public Builder resultOpt(GraphOpt.PathExpandResult resultOpt) { this.resultOpt = resultOpt; return this; } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaType.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaType.java new file mode 100644 index 000000000000..554b88d22e52 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaType.java @@ -0,0 +1,89 @@ +/* + * 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.type; + +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.rel.type.StructKind; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +/** + * Denote DataType of an entity or a relation, including opt, label and attributes + */ +public class GraphSchemaType extends RelRecordType { + protected GraphOpt.Source scanOpt; + protected LabelType labelType; + + protected GraphSchemaType(GraphOpt.Source scanOpt) { + this(scanOpt, LabelType.DEFAULT, ImmutableList.of()); + } + + /** + * @param scanOpt entity or relation + * @param labelType + * @param fields attribute fields, each field denoted by {@link RelDataTypeField} which consist of property name, property id and type + */ + public GraphSchemaType( + GraphOpt.Source scanOpt, LabelType labelType, List fields) { + super(StructKind.NONE, fields, false); + this.scanOpt = scanOpt; + this.labelType = labelType; + } + + public GraphOpt.Source getScanOpt() { + return scanOpt; + } + + public LabelType getLabelType() { + return labelType; + } + + @Override + protected void generateTypeString(StringBuilder sb, boolean withDetail) { + sb.append("Graph_Schema_Type"); + + sb.append("("); + Iterator var3 = + Ord.zip((List) Objects.requireNonNull(this.fieldList, "fieldList")).iterator(); + + while (var3.hasNext()) { + Ord ord = (Ord) var3.next(); + if (ord.i > 0) { + sb.append(", "); + } + + RelDataTypeField field = (RelDataTypeField) ord.e; + if (withDetail) { + sb.append(field.getType().getFullTypeString()); + } else { + sb.append(field.getType().toString()); + } + + sb.append(" "); + sb.append(field.getName()); + } + + sb.append(")"); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaTypeList.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaTypeList.java new file mode 100644 index 000000000000..a857ba52be60 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphSchemaTypeList.java @@ -0,0 +1,164 @@ +/* + * 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.type; + +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; + +import org.apache.commons.lang3.ObjectUtils; + +import java.util.*; + +/** + * A list of {@code IrSchemaType}, to denote fuzzy conditions in a vertex or an edge, i.e. g.V() or g.V().hasLabel("person", "software") + */ +public class GraphSchemaTypeList extends GraphSchemaType implements List { + private List schemaTypes; + + protected GraphSchemaTypeList(GraphOpt.Source scanOpt, List schemaTypes) { + super(scanOpt); + this.schemaTypes = schemaTypes; + } + + public static GraphSchemaTypeList create(List list) { + ObjectUtils.requireNonEmpty(list); + GraphOpt.Source scanOpt = list.get(0).getScanOpt(); + List labelOpts = new ArrayList<>(); + for (GraphSchemaType type : list) { + labelOpts.add("{label=" + type.labelType.getLabel() + ", opt=" + type.scanOpt + "}"); + if (type.getScanOpt() != scanOpt) { + throw new IllegalArgumentException( + "fuzzy label types should have the same opt, but is " + labelOpts); + } + } + return new GraphSchemaTypeList(scanOpt, list); + } + + @Override + public int size() { + return this.schemaTypes.size(); + } + + @Override + public boolean isEmpty() { + return this.schemaTypes.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.schemaTypes.contains(o); + } + + @Override + public Iterator iterator() { + return this.schemaTypes.iterator(); + } + + @Override + public Object[] toArray() { + return this.schemaTypes.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return this.schemaTypes.toArray(a); + } + + @Override + public boolean add(GraphSchemaType graphSchemaType) { + return this.schemaTypes.add(graphSchemaType); + } + + @Override + public boolean remove(Object o) { + return this.schemaTypes.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return this.schemaTypes.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return this.schemaTypes.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return this.schemaTypes.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return this.schemaTypes.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return this.schemaTypes.retainAll(c); + } + + @Override + public void clear() { + this.schemaTypes.clear(); + } + + @Override + public GraphSchemaType get(int index) { + return this.schemaTypes.get(index); + } + + @Override + public GraphSchemaType set(int index, GraphSchemaType element) { + return this.schemaTypes.set(index, element); + } + + @Override + public void add(int index, GraphSchemaType element) { + this.schemaTypes.add(index, element); + } + + @Override + public GraphSchemaType remove(int index) { + return this.schemaTypes.remove(index); + } + + @Override + public int indexOf(Object o) { + return this.schemaTypes.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.schemaTypes.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.schemaTypes.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return this.schemaTypes.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.schemaTypes.subList(fromIndex, toIndex); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/LabelType.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/LabelType.java new file mode 100644 index 000000000000..079b4abf9d3b --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/LabelType.java @@ -0,0 +1,96 @@ +/* + * 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.type; + +import org.apache.commons.lang3.StringUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Objects; + +/** + * Maintain label for each Entity or Relation: Entity(label), Relation(Label, srcLabel, dstLabel). + */ +public class LabelType { + public static LabelType DEFAULT = new LabelType(); + + private String label; + private int labelId; + @Nullable private String srcLabel; + @Nullable private Integer srcLabelId; + @Nullable private String dstLabel; + @Nullable private Integer dstLabelId; + + public LabelType() { + this.label = StringUtils.EMPTY; + this.labelId = -1; + } + + public LabelType label(String label) { + Objects.requireNonNull(label); + this.label = label; + return this; + } + + public LabelType labelId(int labelId) { + this.labelId = labelId; + return this; + } + + public LabelType srcLabel(String srcLabel) { + this.srcLabel = srcLabel; + return this; + } + + public LabelType srcLabelId(int srcLabelId) { + this.srcLabelId = srcLabelId; + return this; + } + + public LabelType dstLabel(String dstLabel) { + this.dstLabel = dstLabel; + return this; + } + + public LabelType dstLabelId(int dstLabelId) { + this.dstLabelId = dstLabelId; + return this; + } + + public String getLabel() { + return label; + } + + public int getLabelId() { + return labelId; + } + + public @Nullable String getSrcLabel() { + return srcLabel; + } + + public @Nullable Integer getSrcLabelId() { + return srcLabelId; + } + + public @Nullable String getDstLabel() { + return dstLabel; + } + + public @Nullable Integer getDstLabelId() { + return dstLabelId; + } +} diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/ExpandTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/ExpandTest.java new file mode 100644 index 000000000000..538d0c72541a --- /dev/null +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/ExpandTest.java @@ -0,0 +1,110 @@ +/* + * 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; + +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.config.*; + +import org.apache.calcite.rel.RelNode; +import org.junit.Assert; +import org.junit.Test; + +public class ExpandTest { + // g.V().hasLabel("person").outE("knows") + @Test + public void expand_1_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + RelNode expand = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"))) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"))) + .build(); + Assert.assertEquals( + "GraphLogicalExpand(tableConfig=[{isAll=false, tables=[knows]}], alias=[~DEFAULT]," + + " opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[~DEFAULT], opt=[VERTEX])", + expand.explain().trim()); + } + + // g.V().hasLabel("person").outE("knows").as("x") + @Test + public void expand_2_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + RelNode expand = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"))) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"), + "x")) + .build(); + Assert.assertEquals( + "GraphLogicalExpand(tableConfig=[{isAll=false, tables=[knows]}], alias=[x]," + + " opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[~DEFAULT], opt=[VERTEX])", + expand.explain().trim()); + } + + // path expand: g.V().hasLabel("person").out('1..3', "knows").with('PATH_OPT', + // SIMPLE).with('RESULT_OPT', ALL_V) + @Test + public void expand_3_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + PathExpandConfig pxdConfig = + PathExpandConfig.newBuilder(builder) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"))) + .getV( + new GetVConfig( + GraphOpt.GetV.END, + new LabelConfig(false).addLabel("person"))) + .range(1, 3) + .pathOpt(GraphOpt.PathExpandPath.SIMPLE) + .resultOpt(GraphOpt.PathExpandResult.AllV) + .build(); + RelNode pathExpand = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"))) + .pathExpand(pxdConfig) + .build(); + System.out.println(pathExpand.explain().trim()); + Assert.assertEquals( + "GraphLogicalPathExpand(expand=[GraphLogicalExpand(tableConfig=[{isAll=false," + + " tables=[knows]}], alias=[~DEFAULT], opt=[OUT])\n" + + "], getV=[GraphLogicalGetV(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[~DEFAULT], opt=[END])\n" + + "], offset=[1], fetch=[3], path_opt=[SIMPLE], result_opt=[AllV]," + + " alias=[~DEFAULT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[~DEFAULT], opt=[VERTEX])", + pathExpand.explain().trim()); + } +} diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/GetVTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/GetVTest.java new file mode 100644 index 000000000000..4f2456375e30 --- /dev/null +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/GetVTest.java @@ -0,0 +1,53 @@ +/* + * 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; + +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.config.*; + +import org.junit.Assert; +import org.junit.Test; + +public class GetVTest { + // source("person").as("x").expand("knows").as("y").getV("person").as("x") -> invalid + @Test + public void getV_1_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + try { + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"), + "x")) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"), + "y")) + .getV( + new GetVConfig( + GraphOpt.GetV.END, + new LabelConfig(false).addLabel("person"), + "x")) + .build(); + } catch (IllegalArgumentException e) { + // expected error + return; + } + Assert.fail("should have thrown error for duplicated alias"); + } +} diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/MatchTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/MatchTest.java new file mode 100644 index 000000000000..e522f58e5366 --- /dev/null +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/MatchTest.java @@ -0,0 +1,98 @@ +/* + * 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; + +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.config.*; +import com.google.common.collect.ImmutableList; + +import org.apache.calcite.rel.RelNode; +import org.junit.Assert; +import org.junit.Test; + +public class MatchTest { + // g.V().optional(match(as("x").hasLabel("person").ouE("knows").as("y"))) + @Test + public void match_1_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + RelNode expand = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"), + "x")) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"), + "y")) + .build(); + RelNode match = builder.match(expand, GraphOpt.Match.OPTIONAL).build(); + Assert.assertEquals( + "GraphLogicalSingleMatch(input=[null]," + + " sentence=[GraphLogicalExpand(tableConfig=[{isAll=false, tables=[knows]}]," + + " alias=[y], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[x], opt=[VERTEX])\n" + + "], matchOpt=[OPTIONAL])", + match.explain().trim()); + } + + // g.V().match(as("x").hasLabel("person").ouE("knows").as("y"), + // as("x").hasLabel("person").outE("knows").as("z")) + @Test + public void match_2_test() { + GraphBuilder builder = SourceTest.mockGraphBuilder(); + RelNode expand1 = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"), + "x")) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"), + "y")) + .build(); + RelNode expand2 = + builder.source( + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person"), + "x")) + .expand( + new ExpandConfig( + GraphOpt.Expand.OUT, + new LabelConfig(false).addLabel("knows"), + "z")) + .build(); + RelNode match = builder.match(expand1, ImmutableList.of(expand2)).build(); + Assert.assertEquals( + "GraphLogicalMultiMatch(input=[null]," + + " sentences=[{s0=[GraphLogicalExpand(tableConfig=[{isAll=false," + + " tables=[knows]}], alias=[y], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[x], opt=[VERTEX])\n" + + "], s1=[GraphLogicalExpand(tableConfig=[{isAll=false, tables=[knows]}]," + + " alias=[z], opt=[OUT])\n" + + " GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}]," + + " alias=[x], opt=[VERTEX])\n" + + "]}])", + match.explain().trim()); + } +} diff --git a/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/SourceTest.java b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/SourceTest.java new file mode 100644 index 000000000000..d0814363539f --- /dev/null +++ b/interactive_engine/compiler/src/test/java/com/alibaba/graphscope/common/ir/SourceTest.java @@ -0,0 +1,98 @@ +/* + * 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; + +import com.alibaba.graphscope.common.ir.schema.GraphOptSchema; +import com.alibaba.graphscope.common.ir.schema.GraphSchemaWrapper; +import com.alibaba.graphscope.common.ir.schema.StatisticSchema; +import com.alibaba.graphscope.common.ir.tools.GraphBuilder; +import com.alibaba.graphscope.common.ir.tools.config.GraphOpt; +import com.alibaba.graphscope.common.ir.tools.config.LabelConfig; +import com.alibaba.graphscope.common.ir.tools.config.SourceConfig; +import com.alibaba.graphscope.common.utils.FileUtils; +import com.alibaba.graphscope.compiler.api.schema.GraphSchema; +import com.alibaba.graphscope.compiler.schema.DefaultGraphSchema; + +import org.apache.calcite.jdbc.JavaTypeFactoryImpl; +import org.apache.calcite.plan.GraphOptCluster; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rex.RexBuilder; +import org.junit.Assert; +import org.junit.Test; + +public class SourceTest { + private static final RelDataTypeFactory typeFactory = new JavaTypeFactoryImpl(); + private static final RexBuilder rexBuilder = new RexBuilder(typeFactory); + private static final StatisticSchema schema = new GraphSchemaWrapper(mockGraphSchema(), true); + + // g.V().hasLabel("person") + @Test + public void single_label_test() { + GraphBuilder builder = mockGraphBuilder(); + SourceConfig sourceConfig = + new SourceConfig(GraphOpt.Source.VERTEX, new LabelConfig(false).addLabel("person")); + RelNode source = builder.source(sourceConfig).build(); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=false, tables=[person]}], alias=[~DEFAULT]," + + " opt=[VERTEX])", + source.explain().trim()); + } + + // g.V().hasLabel("person", "software").as("a") + @Test + public void multiple_labels_test() { + GraphBuilder builder = mockGraphBuilder(); + SourceConfig sourceConfig = + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person").addLabel("software"), + "a"); + RelNode source = builder.source(sourceConfig).build(); + Assert.assertEquals( + "GraphLogicalSource(tableConfig=[{isAll=false, tables=[person, software]}]," + + " alias=[a], opt=[VERTEX])", + source.explain().trim()); + } + + @Test + public void multiple_labels_opt_test() { + try { + GraphBuilder builder = mockGraphBuilder(); + SourceConfig sourceConfig = + new SourceConfig( + GraphOpt.Source.VERTEX, + new LabelConfig(false).addLabel("person").addLabel("knows")); + builder.source(sourceConfig).build(); + } catch (IllegalArgumentException e) { + // expected error + return; + } + Assert.fail("person and knows have different opt types, should have thrown errors"); + } + + public static final GraphBuilder mockGraphBuilder() { + GraphOptCluster cluster = GraphOptCluster.create(rexBuilder); + return GraphBuilder.create(null, cluster, new GraphOptSchema(cluster, schema)); + } + + private static GraphSchema mockGraphSchema() { + String inputJson = FileUtils.readJsonFromResource("schema/modern.json"); + GraphSchema graphSchema = DefaultGraphSchema.buildSchemaFromJson(inputJson); + return graphSchema; + } +} diff --git a/interactive_engine/compiler/src/test/resources/schema/modern.json b/interactive_engine/compiler/src/test/resources/schema/modern.json new file mode 100644 index 000000000000..b4df1a8ee16f --- /dev/null +++ b/interactive_engine/compiler/src/test/resources/schema/modern.json @@ -0,0 +1,205 @@ +{ + "types": [ + { + "id": 1, + "label": "person", + "type": "VERTEX", + "isDimensionType": false, + "propertyDefList": [ + { + "id": 1, + "name": "id", + "data_type": "LONG", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "id" + }, + { + "id": 2, + "name": "name", + "data_type": "STRING", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "name" + }, + { + "id": 3, + "name": "age", + "data_type": "INT", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "age" + } + ], + "indexes": [ + { + "name": "PRIMARY_KEY", + "indexType": "PRIMARY_KEY", + "propertyNames": [ + "id" + ] + } + ], + "comment": "", + "version": 1, + "typeOption": { + "storageEngine": "MEMORY" + }, + "gIdToPId": { + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5 + } + }, + { + "id": 2, + "label": "software", + "type": "VERTEX", + "isDimensionType": false, + "propertyDefList": [ + { + "id": 1, + "name": "id", + "data_type": "LONG", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "id" + }, + { + "id": 2, + "name": "name", + "data_type": "STRING", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "name" + }, + { + "id": 3, + "name": "lang", + "data_type": "STRING", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "lang" + } + ], + "indexes": [ + { + "name": "PRIMARY_KEY", + "indexType": "PRIMARY_KEY", + "propertyNames": [ + "id" + ] + } + ], + "comment": "", + "version": 1, + "typeOption": { + "storageEngine": "MEMORY" + }, + "gIdToPId": { + "1": 1, + "2": 2, + "6": 3, + "7": 4 + } + }, + { + "id": 3, + "label": "knows", + "type": "EDGE", + "isDimensionType": false, + "propertyDefList": [ + { + "id": 1, + "name": "id", + "data_type": "LONG", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "id" + }, + { + "id": 2, + "name": "weight", + "data_type": "DOUBLE", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "weight" + } + ], + "indexes": [ + { + "name": "UNIQUE_WITH_EDGE", + "indexType": "UNIQUE_WITH_EDGE", + "propertyNames": [] + } + ], + "comment": "", + "version": 1, + "rawRelationShips": [ + { + "srcVertexLabel": "person", + "dstVertexLabel": "person", + "edgeLabel": "knows" + } + ], + "typeOption": { + "storageEngine": "MEMORY" + }, + "gIdToPId": { + "1": 1, + "8": 2 + } + }, + { + "id": 4, + "label": "created", + "type": "EDGE", + "isDimensionType": false, + "propertyDefList": [ + { + "id": 1, + "name": "id", + "data_type": "LONG", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "id" + }, + { + "id": 2, + "name": "weight", + "data_type": "DOUBLE", + "hasDefaultValue": false, + "defaultValue": null, + "comment": "weight" + } + ], + "indexes": [ + { + "name": "UNIQUE_WITH_EDGE", + "indexType": "UNIQUE_WITH_EDGE", + "propertyNames": [] + } + ], + "comment": "", + "version": 1, + "rawRelationShips": [ + { + "srcVertexLabel": "person", + "dstVertexLabel": "software", + "edgeLabel": "created" + } + ], + "typeOption": { + "storageEngine": "MEMORY" + }, + "gIdToPId": { + "1": 1, + "8": 2 + } + } + ], + "partitionNum": 4, + "version": 12 +}