Skip to content

Commit 3af9652

Browse files
authored
Merge pull request #25 from PostHog/fix/create-temporary-table-command-tag
Fix CREATE TEMPORARY TABLE command tag
2 parents 2bbc723 + 6095673 commit 3af9652

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)