Skip to content

feat(go): support native fory row format - #3901

Open
ayush00git wants to merge 16 commits into
apache:mainfrom
ayush00git:feat/go-fory-row-format
Open

feat(go): support native fory row format#3901
ayush00git wants to merge 16 commits into
apache:mainfrom
ayush00git:feat/go-fory-row-format

Conversation

@ayush00git

@ayush00git ayush00git commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Why?

What does this PR do?

Related issues

AI Contribution Checklist

  • Substantial AI assistance was used in this PR: yes / no
  • If yes, I included a completed AI Contribution Checklist in this PR description and the required AI Usage Disclosure.
  • If yes, my PR description includes the required ai_review summary and screenshot evidence or equivalent persisted links of the final clean AI review results from both fresh reviewers described in AI_POLICY.md, the Fory-guided reviewer and the independent general reviewer, on the current PR diff or current HEAD after the latest code changes.

Does this PR introduce any user-facing change?

  • Does this PR introduce any public API change?
  • Does this PR introduce any binary protocol compatibility change?

Benchmark

@ayush00git
ayush00git requested a review from chaokunyang as a code owner July 30, 2026 07:14
@ayush00git
ayush00git removed the request for review from chaokunyang July 30, 2026 13:05
@ayush00git
ayush00git requested a review from chaokunyang July 30, 2026 17:03

@wenshao wenshao 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.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Go and Java toolchains not installed on this machine; CI passed all Go checks. Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

— Qwen3-235B-A22B via Qwen Code /review (v0.21.2)

Comment thread go/fory/row/infer.go
Comment on lines +209 to +211
} else if strings.HasPrefix(part, "ignore=") {
switch strings.TrimPrefix(part, "ignore=") {
case "true":

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Suggestion] hasIgnoreTag accepts only literal "true"/"false" while the core tag parser parseFieldTag delegates to parseBoolStrict, which accepts true/1/yes/false/0/no case-insensitively with trimming. The doc comment claims this function mirrors the core parser. — Failure scenario: a struct tagged fory:"ignore=1" serializes correctly through the core fory serializer, but InferSchema returns an error for the same tag.

Suggested change
} else if strings.HasPrefix(part, "ignore=") {
switch strings.TrimPrefix(part, "ignore=") {
case "true":
} else if strings.HasPrefix(part, "ignore=") {
switch strings.ToLower(strings.TrimSpace(strings.TrimPrefix(part, "ignore="))) {
case "true", "1", "yes":

— Qwen3-235B-A22B via Qwen Code /review

Comment thread go/fory/row/row.go
Comment on lines +350 to +351
end := offset + size
if offset < 0 || size < 0 || end > len(data) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Suggestion] boundedSlice's bounds check is defeated by signed integer overflow: end := offset + size wraps negative when size is near MaxInt64 (reachable via NewMapData's attacker-controlled uint64→int cast). All three guards pass on the negative end, and the function reaches a generic runtime panic instead of the intended descriptive one. Go's runtime prevents memory-safety issues, but the defensive boundary is silently bypassed. — Failure scenario: a crafted keysSize = MaxInt64 makes end = 8 + MaxInt64 overflow to negative; data[8:negative] triggers an uninformative panic.

Suggested change
end := offset + size
if offset < 0 || size < 0 || end > len(data) {
end := offset + size
if offset < 0 || size < 0 || end < offset || end > len(data) {

— Qwen3-235B-A22B via Qwen Code /review

Comment thread go/fory/row/writer.go
Comment on lines +214 to +217
dataBytes := int64(numElements) * int64(w.elemSize)
if dataBytes > maxArrayDataBytes {
return fmt.Errorf("row: array of %d elements exceeds maximum size", numElements)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Suggestion] The int64 multiplication int64(numElements) * int64(w.elemSize) overflows for extreme numElements, wrapping dataBytes negative and bypassing the > maxArrayDataBytes guard. The trigger is physically unallocatable, but the explicit guard is provably defeated. — Failure scenario: numElements > MaxInt64/8 wraps dataBytes negative, producing an obscure runtime panic instead of the intended clean error.

Suggested change
dataBytes := int64(numElements) * int64(w.elemSize)
if dataBytes > maxArrayDataBytes {
return fmt.Errorf("row: array of %d elements exceeds maximum size", numElements)
}
if int64(numElements) > maxArrayDataBytes/int64(w.elemSize) {
return fmt.Errorf("row: array of %d elements exceeds maximum size", numElements)
}
dataBytes := int64(numElements) * int64(w.elemSize)

— Qwen3-235B-A22B via Qwen Code /review

Comment thread go/fory/row/infer.go
Comment on lines +165 to +169
elem, err := inferField(listItemName, t.Elem(), path)
if err != nil {
return Field{}, err
}
return Field{Name: name, Type: &ListType{Elem: elem}, Nullable: true}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Suggestion] inferField leaves the list-element Nullable flag at its zero value false for value-type elements (e.g. []int32), diverging from Java's TypeInference (which produces Nullable: true for List<Integer>) and Go's own List()/Map() factories (which always set Nullable: true). The nullable flag is encoded as bit 6 of the field header in SchemaToBytes, so schema bytes differ. The cross-language test fixture uses only pointer elements ([]*string), masking this divergence. — Failure scenario: adding a []int32 field to the xlang fixture would fail both parsed.Equal(encoder.Schema()) and bytes.Equal(reencoded, schemaBytes).

Suggested change
elem, err := inferField(listItemName, t.Elem(), path)
if err != nil {
return Field{}, err
}
return Field{Name: name, Type: &ListType{Elem: elem}, Nullable: true}, nil
elem, err := inferField(listItemName, t.Elem(), path)
if err != nil {
return Field{}, err
}
elem.Nullable = true
return Field{Name: name, Type: &ListType{Elem: elem}, Nullable: true}, nil

— Qwen3-235B-A22B via Qwen Code /review

Comment thread go/fory/row/encoder.go
Comment on lines +476 to +480
key := reflect.New(goType.Key()).Elem()
if err := keyCodec.read(keys, j, key); err != nil {
return err
}
value := reflect.New(goType.Elem()).Elem()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Suggestion] newMapCodec's read loop allocates two heap objects per map entry via reflect.New(...).Elem() inside the loop. Since SetMapIndex copies the key and value, and the read functions fully overwrite their targets (per the valueCodec doc contract), both allocations can be hoisted before the loop. — Concrete cost: deserializing a map with N entries performs 2N short-lived heap allocations that immediately become garbage, adding GC pressure proportional to map size.

Suggested change
key := reflect.New(goType.Key()).Elem()
if err := keyCodec.read(keys, j, key); err != nil {
return err
}
value := reflect.New(goType.Elem()).Elem()
key := reflect.New(goType.Key()).Elem()
value := reflect.New(goType.Elem()).Elem()
for j := 0; j < n; j++ {
if err := keyCodec.read(keys, j, key); err != nil {
return err
}

— Qwen3-235B-A22B via Qwen Code /review

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