Title: reader: ScanIterator.compareValues swallows ClassCastException, silently disabling zone-map pruning on type-mismatched filters · Issue #159 · dfa1/vortex-java · GitHub
Open Graph Title: reader: ScanIterator.compareValues swallows ClassCastException, silently disabling zone-map pruning on type-mismatched filters · Issue #159 · dfa1/vortex-java
X Title: reader: ScanIterator.compareValues swallows ClassCastException, silently disabling zone-map pruning on type-mismatched filters · Issue #159 · dfa1/vortex-java
Description: Summary ScanIterator.compareValues(Object a, Object b) catches ClassCastException and returns 0. Because the canPruneChunk arms treat compareValues(...) < 0 / > 0 as the prune condition, a 0 means "cannot prune" — so a filter whose value...
Open Graph Description: Summary ScanIterator.compareValues(Object a, Object b) catches ClassCastException and returns 0. Because the canPruneChunk arms treat compareValues(...) < 0 / > 0 as the prune condition, a 0 means ...
X Description: Summary ScanIterator.compareValues(Object a, Object b) catches ClassCastException and returns 0. Because the canPruneChunk arms treat compareValues(...) < 0 / > 0 as the prune condition, a 0 ...
Opengraph URL: https://github.com/dfa1/vortex-java/issues/159
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"reader: ScanIterator.compareValues swallows ClassCastException, silently disabling zone-map pruning on type-mismatched filters","articleBody":"## Summary\n\n`ScanIterator.compareValues(Object a, Object b)` catches `ClassCastException` and returns `0`. Because the `canPruneChunk` arms treat `compareValues(...) \u003c 0` / `\u003e 0` as the prune condition, a `0` means **\"cannot prune\"** — so a filter whose value type does not match the stored zone-map stat type silently prunes **nothing** and the whole column is decoded. No error, no log: a correctness-safe but performance-silent failure.\n\n## Where\n\n`reader/src/main/java/io/github/dfa1/vortex/reader/ScanIterator.java`\n\n```java\nprivate static int compareValues(Object a, Object b) {\n try {\n return ((Comparable\u003cObject\u003e) a).compareTo(b);\n } catch (ClassCastException _) {\n return 0; // \u003c-- mismatch =\u003e 0 =\u003e canPruneChunk yields false =\u003e no pruning\n }\n}\n```\n\nInteger zone stats decode as `Long` (`ArrayStats.decodeScalar` returns `int64_value()`), and floats as `Double`. So a caller that builds a `RowFilter` with an `Integer` (or `Float`) value against an integer (or f32) column gets **zero pruning** even though the predicate is perfectly valid — the comparator just throws `ClassCastException` internally and is swallowed.\n\n## How it surfaced\n\nBuilding the Calcite adapter's filter push-down (`feat/vortex-calcite-demo`), a `WHERE date BETWEEN ...` on an `I32` column pruned **0 of 100** chunks until the literal was coerced from `Integer` to `Long`. The filter, expansion, and `canPruneChunk` path were all correct — only the boxed type differed, and the swallow hid it.\n\n## Impact\n\n- Silent performance cliff: a valid, selective filter degrades to a full scan with no signal.\n- Easy to hit from any caller that boxes filter values at the column's \"natural\" width (`Integer` for `I32`, `Float` for `F32`) rather than the stat's storage width (`Long` / `Double`).\n\n## Suggested fix (pick one)\n\n1. **Normalise numeric comparison** in `compareValues` — if both args are `Number` but different boxed types, compare via `Long`/`Double` (or `BigDecimal`) instead of returning `0`. Most robust; makes pruning width-agnostic.\n2. **Coerce on `RowFilter` construction** — normalise integer values to `Long` and float values to `Double` when a `RowFilter` is built, matching the stat storage types.\n3. At minimum, **do not return `0` on mismatch** — a genuine type mismatch (e.g. comparing a string filter to a numeric column) should be a clear error or a logged \"pruning skipped\", not a silent no-op.\n\nOption 1 is preferred: it fixes every caller, not just the careful ones.\n\n## Notes\n\nThe Calcite adapter worked around this by coercing integer literals to `Long` (commit on `feat/vortex-calcite-demo`), so it is unblocked — but the underlying reader trap should be fixed so the next caller does not rediscover it.","author":{"url":"https://github.com/dfa1","@type":"Person","name":"dfa1"},"datePublished":"2026-06-24T19:59:33.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/159/vortex-java/issues/159"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:3e56898d-1bc6-f806-30c6-1a8eb554e60e |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B656:C61B2:23655A8:3305D50:6A4D12D2 |
| html-safe-nonce | b6e4b188033dff901526fb14b92ad97982e1d115ecf9cfd3334afaedf4414ba8 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCNjU2OkM2MUIyOjIzNjU1QTg6MzMwNUQ1MDo2QTREMTJEMiIsInZpc2l0b3JfaWQiOiI2MzcwNTE2ODQ0OTQwMTY5OTM4IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | c1aff3e51815964a38ee6861af410b95a43984bf859ea69b9831087b05f662bc |
| hovercard-subject-tag | issue:4737989586 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/dfa1/vortex-java/159/issue_layout |
| twitter:image | https://opengraph.githubassets.com/8b7b7f98aa26f7f6836e9a9fbf07ca03cc845b69d47f079d6f4da9243015ead9/dfa1/vortex-java/issues/159 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/8b7b7f98aa26f7f6836e9a9fbf07ca03cc845b69d47f079d6f4da9243015ead9/dfa1/vortex-java/issues/159 |
| og:image:alt | Summary ScanIterator.compareValues(Object a, Object b) catches ClassCastException and returns 0. Because the canPruneChunk arms treat compareValues(...) < 0 / > 0 as the prune condition, a 0 means ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | dfa1 |
| hostname | github.com |
| expected-hostname | github.com |
| None | f02420044fa3730a0607f4a52546bdd73f5ae60187ef57d284ecb6d7f3f1d880 |
| turbo-cache-control | no-preview |
| go-import | github.com/dfa1/vortex-java git https://github.com/dfa1/vortex-java.git |
| octolytics-dimension-user_id | 5798698 |
| octolytics-dimension-user_login | dfa1 |
| octolytics-dimension-repository_id | 1249412947 |
| octolytics-dimension-repository_nwo | dfa1/vortex-java |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1249412947 |
| octolytics-dimension-repository_network_root_nwo | dfa1/vortex-java |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f0b6a62c1f0be1086cb0b5b077b82cebe520eb42 |
| ui-target | canary-2 |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width