Golang SQL query variable substituion - sql

I have sql query that needs variable substitution for better consumption of my go-kit service.
I have dep & org as user inputs which are part of my rest service, for instance: dep = 'abc' and org = 'def'.
I've tried few things like:
rows, err := db.Query(
"select name from table where department='&dep' and organisation='&org'",
)
And:
rows, err := db.Query(
"select name from table where department=? and organisation=?", dep , org,
)
That led to error: sql: statement expects 0 inputs; got 2
Only hard-coded values work and substitution fails .
I haven't found much help from oracle blogs regarding this and wondering if there is any way to approach this.

Parameter Placeholder Syntax (reference: http://go-database-sql.org/prepared.html )
The syntax for placeholder parameters in prepared statements is
database-specific. For example, comparing MySQL, PostgreSQL, and
Oracle:
MySQL PostgreSQL Oracle
===== ========== ======
WHERE col = ? WHERE col = $1 WHERE col = :col
VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)
For oracle you need to use :dep, :org as placeholders.

As #dakait stated, on your prepare statement you should use : placeholders.
So, for completeness, you would get it working with something like:
package main
import (
"database/sql"
"fmt"
"log"
)
// Output is an example struct
type Output struct {
Name string
}
const (
dep = "abc"
org = "def"
)
func main() {
query := "SELECT name from table WHERE department= :1 and organisation = :2"
q, err := db.Prepare(query)
if err != nil {
log.Fatal(err)
}
defer q.Close()
var out Output
if err := q.QueryRow(dep, org).Scan(&out.Name); err != nil {
log.Fatal(err)
}
fmt.Println(out.Name)
}

Related

Submitting an SQL query with a slice parameter

I have a Snowflake query where I'm trying to update a field on all items where another field is in a list which is submitted to the query as a variable:
UPDATE my_table SET download_enabled = ? WHERE provider_id = ? AND symbol IN (?)
I've tried doing this query using the gosnowflake.Array function like this:
enable := true
provider := 1
query := "UPDATE my_table SET download_enabled = ? WHERE provider_id = ? AND symbol IN (?)"
if _, err := client.db.ExecContext(ctx, query, enable, provider,
gosnowflake.Array(assets)); err != nil {
fmt.Printf("Error: %v", err)
}
However, this code fails with the following error:
002099 (42601): SQL compilation error: Batch size of 1 for bind variable 1 not the same as previous size of 2.
So then, how can I submit a variable representing a list of values to an SQL query?
I found a potential workaround, which is to submit each item in the list as a separate parameter explicitly:
func Delimit(s string, sep string, count uint) string {
return strings.Repeat(s+sep, int(count)-1) + s
}
func doQuery(enable bool, provider int, assets ...string) error {
query := fmt.Sprintf("UPDATE my_table SET download_enabled = ? " +
"WHERE provider_id = ? AND symbol IN (%s)", Delimit("?", ", ", uint(len(assets))))
params := []interface{}{enable, provider}
for _, asset := range assets {
params = append(params, asset)
}
if _, err := client.db.ExecContext(ctx, query, params...); err != nil {
return err
}
return nil
}
Needless to say this is a less elegant solution then what I wanted but it does work.

gorm raw sql query execution

Am running a query to check if a table exists or not using the gorm orm for golang. Below is my code.
package main
import (
"fmt"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
_ "github.com/lib/pq"
)
// App sets up and runs the app
type App struct {
DB *gorm.DB
}
`const tableCreationQuery = `SELECT count (*)
FROM information_schema.TABLES
WHERE (TABLE_SCHEMA = 'api_test') AND (TABLE_NAME = 'Users')`
func ensureTableExists() {
if err := a.DB.Exec(tableCreationQuery); err != nil {
log.Fatal(err)
}
}`
The expected response should be either 1 or 0. I got this from another SO answer. Instead I get this
2020/09/03 00:27:18 &{0xc000148900 1 0xc000119ba0 0}
exit status 1
FAIL go-auth 0.287s
My untrained mind says its a pointer but how do I reference the returned values to determine what was contained within?
If you want to check if your SQL statement was successfully executed in GORM you can use the following:
tx := DB.Exec(sqlStr, args...)
if tx.Error != nil {
return false
}
return true
However in your example are using a SELECT statement then you need to check the result, which will be better suited to use the DB.Raw() method like below
var exists bool
DB.Raw(sqlStr).Row().Scan(&exists)
return exists

How to make query parameters optional

In Postgres in Go, how can I make query parameters optional?
In this example status is an optional condition. If no status is passed all rows from table records will be fetched.
How to make query parameter &d.Status an optional
type QueryParams struct {
Status string `json:"status"`
}
func (r repo) GetRecords(d *QueryParams) ([]*Records, error) {
statusQuery := ""
if d.Status != "" {
statusQuery = " where status = $1 "
}
query := "select id, title, status from records " + statusQuery
rows, err := r.db.Query(query, &d.Status)
}
Query is variadic so you could build an []interface{} to hold the arguments:
args := []interface{}{}
and then to conditionally build the argument list:
if d.Status != "" {
statusQuery = " where status = $1 "
args = append(args, &d.Status)
}
When you run the query, expand the arguments using ...:
rows, err := r.db.Query(query, args...)
You may use a flexible WHERE clause, e.g.
SELECT id, title, status
FROM records
WHERE status = $1 OR $1 IS NULL;
The logic here is that if you provide a value for $1, it must match the status in order for a record to be returned. Otherwise, if $1 be left out (i.e. is NULL), then all records would be returned.
Note that to make this work from Go with the Postgres driver, you may need to do some extra massaging. I would try, at a first attempt, this:
statusQuery = "where status = $1 or $1::text is null"
query := "select id, title, status from records " + statusQuery
rows, err := r.db.Query(query, &d.Status)
Sometimes the driver can't figure out the type of the bound parameter $1. By explicitly casting it to text, the statement can be made to work.

Append text to column using go with pq driver

I am trying to append text to a database column using golang and the pq driver.
The error I am getting is panic: pq: could not determine data type of parameter $2
sqlStatement := `
UPDATE sales
SET description = concat(description, $2)
WHERE id = $1;`
id:=1
desc := "appendthis"
res, err := db.Exec(sqlStatement, id, desc)
if err != nil {
panic(err)
}
I also tried
SET description = description || $2 which didn't panic, though the field did not get updated.
Any ideas what I am doing wrong?

Query with parameter with golang and sqlserver driver

I'm trying to get a specific item by ID from my sql server database. Here's my code :
var(
allArticlesQry string = "SELECT * FROM Articles"
findArticlesQry string = "SELECT * FROM Articles WHERE Id = ?1"
)
func FindArticle(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
var id = vars["id"]
var article Article
db := connect()
defer db.Close()
stmt, err := db.Prepare(findArticlesQry)
if err != nil{
log.Fatal(err)
}
defer stmt.Close()
err = stmt.QueryRow(id).Scan(&article.Title, &article.Description, &article.Body, &article.Id)
if err != nil{
log.Fatal(err)
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(u.HttpResp{Status: 200, Body: article})
}
I'm using this package for the sqlserver driver and it has this example who should work fine : db.Query("SELECT * FROM t WHERE a = ?3, b = ?2, c = ?1", "x", "y", "z")
But everytime I try to call this function it fails and returns :
2017/09/01 16:31:01 mssql: Incorrect syntax near '?'.
So I don't really understand why my query doesn't work..
EDIT
I tried another way, I removed the part where I prepare the query before executing and now it doesn't crash my server, I have a response but the problem is still the same :
var row = db.QueryRow(findArticlesQry, id).Scan(&article.Title, &article.Description, &article.Body, &article.Id)
And the response :
{
"status": 200,
"description": "",
"body": {
"Number": 102,
"State": 1,
"Class": 15,
"Message": "Incorrect syntax near '?'.",
"ServerName": "DESKTOP-DLROBC4\\LOCALHOST",
"ProcName": "",
"LineNo": 1
}
}
Per your comment, you are using the sqlserver driver, not the mssql driver, so you are using the wrong parameter format. Per the documentation:
The sqlserver driver uses normal MS SQL Server syntax and expects
parameters in the sql query to be in the form of either #Name or #p1
to #pN (ordinal position).
db.QueryContext(ctx, "select * from t where ID = #ID;", sql.Named("ID", 6))
You should therefore change your query to be:
var(
allArticlesQry string = "SELECT * FROM Articles"
findArticlesQry string = "SELECT * FROM Articles WHERE Id = #p1"
)
It seems to be a problem with the placeholders. Why not try just ? for your placeholders as you're not shifting the order or repeating them. So try this:
"SELECT * FROM Articles WHERE Id = ?"
db.Query("SELECT * FROM t WHERE a = ?3, b = ?2, c = ?1", "x", "y", "z"), the question mark here works as placeholder, which later will be replaced with the value of 'x', 'y' and 'z' according to its order in the code during runtime.
But this:
SELECT * FROM Articles WHERE Id = ?1 is not a validate SQL statement, correct is to remove the question mark, or you could give a specific value to #Id, like:
SELECT * FROM Articles WHERE Id = #Id
In a short word, db.Query() could build your query using placeholder, but your findArticlesQry variable is storing plain SQL statement, that should follow the basic SQL grammar, ? is not allowed.