This is an automated email from the ASF dual-hosted git repository.

lgbo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 0b317a7518 imrove jsonpath support (#7556)
0b317a7518 is described below

commit 0b317a7518b217f000a85048b84bb91c56c4811f
Author: lgbo <[email protected]>
AuthorDate: Thu Oct 17 10:05:10 2024 +0800

    imrove jsonpath support (#7556)
---
 .../GlutenClickhouseFunctionSuite.scala            |  31 ++++++
 .../Functions/SparkFunctionGetJsonObject.cpp       | 113 +++++++++++++++++++++
 .../Functions/SparkFunctionGetJsonObject.h         |  93 +++++------------
 3 files changed, 167 insertions(+), 70 deletions(-)

diff --git 
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/compatibility/GlutenClickhouseFunctionSuite.scala
 
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/compatibility/GlutenClickhouseFunctionSuite.scala
index 3d7b922e7b..3012be2e39 100644
--- 
a/backends-clickhouse/src/test/scala/org/apache/gluten/execution/compatibility/GlutenClickhouseFunctionSuite.scala
+++ 
b/backends-clickhouse/src/test/scala/org/apache/gluten/execution/compatibility/GlutenClickhouseFunctionSuite.scala
@@ -295,4 +295,35 @@ class GlutenClickhouseFunctionSuite extends 
GlutenClickHouseTPCHAbstractSuite {
     }
   }
 
+  test("GLUTEN-7552 normalize json path") {
+    withTable("test_7552") {
+      sql("create table test_7552(a string) using parquet")
+      val insert_sql =
+        """
+          |insert into test_7552 values('{\'a\':\'1\'}')
+          |,('{"a":3}')
+          |,('{"3a":4}')
+          |,('{"a c":5}')
+          |,('{"3 d":6"}')
+          |,('{"a:b":7}')
+          |,('{"=a":8}')
+          |""".stripMargin
+      sql(insert_sql)
+      compareResultsAgainstVanillaSpark(
+        """
+          |select a
+          |, get_json_object(a, '$.a')
+          |, get_json_object(a, '$.3a')
+          |, get_json_object(a, '$.a c')
+          |, get_json_object(a, '$.3 d')
+          |, get_json_object(a, '$.a:b')
+          |, get_json_object(a, '$.=a')
+          |from test_7552
+          |""".stripMargin,
+        true,
+        { _ => }
+      )
+    }
+  }
+
 }
diff --git a/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.cpp 
b/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.cpp
index a124b178fc..869221ce77 100644
--- a/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.cpp
+++ b/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.cpp
@@ -21,6 +21,119 @@
 namespace local_engine
 {
 
+std::pair<DB::TokenType, StringRef> 
JSONPathNormalizer::prevToken(DB::IParser::Pos & iter, size_t n)
+{
+    size_t i = 0;
+    for (; i < n && iter->type != DB::TokenType::DollarSign; ++i)
+    {
+        --iter;
+    }
+    std::pair<DB::TokenType, StringRef> res = {iter->type, 
StringRef(iter->begin, iter->end - iter->begin)};
+    for (; i > 0; --i)
+    {
+        ++iter;
+    }
+    return res;
+}
+
+std::pair<DB::TokenType, StringRef> 
JSONPathNormalizer::nextToken(DB::IParser::Pos & iter, size_t n)
+{
+    size_t i = 0;
+    for (; i < n && iter->type != DB::TokenType::EndOfStream; ++i)
+    {
+        ++iter;
+    }
+    std::pair<DB::TokenType, StringRef> res = {iter->type, 
StringRef(iter->begin, iter->end - iter->begin)};
+    for (; i > 0; --i)
+    {
+        --iter;
+    }
+    return res;
+}
+
+bool JSONPathNormalizer::isSubPathBegin(DB::IParser::Pos & iter)
+{
+    if (iter->type == DB::TokenType::Dot || (iter->type == 
DB::TokenType::Number && *iter->begin == '.'))
+    {
+        return true;
+    }
+    return false;
+}
+
+
+void JSONPathNormalizer::normalizeOnNumber(DB::IParser::Pos & iter, String & 
res)
+{
+    if (*iter->begin == '.')
+    {
+        res += ".\"";
+        res += String(iter->begin + 1, iter->end);
+        ++iter;
+        auto token_type = iter->type;
+        while (token_type != DB::TokenType::Dot && token_type != 
DB::TokenType::OpeningSquareBracket
+               && token_type != DB::TokenType::EndOfStream)
+        {
+            auto [_, prev_iter_str] = prevToken(iter);
+            // may contains spaces
+            if (prev_iter_str.data + prev_iter_str.size != iter->begin)
+            {
+                res += String(prev_iter_str.data + prev_iter_str.size, 
iter->begin);
+            }
+            res += String(iter->begin, iter->end);
+            ++iter;
+            token_type = iter->type;
+        }
+        auto [_, prev_iter_str] = prevToken(iter);
+        res += String(prev_iter_str.data + prev_iter_str.size, iter->begin);
+        res += "\"";
+    }
+    else
+        normalizeOnOtherTokens(iter, res);
+}
+
+void JSONPathNormalizer::normalizeOnBareWord(DB::IParser::Pos & iter, String & 
res)
+{
+    auto [prev_iter_type_2, _] = nextToken(iter);
+    /// e.g. $.data.forecast[?(@.aqi>65)]
+    if (prev_iter_type_2 == DB::TokenType::At)
+    {
+        normalizeOnOtherTokens(iter, res);
+    }
+    else
+    {
+        auto token_type = iter->type;
+        res += "\"";
+        size_t i = 0;
+        while (token_type != DB::TokenType::Dot && token_type != 
DB::TokenType::OpeningSquareBracket
+               && token_type != DB::TokenType::EndOfStream)
+        {
+            if (i)
+            {
+                auto [_, prev_iter_str] = prevToken(iter);
+                // may contains spaces
+                if (prev_iter_str.data + prev_iter_str.size != iter->begin)
+                {
+                    res += String(prev_iter_str.data + prev_iter_str.size, 
iter->begin);
+                }
+            }
+            res += String(iter->begin, iter->end);
+            ++iter;
+            i += 1;
+            token_type = iter->type;
+        }
+        auto [_, prev_iter_str] = prevToken(iter);
+        res += String(prev_iter_str.data + prev_iter_str.size, iter->begin);
+        res += "\"";
+    }
+}
+
+
+void JSONPathNormalizer::normalizeOnOtherTokens(DB::IParser::Pos & iter, 
String & res)
+{
+    res += String(iter->begin, iter->end);
+    ++iter;
+}
+
+
 REGISTER_FUNCTION(GetJsonObject)
 {
     factory.registerFunction<DB::FunctionSQLJSON<GetJsonObject, 
GetJsonObjectImpl>>();
diff --git a/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.h 
b/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.h
index b174cb4c7f..6ffe096b67 100644
--- a/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.h
+++ b/cpp-ch/local-engine/Functions/SparkFunctionGetJsonObject.h
@@ -481,6 +481,7 @@ public:
     }
 };
 
+/// CH uses the lexer to parse the json path, it's not a good idea.
 /// If a json field containt spaces, we wrap it by double quotes.
 /// FIXME: If it contains \t, \n, simdjson cannot parse.
 class JSONPathNormalizer
@@ -489,89 +490,41 @@ public:
     static String normalize(const String & json_path_)
     {
         DB::Tokens tokens(json_path_.data(), json_path_.data() + 
json_path_.size());
-        DB::IParser::Pos pos(tokens, 0, 0);
+        DB::IParser::Pos iter(tokens, 0, 0);
         String res;
-        while (pos->type != DB::TokenType::EndOfStream)
+        while (iter->type != DB::TokenType::EndOfStream)
         {
-            if (pos->type == DB::TokenType::Number)
+            if (isSubPathBegin(iter))
             {
-                ++pos;
-                // Two tokens are seperated by white spaces.
-                if (pos->type == DB::TokenType::Number || pos->type == 
DB::TokenType::BareWord)
+                if (iter->type == DB::TokenType::Number)
                 {
-                    --pos;
-                    if (*pos->begin == '.')
-                        res += ".";
-                    ++pos;
-                    res += "\"";
-
-                    while (pos->type == DB::TokenType::Number || pos->type == 
DB::TokenType::BareWord)
-                    {
-                        --pos;
-                        const auto * last_end = pos->end;
-                        const auto * begin = *pos->begin == '.' ? pos->begin + 
1 : pos->begin;
-                        res += String(begin, pos->end);
-                        ++pos;
-                        res += String(last_end, pos->begin);
-                        ++pos;
-                    }
-                    --pos;
-                    const auto * last_end = pos->end;
-                    res += String(pos->begin, pos->end);
-                    ++pos;
-                    res += String(last_end, pos->begin);
-                    res += "\"";
-                }
-                else if (
-                    pos->type == DB::TokenType::Dot || pos->type == 
DB::TokenType::OpeningSquareBracket
-                    || pos->type == DB::TokenType::EndOfStream)
-                {
-                    --pos;
-                    if (*pos->begin == '.')
-                        res += ".";
-                    res += "\"";
-                    const auto * last_end = pos->end;
-                    const auto * begin = *pos->begin == '.' ? pos->begin + 1 : 
pos->begin;
-                    res += String(begin, pos->end);
-                    ++pos;
-                    res += String(last_end, pos->begin);
-                    res += "\"";
+                    normalizeOnNumber(iter, res);
                 }
                 else
                 {
-                    --pos;
-                    res += String(pos->begin, pos->end);
-                    ++pos;
-                }
-            }
-            else if (pos->type == DB::TokenType::BareWord)
-            {
-                res += "\"";
-                ++pos;
-                while (pos->type == DB::TokenType::Number || pos->type == 
DB::TokenType::BareWord)
-                {
-                    --pos;
-                    const auto * last_end = pos->end;
-                    res += String(pos->begin, pos->end);
-                    ++pos;
-                    res += String(last_end, pos->begin);
-                    ++pos;
+                    // It may begins with '=', '==' and so on.
+                    res += ".";
+                    ++iter;
+                    normalizeOnBareWord(iter, res);
                 }
-                --pos;
-                const auto * last_end = pos->end;
-                res += String(pos->begin, pos->end);
-                ++pos;
-                res += String(last_end, pos->begin);
-                res += "\"";
             }
             else
-            {
-                res += String(pos->begin, pos->end);
-                ++pos;
-            }
+                normalizeOnOtherTokens(iter, res);
         }
         return res;
     }
+
+private:
+    static std::pair<DB::TokenType, StringRef> prevToken(DB::IParser::Pos & 
iter, size_t n = 1);
+
+    static std::pair<DB::TokenType, StringRef> nextToken(DB::IParser::Pos & 
iter, size_t n = 1);
+
+    static bool isSubPathBegin(DB::IParser::Pos & iter);
+
+    static void normalizeOnNumber(DB::IParser::Pos & iter, String & res);
+
+    static void normalizeOnBareWord(DB::IParser::Pos & iter, String & res);
+    static void normalizeOnOtherTokens(DB::IParser::Pos & iter, String & res);
 };
 
 /// Flatten a json string into a tuple.


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

Reply via email to