Skip to content

Commit

Permalink
Implement named args for prepared statements
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenafamo committed Jul 10, 2023
1 parent db7bade commit 07eed8a
Show file tree
Hide file tree
Showing 14 changed files with 605 additions and 147 deletions.
9 changes: 9 additions & 0 deletions debug_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ type debugExecutor struct {
exec Executor
}

func (d debugExecutor) PrepareContext(ctx context.Context, query string) (Statement, error) {
d.printer.PrintQuery(query)
if p, ok := d.exec.(Preparer); ok {
return p.PrepareContext(ctx, query)
}

return nil, fmt.Errorf("executor does not implement Preparer")
}

func (d debugExecutor) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
d.printer.PrintQuery(query, args...)
return d.exec.ExecContext(ctx, query, args...)
Expand Down
3 changes: 2 additions & 1 deletion dialect/mysql/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stephenafamo/bob/dialect/mysql/sm"
"github.com/stephenafamo/bob/dialect/mysql/um"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
)

Expand All @@ -22,7 +23,7 @@ func NewTable[T any, Tset any](tableName string, uniques ...[]string) *Table[T,
func NewTablex[T any, Tslice ~[]T, Tset any](tableName string, uniques ...[]string) *Table[T, Tslice, Tset] {
var zeroSet Tset

setMapping := internal.GetMappings(reflect.TypeOf(zeroSet))
setMapping := mappings.GetMappings(reflect.TypeOf(zeroSet))

view, mappings := newView[T, Tslice](tableName)
t := &Table[T, Tslice, Tset]{
Expand Down
9 changes: 5 additions & 4 deletions dialect/mysql/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/stephenafamo/bob/dialect/mysql/dialect"
"github.com/stephenafamo/bob/dialect/mysql/sm"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
"github.com/stephenafamo/scan"
)
Expand All @@ -25,17 +26,17 @@ func NewViewx[T any, Tslice ~[]T](tableName string) *View[T, Tslice] {
func newView[T any, Tslice ~[]T](tableName string) (*View[T, Tslice], internal.Mapping) {
var zero T

mappings := internal.GetMappings(reflect.TypeOf(zero))
mapping := mappings.GetMappings(reflect.TypeOf(zero))
alias := tableName
allCols := mappings.Columns(alias)
allCols := internal.MappingCols(mapping, alias)

return &View[T, Tslice]{
name: tableName,
alias: alias,
mapping: mappings,
mapping: mapping,
allCols: allCols,
scanner: scan.StructMapper[T](),
}, mappings
}, mapping
}

type View[T any, Tslice ~[]T] struct {
Expand Down
3 changes: 2 additions & 1 deletion dialect/psql/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stephenafamo/bob/dialect/psql/im"
"github.com/stephenafamo/bob/dialect/psql/um"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
)

Expand All @@ -21,7 +22,7 @@ func NewTable[T any, Tset any](schema, tableName string) *Table[T, []T, Tset] {
func NewTablex[T any, Tslice ~[]T, Tset any](schema, tableName string) *Table[T, Tslice, Tset] {
var zeroSet Tset

setMapping := internal.GetMappings(reflect.TypeOf(zeroSet))
setMapping := mappings.GetMappings(reflect.TypeOf(zeroSet))
view, mappings := newView[T, Tslice](schema, tableName)
return &Table[T, Tslice, Tset]{
View: view,
Expand Down
9 changes: 5 additions & 4 deletions dialect/psql/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stephenafamo/bob/dialect/psql/dialect"
"github.com/stephenafamo/bob/dialect/psql/sm"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
"github.com/stephenafamo/scan"
)
Expand All @@ -32,22 +33,22 @@ func NewViewx[T any, Tslice ~[]T](schema, tableName string) *View[T, Tslice] {
func newView[T any, Tslice ~[]T](schema, tableName string) (*View[T, Tslice], internal.Mapping) {
var zero T

mappings := internal.GetMappings(reflect.TypeOf(zero))
mapping := mappings.GetMappings(reflect.TypeOf(zero))
alias := tableName
if schema != "" {
alias = fmt.Sprintf("%s.%s", schema, tableName)
}

allCols := mappings.Columns(alias)
allCols := internal.MappingCols(mapping, alias)

return &View[T, Tslice]{
schema: schema,
name: tableName,
alias: alias,
mapping: mappings,
mapping: mapping,
allCols: allCols,
scanner: scan.StructMapper[T](),
}, mappings
}, mapping
}

type View[T any, Tslice ~[]T] struct {
Expand Down
3 changes: 2 additions & 1 deletion dialect/sqlite/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stephenafamo/bob/dialect/sqlite/im"
"github.com/stephenafamo/bob/dialect/sqlite/um"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
)

Expand All @@ -21,7 +22,7 @@ func NewTable[T any, Tset any](schema, tableName string) *Table[T, []T, Tset] {
func NewTablex[T any, Tslice ~[]T, Tset any](schema, tableName string) *Table[T, Tslice, Tset] {
var zeroSet Tset

setMapping := internal.GetMappings(reflect.TypeOf(zeroSet))
setMapping := mappings.GetMappings(reflect.TypeOf(zeroSet))
view, mappings := newView[T, Tslice](schema, tableName)
return &Table[T, Tslice, Tset]{
View: view,
Expand Down
9 changes: 5 additions & 4 deletions dialect/sqlite/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
"github.com/stephenafamo/bob/dialect/sqlite/sm"
"github.com/stephenafamo/bob/internal"
"github.com/stephenafamo/bob/internal/mappings"
"github.com/stephenafamo/bob/orm"
"github.com/stephenafamo/scan"
)
Expand All @@ -32,22 +33,22 @@ func NewViewx[T any, Tslice ~[]T](schema, tableName string) *View[T, Tslice] {
func newView[T any, Tslice ~[]T](schema, tableName string) (*View[T, Tslice], internal.Mapping) {
var zero T

mappings := internal.GetMappings(reflect.TypeOf(zero))
mapping := mappings.GetMappings(reflect.TypeOf(zero))
alias := tableName
if schema != "" {
alias = fmt.Sprintf("%s.%s", schema, tableName)
}

allCols := mappings.Columns(alias)
allCols := internal.MappingCols(mapping, alias)

return &View[T, Tslice]{
schema: schema,
name: tableName,
alias: alias,
mapping: mappings,
mapping: mapping,
allCols: allCols,
scanner: scan.StructMapper[T](),
}, mappings
}, mapping
}

type View[T any, Tslice ~[]T] struct {
Expand Down
117 changes: 117 additions & 0 deletions internal/mappings/mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package mappings

import (
"reflect"
"regexp"
"strings"
)

var (
matchFirstCapRe = regexp.MustCompile("(.)([A-Z][a-z]+)")
matchAllCapRe = regexp.MustCompile("([a-z0-9])([A-Z])")
)

type colProperties struct {
Name string
IsPK bool
IsGenerated bool
AutoIncrement bool
}

func getColProperties(tag string) colProperties {
var p colProperties
if tag == "" {
return p
}

parts := strings.Split(tag, ",")
p.Name = parts[0]

for _, part := range parts[1:] {
switch part {
case "pk":
p.IsPK = true
case "generated":
p.IsGenerated = true
case "autoincr":
p.AutoIncrement = true
}
}

return p
}

type Mapping struct {
All []string
PKs []string
NonPKs []string
Generated []string
NonGenerated []string
AutoIncrement []string
}

func GetMappings(typ reflect.Type) Mapping {
c := Mapping{}

if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}

if typ.Kind() != reflect.Struct {
return c
}

c.All = make([]string, typ.NumField())
c.PKs = make([]string, typ.NumField())
c.NonPKs = make([]string, typ.NumField())
c.Generated = make([]string, typ.NumField())
c.NonGenerated = make([]string, typ.NumField())
c.AutoIncrement = make([]string, typ.NumField())

// Go through the struct fields and populate the map.
// Recursively go into any child structs, adding a prefix where necessary
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)

// Don't consider unexported fields
if !field.IsExported() {
continue
}

// Skip columns that have the tag "-"
tag := field.Tag.Get("db")
if tag == "-" {
continue
}

if tag == "" {
tag = snakeCase(field.Name)
}

props := getColProperties(tag)

c.All[field.Index[0]] = props.Name
if props.IsPK {
c.PKs[field.Index[0]] = props.Name
} else {
c.NonPKs[field.Index[0]] = props.Name
}
if props.IsGenerated {
c.Generated[field.Index[0]] = props.Name
} else {
c.NonGenerated[field.Index[0]] = props.Name
}
if props.AutoIncrement {
c.AutoIncrement[field.Index[0]] = props.Name
}
}

return c
}

// snakeCaseFieldFunc is a NameMapperFunc that maps struct field to snake case.
func snakeCase(str string) string {
snake := matchFirstCapRe.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCapRe.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}
Loading

0 comments on commit 07eed8a

Please sign in to comment.