transact-rs/sqlx · error · io::Error (InvalidData)
VARBIT data contains other characters than 1 or 0.
Error message
VARBIT data contains other characters than 1 or 0.
What it means
sqlx's BitVec decoding for Postgres parses the VARBIT/BIT text representation as a string of '0' and '1' characters. When the server sends text-format data containing any other character, the decoder cannot map it to bits and fails with this io::ErrorKind::InvalidData error. It indicates corrupted or unexpected wire data rather than a caller mistake in most cases.
Source
Thrown at sqlx-postgres/src/types/bit_vec.rs:87
// Chop off zeroes from the back. We get bits in bytes, so if
// our bitvec is not in full bytes, extra zeroes are added to
// the end.
while bitvec.len() > len {
bitvec.pop();
}
Ok(bitvec)
}
PgValueFormat::Text => {
let s = value.as_str()?;
let mut bit_vec = BitVec::with_capacity(s.len());
for c in s.chars() {
match c {
'0' => bit_vec.push(false),
'1' => bit_vec.push(true),
_ => {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"VARBIT data contains other characters than 1 or 0.",
))?;
}
}
}
Ok(bit_vec)
}
}
}
}
View on GitHub (pinned to 03af8bcc57)
Solutions
- Use the binary protocol so BIT/VARBIT values are sent as raw bytes: keep default prepared statements, or cast explicitly to a text-safe type in SQL
- Check the actual column data with `SELECT col::text` for stray characters (prefixes, spaces) and clean the data
- Decode to String or Vec<u8> instead of BitVec if the column may hold non-canonical bit text, and parse it yourself
- Verify the column type is actually BIT/VARBIT, not TEXT that merely looks like bits
Example fix
// before: decoding a possibly hex-prefixed column directly into BitVec
let bits: bitvec::vec::BitVec = sqlx::query_scalar("SELECT flags FROM t").fetch_one(&pool).await?;
// after: normalize in SQL or decode as text and parse
let raw: String = sqlx::query_scalar("SELECT flags::text FROM t").fetch_one(&pool).await?;
let cleaned: String = raw.chars().filter(|c| *c == '0' || *c == '1').collect(); Defensive patterns
Strategy: validation
Validate before calling
// validate text-form bit data before decoding into BitVec
fn is_valid_bit_text(s: &str) -> bool {
!s.is_empty() && s.chars().all(|c| c == '0' || c == '1')
}
let raw: String = sqlx::query_scalar("SELECT flags::text FROM t").fetch_one(&pool).await?;
assert!(is_valid_bit_text(&raw), "unexpected BIT text: {raw:?}"); Type guard
fn as_bit_text(v: &str) -> Option<Vec<bool>> {
v.chars().map(|c| match c { '0' => Some(false), '1' => Some(true), _ => None }).collect()
} Try / catch
match result {
Ok(bits) => handle(bits),
Err(e) if e.to_string().contains("VARBIT data contains other characters") => {
// fall back to parsing raw text yourself
let raw: String = sqlx::query_scalar("SELECT flags::text FROM t").fetch_one(&pool).await?;
handle(parse_bits_manually(&raw)?);
}
Err(e) => return Err(e.into()),
} Prevention
- Prefer prepared queries so BIT columns arrive in binary format
- Keep BIT/VARBIT column data canonical (only 0/1, no prefixes or whitespace)
- Cast to ::text and inspect data when migrating legacy columns
When it happens
Trigger: Decoding a Postgres BIT or VARBIT column into sqlx's BitVec when the value arrives in PgValueFormat::Text and contains characters other than 0/1, e.g. binary-format-like escapes, a prefixed 'B'/'X' literal, or whitespace.
Common situations: Querying a bit column through a proxy or driver setting that forces text protocol; manually cast values like `bit 'X1F'` or hex/bit strings stored with prefixes; reading a column whose declared type differs from stored data after a migration.
Related errors
- VARBIT length mismatch.
- Reading a `MONEY` value in text format is not supported.
- Provided BigDecimal could not convert to i64: overflow.
- cleanup_test() invoked outside `#[sqlx::test]`
- DATABASE_URL must be set
AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03).
Data as JSON: /api/errors/7cfc8101e1ac19ac.
Report an issue: GitHub.