db: add VSC
This commit is contained in:
51
pkg/db/vsc.go
Normal file
51
pkg/db/vsc.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const separator = "\x1f"
|
||||||
|
|
||||||
|
type VSC []string
|
||||||
|
|
||||||
|
// MarshalJSON replaces `nil` with `[]` for hygiene.
|
||||||
|
func (vs VSC) MarshalJSON() ([]byte, error) {
|
||||||
|
if vs == nil {
|
||||||
|
vs = VSC{}
|
||||||
|
}
|
||||||
|
return json.Marshal([]string(vs)) //nolint: wrapcheck // Passthrough
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vs VSC) Value() (driver.Value, error) {
|
||||||
|
fields := make([]string, len(vs))
|
||||||
|
for i, v := range vs {
|
||||||
|
fields[i] = strings.ReplaceAll(v, separator, "")
|
||||||
|
}
|
||||||
|
return strings.Join(fields, separator), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrWrongVSCType = errors.New("incompatible DB type for VSC")
|
||||||
|
|
||||||
|
func (vs *VSC) Scan(src any) error {
|
||||||
|
var s string
|
||||||
|
switch v := src.(type) {
|
||||||
|
case string:
|
||||||
|
s = v
|
||||||
|
case []byte:
|
||||||
|
s = string(v)
|
||||||
|
case nil:
|
||||||
|
*vs = nil
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrWrongVSCType
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
*vs = VSC{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*vs = strings.Split(s, separator)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
48
pkg/db/vsc_test.go
Normal file
48
pkg/db/vsc_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package db_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"git.offline-twitter.com/offline-labs/gas-stack/pkg/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVSCDBRoundTrip(t *testing.T) {
|
||||||
|
conn, err := sqlx.Open("sqlite3", ":memory:")
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, conn.Close()) })
|
||||||
|
|
||||||
|
_, err = conn.Exec(`create table vsc_items (id integer primary key, items text not null)`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type vscRow struct {
|
||||||
|
ID int `db:"id"`
|
||||||
|
Items db.VSC `db:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, want := range []vscRow{
|
||||||
|
{ID: 1, Items: db.VSC{"email"}},
|
||||||
|
{ID: 2, Items: db.VSC{"email", "profile"}},
|
||||||
|
{ID: 3, Items: db.VSC{}},
|
||||||
|
} {
|
||||||
|
_, err = conn.NamedExec(`insert into vsc_items (id, items) values (:id, :items)`, want)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var got vscRow
|
||||||
|
require.NoError(t, conn.Get(&got, `select id, items from vsc_items where id = ?`, want.ID))
|
||||||
|
require.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
t.Run("nil gets upgrade to empty list", func(t *testing.T) {
|
||||||
|
want := vscRow{ID: 4, Items: nil}
|
||||||
|
_, err = conn.NamedExec(`insert into vsc_items (id, items) values (:id, :items)`, want)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var got vscRow
|
||||||
|
require.NoError(t, conn.Get(&got, `select id, items from vsc_items where id = ?`, want.ID))
|
||||||
|
require.NotNil(t, got.Items)
|
||||||
|
require.Equal(t, db.VSC{}, got.Items)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user