diff --git a/go/adbc/driver/internal/driverbase/driver.go b/go/adbc/driver/internal/driverbase/driver.go index 019bcbc6ff..8e0bdd57cc 100644 --- a/go/adbc/driver/internal/driverbase/driver.go +++ b/go/adbc/driver/internal/driverbase/driver.go @@ -22,6 +22,7 @@ package driverbase import ( "context" + "fmt" "runtime/debug" "strings" @@ -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 { @@ -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 { diff --git a/go/adbc/driver/internal/driverbase/driver_buildinfo_test.go b/go/adbc/driver/internal/driverbase/driver_buildinfo_test.go new file mode 100644 index 0000000000..35c6c7e67b --- /dev/null +++ b/go/adbc/driver/internal/driverbase/driver_buildinfo_test.go @@ -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)) + }) +} diff --git a/go/adbc/driver/internal/driverbase/driver_test.go b/go/adbc/driver/internal/driverbase/driver_test.go index ab8efb4c80..95ae931ef9 100644 --- a/go/adbc/driver/internal/driverbase/driver_test.go +++ b/go/adbc/driver/internal/driverbase/driver_test.go @@ -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) @@ -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) @@ -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()