This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new aaafd49564 fix(go/driver/internal/driverbase): fall back to SetOption 
for unhandled typed options (#4924)
aaafd49564 is described below

commit aaafd49564b3fddf15282e68f2075c730e3f4d35
Author: ump45nose <[email protected]>
AuthorDate: Fri Oct 30 11:39:24 2026 +0800

    fix(go/driver/internal/driverbase): fall back to SetOption for unhandled 
typed options (#4924)
    
    Following the proposal in the issue, the fallback lives in the
    `database`, `connection` and `statement` wrappers, since they hold the
    driver's implementation (`DatabaseImplBase` can't dispatch to the
    driver's own `SetOption`).
    
    This PR was prepared with AI assistance.
    
    Closes #4913.
    
    ---------
    
    Signed-off-by: Vincent <[email protected]>
---
 go/driver/internal/driverbase/connection.go        |  21 +++
 go/driver/internal/driverbase/database.go          |  24 +++
 go/driver/internal/driverbase/error.go             |   8 +
 go/driver/internal/driverbase/statement.go         |  21 +++
 .../internal/driverbase/typed_options_test.go      | 163 +++++++++++++++++++++
 5 files changed, 237 insertions(+)

diff --git a/go/driver/internal/driverbase/connection.go 
b/go/driver/internal/driverbase/connection.go
index 977ae02b30..e6f33733f1 100644
--- a/go/driver/internal/driverbase/connection.go
+++ b/go/driver/internal/driverbase/connection.go
@@ -24,6 +24,7 @@ import (
        "errors"
        "fmt"
        "log/slog"
+       "strconv"
        "strings"
 
        "github.com/apache/arrow-adbc/go/adbc"
@@ -562,6 +563,26 @@ func (cnxn *connection) SetOption(key string, val string) 
error {
        return cnxn.ConnectionImpl.SetOption(key, val)
 }
 
+// SetOptionInt sets an integer option, falling back to SetOption with the
+// formatted value when the driver does not handle key as an integer.
+func (cnxn *connection) SetOptionInt(key string, val int64) error {
+       err := cnxn.ConnectionImpl.SetOptionInt(key, val)
+       if isNotImplemented(err) {
+               return cnxn.SetOption(key, strconv.FormatInt(val, 10))
+       }
+       return err
+}
+
+// SetOptionDouble sets a double option, falling back to SetOption with the
+// formatted value when the driver does not handle key as a double.
+func (cnxn *connection) SetOptionDouble(key string, val float64) error {
+       err := cnxn.ConnectionImpl.SetOptionDouble(key, val)
+       if isNotImplemented(err) {
+               return cnxn.SetOption(key, strconv.FormatFloat(val, 'g', -1, 
64))
+       }
+       return err
+}
+
 func (cnxn *connection) GetInfo(ctx context.Context, infoCodes 
[]adbc.InfoCode) (array.RecordReader, error) {
        if cnxn.driverInfoPreparer != nil {
                if err := cnxn.driverInfoPreparer.PrepareDriverInfo(ctx, 
infoCodes); err != nil {
diff --git a/go/driver/internal/driverbase/database.go 
b/go/driver/internal/driverbase/database.go
index 50693a66bd..ba9847659f 100644
--- a/go/driver/internal/driverbase/database.go
+++ b/go/driver/internal/driverbase/database.go
@@ -23,6 +23,7 @@ import (
        "io"
        "log/slog"
        "os"
+       "strconv"
        "strings"
        "sync"
        "time"
@@ -252,6 +253,29 @@ func NewDatabase(impl DatabaseImpl) Database {
        }
 }
 
+// SetOptionInt sets an integer option. If the driver does not handle key as
+// an integer, the value is formatted and passed to SetOption instead, since
+// the typed setters are a convenience over the string representation.
+// Drivers that only override SetOption and parse numbers themselves then
+// accept integer options too (e.g. typed values from connection profiles).
+func (db *database) SetOptionInt(key string, val int64) error {
+       err := db.DatabaseImpl.SetOptionInt(key, val)
+       if isNotImplemented(err) {
+               return db.SetOption(key, strconv.FormatInt(val, 10))
+       }
+       return err
+}
+
+// SetOptionDouble sets a double option, falling back to SetOption like
+// SetOptionInt when the driver does not handle key as a double.
+func (db *database) SetOptionDouble(key string, val float64) error {
+       err := db.DatabaseImpl.SetOptionDouble(key, val)
+       if isNotImplemented(err) {
+               return db.SetOption(key, strconv.FormatFloat(val, 'g', -1, 64))
+       }
+       return err
+}
+
 func (db *database) SetLogger(logger *slog.Logger) {
        if logger != nil {
                db.Base().Logger = logger
diff --git a/go/driver/internal/driverbase/error.go 
b/go/driver/internal/driverbase/error.go
index cb60c17e40..f1f2a1a866 100644
--- a/go/driver/internal/driverbase/error.go
+++ b/go/driver/internal/driverbase/error.go
@@ -18,6 +18,7 @@
 package driverbase
 
 import (
+       "errors"
        "fmt"
 
        "github.com/apache/arrow-adbc/go/adbc"
@@ -35,3 +36,10 @@ func (helper *ErrorHelper) Errorf(code adbc.Status, message 
string, format ...in
                Msg:  fmt.Sprintf("[%s] %s", helper.DriverName, msg),
        }
 }
+
+// isNotImplemented reports whether err is an ADBC error with status
+// NotImplemented, which is how the *ImplBase types reject unknown options.
+func isNotImplemented(err error) bool {
+       adbcErr, ok := errors.AsType[adbc.Error](err)
+       return ok && adbcErr.Code == adbc.StatusNotImplemented
+}
diff --git a/go/driver/internal/driverbase/statement.go 
b/go/driver/internal/driverbase/statement.go
index 5fa43da916..1f964d3139 100644
--- a/go/driver/internal/driverbase/statement.go
+++ b/go/driver/internal/driverbase/statement.go
@@ -19,6 +19,7 @@ package driverbase
 
 import (
        "context"
+       "strconv"
        "strings"
 
        "github.com/apache/arrow-adbc/go/adbc"
@@ -71,6 +72,26 @@ func NewStatement(impl StatementImpl) Statement {
        }
 }
 
+// SetOptionInt sets an integer option, falling back to SetOption with the
+// formatted value when the driver does not handle key as an integer.
+func (st *statement) SetOptionInt(key string, value int64) error {
+       err := st.StatementImpl.SetOptionInt(key, value)
+       if isNotImplemented(err) {
+               return st.SetOption(key, strconv.FormatInt(value, 10))
+       }
+       return err
+}
+
+// SetOptionDouble sets a double option, falling back to SetOption with the
+// formatted value when the driver does not handle key as a double.
+func (st *statement) SetOptionDouble(key string, value float64) error {
+       err := st.StatementImpl.SetOptionDouble(key, value)
+       if isNotImplemented(err) {
+               return st.SetOption(key, strconv.FormatFloat(value, 'g', -1, 
64))
+       }
+       return err
+}
+
 func (st *StatementImplBase) SetOption(key, value string) error {
        switch strings.ToLower(key) {
        case adbc.OptionKeyTelemetryTraceParent:
diff --git a/go/driver/internal/driverbase/typed_options_test.go 
b/go/driver/internal/driverbase/typed_options_test.go
new file mode 100644
index 0000000000..537c35e09f
--- /dev/null
+++ b/go/driver/internal/driverbase/typed_options_test.go
@@ -0,0 +1,163 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package driverbase_test
+
+import (
+       "context"
+       "strings"
+       "testing"
+
+       "github.com/apache/arrow-adbc/go/adbc"
+       "github.com/apache/arrow-adbc/go/adbc/validation"
+       "github.com/apache/arrow-adbc/go/driver/internal/driverbase"
+       "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/stretchr/testify/require"
+)
+
+const (
+       // Keys with this prefix are only understood by the string setters,
+       // like a driver that parses numbers from SetOption itself.
+       stringOnlyKeyPrefix = "string."
+       // This key is handled natively by the integer setter.
+       nativeIntKey = "native.int"
+)
+
+// typedOptionsDatabase, typedOptionsConnection and typedOptionsStatement
+// record the options they accept so tests can see which setter got a value.
+type typedOptionsDatabase struct {
+       driverbase.DatabaseImplBase
+       opts map[string]string
+}
+
+func (d *typedOptionsDatabase) SetOption(key, value string) error {
+       if strings.HasPrefix(key, stringOnlyKeyPrefix) {
+               d.opts[key] = value
+               return nil
+       }
+       return d.DatabaseImplBase.SetOption(key, value)
+}
+
+func (d *typedOptionsDatabase) SetOptionInt(key string, value int64) error {
+       if key == nativeIntKey {
+               d.opts[key] = "native"
+               return nil
+       }
+       return d.DatabaseImplBase.SetOptionInt(key, value)
+}
+
+func (d *typedOptionsDatabase) Open(ctx context.Context) (adbc.Connection, 
error) {
+       cnxn := &typedOptionsConnection{
+               ConnectionImplBase: 
driverbase.NewConnectionImplBase(&d.DatabaseImplBase),
+               opts:               d.opts,
+       }
+       return driverbase.NewConnectionBuilder(cnxn).Connection(), nil
+}
+
+type typedOptionsConnection struct {
+       driverbase.ConnectionImplBase
+       opts map[string]string
+}
+
+func (c *typedOptionsConnection) SetOption(key, value string) error {
+       if strings.HasPrefix(key, stringOnlyKeyPrefix) {
+               c.opts[key] = value
+               return nil
+       }
+       return c.ConnectionImplBase.SetOption(key, value)
+}
+
+func (c *typedOptionsConnection) NewStatement() (adbc.Statement, error) {
+       stmt := &typedOptionsStatement{
+               statement: statement{StatementImplBase: 
driverbase.NewStatementImplBase(c.Base(), c.ErrorHelper)},
+               opts:      c.opts,
+       }
+       return driverbase.NewStatement(stmt), nil
+}
+
+type typedOptionsStatement struct {
+       statement
+       opts map[string]string
+}
+
+func (s *typedOptionsStatement) SetOption(key, value string) error {
+       if strings.HasPrefix(key, stringOnlyKeyPrefix) {
+               s.opts[key] = value
+               return nil
+       }
+       return s.StatementImplBase.SetOption(key, value)
+}
+
+func (s *typedOptionsStatement) Close() error { return nil }
+
+func TestTypedOptionsFallBackToStringSetter(t *testing.T) {
+       ctx := context.Background()
+       alloc := memory.NewCheckedAllocator(memory.DefaultAllocator)
+       defer alloc.AssertSize(t, 0)
+
+       drv := 
driverbase.NewDriverImplBase(driverbase.DefaultDriverInfo("MockDriver"), alloc)
+       dbBase, err := driverbase.NewDatabaseImplBase(ctx, &drv, 
driverbase.TracingOptions{})
+       require.NoError(t, err)
+       opts := map[string]string{}
+       db := driverbase.NewDatabase(&typedOptionsDatabase{DatabaseImplBase: 
dbBase, opts: opts})
+       defer validation.CheckedClose(t, db)
+
+       cnxn, err := db.Open(ctx)
+       require.NoError(t, err)
+       defer validation.CheckedClose(t, cnxn)
+
+       stmt, err := cnxn.NewStatement()
+       require.NoError(t, err)
+       defer validation.CheckedClose(t, stmt)
+
+       for _, tc := range []struct {
+               name   string
+               getset adbc.GetSetOptions
+       }{
+               {"database", db},
+               {"connection", cnxn.(adbc.GetSetOptions)},
+               {"statement", stmt.(adbc.GetSetOptions)},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       intKey := stringOnlyKeyPrefix + tc.name + ".int"
+                       require.NoError(t, tc.getset.SetOptionInt(intKey, -3))
+                       require.Equal(t, "-3", opts[intKey])
+
+                       doubleKey := stringOnlyKeyPrefix + tc.name + ".double"
+                       require.NoError(t, tc.getset.SetOptionDouble(doubleKey, 
0.25))
+                       require.Equal(t, "0.25", opts[doubleKey])
+                       require.NoError(t, tc.getset.SetOptionDouble(doubleKey, 
1e21))
+                       require.Equal(t, "1e+21", opts[doubleKey])
+
+                       // Keys that neither setter understands are still 
rejected.
+                       var adbcErr adbc.Error
+                       err := tc.getset.SetOptionInt(OptionKeyUnrecognized, 1)
+                       require.ErrorAs(t, err, &adbcErr)
+                       require.Equal(t, adbc.StatusNotImplemented, 
adbcErr.Code)
+                       require.Contains(t, adbcErr.Msg, 
"'"+OptionKeyUnrecognized+"'")
+
+                       err = tc.getset.SetOptionDouble(OptionKeyUnrecognized, 
1.5)
+                       require.ErrorAs(t, err, &adbcErr)
+                       require.Equal(t, adbc.StatusNotImplemented, 
adbcErr.Code)
+               })
+       }
+
+       // A typed setter that handles the key itself is not routed through
+       // SetOption.
+       require.NoError(t, db.SetOptionInt(nativeIntKey, 7))
+       require.Equal(t, "native", opts[nativeIntKey])
+}

Reply via email to