or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

connections.mderrors.mdindex.mdlarge-objects.mdnotifications.mdquery-processing.mdquerying.mdreplication.mdtransactions.mdtypes.md

transactions.mddocs/

0

# Transactions

1

2

Complete transaction support including nested transactions via savepoints and two-phase commit preparation.

3

4

## Capabilities

5

6

### Basic Transactions

7

8

Execute multiple queries within a single database transaction with automatic rollback on errors.

9

10

```javascript { .api }

11

/**

12

* Execute function within a transaction

13

* @param fn - Function to execute within transaction scope

14

* @returns Promise resolving to function result

15

*/

16

begin<T>(

17

fn: (sql: TransactionSql) => T | Promise<T>

18

): Promise<T>;

19

20

/**

21

* Execute function within a transaction with options

22

* @param options - Transaction options (isolation level, etc.)

23

* @param fn - Function to execute within transaction scope

24

* @returns Promise resolving to function result

25

*/

26

begin<T>(

27

options: string,

28

fn: (sql: TransactionSql) => T | Promise<T>

29

): Promise<T>;

30

31

interface TransactionSql extends Sql {

32

savepoint<T>(fn: (sql: TransactionSql) => T | Promise<T>): Promise<T>;

33

savepoint<T>(name: string, fn: (sql: TransactionSql) => T | Promise<T>): Promise<T>;

34

prepare(name: string): void;

35

}

36

```

37

38

**Usage Examples:**

39

40

```javascript

41

// Basic transaction

42

const result = await sql.begin(async (sql) => {

43

const user = await sql`

44

INSERT INTO users (name, email)

45

VALUES (${name}, ${email})

46

RETURNING id

47

`;

48

49

await sql`

50

INSERT INTO user_profiles (user_id, bio)

51

VALUES (${user[0].id}, ${bio})

52

`;

53

54

return user[0];

55

});

56

57

// Transaction with options

58

const result = await sql.begin("ISOLATION LEVEL SERIALIZABLE", async (sql) => {

59

const balance = await sql`

60

SELECT balance FROM accounts WHERE id = ${accountId}

61

`;

62

63

if (balance[0].balance < amount) {

64

throw new Error("Insufficient funds");

65

}

66

67

await sql`

68

UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}

69

`;

70

71

return balance[0].balance - amount;

72

});

73

74

// Multiple operations

75

const result = await sql.begin(async (sql) => {

76

// All these operations will be in the same transaction

77

const order = await sql`

78

INSERT INTO orders (user_id, total)

79

VALUES (${userId}, ${total})

80

RETURNING id

81

`;

82

83

for (const item of items) {

84

await sql`

85

INSERT INTO order_items (order_id, product_id, quantity, price)

86

VALUES (${order[0].id}, ${item.productId}, ${item.quantity}, ${item.price})

87

`;

88

}

89

90

await sql`

91

UPDATE products

92

SET stock = stock - ${item.quantity}

93

WHERE id = ${item.productId}

94

`;

95

96

return order[0];

97

});

98

```

99

100

### Savepoints (Nested Transactions)

101

102

Create nested transaction scopes with savepoints for granular rollback control.

103

104

```javascript { .api }

105

/**

106

* Create an unnamed savepoint within transaction

107

* @param fn - Function to execute within savepoint scope

108

* @returns Promise resolving to function result

109

*/

110

savepoint<T>(

111

fn: (sql: TransactionSql) => T | Promise<T>

112

): Promise<T>;

113

114

/**

115

* Create a named savepoint within transaction

116

* @param name - Savepoint name for debugging

117

* @param fn - Function to execute within savepoint scope

118

* @returns Promise resolving to function result

119

*/

120

savepoint<T>(

121

name: string,

122

fn: (sql: TransactionSql) => T | Promise<T>

123

): Promise<T>;

124

```

125

126

**Usage Examples:**

127

128

```javascript

129

const result = await sql.begin(async (sql) => {

130

// Main transaction operations

131

const user = await sql`

132

INSERT INTO users (name, email)

133

VALUES (${name}, ${email})

134

RETURNING id

135

`;

136

137

// Nested transaction with savepoint

138

try {

139

await sql.savepoint("user_profile", async (sql) => {

140

await sql`

141

INSERT INTO user_profiles (user_id, bio)

142

VALUES (${user[0].id}, ${bio})

143

`;

144

145

// This might fail, but won't rollback the user insert

146

await sql`

147

INSERT INTO user_settings (user_id, theme)

148

VALUES (${user[0].id}, ${theme})

149

`;

150

});

151

} catch (error) {

152

console.log("Profile creation failed, but user was created");

153

}

154

155

return user[0];

156

});

157

158

// Multiple savepoints

159

const result = await sql.begin(async (sql) => {

160

const order = await sql`

161

INSERT INTO orders (user_id) VALUES (${userId}) RETURNING id

162

`;

163

164

for (const item of items) {

165

try {

166

await sql.savepoint(async (sql) => {

167

// Try to add item to order

168

await sql`

169

INSERT INTO order_items (order_id, product_id, quantity)

170

VALUES (${order[0].id}, ${item.id}, ${item.quantity})

171

`;

172

173

// Update stock

174

await sql`

175

UPDATE products

176

SET stock = stock - ${item.quantity}

177

WHERE id = ${item.id} AND stock >= ${item.quantity}

178

`;

179

});

180

} catch (error) {

181

console.log(`Failed to add item ${item.id}, continuing with other items`);

182

}

183

}

184

185

return order[0];

186

});

187

```

188

189

### Two-Phase Commit

190

191

Prepare transactions for two-phase commit scenarios across multiple databases.

192

193

```javascript { .api }

194

/**

195

* Prepare transaction for two-phase commit

196

* @param name - Transaction identifier for prepare

197

*/

198

prepare(name: string): void;

199

```

200

201

**Usage Examples:**

202

203

```javascript

204

// Prepare transaction for two-phase commit

205

const result = await sql.begin(async (sql) => {

206

await sql`

207

INSERT INTO orders (user_id, total)

208

VALUES (${userId}, ${total})

209

`;

210

211

await sql`

212

UPDATE inventory

213

SET quantity = quantity - ${orderQuantity}

214

WHERE product_id = ${productId}

215

`;

216

217

// Prepare for two-phase commit instead of committing

218

sql.prepare("order_transaction_123");

219

220

return "prepared";

221

});

222

223

// Later, commit or rollback the prepared transaction

224

await sql`COMMIT PREPARED 'order_transaction_123'`;

225

// or

226

await sql`ROLLBACK PREPARED 'order_transaction_123'`;

227

```

228

229

### Error Handling in Transactions

230

231

Understanding how errors are handled within transactions and savepoints.

232

233

**Usage Examples:**

234

235

```javascript

236

try {

237

const result = await sql.begin(async (sql) => {

238

const user = await sql`

239

INSERT INTO users (name, email)

240

VALUES (${name}, ${email})

241

RETURNING id

242

`;

243

244

// This will cause transaction to rollback if it fails

245

await sql`

246

INSERT INTO user_profiles (user_id, bio)

247

VALUES (${user[0].id}, ${bio})

248

`;

249

250

return user[0];

251

});

252

} catch (error) {

253

// Both user and profile inserts will be rolled back

254

console.error("Transaction failed:", error);

255

}

256

257

// Handling errors in savepoints

258

const result = await sql.begin(async (sql) => {

259

const user = await sql`

260

INSERT INTO users (name, email)

261

VALUES (${name}, ${email})

262

RETURNING id

263

`;

264

265

// Try optional operations in savepoint

266

try {

267

await sql.savepoint(async (sql) => {

268

await sql`

269

INSERT INTO user_profiles (user_id, bio)

270

VALUES (${user[0].id}, ${bio})

271

`;

272

273

// If this fails, only the savepoint is rolled back

274

await sql`

275

INSERT INTO user_preferences (user_id, theme)

276

VALUES (${user[0].id}, ${theme})

277

`;

278

});

279

} catch (error) {

280

console.log("Optional operations failed, but user was created");

281

}

282

283

return user[0]; // User will still be created

284

});

285

```

286

287

### Transaction Isolation Levels

288

289

Configure transaction isolation levels for concurrent access control.

290

291

**Usage Examples:**

292

293

```javascript

294

// Serializable isolation

295

await sql.begin("ISOLATION LEVEL SERIALIZABLE", async (sql) => {

296

// Highest isolation level - prevents all phenomena

297

const result = await sql`SELECT * FROM sensitive_data WHERE id = ${id}`;

298

await sql`UPDATE sensitive_data SET value = ${newValue} WHERE id = ${id}`;

299

return result;

300

});

301

302

// Repeatable read isolation

303

await sql.begin("ISOLATION LEVEL REPEATABLE READ", async (sql) => {

304

// Prevents dirty and non-repeatable reads

305

const balance1 = await sql`SELECT balance FROM accounts WHERE id = ${accountId}`;

306

// ... other operations ...

307

const balance2 = await sql`SELECT balance FROM accounts WHERE id = ${accountId}`;

308

// balance1 and balance2 will be the same

309

});

310

311

// Read committed (default)

312

await sql.begin("ISOLATION LEVEL READ COMMITTED", async (sql) => {

313

// Default level - prevents dirty reads only

314

const result = await sql`SELECT * FROM data WHERE updated_at > ${date}`;

315

return result;

316

});

317

318

// Read uncommitted

319

await sql.begin("ISOLATION LEVEL READ UNCOMMITTED", async (sql) => {

320

// Lowest level - allows all phenomena (rarely used)

321

const result = await sql`SELECT COUNT(*) FROM large_table`;

322

return result;

323

});

324

```

325

326

### Transaction Best Practices

327

328

**Performance Considerations:**

329

330

```javascript

331

// Keep transactions short

332

await sql.begin(async (sql) => {

333

// Do database operations quickly

334

const result = await sql`UPDATE users SET last_login = NOW() WHERE id = ${id}`;

335

// Don't do heavy computation here

336

return result;

337

});

338

339

// Batch operations when possible

340

await sql.begin(async (sql) => {

341

// Insert multiple records in one operation

342

await sql`

343

INSERT INTO logs (user_id, action, timestamp)

344

VALUES ${sql(logEntries.map(entry => [entry.userId, entry.action, entry.timestamp]))}

345

`;

346

});

347

348

// Use savepoints for optional operations

349

await sql.begin(async (sql) => {

350

// Critical operation

351

const order = await sql`

352

INSERT INTO orders (user_id, total) VALUES (${userId}, ${total})

353

RETURNING id

354

`;

355

356

// Optional operations that shouldn't fail the transaction

357

try {

358

await sql.savepoint(async (sql) => {

359

await sql`INSERT INTO audit_log (action, order_id) VALUES ('created', ${order[0].id})`;

360

await sql`UPDATE user_stats SET orders_count = orders_count + 1 WHERE user_id = ${userId}`;

361

});

362

} catch (error) {

363

console.warn("Audit operations failed, but order was created");

364

}

365

366

return order[0];

367

});

368

```