我的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 ...
干杯
最佳答案
我找到了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方言kludge-translate转换为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
,以避免不必要的复杂性。关于mysql - 在Golang中如何支持多种SQL语法(MySQL vs Postgres),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/46096522/