Skip to content

Commit

Permalink
refactor(interactive): Support Date Type and EXTRACT Operator in …
Browse files Browse the repository at this point in the history
…Cypher Queries (#3169)

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

## What do these changes do?
as titled.

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

## Related issue number

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

Fixes

---------

Co-authored-by: BingqingLyu <[email protected]>
Co-authored-by: Longbin Lai <[email protected]>
  • Loading branch information
3 people authored Sep 8, 2023
1 parent 9f1d776 commit 0896854
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ private int getDataTypeId(DataType dataType) {
return 8;
case STRING_LIST:
return 9;
case UNKNOWN:
case DATE:
return 12;
default:
return 11;
throw new UnsupportedOperationException(
"convert from DataType " + dataType + " to ir core is unsupported yet");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,21 @@ private RelDataType deriveType(GraphProperty property) {
switch (property.getDataType()) {
case BOOL:
return typeFactory.createSqlType(SqlTypeName.BOOLEAN);
case CHAR:
case STRING:
return typeFactory.createSqlType(SqlTypeName.CHAR);
case SHORT:
case INT:
return typeFactory.createSqlType(SqlTypeName.INTEGER);
case LONG:
return typeFactory.createSqlType(SqlTypeName.BIGINT);
case FLOAT:
return typeFactory.createSqlType(SqlTypeName.FLOAT);
case DOUBLE:
return typeFactory.createSqlType(SqlTypeName.DOUBLE);
case DATE:
return typeFactory.createSqlType(
SqlTypeName.DATE); // todo: support Time and DateTime in GraphSchema
default:
throw new UnsupportedOperationException(
"type " + property.getDataType().name() + " not supported");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,14 @@ public static DataType toDataType(Object type) {
throw new UnsupportedOperationException(
"unsupported primitive type: " + value);
}
} else {
throw new UnsupportedOperationException("unsupported type: " + type);
} else if ((value = typeMap.get("date")) instanceof Map) {
Object format = ((Map) value).get("date_format");
if (format != null && format.toString().equals("DF_YYYY_MM_DD")) {
return DataType.DATE;
}
}
} else {
throw new UnsupportedOperationException("unsupported type: " + type);
}
throw new UnsupportedOperationException("unsupported type: " + type);
}

/**
Expand Down Expand Up @@ -259,8 +261,11 @@ public static final DataType toDataType(int ordinal) {
return DataType.DOUBLE_LIST;
case 9:
return DataType.STRING_LIST;
case 12:
return DataType.DATE;
default:
return DataType.UNKNOWN;
throw new UnsupportedOperationException(
"convert from ir core type " + ordinal + " to DataType is unsupported yet");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;

import java.util.List;

/**
* convert an expression in calcite to logical expression in ir_core
*/
Expand All @@ -49,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.EXTRACT) {
return visitExtract(call);
} else if (call.getOperands().size() == 1) {
return visitUnaryOperator(call);
} else {
Expand Down Expand Up @@ -97,6 +101,23 @@ private OuterExpression.Expression visitArrayValueConstructor(RexCall call) {
.build();
}

private OuterExpression.Expression visitExtract(RexCall call) {
List<RexNode> operands = call.getOperands();
Preconditions.checkArgument(
operands.size() == 2 && operands.get(0) instanceof RexLiteral,
"'EXTRACT' operator has invalid operands " + operands);
OuterExpression.Expression.Builder exprBuilder = OuterExpression.Expression.newBuilder();
exprBuilder.addOperators(
OuterExpression.ExprOpr.newBuilder()
.setExtract(
OuterExpression.Extract.newBuilder()
.setInterval(
Utils.protoInterval((RexLiteral) operands.get(0)))
.setDataTime(operands.get(1).accept(this)))
.setNodeType(Utils.protoIrDataType(call.getType(), isColumnId)));
return exprBuilder.build();
}

private OuterExpression.Expression visitUnaryOperator(RexCall call) {
SqlOperator operator = call.getOperator();
RexNode operand = call.getOperands().get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
import com.alibaba.graphscope.gaia.proto.DataType;
import com.alibaba.graphscope.gaia.proto.GraphAlgebra;
import com.alibaba.graphscope.gaia.proto.OuterExpression;
import com.google.common.base.Preconditions;
import com.google.protobuf.Int32Value;

import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.util.NlsString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -224,6 +227,8 @@ public static final Common.DataType protoBasicDataType(RelDataType basicType) {
+ elementType.getSqlTypeName()
+ " is unsupported yet");
}
case DATE:
return Common.DataType.DATE;
default:
throw new UnsupportedOperationException(
"basic type " + basicType.getSqlTypeName() + " is unsupported yet");
Expand Down Expand Up @@ -328,4 +333,27 @@ public static final DataType.GraphDataType.GraphElementLabel protoElementLabel(
}
return builder.build();
}

public static final OuterExpression.Extract.Interval protoInterval(RexLiteral literal) {
Preconditions.checkArgument(
literal.getType().getSqlTypeName() == SqlTypeName.SYMBOL,
"interval should be an literal of 'SYMBOL' type");
TimeUnit timeUnit = literal.getValueAs(TimeUnit.class);
switch (timeUnit) {
case YEAR:
return OuterExpression.Extract.Interval.YEAR;
case MONTH:
return OuterExpression.Extract.Interval.MONTH;
case DAY:
return OuterExpression.Extract.Interval.DAY;
case HOUR:
return OuterExpression.Extract.Interval.HOUR;
case MINUTE:
return OuterExpression.Extract.Interval.MINUTE;
case SECOND:
return OuterExpression.Extract.Interval.SECOND;
default:
throw new UnsupportedOperationException("unsupported interval type " + timeUnit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.IntervalSqlType;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.Litmus;
Expand Down Expand Up @@ -580,6 +581,20 @@ private RexNode call_(SqlOperator operator, List<RexNode> operandList) {
RelDataType returnType = operator.inferReturnType(callBinding);
// derive unknown types of operands
operandList = inferOperandTypes(operator, returnType, operandList);
if (operator.getKind() == SqlKind.EXTRACT) {
RexNode intervalOperand = operandList.get(0);
if (intervalOperand instanceof RexLiteral
&& ((RexLiteral) intervalOperand).isNull()
&& intervalOperand.getType() instanceof IntervalSqlType) {
IntervalSqlType intervalType = (IntervalSqlType) intervalOperand.getType();
List<RexNode> newOperands = Lists.newArrayList();
newOperands.add(
getRexBuilder()
.makeFlag(intervalType.getIntervalQualifier().getStartUnit()));
newOperands.add(operandList.get(1));
operandList = newOperands;
}
}
final RexBuilder builder = cluster.getRexBuilder();
return builder.makeCall(returnType, operator, operandList);
}
Expand Down Expand Up @@ -621,7 +636,8 @@ private boolean isCurrentSupported(SqlOperator operator) {
|| (sqlKind == SqlKind.NOT)
|| sqlKind == SqlKind.ARRAY_VALUE_CONSTRUCTOR
|| sqlKind == SqlKind.IS_NULL
|| sqlKind == SqlKind.IS_NOT_NULL;
|| sqlKind == SqlKind.IS_NOT_NULL
|| sqlKind == SqlKind.EXTRACT;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,13 @@ public static final SqlFunction USER_DEFINED_PROCEDURE(StoredProcedureMeta meta)
ReturnTypes.explicit(SqlTypeName.ANY).andThen(SqlTypeTransforms.TO_ARRAY),
GraphInferTypes.FIRST_KNOWN,
OperandTypes.ANY);

public static final SqlFunction EXTRACT =
new SqlFunction(
"EXTRACT",
SqlKind.EXTRACT,
ReturnTypes.BIGINT_NULLABLE,
null,
GraphOperandTypes.INTERVALINTERVAL_INTERVALDATETIME,
SqlFunctionCategory.SYSTEM);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package com.alibaba.graphscope.cypher.antlr4.visitor;

import com.alibaba.graphscope.common.ir.rel.type.group.GraphAggCall;
import com.alibaba.graphscope.common.ir.rex.RexGraphVariable;
import com.alibaba.graphscope.common.ir.rex.RexTmpVariable;
import com.alibaba.graphscope.common.ir.tools.GraphBuilder;
import com.alibaba.graphscope.common.ir.tools.GraphRexBuilder;
import com.alibaba.graphscope.common.ir.tools.GraphStdOperatorTable;
import com.alibaba.graphscope.common.ir.type.GraphProperty;
import com.alibaba.graphscope.common.ir.type.GraphSchemaType;
import com.alibaba.graphscope.cypher.antlr4.visitor.type.ExprVisitorResult;
import com.alibaba.graphscope.grammar.CypherGSBaseVisitor;
import com.alibaba.graphscope.grammar.CypherGSParser;
Expand All @@ -31,12 +33,16 @@
import com.google.common.collect.Lists;

import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexSubQuery;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.tools.RelBuilder;
import org.apache.commons.lang3.ObjectUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -198,8 +204,16 @@ public ExprVisitorResult visitOC_PropertyOrLabelsExpression(
throw new IllegalArgumentException("cannot get property from an literal");
} else {
String aliasName = ctx.oC_Atom().oC_Variable().getText();
RexGraphVariable variable = builder.variable(aliasName);
String propertyName = ctx.oC_PropertyLookup().oC_PropertyKeyName().getText();
return new ExprVisitorResult(builder.variable(aliasName, propertyName));
RexNode expr =
(variable.getType() instanceof GraphSchemaType)
? builder.variable(aliasName, propertyName)
: builder.call(
GraphStdOperatorTable.EXTRACT,
createIntervalLiteral(propertyName),
variable);
return new ExprVisitorResult(expr);
}
}
}
Expand Down Expand Up @@ -508,4 +522,11 @@ public int generate(@Nullable String paramName) {
public ImmutableMap<Integer, String> getDynamicParams() {
return this.paramsBuilder.build();
}

private RexLiteral createIntervalLiteral(String fieldName) {
TimeUnit timeUnit = TimeUnit.valueOf(fieldName.toUpperCase());
SqlIntervalQualifier intervalQualifier =
new SqlIntervalQualifier(timeUnit, null, SqlParserPos.ZERO);
return builder.getRexBuilder().makeIntervalLiteral(null, intervalQualifier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public abstract class GraphOperandTypes {
public static final SqlSingleOperandTypeChecker BOOLEAN_BOOLEAN =
family(SqlTypeFamily.BOOLEAN, SqlTypeFamily.BOOLEAN);

public static final SqlSingleOperandTypeChecker INTERVALINTERVAL_INTERVALDATETIME =
OperandTypes.or(INTERVAL_SAME_SAME, INTERVAL_DATETIME);

/**
* create {@code RexFamilyOperandTypeChecker} to validate type based on {@code RexNode}
* @param families
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,19 @@ public void schema_config_test() throws Exception {
IrGraphSchema graphSchema = new IrGraphSchema(new LocalMetaDataReader(configs));
Assert.assertEquals(
"DefaultGraphVertex{labelId=0, label=person,"
+ " propertyList=[DefaultGraphProperty{id=0, name=id, dataType=LONG},"
+ " DefaultGraphProperty{id=1, name=name, dataType=STRING},"
+ " DefaultGraphProperty{id=2, name=age, dataType=INT}], primaryKeyList=[id]}",
+ " propertyList=[DefaultGraphProperty{id=0, name=id, dataType=LONG},"
+ " DefaultGraphProperty{id=1, name=name, dataType=STRING},"
+ " DefaultGraphProperty{id=2, name=age, dataType=INT}],"
+ " primaryKeyList=[id]}",
graphSchema.getElement("person").toString());
Assert.assertEquals(
"DefaultGraphVertex{labelId=1, label=software,"
+ " propertyList=[DefaultGraphProperty{id=0, name=id, dataType=LONG},"
+ " DefaultGraphProperty{id=1, name=name, dataType=STRING},"
+ " DefaultGraphProperty{id=2, name=lang, dataType=STRING},"
+ " DefaultGraphProperty{id=3, name=creationDate, dataType=DATE}],"
+ " primaryKeyList=[id]}",
graphSchema.getElement("software").toString());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.runtime.CalciteException;
import org.junit.Assert;
import org.junit.Test;

Expand Down Expand Up @@ -262,4 +263,45 @@ public void where_8_test() {
+ "], matchOpt=[INNER])",
after.explain().trim());
}

@Test
public void where_9_test() {
RelNode where =
Utils.eval(
"Match (a:software) With a.creationDate as creationDate Where"
+ " creationDate.month > 1990 and creationDate.day = 12 Return"
+ " creationDate")
.build();
Assert.assertEquals(
"GraphLogicalProject(creationDate=[creationDate], isAppend=[false])\n"
+ " LogicalFilter(condition=[AND(>(EXTRACT(FLAG(MONTH), creationDate), 1990),"
+ " =(EXTRACT(FLAG(DAY), creationDate), 12))])\n"
+ " GraphLogicalProject(creationDate=[a.creationDate], isAppend=[false])\n"
+ " GraphLogicalSource(tableConfig=[{isAll=false, tables=[software]}],"
+ " alias=[a], opt=[VERTEX])",
where.explain().trim());
}

// expect to throw exceptions
@Test
public void where_10_test() {
try {
RelNode where =
Utils.eval(
"Match (a:software) With a.name as creationDate Where"
+ " creationDate.month > 1990 and creationDate.day = 12"
+ " Return creationDate")
.build();
} catch (CalciteException e) {
Assert.assertTrue(
e.getMessage()
.contains(
"Cannot apply EXTRACT to arguments of type 'EXTRACT(<INTERVAL"
+ " MONTH>, <CHAR(1)>)'. Supported form(s):"
+ " 'EXTRACT(<DATETIME_INTERVAL>, <DATETIME_INTERVAL>)'\n"
+ "'EXTRACT(<DATETIME_INTERVAL>, <DATETIME>)'"));
return;
}
Assert.fail("should have thrown exceptions for property 'name' is not a date type");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ schema:
property_name: lang
property_type:
primitive_type: DT_STRING
- property_id: 3
property_name: creationDate
property_type:
date:
date_format: DF_YYYY_MM_DD
primary_keys:
- id
edge_types:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
},
"data_type": 4,
"is_primary_key": false
},
{
"key": {
"id": 3,
"name": "creationDate"
},
"data_type": 12,
"is_primary_key": false
}
]
},
Expand Down
Loading

0 comments on commit 0896854

Please sign in to comment.