Skip to content

Commit 59f1cad

Browse files
authored
Merge pull request #23405 from cockroachdb/2026-05-28-DOC-17061-remote_include-example-apps
Vendor example app includes as local files
2 parents 9763402 + c47489d commit 59f1cad

216 files changed

Lines changed: 3789 additions & 603 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "bank"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
openssl = "0.10.41"
8+
postgres-openssl = "0.5.0"
9+
chrono = "0.4.22"
10+
11+
[dependencies.postgres]
12+
version = "0.19.3"
13+
features = [
14+
"with-uuid-1"
15+
]
16+
17+
[dependencies.uuid]
18+
version = "1.1.2"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// Runs op inside a transaction and retries it as needed.
2+
/// On non-retryable failures, the transaction is aborted and
3+
/// rolled back; on success, the transaction is committed.
4+
fn execute_txn<T, F>(client: &mut Client, op: F) -> Result<T, Error>
5+
where
6+
F: Fn(&mut Transaction) -> Result<T, Error>,
7+
{
8+
let mut txn = client.transaction()?;
9+
loop {
10+
// Set a retry savepoint
11+
// See https://www.cockroachlabs.com/docs/stable/advanced-client-side-transaction-retries
12+
let mut sp = txn.savepoint("cockroach_restart")?;
13+
match op(&mut sp).and_then(|t| sp.commit().map(|_| t)) {
14+
Err(ref err)
15+
if err
16+
.code()
17+
.map(|e| *e == SqlState::T_R_SERIALIZATION_FAILURE)
18+
.unwrap_or(false) => {}
19+
r => break r,
20+
}
21+
}
22+
.and_then(|t| txn.commit().map(|_| t))
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fn transfer_funds(txn: &mut Transaction, from: Uuid, to: Uuid, amount: i64) -> Result<(), Error> {
2+
// Read the balance.
3+
let from_balance: i64 = txn
4+
.query_one("SELECT balance FROM accounts WHERE id = $1", &[&from])?
5+
.get(0);
6+
7+
assert!(from_balance >= amount);
8+
9+
// Perform the transfer.
10+
txn.execute(
11+
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
12+
&[&amount, &from],
13+
)?;
14+
txn.execute(
15+
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
16+
&[&amount, &to],
17+
)?;
18+
Ok(())
19+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"math/rand"
8+
"os"
9+
"time"
10+
11+
"github.com/cockroachdb/cockroach-go/v2/crdb/crdbgorm"
12+
"github.com/google/uuid"
13+
"gorm.io/driver/postgres"
14+
"gorm.io/gorm"
15+
)
16+
17+
// Account is our model, which corresponds to the "accounts" table
18+
type Account struct {
19+
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
20+
Balance int
21+
}
22+
23+
// The `acctIDs` global variable tracks the random IDs generated by `addAccounts`
24+
var acctIDs []uuid.UUID
25+
26+
// Insert new rows into the "accounts" table
27+
// This function generates new UUIDs and random balances for each row, and
28+
// then it appends the ID to the `acctIDs`, which other functions use to track the IDs
29+
func addAccounts(db *gorm.DB, numRows int, transferAmount int) error {
30+
log.Printf("Creating %d new accounts...", numRows)
31+
for i := 0; i < numRows; i++ {
32+
newID := uuid.New()
33+
newBalance := rand.Intn(10000) + transferAmount
34+
if err := db.Create(&Account{ID: newID, Balance: newBalance}).Error; err != nil {
35+
return err
36+
}
37+
acctIDs = append(acctIDs, newID)
38+
}
39+
log.Println("Accounts created.")
40+
return nil
41+
}
42+
43+
// Transfer funds between accounts
44+
// This function adds `amount` to the "balance" column of the row with the "id" column matching `toID`,
45+
// and removes `amount` from the "balance" column of the row with the "id" column matching `fromID`
46+
func transferFunds(db *gorm.DB, fromID uuid.UUID, toID uuid.UUID, amount int) error {
47+
log.Printf("Transferring %d from account %s to account %s...", amount, fromID, toID)
48+
var fromAccount Account
49+
var toAccount Account
50+
51+
db.First(&fromAccount, fromID)
52+
db.First(&toAccount, toID)
53+
54+
if fromAccount.Balance < amount {
55+
return fmt.Errorf("account %s balance %d is lower than transfer amount %d", fromAccount.ID, fromAccount.Balance, amount)
56+
}
57+
58+
fromAccount.Balance -= amount
59+
toAccount.Balance += amount
60+
61+
if err := db.Save(&fromAccount).Error; err != nil {
62+
return err
63+
}
64+
if err := db.Save(&toAccount).Error; err != nil {
65+
return err
66+
}
67+
log.Println("Funds transferred.")
68+
return nil
69+
}
70+
71+
// Print IDs and balances for all rows in "accounts" table
72+
func printBalances(db *gorm.DB) {
73+
var accounts []Account
74+
db.Find(&accounts)
75+
fmt.Printf("Balance at '%s':\n", time.Now())
76+
for _, account := range accounts {
77+
fmt.Printf("%s %d\n", account.ID, account.Balance)
78+
}
79+
}
80+
81+
// Delete all rows in "accounts" table inserted by `main` (i.e., tracked by `acctIDs`)
82+
func deleteAccounts(db *gorm.DB, accountIDs []uuid.UUID) error {
83+
log.Println("Deleting accounts created...")
84+
err := db.Where("id IN ?", accountIDs).Delete(Account{}).Error
85+
if err != nil {
86+
return err
87+
}
88+
log.Println("Accounts deleted.")
89+
return nil
90+
}
91+
92+
func main() {
93+
94+
db, err := gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")+"&application_name=$ docs_simplecrud_gorm"), &gorm.Config{})
95+
if err != nil {
96+
log.Fatal(err)
97+
}
98+
99+
// Automatically create the "accounts" table based on the `Account`
100+
// model.
101+
db.AutoMigrate(&Account{})
102+
103+
// The number of initial rows to insert
104+
const numAccts int = 5
105+
106+
// The amount to be transferred between two accounts.
107+
const transferAmt int = 100
108+
109+
// Insert `numAccts` rows into the "accounts" table.
110+
// To handle potential transaction retry errors, we wrap the call
111+
// to `addAccounts` in `crdbgorm.ExecuteTx`, a helper function for
112+
// GORM which implements a retry loop
113+
if err := crdbgorm.ExecuteTx(context.Background(), db, nil,
114+
func(tx *gorm.DB) error {
115+
return addAccounts(db, numAccts, transferAmt)
116+
},
117+
); err != nil {
118+
// For information and reference documentation, see:
119+
// https://www.cockroachlabs.com/docs/stable/error-handling-and-troubleshooting.html
120+
fmt.Println(err)
121+
}
122+
123+
// Print balances before transfer.
124+
printBalances(db)
125+
126+
// Select two account IDs
127+
fromID := acctIDs[0]
128+
toID := acctIDs[0:][rand.Intn(len(acctIDs))]
129+
130+
// Transfer funds between accounts. To handle potential
131+
// transaction retry errors, we wrap the call to `transferFunds`
132+
// in `crdbgorm.ExecuteTx`
133+
if err := crdbgorm.ExecuteTx(context.Background(), db, nil,
134+
func(tx *gorm.DB) error {
135+
return transferFunds(tx, fromID, toID, transferAmt)
136+
},
137+
); err != nil {
138+
// For information and reference documentation, see:
139+
// https://www.cockroachlabs.com/docs/stable/error-handling-and-troubleshooting.html
140+
fmt.Println(err)
141+
}
142+
143+
// Print balances after transfer to ensure that it worked.
144+
printBalances(db)
145+
146+
// Delete all accounts created by the earlier call to `addAccounts`
147+
// To handle potential transaction retry errors, we wrap the call
148+
// to `deleteAccounts` in `crdbgorm.ExecuteTx`
149+
if err := crdbgorm.ExecuteTx(context.Background(), db, nil,
150+
func(tx *gorm.DB) error {
151+
return deleteAccounts(db, acctIDs)
152+
},
153+
); err != nil {
154+
// For information and reference documentation, see:
155+
// https://www.cockroachlabs.com/docs/stable/error-handling-and-troubleshooting.html
156+
fmt.Println(err)
157+
}
158+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"os"
7+
8+
"github.com/cockroachdb/cockroach-go/v2/crdb/crdbpgxv5"
9+
"github.com/google/uuid"
10+
"github.com/jackc/pgx/v5"
11+
)
12+
13+
func initTable(ctx context.Context, tx pgx.Tx) error {
14+
// Dropping existing table if it exists
15+
log.Println("Drop existing accounts table if necessary.")
16+
if _, err := tx.Exec(ctx, "DROP TABLE IF EXISTS accounts"); err != nil {
17+
return err
18+
}
19+
20+
// Create the accounts table
21+
log.Println("Creating accounts table.")
22+
if _, err := tx.Exec(ctx,
23+
"CREATE TABLE accounts (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), balance INT8)"); err != nil {
24+
return err
25+
}
26+
return nil
27+
}
28+
29+
func insertRows(ctx context.Context, tx pgx.Tx, accts [4]uuid.UUID) error {
30+
// Insert four rows into the "accounts" table.
31+
log.Println("Creating new rows...")
32+
if _, err := tx.Exec(ctx,
33+
"INSERT INTO accounts (id, balance) VALUES ($1, $2), ($3, $4), ($5, $6), ($7, $8)", accts[0], 250, accts[1], 100, accts[2], 500, accts[3], 300); err != nil {
34+
return err
35+
}
36+
return nil
37+
}
38+
39+
func printBalances(conn *pgx.Conn) error {
40+
rows, err := conn.Query(context.Background(), "SELECT id, balance FROM accounts")
41+
if err != nil {
42+
log.Fatal(err)
43+
}
44+
defer rows.Close()
45+
for rows.Next() {
46+
var id uuid.UUID
47+
var balance int
48+
if err := rows.Scan(&id, &balance); err != nil {
49+
log.Fatal(err)
50+
}
51+
log.Printf("%s: %d\n", id, balance)
52+
}
53+
return nil
54+
}
55+
56+
func transferFunds(ctx context.Context, tx pgx.Tx, from uuid.UUID, to uuid.UUID, amount int) error {
57+
// Read the balance.
58+
var fromBalance int
59+
if err := tx.QueryRow(ctx,
60+
"SELECT balance FROM accounts WHERE id = $1", from).Scan(&fromBalance); err != nil {
61+
return err
62+
}
63+
64+
if fromBalance < amount {
65+
log.Println("insufficient funds")
66+
}
67+
68+
// Perform the transfer.
69+
log.Printf("Transferring funds from account with ID %s to account with ID %s...", from, to)
70+
if _, err := tx.Exec(ctx,
71+
"UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from); err != nil {
72+
return err
73+
}
74+
if _, err := tx.Exec(ctx,
75+
"UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to); err != nil {
76+
return err
77+
}
78+
return nil
79+
}
80+
81+
func deleteRows(ctx context.Context, tx pgx.Tx, one uuid.UUID, two uuid.UUID) error {
82+
// Delete two rows into the "accounts" table.
83+
log.Printf("Deleting rows with IDs %s and %s...", one, two)
84+
if _, err := tx.Exec(ctx,
85+
"DELETE FROM accounts WHERE id IN ($1, $2)", one, two); err != nil {
86+
return err
87+
}
88+
return nil
89+
}
90+
91+
func main() {
92+
// Read in connection string
93+
config, err := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
94+
if err != nil {
95+
log.Fatal(err)
96+
}
97+
config.RuntimeParams["application_name"] = "$ docs_simplecrud_gopgx"
98+
conn, err := pgx.ConnectConfig(context.Background(), config)
99+
if err != nil {
100+
log.Fatal(err)
101+
}
102+
defer conn.Close(context.Background())
103+
104+
// Set up table
105+
err = crdbpgx.ExecuteTx(context.Background(), conn, pgx.TxOptions{}, func(tx pgx.Tx) error {
106+
return initTable(context.Background(), tx)
107+
})
108+
109+
// Insert initial rows
110+
var accounts [4]uuid.UUID
111+
for i := 0; i < len(accounts); i++ {
112+
accounts[i] = uuid.New()
113+
}
114+
115+
err = crdbpgx.ExecuteTx(context.Background(), conn, pgx.TxOptions{}, func(tx pgx.Tx) error {
116+
return insertRows(context.Background(), tx, accounts)
117+
})
118+
if err == nil {
119+
log.Println("New rows created.")
120+
} else {
121+
log.Fatal("error: ", err)
122+
}
123+
124+
// Print out the balances
125+
log.Println("Initial balances:")
126+
printBalances(conn)
127+
128+
// Run a transfer
129+
err = crdbpgx.ExecuteTx(context.Background(), conn, pgx.TxOptions{}, func(tx pgx.Tx) error {
130+
return transferFunds(context.Background(), tx, accounts[2], accounts[1], 100)
131+
})
132+
if err == nil {
133+
log.Println("Transfer successful.")
134+
} else {
135+
log.Fatal("error: ", err)
136+
}
137+
138+
// Print out the balances
139+
log.Println("Balances after transfer:")
140+
printBalances(conn)
141+
142+
// Delete rows
143+
err = crdbpgx.ExecuteTx(context.Background(), conn, pgx.TxOptions{}, func(tx pgx.Tx) error {
144+
return deleteRows(context.Background(), tx, accounts[0], accounts[1])
145+
})
146+
if err == nil {
147+
log.Println("Rows deleted.")
148+
} else {
149+
log.Fatal("error: ", err)
150+
}
151+
152+
// Print out the balances
153+
log.Println("Balances after deletion:")
154+
printBalances(conn)
155+
}

0 commit comments

Comments
 (0)