jackc/pgx · error
too many parameters
Error message
too many parameters
What it means
Returned by Bind.Encode in pgproto3/bind.go:130 when len(src.Parameters) exceeds 65535 (math.MaxUint16). The wire protocol encodes the parameter count as a uint16, so a single Bind cannot carry more than 65535 parameters. Fired client-side during Encode, before the message is sent.
Source
Thrown at pgproto3/bind.go:130
// Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
func (src *Bind) Encode(dst []byte) ([]byte, error) {
dst, sp := beginMessage(dst, 'B')
dst = append(dst, src.DestinationPortal...)
dst = append(dst, 0)
dst = append(dst, src.PreparedStatement...)
dst = append(dst, 0)
if len(src.ParameterFormatCodes) > math.MaxUint16 {
return nil, errors.New("too many parameter format codes")
}
dst = pgio.AppendUint16(dst, uint16(len(src.ParameterFormatCodes)))
for _, fc := range src.ParameterFormatCodes {
dst = pgio.AppendInt16(dst, fc)
}
if len(src.Parameters) > math.MaxUint16 {
return nil, errors.New("too many parameters")
}
dst = pgio.AppendUint16(dst, uint16(len(src.Parameters)))
for _, p := range src.Parameters {
if p == nil {
dst = pgio.AppendInt32(dst, -1)
continue
}
dst = pgio.AppendInt32(dst, int32(len(p)))
dst = append(dst, p...)
}
if len(src.ResultFormatCodes) > math.MaxUint16 {
return nil, errors.New("too many result format codes")
}
dst = pgio.AppendUint16(dst, uint16(len(src.ResultFormatCodes)))
for _, fc := range src.ResultFormatCodes {
dst = pgio.AppendInt16(dst, fc)View on GitHub (pinned to ec1a0befd2)
Solutions
- Split the statement so each execution binds <=65535 parameters.
- Use pgx CopyFrom / PostgreSQL COPY for bulk inserts.
- For large IN-lists, pass values via a temp table, array parameter (ANY(unnest($1::int[]))), or jsonb instead of thousands of placeholders.
- Add a unit test asserting placeholder count stays under the limit.
Example fix
// before: 100000 placeholders in one statement
placeholders := make([]string, 100000)
for i := range placeholders { placeholders[i] = fmt.Sprintf("$%d", i+1) }
q := "SELECT * FROM t WHERE id IN (" + strings.Join(placeholders, ",") + ")"
rows, _ := conn.Exec(ctx, q, args...) // -> "too many parameters"
// after: use an array parameter to avoid the placeholder explosion
rows, _ := conn.Query(ctx, "SELECT * FROM t WHERE id = ANY($1::int[])", ids) Defensive patterns
Strategy: validation
Validate before calling
const maxBindParams = 65535 // math.MaxUint16
if len(args) > maxBindParams {
return fmt.Errorf("query has %d bind parameters, protocol max is %d; split the batch or use COPY/ANY($1::array)", len(args), maxBindParams)
} Type guard
func bindParamCountOK(params [][]byte) bool { return len(params) <= 65535 } Try / catch
if _, err := bindMsg.Encode(dst); err != nil {
if strings.Contains(err.Error(), "too many parameters") {
// re-issue in chunks of <=65535 params, or switch to COPY / array param
}
} Prevention
- Keep placeholder counts under 65535 per execution; chunk large IN-lists and multi-row inserts.
- Use pgx CopyFrom for bulk inserts instead of huge parameterized INSERTs.
- Replace big IN-lists with ANY($1::type[]) or a temp table.
- Unit-test dynamic SQL builders to assert the placeholder ceiling.
When it happens
Trigger: Executing a parameterized query or prepared statement with more than 65535 bind parameters (e.g. INSERT ... VALUES with a huge generated placeholder list, or a WHERE col IN (?, ?, ...) with too many placeholders).
Common situations: Dynamically building an IN-list or multi-row INSERT that exceeds 65535 placeholders; bulk-loading rows via parameterized INSERTs instead of COPY; concatenating user input into placeholder lists without a limit.
Related errors
- too many parameter format codes
- too many result format codes
- bad authentication message size
- bad auth type
- authentication message too short
AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04).
Data as JSON: /data/errors/de441029213186d3.json.
Report an issue: GitHub.