srielau commented on code in PR #59868:
URL: https://github.com/apache/spark/pull/59868#discussion_r4428810127


##########
sql/core/src/main/scala/org/apache/spark/sql/catalyst/parser/ParseSqlResult.scala:
##########
@@ -123,9 +134,83 @@ object ParseSqlResult {
     JObject(fields.toList)
   }
 
+  /**
+   * 1-based Unicode code-point starts for `statements` in `batch`. Statements
+   * are in ascending UTF-16 start order, so one left-to-right pass is enough:
+   * each count covers only the gap since the previous start.
+   */
+  private def charStarts(
+      batch: String,
+      statements: Seq[PositionedSqlStatement]): Seq[Int] = {
+    var utf16 = 0
+    var codePoints = 0
+    statements.map { segment =>
+      val startUtf16 = math.max(0, math.min(segment.start, batch.length))
+      if (startUtf16 >= utf16) {
+        codePoints += batch.codePointCount(utf16, startUtf16)
+      } else {
+        codePoints = batch.codePointCount(0, startUtf16)
+      }
+      utf16 = startUtf16
+      codePoints + 1
+    }
+  }
+
+  /** Unicode code-point length of the trimmed statement text. */
+  private def charLength(segment: PositionedSqlStatement): Int = {
+    val text = segment.statement
+    text.codePointCount(0, text.length)
+  }
+
+  /**
+   * Recompute the fragment of each statement `queryContext` by code points.
+   * The indexes are parser (code-point) offsets, but [[SQLQueryContext]]
+   * slices its fragment by UTF-16 units, which is off after a supplementary
+   * character. A context that falls outside the statement covers all of it.
+   * Contexts of another object (a view body) are left as is.
+   */
+  private def rewriteErrorCharIndexes(errorObj: JObject, statement: String): 
JObject = {
+    JObject(errorObj.obj.map {
+      case ("queryContext", JArray(contexts)) =>
+        "queryContext" -> JArray(contexts.map {
+          case ctx: JObject => rewriteQueryContextIndexes(ctx, statement)
+          case other => other
+        }.toList)
+      case other => other
+    })
+  }
+
+  private def rewriteQueryContextIndexes(ctx: JObject, statement: String): 
JObject = {
+    val objectType = ctx.obj.collectFirst { case ("objectType", JString(t)) => 
t }
+    val start = ctx.obj.collectFirst { case ("startIndex", JInt(n)) => n.toInt 
}
+    val stop = ctx.obj.collectFirst { case ("stopIndex", JInt(n)) => n.toInt }
+    (objectType, start, stop) match {
+      case (Some(""), Some(from), Some(to)) =>
+        val length = statement.codePointCount(0, statement.length)
+        val (first, last) =
+          if (from >= 1 && to >= from && to <= length) (from, to) else (1, 
length)
+        JObject(ctx.obj.map {
+          case ("startIndex", _) => "startIndex" -> JInt(first)
+          case ("stopIndex", _) => "stopIndex" -> JInt(last)
+          case ("fragment", _) => "fragment" -> 
JString(codePointSlice(statement, first - 1, last))

Review Comment:
   Follow-up on recounting start/length: I took the smaller step in 
3132e3adfe4. Each statement is counted once and that count is reused for 
`length`, the fallback origin `stopIndex`, and the widening bound. `charStarts` 
still walks the batch once for 1-based starts; that scan covers gaps in the 
original text and is not the same value.
   
   Carrying code-point start/length on `PositionedSqlStatement` from the 
splitter is still follow-up. It is a broader API change than this bugfix and 
does not change results.
   
   The three nits from the later review are also in 880e56fa548: 
`parsePlanWithParameters` now pins `SparkSqlParser.stopIndex`, the widening 
branch emits `JString(statement)` without `codePointSlice`, and the `getText` / 
`extractOriginalText` call sites have supplementary-character tests.



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