在golang中如何支持多种sql语法(mysql vs postgres)

在golang中如何支持多种sql语法(mysql vs postgres),mysql,sql,postgresql,go,prepared-statement,Mysql,Sql,Postgresql,Go,Prepared Statement,我的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 我可以通

我的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

我可以通过在运行时编辑字符串或维护多个查询来解决这个问题

有更好的办法吗

我不想用一个控制我所有db访问的外部库来进行一些巨大的抽象,但是如果有一些轻量级的库能够神奇地修复语法,我会很好地使用它

编辑: 总结一下我之前说过的,困扰我的是mysql必须使用“?”而postgres必须使用1美元、2美元


Cheers

在这种特殊情况下,在SQL的末尾使用一个占位符{{ph}},并使用strings.Replace()将其替换为?或者根据db驱动程序计算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)
}
在某个时候,我将需要添加迁移,并希望只为每个DB添加单独的代码来处理差异(如CREATE)


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

您可以使用一个库来编写流畅的SQL,就像您所说的“一些巨大的抽象”是什么意思?你反对使用ORM吗?因为听起来ORM正是你所需要的。如果你不反对使用ORM,那么GORM就是你的朋友——我现在只有一个表,并且可能只有几个必须非常有效的查询。他们必须使用多个数据库。我并不坚决反对或支持ORM,只是对于我的用例,我认为它不合适。如果您的用例要求您支持不同的SQL方言并具有一个接口,那么您的用例就可以通过ORM得到满足。是的,当然是一张桌子,如果成功的话可能会变成两张或三张。。不要对什么是对什么是错做出决定,要考虑成功和失败。我明白,仅仅为了一个小问题而扔掉整个库是过分的,但在某些情况下,我们希望关注交付与技术正确性。使用接口。定义公共接口并根据设置在运行时设置特定实现。
    // language=SQL
    s := RebindMore(db, "UPDATE table_name SET a = ?, b = ? WHERE c = ?")
    db.MustExec(s, value1, value2)