Skip to content

Commit 14a933b

Browse files
committed
[SPARK-46410][SQL] Assign error classes/subclasses to JdbcUtils.classifyException
### What changes were proposed in this pull request? In the PR, I propose to raise exceptions with only error classes from `JdbcUtils.classifyException`, and introduce new error class `FAILED_JDBC` with sub-classes linked to a particular JDBC operation. ### Why are the changes needed? To improve user experience with Spark SQL by migrating on new error framework when all Spark exceptions from the JDBC datasource have an error class. ### Does this PR introduce _any_ user-facing change? Yes, if user's code depends on exceptions from the JDBC datasource. ### How was this patch tested? By running the affected test suites: ``` $ build/sbt "test:testOnly *JDBCV2Suite" $ build/sbt "test:testOnly *JDBCTableCatalogSuite" $ build/sbt "test:testOnly *JDBCSuite" ``` ### Was this patch authored or co-authored using generative AI tooling? No. Closes #44358 from MaxGekk/error-class-classifyException. Authored-by: Max Gekk <max.gekk@gmail.com> Signed-off-by: Max Gekk <max.gekk@gmail.com>
1 parent e955a59 commit 14a933b

File tree

17 files changed

+501
-154
lines changed

17 files changed

+501
-154
lines changed

common/utils/src/main/resources/error/error-classes.json

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,79 @@
10961096
],
10971097
"sqlState" : "38000"
10981098
},
1099+
"FAILED_JDBC" : {
1100+
"message" : [
1101+
"Failed JDBC <url> on the operation:"
1102+
],
1103+
"subClass" : {
1104+
"ALTER_TABLE" : {
1105+
"message" : [
1106+
"Alter the table <tableName>."
1107+
]
1108+
},
1109+
"CREATE_INDEX" : {
1110+
"message" : [
1111+
"Create the index <indexName> in the <tableName> table."
1112+
]
1113+
},
1114+
"CREATE_NAMESPACE" : {
1115+
"message" : [
1116+
"Create the namespace <namespace>."
1117+
]
1118+
},
1119+
"CREATE_NAMESPACE_COMMENT" : {
1120+
"message" : [
1121+
"Create a comment on the namespace: <namespace>."
1122+
]
1123+
},
1124+
"CREATE_TABLE" : {
1125+
"message" : [
1126+
"Create the table <tableName>."
1127+
]
1128+
},
1129+
"DROP_INDEX" : {
1130+
"message" : [
1131+
"Drop the index <indexName> in the <tableName> table."
1132+
]
1133+
},
1134+
"DROP_NAMESPACE" : {
1135+
"message" : [
1136+
"Drop the namespace <namespace>."
1137+
]
1138+
},
1139+
"GET_TABLES" : {
1140+
"message" : [
1141+
"Get tables from the namespace: <namespace>."
1142+
]
1143+
},
1144+
"LIST_NAMESPACES" : {
1145+
"message" : [
1146+
"List namespaces."
1147+
]
1148+
},
1149+
"NAMESPACE_EXISTS" : {
1150+
"message" : [
1151+
"Check that the namespace <namespace> exists."
1152+
]
1153+
},
1154+
"REMOVE_NAMESPACE_COMMENT" : {
1155+
"message" : [
1156+
"Remove a comment on the namespace: <namespace>."
1157+
]
1158+
},
1159+
"RENAME_TABLE" : {
1160+
"message" : [
1161+
"Rename the table <oldName> to <newName>."
1162+
]
1163+
},
1164+
"TABLE_EXISTS" : {
1165+
"message" : [
1166+
"Check that the table <tableName> exists."
1167+
]
1168+
}
1169+
},
1170+
"sqlState" : "HV000"
1171+
},
10991172
"FAILED_PARSE_STRUCT_TYPE" : {
11001173
"message" : [
11011174
"Failed parsing struct: <raw>."
@@ -6778,11 +6851,6 @@
67786851
"pivot is not supported on a streaming DataFrames/Datasets"
67796852
]
67806853
},
6781-
"_LEGACY_ERROR_TEMP_3064" : {
6782-
"message" : [
6783-
"<msg>"
6784-
]
6785-
},
67866854
"_LEGACY_ERROR_TEMP_3065" : {
67876855
"message" : [
67886856
"<clazz>: <msg>"

connector/docker-integration-tests/src/test/scala/org/apache/spark/sql/jdbc/v2/V2JDBCTest.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,10 @@ private[v2] trait V2JDBCTest extends SharedSparkSession with DockerIntegrationFu
221221

222222
test("CREATE TABLE with table property") {
223223
withTable(s"$catalogName.new_table") {
224-
val m = intercept[AnalysisException] {
224+
val e = intercept[AnalysisException] {
225225
sql(s"CREATE TABLE $catalogName.new_table (i INT) TBLPROPERTIES('a'='1')")
226-
}.message
227-
assert(m.contains("Failed table creation"))
226+
}
227+
assert(e.getErrorClass == "FAILED_JDBC.CREATE_TABLE")
228228
testCreateTableWithProperty(s"$catalogName.new_table")
229229
}
230230
}
@@ -279,7 +279,7 @@ private[v2] trait V2JDBCTest extends SharedSparkSession with DockerIntegrationFu
279279
sql(s"CREATE index i1 ON $catalogName.new_table (col1)")
280280
},
281281
errorClass = "INDEX_ALREADY_EXISTS",
282-
parameters = Map("indexName" -> "i1", "tableName" -> "new_table")
282+
parameters = Map("indexName" -> "`i1`", "tableName" -> "`new_table`")
283283
)
284284

285285
sql(s"DROP index i1 ON $catalogName.new_table")
@@ -304,7 +304,7 @@ private[v2] trait V2JDBCTest extends SharedSparkSession with DockerIntegrationFu
304304
sql(s"DROP index i1 ON $catalogName.new_table")
305305
},
306306
errorClass = "INDEX_NOT_FOUND",
307-
parameters = Map("indexName" -> "i1", "tableName" -> "new_table")
307+
parameters = Map("indexName" -> "`i1`", "tableName" -> "`new_table`")
308308
)
309309
}
310310
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
layout: global
3+
title: FAILED_JDBC error class
4+
displayTitle: FAILED_JDBC error class
5+
license: |
6+
Licensed to the Apache Software Foundation (ASF) under one or more
7+
contributor license agreements. See the NOTICE file distributed with
8+
this work for additional information regarding copyright ownership.
9+
The ASF licenses this file to You under the Apache License, Version 2.0
10+
(the "License"); you may not use this file except in compliance with
11+
the License. You may obtain a copy of the License at
12+
13+
http://www.apache.org/licenses/LICENSE-2.0
14+
15+
Unless required by applicable law or agreed to in writing, software
16+
distributed under the License is distributed on an "AS IS" BASIS,
17+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
See the License for the specific language governing permissions and
19+
limitations under the License.
20+
---
21+
22+
SQLSTATE: HV000
23+
24+
Failed JDBC `<url>` on the operation:
25+
26+
This error class has the following derived error classes:
27+
28+
## ALTER_TABLE
29+
30+
Alter the table `<tableName>`.
31+
32+
## CREATE_INDEX
33+
34+
Create the index `<indexName>` in the `<tableName>` table.
35+
36+
## CREATE_NAMESPACE
37+
38+
Create the namespace `<namespace>`.
39+
40+
## CREATE_NAMESPACE_COMMENT
41+
42+
Create a comment on the namespace: `<namespace>`.
43+
44+
## CREATE_TABLE
45+
46+
Create the table `<tableName>`.
47+
48+
## DROP_INDEX
49+
50+
Drop the index `<indexName>` in the `<tableName>` table.
51+
52+
## DROP_NAMESPACE
53+
54+
Drop the namespace `<namespace>`.
55+
56+
## GET_TABLES
57+
58+
Get tables from the namespace: `<namespace>`.
59+
60+
## LIST_NAMESPACES
61+
62+
List namespaces.
63+
64+
## NAMESPACE_EXISTS
65+
66+
Check that the namespace `<namespace>` exists.
67+
68+
## REMOVE_NAMESPACE_COMMENT
69+
70+
Remove a comment on the namespace: `<namespace>`.
71+
72+
## RENAME_TABLE
73+
74+
Rename the table `<oldName>` to `<newName>`.
75+
76+
## TABLE_EXISTS
77+
78+
Check that the table `<tableName>` exists.
79+
80+

docs/sql-error-conditions.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,14 @@ User defined function (`<functionName>`: (`<signature>`) => `<result>`) failed d
665665

666666
Failed preparing of the function `<funcName>` for call. Please, double check function's arguments.
667667

668+
### [FAILED_JDBC](sql-error-conditions-failed-jdbc-error-class.html)
669+
670+
SQLSTATE: HV000
671+
672+
Failed JDBC `<url>` on the operation:
673+
674+
For more details see [FAILED_JDBC](sql-error-conditions-failed-jdbc-error-class.html)
675+
668676
### FAILED_PARSE_STRUCT_TYPE
669677

670678
[SQLSTATE: 22018](sql-error-conditions-sqlstates.html#class-22-data-exception)

project/MimaExcludes.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ object MimaExcludes {
100100
ProblemFilters.exclude[MissingTypesProblem]("org.apache.spark.storage.CacheId$"),
101101
ProblemFilters.exclude[DirectMissingMethodProblem]("org.apache.spark.storage.CacheId.apply"),
102102

103+
// SPARK-46410: Assign error classes/subclasses to JdbcUtils.classifyException
104+
ProblemFilters.exclude[DirectMissingMethodProblem]("org.apache.spark.sql.jdbc.JdbcDialect.classifyException"),
105+
103106
(problem: Problem) => problem match {
104107
case MissingClassProblem(cls) => !cls.fullName.startsWith("org.sparkproject.jpmml") &&
105108
!cls.fullName.startsWith("org.sparkproject.dmg.pmml")

sql/api/src/main/scala/org/apache/spark/sql/catalyst/analysis/NonEmptyException.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,4 @@ case class NonEmptyNamespaceException(
3636
"details" -> details)) {
3737

3838
def this(namespace: Array[String]) = this(namespace, "", None)
39-
40-
def this(details: String, cause: Option[Throwable]) =
41-
this(Array.empty, details, cause)
4239
}

sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,12 +1180,15 @@ object JdbcUtils extends Logging with SQLConfHelper {
11801180
}
11811181
}
11821182

1183-
def classifyException[T](message: String, dialect: JdbcDialect)(f: => T): T = {
1183+
def classifyException[T](
1184+
errorClass: String,
1185+
messageParameters: Map[String, String],
1186+
dialect: JdbcDialect)(f: => T): T = {
11841187
try {
11851188
f
11861189
} catch {
11871190
case e: SparkThrowable with Throwable => throw e
1188-
case e: Throwable => throw dialect.classifyException(message, e)
1191+
case e: Throwable => throw dialect.classifyException(e, errorClass, messageParameters)
11891192
}
11901193
}
11911194

sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/jdbc/JDBCTable.scala

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@ import org.apache.spark.sql.connector.catalog.TableCapability._
2626
import org.apache.spark.sql.connector.catalog.index.{SupportsIndex, TableIndex}
2727
import org.apache.spark.sql.connector.expressions.NamedReference
2828
import org.apache.spark.sql.connector.write.{LogicalWriteInfo, WriteBuilder}
29+
import org.apache.spark.sql.errors.DataTypeErrorsBase
2930
import org.apache.spark.sql.execution.datasources.jdbc.{JDBCOptions, JdbcOptionsInWrite, JdbcUtils}
3031
import org.apache.spark.sql.jdbc.JdbcDialects
3132
import org.apache.spark.sql.types.StructType
3233
import org.apache.spark.sql.util.CaseInsensitiveStringMap
3334

3435
case class JDBCTable(ident: Identifier, schema: StructType, jdbcOptions: JDBCOptions)
35-
extends Table with SupportsRead with SupportsWrite with SupportsIndex {
36+
extends Table
37+
with SupportsRead
38+
with SupportsWrite
39+
with SupportsIndex
40+
with DataTypeErrorsBase {
3641

3742
override def name(): String = ident.toString
3843

@@ -58,8 +63,13 @@ case class JDBCTable(ident: Identifier, schema: StructType, jdbcOptions: JDBCOpt
5863
columnsProperties: util.Map[NamedReference, util.Map[String, String]],
5964
properties: util.Map[String, String]): Unit = {
6065
JdbcUtils.withConnection(jdbcOptions) { conn =>
61-
JdbcUtils.classifyException(s"Failed to create index $indexName in ${name()}",
62-
JdbcDialects.get(jdbcOptions.url)) {
66+
JdbcUtils.classifyException(
67+
errorClass = "FAILED_JDBC.CREATE_INDEX",
68+
messageParameters = Map(
69+
"url" -> jdbcOptions.url,
70+
"indexName" -> toSQLId(indexName),
71+
"tableName" -> toSQLId(name)),
72+
dialect = JdbcDialects.get(jdbcOptions.url)) {
6373
JdbcUtils.createIndex(
6474
conn, indexName, ident, columns, columnsProperties, properties, jdbcOptions)
6575
}
@@ -74,8 +84,13 @@ case class JDBCTable(ident: Identifier, schema: StructType, jdbcOptions: JDBCOpt
7484

7585
override def dropIndex(indexName: String): Unit = {
7686
JdbcUtils.withConnection(jdbcOptions) { conn =>
77-
JdbcUtils.classifyException(s"Failed to drop index $indexName in ${name()}",
78-
JdbcDialects.get(jdbcOptions.url)) {
87+
JdbcUtils.classifyException(
88+
errorClass = "FAILED_JDBC.DROP_INDEX",
89+
messageParameters = Map(
90+
"url" -> jdbcOptions.url,
91+
"indexName" -> toSQLId(indexName),
92+
"tableName" -> toSQLId(name)),
93+
dialect = JdbcDialects.get(jdbcOptions.url)) {
7994
JdbcUtils.dropIndex(conn, indexName, ident, jdbcOptions)
8095
}
8196
}

0 commit comments

Comments
 (0)