{"record":{"id":"1cfd48b3dad6b064","repo":"prestodb/presto","slug":"a-null-map-must-have-zero-entries","errorCode":null,"errorMessage":"A null map must have zero entries","messagePattern":"A null map must have zero entries","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"presto-common/src/main/java/com/facebook/presto/common/block/AbstractMapBlock.java","lineNumber":488,"sourceCode":"        {\n            return INSTANCE_SIZE + sizeOf(hashTables);\n        }\n\n        public void loadHashTables(int positionCount, int[] offsets, boolean[] mapIsNull, Block keyBlock, MethodHandle keyBlockHashCode)\n        {\n            int[] hashTables = new int[keyBlock.getPositionCount() * HASH_MULTIPLIER];\n            Arrays.fill(hashTables, -1);\n\n            verify(positionCount < offsets.length, \"incorrect offsets size\");\n\n            for (int i = 0; i < positionCount; i++) {\n                int keyOffset = offsets[i];\n                int keyCount = offsets[i + 1] - keyOffset;\n                if (keyCount < 0) {\n                    throw new IllegalArgumentException(format(\"Offset is not monotonically ascending. offsets[%s]=%s, offsets[%s]=%s\", i, offsets[i], i + 1, offsets[i + 1]));\n                }\n                if (mapIsNull != null && mapIsNull[i] && keyCount != 0) {\n                    throw new IllegalArgumentException(\"A null map must have zero entries\");\n                }\n                buildHashTable(\n                        keyBlock,\n                        keyOffset,\n                        keyCount,\n                        keyBlockHashCode,\n                        hashTables,\n                        keyOffset * HASH_MULTIPLIER,\n                        keyCount * HASH_MULTIPLIER);\n            }\n            set(hashTables);\n        }\n\n        // This class intentionally does not implement hashcode and equals.\n        // Any class using Hashtables as a field (MapBlock, MapBlockBuilder) should not include this class's implementation as this is\n        // derived data. Only using KeyBlock hashcode/equals should suffice.\n        // This class has no immutable fields, which makes hashcode/equals error-prone.\n    }","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-common/src/main/java/com/facebook/presto/common/block/AbstractMapBlock.java#L470-L506","documentation":"A MAP block whose isNull flag is set for a row must encode zero entries for that row (offsets[i+1] == offsets[i]). If a null map is declared but the offsets slice claims entries, the block would have key/value data for a null value, which is inconsistent, so the library throws IllegalArgumentException.","triggerScenarios":"Creating a map block where mapIsNull[i] is true but offsets[i+1] - offsets[i] != 0; typically from custom block construction or deserialization that sets null flags independently of the offsets array.","commonSituations":"Custom serializers writing null flags and offsets separately; connector code reusing offsets arrays from a previous non-null batch while adding null flags; version-mismatched page deserialization.","solutions":["Ensure that whenever mapIsNull[i] is true, offsets[i+1] is set equal to offsets[i] before building the block.","Recompute the offsets array from the same source of truth (the per-row entry counts and null flags) rather than patching them independently.","Check serialization round-trips: null flags and offsets must be written/read from the same stream version.","Validate before construction: for each i, (!null || entryCount == 0)."],"exampleFix":"// before\nmapIsNull[i] = true;\n// offsets[i+1] left at 3, leaving 2 entries for the null map\n// after\nmapIsNull[i] = true;\noffsets[i + 1] = offsets[i]; // null map must have zero entries","handlingStrategy":"validation","validationCode":"static void validateNullMapOffsets(int[] offsets, boolean[] mapIsNull, int positionCount) {\n    for (int i = 0; i < positionCount; i++) {\n        if (mapIsNull != null && mapIsNull[i]) {\n            checkArgument(offsets[i + 1] == offsets[i], \"null map at %d must have zero entries\", i);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    Block mapBlock = buildMapBlock(keys, values, offsets, mapIsNull);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"A null map must have zero entries\")) {\n        throw new DataCorruptionException(\"null map row carries entries\", e);\n    }\n    throw e;\n}","preventionTips":["Set null flags and offsets from the same per-row computation loop.","Never patch mapIsNull after offsets are finalized.","Round-trip test serialization: rebuild and validate blocks after decode."],"tags":["presto","block","map-block","null-value","illegal-argument"],"backgroundTag":"invalid-offsets-array","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}