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