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]