How do you handle database errors in Go without getting coupled to the SQL driver?

huangapple go评论81阅读模式
英文:

How do you handle database errors in Go without getting coupled to the SQL driver?

问题

在Go语言中,与SQL数据库进行交互的常见方式是使用内置的database/sql接口。许多不同的第三方包以特定于某个特定数据库的方式实现了这个接口,而不会将这些工作暴露给您作为消费者,例如Postgres驱动程序、MySQL驱动程序等。

然而,database/sql并没有提供任何特定的错误类型,而是将其留给驱动程序处理。这就带来了一个问题:除了nil检查之外,您对这些特定错误的任何错误处理都是基于特定驱动程序的假设。如果以后决定更改驱动程序,所有的错误处理代码都必须进行修改。如果要支持多个驱动程序,还需要为该驱动程序编写额外的检查。

这似乎削弱了使用接口的主要优势:具有约定的可移植性。

以下是使用jackc/pgx/v4/stdlib驱动程序和助手包套件来说明这个问题的示例代码:

import (
	"database/sql"
	"errors"
	"github.com/jackc/pgconn"
	"github.com/jackc/pgerrcode"
)

// 为简化起见,省略了一些代码,err来自database/sql

if err != nil {
	var pgerr *pgconn.PgError
	if errors.As(err, &pgerr) {
		if pgerrcode.IsIntegrityConstraintViolation(pgerr.SQLState()) {
			return nil, errors.New("related entity does not exist")
		}
	}
	// 如果我们想支持另一个数据库驱动程序,我们必须在这里包含它

	return nil, errors.New("failed to insert the thing")
}

如果我已经将特定于驱动程序的代码放入我的包中,为什么还要接受database/sql接口呢?相反,我可以要求使用特定的驱动程序,这可能更安全,因为它可以防止消费者尝试使用我们没有错误处理的其他不受支持的驱动程序。

有没有更好的方法来处理特定的database/sql错误?

英文:

A common way to interact with a SQL database in Go is to use the built in database/sql interface. Many different third-party packages implement this interface in a way that is specific to some particular database without exposing that work to you as a consumer, e.g. Postgres driver, MySQL driver, etc.

However, database/sql doesn't provide any specific error types, leaving it up to the driver instead. This presents a problem: any error handling you do for these specific errors beyond nil checks now works off of the assumption of a particular driver. If you decide to change drivers later, all of the error handling code must be modified. If you want to support multiple drivers, you need to write additional checks for that driver too.

This seemingly undermines the primary benefit of using interfaces: portability with an agreed-upon contract.

Here's an example to illustrate this problem using the jackc/pgx/v4/stdlib driver and suite of helper packages:

import (
	"database/sql"
	"errors"
	"github.com/jackc/pgconn"
	"github.com/jackc/pgerrcode"
)

// Omitted code for the sake of simplification, err comes from database/sql

if err != nil {
	var pgerr *pgconn.PgError
	if errors.As(err, &pgerr) {
		if pgerrcode.IsIntegrityConstraintViolation(pgerr.SQLState()) {
			return nil, errors.New("related entity does not exist")
		}
	}
	// If we wanted to support another database driver, we'd have to include that here

	return nil, errors.New("failed to insert the thing")
}

If I already have to put driver-specific code into my package, why bother accepting the database/sql interface at all? I could instead require the specific driver, which is arguably safer since it prevents the consumer from trying to use some other unsupported driver that we don't have error handling for.

Is there better way to handle specific database/sql errors?

答案1

得分: 1

你不需要特定于驱动程序的代码来获取SQLState。例如:

func getSQLState(err error) {
    type checker interface {
        SQLState() string
    }
    pe := err.(checker)
    log.Println("SQLState:", pe.SQLState())
}

但是SQLState无论如何都是特定于数据库的。如果将来切换到另一个数据库/驱动程序,那么你需要手动更改所有错误代码。编译器无法帮助检测到这一点。

英文:

You don't need driver specific code to get SQLState. Example:

func getSQLState(err error) {
        type checker interface {
                SQLState() string
        }
        pe := err.(checker)
        log.Println("SQLState:", pe.SQLState())
}

But SQLState is a database specific anyway. If you switch to another database/driver in the future then you need to change all error codes manually. Compiler would not help to detect it.

答案2

得分: 0

包sql提供了围绕SQL(或类似SQL)数据库的通用接口。

在提供最小公共功能集和提供对所有实现都不可用的功能之间存在一种折衷。sql包更注重前者,而您可能更喜欢后者。

您可以争论说每个可能的实现都应该能够为您的示例提供特定的错误。也许是这样。也许不是。我不知道。

无论如何,您可以将pgerrcode.IsIntegrityConstraintViolation封装在一个函数中,该函数对您支持的每个驱动程序进行此检查。然后由您决定如何处理缺乏支持的驱动程序。

英文:

> Package sql provides a generic interface around SQL (or SQL-like) databases.

There is a compromise between providing the minimal common set of features, and providing features that would not be available for all implementations. The sql package has prioritized the former, while maybe you prefer more of the latter.

You could argue that every possible implementation should be able to provide a specific error for your example. Maybe that's the case. Maybe not. I don't know.

Either way it is possible for you to wrap pgerrcode.IsIntegrityConstraintViolation inside a function that does this check for every driver that you support. Then it is up to you to decide how to deal with drivers that lacks support.

huangapple
  • 本文由 发表于 2022年1月26日 15:13:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/70859712.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定