在 Heroku 部署期间使用 Go 执行 MySQL 脚本时出现 SQL 语法错误
SQL syntax error when executing MySQL script using Go during Heroku deployment
我正在将使用 Go 制作的 API 和 MySQl 的数据库部署到 Heroku。我在上面关注 guide 并设置了所有内容,但现在我正在尝试执行 MySQL 脚本以使用一些虚拟数据设置表格。但是我不断收到错误消息,说脚本是错误的,即使我在本地使用它没有任何问题。我已经将 MySQL 数据库连接到 Heroku 环境并且启动数据库不会引发任何错误。
以下是将其部署到 Heroku 时显示错误的日志:
2021-06-05T12:49:16.442359+00:00 app[web.1]: ERROR 2021/06/05 12:49:16 main.go:45: Error executing SQL script : Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'USE assessment;
schema.sql
CREATE DATABASE assessment;
USE assessment;
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL
);
INSERT INTO `users` VALUES (1,"test1@gmail.com");
INSERT INTO `users` VALUES (2,"test2@gmail.com");
INSERT INTO `users` VALUES (3,"test3@gmail.com");
INSERT INTO `users` VALUES (4,"test4@gmail.com");
INSERT INTO `users` VALUES (5,"test5@gmail.com");
CREATE TABLE features (
feature_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id INTEGER NOT NULL,
feature_name VARCHAR(100) NOT NULL,
can_access BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO `features` VALUES (1, 1, "automated-investing", 1);
INSERT INTO `features` VALUES (2, 1, "crypto", 0);
INSERT INTO `features` VALUES (3, 2, "crypto", 0);
INSERT INTO `features` VALUES (4, 3, "automated-investing", 0);
INSERT INTO `features` VALUES (5, 4, "automated-investing", 1);
INSERT INTO `features` VALUES (7, 1, "financial-tracking", 1);
INSERT INTO `features` VALUES (8, 2, "financial-tracking", 0);
INSERT INTO `features` VALUES (9, 3, "financial-tracking", 1);
INSERT INTO `features` VALUES (10, 4, "financial-tracking", 0);
main.go
package main
import (
"io/ioutil"
"log"
"net/http"
"os"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"github.com/yudhiesh/api/controller"
"github.com/yudhiesh/api/middleware"
)
func main() {
router := mux.NewRouter()
router.Use(middleware.ResponseHeaders)
router.Use(middleware.LogRequest)
errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
err := godotenv.Load(".env")
port := os.Getenv("PORT")
if port == "" {
errorLog.Fatal("$PORT is not set")
}
dsn := os.Getenv("DSN")
if dsn == "" {
errorLog.Fatal("$DATABASE_URL is not set")
}
db, err := controller.OpenDB(dsn)
if err != nil {
errorLog.Fatal(err)
}
defer db.Close()
c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
if ioErr != nil {
errorLog.Fatalf("Error loading SQL schema : %s", ioErr)
}
_, err = db.Exec(sqlScript)
if err != nil {
// FAILS HERE!
errorLog.Fatalf("Error executing SQL script : %s", err)
}
app := &controller.Application{
DB: db,
ErrorLog: errorLog,
InfoLog: infoLog,
}
addr := ":" + port
server := &http.Server{
Handler: router,
Addr: addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
router.HandleFunc("/feature", app.GetCanAccess).Methods("GET")
router.HandleFunc("/feature", app.InsertFeature).Methods("POST")
http.Handle("/", router)
infoLog.Printf("Connected to port %s", port)
errorLog.Fatal(server.ListenAndServe())
}
好的,所以我决定使用 Heroku 提供的凭据直接连接到 mysql 数据库。
这returns URL 到数据库。
heroku config | grep CLEARDB_DATABASE_URL
看起来像这样:
mysql://alphanum-username:alphanum-password@us-cdbr-iron-east-01.cleardb.net/heroku_alphanum_name?reconnect=true
然后使用 mysql -u username -h host -p
连接到它,主机来自 URL,在本例中是 us-cdbr-iron-east-01.cleardb.net
。密码来自 alphanum-password
.
我 运行 同样的查询但是得到了这个错误,这意味着我没有访问数据库的权限。
ERROR 1044 (42000): Access denied for user 'bbf806af82a4c9'@'%' to database 'assessment'
运行 show databases;
表明您可以作为用户连接到特定的数据库。
mysql> SHOW DATABASES;
+------------------------+
| Database |
+------------------------+
| information_schema |
| herokusdfhsdfhsdfsfsdf |
+------------------------+
从这里我 运行 USE herokusdfhsdfhsdfsfsdf
和 运行 我的其余查询没有问题。
针对 MySQL 的 运行 查询的大多数接口不支持多查询。换句话说,它们不允许用分号分隔多个 SQL 语句,它们每次调用只支持一个 SQL 语句。
在您的情况下,它在 USE ...
上返回语法错误,因为假设输入被解析为单个语句,CREATE DATABASE statement.[=20 中没有 USE
=]
CREATE DATABASE assessment; USE assessment;
这意味着如果你想处理一个包含许多 SQL 语句的文件,你不能只做你正在做的事情并将整个文件视为一个字符串来传递给 Exec()
:
c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
...
_, err = db.Exec(sqlScript)
您必须将该文件的内容拆分为单独的 SQL 语句,然后遍历这些语句,运行一次一个。
这比听起来更复杂,因为:
SQL 脚本可能包含一个 DELIMITER
语句,该语句将语句之间的字符从分号更改为其他字符。
字符串文字或注释中可能有分号。
某些语句,例如 CREATE PROCEDURE、CREATE TRIGGER 等,在例程主体中的语句之间可能包含分号。你不希望这些分号成为语句的结尾,你希望包含所有内容到例程定义的结尾。
DELIMITER
语句本身不能 被MySQL 服务器执行。它只控制客户端。因此,您必须将其视为未发送到循环中服务器的异常。事实上,如果您在 SQL 脚本中找到其他一些 mysql client builtin commands,则必须对其进行类似处理。
如果您最终编写了所有这些逻辑,那么您基本上已经在 Go 中重新实现了 MySQL 命令行客户端。
如果您跳过所有编码工作,只使用 os.exec.Command 运行 MySQL 命令行客户端,将会更快更简单。想一想 — 它将为您节省数周的编码工作,复制已在 MySQL 客户端中实现的 运行ning SQL 脚本的所有细微差别。
我正在将使用 Go 制作的 API 和 MySQl 的数据库部署到 Heroku。我在上面关注 guide 并设置了所有内容,但现在我正在尝试执行 MySQL 脚本以使用一些虚拟数据设置表格。但是我不断收到错误消息,说脚本是错误的,即使我在本地使用它没有任何问题。我已经将 MySQL 数据库连接到 Heroku 环境并且启动数据库不会引发任何错误。
以下是将其部署到 Heroku 时显示错误的日志:
2021-06-05T12:49:16.442359+00:00 app[web.1]: ERROR 2021/06/05 12:49:16 main.go:45: Error executing SQL script : Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'USE assessment;
schema.sql
CREATE DATABASE assessment;
USE assessment;
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL
);
INSERT INTO `users` VALUES (1,"test1@gmail.com");
INSERT INTO `users` VALUES (2,"test2@gmail.com");
INSERT INTO `users` VALUES (3,"test3@gmail.com");
INSERT INTO `users` VALUES (4,"test4@gmail.com");
INSERT INTO `users` VALUES (5,"test5@gmail.com");
CREATE TABLE features (
feature_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
user_id INTEGER NOT NULL,
feature_name VARCHAR(100) NOT NULL,
can_access BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
INSERT INTO `features` VALUES (1, 1, "automated-investing", 1);
INSERT INTO `features` VALUES (2, 1, "crypto", 0);
INSERT INTO `features` VALUES (3, 2, "crypto", 0);
INSERT INTO `features` VALUES (4, 3, "automated-investing", 0);
INSERT INTO `features` VALUES (5, 4, "automated-investing", 1);
INSERT INTO `features` VALUES (7, 1, "financial-tracking", 1);
INSERT INTO `features` VALUES (8, 2, "financial-tracking", 0);
INSERT INTO `features` VALUES (9, 3, "financial-tracking", 1);
INSERT INTO `features` VALUES (10, 4, "financial-tracking", 0);
main.go
package main
import (
"io/ioutil"
"log"
"net/http"
"os"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
"github.com/yudhiesh/api/controller"
"github.com/yudhiesh/api/middleware"
)
func main() {
router := mux.NewRouter()
router.Use(middleware.ResponseHeaders)
router.Use(middleware.LogRequest)
errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
err := godotenv.Load(".env")
port := os.Getenv("PORT")
if port == "" {
errorLog.Fatal("$PORT is not set")
}
dsn := os.Getenv("DSN")
if dsn == "" {
errorLog.Fatal("$DATABASE_URL is not set")
}
db, err := controller.OpenDB(dsn)
if err != nil {
errorLog.Fatal(err)
}
defer db.Close()
c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
if ioErr != nil {
errorLog.Fatalf("Error loading SQL schema : %s", ioErr)
}
_, err = db.Exec(sqlScript)
if err != nil {
// FAILS HERE!
errorLog.Fatalf("Error executing SQL script : %s", err)
}
app := &controller.Application{
DB: db,
ErrorLog: errorLog,
InfoLog: infoLog,
}
addr := ":" + port
server := &http.Server{
Handler: router,
Addr: addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
router.HandleFunc("/feature", app.GetCanAccess).Methods("GET")
router.HandleFunc("/feature", app.InsertFeature).Methods("POST")
http.Handle("/", router)
infoLog.Printf("Connected to port %s", port)
errorLog.Fatal(server.ListenAndServe())
}
好的,所以我决定使用 Heroku 提供的凭据直接连接到 mysql 数据库。
这returns URL 到数据库。
heroku config | grep CLEARDB_DATABASE_URL
看起来像这样:
mysql://alphanum-username:alphanum-password@us-cdbr-iron-east-01.cleardb.net/heroku_alphanum_name?reconnect=true
然后使用 mysql -u username -h host -p
连接到它,主机来自 URL,在本例中是 us-cdbr-iron-east-01.cleardb.net
。密码来自 alphanum-password
.
我 运行 同样的查询但是得到了这个错误,这意味着我没有访问数据库的权限。
ERROR 1044 (42000): Access denied for user 'bbf806af82a4c9'@'%' to database 'assessment'
运行 show databases;
表明您可以作为用户连接到特定的数据库。
mysql> SHOW DATABASES;
+------------------------+
| Database |
+------------------------+
| information_schema |
| herokusdfhsdfhsdfsfsdf |
+------------------------+
从这里我 运行 USE herokusdfhsdfhsdfsfsdf
和 运行 我的其余查询没有问题。
针对 MySQL 的 运行 查询的大多数接口不支持多查询。换句话说,它们不允许用分号分隔多个 SQL 语句,它们每次调用只支持一个 SQL 语句。
在您的情况下,它在 USE ...
上返回语法错误,因为假设输入被解析为单个语句,CREATE DATABASE statement.[=20 中没有 USE
=]
CREATE DATABASE assessment; USE assessment;
这意味着如果你想处理一个包含许多 SQL 语句的文件,你不能只做你正在做的事情并将整个文件视为一个字符串来传递给 Exec()
:
c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
...
_, err = db.Exec(sqlScript)
您必须将该文件的内容拆分为单独的 SQL 语句,然后遍历这些语句,运行一次一个。
这比听起来更复杂,因为:
SQL 脚本可能包含一个
DELIMITER
语句,该语句将语句之间的字符从分号更改为其他字符。字符串文字或注释中可能有分号。
某些语句,例如 CREATE PROCEDURE、CREATE TRIGGER 等,在例程主体中的语句之间可能包含分号。你不希望这些分号成为语句的结尾,你希望包含所有内容到例程定义的结尾。
DELIMITER
语句本身不能 被MySQL 服务器执行。它只控制客户端。因此,您必须将其视为未发送到循环中服务器的异常。事实上,如果您在 SQL 脚本中找到其他一些 mysql client builtin commands,则必须对其进行类似处理。
如果您最终编写了所有这些逻辑,那么您基本上已经在 Go 中重新实现了 MySQL 命令行客户端。
如果您跳过所有编码工作,只使用 os.exec.Command 运行 MySQL 命令行客户端,将会更快更简单。想一想 — 它将为您节省数周的编码工作,复制已在 MySQL 客户端中实现的 运行ning SQL 脚本的所有细微差别。