spark-test commented on code in PR #59868:
URL: https://github.com/apache/spark/pull/59868#discussion_r4404694547


##########
sql/core/src/main/scala/org/apache/spark/sql/catalyst/parser/ParseSqlResult.scala:
##########
@@ -123,8 +131,66 @@ object ParseSqlResult {
     JObject(fields.toList)
   }
 
+  /** 1-based Unicode code-point offset of `segment` in `batch`. */
+  private def charStart(batch: String, segment: PositionedSqlStatement): Int = 
{
+    val utf16 = math.max(0, math.min(segment.start, batch.length))
+    batch.codePointCount(0, utf16) + 1

Review Comment:
   **Non-blocking (P2):** `charStart` counts code points from offset 0 of the 
batch for every statement (`batch.codePointCount(0, utf16)`), where the merge 
base used `segment.start + 1`. It runs once per statement, from `successJson` 
or `errorJson`, so for S statements in an N-unit batch the per-row work grows 
from O(N) to roughly O(N + S*N/2).
   
   The trigger is a batch whose decoded `String` is not compact Latin-1, that 
is, any character above U+00FF anywhere in the row (a CJK comment, a smart 
quote, or the emoji this PR targets). On Latin-1-coded strings `codePointCount` 
returns without scanning, so ASCII and Latin-1 batches are unaffected, and rows 
with a few statements pay only microseconds. For whole-script rows the cost is 
material. By estimate (not measured), a 10^4-statement, 1 MB script adds about 
1.5-5 s per row and a 10^5-statement, 10 MB one about 150-500 s, and such a row 
becomes a straggler task that AQE cannot split.
   
   The splitter already avoids this for the opposite conversion: `utf16Offsets` 
exists to "Map once so each token-boundary lookup is O(1) rather than 
rescanning the prefix". Statements are emitted in ascending start order and 
every start falls on a code-point boundary. Carrying the previous UTF-16 offset 
and a running code-point count from one statement to the next therefore yields 
the same values in a single pass.
   
   **Verification:**
   - **Behavior:** Batches containing supplementary characters and BMP 
whitespace still report the same 1-based code-point start and length for both 
successful and failing statements, including a trailing partial statement.
   - **Inspection:** No statement's code-point start is computed by counting 
from the beginning of the batch; each count covers only the span since the 
previous statement's start, so the whole batch is traversed at most once per 
row.



##########
sql/core/src/test/scala/org/apache/spark/sql/catalyst/parser/ParseSqlResultSuite.scala:
##########
@@ -246,4 +253,32 @@ class ParseSqlResultSuite extends SparkFunSuite {
     assert(j \ "parse_success" === JBool(false))
     assert(j \ "error" \ "errorClass" === JString("PARSE_SYNTAX_ERROR"))
   }
+
+  test("error queryContext indexes are Unicode code points") {

Review Comment:
   **Nit (P3):** This test only checks `queryContext` for single statements at 
batch offset 0 and never reads `start` or `length`. Every other 
failing-statement case (in this suite, `ParseSqlSuite`, and `parse-sql.sql`, 
including its `LABELS_MISMATCH` case) is ASCII-only, where UTF-16 units and 
code points coincide. Two behaviors this PR introduces can therefore regress 
without any test failing:
   
   - **Failing-statement span.** Reverting `errorJson` to `segment.start + 1` / 
`segment.length` passes every suite. A failing statement that both follows and 
contains a supplementary character pins both values: the second statement of 
`s"SELECT '$emoji'; SELECT '$emoji' FRUM t"` should report `start` 13 and 
`length` 17 (UTF-16 gives 14 and 18). An ASCII-only failing statement such as 
`SELEC 2` has the same length in both units, so it cannot catch a UTF-16 
`length`.
   - **Scripting-error contexts.** `SqlScriptingException` keeps the default 
empty `getQueryContext`, so its context is added by `queryContextField`, whose 
`fragment` is a UTF-16 slice. It comes out right only because 
`rewriteErrorCharIndexes` runs on the merged error object. For `BEGIN SELECT 
'$emoji'; lbl_begin: BEGIN SELECT 1; END lbl_end; END`, `LABELS_MISMATCH` 
should report indexes 19..28 with fragment `'lbl_begin:'`. If the rewrite ran 
before the merge, the fragment would be `' lbl_begin'` and nothing would fail.
   
   **Verification:**
   - **Regression:** With a failing statement that follows a supplementary 
character and contains one itself, the new assertions pass on the current 
code-point start and length, and each would fail on its own if the error object 
reported that value in UTF-16 units.
   - **Regression:** With a scripting label mismatch after a supplementary 
character, the new assertion passes on the code-point fragment and indexes and 
would fail if scripting contexts kept the UTF-16-sliced fragment.



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