sqlx

repository·master·Indexed 12 days ago

https://github.com/jmoiron/sqlx

A set of extensions for Go's standard database/sql library that simplifies database interactions. It provides struct mapping via the `db` tag, named parameter support, and helper methods like `Get` and `Select` for rapid data retrieval into structs or slices. It is designed as a superset of the standard library, with `sqlx.DB`, `sqlx.TX`, and `sqlx.Stmt` implementing the same interfaces as their database/sql counterparts.

Tokens
7.1K
Snippets
32
Records
36
Agent score
97%

What's inside sqlx

  1. What is sqlx?

    master

    sqlx is a library that provides extensions to Go's standard database/sql library. It is designed as a superset of the standard library, meaning sqlx.DB, sqlx.TX, and sqlx.Stmt implement the same interfaces as their database/sql counterparts, making it easy to integrate into existing codebases.

    Key features include:

    • Marshaling rows into structs (including support for embedded structs), maps, and slices.
    • Named parameter support for queries and prepared statements.
    • Get and Select methods for rapid data retrieval into structs or slices.
  2. What is reflectx and why is it used?

    master

    The reflectx package is a specialized extension of Go's standard reflect package designed to meet the specific needs of the sqlx package. It provides optimized and enhanced reflection capabilities that standard Go reflection lacks, specifically for database mapping tasks.

    reflectx is used to:

    • Map names to struct fields efficiently.
    • Handle embedded structs.
    • Understand field mapping via specific struct tags (vital for database column-to-field mapping).
    • Support user-specified name-to-field mapping functions.

    While standard library methods like Reflect.Value.FieldByName and Reflect.Value.FieldByNameFunc provide similar functionality, they do not fully support the struct tag behaviors required by marshallers and are slower than the reflectx implementation.

  3. Use sqlx types for sql.Scanner and driver.Valuer compatibility

    master
    The types package provides specialized types that implement the standard library's sql.Scanner and driver.Valuer interfaces. These types are designed to be used as scan and value targets with database/sql, allowing for easier handling of specific database types when performing queries or inserting data.
  4. How sqlx works with structs and maps

    master

    sqlx allows you to map database rows directly to Go types using struct tags or map keys.

    Struct Mapping

    Use the db struct tag to map database column names to struct fields. If a column is nullable in the database, use sql.Null* types (e.g., sql.NullString) in your struct to avoid scanning errors.

    Named Queries

    You can use named parameters (e.g., :first_name) in your SQL. sqlx can resolve these from:

    • A map[string]interface{}
    • A struct (using the db tag or lowercase field names)
    • A slice of structs or maps for batch operations.

    Batch Insertions

    As of version 1.3.0, sqlx.NamedExec supports passing a slice of structs or a slice of maps ([]map[string]interface{}) to perform batch insertions.

    type Person struct {
        FirstName string `db:"first_name"` 
        LastName  string `db:"last_name"` 
        Email     string
    }
    
    // Using a struct for a named query
    err := db.NamedExec(`INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`, personStruct)
    
    // Batch insert with a slice of structs
    personStructs := []Person{{FirstName: "Ardie", ...}, {FirstName: "Sonny", ...}}
    db.NamedExec(`INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`, personStructs)
  5. How sqlx.Rows.StructScan works

    master

    When iterating over rows manually using *sqlx.Rows, you can use StructScan to map a single row into a struct.

    StructScan is more memory-efficient than Select for large result sets because it allows you to process rows one by one. It caches the reflection work required to match column positions to struct fields, making subsequent calls on the same Rows instance fast. However, because of this caching, you cannot use the same Rows instance to scan into different struct types.

    rows, err := db.Queryx("SELECT id, name FROM users")
    if err != nil {
        return err
    }
    defer rows.Close()
    
    for rows.Next() {
        var u User
        err := rows.StructScan(&u)
        if err != nil {
            return err
        }
        fmt.Println(u.Name)
    }
  6. Connect to a database with sqlx.Connect

    master

    Use sqlx.Connect to open a connection to a database and immediately verify it with a Ping. This is a convenience wrapper around sql.Open that ensures the database is reachable.

    If the connection fails, it returns an error. Use sqlx.MustConnect if you prefer the application to panic on connection failure.

    db, err := sqlx.Connect("mysql", "user:password@tcp(localhost:3306)/dbname")
    if err != nil {
        log.Fatalln(err)
    }
  7. Troubleshoot ambiguous row headers

    master

    If your query results in ambiguous column names (e.g., SELECT a.id, b.id FROM ...), sqlx may not be able to correctly map them to a struct or map.

    Solutions:

    1. Use AS in your SQL query to provide unique aliases for columns.
    2. Use rows.Scan to manually scan columns into variables.
    3. Use SliceScan to get a slice of results.
  8. Use StructScan for manual row iteration

    master

    If you prefer to iterate through rows manually using db.Queryx, you can use rows.StructScan to map the current row to a struct.

    place := Place{}
    rows, err := db.Queryx("SELECT * FROM place")
    for rows.Next() {
        err := rows.StructScan(&place)
        if err != nil {
            log.Fatalln(err)
        } 
        fmt.Printf("%#v\n", place)
    }
  9. Use NamedExec and NamedQuery for named parameters

    master

    Named queries allow you to use descriptive placeholders like :name instead of positional placeholders like $1 or ?. sqlx automatically handles the conversion to the driver's specific bind syntax.

    • db.NamedExec(query, arg): Executes a query with named parameters using a map or struct.
    • db.NamedQuery(query, arg): Returns a *sqlx.Rows object for queries with named parameters.
    // Using a map for named parameters
    _, err := db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`, 
        map[string]interface{}{
            "first": "Bin",
            "last": "Smuth",
            "email": "bensmith@allblacks.nz",
        })
    
    // Using a struct for a named query
    rows, err := db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
  10. Configure driver bind variables with BindDriver

    master

    The sqlx.BindDriver(driverName, bindType) function allows you to control which bind variable syntax (e.g., ?, $1, :name) sqlx uses for a specific driver. This is useful for adding support for new drivers at runtime or overriding default behavior.

    // Example concept (exact usage depends on driver requirements)
    sqlx.BindDriver("my_custom_driver", "$1")
  11. Use Get and Select for quick data retrieval

    master

    Instead of manually iterating through rows and calling Scan, use Select for multiple rows and Get for a single row.

    • db.Select(&dest, query, args...): Scans all resulting rows into a slice dest.
    • db.Get(&dest, query, args...): Scans a single row into the destination dest.
    // Select multiple rows into a slice
    people := []Person{}
    db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
    
    // Get a single row into a struct
    jason := Person{}
    err := db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")