pgx库中的命名预处理语句是如何工作的?

9

介绍

database/sql

在 Go 标准 sql 库中,*Stmt 类型有一些方法的定义,例如:

func (s *Stmt) Exec(args ...interface{}) (Result, error)
func (s *Stmt) Query(args ...interface{}) (*Rows, error)

一个新的(未命名的)声明由以下人员准备:

func (db *DB) Prepare(query string) (*Stmt, error)
  • 连接池是抽象的,不直接可访问。
  • 事务在单个连接上准备。
  • 如果连接在语句执行时不可用,则会在新连接上重新准备。

pgx

PreparedStatement 类型没有定义任何方法。可以通过以下方式准备新的命名准备好的语句:

func (p *ConnPool) Prepare(name, sql string) (*PreparedStatement, error)
  1. 操作直接在连接池上进行
  2. 事务在池中的所有连接上准备
  3. 没有明确的方法来执行准备好的语句

Github评论中,作者更好地解释了pgx和database/sql之间的架构差异。在Prepare文档中也指出(我强调):

Prepare是幂等的;也就是说可以安全地使用相同的名称和sql参数多次调用Prepare。这允许一个代码路径准备、查询/执行/PrepareEx而不必考虑该语句是否已经准备好。

小例子

package main

import (
    "github.com/jackc/pgx"
)

func main() {
    conf := pgx.ConnPoolConfig{
        ConnConfig: pgx.ConnConfig{
            Host:     "/run/postgresql",
            User:     "postgres",
            Database: "test",
        },
        MaxConnections: 5,
    }
    db, err := pgx.NewConnPool(conf)
    if err != nil {
        panic(err)
    }
    _, err = db.Prepare("my-query", "select $1")
    if err != nil {
        panic(err)
    }
    // What to do with the prepared statement?
}

问题

  1. name参数给我留下了它可以通过调用name来执行的印象,但是如何做到呢?
  2. 文档让人感觉Query/Exec方法在某种程度上利用了准备好的语句。然而,这些方法不接受name参数。那么它们是如何匹配的?
  3. 据推测,匹配是根据查询内容进行的。那么命名语句的整个意义是什么?

可能的答案

这是我自己得出的结论:

  1. 没有任何方法引用名称来引用查询(猜想)
  2. conn.ExecEx()中,匹配是根据查询主体完成的。如果尚未准备好,则会准备好。
ps, ok := c.preparedStatements[sql]
            if !ok {
                var err error
                ps, err = c.prepareEx("", sql, nil)
                if err != nil {
                    return "", err
                }
            }

3
  1. 这里可以看出,你可以将名称作为“sql”参数传递到“Query/QueryRow/Exec”方法中进行查询。
  2. 这表明你可以将名称作为sql参数传递给Query/QueryRow/Exec方法。这个链接这个链接证实了这一点,并且sendPreparedQuery 方法会在名称前附加 EXECUTE
- mkopriva
1
不要被参数名称所困惑,当您传递准备好的语句的名称时,sql 将保存准备好的语句的名称,这时您就可以在第2步链接的行上获得匹配。 - mkopriva
1个回答

6
@mkopriva 指出 sql 的文本误导了我。它在这里有一个双重功能。如果 sql 变量与 c.preparedStatements[sql] 映射中的任何一个键不匹配,则包含在 sql 中的查询将被准备,并且新的 *PreparedStatement 结构体将被指定给 ps。如果它确实匹配了一个键,ps 变量将指向映射表的一个条目。

因此您可以有效地执行以下操作:

package main

import (
    "fmt"

    "github.com/jackc/pgx"
)

func main() {
    conf := pgx.ConnPoolConfig{
        ConnConfig: pgx.ConnConfig{
            Host:     "/run/postgresql",
            User:     "postgres",
            Database: "test",
        },
        MaxConnections: 5,
    }
    db, err := pgx.NewConnPool(conf)
    if err != nil {
        panic(err)
    }
    if _, err := db.Prepare("my-query", "select $1::int"); err != nil {
        panic(err)
    }
    row := db.QueryRow("my-query", 10)
    var i int
    if err := row.Scan(&i); err != nil {
        panic(err)
    }
    fmt.Println(i)
}

1
Prepare()不是池方法,而是连接方法。因此,您需要从池中选择一个连接,然后对其进行准备。 - Nulik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接