Skip to content

Repository files navigation

qsql

qsql is a supplement to Go's database/sql package. It provides a DB type that embeds *sql.DB and intercepts Exec/Query/QueryRow (and their Context variants) to auto-convert ? placeholders to the format expected by the underlying database driver.

All query/exec methods accept ? as the placeholder — no need to use $1, @p1, or :1 manually.


Types

DB

type DB struct {
    *sql.DB
    // unexported: drvName string, isClose bool, mu sync.Mutex
}

Embedded from *sql.DB AND overridden with placeholder conversion:

  • Exec, ExecContext
  • Query, QueryContext
  • QueryRow, QueryRowContext
  • Begin() (*Tx, error) — returns a wrapped *Tx
  • Close() error
  • DriverName() string

Tx

type Tx struct {
    *sql.Tx
    // unexported: drvName string
}

Wraps *sql.Tx and overrides the same 6 methods with placeholder conversion. Returned from DB.Begin().

DBData

type DBData struct { /* implements sql.Scanner */ }

A universal scan target that converts any database column type to string. Used by QueryPageArr/QueryDBDataArr/QueryPageMap/QueryDBDataMap.


Interfaces

type Txer interface {
    Begin() (*Tx, error)     // returns *Tx (has placeholder conversion)
}

type Execer interface {
    Exec(query string, args ...interface{}) (sql.Result, error)
    ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}

type Queryer interface {
    Query(query string, args ...interface{}) (*sql.Rows, error)
    QueryRow(query string, args ...interface{}) *sql.Row
    QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
    QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}

type QuickSql interface {
    DriverName() string
    InsertStruct(structPtr interface{}, tbName string) (sql.Result, error)
    InsertStructContext(ctx context.Context, structPtr interface{}, tbName string) (sql.Result, error)
    ScanStructs(rows *sql.Rows, structsPtr interface{}) error
    QueryStruct(structPrt interface{}, querySql string, args ...interface{}) error
    QueryStructContext(ctx context.Context, structPrt interface{}, querySql string, args ...interface{}) error
    QueryStructs(structsPrt interface{}, querySql string, args ...interface{}) error
    QueryStructsContext(ctx context.Context, structsPrt interface{}, querySql string, args ...interface{}) error
    QueryElem(ePtr interface{}, querySql string, args ...interface{}) error
    QueryElemContext(ctx context.Context, ePtr interface{}, querySql string, args ...interface{}) error
    QueryElems(ePtr interface{}, querySql string, args ...interface{}) error
    QueryElemsContext(ctx context.Context, ePtr interface{}, querySql string, args ...interface{}) error
    QueryPageArr(querySql string, args ...interface{}) (titles []string, result [][]interface{}, err error)
    QueryPageArrContext(ctx context.Context, querySql string, args ...interface{}) (titles []string, result [][]interface{}, err error)
    QueryDBDataArr(querySql string, args ...interface{}) (titles []string, result [][]*DBData, err error)
    QueryDBDataArrContext(ctx context.Context, querySql string, args ...interface{}) (titles []string, result [][]*DBData, err error)
    QueryPageMap(querySql string, args ...interface{}) (titles []string, result []map[string]interface{}, err error)
    QueryPageMapContext(ctx context.Context, querySql string, args ...interface{}) (titles []string, result []map[string]interface{}, err error)
    QueryDBDataMap(querySql string, args ...interface{}) (titles []string, result []map[string]*DBData, err error)
    QueryDBDataMapContext(ctx context.Context, querySql string, args ...interface{}) (titles []string, result []map[string]*DBData, err error)
    StmtIn(paramStartIdx, paramLen int) string
    Commit(func(tx *sql.Tx) error) error
}

Placeholder Conversion Table

DB/Tx auto-converts ? in SQL strings before sending to the driver:

Driver Constant Driver String Placeholder Format Example
DRV_NAME_MYSQL / DRV_NAME_SQLITE3 "mysql" / "sqlite3" ? (unchanged) WHERE id = ?
DRV_NAME_POSTGRES "postgres" $1, $2, ... WHERE id = $1 AND name = $2
DRV_NAME_ORACLE / _DRV_NAME_OCI8 "oracle" / "oci8" :1, :2, ... WHERE id = :1 AND name = :2
DRV_NAME_SQLSERVER / _DRV_NAME_MSSQL "sqlserver" / "mssql" @p1, @p2, ... WHERE id = @p1 AND name = @p2
empty / unsupported "" no conversion SQL passed as-is

Note: Does NOT parse SQL string literals. Avoid putting ? inside string literals like WHERE x = '?'.


Driver Constants

DRV_NAME_MYSQL     = "mysql"
DRV_NAME_ORACLE    = "oracle"   // alias: "oci8"
DRV_NAME_POSTGRES  = "postgres"
DRV_NAME_SQLITE3   = "sqlite3"  // alias: "sqlite" (modernc.org/sqlite)
DRV_NAME_SQLSERVER = "sqlserver" // alias: "mssql"

Package-level Functions

Create DB

// From an existing *sql.DB:
db := qsql.NewDB(drvName, sqlDB)

// From driver name + DSN (calls sql.Open internally):
db, err := qsql.Open(drvName, dsn)

Query Helpers

All accept Queryer interface. If the Queryer is a *DB, placeholder conversion happens automatically via the intercepted QueryContext/QueryRowContext.

qsql.QueryStruct(queryer, &structObj, "SELECT * FROM t WHERE id = ?", id)
qsql.QueryStructContext(ctx, queryer, &structObj, sql, args...)

qsql.QueryStructs(queryer, &sliceObj, "SELECT * FROM t WHERE id = ?", id)
qsql.QueryStructsContext(ctx, queryer, &sliceObj, sql, args...)

qsql.QueryElem(queryer, &result, "SELECT count(*) FROM t WHERE id = ?", id)
qsql.QueryElemContext(ctx, queryer, &result, sql, args...)

qsql.QueryElems(queryer, &slice, "SELECT name FROM t WHERE id = ?", id)
qsql.QueryElemsContext(ctx, queryer, &slice, sql, args...)

qsql.QueryPageArr(queryer, sql, args...)  (titles []string, result [][]interface{}, err)
qsql.QueryPageArrContext(ctx, queryer, sql, args...)
qsql.QueryDBDataArr(queryer, sql, args...)  (titles []string, result [][]*DBData, err)
qsql.QueryDBDataArrContext(ctx, queryer, sql, args...)

qsql.QueryPageMap(queryer, sql, args...)  (titles []string, result []map[string]interface{}, err)
qsql.QueryPageMapContext(ctx, queryer, sql, args...)
qsql.QueryDBDataMap(queryer, sql, args...)  (titles []string, result []map[string]*DBData, err)
qsql.QueryDBDataMapContext(ctx, queryer, sql, args...)

Insert Helpers

qsql.InsertStruct(drvName, execer, &structObj, "table_name")
qsql.InsertStructContext(drvName, execer, ctx, &structObj, "table_name")

Struct Scan

qsql.ScanStructs(rows, &sliceOfStructs)

Transaction Helpers

// Commit runs a transaction. The callback receives bare *sql.Tx (no placeholder conversion).
// Use db.Begin() directly if you need placeholder conversion inside a transaction.
qsql.Commit(txer, func(tx *sql.Tx) error { ... })

// Lazy rollback (suppresses error in defer):
qsql.Rollback(tx)

StmtIn (WHERE IN helper)

// Generate driver-specific placeholders for IN clauses:
//   MySQL/SQLite3:  ?,?,?
//   PostgreSQL:      $1,$2,$3
//   Oracle:          :1,:2,:3
//   SQLServer:       @p0,@p1,@p2
qsql.StmtIn(paramStartIdx, paramsLen int, drvName ...string) string

DB Instance Methods

All methods on *DB auto-convert ? placeholders:

Standard SQL

db.QueryContext(ctx, "SELECT * FROM t WHERE id = ?", id)
db.QueryRowContext(ctx, "SELECT count(*) FROM t WHERE id = ?", id)
db.ExecContext(ctx, "UPDATE t SET x = ? WHERE id = ?", x, id)
// Also non-Context variants: Query, QueryRow, Exec

Quick Query

db.InsertStruct(&structObj, "table_name")
db.InsertStructContext(ctx, &structObj, "table_name")

db.QueryStruct(&structObj, "SELECT * FROM t WHERE id = ?", id)
db.QueryStructContext(ctx, &structObj, sql, args...)

db.QueryStructs(&sliceObj, "SELECT * FROM t WHERE id = ?", id)
db.QueryStructsContext(ctx, &sliceObj, sql, args...)

db.QueryElem(&result, "SELECT count(*) FROM t WHERE id = ?", id)
db.QueryElemContext(ctx, &result, sql, args...)

db.QueryElems(&slice, "SELECT name FROM t WHERE id = ?", id)
db.QueryElemsContext(ctx, &slice, sql, args...)

db.QueryPageArr(sql, args...) (titles, [][]interface{}, error)
db.QueryPageArrContext(ctx, sql, args...)
db.QueryDBDataArr(sql, args...) (titles, [][]*DBData, error)
db.QueryDBDataArrContext(ctx, sql, args...)
db.QueryPageMap(sql, args...) (titles, []map[string]interface{}, error)
db.QueryPageMapContext(ctx, sql, args...)
db.QueryDBDataMap(sql, args...) (titles, []map[string]*DBData, error)
db.QueryDBDataMapContext(ctx, sql, args...)

Transaction

// Begin returns *Tx (has placeholder conversion on Exec/Query/QueryRow):
tx, err := db.Begin()
tx.ExecContext(ctx, "UPDATE t SET x = ? WHERE id = ?", x, id) // auto-converts ?

// Commit with callback (callback receives bare *sql.Tx, no conversion):
db.Commit(func(tx *sql.Tx) error { ... })

// Helpers:
db.StmtIn(paramStartIdx, paramsLen int) string

Tx Instance Methods

*Tx embeds *sql.Tx and overrides the same 6 methods with placeholder conversion:

tx.ExecContext(ctx, "UPDATE t SET x = ? WHERE id = ?", x, id)
tx.QueryContext(ctx, "SELECT * FROM t WHERE id = ?", id)
tx.QueryRowContext(ctx, "SELECT count(*) FROM t WHERE id = ?", id)
// Also non-Context variants: Exec, Query, QueryRow

SelectBuilder

SelectBuilder provides a chainable SQL builder. It has its own independent placeholder conversion.

bd := qsql.NewSelectBuilder(drvName)
bd.Select("id", "name")
bd.From("users")
bd.Where(true, "created_at BETWEEN ? AND ?", t1, t2)
bd.IfWhereIn(len(ids) > 0, "AND id IN ?", ids)
bd.OrderBy("id DESC")
bd.Offset(0)
bd.Limit(10)

sqlStr := bd.String()   // returns SQL with driver-specific placeholders
args := bd.Args()       // returns args
db.QueryContext(ctx, sqlStr, args...)

Or use StrTo/Args with explicit driver name, and Sql() for (sqlStr, args) tuple:

sqlStr, args := bd.Sql()
titles, data, err := db.QueryDBDataArr(sqlStr, args...)

Conditional variants: IfWhere, IfWhereIn, IfGroupBy, IfOrderBy, IfOffset, IfLimit — each takes a bool condition as the first argument.


DB Cache (Connection Pool)

// Register from ini file:
qsql.RegCacheWithIni("./etc/db.cfg")

// Register manually:
qsql.RegCache("main", db)

// Get cached DB:
db := qsql.GetCache("main")       // panics if not found
db, err := qsql.HasCache("main")  // returns error if not found

// Close all cached DBs:
qsql.CloseCache()

Struct Tags (for InsertStruct / QueryStruct)

Uses github.com/jmoiron/sqlx tag format with db:"field_name":

type User struct {
    Id     int64  `db:"id,auto_increment"` // auto_increment / autoincrement: skip on INSERT
    Name   string `db:"name"`
    Ignore string `db:"-"`                 // "-": skip entirely
}

Design Decisions

  1. Placeholder conversion is at the low level: DB and Tx override ExecContext/QueryContext/QueryRowContext (and non-Context variants). Any code calling these methods on a *DB or *Tx gets automatic conversion — including the high-level query helpers.

  2. Commit callback receives bare *sql.Tx: The Commit(func(tx *sql.Tx) error) helper does not apply placeholder conversion inside the callback. Use db.Begin() directly if you need conversion within a transaction.

  3. Rollback and Commit function signatures unchanged: These are transaction lifecycle helpers that don't touch SQL.

  4. Empty drvName = no conversion: If the driver name is not set or unrecognized, SQL is passed through unchanged.

  5. Simple ? scan: The converter scans for ? rune-by-rune without SQL parsing. Avoid ? inside string literals or comments in SQL.


Reference

  • Go database/sql standard library
  • github.com/jmoiron/sqlx (struct reflection and tag parsing)
  • github.com/gwaylib/errors (error wrapping)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages