PlaceHolderFormat 在 SQL 使用 pgx 驱动程序用于 postgres 期间不会替换参数值的美元符号

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

我是 Go 新手,正在尝试根据 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 = ", creds.Username).
        PlaceholderFormat(sq.Dollar)

        // The line below doesn't substitute the $ sign, it shows this:  SELECT password FROM users WHERE username =  [rgfdgfd] <nil>
    fmt.Println(sq.
        Select("password").
        From("users").
        Where("username = ", 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 时看到以下内容,其中显示美元符号未被替换:

占位符的替换由 postgres 服务器完成,它不应该是 Go 代码或松鼠的工作来执行替换。

当您执行带参数的查询时,数据库驱动程序必须执行的操作的粗略概述如下所示:

  1. 使用查询字符串,占位符保持不变,一个 parse 请求被发送到 postgres 服务器以创建一个 准备好的语句
  2. 使用参数值和新创建语句的标识符,发送一个 bind 请求,通过创建一个 portal 使语句准备好执行。入口(类似于游标,但不相同)表示准备好执行或已经部分执行的语句,其中填充了任何缺少的参数值。
  3. 使用门户的标识符向服务器发送 execute 请求,然后服务器执行门户的查询。

请注意,以上步骤只是粗略的概述,实际上在数据库客户端和服务器之间涉及更多的请求-响应周期。

pgAdmin 而言,我相信它向您显示的是由 parse 请求创建的准备好的语句,尽管我无法确定不熟悉。


理论上,像squirrel这样的帮助库,或者像pgx这样的驱动库,可以自己实现参数替换,然后向服务器发送一个简单的查询。但是,一般来说,鉴于 SQL 注入的可能性,我认为最好将其留给 postgres 服务器的权限。


PlaceholderFormat 的工作是简单地将占位符转换为指定的格式。例如,您可以使用 MySQL 格式 (?,?,...) 编写 SQL,然后调用 PlaceholderFormat(sql.Dollar) 方法将其转换为 PostgreSQL 格式 (,,...).