goqu

repository·master·Indexed 25 days ago

https://github.com/doug-martin/goqu

An expressive SQL builder and executor for Go that allows developers to build complex SQL queries using a type-safe DSL and scan results into structs or primitive values. It supports multiple dialects (including postgres, mysql, and sqlserver) and provides a comprehensive API for SELECT, INSERT, UPDATE, and DELETE operations, as well as transaction management and raw SQL execution.

Tokens
19.9K
Snippets
46
Records
136
Agent score
82%

What's inside goqu

  1. Create and register a custom dialect

    master
    You can create a custom dialect by overriding SQLDialectOptions. Use goqu.DefaultDialectOptions() as a base and modify the fields you need to change. Once configured, register the dialect using goqu.RegisterDialect(name, opts) so it can be retrieved via goqu.Dialect(name).
  2. Insert data using various formats

    master

    The Rows() method accepts multiple formats for defining the data to be inserted:

    • goqu.Vals: A slice of values for specific columns.
    • goqu.Record: A map-like structure (map[string]interface{}) where keys are column names.
    • Structs: Go structs with db tags. Fields can be controlled via goqu tags.
    • map[string]interface{}: Standard Go maps.
    // Insert with Cols and Vals
    ds := goqu.Insert("user").
        Cols("first_name", "last_name").
        Vals(
            goqu.Vals{"Greg", "Farley"},
            goqu.Vals{"Jimmy", "Stewart"},
        )
    
    // Insert goqu.Record
    ds := goqu.Insert("user").Rows(
        goqu.Record{"first_name": "Greg", "last_name": "Farley"},
    )
    
    // Insert Structs
    type User struct {
        FirstName string `db:"first_name"`
        LastName  string `db:"last_name"`
    }
    ds := goqu.Insert("user").Rows(
        User{FirstName: "Greg", LastName: "Farley"},
    )
    
    // Insert map[string]interface{}
    ds := goqu.Insert("user").Rows(
        map[string]interface{}{"first_name": "Greg", "last_name": "Farley"},
    )
  3. Enable SQL trace logging

    master
    To enable trace logging of SQL statements, use the Database.Logger method to set a logger. The provided logger must implement the Logger interface. Note that any transaction started from a database with a set logger will automatically inherit that logger.
  4. Execute a Delete statement

    master

    To execute a delete against a database, use .Executor() on your DeleteDataset to get an executor, then call .Exec() to perform the operation. You can use .RowsAffected() to see how many rows were deleted.

    If you used a .Returning() clause, you can use .ScanVals() to scan the returned values into variables or slices.

    // Basic execution
    db := getDb()
    de := db.Delete("goqu_user").
    	Where(goqu.Ex{"first_name": "Bob"}).
    	Executor()
    
    if r, err := de.Exec(); err != nil {
    	fmt.Println(err.Error())
    } else {
    	c, _ := r.RowsAffected()
    	fmt.Printf("Deleted %d users", c)
    }
    
    // Execution with Returning
    de := db.Delete("goqu_user").
    	Where(goqu.C("last_name").Eq("Yukon")).
    	Returning(goqu.C("id")).
    	Executor()
    
    var ids []int64
    if err := de.ScanVals(&ids); err != nil {
    	fmt.Println(err.Error())
    } else {
    	fmt.Printf("Deleted users [ids:=%+v]", ids)
    }
  5. Use TxDatabase.Wrap for automatic transaction management

    master

    The TxDatabase.Wrap method is a convenience function that automatically handles COMMIT and ROLLBACK logic. You pass a function containing your database operations to Wrap; if the function returns an error, the transaction is rolled back, otherwise it is committed.

    tx, err := db.Begin()
    if err != nil{
       return err
    }
    err = tx.Wrap(func() error{
      update := tx.From("user").
          Where(goqu.Ex{"password": nil}).
          Update(goqu.Record{"status": "inactive"})
      return update.Exec()
    })
    //err will be the original error from the update statement, unless there was an error executing ROLLBACK
    if err != nil{
        return err
    }
  6. Migrate from v7 to v8

    master

    In v8, goqu introduced a major API change to separate concerns between different SQL statement types. Instead of a single dataset type, there are now five distinct dataset types: SelectDataset, InsertDataset, UpdateDataset, DeleteDataset, and TruncateDataset.

    Key changes include:

    • Specific entry points for each statement type (e.g., goqu.Insert, goqu.Update).
    • Removal of ToInsertSQL, ToUpdateSQL, ToDeleteSQL, and ToTruncateSQL from SelectDataset. Use the ToSQL method on the respective dataset type instead.
    • Introduction of the Executor() method to handle execution for non-SELECT statements.
  7. Use built-in dialects for SQL generation

    master

    goqu provides four built-in dialects to ensure correct SQL syntax for different databases. To use a dialect, you must import its package with a blank identifier (_) to register it, then look it up using goqu.Dialect(name).

    Supported dialects:

    • mysql: _ "github.com/doug-martin/goqu/v9/dialect/mysql"
    • postgres: _ "github.com/doug-martin/goqu/v9/dialect/postgres"
    • sqlite3: _ "github.com/doug-martin/goqu/v9/dialect/sqlite3"
    • sqlserver: _ "github.com/doug-martin/goqu/v9/dialect/sqlserver"
    import (
      "fmt"
      "github.com/doug-martin/goqu/v9"
      // import the dialect for side effects
      _ "github.com/doug-martin/goqu/v9/dialect/postgres"
    )
    
    // look up the dialect
    dialect := goqu.Dialect("postgres")
    
    // use dialect.From to build your SQL
    ds := dialect.From("test").Where(goqu.Ex{"id": 10})
    sql, args, err := ds.ToSQL()
    if err != nil {
      fmt.Println("An error occurred while generating the SQL", err.Error())
    } else {
      fmt.Println(sql, args)
    }
  8. Manage transactions with Begin, Commit, and Rollback

    master

    You can start a transaction using db.Begin(), which returns a TxDatabase. To ensure operations occur within the transaction, use tx.From to obtain a dataset. You must manually call tx.Commit() to save changes or tx.Rollback() to abort them if an error occurs.

    tx, err := db.Begin()
    if err != nil{
       return err
    }
    //use tx.From to get a dataset that will execute within this transaction
    update := tx.From("user").
        Where(goqu.Ex{"password": nil}).
        Update(goqu.Record{"status": "inactive"})
    if _, err = update.Exec(); err != nil{
        if rErr := tx.Rollback(); rErr != nil{
            return rErr
        }
        return err
    }
    if err = tx.Commit(); err != nil{
        return err
    }
    return
  9. Create a SelectDataset

    master

    You can create a SelectDataset using several methods depending on your needs:

    1. Quick SQL (Default/Postgres-like): Use goqu.From and goqu.Select. This follows Postgres syntax but uses standard placeholders for prepared statements.
    2. Specific Dialect: Use goqu.Dialect(name) to create a DialectWrapper, then call .From() or .Select() to generate dialect-specific SQL (e.g., MySQL backticks).
    3. Database Integration: Use goqu.New(dialect, db) to create a Database instance. This is used when you want to execute SQL directly using an existing database driver.
    // 1. Quick SQL
    sql, _, _ := goqu.From("table").ToSQL()
    
    // 2. Specific Dialect
    // import _ "github.com/doug-martin/goqu/v9/dialect/mysql"
    dialect := goqu.Dialect("mysql")
    sql, _, _ := dialect.From("table").ToSQL()
    
    // 3. Database Integration
    // mysqlDB := // initialize your db
    db := goqu.New("mysql", mysqlDB)
    sql, _, _ := db.From("table").ToSQL()
  10. Execute SQL Update statements

    master

    To execute an update in goqu, use the goqu.Database#Update method to build your dataset. You must call .Executor() at the end of your builder chain to obtain an executor that can run the query.

    Common steps include:

    1. Calling db.Update("table_name").
    2. Using .Where() to specify the target rows.
    3. Using .Set() with a goqu.Record to define the new values.
    4. Calling .Executor().
    5. Calling .Exec() on the resulting executor to perform the operation and receive a sql.Result.
    db := getDb()
    
    update := db.Update("goqu_user").
    	Where(goqu.C("first_name").Eq("Bob")).
    	Set(goqu.Record{"first_name": "Bobby"}).
    	Executor()
    
    if r, err := update.Exec(); err != nil {
    	fmt.Println(err.Error())
    } else {
    	c, _ := r.RowsAffected()
    	fmt.Printf("Updated %d users", c)
    }
  11. Use Prepared Statements with Prepared()

    master

    By default, goqu interpolates all parameters into the SQL string. To prevent interpolation and instead use placeholders (e.g., ? for MySQL/SQLite or $1, $2 for Postgres), use the .Prepared(true) method on a dataset. This is useful for using prepared statements in your database driver to improve security and performance.

    When .Prepared(true) is set, all subsequent query operations (Select, Insert, Update, Delete) will generate SQL with placeholders and return the arguments separately.

  12. Install goqu

    master

    To install goqu using Go modules, run the following command:

    go get -u github.com/doug-martin/goqu/v9

    If you are not using Go modules (but using Go version >v1.10), you must drop the version from the import path. Use github.com/doug-martin/goqu instead of github.com/doug-martin/goqu/v9.