在golang中如何支持多种sql语法(mysql vs postgres)
我的go应用程序应支持多个数据库。这意味着,在不同的数据库中运行相同的二进制文件,应用程序使用的数据库将由配置决定 问题是,每个数据库都有自己的预处理语句语法。 例如:在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 我可以通
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)