You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(nimble): Route projector schema construction through velox-source buildProjectedNimbleType (facebookincubator#840)
Summary:
Pull Request resolved: facebookincubator#840
`nimble::serde::Projector` and `nimble::NimbleIndexProjector` previously built their projected schemas by calling the **nimble-source** overload of `buildProjectedNimbleType(const Type*, subfields, &outOffsets)` in `dwio/nimble/velox/SchemaUtils.cpp`. That overload silently dropped missing FlatMap keys, which caused shift-by-N corruption when callers deserialized the projected blob against an expanded schema that included the dropped keys: the Deserializer read each output position through a stream offset that no longer corresponded to the source bytes it was meant to carry.
Route both projectors through the **velox-source** overload instead. The velox-source `buildProjectedNimbleType` operates on the velox type derived from the source nimble (`convertToVeloxType` collapses FlatMap to plain `MAP<K,V>`, discarding the specific key list). `resolveVeloxSubfield` inserts any subscript key string into `selectedChildren` unconditionally — no presence check against any source key list — so the projected FlatMap gets one alphabetically-ordered child per requested key, with a value subtree cloned from the source's value type. **Missing-key handling falls out for free** because the velox-source path has no concept of "missing"; it just emits whatever keys the caller asked for.
Each projector's constructor now does:
1. `auto veloxSource = convertToVeloxType(*sourceNimbleSchema_)` — existing helper.
2. `const auto encodings = deriveColumnEncodings(sourceNimbleSchema_->asRow())` — new helper that classifies top-level columns by `Kind` (FlatMap → `flatMapColumns`, ArrayWithOffsets → `dictionaryArrayColumns`, SlidingWindowMap → `deduplicatedMapColumns`) so the velox-source builder knows which top-level MAPs to materialize as FlatMaps.
3. `projectedSchema_ = buildProjectedNimbleType(veloxSource->asRow(), subfields, encodings)` — velox-source overload.
4. `computeSourceStreamOffsets(*sourceNimbleSchema_, subfields, encodings, inputStreamIndices_)` — new helper that walks the source nimble in the same DFS pre-order + FlatMap-alphabetical traversal the velox-source builder uses, emitting one source stream offset per projected stream position. Missing FlatMap keys emit `UINT32_MAX` for every descriptor under the value subtree plus the inMap, lining up positionally with the placeholder slots in the projected schema. The Projector's stream-copy guard (`Projector.cpp:358-365`) and `NimbleIndexProjector`'s equivalent already treat `streamIdx >= streamSizes.size()` as a 0-byte slot, so `UINT32_MAX` (larger than any real source offset) produces 0-byte placeholder slots in the projected blob automatically. The Deserializer's gap-fill (`Deserializer.cpp:467-515`) then turns those slots into null columns.
This collapses the dual-API design to a single source of truth. Projector-side and Deserializer-side schema layouts now agree by construction (both call the same `buildProjectedNimbleType` overload with the same subfields) instead of by an implicit "both overloads must produce structurally identical alphabetical+DFS schemas" contract. The `NimbleMissingChildrenMap` typedef, `generateMissingKeyType` recursive cloner, `Entry` merging, and `buildProjectedType` private worker are no longer needed and have been deleted along with the nimble-source `buildProjectedNimbleType` overload itself. `SchemaUtilsTest` cross-equivalence cases that asserted both overloads produce identical schemas have been deleted — with only one overload remaining, the comparison is tautological.
The "must have at least one real key per FlatMap" guard is preserved in `computeSourceStreamOffsets`: a projection that consists entirely of subscripts whose keys are all missing from the source throws `NimbleInternalError`, since this usually indicates caller misuse (e.g., a typo on every key) and silently projecting an all-null FlatMap would mask it.
Reviewed By: xiaoxmeng
Differential Revision: D107747094
fbshipit-source-id: 016e7da89ae975a024985b2eb8d65c5eb9b4f2cf
0 commit comments