PlaceHolderFormat doesn't replace the dollar sign for the parameter value during SQL using pgx driver for postgres

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

PlaceHolderFormat doesn't replace the dollar sign for the parameter value during SQL using pgx driver for postgres

问题

我是新手,正在尝试在postgresql数据库中检查密码与用户名是否匹配。

我无法实现美元符号替换,而且不想使用字符串连接。

我目前正在使用squirrel,但也尝试过不使用它,但没有太多运气。

我有以下代码:

package datalayer

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"

	sq "github.com/Masterminds/squirrel"
	_ "github.com/jackc/pgx/v4/stdlib"
	"golang.org/x/crypto/bcrypt"

	"github.com/gin-gonic/gin"
)

var (
	// for the database
	db *sql.DB
)

func InitDB(sqlDriver string, dataSource string) error {
	var err error

	// Connect to the postgres db  (sqlDriver is literal string "pgx")
	db, err = sql.Open(sqlDriver, dataSource)

	if err != nil {
		panic(err)
	}
	return db.Ping()
}

// Create a struct that models the structure of a user, both in the request body, and in the DB
type Credentials struct {
	Password string `json:"password", db:"password"`
	Username string `json:"username", db:"username"`
}

func Signin(c *gin.Context) {
	// Parse and decode the request body into a new `Credentials` instance
	creds := &Credentials{}
	err := json.NewDecoder(c.Request.Body).Decode(creds)


	if err != nil {
		// If there is something wrong with the request body, return a 400 status
		c.Writer.WriteHeader(http.StatusBadRequest)
		return
	}
	query := sq.
		Select("password").
		From("users").
		Where("username = $1", creds.Username).
		PlaceholderFormat(sq.Dollar)

		// The line below doesn't substitute the $ sign, it shows this:  SELECT password FROM users WHERE username = $1 [rgfdgfd] <nil>
	fmt.Println(sq.
		Select("password").
		From("users").
		Where("username = $1", creds.Username).
		PlaceholderFormat(sq.Dollar).ToSql())

	rows, sqlerr := query.RunWith(db).Query()
	if sqlerr != nil {
		panic(fmt.Sprintf("QueryRow failed: %v", sqlerr))
	}

	if err != nil {
		// If there is an issue with the database, return a 500 error
		c.Writer.WriteHeader(http.StatusInternalServerError)
		return
	}
	// We create another instance of `Credentials` to store the credentials we get from the database
	storedCreds := &Credentials{}
	// Store the obtained password in `storedCreds`
	err = rows.Scan(&storedCreds.Password)
	if err != nil {
		// If an entry with the username does not exist, send an "Unauthorized"(401) status
		if err == sql.ErrNoRows {
			c.Writer.WriteHeader(http.StatusUnauthorized)
			return
		}
		// If the error is of any other type, send a 500 status
		c.Writer.WriteHeader(http.StatusInternalServerError)
		return
	}

	// Compare the stored hashed password, with the hashed version of the password that was received
	if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(creds.Password)); err != nil {
		// If the two passwords don't match, return a 401 status
		c.Writer.WriteHeader(http.StatusUnauthorized)
	}
	fmt.Printf("We made it !")
	// If we reach this point, that means the users password was correct, and that they are authorized
	// The default 200 status is sent
}

我在检查pgAdmin时看到以下内容,显示美元符号没有被替换:PlaceHolderFormat doesn't replace the dollar sign for the parameter value during SQL using pgx driver for postgres

英文:

I am new to Go and am trying to check a password against a username in a postgresql database.

I can't get dollar substitution to occur and would rather not resort to concatenating strings.

I am currently using squirrel but also tried it without and didn't have much luck.

I have the following code:

    package datalayer
import (
&quot;database/sql&quot;
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;net/http&quot;
sq &quot;github.com/Masterminds/squirrel&quot;
_ &quot;github.com/jackc/pgx/v4/stdlib&quot;
&quot;golang.org/x/crypto/bcrypt&quot;
&quot;github.com/gin-gonic/gin&quot;
)
var (
// for the database
db *sql.DB
)
func InitDB(sqlDriver string, dataSource string) error {
var err error
// Connect to the postgres db  (sqlDriver is literal string &quot;pgx&quot;)
db, err = sql.Open(sqlDriver, dataSource)
if err != nil {
panic(err)
}
return db.Ping()
}
// Create a struct that models the structure of a user, both in the request body, and in the DB
type Credentials struct {
Password string `json:&quot;password&quot;, db:&quot;password&quot;`
Username string `json:&quot;username&quot;, db:&quot;username&quot;`
}
func Signin(c *gin.Context) {
// Parse and decode the request body into a new `Credentials` instance
creds := &amp;Credentials{}
err := json.NewDecoder(c.Request.Body).Decode(creds)
if err != nil {
// If there is something wrong with the request body, return a 400 status
c.Writer.WriteHeader(http.StatusBadRequest)
return
}
query := sq.
Select(&quot;password&quot;).
From(&quot;users&quot;).
Where(&quot;username = $1&quot;, creds.Username).
PlaceholderFormat(sq.Dollar)
// The line below doesn&#39;t substitute the $ sign, it shows this:  SELECT password FROM users WHERE username = $1 [rgfdgfd] &lt;nil&gt;
fmt.Println(sq.
Select(&quot;password&quot;).
From(&quot;users&quot;).
Where(&quot;username = $1&quot;, creds.Username).
PlaceholderFormat(sq.Dollar).ToSql())
rows, sqlerr := query.RunWith(db).Query()
if sqlerr != nil {
panic(fmt.Sprintf(&quot;QueryRow failed: %v&quot;, sqlerr))
}
if err != nil {
// If there is an issue with the database, return a 500 error
c.Writer.WriteHeader(http.StatusInternalServerError)
return
}
// We create another instance of `Credentials` to store the credentials we get from the database
storedCreds := &amp;Credentials{}
// Store the obtained password in `storedCreds`
err = rows.Scan(&amp;storedCreds.Password)
if err != nil {
// If an entry with the username does not exist, send an &quot;Unauthorized&quot;(401) status
if err == sql.ErrNoRows {
c.Writer.WriteHeader(http.StatusUnauthorized)
return
}
// If the error is of any other type, send a 500 status
c.Writer.WriteHeader(http.StatusInternalServerError)
return
}
// Compare the stored hashed password, with the hashed version of the password that was received
if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(creds.Password)); err != nil {
// If the two passwords don&#39;t match, return a 401 status
c.Writer.WriteHeader(http.StatusUnauthorized)
}
fmt.Printf(&quot;We made it !&quot;)
// If we reach this point, that means the users password was correct, and that they are authorized
// The default 200 status is sent
}

I see the following when I check pgAdmin, which shows the dollar sign not being substituted:

PlaceHolderFormat doesn't replace the dollar sign for the parameter value during SQL using pgx driver for postgres

答案1

得分: 3

占位符的替换是由PostgreSQL服务器完成的,不应该由Go代码或squirrel来完成替换。

当执行带有参数的查询时,数据库驱动程序大致需要执行以下操作:

  1. 使用未更改的占位符的查询字符串,向PostgreSQL服务器发送一个parse请求,以创建一个prepared statement(预处理语句)。
  2. 使用参数值和新创建语句的标识符,发送一个bind请求,通过创建一个portal(类似于但不同于游标)来准备执行该语句。一个portal表示一个准备好执行或已部分执行的语句,其中填充了任何缺少的参数值。
  3. 使用portal的标识符,发送一个execute请求给服务器,然后服务器执行portal的查询。

请注意,上述步骤只是一个大致的概述,在实际情况中,数据库客户端和服务器之间涉及更多的请求-响应周期。

至于pgAdmin,我认为它显示给你的是由parse请求创建的预处理语句,尽管我不能确定,因为我对它不熟悉。


理论上,像squirrel这样的辅助库,或者像pgx这样的驱动库,可以自己实现参数的替换,然后向服务器发送一个简单的查询。然而,考虑到SQL注入的可能性,我认为最好将其留给PostgreSQL服务器来处理。


PlaceholderFormat的作用只是将占位符转换为指定的格式。例如,你可以使用MySQL格式的SQL语句(?,?,...),然后调用PlaceholderFormat(sql.Dollar)方法将其转换为PostgreSQL格式的($1,$2,...)

英文:

The substitution of the placeholders is done by the postgres server, it SHOULD NOT be the job of the Go code, or squirrel, to do the substitution.

When you are executing a query that takes parameters, a rough outline of what the database driver has to do is something like the following:

  1. Using the query string, with placeholders untouched, a parse request is sent to the postgres server to create a prepared statement.
  2. Using the parameter values and the identifier of the newly-created statement, a bind request is sent to make the statement ready for execution by creating a portal. A portal (similar to, but not the same as, a cursor) represents a ready-to-execute or already-partially-executed statement, with any missing parameter values filled in.
  3. Using the portal's identifier an execute request is sent to the server which then executes the portal's query.

Note that the above steps are just a rough outline, in reality there are more request-response cycles involved between the db client and server.

And as far as pgAdmin is concerned I believe what it is displaying to you is the prepared statement as created by the parse request, although I can't tell for sure as I am not familiar with it.


In theory, a helper library like squirrel, or a driver library like pgx, could implement the substitution of parameters themselves and then send a simple query to the server. In general, however, given the possibility of SQL injections, it is better to leave it to the authority of the postgres server, in my opinion.


The PlaceholderFormat's job is to simply translate the placeholder to the specified format. For example you could write your SQL using the MySQL format (?,?,...) and then invoke the PlaceholderFormat(sql.Dollar) method to translate that into the PostgreSQL format ($1,$2,...).

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

发表评论

匿名网友

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

确定