Skip to content

Commit

Permalink
Idiomatic error handling (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmzane authored Mar 28, 2024
1 parent 4d0c41a commit d60dbd1
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 38 deletions.
23 changes: 11 additions & 12 deletions builq.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ func (c Columns) Prefixed(p string) string {
type Builder struct {
parts []string
args [][]any
err error // the first error occurred while building the query.
counter int // a counter for numbered placeholders ($1, $2, ...).
placeholder byte // a placeholder used to build the query.
sep byte // a separator between Addf calls.
debug bool // is it DebugBuild call to fill with args.
counter int // a counter for numbered placeholders ($1, $2, ...).
placeholder byte // a placeholder used to build the query.
sep byte // a separator between Addf calls.
debug bool // is it DebugBuild call to fill with args.
}

// OnelineBuilder behaves like Builder but result is 1 line.
Expand Down Expand Up @@ -87,14 +86,13 @@ func (b *Builder) Addf(format constString, args ...any) *Builder {

// Build the query and arguments.
func (b *Builder) Build() (query string, args []any, err error) {
query, args = b.build()
return query, args, b.err
return b.build()
}

// DebugBuild the query, good for debugging but not for REAL usage.
func (b *Builder) DebugBuild() (query string) {
b.debug = true
query, _ = b.build()
query, _, _ = b.build()
b.debug = false
return query
}
Expand All @@ -110,7 +108,7 @@ func (b *Builder) addf(format constString, args ...any) *Builder {
return b
}

func (b *Builder) build() (_ string, _ []any) {
func (b *Builder) build() (string, []any, error) {
var query strings.Builder
// TODO: better default (sum of parts + est len of indexes)
query.Grow(100)
Expand All @@ -121,13 +119,14 @@ func (b *Builder) build() (_ string, _ []any) {
format := b.parts[i]
args := b.args[i]

err := b.write(&query, &resArgs, format, args...)
b.setErr(err)
if err := b.write(&query, &resArgs, format, args...); err != nil {
return "", nil, err
}
}

// drop last separators for clarity.
q := strings.TrimRight(query.String(), string(b.sep))
return q, resArgs
return q, resArgs, nil
}

var (
Expand Down
70 changes: 44 additions & 26 deletions write.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ func (b *Builder) write(sb *strings.Builder, resArgs *[]any, s string, args ...a

arg := args[argID]
s = s[1:]
b.writeArg(sb, resArgs, verb, arg)
if err := b.writeArg(sb, resArgs, verb, arg); err != nil {
return err
}

case '+', '#':
isBatch := verb == '#'
Expand All @@ -56,9 +58,13 @@ func (b *Builder) write(sb *strings.Builder, resArgs *[]any, s string, args ...a
s = s[1:]

if isBatch {
b.writeBatch(sb, resArgs, verb, arg)
if err := b.writeBatch(sb, resArgs, verb, arg); err != nil {
return err
}
} else {
b.writeSlice(sb, resArgs, verb, arg)
if err := b.writeSlice(sb, resArgs, verb, arg); err != nil {
return err
}
}

default:
Expand All @@ -79,30 +85,44 @@ func (b *Builder) write(sb *strings.Builder, resArgs *[]any, s string, args ...a
}
}

func (b *Builder) writeBatch(sb *strings.Builder, resArgs *[]any, verb byte, arg any) {
for i, arg := range b.asSlice(arg) {
func (b *Builder) writeBatch(sb *strings.Builder, resArgs *[]any, verb byte, arg any) error {
args, err := b.asSlice(arg)
if err != nil {
return err
}
for i, arg := range args {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteByte('(')
b.writeSlice(sb, resArgs, verb, arg)
if err := b.writeSlice(sb, resArgs, verb, arg); err != nil {
return err
}
sb.WriteByte(')')
}
return nil
}

func (b *Builder) writeSlice(sb *strings.Builder, resArgs *[]any, verb byte, arg any) {
for i, arg := range b.asSlice(arg) {
func (b *Builder) writeSlice(sb *strings.Builder, resArgs *[]any, verb byte, arg any) error {
args, err := b.asSlice(arg)
if err != nil {
return err
}
for i, arg := range args {
if i > 0 {
sb.WriteString(", ")
}
b.writeArg(sb, resArgs, verb, arg)
if err := b.writeArg(sb, resArgs, verb, arg); err != nil {
return err
}
}
return nil
}

func (b *Builder) writeArg(sb *strings.Builder, resArgs *[]any, verb byte, arg any) {
func (b *Builder) writeArg(sb *strings.Builder, resArgs *[]any, verb byte, arg any) error {
if b.debug {
b.writeDebug(sb, arg)
return
return nil
}

var isSimple bool
Expand All @@ -128,21 +148,25 @@ func (b *Builder) writeArg(sb *strings.Builder, resArgs *[]any, verb byte, arg a
}
case 'd':
isSimple = true
b.assertNumber(arg)
if err := b.assertNumber(arg); err != nil {
return err
}
fmt.Fprint(sb, arg)
}

// ok to have many simple placeholders
if isSimple {
return
return nil
}

switch {
case b.placeholder == 0:
b.placeholder = verb
case b.placeholder != verb:
b.setErr(errMixedPlaceholders)
return errMixedPlaceholders
}

return nil
}

func (b *Builder) writeDebug(sb *strings.Builder, arg any) {
Expand Down Expand Up @@ -172,33 +196,27 @@ func (b *Builder) writeDebug(sb *strings.Builder, arg any) {
}
}

func (b *Builder) asSlice(v any) []any {
func (b *Builder) asSlice(v any) ([]any, error) {
value := reflect.ValueOf(v)

if value.Kind() != reflect.Slice {
b.setErr(errNonSliceArgument)
return nil
return nil, errNonSliceArgument
}

res := make([]any, value.Len())
for i := 0; i < value.Len(); i++ {
res[i] = value.Index(i).Interface()
}
return res
return res, nil
}

func (b *Builder) assertNumber(v any) {
func (b *Builder) assertNumber(v any) error {
switch v.(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64:
return nil
default:
b.setErr(errNonNumericArg)
}
}

func (b *Builder) setErr(err error) {
if b.err == nil {
b.err = err
return errNonNumericArg
}
}

0 comments on commit d60dbd1

Please sign in to comment.