Skip to content

Commit

Permalink
support optional Nullable interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mitranim committed Nov 22, 2021
1 parent 20ba7e9 commit 6eddb4d
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 36 deletions.
13 changes: 10 additions & 3 deletions sqlb.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ type Sparse interface {
Filters struct fields. Used by `Sparse` and `ParseOpt`. Implemented by
`TagFilter`.
*/
type Filter interface {
AllowField(r.StructField) bool
}
type Filter interface{ AllowField(r.StructField) bool }

/*
Optional interface that allows `sqlb` to determine if a given value is null,
allowing some expressions to generate `is null` / `is not null` clauses. Not
actually required; nils of Go nilable types are automatically considered null,
and `sqlb` falls back on encoding the value via `driver.Valuer`. This interface
is supported for additional flexibility and efficiency.
*/
type Nullable interface{ IsNull() bool }
12 changes: 7 additions & 5 deletions sqlb_dict.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,15 @@ func (self StructDict) GotNamed(key string) (interface{}, bool) {
In our benchmarks, making a struct dict is about 15 times faster than a normal
dict (100ns vs 1500ns for 12 fields and 12 methods), but accessing various
fields and methods is about 25 times slower on average(5000ns vs 200ns for 12
fields and 12 methods). The total numbers are close enough, and small enough,
to justify both, depending on the use case.
fields and methods is about 25 times slower on average (5000ns vs 200ns for
12 fields and 12 methods). When using only fields without methods, the
access time numbers are much closer (700ns vs 100ns for 12 fields). The
total numbers are close enough, and small enough, to justify both, depending
on the use case.
Compared to using `reflect.Value.FieldByName` and `reflect.Value.MethodByName`
every time, using a cached dict with field and method indexes improves average
access performance by about 3 times in our benchmarks.
every time, using a cached dict with field and method indexes improves
average access performance by about 3 times in our benchmarks.
*/

val := valueDeref(self[0])
Expand Down
4 changes: 0 additions & 4 deletions sqlb_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,6 @@ func (self Eq) AppendLhs(text []byte, args []interface{}) ([]byte, []interface{}

func (self Eq) AppendRhs(text []byte, args []interface{}) ([]byte, []interface{}) {
bui := Bui{text, args}

// Questionable. Could be avoided by using `is not distinct from`, but at the
// time of writing, that operator doesn't work on indexes in PG, resulting in
// atrocious performance.
val := norm(self[1])

if val == nil {
Expand Down
6 changes: 3 additions & 3 deletions sqlb_ord_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,14 @@ func (self OrdsParser) UnmarshalJSON(src []byte) (err error) {
defer rec(&err)
var vals []string
try(json.Unmarshal(src, &vals))
self.ptr().parseSlice(vals)
self.noescape().parseSlice(vals)
return
}

// See `(*ParserOrds).ParseSlice` for docs.
func (self OrdsParser) ParseSlice(src []string) (err error) {
defer rec(&err)
self.ptr().parseSlice(src)
self.noescape().parseSlice(src)
return
}

Expand Down Expand Up @@ -185,7 +185,7 @@ func (self *OrdsParser) filter(field r.StructField) bool {
}

// Prevents a weird spurious escape that shows up in benchmarks.
func (self *OrdsParser) ptr() *OrdsParser {
func (self *OrdsParser) noescape() *OrdsParser {
return (*OrdsParser)(noescape(unsafe.Pointer(self)))
}

Expand Down
27 changes: 23 additions & 4 deletions sqlb_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,14 +211,33 @@ func rec(ptr *error) {
panic(val)
}

/*
Questionable. Could be avoided by using `is [not] distinct from` which works for
both nulls and non-nulls, but at the time of writing, that operator doesn't
work on indexes in PG, resulting in atrocious performance.
*/
func norm(val interface{}) interface{} {
impl, _ := val.(driver.Valuer)
if impl != nil {
val, err := impl.Value()
val = normNil(val)
if val == nil {
return nil
}

nullable, _ := val.(Nullable)
if nullable != nil {
if nullable.IsNull() {
return nil
}
return val
}

valuer, _ := val.(driver.Valuer)
if valuer != nil {
val, err := valuer.Value()
try(err)
return val
}
return normNil(val)

return val
}

func normNil(val interface{}) interface{} {
Expand Down
29 changes: 12 additions & 17 deletions t_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"
"testing"
"time"
"unsafe"
u "unsafe"
)

type Internal struct {
Expand Down Expand Up @@ -196,8 +196,14 @@ actual (simple):
func is(t testing.TB, exp, act interface{}) {
t.Helper()

expIface := *(*iface)(unsafe.Pointer(&exp))
actIface := *(*iface)(unsafe.Pointer(&act))
// nolint:structcheck
type iface struct {
typ u.Pointer
dat u.Pointer
}

expIface := *(*iface)(u.Pointer(&exp))
actIface := *(*iface)(u.Pointer(&act))

if expIface != actIface {
t.Fatalf(`
Expand All @@ -217,27 +223,16 @@ actual (simple):
}
}

// nolint:structcheck
type iface struct {
typ unsafe.Pointer
dat unsafe.Pointer
}

func notEq(t testing.TB, exp, act interface{}) {
t.Helper()
if r.DeepEqual(exp, act) {
fatalNotEq(t, exp, act)
}
}

func fatalNotEq(t testing.TB, exp, act interface{}) {
t.Helper()
t.Fatalf(`
t.Fatalf(`
unexpected equality (detailed):
%#[1]v
unexpected equality (simple):
%[1]v
`, exp, act)
`, exp, act)
}
}

func panics(t testing.TB, msg string, fun func()) {
Expand Down

0 comments on commit 6eddb4d

Please sign in to comment.