Skip to content

Commit 097ee76

Browse files
Spriteclaude
andcommitted
server: handle Describe Portal for declared cursors (psycopg3 compat)
psycopg3's ServerCursor sends Describe Portal with the cursor name after DECLARE CURSOR. In PostgreSQL, declared cursors are accessible as portals, but DuckGres stores them separately in c.cursors. When Describe Portal can't find the name in c.portals, fall back to checking c.cursors and return the cursor's schema. This is needed for psycopg3's ServerCursor.execute() flow which describes the cursor portal to get column metadata before fetching rows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e430e7e commit 097ee76

File tree

1 file changed

+13
-0
lines changed

1 file changed

+13
-0
lines changed

server/conn.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3580,6 +3580,19 @@ func (c *clientConn) handleDescribe(body []byte) {
35803580
// Describe portal
35813581
p, ok := c.portals[name]
35823582
if !ok {
3583+
// In PostgreSQL, DECLARE CURSOR creates a named cursor that is also
3584+
// accessible as a portal. psycopg3's ServerCursor sends Describe Portal
3585+
// with the cursor name after DECLARE. Check c.cursors as fallback.
3586+
if _, cursorOk := c.cursors[name]; cursorOk {
3587+
cols, colTypes, err := c.getCursorSchema(name)
3588+
if err != nil {
3589+
slog.Debug("Describe cursor-as-portal failed to open.", "user", c.username, "cursor", name, "error", err)
3590+
_ = writeNoData(c.writer)
3591+
return
3592+
}
3593+
_ = c.sendRowDescription(cols, colTypes)
3594+
return
3595+
}
35833596
c.sendError("ERROR", "34000", fmt.Sprintf("portal %q does not exist", name))
35843597
return
35853598
}

0 commit comments

Comments
 (0)