Skip to content

Commit 3551718

Browse files
committed
fix: update PostgreSQL refcursor handling documentation for clarity and accuracy
1 parent ca8e663 commit 3551718

1 file changed

Lines changed: 9 additions & 13 deletions

File tree

skills/o2p-dbmigration/references/postgres-refcursor-handling.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ When migrating from Oracle to PostgreSQL, a critical difference exists in how **
77
## The Core Difference
88

99
**Oracle Behavior:**
10+
1011
- Refcursor output parameters automatically expose their result set to the data reader
1112
- Client code can immediately access result columns
1213
- No additional commands needed
1314

1415
**PostgreSQL Behavior:**
16+
1517
- Refcursor output parameters return a **cursor name** (e.g., `"<unnamed portal 1>"`)
1618
- The cursor remains open in the database session
1719
- Client must execute `FETCH ALL FROM "<cursor_name>"` to retrieve actual data
@@ -27,10 +29,10 @@ System.IndexOutOfRangeException: Field not found in row: <column_name>
2729

2830
This occurs because the data reader contains only the refcursor parameter itself, not the actual query results.
2931

30-
3132
## Database Stored Procedure Pattern
3233

3334
### Oracle Stored Procedure
35+
3436
```sql
3537
CREATE OR REPLACE PROCEDURE get_users(
3638
p_department_id IN NUMBER,
@@ -46,10 +48,11 @@ END;
4648
```
4749

4850
### PostgreSQL Stored Procedure
51+
4952
```sql
5053
CREATE OR REPLACE PROCEDURE get_users(
5154
p_department_id IN INTEGER,
52-
cur_result INOUT refcursor
55+
cur_result OUT refcursor
5356
)
5457
LANGUAGE plpgsql
5558
AS $$
@@ -63,13 +66,12 @@ END;
6366
$$;
6467
```
6568

66-
**Key Difference:** PostgreSQL returns the cursor itself as an `INOUT` parameter, not the result set.
67-
68-
69+
**Key Difference:** PostgreSQL returns the cursor itself as an `OUT` parameter, not the result set.
6970

7071
## Client Code Solution (C#)
7172

7273
### Problematic Oracle-Style Code
74+
7375
This code works with Oracle but fails with PostgreSQL:
7476

7577
```csharp
@@ -260,8 +262,6 @@ public IEnumerable<User> GetUsers(int departmentId)
260262
}
261263
```
262264

263-
264-
265265
## Transactional Context
266266

267267
When working within transactions, ensure the `FETCH` command uses the same transaction:
@@ -312,11 +312,10 @@ public IEnumerable<User> GetUsersInTransaction(
312312
}
313313
```
314314

315-
316-
317315
## Debugging Tips
318316

319317
### Before Fix - What You'll See
318+
320319
When inspecting the data reader after executing a refcursor procedure without proper unwrapping:
321320

322321
```csharp
@@ -330,6 +329,7 @@ var userId = reader.GetInt32(reader.GetOrdinal("user_id"));
330329
```
331330

332331
### After Fix - What You Should See
332+
333333
After properly fetching from the cursor:
334334

335335
```csharp
@@ -343,8 +343,6 @@ Console.WriteLine($"Field 2: {reader.GetName(2)}"); // Output: "email"
343343
var userId = reader.GetInt32(reader.GetOrdinal("user_id")); // Works correctly
344344
```
345345

346-
347-
348346
## Comparison Table: Oracle vs. PostgreSQL Refcursor Handling
349347

350348
| Aspect | Oracle (ODP.NET) | PostgreSQL (Npgsql) |
@@ -356,8 +354,6 @@ var userId = reader.GetInt32(reader.GetOrdinal("user_id")); // Works correctly
356354
| **Cursor Lifetime** | Managed automatically by driver | Remains open; must manage explicitly |
357355
| **Transaction Handling** | Transparent | `FETCH` must use same transaction context |
358356

359-
360-
361357
## Best Practices
362358

363359
1. **Centralize refcursor handling** - Create a generic helper method to avoid duplicating unwrapping logic across your codebase

0 commit comments

Comments
 (0)