| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- package pgserver
- import (
- "testing"
- "github.com/danfragoso/pizzasql-next/pkg/storage"
- "github.com/danfragoso/pizzasql-next/pkg/testkv"
- )
- func simpleQueryMessage(sql string) *Message {
- return &Message{Type: MsgQuery, Data: append([]byte(sql), 0)}
- }
- // A row constraint that only the commit can detect (a column and its CHECK were
- // published after the row was staged) must report its SQLSTATE, leave the wire
- // session idle, and let the client's cleanup ROLLBACK complete.
- func TestConstraintViolationAtCommitClearsTransactionState(t *testing.T) {
- tests := []struct {
- name string
- ddl string
- code string
- }{
- {
- name: "check",
- ddl: "ALTER TABLE t ADD COLUMN qty INTEGER DEFAULT -1 CHECK (qty >= 0)",
- code: ErrCodeCheckViolation,
- },
- {
- name: "not null",
- ddl: "ALTER TABLE t ADD COLUMN note TEXT NOT NULL",
- code: "23502",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- kv := testkv.New(t)
- pool := kv.Pool(8)
- t.Cleanup(func() { pool.Close() })
- schema := storage.NewSchemaManager(pool, "pg_commit_constraints")
- table := storage.NewTableManager(pool, schema, "pg_commit_constraints")
- writer, writerClient := newDataConnectionFor(t, schema, table)
- ddl, ddlClient := newDataConnectionFor(t, schema, table)
- runQuery(t, writer, writerClient, simpleQueryMessage("CREATE TABLE t (id INTEGER PRIMARY KEY)"))
- runQuery(t, writer, writerClient, simpleQueryMessage("BEGIN"))
- runQuery(t, writer, writerClient, simpleQueryMessage("INSERT INTO t (id) VALUES (1)"))
- // The row is only staged, so the new constraint sees an empty table
- // and the commit is where the staged row violates it.
- runQuery(t, ddl, ddlClient, simpleQueryMessage(tt.ddl))
- msgs := runQuery(t, writer, writerClient, simpleQueryMessage("COMMIT"))
- if code := errorCode(msgs[0]); code != tt.code {
- t.Fatalf("COMMIT SQLSTATE = %q, want %q", code, tt.code)
- }
- if writer.txStatus != TxStatusIdle {
- t.Fatalf("tx status = %v, want idle after a failed COMMIT", writer.txStatus)
- }
- // Storage already rolled the transaction back, so the client's
- // cleanup ROLLBACK is a no-op rather than an error.
- msgs = runQuery(t, writer, writerClient, simpleQueryMessage("ROLLBACK"))
- if len(msgs) == 0 || msgs[0].Type == MsgErrorResponse {
- t.Fatalf("ROLLBACK after failed COMMIT = %v, want a successful no-op", msgs)
- }
- if writer.txStatus != TxStatusIdle {
- t.Fatalf("tx status = %v, want idle after the no-op ROLLBACK", writer.txStatus)
- }
- // The session keeps working.
- msgs = runQuery(t, writer, writerClient, simpleQueryMessage("SELECT 1"))
- if len(msgs) < 2 || msgs[0].Type != MsgRowDescription {
- t.Fatalf("SELECT after failed COMMIT = %v, want a result", msgs)
- }
- })
- }
- }
|