Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions go/adbc/driver/internal/driverbase/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package driverbase

import (
"context"
"fmt"
"runtime/debug"
"strings"

Expand All @@ -36,12 +37,10 @@ var (

func init() {
if info, ok := debug.ReadBuildInfo(); ok {
infoDriverVersion = buildDriverVersion(info)
for _, s := range info.Settings {
switch s.Key {
case "vcs.modified":
if s.Value == "true" {
infoDriverVersion += "-dev"
}
if s.Key == "vcs.modified" && s.Value == "true" && infoDriverVersion == "" {
infoDriverVersion = UnknownVersion
}
}
for _, dep := range info.Deps {
Expand All @@ -54,6 +53,51 @@ func init() {
}
}

func buildDriverVersion(info *debug.BuildInfo) string {
if info == nil {
return ""
}

version := strings.TrimSpace(info.Main.Version)
if version == "" || version == "(devel)" {
version = ""
}

var revision string
var modified bool
for _, s := range info.Settings {
switch s.Key {
case "vcs.revision":
revision = shortRevision(s.Value)
case "vcs.modified":
modified = s.Value == "true"
}
}

switch {
case version != "" && revision != "":
version = fmt.Sprintf("%s+%s", version, revision)
case version == "" && revision != "":
version = revision
case version == "":
return ""
}

if modified {
version += "-dev"
}

return version
}

func shortRevision(revision string) string {
revision = strings.TrimSpace(revision)
if len(revision) > 12 {
return revision[:12]
}
return revision
}

// DriverImpl is an interface that drivers implement to provide
// vendor-specific functionality.
type DriverImpl interface {
Expand Down
69 changes: 69 additions & 0 deletions go/adbc/driver/internal/driverbase/driver_buildinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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

import (
"runtime/debug"
"testing"

"github.com/stretchr/testify/require"
)

func TestBuildDriverVersion(t *testing.T) {
t.Run("release version with revision", func(t *testing.T) {
info := &debug.BuildInfo{
Main: debug.Module{Version: "1.2.3"},
Settings: []debug.BuildSetting{
{Key: "vcs.revision", Value: "1234567890abcdef"},
},
}

require.Equal(t, "1.2.3+1234567890ab", buildDriverVersion(info))
})

t.Run("devel revision dirty", func(t *testing.T) {
info := &debug.BuildInfo{
Main: debug.Module{Version: "(devel)"},
Settings: []debug.BuildSetting{
{Key: "vcs.revision", Value: "abcdef1234567890"},
{Key: "vcs.modified", Value: "true"},
},
}

require.Equal(t, "abcdef123456-dev", buildDriverVersion(info))
})

t.Run("release version dirty without revision", func(t *testing.T) {
info := &debug.BuildInfo{
Main: debug.Module{Version: "2.0.0"},
Settings: []debug.BuildSetting{
{Key: "vcs.modified", Value: "true"},
},
}

require.Equal(t, "2.0.0-dev", buildDriverVersion(info))
})

t.Run("no useful metadata", func(t *testing.T) {
info := &debug.BuildInfo{
Main: debug.Module{Version: "(devel)"},
}

require.Empty(t, buildDriverVersion(info))
})
}
185 changes: 97 additions & 88 deletions go/adbc/driver/internal/driverbase/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,44 +94,24 @@ func TestDefaultDriver(t *testing.T) {

// This is what the driverbase provided GetInfo result should look like out of the box,
// with one custom setting registered at initialization
expectedGetInfoTable, err := array.TableFromJSON(alloc, adbc.GetInfoSchema, []string{`[
{
"info_name": 0,
"info_value": [0, "MockDriver"]
},
{
"info_name": 1,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 2,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 100,
"info_value": [0, "ADBC MockDriver Driver - Go"]
},
{
"info_name": 101,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 102,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 103,
"info_value": [2, 1001000]
},
{
"info_name": 10001,
"info_value": [0, "my custom info"]
}
]`})
require.NoError(t, err)
defer expectedGetInfoTable.Release()

require.Truef(t, array.TableEqual(expectedGetInfoTable, getInfoTable), "expected: %s\ngot: %s", expectedGetInfoTable, getInfoTable)
infoValues := getInfoValuesFromTable(t, getInfoTable)
require.Equal(t, map[adbc.InfoCode]any{
adbc.InfoVendorName: "MockDriver",
adbc.InfoVendorVersion: driverbase.UnknownVersion,
adbc.InfoVendorArrowVersion: driverbase.UnknownVersion,
adbc.InfoDriverName: "ADBC MockDriver Driver - Go",
adbc.InfoDriverADBCVersion: int64(adbc.AdbcVersion1_1_0),
adbc.InfoCode(10_001): "my custom info",
}, filterInfoValues(infoValues,
adbc.InfoVendorName,
adbc.InfoVendorVersion,
adbc.InfoVendorArrowVersion,
adbc.InfoDriverName,
adbc.InfoDriverADBCVersion,
adbc.InfoCode(10_001),
))
require.NotEmpty(t, infoValues[adbc.InfoDriverVersion])
require.NotEmpty(t, infoValues[adbc.InfoDriverArrowVersion])

_, err = cnxn.GetObjects(ctx, adbc.ObjectDepthAll, nil, nil, nil, nil, nil)
require.Error(t, err)
Expand Down Expand Up @@ -223,56 +203,30 @@ func TestCustomizedDriver(t *testing.T) {
// - the default DriverInfo set at initialization
// - the DriverInfo set once in the NewDriver constructor
// - the DriverInfo set dynamically when GetInfo is called by implementing DriverInfoPreparer interface
expectedGetInfoTable, err := array.TableFromJSON(alloc, adbc.GetInfoSchema, []string{`[
{
"info_name": 0,
"info_value": [0, "MockDriver"]
},
{
"info_name": 1,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 2,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 3,
"info_value": [1, true]
},
{
"info_name": 4,
"info_value": [1, false]
},
{
"info_name": 100,
"info_value": [0, "ADBC MockDriver Driver - Go"]
},
{
"info_name": 101,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 102,
"info_value": [0, "(unknown or development build)"]
},
{
"info_name": 103,
"info_value": [2, 1001000]
},
{
"info_name": 10001,
"info_value": [0, "my custom info"]
},
{
"info_name": 10002,
"info_value": [0, "this was fetched dynamically"]
}
]`})
require.NoError(t, err)
defer expectedGetInfoTable.Release()

require.Truef(t, array.TableEqual(expectedGetInfoTable, getInfoTable), "expected: %s\ngot: %s", expectedGetInfoTable, getInfoTable)
infoValues := getInfoValuesFromTable(t, getInfoTable)
require.Equal(t, map[adbc.InfoCode]any{
adbc.InfoVendorName: "MockDriver",
adbc.InfoVendorVersion: driverbase.UnknownVersion,
adbc.InfoVendorArrowVersion: driverbase.UnknownVersion,
adbc.InfoVendorSql: true,
adbc.InfoVendorSubstrait: false,
adbc.InfoDriverName: "ADBC MockDriver Driver - Go",
adbc.InfoDriverADBCVersion: int64(adbc.AdbcVersion1_1_0),
adbc.InfoCode(10_001): "my custom info",
adbc.InfoCode(10_002): "this was fetched dynamically",
}, filterInfoValues(infoValues,
adbc.InfoVendorName,
adbc.InfoVendorVersion,
adbc.InfoVendorArrowVersion,
adbc.InfoVendorSql,
adbc.InfoVendorSubstrait,
adbc.InfoDriverName,
adbc.InfoDriverADBCVersion,
adbc.InfoCode(10_001),
adbc.InfoCode(10_002),
))
require.NotEmpty(t, infoValues[adbc.InfoDriverVersion])
require.NotEmpty(t, infoValues[adbc.InfoDriverArrowVersion])

dbObjects, err := cnxn.GetObjects(ctx, adbc.ObjectDepthAll, nil, nil, nil, nil, nil)
require.NoError(t, err)
Expand Down Expand Up @@ -769,6 +723,61 @@ func messagesEqual(expected, actual logMessage) bool {
return true
}

func filterInfoValues(values map[adbc.InfoCode]any, codes ...adbc.InfoCode) map[adbc.InfoCode]any {
filtered := make(map[adbc.InfoCode]any, len(codes))
for _, code := range codes {
filtered[code] = values[code]
}
return filtered
}

func getInfoValuesFromTable(t *testing.T, table arrow.Table) map[adbc.InfoCode]any {
t.Helper()

values := make(map[adbc.InfoCode]any)
codeChunks := table.Column(0).Data().Chunks()
valueChunks := table.Column(1).Data().Chunks()
require.Len(t, codeChunks, len(valueChunks))

for chunkIdx := range codeChunks {
codeArr, ok := codeChunks[chunkIdx].(*array.Uint32)
require.True(t, ok)
unionArr, ok := valueChunks[chunkIdx].(*array.DenseUnion)
require.True(t, ok)

offsets := unionArr.RawValueOffsets()
for row := 0; row < codeArr.Len(); row++ {
code := adbc.InfoCode(codeArr.Value(row))
childID := unionArr.ChildID(row)
offset := int(offsets[row])
child := unionArr.Field(childID)
if child.IsNull(offset) {
values[code] = nil
continue
}

switch childID {
case 0:
strArray, ok := child.(*array.String)
require.True(t, ok)
values[code] = strArray.Value(offset)
case 1:
boolArray, ok := child.(*array.Boolean)
require.True(t, ok)
values[code] = boolArray.Value(offset)
case 2:
intArray, ok := child.(*array.Int64)
require.True(t, ok)
values[code] = intArray.Value(offset)
default:
t.Fatalf("unexpected dense union child id %d for info code %d", childID, code)
}
}
}

return values
}

func tableFromRecordReader(rdr array.RecordReader) arrow.Table {
defer rdr.Release()

Expand Down