This is an automated email from the ASF dual-hosted git repository.
xiazcy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/master by this push:
new a0b158a1b5 CTR simplify gremlin-go per-request option handling
a0b158a1b5 is described below
commit a0b158a1b5d9876e5f791bffecc225d98857610c
Author: Yang Xia <[email protected]>
AuthorDate: Thu Oct 22 18:17:39 2026 -0700
CTR simplify gremlin-go per-request option handling
Replace the reflection-based option dispatch with a direct switch. Numeric
options now accept any integer-valued numeric type, and a value that can't be
applied is ignored instead of panicking.
Assisted-by: Kiro:claude-opus-5-5
---
CHANGELOG.asciidoc | 1 +
gremlin-go/driver/client.go | 102 +++++++++++++++++++++++++++++++--------
gremlin-go/driver/client_test.go | 54 ++++++++++++++++++---
3 files changed, 131 insertions(+), 26 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index b65fd44022..94b5eabcd9 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -29,6 +29,7 @@
image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
* Restricted GraphSON and Gryo `Class` deserialization to classes registered
with `ClassRegistry.register()`, `registerStrategy()` or `registerStrategies()`.
* Fixed `gremlin-go` to report a malformed or truncated GraphBinary response
as a deserialization error rather than a bare decoder message.
* Added a per-request `language` option to the `gremlin-go`
`RequestOptionsBuilder`, and honored `With("language", ...)` on traversals.
+* Changed `gremlin-go` per-request options set via `with()` to accept any
numeric type holding an integer value an `int` can hold and to ignore other
values rather than panicking.
* Made `TinkerGraph` an interface and renamed the in-memory implementation to
`TinkerMemoryGraph`; `TinkerGraph.open()` and `gremlin.graph=...TinkerGraph`
behave as before.
* Renamed `TinkerTransactionGraph` to `TinkerStorageGraph`.
* Fixed a `NullPointerException` when rolling back a transaction on a
`TinkerStorageGraph` that has a vertex index but no edge index.
diff --git a/gremlin-go/driver/client.go b/gremlin-go/driver/client.go
index edf84a2e2e..d8341036e1 100644
--- a/gremlin-go/driver/client.go
+++ b/gremlin-go/driver/client.go
@@ -21,9 +21,9 @@ package gremlingo
import (
"crypto/tls"
+ "math"
"net/http"
"net/url"
- "reflect"
"sync"
"time"
@@ -317,29 +317,91 @@ func (client *Client)
submitGremlinLangWithBuilder(gremlinLang *GremlinLang, bui
}
func applyOptionsConfig(builder *RequestOptionsBuilder, config
map[string]interface{}) *RequestOptionsBuilder {
- builderValue := reflect.ValueOf(builder)
-
- // Map configuration keys to setter method names.
- // "parameters" is intentionally excluded because parameters are
handled separately
- // via SetParametersString in submitGremlinLang, and including it here
would
- // trigger the mutual exclusion panic between SetParameters and
SetParametersString.
- setterMap := map[string]string{
- "timeoutMillis": "SetTimeoutMillis",
- "batchSize": "SetBatchSize",
- "materializeProperties": "SetMaterializeProperties",
- "bulkResults": "SetBulkResults",
- "language": "SetLanguage",
- }
-
+ // Numeric options accept any numeric type holding an exact integer
(see toInt), so an option supplied
+ // as int32, int64 or float64 is converted rather than dropped. A value
that cannot be used for its option
+ // is ignored rather than rejected, so a mistyped with() value never
panics. Two keys are deliberately not handled here:
+ // - "parameters" is applied separately via SetParametersString in
submitGremlinLangWithBuilder;
+ // setting it here would trigger the
SetParameters/SetParametersString mutual-exclusion panic.
+ // - "g" is not accepted as a with() override by the HTTP API; the
traversal source is set
+ // through the client's TraversalSource.
for key, value := range config {
- if methodName, exists := setterMap[key]; exists {
- method := builderValue.MethodByName(methodName)
- if method.IsValid() {
- args := []reflect.Value{reflect.ValueOf(value)}
- method.Call(args)
+ switch key {
+ case "timeoutMillis":
+ if n, ok := toInt(value); ok {
+ builder.SetTimeoutMillis(n)
+ }
+ case "batchSize":
+ if n, ok := toInt(value); ok {
+ builder.SetBatchSize(n)
+ }
+ case "materializeProperties":
+ if s, ok := value.(string); ok {
+ builder.SetMaterializeProperties(s)
+ }
+ case "bulkResults":
+ if b, ok := value.(bool); ok {
+ builder.SetBulkResults(b)
+ }
+ case "language":
+ if s, ok := value.(string); ok {
+ builder.SetLanguage(s)
}
}
}
return builder
}
+
+// toInt converts the numeric types that can arrive through with() (values are
raw interface{}) into an int, so
+// an option supplied as, for example, an int64 or float64 is accepted rather
than dropped. It reports false for
+// non-numeric values and for values that cannot be represented exactly as an
int: non-finite or fractional
+// floats and integers outside the int range.
+func toInt(value interface{}) (int, bool) {
+ switch n := value.(type) {
+ case int:
+ return n, true
+ case int8:
+ return int(n), true
+ case int16:
+ return int(n), true
+ case int32:
+ return int(n), true
+ case int64:
+ if n < math.MinInt || n > math.MaxInt {
+ return 0, false
+ }
+ return int(n), true
+ case uint:
+ return uintToInt(uint64(n))
+ case uint8:
+ return int(n), true
+ case uint16:
+ return int(n), true
+ case uint32:
+ return uintToInt(uint64(n))
+ case uint64:
+ return uintToInt(n)
+ case float32:
+ return floatToInt(float64(n))
+ case float64:
+ return floatToInt(n)
+ default:
+ return 0, false
+ }
+}
+
+func uintToInt(n uint64) (int, bool) {
+ if n > uint64(math.MaxInt) {
+ return 0, false
+ }
+ return int(n), true
+}
+
+func floatToInt(f float64) (int, bool) {
+ // -float64(math.MinInt) is 2^63 (2^31 on 32-bit platforms), the first
value past the int range
+ if math.IsNaN(f) || math.IsInf(f, 0) || f != math.Trunc(f) ||
+ f < float64(math.MinInt) || f >= -float64(math.MinInt) {
+ return 0, false
+ }
+ return int(f), true
+}
diff --git a/gremlin-go/driver/client_test.go b/gremlin-go/driver/client_test.go
index 9a911a3774..559df4b026 100644
--- a/gremlin-go/driver/client_test.go
+++ b/gremlin-go/driver/client_test.go
@@ -22,6 +22,7 @@ package gremlingo
import (
"crypto/tls"
"errors"
+ "math"
"testing"
"github.com/stretchr/testify/assert"
@@ -406,15 +407,56 @@ func captureRequest(t *testing.T) (*Client,
*RequestMessage) {
}
func TestRequestOptionsLanguage(t *testing.T) {
- t.Run("With(language) reaches the request", func(t *testing.T) {
- builder := applyOptionsConfig(new(RequestOptionsBuilder),
- map[string]interface{}{"language": "gremlin-groovy"})
- assert.Equal(t, "gremlin-groovy", MakeStringRequest("g.V()",
"g", builder.Create()).Fields["language"])
- })
-
t.Run("SubmitWithOptions sends the per-request language", func(t
*testing.T) {
client, captured := captureRequest(t)
_, _ = client.SubmitWithOptions("g.V()",
new(RequestOptionsBuilder).SetLanguage("gremlin-groovy").Create())
assert.Equal(t, "gremlin-groovy", captured.Fields["language"])
})
}
+
+func TestApplyOptionsConfig(t *testing.T) {
+ t.Run("applies each supported option from a traversal's with()", func(t
*testing.T) {
+ opts := applyOptionsConfig(new(RequestOptionsBuilder),
map[string]interface{}{
+ "timeoutMillis": 1234,
+ "batchSize": 10,
+ "materializeProperties": "tokens",
+ "bulkResults": false,
+ "language": "gremlin-groovy",
+ }).Create()
+ assert.Equal(t, 1234, opts.timeoutMillis)
+ assert.Equal(t, 10, opts.batchSize)
+ assert.Equal(t, "tokens", opts.materializeProperties)
+ assert.Equal(t, false, *opts.bulkResults)
+ assert.Equal(t, "gremlin-groovy", opts.language)
+ })
+
+ t.Run("ignores a value whose type does not match the option", func(t
*testing.T) {
+ // a mistyped with() value is dropped rather than panicking;
the server applies its own default
+ opts := applyOptionsConfig(new(RequestOptionsBuilder),
map[string]interface{}{
+ "timeoutMillis": "not-a-number",
+ "language": 42,
+ }).Create()
+ assert.Equal(t, 0, opts.timeoutMillis)
+ assert.Equal(t, "", opts.language)
+ })
+
+ t.Run("coerces non-int numeric values for numeric options", func(t
*testing.T) {
+ // with() carries raw interface{} values, so a numeric option
may arrive as int64 or float64;
+ // these are converted to int rather than dropped
+ for _, value := range []interface{}{500, int32(500),
int64(500), float64(500)} {
+ opts := applyOptionsConfig(new(RequestOptionsBuilder),
+ map[string]interface{}{"timeoutMillis": value,
"batchSize": value}).Create()
+ assert.Equal(t, 500, opts.timeoutMillis)
+ assert.Equal(t, 500, opts.batchSize)
+ }
+ })
+
+ t.Run("ignores numeric values that are not exact integers", func(t
*testing.T) {
+ // fractional, non-finite and out-of-range values cannot be
represented exactly as an int
+ for _, value := range []interface{}{500.5, math.NaN(),
math.Inf(1), uint64(math.MaxUint64)} {
+ opts := applyOptionsConfig(new(RequestOptionsBuilder),
+ map[string]interface{}{"timeoutMillis":
value}).Create()
+ assert.Equal(t, 0, opts.timeoutMillis, "value %v",
value)
+ }
+ })
+}