Skip to content

fix(query): valToBytes []byte fast path for string-typed values#9710

Open
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/valtobytes-bytes-fast-path
Open

fix(query): valToBytes []byte fast path for string-typed values#9710
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:perf/valtobytes-bytes-fast-path

Conversation

@shaunpatterson
Copy link
Copy Markdown
Contributor

Description

When a types.Val with Tid=StringID or Tid=DefaultID holds its payload as []byte (the storage form coming back from posting list reads), valToBytes previously fell through to json.Marshal(v.Value) — and encoding/json's default for []byte is base64. The JSON response then contained base64-encoded text where callers expected a plain string.

Callers that already work with []byte payloads include worker/task.go and query/query.go, which build types.Val{Tid: StringID, Value: []byte(...)}; the prior default case mis-encoded such values.

The fix adds an explicit []byte branch that routes through stringJsonMarshal (the same path the case string branch uses) via unsafe.String, avoiding the json.Marshal base64 step entirely. The zero-length slice is handled separately to keep the empty-string output as []byte("\"\"") without an intermediate allocation.

A new table-driven test TestValToBytesByteSlice covers empty, ASCII, UTF-8, JSON-special, control-char, and U+2028/U+2029 inputs against both StringID and DefaultID, asserting the output equals the JSON-quoted form (never base64).

Perf (BenchmarkFastJsonNode2Chilren, 5×3s avg, Apple M4 Max):

  • ns/op: 88.13M → 86.58M (−1.76%)
  • B/op: 50.33M → 46.14M (−8.32%)
  • allocs/op: 524315 (unchanged)

Checklist

  • The PR title follows the Conventional Commits syntax, leading with fix:, feat:, chore:, ci:, etc.
  • Code compiles correctly and linting (via trunk) passes locally
  • Tests added for new functionality, or regression tests for bug fixes added as applicable

Thank you for your contribution to Dgraph!

🤖 Generated with Claude Code

Before this change, when a types.Val with Tid=StringID or Tid=DefaultID
held its payload as []byte (the storage form coming back from posting
list reads), valToBytes fell through to json.Marshal(v.Value) — and
encoding/json's default for []byte is base64. The JSON response then
contained base64-encoded text where callers expected a plain string.

Callers that already work with []byte payloads include worker/task.go
and query/query.go, which build types.Val{Tid: StringID, Value:
[]byte(...)}; the prior default case mis-encoded such values.

The fix adds an explicit []byte branch that routes through
stringJsonMarshal (the same path the case-string branch uses) via
unsafe.String, avoiding the json.Marshal base64 step entirely. The
zero-length slice is handled separately to keep the empty-string
output as []byte("\"\"") without an intermediate allocation.

Perf (BenchmarkFastJsonNode2Chilren, 5x3s avg, Apple M4 Max):
  ns/op   88.13M -> 86.58M (-1.76%)
  B/op    50.33M -> 46.14M (-8.32%)
  allocs/op  524315 (unchanged)
@shaunpatterson shaunpatterson requested a review from a team as a code owner May 23, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant