This is an automated email from the ASF dual-hosted git repository.
peter-toth pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 8f82d56fb5a2 [SPARK-57960][SQL] Apply escapeSql to table/schema
identifiers in JDBC index metadata queries
8f82d56fb5a2 is described below
commit 8f82d56fb5a2c6fe99d616da8908856f72236392
Author: Peter Toth <[email protected]>
AuthorDate: Tue Jul 7 10:07:18 2026 +0200
[SPARK-57960][SQL] Apply escapeSql to table/schema identifiers in JDBC
index metadata queries
### What changes were proposed in this pull request?
`PostgresDialect.indexExists` and `H2Dialect.indexExists`/`listIndexes`
embed the table (and, for H2, schema) name into a SQL string literal in the
`pg_indexes` / `INFORMATION_SCHEMA` lookup query without escaping. A table or
schema name containing a single quote therefore produces a malformed query that
fails with a syntax error. This is inconsistent with the index name in the same
methods, which is already escaped via `escapeSql` (SPARK-57446 / SPARK-57447).
This change applies `escapeSql` to the table/schema identifiers at these
sites so quote-containing identifiers work correctly.
`MySQLDialect.indexExists` places the table in identifier position via
`quoteIdentifier` and is already correct.
### Why are the changes needed?
To correctly handle table and schema names that contain a single quote,
consistent with the existing index-name handling.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
New unit test in `JDBCSuite` asserting the generated lookup SQL escapes
single quotes in the table/schema name for the H2 and Postgres dialects.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8)
Closes #57038 from
peter-toth/SPARK-57960-jdbc-dialect-escape-identifier-literals.
Authored-by: Peter Toth <[email protected]>
Signed-off-by: Peter Toth <[email protected]>
(cherry picked from commit e58ade647be7a6eab13aa1ef72517a7d4b9abbd2)
Signed-off-by: Peter Toth <[email protected]>
---
.../org/apache/spark/sql/jdbc/H2Dialect.scala | 8 +++----
.../apache/spark/sql/jdbc/PostgresDialect.scala | 2 +-
.../org/apache/spark/sql/jdbc/JDBCSuite.scala | 27 ++++++++++++++++++++++
3 files changed, 32 insertions(+), 5 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 95bb1cbf99ba..86420b84e50d 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
@@ -132,8 +132,8 @@ private[sql] case class H2Dialect() extends JdbcDialect
with NoLegacyJDBCError {
tableIdent: Identifier,
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 =
'${escapeSql(indexName)}'"
+ s"TABLE_SCHEMA = '${escapeSql(tableIdent.namespace().last)}' AND " +
+ s"TABLE_NAME = '${escapeSql(tableIdent.name())}' AND INDEX_NAME =
'${escapeSql(indexName)}'"
JdbcUtils.checkIfIndexExists(conn, sql, options)
}
@@ -160,8 +160,8 @@ private[sql] case class H2Dialect() extends JdbcDialect
with NoLegacyJDBCError {
| AND i.INDEX_CATALOG = ic.INDEX_CATALOG
| AND i.INDEX_SCHEMA = ic.INDEX_SCHEMA
| AND i.INDEX_NAME = ic.INDEX_NAME
- | AND i.TABLE_NAME = '${tableIdent.name()}'
- | AND i.INDEX_SCHEMA = '${tableIdent.namespace().last}'
+ | AND i.TABLE_NAME = '${escapeSql(tableIdent.name())}'
+ | AND i.INDEX_SCHEMA = '${escapeSql(tableIdent.namespace().last)}'
|""".stripMargin
}
var indexMap: Map[String, TableIndex] = Map()
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 8941767ec357..9fc69594932b 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
@@ -270,7 +270,7 @@ private case class PostgresDialect()
indexName: String,
tableIdent: Identifier,
options: JDBCOptions): Boolean = {
- val sql = s"SELECT * FROM pg_indexes WHERE tablename =
'${tableIdent.name()}' AND" +
+ val sql = s"SELECT * FROM pg_indexes WHERE tablename =
'${escapeSql(tableIdent.name())}' AND" +
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 29e3e0e6fb68..11cef75823b2 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
@@ -864,6 +864,33 @@ class JDBCSuite extends SharedSparkSession {
}
}
+ test("SPARK-57960: (H2|Postgres)Dialect escape a single quote in indexExists
table/schema name") {
+ // indexExists also embeds the table (and, for H2, schema) name as a SQL
string literal, so a
+ // single quote in the identifier must be escaped too, consistent with the
index-name escaping.
+ val ident = Identifier.of(Array("sch'ema"), "ta'ble")
+ Seq(
+ "jdbc:h2:mem:testdb0" -> Seq("TABLE_SCHEMA = 'sch''ema'", "TABLE_NAME =
'ta''ble'"),
+ "jdbc:postgresql://127.0.0.1/db" -> Seq("tablename = 'ta''ble'")
+ ).foreach { case (jdbcUrl, expectedClauses) =>
+ 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, "idx", ident, options)
+
+ val sqlCaptor = ArgumentCaptor.forClass(classOf[String])
+ verify(stmt).executeQuery(sqlCaptor.capture())
+ expectedClauses.foreach { expectedClause =>
+ assert(sqlCaptor.getValue.contains(expectedClause),
+ s"Unexpected lookup SQL for $jdbcUrl: ${sqlCaptor.getValue}")
+ }
+ }
+ }
+
test("quote column names by jdbc dialect") {
val mySQLDialect = JdbcDialects.get("jdbc:mysql://127.0.0.1/db")
val postgresDialect = JdbcDialects.get("jdbc:postgresql://127.0.0.1/db")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]