Skip to content

Commit aa0ac68

Browse files
authored
Merge pull request #23 from bugst/sql
Added SQL methods to implement `sql.Scanner` and `driver.Valuer` interfaces
2 parents bf57265 + 1f2e312 commit aa0ac68

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ The `Version` and `RelaxedVersion` provides optimized `MarshalBinary`/`Unmarshal
9696

9797
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.
9898

99+
## SQL support
100+
101+
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.
102+
99103
## Lexicographic sortable strings that keeps semantic versioning order
100104

101105
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`:

sql.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// Copyright 2018-2025 Cristian Maglie. All rights reserved.
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
//
6+
7+
package semver
8+
9+
import (
10+
"database/sql/driver"
11+
"fmt"
12+
)
13+
14+
// Scan implements the sql.Scanner interface
15+
func (v *Version) Scan(value interface{}) error {
16+
raw, ok := value.(string)
17+
if !ok {
18+
return fmt.Errorf("incompatible type %T for Version", value)
19+
}
20+
21+
v.raw = raw
22+
v.bytes = []byte(v.raw)
23+
if err := parse(v); err != nil {
24+
return err
25+
}
26+
return nil
27+
}
28+
29+
// Value implements the driver.Valuer interface
30+
func (v *Version) Value() (driver.Value, error) {
31+
return v.raw, nil
32+
}
33+
34+
// Scan implements the sql.Scanner interface
35+
func (v *RelaxedVersion) Scan(value interface{}) error {
36+
raw, ok := value.(string)
37+
if !ok {
38+
return fmt.Errorf("incompatible type %T for Version", value)
39+
}
40+
41+
res := ParseRelaxed(raw)
42+
*v = *res
43+
return nil
44+
}
45+
46+
// Value implements the driver.Valuer interface
47+
func (v *RelaxedVersion) Value() (driver.Value, error) {
48+
if v.version != nil {
49+
return v.version.raw, nil
50+
}
51+
return string(v.customversion), nil
52+
}

sql_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Copyright 2018-2025 Cristian Maglie. All rights reserved.
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
//
6+
7+
package semver
8+
9+
import (
10+
"database/sql/driver"
11+
"testing"
12+
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestSQLDriverInterfaces(t *testing.T) {
17+
18+
t.Run("Version", func(t *testing.T) {
19+
// Test Version Scan/Value
20+
v := &Version{}
21+
if _, ok := interface{}(v).(driver.Valuer); !ok {
22+
t.Error("Version does not implement driver.Valuer")
23+
}
24+
if _, ok := interface{}(v).(driver.Valuer); !ok {
25+
t.Error("Version does not implement driver.Valuer")
26+
}
27+
require.Error(t, v.Scan(1))
28+
require.Error(t, v.Scan(nil))
29+
require.Error(t, v.Scan("123asdf"))
30+
require.NoError(t, v.Scan("1.2.3-rc.1+build.2"))
31+
require.Equal(t, "1.2.3-rc.1+build.2", v.String())
32+
d, err := v.Value()
33+
require.NoError(t, err)
34+
require.Equal(t, "1.2.3-rc.1+build.2", d)
35+
})
36+
37+
t.Run("RelaxedVersion", func(t *testing.T) {
38+
// Test RelaxedVersion Scan/Value
39+
rv := &RelaxedVersion{}
40+
if _, ok := interface{}(rv).(driver.Valuer); !ok {
41+
t.Error("RelaxedVersion does not implement driver.Valuer")
42+
}
43+
if _, ok := interface{}(rv).(driver.Valuer); !ok {
44+
t.Error("RelaxedVersion does not implement driver.Valuer")
45+
}
46+
require.Error(t, rv.Scan(1))
47+
require.Error(t, rv.Scan(nil))
48+
require.NoError(t, rv.Scan("4.5.6-rc.1+build.2"))
49+
require.Empty(t, rv.customversion)
50+
require.NotNil(t, rv.version)
51+
require.Equal(t, "4.5.6-rc.1+build.2", rv.String())
52+
rd, err := rv.Value()
53+
require.NoError(t, err)
54+
require.Equal(t, "4.5.6-rc.1+build.2", rd)
55+
56+
require.NoError(t, rv.Scan("a1-2.2-3.3"))
57+
require.NotEmpty(t, rv.customversion)
58+
require.Nil(t, rv.version)
59+
require.Equal(t, "a1-2.2-3.3", rv.String())
60+
rd2, err := rv.Value()
61+
require.NoError(t, err)
62+
require.Equal(t, "a1-2.2-3.3", rd2)
63+
})
64+
}

0 commit comments

Comments
 (0)