Files
gh-stirlingmarketinggroup-c…/references/api-reference.md
2025-11-30 08:58:35 +08:00

793 lines
19 KiB
Markdown

# cool-mysql API Reference
Complete API documentation for all cool-mysql methods, organized by category.
## Database Creation
### New
```go
func New(wUser, wPass, wSchema, wHost string, wPort int,
rUser, rPass, rSchema, rHost string, rPort int,
collation, timeZone string) (*Database, error)
```
Create a new database connection from connection parameters.
**Parameters:**
- `wUser`, `wPass`, `wSchema`, `wHost`, `wPort` - Write connection credentials
- `rUser`, `rPass`, `rSchema`, `rHost`, `rPort` - Read connection credentials
- `collation` - Database collation (e.g., `"utf8mb4_unicode_ci"`)
- `timeZone` - Time zone for connections (e.g., `"America/New_York"`, `"UTC"`)
**Returns:**
- `*Database` - Database instance with dual connection pools
- `error` - Connection error if unable to establish connections
**Example:**
```go
db, err := mysql.New(
"root", "password", "mydb", "localhost", 3306,
"root", "password", "mydb", "localhost", 3306,
"utf8mb4_unicode_ci",
"UTC",
)
```
### NewFromDSN
```go
func NewFromDSN(writesDSN, readsDSN string) (*Database, error)
```
Create database connection from DSN strings.
**Parameters:**
- `writesDSN` - Write connection DSN
- `readsDSN` - Read connection DSN
**DSN Format:**
```
username:password@protocol(address)/dbname?param=value
```
**Example:**
```go
writesDSN := "user:pass@tcp(write-host:3306)/dbname?parseTime=true&loc=UTC"
readsDSN := "user:pass@tcp(read-host:3306)/dbname?parseTime=true&loc=UTC"
db, err := mysql.NewFromDSN(writesDSN, readsDSN)
```
### NewFromConn
```go
func NewFromConn(writesConn, readsConn *sql.DB) (*Database, error)
```
Create database from existing `*sql.DB` connections.
**Parameters:**
- `writesConn` - Existing write connection
- `readsConn` - Existing read connection
**Example:**
```go
writesConn, _ := sql.Open("mysql", writesDSN)
readsConn, _ := sql.Open("mysql", readsDSN)
db, err := mysql.NewFromConn(writesConn, readsConn)
```
## Query Methods (SELECT)
### Select
```go
func (db *Database) Select(dest any, query string, cacheTTL time.Duration, params ...mysql.Params) error
```
Execute SELECT query and scan results into destination. Uses read connection pool.
**Parameters:**
- `dest` - Destination for results (struct, slice, map, channel, function, or primitive)
- `query` - SQL query with `@@paramName` placeholders
- `cacheTTL` - Cache duration (`0` = no cache, `> 0` = cache for duration)
- `params` - Query parameters (`mysql.Params{}` or structs)
**Destination Types:**
- `*[]StructType` - Slice of structs
- `*StructType` - Single struct (returns `sql.ErrNoRows` if not found)
- `*string`, `*int`, `*time.Time`, etc. - Single value
- `chan StructType` - Channel for streaming results
- `func(StructType)` - Function called for each row
- `*[]map[string]any` - Slice of maps
- `*json.RawMessage` - JSON result
**Returns:**
- `error` - Query error or `sql.ErrNoRows` for single-value queries with no results
**Examples:**
```go
// Select into struct slice
var users []User
err := db.Select(&users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE age > @@minAge", 5*time.Minute,
18)
// Select single value
var name string
err := db.Select(&name, "SELECT `name` FROM `users` WHERE `id` = @@id", 0,
1)
// Select into channel
userCh := make(chan User)
go func() {
defer close(userCh)
db.Select(userCh, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
}()
// Select with function
db.Select(func(u User) {
log.Printf("User: %s", u.Name)
}, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
```
### SelectContext
```go
func (db *Database) SelectContext(ctx context.Context, dest any, query string,
cacheTTL time.Duration, params ...mysql.Params) error
```
Context-aware version of `Select()`. Supports cancellation and deadlines.
**Parameters:**
- `ctx` - Context for cancellation/timeout
- Additional parameters same as `Select()`
**Example:**
```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var users []User
err := db.SelectContext(ctx, &users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users`", 0)
```
### SelectWrites
```go
func (db *Database) SelectWrites(dest any, query string, cacheTTL time.Duration,
params ...mysql.Params) error
```
Select using write connection pool. Use for read-after-write consistency.
**When to Use:**
- Immediately after INSERT/UPDATE/DELETE when you need to read the modified data
- When you need strong consistency and can't risk reading stale replica data
**Example:**
```go
// Insert then immediately read
db.Insert("users", user)
db.SelectWrites(&user, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `id` = @@id", 0,
user.ID)
```
### SelectWritesContext
```go
func (db *Database) SelectWritesContext(ctx context.Context, dest any, query string,
cacheTTL time.Duration, params ...mysql.Params) error
```
Context-aware version of `SelectWrites()`.
### SelectJSON
```go
func (db *Database) SelectJSON(dest *json.RawMessage, query string,
cacheTTL time.Duration, params ...mysql.Params) error
```
Select query results as JSON.
**Example:**
```go
var result json.RawMessage
err := db.SelectJSON(&result,
"SELECT JSON_OBJECT('id', id, 'name', name) FROM `users` WHERE `id` = @@id",
0, 1)
```
### SelectJSONContext
```go
func (db *Database) SelectJSONContext(ctx context.Context, dest *json.RawMessage,
query string, cacheTTL time.Duration,
params ...mysql.Params) error
```
Context-aware version of `SelectJSON()`.
## Utility Query Methods
### Count
```go
func (db *Database) Count(query string, cacheTTL time.Duration, params ...mysql.Params) (int64, error)
```
Execute COUNT query and return result as `int64`. Uses read pool.
**Parameters:**
- `query` - Query that returns a single integer (typically `SELECT COUNT(*)`)
- `cacheTTL` - Cache duration
- `params` - Query parameters
**Returns:**
- `int64` - Count result
- `error` - Query error
**Example:**
```go
count, err := db.Count("SELECT COUNT(*) FROM `users` WHERE `active` = @@active",
5*time.Minute, 1)
```
### CountContext
```go
func (db *Database) CountContext(ctx context.Context, query string, cacheTTL time.Duration,
params ...mysql.Params) (int64, error)
```
Context-aware version of `Count()`.
### Exists
```go
func (db *Database) Exists(query string, cacheTTL time.Duration, params ...mysql.Params) (bool, error)
```
Check if query returns any rows. Uses read pool.
**Parameters:**
- `query` - Query to check (typically `SELECT 1 FROM ... WHERE ...`)
- `cacheTTL` - Cache duration
- `params` - Query parameters
**Returns:**
- `bool` - `true` if rows exist, `false` otherwise
- `error` - Query error
**Example:**
```go
exists, err := db.Exists("SELECT 1 FROM `users` WHERE `email` = @@email", 0,
"user@example.com")
```
### ExistsContext
```go
func (db *Database) ExistsContext(ctx context.Context, query string, cacheTTL time.Duration,
params ...mysql.Params) (bool, error)
```
Context-aware version of `Exists()`.
### ExistsWrites
```go
func (db *Database) ExistsWrites(query string, params ...mysql.Params) (bool, error)
```
Check existence using write pool for read-after-write consistency.
### ExistsWritesContext
```go
func (db *Database) ExistsWritesContext(ctx context.Context, query string,
params ...mysql.Params) (bool, error)
```
Context-aware version of `ExistsWrites()`.
## Insert Operations
### Insert
```go
func (db *Database) Insert(table string, data any) error
```
Insert data into table. Automatically chunks large batches based on `max_allowed_packet`.
**Parameters:**
- `table` - Table name
- `data` - Single struct, slice of structs, or channel of structs
**Returns:**
- `error` - Insert error
**Examples:**
```go
// Single insert
user := User{Name: "Alice", Email: "alice@example.com"}
err := db.Insert("users", user)
// Batch insert
users := []User{
{Name: "Bob", Email: "bob@example.com"},
{Name: "Charlie", Email: "charlie@example.com"},
}
err := db.Insert("users", users)
// Streaming insert
userCh := make(chan User)
go func() {
for _, u := range users {
userCh <- u
}
close(userCh)
}()
err := db.Insert("users", userCh)
```
### InsertContext
```go
func (db *Database) InsertContext(ctx context.Context, table string, data any) error
```
Context-aware version of `Insert()`.
**Example:**
```go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
err := db.InsertContext(ctx, "users", users)
```
## Upsert Operations
### Upsert
```go
func (db *Database) Upsert(table string, uniqueCols, updateCols []string,
where string, data any) error
```
Perform INSERT ... ON DUPLICATE KEY UPDATE operation.
**Parameters:**
- `table` - Table name
- `uniqueCols` - Columns that define uniqueness (used in conflict detection)
- `updateCols` - Columns to update on duplicate key
- `where` - Optional WHERE clause for conditional update (can be empty)
- `data` - Single struct, slice of structs, or channel of structs
**Returns:**
- `error` - Upsert error
**Examples:**
```go
// Basic upsert on unique email
err := db.Upsert(
"users",
[]string{"email"}, // unique column
[]string{"name", "updated_at"}, // columns to update
"", // no WHERE clause
user,
)
// Upsert with conditional update
err := db.Upsert(
"users",
[]string{"id"},
[]string{"name", "email"},
"updated_at < VALUES(updated_at)", // only update if newer
users,
)
// Batch upsert
err := db.Upsert(
"users",
[]string{"email"},
[]string{"name", "last_login"},
"",
[]User{{Email: "a@example.com", Name: "Alice"}, ...},
)
```
### UpsertContext
```go
func (db *Database) UpsertContext(ctx context.Context, table string, uniqueCols,
updateCols []string, where string, data any) error
```
Context-aware version of `Upsert()`.
## Execute Operations
### Exec
```go
func (db *Database) Exec(query string, params ...mysql.Params) error
```
Execute query without returning results (UPDATE, DELETE, etc.). Uses write pool.
**Parameters:**
- `query` - SQL query with `@@paramName` placeholders
- `params` - Query parameters
**Returns:**
- `error` - Execution error
**Example:**
```go
err := db.Exec("UPDATE `users` SET `active` = @@active WHERE `id` = @@id",
mysql.Params{"active": 1, "id": 123})
err := db.Exec("DELETE FROM `users` WHERE last_login < @@cutoff",
time.Now().Add(-365*24*time.Hour))
```
### ExecContext
```go
func (db *Database) ExecContext(ctx context.Context, query string, params ...mysql.Params) error
```
Context-aware version of `Exec()`.
### ExecResult
```go
func (db *Database) ExecResult(query string, params ...mysql.Params) (sql.Result, error)
```
Execute query and return `sql.Result` for accessing `LastInsertId()` and `RowsAffected()`.
**Returns:**
- `sql.Result` - Execution result
- `error` - Execution error
**Example:**
```go
result, err := db.ExecResult("UPDATE `users` SET `name` = @@name WHERE `id` = @@id",
mysql.Params{"name": "Alice", "id": 1})
if err != nil {
return err
}
rowsAffected, _ := result.RowsAffected()
log.Printf("Updated %d rows", rowsAffected)
```
### ExecResultContext
```go
func (db *Database) ExecResultContext(ctx context.Context, query string,
params ...mysql.Params) (sql.Result, error)
```
Context-aware version of `ExecResult()`.
## Transaction Management
### GetOrCreateTxFromContext
```go
func GetOrCreateTxFromContext(ctx context.Context) (*sql.Tx, func() error, func(), error)
```
Get existing transaction from context or create new one.
**Returns:**
- `*sql.Tx` - Transaction instance
- `func() error` - Commit function
- `func()` - Cancel function (rolls back if not committed)
- `error` - Transaction creation error
**Usage Pattern:**
```go
tx, commit, cancel, err := mysql.GetOrCreateTxFromContext(ctx)
defer cancel() // Always safe to call - rolls back if commit() not called
if err != nil {
return err
}
// Store transaction in context
ctx = mysql.NewContextWithTx(ctx, tx)
// Do database operations...
if err := commit(); err != nil {
return err
}
```
### NewContextWithTx
```go
func NewContextWithTx(ctx context.Context, tx *sql.Tx) context.Context
```
Store transaction in context for use by database operations.
### TxFromContext
```go
func TxFromContext(ctx context.Context) (*sql.Tx, bool)
```
Retrieve transaction from context.
**Returns:**
- `*sql.Tx` - Transaction if present
- `bool` - `true` if transaction exists in context
## Context Management
### NewContext
```go
func NewContext(ctx context.Context, db *Database) context.Context
```
Store database instance in context.
**Example:**
```go
ctx := mysql.NewContext(context.Background(), db)
```
### NewContextWithFunc
```go
func NewContextWithFunc(ctx context.Context, f func() *Database) context.Context
```
Store database factory function in context for lazy initialization.
**Example:**
```go
ctx := mysql.NewContextWithFunc(ctx, sync.OnceValue(func() *Database {
db, err := mysql.New(...)
if err != nil {
panic(err)
}
return db
}))
```
### FromContext
```go
func FromContext(ctx context.Context) *Database
```
Retrieve database from context.
**Returns:**
- `*Database` - Database instance or `nil` if not found
**Example:**
```go
db := mysql.FromContext(ctx)
if db == nil {
return errors.New("database not in context")
}
```
## Caching Configuration
### EnableRedis
```go
func (db *Database) EnableRedis(client *redis.Client)
```
Enable Redis caching with distributed locking.
**Example:**
```go
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
db.EnableRedis(redisClient)
```
### EnableMemcache
```go
func (db *Database) EnableMemcache(client *memcache.Client)
```
Enable Memcached caching.
**Example:**
```go
memcacheClient := memcache.New("localhost:11211")
db.EnableMemcache(memcacheClient)
```
### UseCache
```go
func (db *Database) UseCache(cache Cache)
```
Use custom cache implementation.
**Examples:**
```go
// In-memory cache
db.UseCache(mysql.NewWeakCache())
// Multi-level cache
db.UseCache(mysql.NewMultiCache(
mysql.NewWeakCache(), // L1: Local fast cache
mysql.NewRedisCache(redisClient), // L2: Distributed cache
))
```
### NewWeakCache
```go
func NewWeakCache() *WeakCache
```
Create in-memory cache with weak pointers (GC-managed).
### NewRedisCache
```go
func NewRedisCache(client *redis.Client) *RedisCache
```
Create Redis cache with distributed locking support.
### NewMultiCache
```go
func NewMultiCache(caches ...Cache) *MultiCache
```
Create layered cache that checks caches in order.
## Parameter Interpolation
### InterpolateParams
```go
func (db *Database) InterpolateParams(query string, params ...mysql.Params) (string, []any, error)
```
Manually interpolate parameters in query. Useful for debugging or logging.
**Parameters:**
- `query` - Query with `@@paramName` placeholders
- `params` - Parameters to interpolate
**Returns:**
- `string` - Query with `?` placeholders
- `[]any` - Normalized parameter values
- `error` - Interpolation error
**Example:**
```go
replacedQuery, normalizedParams, err := db.InterpolateParams(
"SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `id` = @@id",
mysql.Params{"id": 1},
)
// replacedQuery: "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `id` = ?"
// normalizedParams: []any{1}
```
## Template Functions
### AddTemplateFuncs
```go
func (db *Database) AddTemplateFuncs(funcs template.FuncMap)
```
Add custom functions available in query templates.
**Example:**
```go
db.AddTemplateFuncs(template.FuncMap{
"upper": strings.ToUpper,
"lower": strings.ToLower,
})
db.Select(&users,
"SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `name` = @@name{{ if .UpperCase }} COLLATE utf8mb4_bin{{ end }}",
0,
mysql.Params{"name": "alice", "upperCase": true})
```
## Special Types
### Params
```go
type Params map[string]any
```
Parameter map for query placeholders.
### Raw
```go
type Raw string
```
Literal SQL that won't be escaped. **Use with caution - SQL injection risk.**
**Example:**
```go
db.Select(&users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE @@condition", 0,
mysql.Params{
"condition": mysql.Raw("created_at > NOW() - INTERVAL 1 DAY"),
})
```
### MapRow / SliceRow / MapRows / SliceRows
```go
type MapRow map[string]any
type SliceRow []any
type MapRows []map[string]any
type SliceRows [][]any
```
Flexible result types when struct mapping isn't needed.
**Example:**
```go
var rows mysql.MapRows
db.Select(&rows, "SELECT `id`, name FROM `users`", 0)
for _, row := range rows {
fmt.Printf("ID: %v, Name: %v\n", row["id"], row["name"])
}
```
## Custom Interfaces
### Zeroer
```go
type Zeroer interface {
IsZero() bool
}
```
Implement for custom zero-value detection with `defaultzero` tag.
**Example:**
```go
type CustomTime struct {
time.Time
}
func (ct CustomTime) IsZero() bool {
return ct.Time.IsZero() || ct.Time.Unix() == 0
}
```
### Valueser
```go
type Valueser interface {
Values() []any
}
```
Implement for custom value conversion during inserts.
**Example:**
```go
type Point struct {
X, Y float64
}
func (p Point) Values() []any {
return []any{p.X, p.Y}
}
```
## Error Handling
### Automatic Retries
cool-mysql automatically retries these MySQL error codes:
- `1213` - Deadlock detected
- `1205` - Lock wait timeout exceeded
- `2006` - MySQL server has gone away
- `2013` - Lost connection to MySQL server during query
Retry behavior uses exponential backoff and can be configured with `COOL_MAX_ATTEMPTS` environment variable.
### sql.ErrNoRows
- **Single value/struct queries**: Returns `sql.ErrNoRows` when no results
- **Slice queries**: Returns empty slice (not `sql.ErrNoRows`)
**Example:**
```go
var name string
err := db.Select(&name, "SELECT `name` FROM `users` WHERE `id` = @@id", 0,
999)
if errors.Is(err, sql.ErrNoRows) {
// Handle not found
}
var users []User
err := db.Select(&users, "SELECT `id`, `name`, `email`, `age`, `active`, `created_at`, `updated_at` FROM `users` WHERE `id` = @@id", 0,
999)
// err is nil, users is empty slice []
```