├── .gitignore ├── LINCENSE ├── README.md ├── context.go ├── examples ├── main.go └── sql.tmpl ├── go.mod └── template.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LINCENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mohammed Al Ashaal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # sqltmpl 🚀 3 | 4 | **`sqltmpl`** is a powerful and flexible SQL template engine for Go that takes the hassle out of writing safe, dynamic SQL statements! Built on top of Go’s `text/template`, `sqltmpl` helps you bind parameters securely and simplifies complex query generation. Perfect for developers who want clean, readable SQL without compromising on safety. 5 | 6 | ## 🌟 Features 7 | 8 | - **Dynamic SQL Binding**: Handles both single values and `IN` clauses with a smart `Bind` function. 9 | - **Flexible Pattern Matching**: Supports `LIKE`, `ILIKE`, and other custom operators with `Concat` for pattern building. 10 | - **Easy-to-Use Templating**: Write your SQL queries in structured, maintainable templates. 11 | - **Cross-Database Support**: Customize placeholder binding to suit PostgreSQL, SQLite, MySQL, and more! 12 | 13 | ## Installation 14 | 15 | ```bash 16 | go get github.com/alash3al/sqltmpl 17 | ``` 18 | 19 | ## 💡 Quick Start Guide 20 | 21 | 1. **Define Your SQL Template File (`sql.tmpl`)**: 22 | 23 | ```sql 24 | {{define "get_user_by_email"}} 25 | SELECT * FROM users 26 | WHERE email IN({{.Bind .Args.emails}}) 27 | OR (email = {{.Bind .Args.email}}) 28 | OR email LIKE {{.Bind (.Concat "%" .Args.email "%")}} 29 | {{end}} 30 | ``` 31 | 32 | 2. **Use `sqltmpl` in Your Go Code**: 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "fmt" 39 | "text/template" 40 | "github.com/alash3al/sqltmpl" 41 | ) 42 | 43 | func main() { 44 | // Initialize sqltmpl with template file and PostgreSQL-style bind 45 | tpl := sqltmpl.New( 46 | template.Must(template.ParseFiles("sql.tmpl")), 47 | func(i int) string { 48 | return fmt.Sprintf("$%d", i) // Bind style for PostgreSQL 49 | }, 50 | ) 51 | 52 | // Execute template with your parameters 53 | sql, args, err := tpl.Execute("get_user_by_email", map[string]any{ 54 | "emails": []string{"user1@example.com", "user2@example.com"}, 55 | "email": "user3@example.com", 56 | }) 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | fmt.Println(sql, args) 62 | } 63 | ``` 64 | 65 | 3. **Output**: 66 | 67 | ```sql 68 | SELECT * FROM users 69 | WHERE email IN ($1, $2) 70 | OR (email = $3) 71 | OR email LIKE $4 72 | ``` 73 | 74 | ```go 75 | []interface{"user1@example.com", "user2@example.com", "user3@example.com"} 76 | ``` 77 | 78 | ## 🔧 Advanced Usage 79 | 80 | ### Smart `Bind` Function for Flexible Binding 81 | 82 | `sqltmpl` automatically detects when to use: 83 | - **Single Value Binding**: For cases like `WHERE column = value`. 84 | - **Multi-Value Binding**: For `IN` clauses (`WHERE column IN (value1, value2, ...)`). 85 | 86 | ### `Concat` Function for Pattern Matching 87 | 88 | Build dynamic patterns with `%` wildcards for `LIKE` and `ILIKE` searches: 89 | 90 | ```sql 91 | WHERE column LIKE {{.Bind (.Concat "%" .Args.pattern "%")}} 92 | ``` 93 | 94 | ### Custom Binding Styles for Your Database 95 | 96 | `sqltmpl` lets you define custom binding styles for any SQL engine: 97 | 98 | ```go 99 | tpl := sqltmpl.New(myTemplate, func(i int) string { return fmt.Sprintf(":%d", i) }) // e.g., Oracle-style 100 | ``` 101 | 102 | ## 📚 Example Use Cases 103 | 104 | - **User Search with `LIKE` or `ILIKE`**: Perform case-insensitive searches in SQL databases. 105 | - **Multi-Value Filters**: Filter records with multiple values using `IN` clauses. 106 | - **Dynamic SQL Generation**: Build complex SQL statements using conditions, ranges, and subqueries. 107 | 108 | ## 💡 Why Choose `sqltmpl`? 109 | 110 | - **Safety First**: Prevents SQL injection with automatic binding for both single values and slices. 111 | - **Clean and Reusable SQL**: Manage all your SQL in template files, keeping code organized and readable. 112 | - **Database Agnostic**: Works seamlessly across different databases with customizable placeholders. 113 | 114 | ## 🛠️ Contributing 115 | 116 | We welcome contributions! Whether it’s a new feature, bug fix, or documentation improvement, feel free to open a PR or issue. Let's make SQL templating easier for everyone! ✨ 117 | 118 | ## 📄 License 119 | 120 | `sqltmpl` is licensed under the MIT License. 121 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package sqltmpl 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // Context is what is being exposed to the template being executed 9 | // it contains the args passed while executing a template. 10 | type Context struct { 11 | Args any 12 | bindings []any 13 | placeholderFunc func(i int) string 14 | } 15 | 16 | // Bind a helper function that safely injects a value into the SQL statement 17 | // 18 | // Example `SELECT * FROM users WHERE email = {{.Bind .Args.email}}` 19 | func (c *Context) Bind(value any) string { 20 | v := reflect.ValueOf(value) 21 | 22 | if (v.Kind() == reflect.Slice) || (v.Kind() == reflect.Array) { 23 | var result []string 24 | 25 | for i := 0; i < v.Len(); i++ { 26 | result = append(result, c.Bind(v.Index(i).Interface())) 27 | } 28 | 29 | return strings.Join(result, ", ") 30 | } 31 | 32 | c.bindings = append(c.bindings, value) 33 | return c.placeholderFunc(len(c.bindings)) 34 | } 35 | 36 | // Concat a helper function that safely injects multiple values into the SQL statement 37 | // 38 | // Example `SELECT * FROM users WHERE email in ({{.Concat .Args.emails}})` 39 | func (c *Context) Concat(values ...string) string { 40 | return strings.Join(values, "") 41 | } 42 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/alash3al/sqltmpl" 6 | "text/template" 7 | ) 8 | 9 | func main() { 10 | tpl := sqltmpl.New( 11 | template.Must(template.ParseFiles("sql.tmpl")), 12 | func(i int) string { 13 | return fmt.Sprintf("$%d", i) 14 | }, 15 | ) 16 | 17 | fmt.Println(tpl.Execute("get_user_by_email", map[string]any{ 18 | "email": "m@e.com", 19 | "emails": []string{"e1@o.com", "e2@o.com"}, 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /examples/sql.tmpl: -------------------------------------------------------------------------------- 1 | {{define "get_user_by_email"}} 2 | 3 | SELECT * FROM users WHERE email IN ({{.Bind .Args.emails}}) OR (email = {{.Bind .Args.email}}) OR email LIKE {{.Bind (.Concat "%" .Args.email "%")}} 4 | 5 | {{end}} 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alash3al/sqltmpl 2 | 3 | go 1.22.0 4 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | package sqltmpl 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "text/template" 7 | ) 8 | 9 | // Template is the representation of a parsed template. 10 | type Template struct { 11 | tpl *template.Template 12 | placeholderFunc func(i int) string 13 | } 14 | 15 | func New(tpl *template.Template, placeholderFunc func(i int) string) *Template { 16 | return &Template{ 17 | tpl: tpl, 18 | placeholderFunc: placeholderFunc, 19 | } 20 | } 21 | 22 | // Execute executes a template by the specified name and pass the specified data to the template Context 23 | // then it returns the (SQL statement, the bindings) or any error if any. 24 | func (t *Template) Execute(name string, data any) (string, []any, error) { 25 | output := bytes.NewBuffer(nil) 26 | ctx := Context{Args: data, placeholderFunc: t.placeholderFunc} 27 | 28 | if err := t.tpl.ExecuteTemplate(output, name, &ctx); err != nil { 29 | return "", nil, err 30 | } 31 | 32 | return strings.TrimSpace(output.String()), ctx.bindings, nil 33 | } 34 | --------------------------------------------------------------------------------