The querysql package provides several layers; all of them provide safety against common mistakes and avoids a lot of iterator boilerplate. Also included is a convenient logging mechanism.
All of the methods allows conveniently fetching results into your own structs, using reflection.
- To execute a single select statement, use:
- querysql.Slicereturns a slice
- querysql.Singlevalidates that the result is exactly one row, and returns that single row
- querysql.Itercalls a callback function for each row
 
- To execute multiple select statements in the same database
roundtrip, querysql.Query2,querysql.Query3, ... is available
- querysql.Newoffers a lower-level API with more options, used to build the primitives above
Below the variables ctx and db are used for context.Context and *sql.DB.
The most common mode is to process a single select statement per query:
// Read a single integer; errors if not exactly 1 row
n := querysql.MustSingle[int](ctx, db, `select 1`)
n, err := querysql.Single[int](ctx, db, `select 1`)
// Read a slice of integers; can have any number of elements
slice := querysql.MustSlice[int](ctx, db, `select @p1`, 3)
slice, err := querysql.Slice[int](ctx, db, `select @p1`, 3)
// Support for reading into structs, or slices of structs
type row {
	X int
	Y string
}
singleRow, err := querysql.QuerySingle[row](ctx, db, `select 1 as X, "hello" as Y`)
sliceOfRows, err := querysql.QuerySlice[row](ctx, db, `select 1 as X, "hello" as Y`)
// Avoid allocating slices by passing a callback 
err := querysql.QueryIter(ctx, db, func(row int) error {
	fmt.Println("Row %d", row)
}, `select 1 union all select 2`)This mode still supports logging and will return ErrNotDone
if there are several non-logging select statements.
SQL supports doing several selects on the same round-trip, which can be very useful to get information from several tables in the same query. In this case a somewhat special syntax is used to pass the specifications for how to read each type:
qry := `
    select 1
    union all select 2;
    select 1 as X, "one" as Y 
` 
singleInteger, sliceOfStruct, err := querysql.Query2(
	querysql.SingleOf[int], 
	querysql.SliceOf[MyStruct],
	ctx, db, qry, arg1, arg2)We have defined Query2, Query3 and Query4 for this use up
to 4 select statements.
If you prefer, you can instead scan into pointers; this also allows using a single function for any number of results or dynamic number of results:
var singleInteger int
var sliceOfStruct []MyStruct
singleInteger, sliceOfStruct, err := querysql.Query(
	[]querysql.Target{
		querysql.SingleInto(&singleInteger),
		querysql.SliceInto(&sliceOfStruct),
	},
	ctx, db, qry, arg1, arg2)When writing longer multi-statement SQL queries the lack of
debugging between statements can be a real problem. A work-around
is provided in this library. Any target-less select statements
where the name of the first column is _log will be re-directed
to a logger (if one is configured; and otherwise the data will be
ignored). Example:
qry := `
    declare @a = 'world';
    select A='one';
    -- logging
    select _log='info', hello=@a;
    
    select B='two';
    -- log one entry per row, at a non-standard level
    select _log='info', hello=@a from SomeTable;
` 
// configure a logger on ctx
ctx := querysql.WithLogger(context.Background(), LogrusMSSQLLogger(logger, logrus.InfoLevel))
// do the query like normal; the middle select will be directed to the logger
firstResult, secondResult, err := querysql.Query2(ctx, ...)The LogrusMSSQLLogger above is, as given by the name, specific
to one combination of tools, and you may need to write your
own implementation of RowsLogger based on the one provided in this library.
The *sql.Rows is passed straight through to the RowsLogger,
but by convention the first column in the result will be the special
column _log, which may either contain a log-level (info, debug, warning, error).
For more advanced usecase you may use querysql.New.
In the following example we first do a select to get a "command"
back, then scan into different types depending on the value of
the first select:
rs := querysql.New(ctx, dbi, `
    select 'ints';
    select 1 union all select 2;
`)
// rs is automatically closed after processing the last select statement,
// but it is still a good idea to defer a Close in case you do not process
// all the select statements
defer rs.Close()
mode, err := querysql.NextResult(rs, querysql.SingleOf[string])
if mode == "ints" {
	data, err := querysql.NextResult(rs, querysql.SliceOf[int])
	...
} else if mode == "structs" {
	type SomeStruct struct {
		X int
		Y string
	}
	data, err := querysql.Next(rs, querysql.Call(func (row MyStruct)) {
		fmt.Println(row)
		return nil
    })
}You may also process a dynamic number of results; rs.Done() will be be true
when there are no more result sets available. At this point, rs has also been
automatically closed; although it is still a good idea to defer rs.Close() in
case you do not reach the point where rs.Done() == true.
- Allow querying directly into maptypes, using the first columns as the key
- Automatically deserialize XML or JSON results from SQL to structs