You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLx无法识别AtP占位符:集成测试报sql参数不匹配错误

集成测试SQL参数错误排查

每次执行集成测试都会报错:

sql: expected 0 arguments, got 1

开发环境运行完全正常,推测问题出在@占位符的使用上,但数日未能解决。

使用的查询代码

query := sq.StatementBuilder.
    PlaceholderFormat(sq.AtP).
    Select(
        "id",
        "username",
        "hashed_password",
    ).
    From("users").
    Where(sq.Eq{
        "username": username,
    })

db := r.getQuerier(ctx)
sqlQuery, args, err := query.ToSql()
if err != nil {
    return entities.User{}, err
}

zlog.Debug().Msg(sqlQuery)
zlog.Debug().Any("args", args).Send()

var u user
err = db.GetContext(ctx, &u, sqlQuery, args...)
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        return entities.User{}, auth.ErrUserNotFound
    }

    return entities.User{}, err
}

日志输出

{"level":"debug","time":"2024-11-28T10:52:33+03:00","message":"SELECT id, username, hashed_password FROM users WHERE username = @p1"}
{"level":"debug","args":["testuser_with_refresh_token"],"time":"2024-11-28T10:52:33+03:00"}

Testcontainers配置

func CreateMSSQLServerContainer(t *testing.T) (string, func(), error) {
    containerReq := testcontainers.ContainerRequest{
        Image:        "mcr.microsoft.com/mssql/server",
        ExposedPorts: []string{"1433/tcp"},
        Env: map[string]string{
            "ACCEPT_EULA": "Y",
            "SA_PASSWORD": "YourStrong!Passw0rd",
            "MSSQL_PID":   "Developer",
        },
        WaitingFor: wait.ForSQL("1433/tcp", "sqlserver", func(host string, port nat.Port) string {
            return fmt.Sprintf("sqlserver://sa:YourStrong!Passw0rd@%s:%s?connection+timeout=30", host, port.Port())
        }).WithStartupTimeout(StartupTimeout),
    }

    _, host, ports, cleanup, err := createContainer(t, containerReq)
    if err != nil {
        return "", cleanup, err
    }

    sqlConnectionString := fmt.Sprintf("sqlserver://sa:YourStrong!Passw0rd@%s:%s?connection+timeout=30", host, ports["1433/tcp"][0].HostPort)

    return sqlConnectionString, cleanup, nil
}

解决方案

问题根源是MSSQL驱动对参数占位符的处理逻辑:你用squirrel生成的@p1属于命名占位符,但database/sql标准库配合MSSQL驱动时,无法直接识别这种占位符并绑定传入的参数,因此报错"预期0个参数,实际传入1个"。

有两种可靠的解决方式:

1. 切换为问号占位符

修改squirrel的占位符配置,使用标准库兼容的?占位符:

query := sq.StatementBuilder.
    PlaceholderFormat(sq.Question). // 替换为问号占位符
    Select("id", "username", "hashed_password").
    From("users").
    Where(sq.Eq{"username": username})

生成的SQL会变为WHERE username = ?,和db.GetContext()的参数传递逻辑完全兼容,测试环境即可正常运行。

2. 使用squirrel内置的数据库交互方法

避免手动调用ToSql(),直接用squirrel提供的RunWith方法绑定数据库连接,它会自动适配驱动的占位符规则:

var u user
err := query.RunWith(db).QueryRowContext(ctx).Scan(&u.id, &u.username, &u.hashed_password)
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        return entities.User{}, auth.ErrUserNotFound
    }
    return entities.User{}, err
}

这种方式无需手动处理占位符转换,兼容性更强,也能避免类似问题。

内容的提问来源于stack exchange,提问作者Alexandr Rutkowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:54:51