Skip to content

Commit

Permalink
Support MSSQL placeholders (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmzane authored Apr 23, 2024
1 parent d60dbd1 commit a00936f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 6 deletions.
9 changes: 5 additions & 4 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Safety & Sanitization

Query arguments should be passed via `%$` or `%?`, this way they won't appear in the query string but instead will be appended to the arguments slice (2nd return value of the `Builder.Build()` method), thus preventing potential SQL injections.
Query arguments should be passed via `%$`, `%?`, or `%@`, this way they won't appear in the query string but instead will be appended to the arguments slice (2nd return value of the `Builder.Build()` method), thus preventing potential SQL injections.

Examples in [example_test.go](example_test.go) explicitly show that query arguments are not a part of the `query` string but returned separately as the `args` slice.

Expand Down Expand Up @@ -63,16 +63,17 @@ Please note that unlike `fmt`, `builq` does not support width and explicit argum

## Argument placeholder

`builq` supports only 2 popular formats:
`builq` supports 3 formats:

* PostgreSQL via `%$` (`$1, $2, $3..`)
* MySQL/SQLite via `%?` (`?, ?, ?..`)
* MSSQL via `%@` (`@p1, @p2, @p3..`)

This should cover almost all available databases, if not - feel free to make an issue.

## Slice/batch modifiers

Both `%$` and `%?` formats can be extended with `+` or `#`:
All formats can be extended with `+` or `#`:

* `%+$` will expand slice argument as `$1, $2, ... $(len(arg)-1)`
* `%#?` will expand slice of slices argument as `(?, ?), (?, ?), ... (?, ?)`
Expand Down Expand Up @@ -101,4 +102,4 @@ BenchmarkBuildManyArgs-10 85747 14027 ns/op 1056 B/op 24 a
BenchmarkBuildCached
BenchmarkBuildCached-10 1000000000 0.6213 ns/op 0 B/op 0 allocs/op
PASS
```
```
53 changes: 53 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,31 @@ func Example_sliceMySQL() {
// [42 true str]
}

func Example_sliceMSSQL() {
params := []any{42, true, "str"}

var b builq.Builder
b.Addf("INSERT INTO table (id, flag, name)")
b.Addf("VALUES (%+@);", params)

query, args, err := b.Build()
if err != nil {
panic(err)
}

fmt.Println("query:")
fmt.Println(query)
fmt.Println("args:")
fmt.Println(args)

// Output:
// query:
// INSERT INTO table (id, flag, name)
// VALUES (@p1, @p2, @p3);
// args:
// [42 true str]
}

func Example_insertReturn() {
cols := builq.Columns{"id", "is_active", "name"}
params := []any{true, "str"}
Expand Down Expand Up @@ -463,6 +488,34 @@ func Example_batchMySQL() {
// [42 true str 69 true noice]
}

func Example_batchMSSQL() {
params := [][]any{
{42, true, "str"},
{69, true, "noice"},
}

var b builq.Builder
b.Addf("INSERT INTO table (id, flag, name)")
b.Addf("VALUES %#@;", params)

query, args, err := b.Build()
if err != nil {
panic(err)
}

fmt.Println("query:")
fmt.Println(query)
fmt.Println("args:")
fmt.Println(args)

// Output:
// query:
// INSERT INTO table (id, flag, name)
// VALUES (@p1, @p2, @p3), (@p4, @p5, @p6);
// args:
// [42 true str 69 true noice]
}

func Example_sliceInBatch() {
params := [][]any{
{42, []any{1, 2, 3}},
Expand Down
9 changes: 7 additions & 2 deletions write.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (b *Builder) write(sb *strings.Builder, resArgs *[]any, s string, args ...a
}

switch verb := s[0]; verb {
case '$', '?', 's', 'd':
case '$', '?', '@', 's', 'd':
if argID >= len(args) {
return fmt.Errorf("%w: have %d args, want %d", errTooFewArguments, len(args), argID+1)
}
Expand All @@ -49,7 +49,7 @@ func (b *Builder) write(sb *strings.Builder, resArgs *[]any, s string, args ...a
}

switch verb := s[0]; verb {
case '$', '?':
case '$', '?', '@':
if argID >= len(args) {
return fmt.Errorf("%w: have %d args, want %d", errTooFewArguments, len(args), argID+1)
}
Expand Down Expand Up @@ -136,6 +136,11 @@ func (b *Builder) writeArg(sb *strings.Builder, resArgs *[]any, verb byte, arg a
case '?':
sb.WriteByte('?')
*resArgs = append(*resArgs, arg)
case '@':
b.counter++
sb.WriteString("@p")
sb.WriteString(strconv.Itoa(b.counter))
*resArgs = append(*resArgs, arg)
case 's':
isSimple = true
switch arg := arg.(type) {
Expand Down

0 comments on commit a00936f

Please sign in to comment.