Reputation: 598
I am trying to use named parameter query in golang as follows:
stmt, err := db.Prepare("insert into users(name, email) values(@name, @email)")
if err != nil {
//error handling
}
res, err := stmt.Exec(sql.Named("name", name), sql.Named("email", email))
if err != nil {
//error: sql: expected 0 arguments, got 2
}
I get the following error: "sql: expected 0 arguments, got 2".
What is correct way to write this query?
Upvotes: 8
Views: 19054
Reputation: 47
Use sql params by name for Oracle, Postgre, Mysql ....
/*Examples:
ParamReplacingMode = 0 // no replacing params
isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle
Params(isql,
":C", "USA", // the order for Params is not bound, you can add params any order, case insensitive
":Sum", 130.5)
res,err:= SqlQuery(isql) // result: db.Query("SELECT * FROM table WHERE price+vat>:Sum and country=:C", 130.5,"USA")
if err!=nil {
fmt.Println("Error:"+fmt.Sprint(err)+SqlStr(isql))
}
or
ParamReplacingMode = 1 // MySQL - replacing params to "?"
isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle convert to Mysql
Params(isql,
":C", "USA", // the order for Params is not bound, you can add params any order, case insensitive
":Sum", 130.5)
res,err:= SqlQuery(isql) // result: db.Query("SELECT * FROM table WHERE price+vat>? and country=?", 130.5,"USA") //replacing params to "?"
or
ParamReplacingMode = 0 //no replacing params
isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>$1 and country=$2") // by Postgre
AddParam(isql,"$1", 130.5) // AddParam() is the same as Params(), but only for 1 param
AddParam(isql,"$2", "USA") // the order for AddParams is not bound, you can add params any order
res,err:= SqlQuery(isql) // result: db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
or
ParamReplacingMode = 2 // mode Oracle to Postgre, replacing params to <$Number>
isql:=InitSql(db,"SELECT * FROM table WHERE price+vat>:Sum and country=:C") // by Oracle convert to Postgre
AddParam(isql,":C","USA") // AddParam()) is the same as Params(), but only for 1 param
AddParam(isql,":Sum",130.5)
res,err:= SqlQuery(isql) // result: db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
SqlExec() is similar as SqlQuery(), but call db.Exec(...)
Example , add NULL value:
isql:=InitSql(db,"INSERT INTO table (id, name) VALUES (:ID,:Name)" )
Params(isql, ":ID", 1, ":Name", nil) //NULL
res,err:= SqlExec(isql)
if err!=nil {
fmt.Println("Error:"+fmt.Sprint(err)+SqlStr(isql))
}
*/
type (
TisqlMode int32
TisqlAt struct {
ParamName string
Pos int
ParamVal any
}
Tisql struct {
Sql string
ResultSql string
DB *sql.DB
Params map[string]any
At []TisqlAt
}
)
const (
Oracle TisqlMode = iota //0, no replacing params
Mysql //1, "SELECT * FROM table WHERE price+vat>:Sum and country=:C" -> db.Query("SELECT * FROM table WHERE price+vat>? and country=?", 130.5,"USA")
Postgre //2, "SELECT * FROM table WHERE price+vat>:Sum and country=:C" -> db.Query("SELECT * FROM table WHERE price+vat>$1 and country=$2", 130.5,"USA")
)
var ParamReplacingMode TisqlMode = -1 //-1 = unknown, 0 = no replacing params, 1 = to MySql, 2 = to Postgre
func indexAt(pStr, pSubStr string, pFrom ...int) int { //Index from position
pos := 0
if len(pFrom) > 0 {
pos = pFrom[0]
}
if pos >= len(pStr) {
return -1
}
if pos < 0 {
pos = 0
}
idx := strings.Index(pStr[pos:], pSubStr)
if idx > -1 {
idx += pos
}
return idx
}
func InitSql(db *sql.DB, sql string) *Tisql {
if ParamReplacingMode < 0 { // unknow
_, err := db.Exec("?")
if err != nil {
s := strings.ToLower(fmt.Sprint(err))
if indexAt(s, "mysql") > 0 {
ParamReplacingMode = 1
} else {
ParamReplacingMode = 0
}
}
}
var isql Tisql
isql.Sql = sql
isql.DB = db
isql.Params = make(map[string]any)
return &isql
}
func AddParam(isql *Tisql, pParam string, pValue any) bool {
pParam = strings.TrimSpace(pParam)
if len(pParam) > 1 {
isql.Params[pParam] = pValue
return true
}
return false
}
func Params(isql *Tisql, pParam ...any) (added int) { //https://stackoverflow.com/questions/6996704/how-to-check-variable-type-at-runtime-in-go-language
c := len(pParam)
if c%2 == 1 {
c = c - 1
}
cnt, i, s := 0, 0, ""
for i < c {
s = strings.TrimSpace(pParam[i].(string))
if len(s) > 1 {
isql.Params[s] = pParam[i+1]
cnt++
}
i += 2
}
return cnt
}
func paramOrder(isql *Tisql, pCheckParamCount bool) error {
var at TisqlAt
isql.ResultSql = isql.Sql
t := ""
b := strings.ToLower(isql.Sql) + " "
mMode := ParamReplacingMode
var p, p1, p2 int
for name, v := range isql.Params {
p1 = 0
for p1 >= 0 {
p = indexAt(b, strings.ToLower(name), p1)
if p < 0 {
p1 = -1
continue
} else {
p2 = p + len(name)
t = b[p2 : p2+1] //char after param
if indexAt(" :,;!?%$<>^*+-/()[]{}=|'`\"\r\n\t", t) < 0 {
p1 = p + 1
continue
}
p1 = -1
}
}
if p >= 0 {
at.Pos = p
at.ParamVal = v
at.ParamName = name
isql.At = append(isql.At, at)
}
}
if pCheckParamCount && len(isql.At) != len(isql.Params) {
return fmt.Errorf("Different count of params %d / %d", len(isql.At), len(isql.Params))
}
if len(isql.At) > 1 {
sort.Slice(isql.At,
func(i, j int) bool {
return isql.At[i].Pos < isql.At[j].Pos
})
}
mLen := len(isql.Sql)
switch mMode {
case 1: //to Mysql
{
p1, p2, s := 0, 0, ""
for _, at := range isql.At {
p2 = at.Pos
if p2 >= 0 && p2 <= mLen {
if p2 > p1 {
s += isql.Sql[p1:p2] + "?"
}
p1 = p2 + len(at.ParamName)
}
}
if p1 < len(isql.Sql) {
s += isql.Sql[p1:len(isql.Sql)]
}
isql.ResultSql = s
}
case 2: //to Postgre
{
p1, p2, s := 0, 0, ""
for i, at := range isql.At {
p2 = at.Pos
if p2 >= 0 && p2 <= mLen {
if p2 > p1 {
s += isql.Sql[p1:p2] + "$" + fmt.Sprint(i+1)
}
p1 = p2 + len(at.ParamName)
}
}
if p1 < len(isql.Sql) {
s += isql.Sql[p1:len(isql.Sql)]
}
isql.ResultSql = s
}
}
return nil
}
func ParamsStr(isql *Tisql) string {
s := ""
for i, at := range isql.At {
s += "[" + fmt.Sprint(i+1) + ". " + at.ParamName + "=\"" + fmt.Sprint(at.ParamVal) + "\"]"
}
return s
}
func SqlStr(isql *Tisql) string {
s := " SQL:[" + isql.ResultSql + "]"
if len(isql.At) > 0 {
s += " Params:" + ParamsStr(isql)
}
return s
}
func SqlExec(isql *Tisql, opt ...bool) (sql.Result, error) {
checkParamCount := false
if len(opt) > 0 {
checkParamCount = opt[0]
}
err := paramOrder(isql, checkParamCount)
if err != nil {
return nil, err
}
mLen := len(isql.At)
mVal := make([]any, mLen)
for i := range mVal {
mVal[i] = isql.At[i].ParamVal
}
return isql.DB.Exec(isql.ResultSql, mVal...)
}
func SqlQuery(isql *Tisql, opt ...bool) (*sql.Rows, error) {
checkParamCount := false
if len(opt) > 0 {
checkParamCount = opt[0]
}
err := paramOrder(isql, checkParamCount)
if err != nil {
return nil, err
}
mLen := len(isql.At)
mVal := make([]any, mLen)
for i := range mVal {
mVal[i] = isql.At[i].ParamVal
}
return isql.DB.Query(isql.ResultSql, mVal...)
}
Upvotes: 0
Reputation: 1438
Go needs to support every kind of SQL server - and not all SQL servers support named parameters. The servers that do support them do it with a variety of quirks and "gotchas". But they all support positional parameters just fine.
However there is an open issue for it for the mysql-driver: https://github.com/go-sql-driver/mysql/issues/561
Upvotes: 3