Skip to content

Commit

Permalink
Merge pull request #23 from bugst/sql
Browse files Browse the repository at this point in the history
Added SQL methods to implement `sql.Scanner` and `driver.Valuer` interfaces
  • Loading branch information
cmaglie authored Feb 25, 2025
2 parents bf57265 + 1f2e312 commit aa0ac68
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ The `Version` and `RelaxedVersion` provides optimized `MarshalBinary`/`Unmarshal

The `Version` and `RelaxedVersion` have the YAML un/marshaler implemented so they can be YAML decoded/encoded with the excellent `gopkg.in/yaml.v3` library.

## SQL support

The `Version` and `RelaxedVersion` types provides the `sql.Scanner` and `driver.Valuer` interfaces. Those objects could be directly used in SQL queries, their value will be mapped into a string field.

## Lexicographic sortable strings that keeps semantic versioning order

The `Version` and `RelaxedVersion` objects provides the `SortableString()` method that returns a string with a peculiar property: the alphanumeric sorting of two `Version.SortableString()` matches the semantic versioning ordering of the underling `Version` objects. In other words, given two `Version` object `a` and `b`:
Expand Down
52 changes: 52 additions & 0 deletions sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright 2018-2025 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package semver

import (
"database/sql/driver"
"fmt"
)

// Scan implements the sql.Scanner interface
func (v *Version) Scan(value interface{}) error {
raw, ok := value.(string)
if !ok {
return fmt.Errorf("incompatible type %T for Version", value)
}

v.raw = raw
v.bytes = []byte(v.raw)
if err := parse(v); err != nil {
return err
}
return nil
}

// Value implements the driver.Valuer interface
func (v *Version) Value() (driver.Value, error) {
return v.raw, nil
}

// Scan implements the sql.Scanner interface
func (v *RelaxedVersion) Scan(value interface{}) error {
raw, ok := value.(string)
if !ok {
return fmt.Errorf("incompatible type %T for Version", value)
}

res := ParseRelaxed(raw)
*v = *res
return nil
}

// Value implements the driver.Valuer interface
func (v *RelaxedVersion) Value() (driver.Value, error) {
if v.version != nil {
return v.version.raw, nil
}
return string(v.customversion), nil
}
64 changes: 64 additions & 0 deletions sql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Copyright 2018-2025 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package semver

import (
"database/sql/driver"
"testing"

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

func TestSQLDriverInterfaces(t *testing.T) {

t.Run("Version", func(t *testing.T) {
// Test Version Scan/Value
v := &Version{}
if _, ok := interface{}(v).(driver.Valuer); !ok {
t.Error("Version does not implement driver.Valuer")
}
if _, ok := interface{}(v).(driver.Valuer); !ok {
t.Error("Version does not implement driver.Valuer")
}
require.Error(t, v.Scan(1))
require.Error(t, v.Scan(nil))
require.Error(t, v.Scan("123asdf"))
require.NoError(t, v.Scan("1.2.3-rc.1+build.2"))
require.Equal(t, "1.2.3-rc.1+build.2", v.String())
d, err := v.Value()
require.NoError(t, err)
require.Equal(t, "1.2.3-rc.1+build.2", d)
})

t.Run("RelaxedVersion", func(t *testing.T) {
// Test RelaxedVersion Scan/Value
rv := &RelaxedVersion{}
if _, ok := interface{}(rv).(driver.Valuer); !ok {
t.Error("RelaxedVersion does not implement driver.Valuer")
}
if _, ok := interface{}(rv).(driver.Valuer); !ok {
t.Error("RelaxedVersion does not implement driver.Valuer")
}
require.Error(t, rv.Scan(1))
require.Error(t, rv.Scan(nil))
require.NoError(t, rv.Scan("4.5.6-rc.1+build.2"))
require.Empty(t, rv.customversion)
require.NotNil(t, rv.version)
require.Equal(t, "4.5.6-rc.1+build.2", rv.String())
rd, err := rv.Value()
require.NoError(t, err)
require.Equal(t, "4.5.6-rc.1+build.2", rd)

require.NoError(t, rv.Scan("a1-2.2-3.3"))
require.NotEmpty(t, rv.customversion)
require.Nil(t, rv.version)
require.Equal(t, "a1-2.2-3.3", rv.String())
rd2, err := rv.Value()
require.NoError(t, err)
require.Equal(t, "a1-2.2-3.3", rd2)
})
}

0 comments on commit aa0ac68

Please sign in to comment.