|
|
@@ -0,0 +1,89 @@
|
|
|
+package pgserver
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "net"
|
|
|
+ "testing"
|
|
|
+)
|
|
|
+
|
|
|
+func runMessageCycle(t *testing.T, c *Connection, client net.Conn, messages ...*Message) []*Message {
|
|
|
+ t.Helper()
|
|
|
+ done := make(chan error, 1)
|
|
|
+ go func() {
|
|
|
+ for _, msg := range messages {
|
|
|
+ if err := c.handleMessage(msg); err != nil {
|
|
|
+ done <- err
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ done <- nil
|
|
|
+ }()
|
|
|
+ r := bufio.NewReader(client)
|
|
|
+ var response []*Message
|
|
|
+ for {
|
|
|
+ msg, err := ReadMessage(r)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ response = append(response, msg)
|
|
|
+ if msg.Type == MsgReadyForQuery {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err := <-done; err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ return response
|
|
|
+}
|
|
|
+
|
|
|
+func TestSyntaxErrorAbortsSimpleAndExtendedTransaction(t *testing.T) {
|
|
|
+ for _, extended := range []bool{false, true} {
|
|
|
+ c, client := newDataConnection(t)
|
|
|
+ runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("BEGIN\x00")})
|
|
|
+ var response []*Message
|
|
|
+ if extended {
|
|
|
+ response = runExtendedExecution(t, c, client, "SELEC invalid")
|
|
|
+ } else {
|
|
|
+ response = runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("SELEC invalid\x00")})
|
|
|
+ }
|
|
|
+ if errorCode(response[0]) != "42601" || c.txStatus != TxStatusFailed {
|
|
|
+ t.Fatalf("extended=%v did not abort on syntax error", extended)
|
|
|
+ }
|
|
|
+ response = runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("SELECT 1\x00")})
|
|
|
+ if errorCode(response[0]) != "25P02" {
|
|
|
+ t.Fatal("failed transaction accepted SELECT")
|
|
|
+ }
|
|
|
+ runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("ROLLBACK\x00")})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestProxyErrorRelayTransactionAndSyncRecovery(t *testing.T) {
|
|
|
+ for _, extended := range []bool{false, true} {
|
|
|
+ c, client := newDataConnection(t)
|
|
|
+ for _, sql := range []string{"CREATE TABLE t (id INTEGER PRIMARY KEY)", "BEGIN", "INSERT INTO t VALUES (1)", "SAVEPOINT s"} {
|
|
|
+ runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte(sql + "\x00")})
|
|
|
+ }
|
|
|
+ kind := byte(MsgQuery)
|
|
|
+ if extended {
|
|
|
+ kind = MsgParse
|
|
|
+ }
|
|
|
+ messages := []*Message{{Type: MsgProxyError, Data: []byte{kind}}}
|
|
|
+ if extended {
|
|
|
+ c.portals[""] = &portal{query: "INSERT INTO t VALUES (2)"}
|
|
|
+ messages = append(messages, &Message{Type: MsgExecute, Data: []byte{0, 0, 0, 0, 0}}, &Message{Type: MsgSync})
|
|
|
+ }
|
|
|
+ response := runMessageCycle(t, c, client, messages...)
|
|
|
+ if errorCode(response[0]) != "42501" {
|
|
|
+ t.Fatalf("extended=%v missing permission error", extended)
|
|
|
+ }
|
|
|
+ if last := response[len(response)-1]; last.Type != MsgReadyForQuery || string(last.Data) != "E" {
|
|
|
+ t.Fatalf("extended=%v false ready state: %v", extended, last)
|
|
|
+ }
|
|
|
+ runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("ROLLBACK TO SAVEPOINT s\x00")})
|
|
|
+ response = runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("SELECT COUNT(*) FROM t\x00")})
|
|
|
+ if row := dataRowValues(t, response[1]); len(row) != 1 || row[0] != "1" {
|
|
|
+ t.Fatalf("relay lost earlier writes or executed discarded messages: %v", row)
|
|
|
+ }
|
|
|
+ runQuery(t, c, client, &Message{Type: MsgQuery, Data: []byte("ROLLBACK\x00")})
|
|
|
+ }
|
|
|
+}
|