Skip to content

feat: add signed ↔ unsigned integer coercions - #3

Merged
fsaintjacques merged 2 commits into
mainfrom
fsj/signed-unsigned-coercion
Jul 23, 2026
Merged

feat: add signed ↔ unsigned integer coercions#3
fsaintjacques merged 2 commits into
mainfrom
fsj/signed-unsigned-coercion

Conversation

@fsaintjacques

Copy link
Copy Markdown
Owner

Motivation

BigQuery has a single integer type, which the Storage Read API surfaces as Arrow Int64 regardless of the destination proto field's signedness. Any proto message with a uint64/uint32 field is therefore unmappable today: sign-crossover pairs are classified Incompatible, which no coercion flag (per-field [(apb.apb).coerce = true] or coerce_all) can rescue.

What

Reclassifies the full signed ↔ unsigned matrix as CoercionAvailable and adds the corresponding scalar encoders. Values are reinterpreted as two's complement — a C-style cast, matching what a protobuf decoder does when a field's declared type changes between int and uint kinds. There are no runtime range checks; encoding never fails on these pairs.

Source (Arrow) Target (proto) Risk Behavior
Int32 uint32 / fixed32 Semantic bit-identical (-1u32::MAX)
Int32 uint64 / fixed64 Semantic sign-extends (-1u64::MAX)
Int64 uint64 / fixed64 Semantic bit-identical (-1u64::MAX)
Int64 uint32 / fixed32 Truncation keeps low 32 bits
UInt32 int32 / sint32 / sfixed32 Semantic u32::MAX-1
UInt32 int64 / sint64 / sfixed64 Lossless every u32 fits in i64
UInt64 int64 / sint64 / sfixed64 Semantic u64::MAX-1
UInt64 int32 / sint32 / sfixed32 Truncation keeps low 32 bits

Wire encodings follow each target's native rules (varint with 64-bit sign extension for negative int32/int64 targets, zigzag for sint, fixed for (s)fixed). Repeated/map/oneof contexts pick the new pairs up for free via the shared scalar encoder dispatch.

Notes

  • Coercion remains opt-in (per-field annotation or coerce_all/--coerce); default inference still rejects these pairs.
  • Existing same-signedness narrowing coercions (e.g. Int64 → int32) keep their runtime truncation checks — this PR only defines the crossover pairs, which follow protobuf's own reinterpretation convention.
  • cargo clippy --all-targets failures in transcode/tests.rs (approx-PI constants) pre-exist on main and are untouched here.

Testing

  • 13 new unit tests: 8 transcode round-trips asserting the exact reinterpretation values (wrap-around, sign-extension, truncation) via decode_message, 5 compatibility classification tests covering the full matrix.
  • cargo test -p apb-core: 190 passed.

🤖 Generated with Claude Code

Sign-crossover pairs (e.g. Arrow Int64 → proto uint64) were classified as
Incompatible, so no coercion flag could bind them. This matters for BigQuery,
whose only integer type surfaces as Arrow Int64 regardless of the proto
field's signedness — any uint64/uint32 proto field was unmappable.

Reclassify the full signed ↔ unsigned matrix as CoercionAvailable. Values
are reinterpreted as two's complement (a C-style cast, matching what a
protobuf decoder does when a field changes between int and uint kinds); no
runtime range checks:

- Int32/Int64 → uint32/fixed32/uint64/fixed64: Semantic (negatives become
  large unsigned values, widening sign-extends), except Int64 → 32-bit
  targets which are Truncation (low 32 bits kept)
- UInt32 → int32/sint32/sfixed32: Semantic (above i32::MAX wraps negative)
- UInt32 → int64/sint64/sfixed64: Lossless (every u32 fits in i64)
- UInt64 → int64/sint64/sfixed64: Semantic; → 32-bit targets: Truncation

Repeated/map/oneof contexts get the new pairs for free via the shared
scalar encoder dispatch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in signed ↔ unsigned integer coercions to apb-core so Arrow integer columns (notably BigQuery’s Int64 output) can map to protobuf unsigned fields by reinterpreting values as two’s complement, with new scalar encoders and unit tests covering the full matrix.

Changes:

  • Reclassify signed/unsigned integer crossover pairs as CoercionAvailable with appropriate risk levels (semantic/lossless/truncation).
  • Add scalar encoder variants + plan selection for crossover pairs, implementing two’s-complement reinterpretation without runtime range checks.
  • Extend unit tests with round-trip assertions for wrap-around, sign-extension, and truncation behaviors; document the feature in README.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Documents the new signed ↔ unsigned crossover coercion behavior and motivation (BigQuery).
crates/apb-core/src/types/compatibility.rs Marks signed/unsigned crossover pairs as CoercionAvailable and adds matrix-coverage tests.
crates/apb-core/src/transcode/tests.rs Adds round-trip transcoding tests validating reinterpretation/truncation outcomes.
crates/apb-core/src/transcode/plan.rs Extends scalar encoder selection to route crossover pairs to new encoders under Coerce.
crates/apb-core/src/transcode/encode.rs Introduces new ScalarKind variants and encoding functions implementing the crossover rules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/apb-core/src/transcode/encode.rs Outdated
Rename Int32AsUFixed32/Int32AsUFixed64/Int64AsUFixed32/Int64AsUFixed64 (and
their encoder functions) to ...AsFixed32/...AsFixed64, matching the existing
unsigned-fixed variants (UInt64AsFixed32, UInt32AsFixed64) so a single naming
scheme covers the fixed32/fixed64 proto kinds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fsaintjacques
fsaintjacques merged commit 5bde64f into main Jul 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants