【问题标题】:In golang how to support multiple sql syntax (mysql vs postgres)在golang中如何支持多种sql语法(mysql vs postgres)
【发布时间】:2018-02-16 04:51:10
【问题描述】:

我的 go 应用应该支持多个数据库。这意味着,在不同的数据库上运行相同的二进制文件,应用程序正在使用的数据库将由配置决定。

问题是,每个数据库都有自己的预处理语句语法。 示例:

db.Prepare("select p, f, t from mytable where p = $1") 

适用于 postgres,但不适用于 mysql。

db.Prepare("select p, f, t from mytable where p = ?") 

适用于 mysql 但不适用于 postgres。

关闭诅咒我可以通过在运行时编辑字符串或维护多个查询来解决它。

有没有更好的办法?

我不想使用一个可以控制我所有数据库访问的外部库进行一些巨大的抽象,但是如果有一些轻量级的库可以神奇地修复语法,那我很满意。

编辑: 总结一下我之前说过的话,困扰我的部分是对于 mysql 你将不得不使用“?”而对于 postgres,您将不得不使用 $1, $2...

干杯

【问题讨论】:

  • 您可以使用 lib 编写流畅的 SQL,例如 github.com/Masterminds/squirrel
  • 你所说的“一些巨大的抽象”是什么意思?你反对使用 ORM 吗?因为听起来 ORM 正是您所需要的。如果您不反对使用 ORM,那么 GORM 就是您的朋友 - jinzhu.me/gorm
  • 我现在只有一个表,并且可能只有几个必须非常高效的查询。他们必须使用多个数据库。我并不反对或支持 ORM,只是对于我的用例,我认为它不合适。
  • 如果您的用例要求您支持不同的 SQL 方言并拥有一个接口,那么 ORM 就可以满足您的用例。是的,肯定是一张桌子,如果成功可能会变成两三张。不要就什么是对什么是错做出决定,认为成功和不成功。我知道仅仅为了一个小问题而扔掉整个库是过分的,但在某些情况下,我们希望专注于交付与技术正确性。
  • 使用接口。根据设置定义通用接口并在运行时设置特定的实现。

标签: mysql sql postgresql go prepared-statement


【解决方案1】:

我找到了 db.Rebind() 来帮助解决这个问题。所以:

    name := "my name"
    var p = property{}
    // language=SQL
    s := "SELECT * FROM property WHERE name=?"
    err := db.Get(&p, db.Rebind(s), name)

顶部的语言注释是为了让 IntelliJ 仍然可以在 UI 中为我检查 SQL 语句的语法。

我还必须为每个数据库编写单独的 CREATE 语句(我的应用程序同时支持 mysql、postgres 和 sqlite)。

我还发现mysql和sqlite之间的UPDATE语句语法是一样的,但是postgres需要特殊处理。由于我的 UPDATE 语句非常一致,因此我能够编写一个函数来将 mysql 方言混合翻译为 postgres 方言。这绝对不是一个通用的解决方案,但效果很好,足以让我的单元和集成测试通过。 YMMV。

// RebindMore takes a MySQL SQL string and convert it to Postgres if necessary.
// The db.Rebind() handles converting '?' to '$1', but does not handle SQL statement
// syntactic changes needed by Postgres.
//
// convert: "UPDATE table_name SET a = ?, b = ?, c = ? WHERE d = ?"
// to:      "UPDATE table_name SET (a, b, c) = ROW (?, ?, ?) WHERE d = ?"
func RebindMore(db *sqlx.DB, s string) string {
    if db.DriverName() != "postgres" {
        return s
    }

    if !strings.HasPrefix(strings.ToLower(s), "update") {
        return db.Rebind(s)
    }

    // Convert a MySQL update statement into a Postgres update statement.
    var idx int
    idx = strings.Index(strings.ToLower(s), "set")
    if idx < 0 {
        log.Fatal().Msg("no SET clause in RebindMore (" + s + ")")
    }

    prefix := s[:idx+3]
    s2 := s[idx+3:]

    idx = strings.Index(strings.ToLower(s2), "where")
    if idx < 0 {
        log.Fatal().Msg("no WHERE clause in RebindMore (" + s + ")")
    }

    suffix := s2[idx:]
    s3 := s2[:idx]

    s4 := strings.TrimSpace(s3)
    arr := strings.Split(s4, ",")

    var names = ""
    var values = ""

    for i := 0; i < len(arr); i++ {
        nameEqValue := arr[i]
        s5 := strings.ReplaceAll(nameEqValue, " ", "")
        nvArr := strings.Split(s5, "=")
        if names != "" {
            names += ","
        }
        names += nvArr[0]
        if values != "" {
            values += ","
        }
        values += nvArr[1]
    }

    s6 := prefix + " (" + names + ") = ROW (" + values + ") " + suffix
    return db.Rebind(s6)
}

这样称呼:

    // language=SQL
    s := RebindMore(db, "UPDATE table_name SET a = ?, b = ? WHERE c = ?")
    db.MustExec(s, value1, value2)

在某些时候,我需要添加迁移,并希望只为每个 DB 添加单独的代码来处理差异(例如 CREATE)。

最后一点值得指出的是 MySQL 和 Postgres 处理大小写的方式非常不同。我最终只是将每个表和列名都转换为 lower_case 以避免不必要的复杂性。

【讨论】:

    【解决方案2】:

    在这种特殊情况下,在 SQL 末尾使用占位符 {{ph}} 并使用 strings.Replace() 将其替换为 ?或者根据 db 驱动程序 $1。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-19
      • 1970-01-01
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-21
      相关资源
      最近更新 更多