portal_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. package pgserver
  2. import (
  3. "bufio"
  4. "bytes"
  5. "strconv"
  6. "testing"
  7. )
  8. // Bounded Execute is only reachable over the raw protocol. pgx v5.11.0 sends
  9. // maxRows = 0 for every Execute (pgconn.ExecParams, ExecPrepared, and the
  10. // Pipeline equivalents have no row-limit argument), and database/sql drives pgx,
  11. // so neither a pgx nor a database/sql test can request a suspended portal. These
  12. // tests therefore drive the protocol messages directly, one round trip per
  13. // Execute.
  14. // portalSession sends extended-protocol messages to a connection backed by a
  15. // real executor and returns the frames the client would receive for that round
  16. // trip.
  17. type portalSession struct {
  18. t *testing.T
  19. c *Connection
  20. conn *countingConn
  21. }
  22. func newPortalSession(t *testing.T) *portalSession {
  23. t.Helper()
  24. c, _ := newDataConnection(t)
  25. conn := &countingConn{}
  26. c.conn = conn
  27. c.writer = bufio.NewWriter(conn)
  28. return &portalSession{t: t, c: c, conn: conn}
  29. }
  30. // send handles the messages, flushes buffered output the way a client's Flush
  31. // message would, and returns the frames produced since the previous send.
  32. func (s *portalSession) send(messages ...*Message) []*Message {
  33. s.t.Helper()
  34. for _, message := range messages {
  35. if err := s.c.handleMessage(message); err != nil {
  36. s.t.Fatalf("handle %c: %v", message.Type, err)
  37. }
  38. }
  39. if err := s.c.handleMessage(&Message{Type: MsgFlush}); err != nil {
  40. s.t.Fatalf("flush: %v", err)
  41. }
  42. frames := readAllMessages(s.t, s.conn.bytes())
  43. s.conn.reset()
  44. return frames
  45. }
  46. // setup runs SQL outside the protocol; the statements are the test fixture, not
  47. // the behavior under test.
  48. func (s *portalSession) setup(statements ...string) {
  49. s.t.Helper()
  50. for _, sql := range statements {
  51. if _, err := s.c.executor.Execute(parseStmt(s.t, sql)); err != nil {
  52. s.t.Fatalf("%s: %v", sql, err)
  53. }
  54. }
  55. }
  56. // countRows reports how many rows a table holds, so a test can prove a DML
  57. // portal ran once.
  58. func (s *portalSession) countRows(table string) int64 {
  59. s.t.Helper()
  60. result, err := s.c.executor.Execute(parseStmt(s.t, "SELECT COUNT(*) FROM "+table))
  61. if err != nil {
  62. s.t.Fatalf("count %s: %v", table, err)
  63. }
  64. return result.Rows[0][0].(int64)
  65. }
  66. func executeRowsMessage(portal string, maxRows int32) *Message {
  67. mb := NewMessageBuilder()
  68. mb.WriteString(portal)
  69. mb.WriteInt32(maxRows)
  70. return &Message{Type: MsgExecute, Data: mb.Bytes()}
  71. }
  72. func closePortalMessage(portal string) *Message {
  73. return &Message{Type: MsgClose, Data: append([]byte("P"+portal), 0)}
  74. }
  75. func queryMessageData(sql string) *Message {
  76. return &Message{Type: MsgQuery, Data: append([]byte(sql), 0)}
  77. }
  78. func syncMessage() *Message {
  79. return &Message{Type: MsgSync}
  80. }
  81. func commandTagOf(message *Message) string {
  82. if message == nil || message.Type != MsgCommandComplete {
  83. return ""
  84. }
  85. if end := bytes.IndexByte(message.Data, 0); end >= 0 {
  86. return string(message.Data[:end])
  87. }
  88. return string(message.Data)
  89. }
  90. func assertFrameTypes(t *testing.T, frames []*Message, want string) {
  91. t.Helper()
  92. if got := messageTypes(frames); got != want {
  93. t.Fatalf("frames = %q, want %q", got, want)
  94. }
  95. }
  96. func assertRowIDs(t *testing.T, frames []*Message, want ...string) {
  97. t.Helper()
  98. rows := dataRowsRaw(t, frames)
  99. if len(rows) != len(want) {
  100. t.Fatalf("got %d rows, want %d", len(rows), len(want))
  101. }
  102. for i, id := range want {
  103. if len(rows[i]) != 1 || string(rows[i][0]) != id {
  104. t.Fatalf("row %d = %q, want %q", i, rows[i], id)
  105. }
  106. }
  107. }
  108. func readyStatus(t *testing.T, frames []*Message) byte {
  109. t.Helper()
  110. for _, message := range frames {
  111. if message.Type == MsgReadyForQuery {
  112. if len(message.Data) != 1 {
  113. t.Fatalf("ReadyForQuery payload = %q", message.Data)
  114. }
  115. return message.Data[0]
  116. }
  117. }
  118. t.Fatalf("no ReadyForQuery in %q", messageTypes(frames))
  119. return 0
  120. }
  121. const portalSelectSQL = "SELECT id FROM t ORDER BY id"
  122. func createNumberedTable(t *testing.T, s *portalSession, rows int) {
  123. t.Helper()
  124. s.setup("CREATE TABLE t (id INTEGER PRIMARY KEY)")
  125. for i := 1; i <= rows; i++ {
  126. s.setup("INSERT INTO t (id) VALUES (" + strconv.Itoa(i) + ")")
  127. }
  128. }
  129. // A bounded Execute returns at most maxRows rows and suspends; each later
  130. // Execute resumes where the previous one stopped. The RowDescription is sent by
  131. // the first Execute only, and Describe('P') still answers NoData without
  132. // disturbing that sequence.
  133. func TestBoundedExecuteSuspendsAndResumes(t *testing.T) {
  134. s := newPortalSession(t)
  135. createNumberedTable(t, s, 5)
  136. frames := s.send(
  137. parseMessage("stmt", portalSelectSQL),
  138. bindResultMessage("p", "stmt", nil, nil),
  139. &Message{Type: MsgDescribe, Data: []byte("Pp\x00")},
  140. )
  141. assertFrameTypes(t, frames, "12n")
  142. first := s.send(executeRowsMessage("p", 2))
  143. assertFrameTypes(t, first, "TDDs")
  144. assertRowIDs(t, first, "1", "2")
  145. second := s.send(executeRowsMessage("p", 2))
  146. assertFrameTypes(t, second, "DDs")
  147. assertRowIDs(t, second, "3", "4")
  148. third := s.send(executeRowsMessage("p", 2))
  149. assertFrameTypes(t, third, "DC")
  150. assertRowIDs(t, third, "5")
  151. if tag := commandTagOf(third[len(third)-1]); tag != "SELECT 5" {
  152. t.Fatalf("completion tag = %q, want %q", tag, "SELECT 5")
  153. }
  154. // A completed portal repeats its tag without re-running the statement.
  155. fourth := s.send(executeRowsMessage("p", 0))
  156. assertFrameTypes(t, fourth, "C")
  157. if tag := commandTagOf(fourth[0]); tag != "SELECT 5" {
  158. t.Fatalf("repeated completion tag = %q, want %q", tag, "SELECT 5")
  159. }
  160. // Completion drops the row data, so an exhausted portal only keeps the
  161. // metadata a repeated Execute answers with.
  162. if rows := s.c.portals["p"].execution.result.Rows; len(rows) != 0 {
  163. t.Fatalf("completed portal still holds %d rows", len(rows))
  164. }
  165. }
  166. // maxRows = 0 keeps the pre-existing meaning: every remaining row in one
  167. // Execute followed by CommandComplete.
  168. func TestExecuteUnboundedStreamsAllRows(t *testing.T) {
  169. s := newPortalSession(t)
  170. createNumberedTable(t, s, 3)
  171. s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  172. frames := s.send(executeRowsMessage("p", 0))
  173. assertFrameTypes(t, frames, "TDDDC")
  174. assertRowIDs(t, frames, "1", "2", "3")
  175. if tag := commandTagOf(frames[len(frames)-1]); tag != "SELECT 3" {
  176. t.Fatalf("completion tag = %q, want %q", tag, "SELECT 3")
  177. }
  178. }
  179. // A limit that falls exactly on the last row still suspends, as PostgreSQL
  180. // does; the following Execute reports completion with no further rows.
  181. func TestBoundedExecuteExactLimitSuspends(t *testing.T) {
  182. s := newPortalSession(t)
  183. createNumberedTable(t, s, 3)
  184. s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  185. frames := s.send(executeRowsMessage("p", 3))
  186. assertFrameTypes(t, frames, "TDDDs")
  187. assertRowIDs(t, frames, "1", "2", "3")
  188. frames = s.send(executeRowsMessage("p", 0))
  189. assertFrameTypes(t, frames, "C")
  190. if tag := commandTagOf(frames[0]); tag != "SELECT 3" {
  191. t.Fatalf("completion tag = %q, want %q", tag, "SELECT 3")
  192. }
  193. }
  194. // Close('P') destroys a suspended portal, and a later Execute reports 34000
  195. // until Sync recovers the cycle.
  196. func TestCloseEndsSuspendedPortal(t *testing.T) {
  197. s := newPortalSession(t)
  198. createNumberedTable(t, s, 3)
  199. s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  200. assertFrameTypes(t, s.send(executeRowsMessage("p", 1)), "TDs")
  201. assertFrameTypes(t, s.send(closePortalMessage("p")), "3")
  202. frames := s.send(executeRowsMessage("p", 1))
  203. assertFrameTypes(t, frames, "E")
  204. if code := errorCode(findError(frames)); code != ErrCodeUndefinedCursor {
  205. t.Fatalf("SQLSTATE = %q, want %q", code, ErrCodeUndefinedCursor)
  206. }
  207. assertFrameTypes(t, s.send(syncMessage()), "Z")
  208. if s.c.extendedFailed {
  209. t.Fatal("Sync did not clear the extended-failure state")
  210. }
  211. }
  212. // Re-binding a portal name replaces the materialized result, the row cursor,
  213. // and the row metadata: the stream starts over.
  214. func TestRebindRestartsPortal(t *testing.T) {
  215. s := newPortalSession(t)
  216. createNumberedTable(t, s, 5)
  217. s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  218. assertFrameTypes(t, s.send(executeRowsMessage("p", 2)), "TDDs")
  219. frames := s.send(
  220. bindResultMessage("p", "stmt", nil, nil),
  221. executeRowsMessage("p", 0),
  222. )
  223. assertFrameTypes(t, frames, "2TDDDDDC")
  224. assertRowIDs(t, frames, "1", "2", "3", "4", "5")
  225. }
  226. // Inside an explicit transaction a portal survives Sync and keeps resuming;
  227. // COMMIT and ROLLBACK end the transaction and with it every portal.
  228. func TestPortalLivesUntilTransactionEnd(t *testing.T) {
  229. for _, ending := range []string{"COMMIT", "ROLLBACK"} {
  230. t.Run(ending, func(t *testing.T) {
  231. s := newPortalSession(t)
  232. createNumberedTable(t, s, 3)
  233. if status := readyStatus(t, s.send(queryMessageData("BEGIN"))); status != TxStatusInBlock {
  234. t.Fatalf("BEGIN status = %c, want %c", status, TxStatusInBlock)
  235. }
  236. frames := s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  237. assertFrameTypes(t, frames, "12")
  238. frames = s.send(executeRowsMessage("p", 2), syncMessage())
  239. assertFrameTypes(t, frames, "TDDsZ")
  240. if status := readyStatus(t, frames); status != TxStatusInBlock {
  241. t.Fatalf("Sync inside the transaction reported status %c, want %c", status, TxStatusInBlock)
  242. }
  243. frames = s.send(executeRowsMessage("p", 0), syncMessage())
  244. assertFrameTypes(t, frames, "DCZ")
  245. assertRowIDs(t, frames, "3")
  246. if status := readyStatus(t, s.send(queryMessageData(ending))); status != TxStatusIdle {
  247. t.Fatalf("%s status = %c, want %c", ending, status, TxStatusIdle)
  248. }
  249. frames = s.send(executeRowsMessage("p", 1))
  250. assertFrameTypes(t, frames, "E")
  251. if code := errorCode(findError(frames)); code != ErrCodeUndefinedCursor {
  252. t.Fatalf("SQLSTATE after %s = %q, want %q", ending, code, ErrCodeUndefinedCursor)
  253. }
  254. s.send(syncMessage())
  255. })
  256. }
  257. }
  258. // Outside an explicit block the portal belongs to the implicit transaction that
  259. // Sync ends, so Execute afterwards reports 34000 (PostgreSQL behaves the same);
  260. // the prepared statement survives and can be bound again.
  261. func TestPortalEndsAtSyncOutsideTransaction(t *testing.T) {
  262. s := newPortalSession(t)
  263. createNumberedTable(t, s, 3)
  264. frames := s.send(parseMessage("stmt", portalSelectSQL), bindResultMessage("p", "stmt", nil, nil))
  265. assertFrameTypes(t, frames, "12")
  266. frames = s.send(executeRowsMessage("p", 1), syncMessage())
  267. assertFrameTypes(t, frames, "TDsZ")
  268. frames = s.send(executeRowsMessage("p", 1))
  269. assertFrameTypes(t, frames, "E")
  270. if code := errorCode(findError(frames)); code != ErrCodeUndefinedCursor {
  271. t.Fatalf("SQLSTATE = %q, want %q", code, ErrCodeUndefinedCursor)
  272. }
  273. s.send(syncMessage())
  274. frames = s.send(bindResultMessage("p", "stmt", nil, nil), executeRowsMessage("p", 0))
  275. assertFrameTypes(t, frames, "2TDDDC")
  276. assertRowIDs(t, frames, "1", "2", "3")
  277. }
  278. // A suspended INSERT ... RETURNING runs the statement exactly once: the resume
  279. // streams the prefetched rows, and a completed portal never runs it again.
  280. func TestBoundedExecuteDMLEvaluatesOnce(t *testing.T) {
  281. s := newPortalSession(t)
  282. s.setup("CREATE TABLE t (id INTEGER PRIMARY KEY)")
  283. frames := s.send(
  284. parseMessage("stmt", "INSERT INTO t (id) VALUES (1), (2), (3) RETURNING id"),
  285. bindResultMessage("p", "stmt", nil, nil),
  286. )
  287. assertFrameTypes(t, frames, "12")
  288. frames = s.send(executeRowsMessage("p", 2))
  289. assertFrameTypes(t, frames, "TDDs")
  290. assertRowIDs(t, frames, "1", "2")
  291. frames = s.send(executeRowsMessage("p", 0))
  292. // The autocommit INSERT publishes its commit LSN with the completion.
  293. assertFrameTypes(t, frames, "DCS")
  294. assertRowIDs(t, frames, "3")
  295. if tag := commandTagOf(frames[len(frames)-2]); tag != "INSERT 0 3" {
  296. t.Fatalf("completion tag = %q, want %q", tag, "INSERT 0 3")
  297. }
  298. frames = s.send(executeRowsMessage("p", 0))
  299. assertFrameTypes(t, frames, "C")
  300. if rows := s.countRows("t"); rows != 3 {
  301. t.Fatalf("table has %d rows after three Executes, want 3", rows)
  302. }
  303. }
  304. // Per-portal result formats keep applying on every Execute of the same portal,
  305. // and only the first Execute carries the RowDescription.
  306. func TestPortalResumeKeepsBinaryResultFormats(t *testing.T) {
  307. s := newPortalSession(t)
  308. s.setup(
  309. "CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT)",
  310. "INSERT INTO t VALUES (1, 'one')",
  311. "INSERT INTO t VALUES (2, 'two')",
  312. )
  313. frames := s.send(
  314. parseMessage("stmt", "SELECT id, name FROM t ORDER BY id"),
  315. bindResultMessage("p", "stmt", nil, []int16{1}),
  316. )
  317. assertFrameTypes(t, frames, "12")
  318. frames = s.send(executeRowsMessage("p", 1))
  319. assertFrameTypes(t, frames, "TDs")
  320. descriptions := 0
  321. for _, message := range frames {
  322. if message.Type == MsgRowDescription {
  323. descriptions++
  324. formats := rowDescriptionFormats(t, message.Data)
  325. if len(formats) != 2 || formats[0] != 1 || formats[1] != 1 {
  326. t.Fatalf("RowDescription formats = %v, want binary for both columns", formats)
  327. }
  328. }
  329. }
  330. if descriptions != 1 {
  331. t.Fatalf("got %d RowDescriptions, want 1", descriptions)
  332. }
  333. rows := dataRowsRaw(t, frames)
  334. assertBytes(t, "first id", rows[0][0], []byte{0, 0, 0, 1})
  335. frames = s.send(executeRowsMessage("p", 1))
  336. assertFrameTypes(t, frames, "Ds")
  337. rows = dataRowsRaw(t, frames)
  338. assertBytes(t, "second id", rows[0][0], []byte{0, 0, 0, 2})
  339. frames = s.send(executeRowsMessage("p", 1))
  340. assertFrameTypes(t, frames, "C")
  341. if tag := commandTagOf(frames[0]); tag != "SELECT 2" {
  342. t.Fatalf("completion tag = %q, want %q", tag, "SELECT 2")
  343. }
  344. }
  345. // Execute of a portal that was never bound is 34000, like PostgreSQL's
  346. // "portal does not exist".
  347. func TestExecuteUnknownPortalIsUndefinedCursor(t *testing.T) {
  348. s := newPortalSession(t)
  349. frames := s.send(executeRowsMessage("missing", 0))
  350. assertFrameTypes(t, frames, "E")
  351. if code := errorCode(findError(frames)); code != ErrCodeUndefinedCursor {
  352. t.Fatalf("SQLSTATE = %q, want %q", code, ErrCodeUndefinedCursor)
  353. }
  354. s.send(syncMessage())
  355. }
  356. // A failed Execute leaves the transaction aborted: a later Execute yields 25P02
  357. // until ROLLBACK, which also destroys the portals.
  358. func TestFailedExecuteLeavesTransactionConsistent(t *testing.T) {
  359. s := newPortalSession(t)
  360. s.setup("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
  361. if status := readyStatus(t, s.send(queryMessageData("BEGIN"))); status != TxStatusInBlock {
  362. t.Fatalf("BEGIN status = %c, want %c", status, TxStatusInBlock)
  363. }
  364. s.send(
  365. parseMessage("failing", "INSERT INTO t (id, name) VALUES (1, NULL)"),
  366. bindResultMessage("bad", "failing", nil, nil),
  367. )
  368. frames := s.send(executeRowsMessage("bad", 0))
  369. assertFrameTypes(t, frames, "E")
  370. if code := errorCode(findError(frames)); code != "23502" {
  371. t.Fatalf("SQLSTATE = %q, want 23502", code)
  372. }
  373. if status := readyStatus(t, s.send(syncMessage())); status != TxStatusFailed {
  374. t.Fatalf("status after failure = %c, want %c", status, TxStatusFailed)
  375. }
  376. s.send(
  377. parseMessage("reading", portalSelectSQL),
  378. bindResultMessage("ok", "reading", nil, nil),
  379. )
  380. frames = s.send(executeRowsMessage("ok", 0))
  381. assertFrameTypes(t, frames, "E")
  382. if code := errorCode(findError(frames)); code != ErrCodeTransactionAborted {
  383. t.Fatalf("SQLSTATE in the aborted transaction = %q, want %q", code, ErrCodeTransactionAborted)
  384. }
  385. if status := readyStatus(t, s.send(syncMessage())); status != TxStatusFailed {
  386. t.Fatalf("status after aborted Execute = %c, want %c", status, TxStatusFailed)
  387. }
  388. if status := readyStatus(t, s.send(queryMessageData("ROLLBACK"))); status != TxStatusIdle {
  389. t.Fatalf("ROLLBACK status = %c, want %c", status, TxStatusIdle)
  390. }
  391. frames = s.send(executeRowsMessage("ok", 0))
  392. assertFrameTypes(t, frames, "E")
  393. if code := errorCode(findError(frames)); code != ErrCodeUndefinedCursor {
  394. t.Fatalf("SQLSTATE after ROLLBACK = %q, want %q", code, ErrCodeUndefinedCursor)
  395. }
  396. s.send(syncMessage())
  397. }