Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
13b431c
Separate public API from impl of comparison functions
serramatutu Apr 30, 2026
a231fa1
Add `nullable` to `equalOption`
serramatutu Apr 30, 2026
e8238e1
Check for nullable opt in most array compare implementations
serramatutu Apr 30, 2026
48fde39
Add stricter tests to null JSON in record and struct
serramatutu Apr 28, 2026
1a2c73b
`AppendEmptyValue()` if field is nullable
serramatutu Apr 28, 2026
7a3b5ba
Add `nullable` argument to `GetOneForMarshal()`
serramatutu Apr 29, 2026
572e1ad
Make struct and record use `field.Nullable` when serializing
serramatutu Apr 29, 2026
0c879e6
Fix tests that were implicitly depending on wrong nullable semantics
serramatutu Apr 29, 2026
8ead25e
Add `TimestampWithOffset` extension type
serramatutu Oct 28, 2025
47cc456
Allow dictionary encoding of `TimestampWithOffset`
serramatutu Dec 22, 2025
8c4aeb5
Allow run-end encoding of `TimestampWithOffset`
serramatutu Dec 22, 2025
215dbd0
Optimize `Values()` and `MarshalJSON()` for REE
serramatutu Dec 22, 2025
faffeba
General code smell patches
serramatutu Jan 30, 2026
9610577
Test with `-08:30` and `11:00` timezone
serramatutu Jan 30, 2026
8265d65
Rename constructors
serramatutu Jan 30, 2026
a4e0baf
Leverage generics for REE and dict-encoding
serramatutu Feb 2, 2026
708e46e
Make equals also check for data type
serramatutu Feb 3, 2026
d5725e1
Remove outdated docs
serramatutu Apr 10, 2026
26c628c
Remove unnecessary IsInteger call
serramatutu Apr 10, 2026
591763d
Remove specific type for `TimestampWithOffsetRunEndsType`
serramatutu Apr 10, 2026
f633409
Uncomment test
serramatutu Apr 10, 2026
3ec66ac
Fix REE ends type
serramatutu Apr 15, 2026
e427a2e
Support unix epoch
serramatutu Apr 15, 2026
3dffed2
Fix inconsistency with `UnsafeAppend()`
serramatutu Apr 15, 2026
257793e
Stop appending nulls to child arrays
serramatutu Apr 15, 2026
f225db3
Fix lint
serramatutu Apr 15, 2026
1a7412e
Simplify `fieldValuesFromTime()` implementation
serramatutu Apr 15, 2026
f58d662
Add subtests
serramatutu Apr 30, 2026
b6f73a4
Post-rebase fixes for CI: JSON int precision, GetOneForMarshal, TinyGo
zeroshade Jul 2, 2026
79cf180
Make nested nullability field-local in comparison and union marshal
zeroshade Jul 2, 2026
6f92ce2
Make exact Equal and chunked comparison field-local too
zeroshade Jul 2, 2026
778d8d7
Preserve nullability option in exact chunked comparison
zeroshade Jul 2, 2026
1befc5e
Address review feedback on nullability-aware comparison and REE offsets
zeroshade Jul 2, 2026
5b96785
Fix TimestampWithOffset builder nil validity and reuse handling
zeroshade Jul 2, 2026
51dcf3f
Treat empty validity slice as all-valid in TimestampWithOffset builder
zeroshade Jul 2, 2026
212e428
Reset REE run tracker on TimestampWithOffset builder null paths
zeroshade Jul 2, 2026
a8b9af4
Compare record/table fields ignoring metadata
zeroshade Jul 3, 2026
9b3a302
Address roborev findings on record/table field comparison
zeroshade Jul 3, 2026
03b049f
Avoid breaking arrow.Array with optional NullableMarshaler
zeroshade Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions arrow/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ type Array interface {
Release()
}

// NullableMarshaler is an optional interface an Array may implement to control
// whether a value is rendered as JSON null based on the field-local nullability
// from the schema, rather than solely on the array's own validity bitmap.
//
// It exists so containers (struct/union/dictionary/run-end-encoded/extension)
// and RecordToJSON can propagate each field's Nullable flag down to its values
// without a breaking change to the Array interface. Arrays that do not implement
// it fall back to GetOneForMarshal, preserving the previous validity-based behavior.
type NullableMarshaler interface {
// GetOneForMarshalNullable returns the value at i for json.Marshal. When
// nullable is false, a value is returned even if the array's validity bitmap
// marks it null (a non-nullable field must not serialize as null).
GetOneForMarshalNullable(i int, nullable bool) interface{}
}

// ValueType is a generic constraint for valid Arrow primitive types
type ValueType interface {
bool | FixedWidthType | string | []byte
Expand Down
36 changes: 24 additions & 12 deletions arrow/array/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,17 @@ func (a *Binary) setData(data *Data) {
}
}

func (a *Binary) GetOneForMarshal(i int) interface{} {
if a.IsNull(i) {
func (a *Binary) GetOneForMarshalNullable(i int, nullable bool) interface{} {
if nullable && a.IsNull(i) {
return nil
}
return a.Value(i)
}

func (a *Binary) GetOneForMarshal(i int) interface{} {
return a.GetOneForMarshalNullable(i, true)
}

func (a *Binary) MarshalJSON() ([]byte, error) {
vals := make([]interface{}, a.Len())
for i := 0; i < a.Len(); i++ {
Expand Down Expand Up @@ -223,9 +227,9 @@ func (a *Binary) ValidateFull() error {
return nil
}

func arrayEqualBinary(left, right *Binary) bool {
func arrayEqualBinary(left, right *Binary, opt equalOption) bool {
for i := 0; i < left.Len(); i++ {
if left.IsNull(i) {
if opt.nullable && left.IsNull(i) {
continue
}
if !bytes.Equal(left.Value(i), right.Value(i)) {
Expand Down Expand Up @@ -346,13 +350,17 @@ func (a *LargeBinary) setData(data *Data) {
}
}

func (a *LargeBinary) GetOneForMarshal(i int) interface{} {
if a.IsNull(i) {
func (a *LargeBinary) GetOneForMarshalNullable(i int, nullable bool) interface{} {
if nullable && a.IsNull(i) {
return nil
}
return a.Value(i)
}

func (a *LargeBinary) GetOneForMarshal(i int) interface{} {
return a.GetOneForMarshalNullable(i, true)
}

func (a *LargeBinary) MarshalJSON() ([]byte, error) {
vals := make([]interface{}, a.Len())
for i := 0; i < a.Len(); i++ {
Expand Down Expand Up @@ -417,9 +425,9 @@ func (a *LargeBinary) ValidateFull() error {
return nil
}

func arrayEqualLargeBinary(left, right *LargeBinary) bool {
func arrayEqualLargeBinary(left, right *LargeBinary, opt equalOption) bool {
for i := 0; i < left.Len(); i++ {
if left.IsNull(i) {
if opt.nullable && left.IsNull(i) {
continue
}
if !bytes.Equal(left.Value(i), right.Value(i)) {
Expand Down Expand Up @@ -522,13 +530,17 @@ func (a *BinaryView) ValueStr(i int) string {
return base64.StdEncoding.EncodeToString(a.Value(i))
}

func (a *BinaryView) GetOneForMarshal(i int) interface{} {
if a.IsNull(i) {
func (a *BinaryView) GetOneForMarshalNullable(i int, nullable bool) interface{} {
if nullable && a.IsNull(i) {
return nil
}
return a.Value(i)
}

func (a *BinaryView) GetOneForMarshal(i int) interface{} {
return a.GetOneForMarshalNullable(i, true)
}

func (a *BinaryView) MarshalJSON() ([]byte, error) {
vals := make([]interface{}, a.Len())
for i := 0; i < a.Len(); i++ {
Expand All @@ -539,10 +551,10 @@ func (a *BinaryView) MarshalJSON() ([]byte, error) {
return json.Marshal(vals)
}

func arrayEqualBinaryView(left, right *BinaryView) bool {
func arrayEqualBinaryView(left, right *BinaryView, opt equalOption) bool {
leftBufs, rightBufs := left.dataBuffers, right.dataBuffers
for i := 0; i < left.Len(); i++ {
if left.IsNull(i) {
if opt.nullable && left.IsNull(i) {
continue
}
if !left.ValueHeader(i).Equals(leftBufs, right.ValueHeader(i), rightBufs) {
Expand Down
12 changes: 8 additions & 4 deletions arrow/array/boolean.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,17 @@ func (a *Boolean) setData(data *Data) {
}
}

func (a *Boolean) GetOneForMarshal(i int) interface{} {
if a.IsValid(i) {
func (a *Boolean) GetOneForMarshalNullable(i int, nullable bool) interface{} {
if !nullable || a.IsValid(i) {
return a.Value(i)
}
return nil
}

func (a *Boolean) GetOneForMarshal(i int) interface{} {
return a.GetOneForMarshalNullable(i, true)
}

func (a *Boolean) MarshalJSON() ([]byte, error) {
vals := make([]interface{}, a.Len())
for i := 0; i < a.Len(); i++ {
Expand All @@ -109,9 +113,9 @@ func (a *Boolean) MarshalJSON() ([]byte, error) {
return json.Marshal(vals)
}

func arrayEqualBoolean(left, right *Boolean) bool {
func arrayEqualBoolean(left, right *Boolean, opt equalOption) bool {
for i := 0; i < left.Len(); i++ {
if left.IsNull(i) {
if opt.nullable && left.IsNull(i) {
continue
}
if left.Value(i) != right.Value(i) {
Expand Down
Loading