From 9450f41ca2134b107afabdb7806370b0c941f158 Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Tue, 14 May 2024 23:43:43 +0300 Subject: [PATCH] feat: implement DebugString() (#1) --- builder.go | 60 ++++++++++++++++++++++++++++++++++++------------- builder_test.go | 18 ++++++++++----- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/builder.go b/builder.go index 353a979..7e6059f 100644 --- a/builder.go +++ b/builder.go @@ -2,15 +2,14 @@ package queries import ( "fmt" - "slices" "strings" ) type Builder struct { - query strings.Builder - Args []any - counter int - placeholders []rune + query strings.Builder + Args []any + counter int + placeholder rune } func (b *Builder) Appendf(format string, args ...any) { @@ -21,21 +20,47 @@ func (b *Builder) Appendf(format string, args ...any) { fmt.Fprintf(&b.query, format, a...) } -func (b *Builder) String() string { - slices.Sort(b.placeholders) - if len(slices.Compact(b.placeholders)) > 1 { - panic(fmt.Sprintf("queries.Builder: bad query: %s placeholders used", string(b.placeholders))) +func (b *Builder) String() string { return b.string() } + +func (b *Builder) DebugString() string { + query := b.string() + for i, arg := range b.Args { + var sarg string + switch arg := arg.(type) { + case string: + sarg = fmt.Sprintf("'%s'", arg) + case fmt.Stringer: + sarg = fmt.Sprintf("'%s'", arg.String()) + default: + sarg = fmt.Sprintf("%v", arg) + } + + switch b.placeholder { + case '?': + query = strings.Replace(query, "?", sarg, 1) + case '$': + query = strings.Replace(query, fmt.Sprintf("$%d", i+1), sarg, 1) + case '@': + query = strings.Replace(query, fmt.Sprintf("@p%d", i+1), sarg, 1) + default: + panic("unreachable") + } } + return query +} - s := b.query.String() - if strings.Contains(s, "%!") { +func (b *Builder) string() string { + query := b.query.String() + if strings.Contains(query, "%!") { // fmt silently recovers panics and writes them to the output. // we want panics to be loud, so we find and rethrow them. // see also https://github.com/golang/go/issues/28150. - panic(fmt.Sprintf("queries.Builder: bad query: %s", s)) + panic(fmt.Sprintf("queries: bad query: %s", query)) } - - return s + if b.placeholder == -1 { + panic("queries: bad query: different placeholders used") + } + return query } type argument struct { @@ -48,7 +73,12 @@ func (a argument) Format(s fmt.State, verb rune) { switch verb { case '?', '$', '@': a.builder.Args = append(a.builder.Args, a.value) - a.builder.placeholders = append(a.builder.placeholders, verb) + if a.builder.placeholder == 0 { + a.builder.placeholder = verb + } + if a.builder.placeholder != verb { + a.builder.placeholder = -1 + } } switch verb { diff --git a/builder_test.go b/builder_test.go index 43c1abb..a421a34 100644 --- a/builder_test.go +++ b/builder_test.go @@ -1,6 +1,7 @@ package queries_test import ( + "context" "testing" "go-simpler.org/queries" @@ -25,27 +26,32 @@ func TestBuilder_placeholders(t *testing.T) { tests := map[string]struct { format string query string + debug string }{ "?": { format: "select * from tbl where foo = %? and bar = %? and baz = %?", query: "select * from tbl where foo = ? and bar = ? and baz = ?", + debug: "select * from tbl where foo = 42 and bar = 'test' and baz = 'context.Background'", }, "$": { format: "select * from tbl where foo = %$ and bar = %$ and baz = %$", query: "select * from tbl where foo = $1 and bar = $2 and baz = $3", + debug: "select * from tbl where foo = 42 and bar = 'test' and baz = 'context.Background'", }, "@": { format: "select * from tbl where foo = %@ and bar = %@ and baz = %@", query: "select * from tbl where foo = @p1 and bar = @p2 and baz = @p3", + debug: "select * from tbl where foo = 42 and bar = 'test' and baz = 'context.Background'", }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { var qb queries.Builder - qb.Appendf(tt.format, 1, 2, 3) + qb.Appendf(tt.format, 42, "test", context.Background()) assert.Equal[E](t, qb.String(), tt.query) - assert.Equal[E](t, qb.Args, []any{1, 2, 3}) + assert.Equal[E](t, qb.Args, []any{42, "test", context.Background()}) + assert.Equal[E](t, qb.DebugString(), tt.debug) }) } } @@ -59,25 +65,25 @@ func TestBuilder_badQuery(t *testing.T) { appends: func(qb *queries.Builder) { qb.Appendf("select %d from tbl", "foo") }, - panicMsg: "queries.Builder: bad query: select %!d(string=foo) from tbl", + panicMsg: "queries: bad query: select %!d(string=foo) from tbl", }, "too few arguments": { appends: func(qb *queries.Builder) { qb.Appendf("select %s from tbl") }, - panicMsg: "queries.Builder: bad query: select %!s(MISSING) from tbl", + panicMsg: "queries: bad query: select %!s(MISSING) from tbl", }, "too many arguments": { appends: func(qb *queries.Builder) { qb.Appendf("select %s from tbl", "foo", "bar") }, - panicMsg: "queries.Builder: bad query: select foo from tbl%!(EXTRA queries.argument=bar)", + panicMsg: "queries: bad query: select foo from tbl%!(EXTRA queries.argument=bar)", }, "different placeholders": { appends: func(qb *queries.Builder) { qb.Appendf("select * from tbl where foo = %? and bar = %$ and baz = %@", 1, 2, 3) }, - panicMsg: "queries.Builder: bad query: $?@ placeholders used", + panicMsg: "queries: bad query: different placeholders used", }, }