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)
+               }
+       })
+}

Reply via email to