windmill-labs/windmill · error

26000

26000

Error message

prepared statement ${formatProtocolName(bind.statementName, "statement")} does not exist

What it means

In `wmill datatable serve`, a Bind (B) message referenced a statement name that is not present in the connection's `preparedStatements` map, so `handleBindMessage` throws SQLSTATE `26000` (invalid_sql_statement_name). Per the Postgres protocol, a Bind must target a statement created earlier by a Parse on the same connection.

Source

Thrown at cli/src/commands/datatable/serve.ts:441

  preparedStatements.set(parse.statementName, {
    query: parse.query,
    parameterTypeOids: parse.parameterTypeOids,
  });
  if (parse.statementName === "") {
    portals.delete("");
  }
  return buildParseComplete();
}

function handleBindMessage(
  data: Uint8Array,
  preparedStatements: Map<string, PreparedStatementState>,
  portals: Map<string, PortalState>,
): Uint8Array {
  const bind = readBindMessage(data);
  const statement = preparedStatements.get(bind.statementName);
  if (!statement) {
    throw createPgProtocolError(
      `prepared statement ${formatProtocolName(bind.statementName, "statement")} does not exist`,
      "26000",
    );
  }
  for (let i = 0; i < bind.parameters.length; i += 1) {
    if (bind.parameters[i] !== null && resolveParameterFormatCode(bind.parameterFormatCodes, i) !== 0) {
      throw createPgProtocolError(
        "binary parameter formats are not supported by 'wmill datatable serve'",
        "0A000",
      );
    }
  }
  if (bind.resultFormatCodes.some((code) => code !== 0)) {
    throw createPgProtocolError(
      "binary result formats are not supported by 'wmill datatable serve'",
      "0A000",
    );
  }

View on GitHub (pinned to e474e8803c)

Solutions

  1. Reconnect or reset the connection so the driver's prepared-statement cache and the server state resynchronize (the driver will re-Parse on a fresh session).
  2. Disable prepared statements in the client driver if supported (e.g. `prepared_statements=false` in the Postgres DSN, or `preferPreparedStatements: false`) so Binds always reference the unnamed statement.
  3. Upgrade `wmill` / the datatable server — a bug where statements were dropped prematurely would be fixed in a newer release.
  4. In application code, catch SQLSTATE 26000 and retry the query after re-preparing, the standard Postgres recovery for invalid_sql_statement_name.

Example fix

// before
postgres://user:pw@host/db  // pool reuses cached statement names across connections
// after
postgres://user:pw@host/db?prepared_statements=false
Defensive patterns

Strategy: retry

Try / catch

async function safeQuery(client, sql, params) {
  try {
    return await client.query(sql, params);
  } catch (err) {
    if (err.code === '26000') {
      return await client.query({ text: sql, values: params, name: undefined }); // re-issue unnamed
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: The client sends Bind for a named statement that was never Parsed, was already closed via a Close (C) message, or belonged to a different connection/session; also happens after a server error discarded prepared statements while the driver's local cache still thinks they exist.

Common situations: Connection pooling/multiplexing where the driver reuses a statement name across recycled connections; a prior error mid-transaction cleared server-side state but the client kept its prepared-statement cache; driver retry logic replaying Bind without re-Parsing after a disconnect.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/013086162ee2e556. Report an issue: GitHub.