Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,63 @@ if err != nil {
`RunInTx` commits when the callback returns `nil` and rolls back when it returns an error. Nested `RunInTx` calls use savepoints on dialects that support them.
Inside a nested callback, call patterns should return errors instead of calling `Commit`/`Rollback` directly.

## Read Replicas

Rain can route builder-based reads to replicas while keeping writes, raw SQL, and transactions on primary:

```go
primaryDB, err := rain.Open("postgres", "postgres://user:pass@localhost/primary")
if err != nil {
panic(err)
}

read1, err := rain.Open("postgres", "postgres://user:pass@localhost/read1")
if err != nil {
panic(err)
}

read2, err := rain.Open("postgres", "postgres://user:pass@localhost/read2")
if err != nil {
panic(err)
}

db, err := rain.WithReplicas(primaryDB, []*rain.DB{read1, read2}, nil)
if err != nil {
panic(err)
}
defer func() { _ = db.Close() }()

var replicaRows []User
if err := db.Select().
Table(Users).
Where(Users.Active.Eq(true)).
Scan(context.Background(), &replicaRows); err != nil {
panic(err)
}

var primaryRows []User
if err := db.Primary().Select().
Table(Users).
Where(Users.Active.Eq(true)).
Scan(context.Background(), &primaryRows); err != nil {
panic(err)
}

// Writes stay on primary.
if _, err := db.Insert().
Table(Users).
Model(&User{Email: "replica-aware@example.com", Name: "Replica Aware"}).
Exec(context.Background()); err != nil {
panic(err)
}
```

Notes:
- `Select()` uses a replica by default.
- `Primary().Select()` forces reads to the primary database.
- `Insert`, `Update`, `Delete`, `Exec`, `Query`, `QueryRow`, `Begin`, and `RunInTx` always use primary.
- v1 does not hide replica lag automatically; use `Primary()` when you need read-after-write consistency.

## Opt-in Query Cache (v1)

Rain supports opt-in caching for `SELECT` helpers (`Scan`, `Count`, and `Exists`). Caching is disabled unless you set a cache backend on `DB`.
Expand Down
19 changes: 18 additions & 1 deletion examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ var Posts = schema.Define("posts", func(t *PostsTable) {
})

func main() {
db, err := rain.Open("postgres", "postgres://example")
primaryDB, err := rain.Open("postgres", "postgres://primary")
if err != nil {
panic(err)
}

readReplica, err := rain.Open("postgres", "postgres://read-replica")
if err != nil {
panic(err)
}

db, err := rain.WithReplicas(primaryDB, []*rain.DB{readReplica}, nil)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -94,9 +104,16 @@ func main() {
Where(Users.ID.Eq(int64(99))).
ToSQL()

primarySelectSQL, primarySelectArgs, _ := db.Primary().Select().
Table(Users).
Column(Users.ID, Users.Email).
Limit(5).
ToSQL()

fmt.Println(selectSQL, selectArgs)
fmt.Println(aggSQL, aggArgs)
fmt.Println(insertSQL, insertArgs)
fmt.Println(updateSQL, updateArgs)
fmt.Println(deleteSQL, deleteArgs)
fmt.Println(primarySelectSQL, primarySelectArgs)
}
4 changes: 2 additions & 2 deletions pkg/rain/query_runtime_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestSelectQueryCacheArgsAndManualInvalidation(t *testing.T) {

counter := &countingRunner{base: db}
queryFor := func(email string) *SelectQuery {
return (&SelectQuery{runner: counter, dialect: db.Dialect(), cache: db.queryCache}).
return (&SelectQuery{runner: counter, dialect: db.Dialect(), cache: db.queryCache()}).
Table(users).
Where(users.Email.Eq(email)).
Cache(QueryCacheOptions{TTL: 5 * time.Minute, Tags: []string{"users"}})
Expand Down Expand Up @@ -269,7 +269,7 @@ func TestSelectAggregateCacheForCountAndExists(t *testing.T) {
}

counter := &countingRunner{base: db}
query := (&SelectQuery{runner: counter, dialect: db.Dialect(), cache: db.queryCache}).
query := (&SelectQuery{runner: counter, dialect: db.Dialect(), cache: db.queryCache()}).
Table(users).
Where(users.Email.Eq("agg@example.com")).
Cache(QueryCacheOptions{TTL: time.Minute, Tags: []string{"users"}})
Expand Down
Loading
Loading