diff --git a/README.md b/README.md index e3fb934..592e518 100644 --- a/README.md +++ b/README.md @@ -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`: diff --git a/sql.go b/sql.go new file mode 100644 index 0000000..bd9343e --- /dev/null +++ b/sql.go @@ -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 +} diff --git a/sql_test.go b/sql_test.go new file mode 100644 index 0000000..eeeec57 --- /dev/null +++ b/sql_test.go @@ -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) + }) +}