I've already setup and pinged my mysql database connection. It is working and I can return rows using both db.Query and by preparing a query first. I can use the placeholder ? to then specify an id. Is it possible to use the ? as a placeholder for a column name? In the example here I am trying to return all rows from column firstName in table persons.
qry, err := db.Prepare("SELECT ? FROM persons")
if err != nil { log.Fatal(err) }
defer qry.Close()
rows, err :=qry.Query("firstName")
if err != nil { log.Fatal(err) }
defer rows.Close()
I get the following error:
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 '?' at line 1
You can't use placeholders for identifiers (such as table and column names), placeholders are for values. You can think of identifiers as being similar to variable or function names in Go so being able to use placeholders for identifiers would be akin to having an eval as in various scripting languages.
This reduces you to using fmt.Sprintf and similar string operations for building the SQL when you don't know the identifiers until runtime:
col := "firstName"
sql := fmt.Sprintf("select %s from persons", col)
but this opens you up to SQL injection and quoting problems so you'd want some sort of whitelist:
quotedColumns := map[string]string{
"firstName": "`firstName`",
"lastName": "`lastName`",
...
}
quoted, ok := quotedColumns[columnName]
if !ok {
// Do something with the error here and run away...
}
sql := fmt.Sprintf("select %s from persons", quoted)
Note that I've included the MySQL backtick quoting in the map's values. There's nothing in the standard interface for quoting/escaping an identifier so you have to do it yourself. If you're already writing the whitelist map by hand then you may as well include the quoting by hand too; otherwise you could write your own quoting function for identifiers by reading the MySQL documentation on quoting and doing a couple (hopefully) simple string operations.
Related
I have a stored procedure which inserts an entity and returns its new UUID, and while I can generate the code to create the right query, it errors out when using go-sql-driver/mysql. So the following code...
func test_insert() *sql.Rows {
db := openDbConnection()
defer db.Close()
results, err := db.Query("call saveargument(null, 'Test Argument', 'Test Argument', '1', null, null, null, 1, 'test_user', #newargumentid);\nselect #newargumentid;")
toolkit.HandleError(err)
return results
}
func openDbConnection() *sql.DB {
var db, err = sql.Open("mysql", getConnectionString(entities.GetConfig()))
toolkit.HandleError(err)
return db
}
... produces the following error:
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 'select #newargumentid' at line 2
I'm not sure why such a basic piece of SQL could be so problematic. Any insights anyone?
You can't run more than one statement in a single db.Query() call. This is true of most query interfaces in all programming languages.
Call your stored procedure in one call to db.Query(), then query select #newargumentid in a second call to db.Query().
I just tested #Bill's answer and I can confirm it does not work as at Go1.14. My workaround was to rewrite my stored procedure to "return" values by doing a select at the end:
CREATE PROCEDURE foo()
BEGIN
# do stuff...
SELECT 'bar';
END;
And then in Go just read it like any other query:
res, _ := db.Query("CALL foo()")
res.Next()
var bar string
res.Scan(&bar)
println(bar)
Note this method also works for multiple columns and multiple rows.
OS Mojave,
MySQL v8.0.15,
go 1.12.3 darwin/amd64
import (
"database/sql"
// import mysql driver anonymously (just run the init)
_ "github.com/go-sql-driver/mysql"
)
...
_, err = db.db.Exec("USE ?", "test")
if err != nil {
return errors.Wrapf(err, "error selecting database %s", opt.Database)
}
_, err = db.db.Prepare("SELECT value FROM ? WHERE key = ?")
if err != nil {
return errors.Wrap(err, "error generating SELECT statement")
}
The error I get is error selecting database test: 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 '?' at line 1
I get the same error (about syntax around '?') for any parameter replacement I attempt to do. I've checked the manual as well as tutorials and examples I've found online and can't quite tell what I'm doing wrong.
If I replace the '?'s with values (strings) then everything works fine.
Question marks ? in prepared statements are for values, not table, database or column names.
Use fmt.Sprintf to fill in the database structure related values.
Example:
_, err := db.db.Prepare(fmt.Sprintf("SELECT value FROM %s WHERE key = ?", table))
I'm using the github.com/go-sql-driver/mysql and mysql 5.7.10. I have a function:
bulkSetStatus := func(docVers []*_documentVersion) error {
if len(docVers) > 0 {
query := strings.Repeat("CALL documentVersionSetStatus(?, ?); ", len(docVers))
args := make([]interface{}, 0, len(docVers)*2)
for _, docVer := range docVers {
args = append(args, docVer.Id, docVer.Status)
}
_, err := db.Exec(query, args...)
return err
}
return nil
}
which works if len(docVers) == 1 but when there are more, resulting in multiple CALLs to the stored procedure, it errors:
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 'CALL documentVersionSetStatus(?, ?)' at line 1
I have also tried a newline character between each call but I get the same error. If I run this in mysql workbench with multiple CALLs to this procedure it works fine, I'm not sure what is wrong with the syntax here.
I have logged out the exact full text with the arguments and it is as expected:
CALL documentVersionSetStatus("9c71cac14a134e7abbc4725997d90d2b", "inprogress"); CALL documentVersionSetStatus("beb65318da96406fa92990426a279efa", "inprogress");
go-sql-driver, by default, does not allow you to have multiple statements in one query (as you are doing by chaining together multiple CALL statements like that) due to the security implications if an attacker manages to perform SQL injection (for example, by injecting 0 OR 0; DROP TABLE foo).
To allow this, you must explicitly enable it by passing multiStatements parameter when connecting to the database, e.g.
db, err := sql.Open("mysql", "user:password#/dbname?multiStatements=True")
Source: https://github.com/go-sql-driver/mysql#multistatements
I have fixed the proc call by doing some manual string interpolation for the parameters instead of using the correct ? way of doing it:
bulkSetStatus := func(docVers []*_documentVersion) error {
if len(docVers) > 0 {
query := strings.Repeat("CALL documentVersionSetStatus(%q, %q); ", len(docVers))
args := make([]interface{}, 0, len(docVers)*2)
for _, docVer := range docVers {
args = append(args, docVer.Id, docVer.Status)
}
_, err := db.Exec(fmt.Sprintf(query, args...))
return err
}
return nil
}
so I swap out the ? for %q and us fmt.Sprintf to inject the parameters, I should note that slugonamission's answer is partially correct, I did need to add the connection string parameter multiStatements=true in order to get this to work with my other changes. I will log an issue on the github repo it looks like there may be some param interpolation issue when there is more than one statement, I think the error was happening because the mysql db was trying to run the script with ? literals in it.
I can't seem to dynamically ORDER BY with db.Select(). I've Googled without any luck...
WORKS
rows, err := db.Query("SELECT * FROM Apps ORDER BY title DESC")
DOES NOT WORK
rows, err := db.Query("SELECT * FROM Apps ORDER BY ? DESC", "title")
I'm not getting any errors, the query simply fails to order.
Placeholders ('?') can only be used to insert dynamic, escaped values for filter parameters (e.g. in the WHERE part), where data values should appear, not for SQL keywords, identifiers etc. You cannot use it to dynamically specify the ORDER BY OR GROUP BY values.
You can still do it though, for example you can use fmt.Sprintf() to assemble the dynamic query text like this:
ordCol := "title"
qtext := fmt.Sprintf("SELECT * FROM Apps ORDER BY %s DESC", ordCol)
rows, err := db.Query(qtext)
Things to keep in mind:
Doing so you will have to manually defend vs SQL injection, e.g. if the value of the column name comes from the user, you cannot accept any value and just insert it directly into the query else the user will be able to do all kinds of bad things. Trivially you should only accept letters of the English alphabet + digits + underscore ('_').
Without attempting to provide a complete, all-extensive checker or escaping function, you can use this simple regexp which only accepts English letters, digits and '_':
valid := regexp.MustCompile("^[A-Za-z0-9_]+$")
if !valid.MatchString(ordCol) {
// invalid column name, do not proceed in order to prevent SQL injection
}
Examples (try it on the Go Playground):
fmt.Println(valid.MatchString("title")) // true
fmt.Println(valid.MatchString("another_col_2")) // true
fmt.Println(valid.MatchString("it's a trap!")) // false
fmt.Println(valid.MatchString("(trap)")) // false
fmt.Println(valid.MatchString("also*trap")) // false
I'm using the mymysql package and I'm trying to create a function which gets an SQL query and some parameters (as variadic empty interface):
func FindByQuery(statement string, params ...interface{}) (diver *DiverT, err error) {
values := make([]interface{}, len(params))
for i := range params {
values[i] = params[i]
}
// Both statements result in the same error...
row, _, execError := Db.QueryFirst(statement,values...)
row, _, execError := Db.QueryFirst(statement,params...)
// Additional code...
}
When I call this method using some kind of SQL, I always get an SQL error. I do something like:
FindByQuery("SELECT * FROM Diver WHERE Name=?", "Markus")
Which results in the following error:
Received #1064 error from MySQL server: "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 '?%!(EXTRA string=Markus)' at line 1"
What should I do that the parameter is converted correctly to a string (or whatever it is, if I have different parameter(s))?
Try using printf format syntax:
FindByQuery("SELECT * FROM Diver WHERE Name=%s", "Markus")