You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ur...@apache.org on 2026/09/14 13:42:52 UTC
(spark) branch master updated: [SPARK-59336][SQL] Fix syntax error classifier in Postgres connector
This is an automated email from the ASF dual-hosted git repository.
uros-b pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 0969874ae571 [SPARK-59336][SQL] Fix syntax error classifier in Postgres connector
0969874ae571 is described below
commit 0969874ae5712791e9edf57c863fee5226557f80
Author: Uros Stankovic <ur...@databricks.com>
AuthorDate: Mon Sep 14 13:42:39 2026 +0000
[SPARK-59336][SQL] Fix syntax error classifier in Postgres connector
### What changes were proposed in this pull request?
- Don't classify Postgres 42501 error as syntax error, since that error is permission issue.
https://www.postgresql.org/docs/17/errcodes-appendix.html
<img width="1268" height="2640" alt="image" src="https://github.com/user-attachments/assets/74e029c5-7edc-44c7-8956-c7a71011cef8" />
### Why are the changes needed?
- To properly classify syntax errors in Postgres connector.
### Does this PR introduce _any_ user-facing change?
- Yes. For PostgreSQL JDBC queries, Spark now wraps only SQLSTATE `42601` as a syntax error. Other class-42 errors, including `42000` (syntax error or access rule violation), `42501` (insufficient privilege), and `42P01` (undefined table), are propagated as their original PostgreSQL JDBC errors.
### How was this patch tested?
- New integration test where we assert no permission error is not classified as SyntaxError (that is behavioural change)
- New unit tests
- Existing positive test for syntax error classification here: https://github.com/urosstan-db/spark/blob/6697d711944e4c6cbcf038ce80df8d2272def3a3/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/SharedJDBCIntegrationSuite.scala#L57-L74
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Codex CLI
Closes #58621 from urosstan-db/SPARK-57780-postgres-permission-error.
Authored-by: Uros Stankovic <ur...@databricks.com>
Signed-off-by: Uros Bojanic <22...@users.noreply.github.com>
---
.../spark/sql/jdbc/PostgresIntegrationSuite.scala | 44 ++++++++++++++++++++++
.../apache/spark/sql/jdbc/PostgresDialect.scala | 4 +-
.../spark/sql/jdbc/PostgresDialectSuite.scala | 12 +++++-
3 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/PostgresIntegrationSuite.scala b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/PostgresIntegrationSuite.scala
index 78cd846d718c..20e80195091d 100644
--- a/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/PostgresIntegrationSuite.scala
+++ b/connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/PostgresIntegrationSuite.scala
@@ -23,6 +23,8 @@ import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.util.Properties
+import scala.util.Using
+
import org.apache.spark.SparkException
import org.apache.spark.sql.{DataFrame, Row}
import org.apache.spark.sql.catalyst.util.DateTimeUtils
@@ -30,6 +32,7 @@ import org.apache.spark.sql.functions.lit
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
import org.apache.spark.tags.DockerTest
+import org.apache.spark.util.Utils
/**
* To run this test suite for a specific version (e.g., postgres:18.2-alpine):
@@ -382,6 +385,47 @@ class PostgresIntegrationSuite extends SharedJDBCIntegrationSuite {
assert(sql("select c1, c3 from queryOption").collect().toSet == expectedResult)
}
+ test("SPARK-59336: do not classify missing table as a syntax error") {
+ val postgresError = intercept[SQLException] {
+ spark.read.format("jdbc")
+ .option("url", jdbcUrl)
+ .option("query", "SELECT * FROM table_that_does_not_exist")
+ .load()
+ }
+ assertResult("42P01")(postgresError.getSQLState)
+ }
+
+ test("SPARK-59336: do not classify insufficient privilege as a syntax error") {
+ val restrictedUser = "restricted_user"
+ val restrictedPassword = "restricted_password"
+ val restrictedJdbcUrl = s"jdbc:postgresql://$dockerIp:$externalPort/postgres"
+
+ Utils.tryWithSafeFinally {
+ Using.resource(getConnection()) { conn =>
+ conn.prepareStatement(s"DROP USER IF EXISTS $restrictedUser").executeUpdate()
+ conn.prepareStatement(s"CREATE USER $restrictedUser PASSWORD '$restrictedPassword'")
+ .executeUpdate()
+ }
+
+ val postgresError = intercept[SQLException] {
+ spark.read.format("jdbc")
+ .option("url", restrictedJdbcUrl)
+ .option("dbtable", "bar")
+ .option("user", restrictedUser)
+ .option("password", restrictedPassword)
+ .load()
+ }
+ assertResult("42501")(postgresError.getSQLState)
+ assert(
+ postgresError.getMessage.contains("permission denied"),
+ s"Unexpected PostgreSQL error message: ${postgresError.getMessage}")
+ } {
+ Using.resource(getConnection()) { conn =>
+ conn.prepareStatement(s"DROP USER IF EXISTS $restrictedUser").executeUpdate()
+ }
+ }
+ }
+
test("write byte as smallint") {
spark.createDataFrame(Seq((1.toByte, 2.toShort)))
.write.jdbc(jdbcUrl, "byte_to_smallint_test", new Properties)
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 a85a6ef50c4b..c78d9be492ab 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
@@ -260,7 +260,9 @@ private case class PostgresDialect()
// See https://www.postgresql.org/docs/current/errcodes-appendix.html
override def isSyntaxErrorBestEffort(exception: SQLException): Boolean = {
- Option(exception.getSQLState).exists(_.startsWith("42"))
+ // SQLSTATE 42000 covers both syntax errors and access-rule violations, so preserve the
+ // original exception for that ambiguous state.
+ exception.getSQLState == "42601"
}
// SHOW INDEX syntax
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/PostgresDialectSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/PostgresDialectSuite.scala
index aec5fceb7799..659322fb8f37 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/PostgresDialectSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/PostgresDialectSuite.scala
@@ -18,7 +18,7 @@
package org.apache.spark.sql.jdbc
-import java.sql.{Connection, ResultSet, ResultSetMetaData, Statement, Types}
+import java.sql.{Connection, ResultSet, ResultSetMetaData, SQLException, Statement, Types}
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyString
@@ -82,6 +82,16 @@ class PostgresDialectSuite extends SparkFunSuite with MockitoSugar {
verify(conn).setAutoCommit(false)
}
+ test("SPARK-59336: classify only SQLSTATE 42601 as a syntax error") {
+ assert(dialect.isSyntaxErrorBestEffort(new SQLException("syntax error", "42601")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("access rule violation", "42000")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("permission denied", "42501")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("undefined table", "42P01")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("undefined column", "42703")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("undefined function", "42883")))
+ assert(!dialect.isSyntaxErrorBestEffort(new SQLException("error without SQLSTATE")))
+ }
+
test("updateExtraColumnMeta escapes a single quote in the table and column name") {
// The array-dimension lookup embeds the table and column name as SQL string literals, so a
// single quote in either must be escaped to keep the WHERE clause well-formed.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org