go-sqlbuilder

repository·master·Indexed 23 days ago

https://github.com/huandu/go-sqlbuilder

A high-performance SQL string concatenation utility for Go designed to build complex SQL statements compatible with the standard database/sql package. It is driver-agnostic and provides specialized builders for SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, UNION, and CTE, as well as a Struct factory for lightweight ORM-like functionality. The library includes a Cond utility for programmatic WHERE clause construction and an Args system for managing parameterized queries across different SQL flavors.

Tokens
12K
Snippets
14
Records
110
Agent score
78%

What's inside go-sqlbuilder

  1. Overview of go-sqlbuilder

    master

    The sqlbuilder package provides a suite of utilities for constructing SQL strings in Go. It is designed to be compatible with the standard library sql.DB and sql.Stmt interfaces.

    Key characteristics:

    • Driver Agnostic: It does not depend on specific database drivers and does not manage database connections.
    • Performance Focused: Designed to optimize SQL statement creation and minimize memory usage.
    • Versatile: It does not execute the generated SQL itself, making it suitable for building custom ORMs, business-specific database interaction layers, or handling non-standard SQL in complex enterprise environments.
  2. Use Struct as a lightweight ORM

    master

    The Struct builder factory uses Go struct definitions and field tags to automate SQL generation. It supports:

    • db tag: Sets the column name.
    • fieldtag: Filters fields based on custom tags.
    • fieldas: Sets a column alias (AS).
    • fieldopt: Provides options like omitempty (omit zero values in UPDATE), withquote (add quotes), expand (expand nested structs), or noexpand (treat nested struct as one column).
    • DefaultFieldMapper: A global function (defaults to nil) to map struct names to column names (e.g., sqlbuilder.SnakeCaseMapper).

    Struct does not interact with database/sql directly; it only generates SQL and arguments for use with standard library methods like DB#Query or Rows#Scan.

    type User struct {
        ID     int64  `db:"id" fieldtag:"pk"` 
        Name   string `db:"name"` 
        Status int    `db:"status"` 
    }
    
    var userStruct = NewStruct(new(User))
    
    // Usage in a query
    sb := userStruct.SelectFrom("user")
    sb.Where(sb.Equal("id", 1234))
    sql, args := sb.Build()
    
    // Scanning results
    rows, _ := db.Query(sql, args...)
    var user User
    rows.Scan(userStruct.Addr(&user)...)
  3. Create nested SQL and JOINs

    master

    You can nest queries by passing one builder as an argument to another using BuilderAs. This works for both WHERE clauses (subqueries) and JOIN clauses.

    // Nested Subquery in WHERE
    sb.Where(sb.In("status", statusSb))
    
    // Nested JOIN
    sb.Join(
        sb.BuilderAs(nestedSb, "b"),
        "a.user_id = b.user_id",
    )
  4. Share WHERE clauses between builders

    master

    You can reuse a WHERE clause by accessing the WhereClause field of a builder. This is useful for applying the same filters to different types of statements (e.g., applying a SELECT filter to an UPDATE statement).

    sb := Select("name", "level").From("users")
    sb.Where(sb.Equal("id", 1234))
    
    ub := Update("users")
    ub.Set(ub.Add("level", 10))
    
    // Transfer the WHERE clause from SELECT to UPDATE
    ub.WhereClause = sb.WhereClause
    
    sql, _ := ub.Build()
    // UPDATE users SET level = level + ? WHERE id = ?
  5. Basic usage of sqlbuilder

    master

    You can construct SQL statements using fluent method chaining. For simple queries, use the package-level functions. For queries requiring user input escaping, initialize a specific builder (e.g., NewSelectBuilder) to ensure arguments are properly parameterized.

    // Simple string construction
    sql := sqlbuilder.Select("id", "name").From("demo.user").
        Where("status = 1").Limit(10).
        String()
    
    // Parameterized construction (recommended for user input)
    sb := sqlbuilder.NewSelectBuilder()
    sb.Select("id", "name", sb.As("COUNT(*)", "c"))
    sb.From("user")
    sb.Where(sb.In("status", 1, 2, 5))
    
    sql, args := sb.Build()
    // sql: SELECT id, name, COUNT(*) AS c FROM user WHERE status IN (?, ?, ?)
    // args: [1 2 5]
  6. Manage WHERE clauses with WhereClause

    master

    The WhereClause type is a builder used to construct WHERE conditions for SQL queries. It can be shared among multiple builders (e.g., a SELECT builder and an UPDATE builder), but it is not thread-safe.

    Key capabilities:

    • Add Expressions: Use AddWhereExpr to add AND conditions. It accepts an *Args object and one or more string expressions.
    • Merge Clauses: Use AddWhereClause to append all conditions from one WhereClause into another.
    • Flavor Management: You can set the SQL dialect (e.g., MySQL, PostgreSQL) using SetFlavor. If the WhereClause is part of a larger builder, the builder's flavor will typically take precedence during the build process.
    • Cloning: Use CopyWhereClause to create a shallow copy of the clauses to avoid mutating the original instance.
  7. Use special argument syntax in Compile

    master

    When using Args.Compile or Args.CompileWithFlavor, you can use a special syntax within the format string to represent arguments:

    • $?: Refers to successive arguments passed in the initialValue slice. It behaves similarly to %v in fmt.Sprintf.
    • $0, $1, ..., $n: Refers to the $n$-th argument added via Args.Add. The next $? will use the argument at index $n+1$.
    • ${name}: Refers to a named argument created using sql.NamedArg or internal named argument mechanisms.
    • $$: Represents a literal $ character in the SQL string.

    Example of successive arguments:

    args := &sqlbuilder.Args{}
    // If initialValue is []interface{}{1, "name"}
    // "SELECT * FROM t WHERE id = $? AND name = $?" becomes "SELECT * FROM t WHERE id = 1 AND name = 'name'" (with placeholders)
    query, values := args.Compile("SELECT * FROM t WHERE id = $? AND name = $?", 1, "name")
  8. How SQL injection management works internally

    master
    The injection type is an internal helper used by the library to manage and order injected SQL fragments. It uses injectionMarker values to categorize SQL strings and ensures that when fragments are written to the final query, they are ordered by their marker in ascending order. This mechanism allows the builder to collect various parts of a query (like WHERE clauses or JOIN conditions) and join them with a blank space (" ") when they are finally rendered into the SQL string.
  9. Build WHERE clauses with Cond

    master

    Builders that support WHERE clauses have an anonymous Cond field. You can use this field to build complex conditions using methods like And, Or, and various comparison operators. This allows for clean, programmatic construction of logic.

    sb := sqlbuilder.NewSelectBuilder()
    sb.Select("id").From("user")
    sb.Where(
        sb.In("status", 1, 2, 5),
        sb.Or(
            sb.Equal("name", "foo"),
            sb.Like("email", "foo@%"),
        ),
    )
    
    sql, args := sb.Build()
    // SELECT id FROM user WHERE status IN (?, ?, ?) AND (name = ? OR email LIKE ?)
  10. Build UPDATE ... FROM for specific flavors

    master

    For PostgreSQL, SQLite, and SQLServer, UpdateBuilder.From can be used to emit a FROM clause. This is ignored by other database flavors.

    ub := PostgreSQL.NewUpdateBuilder()
    ub.Update("users")
    ub.Set(ub.Assign("name", "Huan Du"))
    ub.From("people")
    ub.Where("users.person_id = people.id")
    
    sql, args := ub.Build()
    // UPDATE users SET name = $1 FROM people WHERE users.person_id = people.id
  11. Interpolate arguments into SQL

    master

    For drivers that do not support prepared statements (e.g., some Redis or Elasticsearch drivers), you can interpolate arguments directly into the SQL string using Flavor.Interpolate(sql, args).

    Security Warning: Interpolation is less secure than using prepared statements provided by the database driver.