Can't retrieve output parameter with Go MySQL - mysql

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.

Related

golang mysql driver failing at parameter replacement

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))

Using placeholder ? in Go mySql query for anything other than int

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.

mysql complains at syntax from go driver

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.

Delphi, ADO, MySQL: checking whether an empty set was returned

The version of Delphi is 7.
I'm sending a query to a MySQL database. What can be returned is either a set of data rows or simply an empty set. Nothing unusual. But I have no idea how to make a checking mechanism that will check whether it is a set of data or an empty set.
Here's some code:
var
Q: TADOQuery;
begin
Q := TADOQuery.Create(self);
Q.Connection := ADOConnection;
Q.SQL.Add('CALL get_shopping_cart_list()'); // Call stored procedure
Q.Open; // Send query and get some
// results back
// PSEUDOCODE
// IF get_shopping_cart_list() RETURNS A NON-EMPY SET THEN
// SHOW WHAT WE HAVE
// ELSE
// SHOW A MESSAGE THAT SAYS 'EMPTY SET'
Q.Free;
end;
Depending on Delphi version it can be either
if Q.IsEmpty then ...
or
if Q.BOF and Q.EOF then ...
You can also dive into Microsoft ADO. As long as there are no multiple statements in your query, Q.RecordSet.EOF and Q.RecordSet.BOF should do.
http://msdn.microsoft.com/library/windows/desktop/ms675787.aspx
http://www.w3schools.com/ado/ado_ref_recordset.asp
http://msdn.microsoft.com/library/windows/desktop/ms677539.aspx
Also please do not forget to guard memory management of errors.
Q := TADOQuery.Create;
try
.... do this or that ....
.... do this or that ....
.... do this or that ....
finally
Q.free;
end;

go convert empty interface to string

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")