diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java index e6679951cda7..c5a60a0663ff 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlArrayValueConstructor.java @@ -16,6 +16,8 @@ package com.alibaba.graphscope.common.ir.rex.operator; +import com.alibaba.graphscope.common.ir.type.GraphTypeFactoryImpl; + import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.sql.SqlCallBinding; @@ -43,7 +45,11 @@ public RelDataType inferReturnType(SqlOperatorBinding opBinding) { RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); List argTypes = opBinding.collectOperandTypes(); RelDataType componentType = getComponentType(typeFactory, argTypes); - return SqlTypeUtil.createArrayType(typeFactory, componentType, false); + if (componentType != null && componentType.getSqlTypeName() != SqlTypeName.ANY) { + return SqlTypeUtil.createArrayType(typeFactory, componentType, false); + } else { + return ((GraphTypeFactoryImpl) typeFactory).createArbitraryArrayType(argTypes, false); + } } // operands of array value constructor can be any, even if empty, i.e [] diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java index 6933df49636d..7d55978caca3 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/rex/operator/SqlMapValueConstructor.java @@ -16,6 +16,8 @@ package com.alibaba.graphscope.common.ir.rex.operator; +import com.alibaba.graphscope.common.ir.type.GraphTypeFactoryImpl; + import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.sql.SqlCallBinding; @@ -24,7 +26,6 @@ import org.apache.calcite.sql.fun.SqlMultisetValueConstructor; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; -import org.apache.calcite.util.Pair; import org.apache.calcite.util.Static; import org.apache.calcite.util.Util; import org.checkerframework.checker.nullness.qual.Nullable; @@ -44,11 +45,16 @@ public SqlMapValueConstructor() { public RelDataType inferReturnType(SqlOperatorBinding opBinding) { RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); List argTypes = opBinding.collectOperandTypes(); - Pair type = - Pair.of( - getComponentType(typeFactory, Util.quotientList(argTypes, 2, 0)), - getComponentType(typeFactory, Util.quotientList(argTypes, 2, 1))); - return SqlTypeUtil.createMapType(opBinding.getTypeFactory(), type.left, type.right, false); + List keyTypes = Util.quotientList(argTypes, 2, 0); + List valueTypes = Util.quotientList(argTypes, 2, 1); + RelDataType keyType = typeFactory.leastRestrictive(keyTypes); + RelDataType valueType = getComponentType(typeFactory, valueTypes); + if (valueType != null && valueType.getSqlTypeName() != SqlTypeName.ANY) { + return SqlTypeUtil.createMapType(opBinding.getTypeFactory(), keyType, valueType, false); + } else { + return ((GraphTypeFactoryImpl) typeFactory) + .createArbitraryMapType(keyType, valueTypes, false); + } } @Override diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java index 3e57e4e77d3f..6d138aa08f81 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/RexToProtoConverter.java @@ -51,6 +51,8 @@ public OuterExpression.Expression visitCall(RexCall call) { return visitCase(call); } else if (operator.getKind() == SqlKind.ARRAY_VALUE_CONSTRUCTOR) { return visitArrayValueConstructor(call); + } else if (operator.getKind() == SqlKind.MAP_VALUE_CONSTRUCTOR) { + return visitMapValueConstructor(call); } else if (operator.getKind() == SqlKind.EXTRACT) { return visitExtract(call); } else if (call.getOperands().size() == 1) { @@ -101,6 +103,35 @@ private OuterExpression.Expression visitArrayValueConstructor(RexCall call) { .build(); } + private OuterExpression.Expression visitMapValueConstructor(RexCall call) { + OuterExpression.VariableKeyValues.Builder varMapBuilder = + OuterExpression.VariableKeyValues.newBuilder(); + List operands = call.getOperands(); + for (int i = 0; i < operands.size() - 1; i += 2) { + RexNode key = operands.get(i); + RexNode value = operands.get(i + 1); + Preconditions.checkArgument( + key instanceof RexLiteral, + "key type of 'MAP_VALUE_CONSTRUCTOR' should be 'literal', but is " + + key.getClass()); + Preconditions.checkArgument( + value instanceof RexGraphVariable, + "value type of 'MAP_VALUE_CONSTRUCTOR' should be 'variable', but is " + + value.getClass()); + varMapBuilder.addKeyVals( + OuterExpression.VariableKeyValue.newBuilder() + .setKey(key.accept(this).getOperators(0).getConst()) + .setValue(value.accept(this).getOperators(0).getVar()) + .build()); + } + return OuterExpression.Expression.newBuilder() + .addOperators( + OuterExpression.ExprOpr.newBuilder() + .setMap(varMapBuilder) + .setNodeType(Utils.protoIrDataType(call.getType(), isColumnId))) + .build(); + } + private OuterExpression.Expression visitExtract(RexCall call) { List operands = call.getOperands(); Preconditions.checkArgument( diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java index 1245a82bb6d6..4bd4a9fb215c 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/runtime/proto/Utils.java @@ -256,6 +256,7 @@ public static final DataType.IrDataType protoIrDataType( + " to IrDataType is unsupported yet"); case MULTISET: case ARRAY: + case MAP: logger.warn("multiset or array type can not be converted to any ir core data type"); return DataType.IrDataType.newBuilder().build(); default: diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryArrayType.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryArrayType.java new file mode 100644 index 000000000000..de91c4e0d76a --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryArrayType.java @@ -0,0 +1,48 @@ +/* + * 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.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.type.AbstractSqlType; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.Collections; +import java.util.List; + +/** + * introduce a new array type to allow different component types in a single array, + * to support {@code ListLiteral} in cypher, i.e. [a.name, a, b.age] + */ +public class ArbitraryArrayType extends AbstractSqlType { + private final List componentTypes; + + public ArbitraryArrayType(List componentTypes, boolean isNullable) { + super(SqlTypeName.ARRAY, isNullable, null); + this.componentTypes = ObjectUtils.requireNonEmpty(componentTypes); + this.computeDigest(); + } + + @Override + protected void generateTypeString(StringBuilder sb, boolean withDetail) { + sb.append("(" + this.componentTypes.toString() + ") ARRAY"); + } + + public List getComponentTypes() { + return Collections.unmodifiableList(componentTypes); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryMapType.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryMapType.java new file mode 100644 index 000000000000..8cc878324d75 --- /dev/null +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/ArbitraryMapType.java @@ -0,0 +1,57 @@ +/* + * 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.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.type.AbstractSqlType; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * introduce a new map type to allow different value types in a single map, + * to support {@code MapLiteral} in cypher, i.e. [name: a.name, a: a, age: b.age] + */ +public class ArbitraryMapType extends AbstractSqlType { + private final RelDataType keyType; + private final List valueTypes; + + protected ArbitraryMapType( + RelDataType keyType, List valueTypes, boolean isNullable) { + super(SqlTypeName.MAP, isNullable, null); + this.keyType = Objects.requireNonNull(keyType); + this.valueTypes = ObjectUtils.requireNonEmpty(valueTypes); + this.computeDigest(); + } + + @Override + protected void generateTypeString(StringBuilder sb, boolean withDetail) { + sb.append("(" + keyType.toString() + ", " + valueTypes.toString() + ") MAP"); + } + + @Override + public RelDataType getKeyType() { + return this.keyType; + } + + public List getValueTypes() { + return Collections.unmodifiableList(this.valueTypes); + } +} diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeFactoryImpl.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeFactoryImpl.java index b1b96fb0b9c4..6e886b4e0560 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeFactoryImpl.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/common/ir/type/GraphTypeFactoryImpl.java @@ -23,6 +23,7 @@ import org.apache.calcite.rel.type.RelDataType; import java.nio.charset.Charset; +import java.util.List; public class GraphTypeFactoryImpl extends JavaTypeFactoryImpl { private final Configs configs; @@ -63,4 +64,14 @@ public RelDataType createTypeWithNullability(RelDataType type, boolean nullable) public Charset getDefaultCharset() { return Charset.forName(FrontendConfig.CALCITE_DEFAULT_CHARSET.get(configs)); } + + public RelDataType createArbitraryArrayType( + List componentTypes, boolean isNullable) { + return new ArbitraryArrayType(componentTypes, isNullable); + } + + public RelDataType createArbitraryMapType( + RelDataType keyType, List valueTypes, boolean isNullable) { + return new ArbitraryMapType(keyType, valueTypes, isNullable); + } } diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/antlr4/visitor/ExpressionVisitor.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/antlr4/visitor/ExpressionVisitor.java index fec064c5c98d..d2f11c3c76e7 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/antlr4/visitor/ExpressionVisitor.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/antlr4/visitor/ExpressionVisitor.java @@ -272,6 +272,38 @@ public ExprVisitorResult visitOC_ListLiteral(CypherGSParser.OC_ListLiteralContex builder.call(GraphStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR, expressions)); } + @Override + public ExprVisitorResult visitOC_MapLiteral(CypherGSParser.OC_MapLiteralContext ctx) { + List keys = + ctx.oC_PropertyKeyName().stream() + .map(k -> k.getText()) + .collect(Collectors.toList()); + List values = + ctx.oC_Expression().stream() + .map(k -> visitOC_Expression(k)) + .collect(Collectors.toList()); + Preconditions.checkArgument( + keys.size() == values.size(), + "keys size=" + + keys.size() + + " is not consistent with values size=" + + values.size() + + " in MapLiteral"); + List aggCallList = Lists.newArrayList(); + List expressions = Lists.newArrayList(); + for (int i = 0; i < keys.size(); ++i) { + ExprVisitorResult valueExpr = values.get(i); + if (!valueExpr.getAggCalls().isEmpty()) { + aggCallList.addAll(valueExpr.getAggCalls()); + } + expressions.add(builder.literal(keys.get(i))); + expressions.add(valueExpr.getExpr()); + } + return new ExprVisitorResult( + aggCallList, + builder.call(GraphStdOperatorTable.MAP_VALUE_CONSTRUCTOR, expressions)); + } + @Override public ExprVisitorResult visitOC_Parameter(CypherGSParser.OC_ParameterContext ctx) { String paramName = ctx.oC_SymbolicName().getText(); diff --git a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/result/CypherRecordParser.java b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/result/CypherRecordParser.java index 7b394d2df623..0c0afc734a73 100644 --- a/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/result/CypherRecordParser.java +++ b/interactive_engine/compiler/src/main/java/com/alibaba/graphscope/cypher/result/CypherRecordParser.java @@ -16,17 +16,18 @@ package com.alibaba.graphscope.cypher.result; -import com.alibaba.graphscope.common.ir.type.GraphLabelType; -import com.alibaba.graphscope.common.ir.type.GraphPathType; -import com.alibaba.graphscope.common.ir.type.GraphSchemaType; +import com.alibaba.graphscope.common.ir.type.*; import com.alibaba.graphscope.common.result.RecordParser; import com.alibaba.graphscope.gaia.proto.Common; import com.alibaba.graphscope.gaia.proto.IrResult; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.sql.type.ArraySqlType; +import org.apache.calcite.sql.type.MapSqlType; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.NotImplementedException; import org.checkerframework.checker.nullness.qual.Nullable; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class CypherRecordParser implements RecordParser { @@ -82,7 +84,23 @@ protected AnyValue parseEntry(IrResult.Entry entry, @Nullable RelDataType dataTy switch (dataType.getSqlTypeName()) { case MULTISET: case ARRAY: - return parseCollection(entry.getCollection(), dataType.getComponentType()); + if (dataType instanceof ArraySqlType) { + return parseCollection(entry.getCollection(), dataType.getComponentType()); + } else if (dataType instanceof ArbitraryArrayType) { + return parseCollection( + entry.getCollection(), + ((ArbitraryArrayType) dataType).getComponentTypes()); + } + case MAP: + if (dataType instanceof MapSqlType) { + return parseKeyValues( + entry.getMap(), dataType.getKeyType(), dataType.getValueType()); + } else if (dataType instanceof ArbitraryMapType) { + return parseKeyValues( + entry.getMap(), + dataType.getKeyType(), + ((ArbitraryMapType) dataType).getValueTypes()); + } default: return parseElement(entry.getElement(), dataType); } @@ -102,8 +120,7 @@ protected AnyValue parseElement(IrResult.Element element, @Nullable RelDataType } } - protected AnyValue parseCollection( - IrResult.Collection collection, @Nullable RelDataType componentType) { + protected AnyValue parseCollection(IrResult.Collection collection, RelDataType componentType) { switch (componentType.getSqlTypeName()) { case BOOLEAN: Boolean[] boolObjs = @@ -132,7 +149,6 @@ protected AnyValue parseCollection( .map(k -> k.getObject().getStr()) .toArray(String[]::new)); case ROW: - case ANY: return VirtualValues.fromList( collection.getCollectionList().stream() .map(k -> parseElement(k, componentType)) @@ -143,6 +159,54 @@ protected AnyValue parseCollection( } } + protected AnyValue parseCollection( + IrResult.Collection collection, List componentTypes) { + List elements = collection.getCollectionList(); + Preconditions.checkArgument( + elements.size() == componentTypes.size(), + "Collection element size=" + + elements.size() + + " is not consistent with type size=" + + componentTypes.size()); + List values = Lists.newArrayList(); + for (int i = 0; i < elements.size(); ++i) { + values.add(parseElement(elements.get(i), componentTypes.get(i))); + } + return VirtualValues.fromList(values); + } + + protected AnyValue parseKeyValues( + IrResult.KeyValues keyValues, RelDataType keyType, RelDataType valueType) { + Map valueMap = Maps.newLinkedHashMap(); + keyValues + .getKeyValuesList() + .forEach( + entry -> { + valueMap.put( + entry.getKey().getStr(), + parseElement(entry.getValue(), valueType)); + }); + return VirtualValues.fromMap(valueMap, valueMap.size(), 0); + } + + protected AnyValue parseKeyValues( + IrResult.KeyValues keyValues, RelDataType keyType, List valueTypes) { + List entries = keyValues.getKeyValuesList(); + Preconditions.checkArgument( + entries.size() == valueTypes.size(), + "KeyValues entry size=" + + entries.size() + + " is not consistent with value type size=" + + valueTypes.size()); + Map valueMap = Maps.newLinkedHashMap(); + for (int i = 0; i < entries.size(); ++i) { + IrResult.KeyValues.KeyValue entry = entries.get(i); + valueMap.put( + entry.getKey().getStr(), parseElement(entry.getValue(), valueTypes.get(i))); + } + return VirtualValues.fromMap(valueMap, valueMap.size(), 0); + } + protected NodeValue parseVertex(IrResult.Vertex vertex, @Nullable RelDataType dataType) { return VirtualValues.nodeValue( vertex.getId(),