The ParallelExecutor.Add() method now accepts multiple types directly without needing func() error wrappers:
✅ FluentSelect - Pass directly
✅ FluentInsert - Pass directly
✅ DeferredExec - For UPDATE/DELETE statements
✅ func() error - Still supported for custom logic
✅ Executable interface - Custom types
var users []User
executor := goquery.NewParallelExecutor().
Add(func() error {
return store.Select("SELECT * FROM users").Dest(&users).Fetch()
})var users []User
executor := goquery.NewParallelExecutor().
Add(store.Select("SELECT * FROM users").Dest(&users))package main
import (
"context"
"github.com/usace/goquery/v3"
)
func main() {
var store goquery.DataStore
// Declare results outside
var users []User
var products []Product
var newOrder Order
executor := goquery.NewParallelExecutor().
MaxConcurrency(5).
// SELECT queries - pass FluentSelect directly
Add(
store.Select("SELECT * FROM users WHERE active = $1").
Params(true).
Dest(&users),
).
Add(
store.Select("SELECT * FROM products").
Dest(&products),
).
// INSERT - pass FluentInsert directly
Add(
store.Insert(&ordersDS).Records(&newOrder),
).
// UPDATE/DELETE - use DeferredExec
Add(
goquery.NewDeferredExec(store, goquery.NoTx,
"UPDATE stats SET views = views + 1"),
).
// Custom logic - use func() error
Add(func() error {
// Complex logic here
return customOperation()
})
ctx := context.Background()
if err := executor.Run(ctx); err != nil {
log.Fatal(err)
}
// Access results
fmt.Printf("Loaded %d users, %d products\n", len(users), len(products))
}var data []Record
executor.Add(
store.Select("SELECT * FROM table").Dest(&data),
)
// Automatically calls .Fetch()executor.Add(
store.Insert(&dataset).Records(&record),
)
// Automatically calls .Execute()executor.Add(
goquery.NewDeferredExec(store, goquery.NoTx,
"UPDATE users SET last_login = NOW() WHERE id = $1", userID),
goquery.NewDeferredExec(store, goquery.NoTx,
"DELETE FROM sessions WHERE expired = true"),
)Why DeferredExec?
store.Exec() executes immediately, so you can't pass it to Add(). DeferredExec wraps it for deferred execution.
executor.Add(func() error {
log.Println("Starting operation...")
var data []Record
err := store.Select("SELECT * FROM records").Dest(&data).Fetch()
if err != nil {
return err
}
return processData(data)
})type Executable interface {
Execute() error
}
type MyQuery struct {
store DataStore
}
func (q *MyQuery) Execute() error {
return q.store.Exec(goquery.NoTx, "UPDATE ...")
}
executor.Add(&MyQuery{store: store})- Less Boilerplate - No need to wrap everything in
func() error - More Readable - Code intent is clearer
- Flexible - Mix and match different types
- Backward Compatible -
func() errorstill works - Type Safe - Panics on unsupported types
// ✅ CORRECT
var users []User
executor.Add(store.Select("SELECT * FROM users").Dest(&users))
// users is accessible here
// ❌ WRONG
executor.Add(func() error {
var users []User // Lost after function returns!
return store.Select("SELECT * FROM users").Dest(&users).Fetch()
})
// users is NOT accessible herefor _, id := range ids {
id := id // Capture loop variable!
executor.Add(func() error {
return fetchDataForID(id)
})
}func NewDeferredExec(store DataStore, tx *Tx, stmt string, params ...interface{}) *DeferredExecCreates a deferred Exec call.
Parameters:
store- The DataStore to execute againsttx- Transaction (usegoquery.NoTxfor no transaction)stmt- SQL statementparams- Statement parameters
Returns:
*DeferredExec- A deferred execution wrapper
Example:
exec := goquery.NewDeferredExec(
store,
goquery.NoTx,
"UPDATE users SET active = $1 WHERE id = $2",
false,
123,
)
executor.Add(exec)var users []User
var products []Product
executor := goquery.NewParallelExecutor().
Add(func() error {
return store.Select("SELECT * FROM users").Dest(&users).Fetch()
}).
Add(func() error {
return store.Select("SELECT * FROM products").Dest(&products).Fetch()
}).
Add(func() error {
return store.Exec(goquery.NoTx, "UPDATE stats SET count = count + 1")
})
ctx := context.Background()
executor.Run(ctx)var users []User
var products []Product
executor := goquery.NewParallelExecutor().
Add(
store.Select("SELECT * FROM users").Dest(&users),
store.Select("SELECT * FROM products").Dest(&products),
goquery.NewDeferredExec(store, goquery.NoTx,
"UPDATE stats SET count = count + 1"),
)
ctx := context.Background()
executor.Run(ctx)- Full Documentation: See original
parallel_exec.mdfor complete details - goquery Basics: See
readme.mdfor DataStore operations and query patterns - Examples: See
parallel_exec_example.gofor more usage patterns
The Add() method uses type switching to handle different input types:
func (p *ParallelExecutor) Add(queries ...interface{}) *ParallelExecutor {
for _, q := range queries {
switch v := q.(type) {
case func() error:
p.queries = append(p.queries, v)
case *FluentSelect:
p.queries = append(p.queries, func() error { return v.Fetch() })
case *FluentInsert:
p.queries = append(p.queries, func() error { return v.Execute() })
case *DeferredExec:
p.queries = append(p.queries, func() error { return v.Execute() })
case Executable:
p.queries = append(p.queries, func() error { return v.Execute() })
default:
panic(fmt.Sprintf("unsupported type: %T", q))
}
}
return p
}All types are internally converted to func() error for uniform execution.