使用pgx和go返回简单的PostgreSQL查询中的id

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

Returning id in simple postgresql query using pgx and go

问题

我正在尝试运行这个简单的查询来返回id,根据作者的说法,我们使用QueryRow函数,但是这个查询结果返回了一个期望2个参数但是接收到3个参数的错误。

querymodel.go

type WidgetetModel struct {
	DB *pgxpool.Pool
}

func (m *WidgetModel) Insert(title, content, expires string) (int, error) {
	stmt := `INSERT INTO widgets (title, content, created, expires) VALUES($1, $2, NOW(), NOW() + INTERVAL '$3 day') RETURNING id;`

	var id int
	err := m.DB.QueryRow(context.Background(), stmt, title, content, expires).Scan(&id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return 0, models.ErrNoRecord
		} else {
			return 0, err
		}
	}

	return 0, nil
}

handlers.go

func (app *application) createWidget(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.Header().Set("Allow", http.MethodPost)
		app.clientError(w, http.StatusMethodNotAllowed) // 使用clientError()辅助函数。
		return
	}

	title := "Widget 1"
	content := "Some content here..."
	expires := "7"

	id, err := app.widgets.Insert(title, content, expires)
	if err != nil {
		app.serverError(w, err) <-- 第57行
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/widget?id=%v", id), http.StatusSeeOther)
}

main.go

我只是在这里使用一个结构体来注入处理程序的依赖项。

dbPool, err := openDB(*dsn)
if err != nil {
	errorLog.Fatal(err)
}
defer dbPool.Close()

app := &application{
	errorLog: errorLog,
	infoLog:  infoLog,
	snippets: &postgresql.WidgetModel{DB: dbPool},
}
func openDB(dsn string) (*pgxpool.Pool, error) {
	pool, err := pgxpool.Connect(context.Background(), dsn)
	if err != nil {
		return nil, err
	}
	if err = pool.Ping(context.Background()); err != nil {
		return nil, err
	}
	return pool, nil
}

错误输出

go run ./cmd/web                                      
INFO    2022/02/20 17:19:30 Starting server on :4000
ERROR   2022/02/20 17:19:38 handlers.go:57: expected 2 arguments, got 3
goroutine 34 [running]:
runtime/debug.Stack()
        /usr/local/go/src/runtime/debug/stack.go:24 +0x88
main.(*application).serverError(0x1400008e000, {0x1005c7be8, 0x1400009a1c0}, {0x1005bf2c0, 0x14000094120})
        /Users/spencerlong/Desktop/Golang/Snippetts/cmd/web/helpers.go:12 +0x44
main.(*application).createSnippet(0x1400008e000, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /Users/spencerlong/Desktop/Golang/Snippetts/cmd/web/handlers.go:57 +0x200
net/http.HandlerFunc.ServeHTTP(0x14000094020, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2046 +0x40
net/http.(*ServeMux).ServeHTTP(0x14000092000, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2424 +0x18c
net/http.serverHandler.ServeHTTP({0x1400009a000}, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2878 +0x444
net/http.(*conn).serve(0x140000b0000, {0x1005ca280, 0x14000096180})
        /usr/local/go/src/net/http/server.go:1929 +0xb6c
created by net/http.(*Server).Serve
        /usr/local/go/src/net/http/server.go:3033 +0x4b8

helpers.go

func (app *application) serverError(w http.ResponseWriter, err error) {
	trace := fmt.Sprintf("%s\n%s", err.Error(), debug.Stack())
	app.errorLog.Output(2, trace)

	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
英文:

I'm trying to run this simple query to return the id, according to the author we use the QueryRow function, ok, but this query result returns a expects 2 args but received 3 error somewhere in here.

querymodel.go

 type WidgetetModel struct {
	DB *pgxpool.Pool
}

func (m *WidgetModel) Insert(title, content, expires string) (int, error) {
    	stmt := `INSERT INTO widgets (title, content, created, expires) VALUES($1, $2, NOW(), NOW() + INTERVAL &#39;$3 day&#39;) RETURNING id;`
    
    	var id int
    	err := m.DB.QueryRow(context.Background(), stmt, title, content, expires).Scan(&amp;id)
    	if err != nil {
    		if errors.Is(err, sql.ErrNoRows) {
    			return 0, models.ErrNoRecord
    		} else {
    			return 0, err
    		}
    	}
    
    	return 0, nil
    }

handlers.go

func (app *application) createWidget(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.Header().Set(&quot;Allow&quot;, http.MethodPost)
		app.clientError(w, http.StatusMethodNotAllowed) // Use the clientError() helper.
		return
	}

	title := &quot;Widget 1&quot;
	content := &quot;Some content here...&quot;
	expires := &quot;7&quot;

	id, err := app.widgets.Insert(title, content, expires)
	if err != nil {
		app.serverError(w, err) &lt;-- line 57
		return
	}

	http.Redirect(w, r, fmt.Sprintf(&quot;/widget?id=%v&quot;, id), http.StatusSeeOther)
}

main.go

I'm just using a struct to inject dependencies for my handlers here.

dbPool, err := openDB(*dsn)
	if err != nil {
		errorLog.Fatal(err)
	}
	defer dbPool.Close()

app := &amp;application{
		errorLog: errorLog,
		infoLog:  infoLog,
		snippets: &amp;postgresql.WidgetModel{DB: dbPool},
}

///

func openDB(dsn string) (*pgxpool.Pool, error) {
	pool, err := pgxpool.Connect(context.Background(), dsn)
	if err != nil {
		return nil, err
	}
	if err = pool.Ping(context.Background()); err != nil {
		return nil, err
	}
	return pool, nil
}

error output

go run ./cmd/web                                      
INFO    2022/02/20 17:19:30 Starting server on :4000
ERROR   2022/02/20 17:19:38 handlers.go:57: expected 2 arguments, got 3
goroutine 34 [running]:
runtime/debug.Stack()
        /usr/local/go/src/runtime/debug/stack.go:24 +0x88
main.(*application).serverError(0x1400008e000, {0x1005c7be8, 0x1400009a1c0}, {0x1005bf2c0, 0x14000094120})
        /Users/spencerlong/Desktop/Golang/Snippetts/cmd/web/helpers.go:12 +0x44
main.(*application).createSnippet(0x1400008e000, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /Users/spencerlong/Desktop/Golang/Snippetts/cmd/web/handlers.go:57 +0x200
net/http.HandlerFunc.ServeHTTP(0x14000094020, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2046 +0x40
net/http.(*ServeMux).ServeHTTP(0x14000092000, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2424 +0x18c
net/http.serverHandler.ServeHTTP({0x1400009a000}, {0x1005c7be8, 0x1400009a1c0}, 0x140000c8000)
        /usr/local/go/src/net/http/server.go:2878 +0x444
net/http.(*conn).serve(0x140000b0000, {0x1005ca280, 0x14000096180})
        /usr/local/go/src/net/http/server.go:1929 +0xb6c
created by net/http.(*Server).Serve
        /usr/local/go/src/net/http/server.go:3033 +0x4b8

helpers.go

func (app *application) serverError(w http.ResponseWriter, err error) {
	trace := fmt.Sprintf(&quot;%s\n%s&quot;, err.Error(), debug.Stack())
	app.errorLog.Output(2, trace)

	http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

答案1

得分: 3

根据你收到的错误信息:

期望2个参数,但给出了3个

我怀疑你查询中的$3在SQL引号内没有被解释为参数。而且据我所知,这不是在Postgres中使用参数化间隔的正确方式。正确的方式应该是写成interval '1 day' * $1

所以我认为如果你将代码改为:

stmt := `INSERT INTO widgets (title, content, created, expires) VALUES($1, $2, NOW(), NOW() + INTERVAL '1 day' * $3) RETURNING id;`

它应该可以工作。但请确保将expires参数的类型更改为int。

英文:

From the error you are receiving:

expected 2 arguments, got 3

I suspect that $3 in your query which is inside a SQL quote is not interpreted as a parameter. Also to my knowledge, this is not a correct way of using parametric intervals in postgres. The correct way would be to write interval &#39;1 day&#39; * $1

So I think if you change your code to:

stmt := `INSERT INTO widgets (title, content, created, expires) VALUES($1, $2, NOW(), NOW() + INTERVAL &#39;1 day&#39; * $3) RETURNING id;`

It will work. But make sure to change your expires parameter to type int.

答案2

得分: 2

&#39;$3 day&#39; 无法作为字符串字面量的一部分传递查询参数。

尝试将所有 INTERVAL &#39;$3 day&#39; 部分替换为参数。像这样:

func (m *WidgetModel) Insert(title, content, expires string) (int, error) {
	stmt := `
INSERT INTO widgets (title, content, created, expires)
VALUES ($1, $2, NOW(), NOW() + $3)
RETURNING id;`

	var id int
	expiresDays, err := strconv.ParseInt(expires, 10, 32)
	if err != nil {
		return 0, err
	}
	expiresInterval := pgtype.Interval{
		Days:   int32(expiresDays),
		Status: pgtype.Present}
	err = m.DB.QueryRow(context.Background(), stmt, title, content,
		expiresInterval).Scan(&id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return 0, models.ErrNoRecord
		} else {
			return 0, err
		}
	}

	return 0, nil
}
英文:

&#39;$3 day&#39;
You cannot pass query parameter as part of string literal.

Try to replace all INTERVAL &#39;$3 day&#39; part as a parameter. Like this one.

func (m *WidgetModel) Insert(title, content, expires string) (int, error) {
	stmt := `
INSERT INTO widgets (title, content, created, expires)
VALUES ($1, $2, NOW(), NOW() + $3)
RETURNING id;`

	var id int
	expiresDays, err := strconv.ParseInt(expires, 10, 32)
	if err != nil {
		return 0, err
	}
	expiresInterval := pgtype.Interval{
		Days:   int32(expiresDays),
		Status: pgtype.Present}
	err = m.DB.QueryRow(context.Background(), stmt, title, content,
		expiresInterval).Scan(&amp;id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return 0, models.ErrNoRecord
		} else {
			return 0, err
		}
	}

	return 0, nil
}

huangapple
  • 本文由 发表于 2022年2月20日 12:06:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/71191299.html
匿名

发表评论

匿名网友

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

确定