直接从文件或字符串中执行Go中的SQL脚本

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

Execute SQL script in Go directly from file or string

问题

在我的应用程序安装脚本中,我正在检查数据库是否包含任何表。如果数据库为空,我想运行 DML 和 DDL SQL 脚本。

目前并不重要它是否从一个单独的 .sql 文件中读取 SQL,所以我直接将它们放入两个字符串中 - 一个用于 DDL,一个用于 DML - 然后将它们连接起来。

我的问题是,当我尝试使用 .Exec(sqlStr) 运行生成表并将数据插入其中的脚本时,我现在遇到了这个错误:

"pq: cannot insert multiple commands into a prepared statement"

当然,我可以使用一个变通方法。类似这样:

sqlStr := sqlDML + sqlDDL
sqlStmtSlice := strings.Split(sqlStr, ";")
for i:= 0; i < len(sqlStmtSlice) i++ {
// 逐个执行每个语句!
}

然而,我不确定我是否喜欢这种方法。肯定有更好的方法来从文件中加载 SQL 脚本并执行整个批处理,对吗?你知道吗?

附注:我正在使用 Go 的 PostgreSQL 驱动程序,但我不认为这会有任何区别。

编辑

由于目前似乎没有更好的解决方案来完成这个任务,我对上面的伪代码进行了一点改进,经过测试似乎工作得很好:

tx, err := db.Begin()

sqlStr := fmt.Sprintf(sqlDML + sqlDDL)
sqlStmtSlice := strings.Split(sqlStr, ";\r")

if err != nil {
return err
}

defer func() {
_ = tx.Rollback()
}()

for _, q := range sqlStmtSlice {
_, err := tx.Exec(q)

if err != nil {
    return err
}

}

err = tx.Commit()

英文:

In my install script for my application I'm checking to see if the db contains any tables. If the database is empty I have DML and DDL SQL script I'd like to run.

It's not important that it reads the SQL from a seperate .sql file, so right now I've just put it directly into two strings - one for DDL and one for DML - and concatenated those.

My problem now is that I'm now getting this error, when trying to run the script for generating tables and inserting data into them with .Exec(sqlStr):

&quot;pq: cannot insert multiple commands into a prepared statement&quot;

I can of course do a workaround. Something like:

sqlStr := sqlDML + sqlDDL
sqlStmtSlice := strings.Split(sqlStr, &quot;;&quot;)
for i:= 0; i &lt; len(sqlStmtSlice) i++ {
    // Exec() each individual statement!
}

However, I'm not sure I like that method at all. Surely there has to be a much better way of just loading a SQL script from file and executing the whole batch, right? Do you know?

Ps. I'm using the PostgreSQL driver for Go, but I don't think that makes any difference.

Edit:

Since there doesn't seem to be any better solution to get this done at the time, I made a slight improvement to the above pseudo code, which is tested and seems to work just fine:

tx, err := db.Begin()

sqlStr := fmt.Sprintf(sqlDML + sqlDDL)
sqlStmtSlice := strings.Split(sqlStr, &quot;;\r&quot;)

if err != nil {
    return err
}

defer func() {
    _ = tx.Rollback()
}()

for _, q := range sqlStmtSlice {
    _, err := tx.Exec(q)

    if err != nil {
        return err
    }
}

err = tx.Commit()

答案1

得分: 7

据我所知,如果不允许多个语句查询,目前没有更好的方法。这与你使用的驱动程序无关,因为这是database/sql包的限制。关于它是否是一个好的设计的争论是另一个问题(我相信已经有很多讨论了)。

在替代方案方面,你可以使用SQL模式迁移工具或从中获取灵感。一般的约定是使用一个语义上无关的标记,比如注释,并在这些标记周围进行分割。

关于在Go语言中的示例,你可以参考以下项目:

  • goose:功能齐全,迁移可以用Go语言编写
  • rambler:轻量级,仅支持SQL

声明:我是rambler的开发者。不过,你应该确实看看goose,它非常酷。

英文:

As far as I know, there isn't a real better method if multiple statement queries aren't allowed. It has nothing to do with the driver you're using, as this is a database/sql package limitation. Debating for whether or not it is a good design is another question (and I'm sure there is plenty already).

On the alternatives side, you could probably use a SQL Schema Migration tool or use inspiration from them. The general convention is to use a semantically inert marker, such as a comment, and split around theses.

For examples in golang, you can see:

  • goose: full featured, migrations can be written in Go
  • rambler: lightweight, SQL-only

Disclaimer: I'm rambler's developer. That said, you should definitively have a look at goose, which is really cool.

答案2

得分: 1

我已经翻译好了,以下是翻译的内容:

在一段时间内一直在努力解决同样的问题。我需要它出于稍微不同的原因(在运行测试之前初始化数据库)。

正如Elwinar已经提到的,database/sql包不允许这样做。我克服这个问题的方法是创建一个Python脚本(我们称之为sql_runner.py),该脚本执行一个文件,然后运行一个go exec命令来执行Python脚本(不要忘记将其设置为可执行)。

所以运行SQL文件的Python命令是:cursor.execute(open("setting_up.sql"), "r").read())

以下是运行该文件的Go代码的一部分:

cmd := exec.Command("sql.py")

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Println(cmd.Run())
英文:

Have struggled with the same problem for some time. I needed it for a slightly different reason (initialize the database before running tests).

As Elwinar already mentioned, database/sql package does not allow this. The way I overcame this is by creating a python script (let's call it sql_runner.py) which executes a file and then run a go exec command to execute python script (do not forget to make it executable).

So a python command to run sql file: cursor.execute(open(&quot;setting_up.sql&quot;), &quot;r&quot;).read())

And part's of a Go code to run this file:

cmd := exec.Command(&quot;sql.py&quot;)

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Println(cmd.Run())

huangapple
  • 本文由 发表于 2015年3月3日 21:18:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/28832831.html
匿名

发表评论

匿名网友

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

确定