bitflicker64 commented on code in PR #3370:
URL: https://github.com/apache/hugegraph/pull/3370#discussion_r4305308335


##########
hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherTemporalFunctions.java:
##########
@@ -0,0 +1,393 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hugegraph.opencypher;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.opencypher.gremlin.translation.CypherAst;
+import org.opencypher.v9_0.expressions.FunctionInvocation;
+import org.opencypher.v9_0.expressions.Parameter;
+import org.opencypher.v9_0.expressions.StringLiteral;
+import org.opencypher.v9_0.util.InputPosition;
+import org.opencypher.v9_0.util.symbols.AnyType;
+import org.slf4j.Logger;
+
+import scala.Option;
+import scala.Product;
+import scala.Some;
+import scala.Tuple2;
+import scala.collection.JavaConverters;
+import scala.collection.Seq;
+
+/**
+ * Rewrites the Cypher temporal constructor functions that the 
cypher-for-gremlin
+ * translator does not implement into parameters bound to native date values.
+ * <p>
+ * The translator's function list is fixed inside its own dependency, so the
+ * supported constructors (date, datetime, localdatetime) are folded into the
+ * parsed statement right before translation: each call with a foldable 
argument
+ * becomes a synthetic bound parameter and the date value is added to the
+ * bindings. Calls that cannot be folded are left in place and keep failing
+ * with the translator's original error, as do all other temporal functions.
+ */
+public final class CypherTemporalFunctions {
+
+    private static final Logger logger = 
getLogger(CypherTemporalFunctions.class);
+
+    private static final String DATE = "date";
+    private static final String DATETIME = "datetime";
+    private static final String LOCALDATETIME = "localdatetime";
+
+    // Two leading spaces keep the prefix away from ordinary parameter names;
+    // backtick-escaped Cypher parameters can still carry it, so generated
+    // names skip every one the request already occupies.
+    private static final String SYNTHETIC_PREFIX = "  cypher.fold.";
+
+    private CypherTemporalFunctions() {
+    }
+
+    /**
+     * Folds the supported temporal constructors in the parsed statement and
+     * returns the effective request parameters for translation: the same
+     * instance when nothing was folded, a copy carrying the synthetic
+     * bindings otherwise. The parsed statement is only replaced when the
+     * rewrite completed without errors, so a fold failure leaves the query
+     * exactly as it was parsed.
+     */
+    public static Map<String, Object> fold(CypherAst ast, Map<String, Object> 
parameters) {
+        TemporalFold fold = new TemporalFold(parameters);
+        try {
+            Field statementField = 
CypherAst.class.getDeclaredField("statement");
+            Field parametersField = 
CypherAst.class.getDeclaredField("parameters");
+            statementField.setAccessible(true);
+            parametersField.setAccessible(true);
+
+            Object folded = fold.copy(ast.statement());
+            if (fold.bindings.isEmpty()) {
+                return parameters;
+            }
+            Object merged = mergeParameters(parametersField.get(ast), 
fold.bindings);
+            statementField.set(ast, folded);

Review Comment:
   Important: Only `statement` and `parameters` are replaced here. `CypherAst` 
also keeps `expressionTypes`, a `Map[Expression, CypherType]` built from the 
parsed statement, and the walkers look types up by structural equality. Every 
expression that `rebuild` copies now holds a `Parameter` where the original 
held a `FunctionInvocation`, so `expressionTypes.get` returns nothing for it. 
`ProjectionWalker.finalizeProjection` uses `qualifiedType(expression)` to 
decide whether a RETURN item needs `valueMap()`, so a node or relationship item 
that contains a folded call is returned as a raw element. `getReturnTypes` was 
computed at parse time and still says `Node`, so 
`ReturnNormalizer.normalizeElement` casts the element to `Map` and throws.
   
   I ran parse, fold, translate and `ReturnNormalizer` at fe5daa6d on a 
TinkerGraph 3.8.1 graph:
   - `MATCH (n) RETURN CASE WHEN n.d > date('2000-01-01') THEN n END AS m` 
throws `ClassCastException: TinkerVertex cannot be cast to java.util.Map`. The 
same query with `$d` bound to a `Date` returns `{m={_type=node, ...}}`.
   - `MATCH (n) RETURN collect(CASE WHEN n.d > date('2000-01-01') THEN n END) 
AS ns` throws the same exception.
   - `MATCH (a)-[r]->(b) RETURN CASE WHEN r.since > date('2000-01-01') THEN r 
END AS rel` throws `TinkerEdge cannot be cast to java.util.Map`.
   
   The new docs say the constructors work wherever a bound value is accepted. 
Here the request fails with an internal cast error instead of a result, or the 
old `Unknown function` message.
   
   Please carry the types over to the rebuilt nodes before the swap. For 
example, record each `original -> copy` pair for `Expression` nodes in an 
`IdentityHashMap` inside `copy()`, then read the `expressionTypes` field the 
same way as `statement` and add `copy -> type` for every original that has a 
type. I tried exactly that on this head: all three queries above return the 
same rows as their `$d` versions, and the 18 unit tests still pass. Please also 
add a unit test that checks the CASE query translates with `valueMap`, like its 
`$d` version does.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to