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

dongjoon-hyun pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-3.5 by this push:
     new 9db8d048c90c [SPARK-57447][SQL] Escape the index name in 
`(H2|MySQL|Postgres)Dialect.indexExists`
9db8d048c90c is described below

commit 9db8d048c90c846e3e8e7e4bad653bb9b36ee24f
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Sun Jun 14 20:37:14 2026 -0700

    [SPARK-57447][SQL] Escape the index name in 
`(H2|MySQL|Postgres)Dialect.indexExists`
    
    `indexExists` in `H2Dialect`, `MySQLDialect`, and `PostgresDialect` embeds 
the
    index name as a SQL string literal in its lookup query. This PR wraps the 
index
    name with the existing `JdbcDialect.escapeSql` helper so that single quotes 
are
    properly escaped.
    
    Note that the scope of this PR is only index names. For table names or 
TABLE_SCHEMA, we are going to handle independently.
    
    If an index name contains a single quote, the lookup query becomes malformed
    (e.g. `... INDEX_NAME = 'a'b'`). `JdbcUtils.checkIfIndexExists` swallows the
    resulting exception and returns `false`, so an existing index whose name
    contains a single quote is incorrectly reported as not existing.
    
    No.
    
    Pass the CIs.
    
    Generated-by: Claude Code
    
    Closes #56508 from dongjoon-hyun/SPARK-57447.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 86db727372453d45c712bf4f9506c902589bb18e)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit ab289d60ed936cfd8aa177790dc4a161051e0205)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit 85a097243972a864cc31e5f1a3366d645110dab2)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit c697510102cfe6af3307f2b37f863ae3066569c1)
    Signed-off-by: Dongjoon Hyun <[email protected]>
    (cherry picked from commit da40fdb07f19d03e83efe0a75aa0a1a12af4550c)
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 .../org/apache/spark/sql/jdbc/H2Dialect.scala      |  2 +-
 .../org/apache/spark/sql/jdbc/MySQLDialect.scala   |  2 +-
 .../apache/spark/sql/jdbc/PostgresDialect.scala    |  2 +-
 .../org/apache/spark/sql/jdbc/JDBCSuite.scala      | 29 +++++++++++++++++++++-
 4 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/H2Dialect.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/H2Dialect.scala
index 336220922b5e..d9a90766937c 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/H2Dialect.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/H2Dialect.scala
@@ -124,7 +124,7 @@ private[sql] object H2Dialect extends JdbcDialect {
       options: JDBCOptions): Boolean = {
     val sql = "SELECT * FROM INFORMATION_SCHEMA.INDEXES WHERE " +
       s"TABLE_SCHEMA = '${tableIdent.namespace().last}' AND " +
-      s"TABLE_NAME = '${tableIdent.name()}' AND INDEX_NAME = '$indexName'"
+      s"TABLE_NAME = '${tableIdent.name()}' AND INDEX_NAME = 
'${escapeSql(indexName)}'"
     JdbcUtils.checkIfIndexExists(conn, sql, options)
   }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala
index fbb77ade9aad..5cdba5f1b91a 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala
@@ -243,7 +243,7 @@ private case object MySQLDialect extends JdbcDialect with 
SQLConfHelper {
       tableIdent: Identifier,
       options: JDBCOptions): Boolean = {
     val sql = s"SHOW INDEXES FROM ${quoteIdentifier(tableIdent.name())} " +
-      s"WHERE key_name = '$indexName'"
+      s"WHERE key_name = '${escapeSql(indexName)}'"
     JdbcUtils.checkIfIndexExists(conn, sql, options)
   }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala
index dd4545bc41b4..1d5ac1f552bf 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala
@@ -213,7 +213,7 @@ private object PostgresDialect extends JdbcDialect with 
SQLConfHelper {
       tableIdent: Identifier,
       options: JDBCOptions): Boolean = {
     val sql = s"SELECT * FROM pg_indexes WHERE tablename = 
'${tableIdent.name()}' AND" +
-      s" indexname = '$indexName'"
+      s" indexname = '${escapeSql(indexName)}'"
     JdbcUtils.checkIfIndexExists(conn, sql, options)
   }
 
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
index e6038938f2b9..0d96517a712f 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCSuite.scala
@@ -18,13 +18,14 @@
 package org.apache.spark.sql.jdbc
 
 import java.math.BigDecimal
-import java.sql.{Date, DriverManager, Timestamp}
+import java.sql.{Connection, Date, DriverManager, ResultSet, Statement, 
Timestamp}
 import java.time.{Instant, LocalDate, LocalDateTime}
 import java.util.{Calendar, GregorianCalendar, Properties, TimeZone}
 
 import scala.collection.JavaConverters._
 import scala.util.Random
 
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers._
 import org.mockito.Mockito._
 
@@ -34,6 +35,7 @@ import org.apache.spark.sql.catalyst.{analysis, 
TableIdentifier}
 import org.apache.spark.sql.catalyst.parser.CatalystSqlParser
 import org.apache.spark.sql.catalyst.plans.logical.ShowCreateTable
 import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap, 
CharVarcharUtils, DateTimeTestUtils}
+import org.apache.spark.sql.connector.catalog.Identifier
 import org.apache.spark.sql.execution.{DataSourceScanExec, ExtendedMode, 
ProjectExec}
 import org.apache.spark.sql.execution.command.{ExplainCommand, 
ShowCreateTableCommand}
 import org.apache.spark.sql.execution.datasources.LogicalRelation
@@ -779,6 +781,31 @@ class JDBCSuite extends QueryTest with SharedSparkSession {
     assert(JdbcDialects.get("test.invalid") == NoopDialect)
   }
 
+  test("SPARK-57447: (H2|MySQL|Postgres)Dialect escape a single quote in 
indexExists") {
+    // indexExists builds a lookup query with the index name as a SQL string 
literal, so a single
+    // quote in the name must be escaped to keep the WHERE clause well-formed.
+    Seq(
+      "jdbc:h2:mem:testdb0" -> "INDEX_NAME = 'i''1'",
+      "jdbc:mysql://127.0.0.1/db" -> "key_name = 'i''1'",
+      "jdbc:postgresql://127.0.0.1/db" -> "indexname = 'i''1'"
+    ).foreach { case (jdbcUrl, expectedClause) =>
+      val dialect = JdbcDialects.get(jdbcUrl)
+      val conn = mock(classOf[Connection])
+      val stmt = mock(classOf[Statement])
+      val rs = mock(classOf[ResultSet])
+      when(conn.createStatement()).thenReturn(stmt)
+      when(stmt.executeQuery(anyString())).thenReturn(rs)
+
+      val options = new JDBCOptions(jdbcUrl, "test.people", Map.empty[String, 
String])
+      dialect.indexExists(conn, "i'1", Identifier.of(Array("test"), "people"), 
options)
+
+      val sqlCaptor = ArgumentCaptor.forClass(classOf[String])
+      verify(stmt).executeQuery(sqlCaptor.capture())
+      assert(sqlCaptor.getValue.contains(expectedClause),
+        s"Unexpected lookup SQL for $jdbcUrl: ${sqlCaptor.getValue}")
+    }
+  }
+
   test("quote column names by jdbc dialect") {
     val MySQL = JdbcDialects.get("jdbc:mysql://127.0.0.1/db")
     val Postgres = JdbcDialects.get("jdbc:postgresql://127.0.0.1/db")


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

Reply via email to