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


##########
hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/opencypher/CypherTemporalFunctions.java:
##########
@@ -0,0 +1,435 @@
+/*
+ * 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.IdentityHashMap;
+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.Expression;
+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.opencypher.v9_0.util.symbols.CypherType;
+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. The inferred types of the rebuilt expressions
+     * are carried over with the statement.
+     */
+    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");
+            Field typesField = 
CypherAst.class.getDeclaredField("expressionTypes");
+            statementField.setAccessible(true);
+            parametersField.setAccessible(true);
+            typesField.setAccessible(true);
+
+            Object folded = fold.copy(ast.statement());
+            if (fold.bindings.isEmpty()) {
+                return parameters;
+            }
+            Object merged = mergeParameters(parametersField.get(ast), 
fold.bindings);
+            Object types = carryTypes(typesField.get(ast), fold.copies);
+            statementField.set(ast, folded);
+            parametersField.set(ast, merged);
+            typesField.set(ast, types);
+        } catch (Exception | LinkageError e) {
+            logger.warn("Failed to fold temporal functions, keeping the parsed 
statement", e);
+            return parameters;
+        }
+        Map<String, Object> effective = new LinkedHashMap<>(parameters);
+        effective.putAll(fold.bindings);
+        return effective;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object mergeParameters(Object parameters, Map<String, 
Object> bindings) {
+        scala.collection.immutable.Map<String, Object> merged =
+                (scala.collection.immutable.Map<String, Object>) parameters;
+        for (Map.Entry<String, Object> binding : bindings.entrySet()) {
+            merged = merged.$plus(new Tuple2<>(binding.getKey(), 
binding.getValue()));
+        }
+        return merged;
+    }
+
+    /**
+     * Returns the expression type map extended with the types of the rebuilt
+     * expressions, so a statement the fold touched still finds the type of
+     * every expression, for example the valueMap() decision for a RETURN
+     * item that holds a node or relationship.
+     */
+    @SuppressWarnings("unchecked")
+    private static Object carryTypes(Object types, Map<Object, Object> copies) 
{
+        scala.collection.immutable.Map<Expression, CypherType> expressionTypes 
=
+                (scala.collection.immutable.Map<Expression, CypherType>) types;
+        scala.collection.immutable.Map<Expression, CypherType> carried = 
expressionTypes;
+        for (Map.Entry<Object, Object> pair : copies.entrySet()) {
+            if (pair.getKey() == pair.getValue()) {
+                continue;
+            }
+            Option<CypherType> type = expressionTypes.get((Expression) 
pair.getKey());
+            if (type.isDefined()) {
+                carried = carried.$plus(new Tuple2<>((Expression) 
pair.getValue(), type.get()));
+            }
+        }
+        return carried;
+    }
+
+    /**
+     * Returns a copy of the given parameters in which folded temporal values
+     * are replaced by a printable constructor form, so the string translation
+     * stays executable Groovy instead of rendering a date through toString.
+     */
+    public static Map<String, Object> explainParameters(Map<String, Object> 
parameters) {
+        boolean folded = false;
+        for (String name : parameters.keySet()) {

Review Comment:
   ⚠️ Important. Blocking: no. Summary: `evalCypher` calls `fold()` for every 
request, and this line starts the recursive AST walk before checking whether 
any synthetic binding was added. When nothing folds, the traversal still 
records expression mappings and allocates temporary sequence/set lists. Please 
avoid this work when no supported temporal constructor occurs, or add benchmark 
evidence showing the added latency and allocation cost is acceptable. Evidence: 
static trace through `TemporalFold.copy()`, `copySeq()` and `copySet()` on this 
head; no performance benchmark was run.



-- 
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