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
相关产品推荐
相关产品推荐

