Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SPARK-34920][CORE][SQL] Add error classes with SQLSTATE #32850

Closed
wants to merge 13 commits into from

Conversation

karenfeng
Copy link
Contributor

What changes were proposed in this pull request?

Unifies exceptions thrown from Spark under a single base trait SparkError, which unifies:

Why are the changes needed?

  • Adding error classes creates a consistent label for exceptions, even as error messages change
  • Creating a single, centralized source-of-truth for parametrized error messages improves auditing for error message quality
  • Adding SQLSTATE helps ODBC/JDBC users receive standardized error codes

Does this PR introduce any user-facing change?

Yes, changes ODBC experience by:

  • Adding error classes to error messages
  • Adding SQLSTATE to TStatus

How was this patch tested?

Unit tests, as well as local tests with PyODBC.

Signed-off-by: Karen Feng <karen.feng@databricks.com>
Signed-off-by: Karen Feng <karen.feng@databricks.com>
Signed-off-by: Karen Feng <karen.feng@databricks.com>
Signed-off-by: Karen Feng <karen.feng@databricks.com>
Signed-off-by: Karen Feng <karen.feng@databricks.com>
@karenfeng karenfeng changed the title [SPARK-34920] Add SQLSTATE to exceptions thrown from Spark [SPARK-34920] Add error classes with SQLSTATE Jun 9, 2021
Signed-off-by: Karen Feng <karen.feng@databricks.com>
@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44122/

@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44122/

@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44123/

@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44123/

@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Test build #139595 has finished for PR 32850 at commit 4e5e410.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@karenfeng
Copy link
Contributor Author

@wangyum, this is similar to your work in #32013. Can you take a look?

@SparkQA
Copy link

SparkQA commented Jun 9, 2021

Test build #139596 has finished for PR 32850 at commit 92e275f.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

"sqlState" : "40000",
"messageFormatLines" : [ "Writing job aborted" ]
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean sqlState always unique?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily; sqlState can be re-used, especially for common error classes without known subclasses, like 42000 (syntax or semantic error). In those cases, the error class will be the disambiguator.

@karenfeng karenfeng changed the title [SPARK-34920] Add error classes with SQLSTATE [SPARK-34920][CORE][SQL] Add error classes with SQLSTATE Jun 10, 2021

## Fields

All fields, excluding error messages, should be consistent across releases.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by "consistent"? we can't change the error message in a new release?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds a pretty strict requirement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error messages can (and probably will) change between releases; that's why I included the caveat of "excluding error messages." This is actually a major driver for this - so we can improve the quality of error messages over time.

To clarify, I think it would be beneficial for our user base if we were consistent across error class and SQLSTATE across releases. With consistent error classes, users can build in known work-arounds or catch and re-throw known error types. With consistent SQLSTATEs, clients will also have predictable behavior.

@cloud-fan
Copy link
Contributor

cc @viirya @maropu @dongjoon-hyun


To throw an exception, do the following.

1. Check if an appropriate error class already exists in `error-class.json`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a concern that this will be another burden. E.g. naming a error class, finding error class.

I also worry that the number of error classes will grow quickly and hard to maintain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today, the error messages thrown from Spark are distributed across the entire code base with no single source of truth - as a result, it is hard to audit error message for quality and redundancy. With error classes, I'm hoping that we can improve the auditing process. However, I do recognize that the number of error classes will likely grow quickly, and that maintaining a high level of quality will require vigilant pruning. What do you think will help reduce the burden here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to load these error states from a json file instead of defining them statically in a companion object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think json file is more general for auditing, doc generation, internationalization (each error message has translations for different languages).

* The error message is constructed by concatenating the lines with
* linebreaks.
*/
case class ErrorInfo(sqlState: Option[String], messageFormatLines: Seq[String]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is defined in core. But sql state is a SQL concept?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree; SQLSTATE is a SQL concept. Off the top of my head, I'm not sure how to have this only be set in the sql component without increasing the complexity of the implementation:

  • Create a JSON file that maps error classes to SQLSTATE in SQL, which may increase maintenance burden and decrease developer usability
  • Create a SqlErrorInfo(sqlState, messageFormatLines) class that extends ErrorInfo(messageFormatLines) in the sql component, and parse the JSON file again to get the sqlState field

What do you think?

Copy link
Contributor Author

@karenfeng karenfeng Jun 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the implementation difficulty, I think it also makes sense to have sqlState be accessible from any type of Spark exception (regardless of the base component), given that we currently throw SparkExceptions (from the core component) during query execution. We could introduce a SparkSqlError type, but we would have to do a major refactor of the existing exception types as well in that case, as well as a cleaner division between the core and sql components.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to have a unified representation for all the errors. "error class" and "message" are required fields, and it's ok to have more optional fields for other purposes, like "sqlState" for JDBC compatibility.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, maybe we can consider giving a general name for sqlState (e.g. errorState, errorStateNumber, or etc).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Postgres calls their SQLSTATEs error codes, but I'm not sure if it'd be safe for us to do the same thing, especially given that we could add Spark-specific error codes in the future that span across non-SQL functionality.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer sqlState, to clearly indicate what it is, since SQL state is a standard.

Comment on lines 6 to 9
"DUPLICATE_KEY_ERROR" : {
"sqlState" : "23000",
"messageFormatLines" : [ "Found duplicate keys '%s'" ]
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add vendorCode in the future, but I think we would need to determine how error codes should be defined - whether there should be a class hierarchy, if they should be arbitrary, or something else (eg. hashed from the error class). There are some clients that except the vendor code to match Hive error code classes, given that they are thrown within a HiveSQLException.

@@ -316,7 +316,8 @@ object QueryParsingErrors {
}

def duplicateKeysError(key: String, ctx: ParserRuleContext): Throwable = {
new ParseException(s"Found duplicate keys '$key'.", ctx)
// Found duplicate keys '$key'
new ParseException(errorClass = "DUPLICATE_KEY_ERROR", messageParameters = Seq(key), ctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a Scala-focused developer perspective, I agree that objects are easier to maintain. However, using string classes has a couple of benefits:

  • The JSON file can be easily translated into a auto-generated documentation page (without tricks like reflection)
  • Other clients (eg. Python and R) can natively throw errors with error classes, creating a single easily-auditable source of truth

What do you think? For a happy medium, we could auto-generate Scala from JSON, but I think that may increase maintenance burden.

Signed-off-by: Karen Feng <karen.feng@databricks.com>
@SparkQA
Copy link

SparkQA commented Jun 21, 2021

Kubernetes integration test unable to build dist.

exiting with code: 1
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44625/


To throw an exception, do the following.

1. Check if an appropriate error class already exists in `error-class.json`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to load these error states from a json file instead of defining them statically in a companion object?

"DIVIDE_BY_ZERO" : {
"sqlState" : "22012",
"message" : [ "divide by zero" ]
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment; it would be nice to automatically generate a user's doc page (just like the PostgreSQL one) from this error state definition file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning for having all of the error states is linked to having everything in a JSON file - it's relatively straightforward to generate docs pages. (It's also easier to use the same error states in Python/R.) I have code lined up for docs page generation as a followup, but want to make sure this goes in first.

core/src/main/scala/org/apache/spark/SparkException.scala Outdated Show resolved Hide resolved
Signed-off-by: Karen Feng <karen.feng@databricks.com>
@cloud-fan
Copy link
Contributor

retest this please

* @param message C-style message format compatible with printf.
* The error message is constructed by concatenating the lines with newlines.
*/
case class ErrorInfo(sqlState: Option[String], message: Seq[String]) {
Copy link
Contributor

@cloud-fan cloud-fan Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a public API, it's better to use class as case class exposes too many APIs (apply, unapply, copy, etc.)

Copy link
Contributor Author

@karenfeng karenfeng Jun 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make ErrorInfo a private API. These fields should be accessed from the exception type.

Copy link
Contributor

@cloud-fan cloud-fan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except some minor comments

Signed-off-by: Karen Feng <karen.feng@databricks.com>
@SparkQA
Copy link

SparkQA commented Jun 29, 2021

Test build #140401 has finished for PR 32850 at commit 2a06bb1.

  • This patch fails to build.
  • This patch merges cleanly.
  • This patch adds no public classes.

Signed-off-by: Karen Feng <karen.feng@databricks.com>
@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Test build #140378 has finished for PR 32850 at commit e471e6b.

  • This patch fails from timeout after a configured wait of 500m.
  • This patch merges cleanly.
  • This patch adds the following public classes (experimental):
  • class SparkArithmeticException(

Signed-off-by: Karen Feng <karen.feng@databricks.com>
Signed-off-by: Karen Feng <karen.feng@databricks.com>
@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44934/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44934/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Test build #140419 has finished for PR 32850 at commit d73bb83.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test starting
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44937/

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Kubernetes integration test status success
URL: https://amplab.cs.berkeley.edu/jenkins/job/SparkPullRequestBuilder-K8s/44937/

@wangyum
Copy link
Member

wangyum commented Jun 30, 2021

Last question. How do other common modules use these errors? For example: spark-unsafe, spark-network-common.

@SparkQA
Copy link

SparkQA commented Jun 30, 2021

Test build #140422 has finished for PR 32850 at commit 6d8e915.

  • This patch passes all tests.
  • This patch merges cleanly.
  • This patch adds no public classes.

@cloud-fan
Copy link
Contributor

How do other common modules use these errors? For example: spark-unsafe, spark-network-common.

Similar problems appear in the config framework as well. spark-unsafe, spark-network-common can only hardcode config names instead of using the config framework. I think we should create a new module to contain these basic infras such as config, error systems, etc., and other modules all depend on this basic module.

@cloud-fan
Copy link
Contributor

thanks, merging to master!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants