Skip to content

Commit 6095673

Browse files
EDsCODEclaude
andcommitted
Fix CREATE TEMPORARY TABLE command tag
The getCommandType function was returning "CREATE" instead of "CREATE TABLE" for CREATE TEMPORARY TABLE queries. This caused incorrect command tags to be sent to clients like Fivetran, potentially causing sync failures. Changes: - Fix getCommandType to return "CREATE TABLE" for: - CREATE TEMPORARY TABLE - CREATE TEMP TABLE - CREATE UNLOGGED TABLE - Also fix "CREATE INDEX" for CREATE UNIQUE INDEX - Also fix "CREATE VIEW" for CREATE OR REPLACE VIEW - Add error logging in handleExecute for better debugging - Add Describe operation logging for protocol debugging - Add unit tests for CREATE TEMPORARY TABLE command types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 2bbc723 commit 6095673

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

server/conn.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ func (c *clientConn) handleQuery(body []byte) error {
269269
query := string(bytes.TrimRight(body, "\x00"))
270270
query = strings.TrimSpace(query)
271271

272-
if query == "" {
272+
// Treat empty queries or queries with just semicolons as empty
273+
// PostgreSQL returns EmptyQueryResponse for queries like "" or ";" or ";;;"
274+
if query == "" || isEmptyQuery(query) {
273275
writeEmptyQueryResponse(c.writer)
274276
writeReadyForQuery(c.writer, c.txStatus)
275277
c.writer.Flush()
@@ -383,6 +385,17 @@ func (c *clientConn) handleQuery(body []byte) error {
383385
return nil
384386
}
385387

388+
// isEmptyQuery checks if a query contains only semicolons and whitespace.
389+
// PostgreSQL returns EmptyQueryResponse for queries like ";" or ";;;" or "; ; ;"
390+
func isEmptyQuery(query string) bool {
391+
for _, r := range query {
392+
if r != ';' && r != ' ' && r != '\t' && r != '\n' && r != '\r' {
393+
return false
394+
}
395+
}
396+
return true
397+
}
398+
386399
// stripLeadingComments removes leading SQL comments from a query.
387400
// Handles both block comments /* ... */ and line comments -- ...
388401
func stripLeadingComments(query string) string {
@@ -458,11 +471,16 @@ func (c *clientConn) getCommandType(upperQuery string) string {
458471
return "UPDATE"
459472
case strings.HasPrefix(upperQuery, "DELETE"):
460473
return "DELETE"
461-
case strings.HasPrefix(upperQuery, "CREATE TABLE"):
474+
case strings.HasPrefix(upperQuery, "CREATE TABLE"),
475+
strings.HasPrefix(upperQuery, "CREATE TEMPORARY TABLE"),
476+
strings.HasPrefix(upperQuery, "CREATE TEMP TABLE"),
477+
strings.HasPrefix(upperQuery, "CREATE UNLOGGED TABLE"):
462478
return "CREATE TABLE"
463-
case strings.HasPrefix(upperQuery, "CREATE INDEX"):
479+
case strings.HasPrefix(upperQuery, "CREATE INDEX"),
480+
strings.HasPrefix(upperQuery, "CREATE UNIQUE INDEX"):
464481
return "CREATE INDEX"
465-
case strings.HasPrefix(upperQuery, "CREATE VIEW"):
482+
case strings.HasPrefix(upperQuery, "CREATE VIEW"),
483+
strings.HasPrefix(upperQuery, "CREATE OR REPLACE VIEW"):
466484
return "CREATE VIEW"
467485
case strings.HasPrefix(upperQuery, "CREATE SCHEMA"):
468486
return "CREATE SCHEMA"
@@ -1136,6 +1154,8 @@ func (c *clientConn) handleDescribe(body []byte) {
11361154
c.sendError("ERROR", "26000", fmt.Sprintf("prepared statement %q does not exist", name))
11371155
return
11381156
}
1157+
log.Printf("[%s] Describe statement %q: %s", c.username, name, ps.query)
1158+
11391159
// Send parameter description based on the number of $N placeholders we found
11401160
// If the client didn't send explicit types, create them
11411161
paramTypes := ps.paramTypes
@@ -1150,7 +1170,9 @@ func (c *clientConn) handleDescribe(body []byte) {
11501170

11511171
// For queries that return results, we need to send RowDescription
11521172
// For other queries, send NoData
1153-
if !queryReturnsResults(ps.query) {
1173+
returnsResults := queryReturnsResults(ps.query)
1174+
log.Printf("[%s] Describe statement %q: returnsResults=%v", c.username, name, returnsResults)
1175+
if !returnsResults {
11541176
writeNoData(c.writer)
11551177
return
11561178
}
@@ -1295,6 +1317,7 @@ func (c *clientConn) handleExecute(body []byte) {
12951317
// Non-result-returning query: use Exec with converted query
12961318
result, err := c.db.Exec(p.stmt.convertedQuery, args...)
12971319
if err != nil {
1320+
log.Printf("[%s] Execute error: %v", c.username, err)
12981321
c.sendError("ERROR", "42000", err.Error())
12991322
c.setTxError()
13001323
return
@@ -1308,6 +1331,7 @@ func (c *clientConn) handleExecute(body []byte) {
13081331
// Result-returning query: use Query with converted query
13091332
rows, err := c.db.Query(p.stmt.convertedQuery, args...)
13101333
if err != nil {
1334+
log.Printf("[%s] Query error: %v", c.username, err)
13111335
c.sendError("ERROR", "42000", err.Error())
13121336
c.setTxError()
13131337
return
@@ -1316,6 +1340,7 @@ func (c *clientConn) handleExecute(body []byte) {
13161340

13171341
cols, err := rows.Columns()
13181342
if err != nil {
1343+
log.Printf("[%s] Columns error: %v", c.username, err)
13191344
c.sendError("ERROR", "42000", err.Error())
13201345
c.setTxError()
13211346
return

server/conn_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,35 @@ import (
44
"testing"
55
)
66

7+
func TestIsEmptyQuery(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
query string
11+
expected bool
12+
}{
13+
{"empty string", "", true},
14+
{"single semicolon", ";", true},
15+
{"multiple semicolons", ";;;", true},
16+
{"semicolons with spaces", "; ; ;", true},
17+
{"semicolons with tabs", ";\t;\t;", true},
18+
{"semicolons with newlines", ";\n;\n;", true},
19+
{"only whitespace", " \t\n", true},
20+
{"SELECT query", "SELECT 1", false},
21+
{"SELECT with semicolon", "SELECT 1;", false},
22+
{"comment", "/* comment */", false},
23+
{"semicolon then query", ";SELECT 1", false},
24+
}
25+
26+
for _, tt := range tests {
27+
t.Run(tt.name, func(t *testing.T) {
28+
result := isEmptyQuery(tt.query)
29+
if result != tt.expected {
30+
t.Errorf("isEmptyQuery(%q) = %v, want %v", tt.query, result, tt.expected)
31+
}
32+
})
33+
}
34+
}
35+
736
func TestStripLeadingComments(t *testing.T) {
837
tests := []struct {
938
name string
@@ -112,6 +141,21 @@ func TestGetCommandType(t *testing.T) {
112141
query: "CREATE TABLE users (id INT)",
113142
expected: "CREATE TABLE",
114143
},
144+
{
145+
name: "CREATE TEMPORARY TABLE",
146+
query: "CREATE TEMPORARY TABLE temp_users (id INT)",
147+
expected: "CREATE TABLE",
148+
},
149+
{
150+
name: "CREATE TEMP TABLE",
151+
query: "CREATE TEMP TABLE temp_users (id INT)",
152+
expected: "CREATE TABLE",
153+
},
154+
{
155+
name: "CREATE TEMPORARY TABLE with Fivetran comment",
156+
query: "/*Fivetran*/CREATE TEMPORARY TABLE temp_users (id INT)",
157+
expected: "CREATE TABLE",
158+
},
115159
{
116160
name: "CREATE SCHEMA",
117161
query: "CREATE SCHEMA myschema",

0 commit comments

Comments
 (0)