From 43b1e0c39171e207ff37a1e0d4844483bd6f3775 Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Sat, 4 Jan 2025 16:13:45 +0100 Subject: add SQL support --- option/option.go | 50 +++++++++++++++++++++++++++++++++++++++++++++----- option/option_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) (limited to 'option') diff --git a/option/option.go b/option/option.go index 1ea2e79..0f19bb5 100644 --- a/option/option.go +++ b/option/option.go @@ -113,9 +113,13 @@ package option import ( + "database/sql" + "database/sql/driver" "encoding/json" "fmt" "iter" + + "github.com/majewsky/gg/internal/hack" ) // Option is a type that contains either one or no instances of T. @@ -289,6 +293,16 @@ func (o Option[T]) Xor(other Option[T]) Option[T] { //////////////////////////////////////////////////////////////////////////////// // formatting/marshalling support +// These are static assertions that Option implements the intended interfaces. +// (The YAML interfaces are not checked because we don't want to add 3rd-party lib deps here.) +var ( + _ fmt.Formatter = Option[bool]{} + _ sql.Scanner = &Option[bool]{} + _ driver.Valuer = Option[bool]{} + _ json.Marshaler = Option[bool]{} + _ json.Unmarshaler = &Option[bool]{} +) + // Format implements the fmt.Formatter interface. // // If there is a contained value, it will be formatted as if it was given directly. @@ -311,7 +325,33 @@ type yamlMarshaler interface { MarshalYAML() (any, error) } -// MarshalJSON implements the json.Marshaler interface. +// Scan implements the database/sql.Scanner interface. +func (o *Option[T]) Scan(src any) error { + switch src := src.(type) { + case nil: + *o = None[T]() + return nil + default: + var data T + err := hack.ConvertAssign(&data, src) + if err != nil { + return err + } + *o = Some(data) + return nil + } +} + +// Value implements the database/sql/driver.Valuer interface. +func (o Option[T]) Value() (driver.Value, error) { + if o.isSome { + return driver.DefaultParameterConverter.ConvertValue(o.value) + } else { + return nil, nil + } +} + +// MarshalJSON implements the encoding/json.Marshaler interface. func (o Option[T]) MarshalJSON() ([]byte, error) { if o.isSome { return json.Marshal(o.value) @@ -320,7 +360,7 @@ func (o Option[T]) MarshalJSON() ([]byte, error) { } } -// UnmarshalJSON implements the json.Unmarshaler interface. +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. func (o *Option[T]) UnmarshalJSON(buf []byte) error { var data *T err := json.Unmarshal(buf, &data) @@ -331,7 +371,7 @@ func (o *Option[T]) UnmarshalJSON(buf []byte) error { return nil } -// MarshalYAML implements the yaml.Marshaler interface. +// MarshalYAML implements the yaml.Marshaler interface from gopkg.in/yaml.v2 and v3. func (o Option[T]) MarshalYAML() (any, error) { if o.isSome { // If we just return o.value directly here, MarshalYAML will not be called @@ -347,9 +387,9 @@ func (o Option[T]) MarshalYAML() (any, error) { } } -// UnmarshalYAML implements the yaml.Unmarshaler interface. +// UnmarshalYAML implements the yaml.Unmarshaler interface from gopkg.in/yaml.v2. // -// This function signature is compatible with both v2 and v3 of github.com/go-yaml/yaml, +// gopkg.in/yaml.v3 supports this interface via backwards-compatibility, // so we intentionally do not use the v3-only signature that refers to the yaml.Node type. func (o *Option[T]) UnmarshalYAML(unmarshal func(any) error) error { var data *T diff --git a/option/option_test.go b/option/option_test.go index 2bac4f5..80e8e84 100644 --- a/option/option_test.go +++ b/option/option_test.go @@ -146,6 +146,35 @@ func TestFormat(t *testing.T) { AssertEqual(t, fmt.Sprintf("value is %#v", listOfOptions), "value is []option.Option[int]{, 42}") } +func TestMarshalSQL(t *testing.T) { + value, err := None[string]().Value() + AssertEqual(t, err, nil) + AssertEqual(t, value, nil) + + value, err = Some("hello").Value() + AssertEqual(t, err, nil) + AssertEqual(t, value, "hello") + + _, err = Some(struct{}{}).Value() + AssertEqual(t, err.Error(), "unsupported type struct {}, a struct") +} + +func TestUnmarshalSQL(t *testing.T) { + var o1 Option[string] + err := o1.Scan(nil) + AssertEqual(t, err, nil) + AssertEqual(t, o1, None[string]()) + + var o2 Option[string] + err = o2.Scan("hello") + AssertEqual(t, err, nil) + AssertEqual(t, o2, Some("hello")) + + var o3 Option[struct{}] + err = o3.Scan("hello") + AssertEqual(t, err.Error(), "unsupported Scan, storing driver.Value type string into type *struct {}") +} + func TestMarshalAndUnmarshalJSON(t *testing.T) { type payload struct { N1 Option[int] `json:"n1"` -- cgit v1.2.3