diff --git a/GUIDE.md b/GUIDE.md index 99b1489..0467ba0 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -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. @@ -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 `(?, ?), (?, ?), ... (?, ?)` @@ -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 -``` \ No newline at end of file +``` diff --git a/example_test.go b/example_test.go index 6011b4b..305747f 100644 --- a/example_test.go +++ b/example_test.go @@ -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"} @@ -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}}, diff --git a/write.go b/write.go index 3173822..b64e937 100644 --- a/write.go +++ b/write.go @@ -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) } @@ -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) } @@ -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) {