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 9296949f8 feat(go/adbc/sqldriver): support reflecting more types 
(#4416)
9296949f8 is described below

commit 9296949f8e1470cf14653ce51e536eead273a782
Author: David Li <[email protected]>
AuthorDate: Sun Jun 21 16:50:00 2026 -0700

    feat(go/adbc/sqldriver): support reflecting more types (#4416)
    
    Closes #4185.
    
    Assisted-by: GPT-5.5 <[email protected]>
---
 go/adbc/go.mod                 |   2 +-
 go/adbc/sqldriver/driver.go    |  16 +-
 go/adbc/sqldriver/type_test.go | 356 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 370 insertions(+), 4 deletions(-)

diff --git a/go/adbc/go.mod b/go/adbc/go.mod
index 71de12873..4fcace770 100644
--- a/go/adbc/go.mod
+++ b/go/adbc/go.mod
@@ -21,7 +21,7 @@ go 1.25.0
 
 // Only require 1.25, but prefer 1.26 to build to pick up latest CVE fixes in
 // Go itself.
-toolchain go1.26.1
+toolchain go1.26.4
 
 require (
        github.com/apache/arrow-go/v18 v18.6.0
diff --git a/go/adbc/sqldriver/driver.go b/go/adbc/sqldriver/driver.go
index 394be6a42..5ab33dcc1 100644
--- a/go/adbc/sqldriver/driver.go
+++ b/go/adbc/sqldriver/driver.go
@@ -33,6 +33,7 @@ import (
        "github.com/apache/arrow-adbc/go/adbc"
        "github.com/apache/arrow-go/v18/arrow"
        "github.com/apache/arrow-go/v18/arrow/array"
+       "github.com/apache/arrow-go/v18/arrow/array/arreflect"
        "github.com/apache/arrow-go/v18/arrow/decimal128"
        "github.com/apache/arrow-go/v18/arrow/decimal256"
        "github.com/apache/arrow-go/v18/arrow/memory"
@@ -716,6 +717,8 @@ func (r *rows) Next(dest []driver.Value) error {
                        dest[i] = col.Value(int(r.curRow))
                case *array.Float64:
                        dest[i] = col.Value(int(r.curRow))
+               case *array.Float16:
+                       dest[i] = col.Value(int(r.curRow))
                case *array.String:
                        dest[i] = col.Value(int(r.curRow))
                case *array.LargeString:
@@ -738,11 +741,18 @@ func (r *rows) Next(dest []driver.Value) error {
                        dest[i] = col.Value(int(r.curRow))
                case *array.Decimal256:
                        dest[i] = col.Value(int(r.curRow))
+               case *array.MonthInterval:
+                       dest[i] = col.Value(int(r.curRow))
+               case *array.DayTimeInterval:
+                       dest[i] = col.Value(int(r.curRow))
+               case *array.MonthDayNanoInterval:
+                       dest[i] = col.Value(int(r.curRow))
                default:
-                       return &adbc.Error{
-                               Code: adbc.StatusNotImplemented,
-                               Msg:  "not yet implemented populating from 
columns of type " + col.DataType().String(),
+                       v, err := arreflect.AtAny(col, int(r.curRow))
+                       if err != nil {
+                               return err
                        }
+                       dest[i] = v
                }
        }
 
diff --git a/go/adbc/sqldriver/type_test.go b/go/adbc/sqldriver/type_test.go
new file mode 100644
index 000000000..fd308db90
--- /dev/null
+++ b/go/adbc/sqldriver/type_test.go
@@ -0,0 +1,356 @@
+// 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 sqldriver_test
+
+import (
+       "context"
+       "database/sql"
+       "errors"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/arrow-adbc/go/adbc"
+       "github.com/apache/arrow-adbc/go/adbc/sqldriver"
+       "github.com/apache/arrow-adbc/go/adbc/validation"
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/array"
+       "github.com/apache/arrow-go/v18/arrow/float16"
+       "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/stretchr/testify/require"
+)
+
+var errNotImplemented = errors.New("demo; not implemented")
+
+type typeDriver struct {
+       payload arrow.RecordBatch
+}
+
+func (td *typeDriver) NewDatabase(opts map[string]string) (adbc.Database, 
error) {
+       return &typeDatabase{td}, nil
+}
+
+type typeDatabase struct {
+       driver *typeDriver
+}
+
+func (td *typeDatabase) SetOptions(map[string]string) error { return nil }
+func (td *typeDatabase) Open(ctx context.Context) (adbc.Connection, error) {
+       return &typeConnection{td.driver}, nil
+}
+func (td *typeDatabase) Close() error { return nil }
+
+type typeConnection struct {
+       driver *typeDriver
+}
+
+func (tc *typeConnection) NewStatement() (adbc.Statement, error) {
+       return &typeStatement{tc.driver}, nil
+}
+func (tc *typeConnection) GetInfo(context.Context, []adbc.InfoCode) 
(array.RecordReader, error) {
+       return nil, errNotImplemented
+}
+func (tc *typeConnection) GetObjects(context.Context, adbc.ObjectDepth, 
*string, *string, *string, *string, []string) (array.RecordReader, error) {
+       return nil, errNotImplemented
+}
+func (tc *typeConnection) GetTableSchema(context.Context, *string, *string, 
string) (*arrow.Schema, error) {
+       return nil, errNotImplemented
+}
+func (tc *typeConnection) GetTableTypes(context.Context) (array.RecordReader, 
error) {
+       return nil, errNotImplemented
+}
+func (tc *typeConnection) Commit(context.Context) error   { return 
errNotImplemented }
+func (tc *typeConnection) Rollback(context.Context) error { return 
errNotImplemented }
+func (tc *typeConnection) Close() error                   { return nil }
+func (tc *typeConnection) ReadPartition(context.Context, []byte) 
(array.RecordReader, error) {
+       return nil, errNotImplemented
+}
+
+type typeStatement struct {
+       driver *typeDriver
+}
+
+func (ts *typeStatement) ExecuteQuery(context.Context) (array.RecordReader, 
int64, error) {
+       if ts.driver.payload == nil {
+               return nil, -1, errors.New("no payload set")
+       }
+       rr, _ := array.NewRecordReader(ts.driver.payload.Schema(), 
[]arrow.RecordBatch{ts.driver.payload})
+       return rr, -1, nil
+}
+func (ts *typeStatement) Close() error                                         
{ return nil }
+func (ts *typeStatement) SetOption(string, string) error                       
{ return nil }
+func (ts *typeStatement) SetSqlQuery(string) error                             
{ return nil }
+func (ts *typeStatement) ExecuteUpdate(context.Context) (int64, error)         
{ return -1, errNotImplemented }
+func (ts *typeStatement) Prepare(context.Context) error                        
{ return nil }
+func (ts *typeStatement) SetSubstraitPlan([]byte) error                        
{ return nil }
+func (ts *typeStatement) Bind(context.Context, arrow.RecordBatch) error        
{ return nil }
+func (ts *typeStatement) BindStream(context.Context, array.RecordReader) error 
{ return nil }
+func (ts *typeStatement) GetParameterSchema() (*arrow.Schema, error)           
{ return nil, errNotImplemented }
+func (ts *typeStatement) ExecutePartitions(context.Context) (*arrow.Schema, 
adbc.Partitions, int64, error) {
+       return nil, adbc.Partitions{}, -1, errNotImplemented
+}
+
+type testCase struct {
+       ty       arrow.DataType
+       json     string
+       expected []any
+}
+
+func ptr[T any](v T) *T { return &v }
+
+func TestArrowTypes(t *testing.T) {
+       date := time.Date(2026, time.June, 19, 0, 0, 0, 0, time.UTC)
+       timestamp := time.Date(2026, time.June, 19, 1, 2, 3, 4_005_006, 
time.UTC)
+       timeOfDay := time.Date(1970, time.January, 1, 1, 2, 3, 4_005_006, 
time.UTC)
+
+       testCases := []testCase{
+               {
+                       ty:       arrow.PrimitiveTypes.Int8,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{int8(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Int16,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{int16(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Int32,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{int32(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Int64,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{int64(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Uint8,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{uint8(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Uint16,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{uint16(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Uint32,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{uint32(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Uint64,
+                       json:     `[{"a": 1}, {"a": null}]`,
+                       expected: []any{uint64(1), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Float32,
+                       json:     `[{"a": 1.5}, {"a": null}]`,
+                       expected: []any{float32(1.5), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Float64,
+                       json:     `[{"a": 1.5}, {"a": null}]`,
+                       expected: []any{float64(1.5), nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Date32,
+                       json:     `[{"a": "2026-06-19"}, {"a": null}]`,
+                       expected: []any{date, nil},
+               },
+               {
+                       ty:       arrow.PrimitiveTypes.Date64,
+                       json:     `[{"a": "2026-06-19"}, {"a": null}]`,
+                       expected: []any{date, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Boolean,
+                       json:     `[{"a": true}, {"a": null}]`,
+                       expected: []any{true, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Date32,
+                       json:     `[{"a": "2026-06-19"}, {"a": null}]`,
+                       expected: []any{date, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Date64,
+                       json:     `[{"a": "2026-06-19"}, {"a": null}]`,
+                       expected: []any{date, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.DayTimeInterval,
+                       json:     `[{"a": {"days": 1, "milliseconds": 2}}, 
{"a": null}]`,
+                       expected: []any{arrow.DayTimeInterval{Days: 1, 
Milliseconds: 2}, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Duration_s,
+                       json:     `[{"a": "5s"}, {"a": null}]`,
+                       expected: []any{5 * time.Second, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Duration_ms,
+                       json:     `[{"a": "5ms"}, {"a": null}]`,
+                       expected: []any{5 * time.Millisecond, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Duration_us,
+                       json:     `[{"a": "5us"}, {"a": null}]`,
+                       expected: []any{5 * time.Microsecond, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Duration_ns,
+                       json:     `[{"a": "5ns"}, {"a": null}]`,
+                       expected: []any{5 * time.Nanosecond, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Float16,
+                       json:     `[{"a": 1.5}, {"a": null}]`,
+                       expected: []any{float16.New(1.5), nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.MonthInterval,
+                       json:     `[{"a": {"months": 3}}, {"a": null}]`,
+                       expected: []any{arrow.MonthInterval(3), nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Time32s,
+                       json:     `[{"a": "01:02:03"}, {"a": null}]`,
+                       expected: []any{timeOfDay.Truncate(time.Second), nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Time32ms,
+                       json:     `[{"a": "01:02:03.004"}, {"a": null}]`,
+                       expected: []any{timeOfDay.Truncate(time.Millisecond), 
nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Time64us,
+                       json:     `[{"a": "01:02:03.004005"}, {"a": null}]`,
+                       expected: []any{timeOfDay.Truncate(time.Microsecond), 
nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Time64ns,
+                       json:     `[{"a": "01:02:03.004005006"}, {"a": null}]`,
+                       expected: []any{timeOfDay, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Timestamp_s,
+                       json:     `[{"a": "2026-06-19T01:02:03Z"}, {"a": 
null}]`,
+                       expected: []any{timestamp.Truncate(time.Second), nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Timestamp_ms,
+                       json:     `[{"a": "2026-06-19T01:02:03.004Z"}, {"a": 
null}]`,
+                       expected: []any{timestamp.Truncate(time.Millisecond), 
nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Timestamp_us,
+                       json:     `[{"a": "2026-06-19T01:02:03.004005Z"}, {"a": 
null}]`,
+                       expected: []any{timestamp.Truncate(time.Microsecond), 
nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.Timestamp_ns,
+                       json:     `[{"a": "2026-06-19T01:02:03.004005006Z"}, 
{"a": null}]`,
+                       expected: []any{timestamp, nil},
+               },
+               {
+                       ty:       arrow.FixedWidthTypes.MonthDayNanoInterval,
+                       json:     `[{"a": {"months": 1, "days": 2, 
"nanoseconds": 3}}, {"a": null}]`,
+                       expected: []any{arrow.MonthDayNanoInterval{Months: 1, 
Days: 2, Nanoseconds: 3}, nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.Binary,
+                       json:     `[{"a": "AQID"}, {"a": null}]`,
+                       expected: []any{[]byte{1, 2, 3}, nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.String,
+                       json:     `[{"a": "value"}, {"a": null}]`,
+                       expected: []any{"value", nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.LargeBinary,
+                       json:     `[{"a": "AQID"}, {"a": null}]`,
+                       expected: []any{[]byte{1, 2, 3}, nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.LargeString,
+                       json:     `[{"a": "value"}, {"a": null}]`,
+                       expected: []any{"value", nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.BinaryView,
+                       json:     `[{"a": "AQID"}, {"a": null}]`,
+                       expected: []any{[]byte{1, 2, 3}, nil},
+               },
+               {
+                       ty:       arrow.BinaryTypes.StringView,
+                       json:     `[{"a": "value"}, {"a": null}]`,
+                       expected: []any{"value", nil},
+               },
+               {
+                       // XXX: arreflect's fallback drops the null
+                       ty:       arrow.ListOf(arrow.PrimitiveTypes.Int32),
+                       json:     `[{"a": [1, null, 2]}, {"a": null}]`,
+                       expected: []any{[]int32{1, 0, 2}, nil},
+               },
+               {
+                       ty:   arrow.StructOf(arrow.Field{Name: "a", Type: 
arrow.PrimitiveTypes.Int32, Nullable: true}, arrow.Field{Name: "b", Type: 
arrow.BinaryTypes.String, Nullable: true}),
+                       json: `[{"a": {"a": 1, "b": "value"}}, {"a": null}]`,
+                       expected: []any{struct {
+                               A *int32  `arrow:"a"`
+                               B *string `arrow:"b"`
+                       }{A: ptr(int32(1)), B: ptr("value")}, nil},
+               },
+       }
+
+       drv := &typeDriver{}
+       const driverName = "adbc-arrow-types"
+       sql.Register(driverName, sqldriver.Driver{drv})
+
+       for _, tc := range testCases {
+               t.Run(tc.ty.String(), func(t *testing.T) {
+                       mem := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                       defer mem.AssertSize(t, 0)
+                       schema := arrow.NewSchema([]arrow.Field{{Name: "a", 
Type: tc.ty, Nullable: true}}, nil)
+                       batch, _, err := array.RecordFromJSON(mem, schema, 
strings.NewReader(tc.json))
+                       require.NoError(t, err)
+                       defer batch.Release()
+
+                       drv.payload = batch
+
+                       db, err := sql.Open(driverName, "a=b")
+                       require.NoError(t, err)
+                       defer validation.CheckedClose(t, db)
+
+                       rows, err := db.Query("")
+                       require.NoError(t, err)
+                       defer validation.CheckedClose(t, rows)
+
+                       values := make([]any, 0, 2)
+                       for rows.Next() {
+                               var v any
+                               require.NoError(t, rows.Scan(&v))
+                               values = append(values, v)
+                       }
+                       require.NoError(t, rows.Err())
+                       require.Equal(t, tc.expected, values)
+               })
+       }
+}

Reply via email to