prestodb/presto · error · IllegalArgumentException
Invalid row block
Error message
Invalid row block
What it means
When converting a RunLengthEncodedBlock containing a single row value into a columnar row, a NULL row must have every null-suppressed field block empty (0 positions). If any field of a null RLE row has a non-zero position count, the block is structurally inconsistent and IllegalArgumentException("Invalid row block") is thrown. This indicates corrupted or incorrectly constructed nested blocks.
Source
Thrown at presto-common/src/main/java/com/facebook/presto/common/block/ColumnarRow.java:116
// 3) the estimated serialized size for the fields Blocks which were just constructed as new DictionaryBlocks:
// the average row size: averageRowSize * the number of rows: nonNullPositionCount
(Integer.BYTES + Byte.BYTES) * positionCount + averageRowSize * nonNullPositionCount);
}
private static ColumnarRow toColumnarRow(RunLengthEncodedBlock rleBlock)
{
Block rleValue = rleBlock.getValue();
int positionCount = rleBlock.getPositionCount();
ColumnarRow columnarRow = toColumnarRow(rleValue);
Block[] fields = new Block[columnarRow.getFieldCount()];
long averageRowSize = 0;
for (int i = 0; i < columnarRow.getFieldCount(); i++) {
Block nullSuppressedField = columnarRow.getField(i);
if (rleValue.isNull(0)) {
// the rle value is a null row so, all null-suppressed fields should empty
if (nullSuppressedField.getPositionCount() != 0) {
throw new IllegalArgumentException("Invalid row block");
}
fields[i] = nullSuppressedField;
}
else {
fields[i] = new RunLengthEncodedBlock(nullSuppressedField, positionCount);
averageRowSize += nullSuppressedField.getSizeInBytes() / nullSuppressedField.getPositionCount();
}
}
return new ColumnarRow(
rleBlock,
fields,
INSTANCE_SIZE + rleBlock.getRetainedSizeInBytes(),
// The estimated serialized size is the sum of the following:
// 1) the offsets size: Integer.BYTES * positionCount. Note that even though ColumnarRow doesn't have the offsets array, the serialized RowBlock still has it. Please see RowBlockEncodingBuffer.
// 2) nulls array size: Byte.BYTES * positionCount
// 3) the estimated serialized size for the fields Blocks which were just constructed as new RunLengthEncodedBlocks:
// the average row size: averageRowSize * the number of rows: positionCount
(Integer.BYTES + Byte.BYTES) * positionCount + averageRowSize * positionCount);View on GitHub (pinned to 55bb57d202)
Solutions
- Fix the producer so a NULL row's RLE block wraps field blocks with positionCount 0
- Rebuild the RLE block using RunLengthEncodedBlock(valueBlock, positionCount) from a properly formed row block instead of hand-assembling fields
- Validate the source data/page upstream to catch malformed blocks before conversion
- If the RLE value is non-null, ensure each field block has exactly 1 position (the loop divides by positionCount later)
- Report/inspect the connector or codec that emitted the block, since this is data corruption, not user input
Example fix
// before — building RLE row block from a null row with leftover fields
Block[] nonEmptyFields = existingFields; // fields still hold positions
RunLengthEncodedBlock rle = new RunLengthEncodedBlock(new RowBlockBuilder(...).build(), 1);
// after — empty field blocks for a null row
RowBlockBuilder builder = new RowBlockBuilder(fieldTypes, null, 1);
builder.buildEntry(b -> { for (Type t : fieldTypes) { b.appendNull(); } });
RunLengthEncodedBlock rle = new RunLengthEncodedBlock(builder.build(), positionCount); Defensive patterns
Strategy: validation
Validate before calling
static void validateRleRowBlock(RunLengthEncodedBlock rleValue, ColumnarRow columnarRow) {
for (int i = 0; i < columnarRow.getFieldCount(); i++) {
Block field = columnarRow.getField(i);
if (rleValue.isNull(0) && field.getPositionCount() != 0) {
throw new IllegalArgumentException("Field " + i + " of null RLE row must be empty");
}
}
} Try / catch
try {
ColumnarRow row = ColumnarRow.toColumnarRow(rleBlock);
} catch (IllegalArgumentException e) {
if (e.getMessage().equals("Invalid row block")) {
throw new corruptPageException("Malformed RLE row block: null row with non-empty fields");
}
throw e;
} Prevention
- Never hand-assemble RLE row blocks; build row values with RowBlockBuilder
- For null rows, emit empty field blocks (0 positions)
- Validate writer output against reader expectations in connector tests
- Treat this exception as data corruption — verify the upstream producer, not the caller
When it happens
Trigger: Calling ColumnarRow.toColumnarRow / columnarRow on a RunLengthEncodedBlock whose single value is a NULL row but whose internal field blocks contain a non-zero number of positions — i.e. a malformed RLE-wrapped row block produced by a writer or deserializer.
Common situations: Custom serializers building RLE row blocks with mismatched field lengths; corrupted intermediate data from a connector; bugs in code that wraps a null row into RunLengthEncodedBlock without emptying field blocks.
Related errors
- Invalid row block:
- New column does not have same number of rows as old column
- Declared positions (%s) does not match block %s's number of
- Offset is not monotonically ascending. offsets[%s]=%s, offse
- A null map must have zero entries
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/12ee6528c2279da0.
Report an issue: GitHub.