在Heroku部署期间,使用Go执行MySQL脚本时出现了SQL语法错误。

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

SQL syntax error when executing MySQL script using Go during Heroku deployment

问题

我正在部署一个使用Go和MySQL数据库的API到Heroku。我正在按照这个指南进行操作,并且已经设置好了一切,但是现在我正在尝试执行一个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 {
        // 在这里出错!
		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())
}
英文:

I am deploying an API made using Go and MySQl for the database to Heroku. I am following this guide on it and set up everything but now I am trying to execute a MySQL script to set up the tables with some dummy data. But I am constantly getting errors saying that the script is wrong even though I have used it locally with no issues. I have already connected the MySQL database to the Heroku environment and starting the database throws no errors.

Here are the logs showing the error when deploying it to 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())
}

答案1

得分: 4

大多数用于对MySQL运行查询的接口不支持多查询。换句话说,它们不允许使用分号分隔的多个SQL语句,每次调用只支持一个SQL语句。

在你的情况下,它返回了一个语法错误,因为给定输入被解析为单个语句,CREATE DATABASE语句中没有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客户端内置命令,也必须类似处理。

如果最终编写所有这些逻辑,基本上你已经在Go中重新实现了MySQL命令行客户端。

如果你跳过所有这些编码工作,直接使用os.exec.Command运行MySQL命令行客户端,会更快更简单。想想看,这将节省你数周的编码工作,避免重复实现MySQL客户端已经实现的运行SQL脚本的细微特性。

英文:

Most interfaces to run queries against MySQL do not support multi-query. In other words, they don't allow multiple SQL statements separated by semicolons, they only support one SQL statement per call.

In your case it returned a syntax error on USE ... because given that the input is parsed as a single statement, there is no USE in the CREATE DATABASE statement.

CREATE DATABASE assessment; USE assessment;

This means if you want to process a file of many SQL statements, you can't just do what you're doing and treat the entire file as one string to pass to a single call of Exec():

c, ioErr := ioutil.ReadFile("./schema.sql")
sqlScript := string(c)
...
_, err = db.Exec(sqlScript)

You must split the content of that file into individual SQL statements, and then loop over those statements, running each one at a time.

This is more complicated than it sounds, because:

  • SQL scripts may contain a DELIMITER statement that changes the character between statements from semicolon to something else.

  • There might be semicolons inside string literals or inside comments.

  • Some statements such as CREATE PROCEDURE, CREATE TRIGGER, etc. may contain semicolons between statements in the body of the routine. You don't want these semicolons to be the end of the statement, you want to include all the content to the end of the routine definition.

  • The DELIMITER statement itself can't be executed by the MySQL Server. It only control the client. So you must treat it as an exception that isn't sent to the server in your loop. In fact, there are a bunch of other mysql client builtin commands which must be treated similarly if you find them in an SQL script.

If you eventually code all this logic, you've basically reimplemented the MySQL command-line client in Go.

It would be far quicker and simpler if you skip past all that coding work, and just run the MySQL command-line client using os.exec.Command. Think about it — it'll save you weeks of coding work, duplicating all the nuanced features of running SQL scripts that is already implemented in the MySQL client.

答案2

得分: 0

好的,以下是翻译好的内容:

好的,所以我决定使用从Heroku获取的凭据直接连接到MySQL数据库。

这将返回数据库的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,并且没有遇到任何问题继续运行我的查询。

英文:

Ok so I decided to directly connect to the mysql database using the credentials given from Heroku.

This returns the URL to the database.

heroku config | grep CLEARDB_DATABASE_URL

It looks something like this:

mysql://alphanum-username:alphanum-password@us-cdbr-iron-east-01.cleardb.net/heroku_alphanum_name?reconnect=true

Then connect to it using mysql -u username -h host -p the host is from the URL which in this case is us-cdbr-iron-east-01.cleardb.net. Password is from alphanum-password.

I ran the same query but got this error which means I did not have access to the database.

ERROR 1044 (42000): Access denied for user 'bbf806af82a4c9'@'%' to database 'assessment'

Running show databases; shows that there are specific databases you can connect to as a user.

mysql> SHOW DATABASES;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| herokusdfhsdfhsdfsfsdf |
+------------------------+

From here I ran USE herokusdfhsdfhsdfsfsdf and ran the rest of my query with no issue.

huangapple
  • 本文由 发表于 2021年6月5日 21:06:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/67849900.html
匿名

发表评论

匿名网友

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

确定