or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-configuration.mderror-handling.mdfiltering-conditions.mdindex.mdquery-operations.mdresult-transformation.mdstored-procedures.md

result-transformation.mddocs/

0

# Result Transformation

1

2

Result processing including column selection, ordering, pagination, response formatting (CSV, GeoJSON), and query optimization tools.

3

4

## Capabilities

5

6

### Column Selection for Modifications

7

8

When using INSERT, UPDATE, UPSERT, or DELETE operations, by default the modified rows are not returned. Use select() to return specific columns from the modified data.

9

10

```typescript { .api }

11

/**

12

* Perform a SELECT on the query result. By default, `.insert()`, `.update()`, `.upsert()`,

13

* and `.delete()` do not return modified rows. By calling this method, modified rows are

14

* returned in `data`.

15

* @param columns - The columns to retrieve, separated by commas

16

*/

17

select<Query extends string = '*'>(columns?: Query): PostgrestTransformBuilder<...>;

18

```

19

20

**Usage Examples:**

21

22

```typescript

23

import { PostgrestClient } from "@supabase/postgrest-js";

24

25

const client = new PostgrestClient("https://api.example.com");

26

27

// Return inserted data

28

const { data: newUser } = await client

29

.from("users")

30

.insert({ name: "John Doe", email: "john@example.com" })

31

.select("id, name, email");

32

33

// Return updated data with specific columns

34

const { data: updatedUser } = await client

35

.from("users")

36

.update({ name: "Jane Doe" })

37

.eq("id", 123)

38

.select("id, name, updated_at");

39

40

// Return all columns after upsert

41

const { data: upsertedUser } = await client

42

.from("users")

43

.upsert({ email: "jane@example.com", name: "Jane" })

44

.select("*");

45

46

// Return deleted records

47

const { data: deletedPosts } = await client

48

.from("posts")

49

.delete()

50

.eq("status", "archived")

51

.select("id, title, deleted_at");

52

```

53

54

### Ordering and Sorting

55

56

Control the order of returned results with flexible sorting options.

57

58

```typescript { .api }

59

/**

60

* Order the query result by `column`. You can call this method multiple times to order

61

* by multiple columns. You can order referenced tables, but it only affects the ordering

62

* of the parent table if you use `!inner` in the query.

63

* @param column - The column to order by

64

* @param options - Named parameters

65

* @param options.ascending - Sort direction (default: true)

66

* @param options.nullsFirst - Position of null values (default: false)

67

* @param options.referencedTable - Reference table for ordering

68

* @param options.foreignTable - Foreign table for ordering (deprecated)

69

*/

70

order(

71

column: string,

72

options?: {

73

ascending?: boolean;

74

nullsFirst?: boolean;

75

referencedTable?: string;

76

foreignTable?: string; // deprecated

77

}

78

): this;

79

```

80

81

**Usage Examples:**

82

83

```typescript

84

// Basic ascending order

85

const { data: usersByName } = await client

86

.from("users")

87

.select("*")

88

.order("name");

89

90

// Descending order

91

const { data: newestPosts } = await client

92

.from("posts")

93

.select("*")

94

.order("created_at", { ascending: false });

95

96

// Multiple column ordering

97

const { data: sortedUsers } = await client

98

.from("users")

99

.select("*")

100

.order("department")

101

.order("name");

102

103

// Null handling

104

const { data: usersWithNulls } = await client

105

.from("users")

106

.select("*")

107

.order("last_login", { ascending: false, nullsFirst: true });

108

109

// Order by referenced table

110

const { data: postsWithAuthors } = await client

111

.from("posts")

112

.select(`

113

*,

114

users (

115

id,

116

name

117

)

118

`)

119

.order("created_at", { ascending: false })

120

.order("name", { referencedTable: "users" });

121

122

// Complex ordering

123

const { data: complexSorted } = await client

124

.from("products")

125

.select("*")

126

.order("category")

127

.order("price", { ascending: false })

128

.order("name");

129

```

130

131

### Pagination and Limiting

132

133

Control the number and range of returned results for pagination.

134

135

```typescript { .api }

136

/**

137

* Limit the query result by `count`

138

* @param count - The maximum number of rows to return

139

* @param options - Named parameters

140

* @param options.referencedTable - Reference table for limit

141

* @param options.foreignTable - Foreign table for limit (deprecated)

142

*/

143

limit(

144

count: number,

145

options?: {

146

referencedTable?: string;

147

foreignTable?: string; // deprecated

148

}

149

): this;

150

151

/**

152

* Limit the query result by starting at an offset `from` and ending at the offset `to`.

153

* Only records within this range are returned. This respects the query order and if there

154

* is no order clause the range could behave unexpectedly. The `from` and `to` values are

155

* 0-based and inclusive.

156

* @param from - The starting index from which to limit the result

157

* @param to - The last index to which to limit the result

158

* @param options - Named parameters

159

* @param options.referencedTable - Reference table for range

160

* @param options.foreignTable - Foreign table for range (deprecated)

161

*/

162

range(

163

from: number,

164

to: number,

165

options?: {

166

referencedTable?: string;

167

foreignTable?: string; // deprecated

168

}

169

): this;

170

```

171

172

**Usage Examples:**

173

174

```typescript

175

// Simple limit

176

const { data: first10Users } = await client

177

.from("users")

178

.select("*")

179

.limit(10);

180

181

// Pagination with range (page 2, 10 items per page)

182

const { data: page2Users } = await client

183

.from("users")

184

.select("*")

185

.order("created_at")

186

.range(10, 19); // Items 11-20 (0-based, inclusive)

187

188

// Pagination with limit and offset pattern

189

const page = 3;

190

const pageSize = 20;

191

const { data: page3Users } = await client

192

.from("users")

193

.select("*")

194

.order("id")

195

.range((page - 1) * pageSize, page * pageSize - 1);

196

197

// Limit on referenced table

198

const { data: usersWithRecentPosts } = await client

199

.from("users")

200

.select(`

201

*,

202

posts (

203

id,

204

title,

205

created_at

206

)

207

`)

208

.limit(5) // Limit users

209

.limit(3, { referencedTable: "posts" }); // Limit posts per user

210

211

// Range on referenced table

212

const { data: usersWithPostRange } = await client

213

.from("users")

214

.select(`

215

*,

216

posts (

217

id,

218

title

219

)

220

`)

221

.range(0, 9) // First 10 users

222

.range(0, 4, { referencedTable: "posts" }); // First 5 posts per user

223

```

224

225

### Response Format Control

226

227

Transform the response format for different data consumption needs.

228

229

```typescript { .api }

230

/**

231

* Return `data` as a single object instead of an array of objects. Query result must be

232

* one row (e.g. using `.limit(1)`), otherwise this returns an error.

233

*/

234

single(): PostgrestBuilder<ClientOptions, ResultOne>;

235

236

/**

237

* Return `data` as a single object instead of an array of objects. Query result must be

238

* zero or one row (e.g. using `.limit(1)`), otherwise this returns an error.

239

*/

240

maybeSingle(): PostgrestBuilder<ClientOptions, ResultOne | null>;

241

242

/**

243

* Return `data` as a string in CSV format

244

*/

245

csv(): PostgrestBuilder<ClientOptions, string>;

246

247

/**

248

* Return `data` as an object in GeoJSON format

249

*/

250

geojson(): PostgrestBuilder<ClientOptions, Record<string, unknown>>;

251

```

252

253

**Usage Examples:**

254

255

```typescript

256

// Get single record (must return exactly 1 row)

257

const { data: user } = await client

258

.from("users")

259

.select("*")

260

.eq("id", 123)

261

.single(); // data is User object, not User[]

262

263

// Get single record or null (0 or 1 rows)

264

const { data: user } = await client

265

.from("users")

266

.select("*")

267

.eq("email", "might-not-exist@example.com")

268

.maybeSingle(); // data is User | null

269

270

// Get results as CSV

271

const { data: csvData } = await client

272

.from("users")

273

.select("name, email, created_at")

274

.csv(); // data is string in CSV format

275

276

// Get geographic data as GeoJSON

277

const { data: geoData } = await client

278

.from("locations")

279

.select("*")

280

.geojson(); // data is GeoJSON object

281

282

// Typical single record patterns

283

const { data: currentUser } = await client

284

.from("users")

285

.select("*")

286

.eq("id", userId)

287

.single();

288

289

const { data: latestPost } = await client

290

.from("posts")

291

.select("*")

292

.order("created_at", { ascending: false })

293

.limit(1)

294

.single();

295

```

296

297

### Query Analysis and Debugging

298

299

Tools for analyzing and debugging query performance.

300

301

```typescript { .api }

302

/**

303

* Return `data` as the EXPLAIN plan for the query. You need to enable the db_plan_enabled

304

* setting before using this method.

305

* @param options - Named parameters

306

* @param options.analyze - Include execution statistics

307

* @param options.verbose - Verbose output

308

* @param options.settings - Include settings

309

* @param options.buffers - Include buffer usage

310

* @param options.wal - Include WAL usage

311

* @param options.format - Output format

312

*/

313

explain(

314

options?: {

315

analyze?: boolean;

316

verbose?: boolean;

317

settings?: boolean;

318

buffers?: boolean;

319

wal?: boolean;

320

format?: 'json' | 'text';

321

}

322

): PostgrestBuilder<ClientOptions, Record<string, unknown>[] | string>;

323

```

324

325

**Usage Examples:**

326

327

```typescript

328

// Basic query plan

329

const { data: plan } = await client

330

.from("users")

331

.select("*")

332

.eq("active", true)

333

.explain();

334

335

// Detailed analysis with execution stats

336

const { data: detailedPlan } = await client

337

.from("large_table")

338

.select("*")

339

.gt("created_at", "2023-01-01")

340

.explain({

341

analyze: true,

342

verbose: true,

343

buffers: true

344

});

345

346

// JSON format for programmatic analysis

347

const { data: jsonPlan } = await client

348

.from("complex_query")

349

.select(`

350

*,

351

related_table (

352

id,

353

name

354

)

355

`)

356

.explain({ format: 'json', analyze: true });

357

358

// Text format for human reading

359

const { data: textPlan } = await client

360

.from("performance_test")

361

.select("*")

362

.in("category", ["important", "urgent"])

363

.explain({ format: 'text', analyze: true });

364

```

365

366

### Transaction Control

367

368

Control transaction behavior for testing and development.

369

370

```typescript { .api }

371

/**

372

* Rollback the query. `data` will still be returned, but the query is not committed.

373

*/

374

rollback(): this;

375

```

376

377

**Usage Examples:**

378

379

```typescript

380

// Test insert without committing

381

const { data: testUser } = await client

382

.from("users")

383

.insert({ name: "Test User", email: "test@example.com" })

384

.select()

385

.rollback(); // Data returned but not committed

386

387

// Test complex operation

388

const { data: results } = await client

389

.from("orders")

390

.update({ status: "processed" })

391

.eq("payment_status", "completed")

392

.select()

393

.rollback(); // See what would be updated without committing

394

```

395

396

### Request Control

397

398

Control request behavior and cancellation.

399

400

```typescript { .api }

401

/**

402

* Set the AbortSignal for the fetch request

403

* @param signal - The AbortSignal to use for the fetch request

404

*/

405

abortSignal(signal: AbortSignal): this;

406

```

407

408

**Usage Examples:**

409

410

```typescript

411

// Request cancellation

412

const controller = new AbortController();

413

414

// Cancel request after 5 seconds

415

setTimeout(() => controller.abort(), 5000);

416

417

const { data, error } = await client

418

.from("large_table")

419

.select("*")

420

.abortSignal(controller.signal);

421

422

// User-initiated cancellation

423

const searchController = new AbortController();

424

425

const searchPromise = client

426

.from("documents")

427

.select("*")

428

.textSearch("content", "search terms")

429

.abortSignal(searchController.signal);

430

431

// Cancel if user starts new search

432

function handleNewSearch() {

433

searchController.abort();

434

// Start new search...

435

}

436

```

437

438

### PostgREST 13+ Features

439

440

Advanced features available in PostgREST version 13 and later.

441

442

```typescript { .api }

443

/**

444

* Set the maximum number of rows that can be affected by the query. Only available in

445

* PostgREST v13+ and only works with PATCH and DELETE methods.

446

* @param value - The maximum number of rows that can be affected

447

*/

448

maxAffected(value: number): this;

449

```

450

451

**Usage Examples:**

452

453

```typescript

454

// Prevent accidental mass updates

455

const { data, error } = await client

456

.from("users")

457

.update({ updated_at: new Date().toISOString() })

458

.eq("active", true)

459

.maxAffected(100) // Fail if more than 100 rows would be affected

460

.select();

461

462

// Safe delete with limit

463

const { data, error } = await client

464

.from("old_logs")

465

.delete()

466

.lt("created_at", "2022-01-01")

467

.maxAffected(1000) // Prevent deleting more than 1000 rows

468

.select("id");

469

470

// Batch processing with safety limit

471

const { data, error } = await client

472

.from("pending_emails")

473

.update({ status: "processing" })

474

.eq("status", "queued")

475

.maxAffected(50) // Process max 50 emails at once

476

.select();

477

```

478

479

### Type Override Control

480

481

Override the type of returned data with flexible merge options.

482

483

```typescript { .api }

484

/**

485

* Override the type of the returned `data` field in the response.

486

* @typeParam NewResult - The new type to cast the response data to

487

* @typeParam Options - Optional type configuration (defaults to { merge: true })

488

* @typeParam Options.merge - When true, merges the new type with existing return type. When false, replaces the existing types entirely (defaults to true)

489

*/

490

overrideTypes<

491

NewResult,

492

Options extends { merge?: boolean } = { merge: true }

493

>(): PostgrestBuilder<...>;

494

495

/**

496

* Override the type of the returned `data` (deprecated)

497

* @deprecated Use overrideTypes<yourType, { merge: false }>() method at the end of your call chain instead

498

*/

499

returns<NewResult>(): PostgrestBuilder<...>;

500

```

501

502

**Usage Examples:**

503

504

```typescript

505

// Merge with existing types (default behavior)

506

interface CustomFields {

507

computed_score: number;

508

display_name: string;

509

}

510

511

const { data: enhancedUsers } = await client

512

.from("users")

513

.select("id, name, email") // Original fields: { id, name, email }

514

.overrideTypes<CustomFields>();

515

// Result type: { id, name, email, computed_score, display_name }

516

517

// Replace existing types completely

518

interface SimpleUser {

519

id: number;

520

display_name: string;

521

}

522

523

const { data: simpleUsers } = await client

524

.from("users")

525

.select("*")

526

.overrideTypes<SimpleUser, { merge: false }>();

527

// Result type: { id, display_name } only

528

529

// Override array result types

530

interface ProcessedData {

531

processed_at: string;

532

result: any;

533

}

534

535

const { data: processedResults } = await client

536

.from("raw_data")

537

.select("*")

538

.overrideTypes<ProcessedData[]>(); // Explicit array type

539

540

// Override for single object results

541

const { data: processedUser } = await client

542

.from("users")

543

.select("*")

544

.eq("id", 123)

545

.single()

546

.overrideTypes<CustomUser>(); // Single object override

547

548

// Type-safe merging with complex types

549

interface UserWithMetrics {

550

total_orders: number;

551

last_login: string;

552

preferences: {

553

theme: 'light' | 'dark';

554

notifications: boolean;

555

};

556

}

557

558

const { data: usersWithMetrics } = await client

559

.from("users")

560

.select("id, name, email") // Base fields preserved

561

.overrideTypes<UserWithMetrics>(); // Additional fields merged

562

// Result: { id, name, email, total_orders, last_login, preferences }

563

564

// Error handling with type overrides

565

try {

566

const { data } = await client

567

.from("complex_view")

568

.select("*")

569

.overrideTypes<MyCustomType>()

570

.throwOnError();

571

// data is properly typed as MyCustomType[]

572

} catch (error) {

573

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

574

}

575

```

576

577

### Method Chaining

578

579

All transformation methods can be chained together for complex result processing:

580

581

```typescript

582

// Complex transformation pipeline

583

const { data: processedResults } = await client

584

.from("analytics")

585

.select(`

586

id,

587

event_name,

588

user_id,

589

created_at,

590

metadata,

591

users (

592

id,

593

name,

594

segment

595

)

596

`)

597

.gte("created_at", "2023-01-01")

598

.eq("event_name", "purchase")

599

.order("created_at", { ascending: false })

600

.order("name", { referencedTable: "users" })

601

.limit(100)

602

.range(0, 49) // First 50 results

603

.limit(5, { referencedTable: "users" }); // Limit user data per event

604

```