github.com/jackc/pgx/v5@v5.5.5/large_objects_test.go (about) 1 package pgx_test 2 3 import ( 4 "context" 5 "io" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/jackc/pgx/v5" 11 "github.com/jackc/pgx/v5/pgconn" 12 "github.com/jackc/pgx/v5/pgxtest" 13 ) 14 15 func TestLargeObjects(t *testing.T) { 16 // We use a very short limit to test chunking logic. 17 pgx.SetMaxLargeObjectMessageLength(t, 2) 18 19 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 20 defer cancel() 21 22 conn, err := pgx.Connect(ctx, os.Getenv("PGX_TEST_DATABASE")) 23 if err != nil { 24 t.Fatal(err) 25 } 26 27 pgxtest.SkipCockroachDB(t, conn, "Server does support large objects") 28 29 tx, err := conn.Begin(ctx) 30 if err != nil { 31 t.Fatal(err) 32 } 33 34 testLargeObjects(t, ctx, tx) 35 } 36 37 func TestLargeObjectsSimpleProtocol(t *testing.T) { 38 // We use a very short limit to test chunking logic. 39 pgx.SetMaxLargeObjectMessageLength(t, 2) 40 41 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 42 defer cancel() 43 44 config, err := pgx.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) 45 if err != nil { 46 t.Fatal(err) 47 } 48 49 config.DefaultQueryExecMode = pgx.QueryExecModeSimpleProtocol 50 51 conn, err := pgx.ConnectConfig(ctx, config) 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 pgxtest.SkipCockroachDB(t, conn, "Server does support large objects") 57 58 tx, err := conn.Begin(ctx) 59 if err != nil { 60 t.Fatal(err) 61 } 62 63 testLargeObjects(t, ctx, tx) 64 } 65 66 func testLargeObjects(t *testing.T, ctx context.Context, tx pgx.Tx) { 67 lo := tx.LargeObjects() 68 69 id, err := lo.Create(ctx, 0) 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 obj, err := lo.Open(ctx, id, pgx.LargeObjectModeRead|pgx.LargeObjectModeWrite) 75 if err != nil { 76 t.Fatal(err) 77 } 78 79 n, err := obj.Write([]byte("testing")) 80 if err != nil { 81 t.Fatal(err) 82 } 83 if n != 7 { 84 t.Errorf("Expected n to be 7, got %d", n) 85 } 86 87 pos, err := obj.Seek(1, 0) 88 if err != nil { 89 t.Fatal(err) 90 } 91 if pos != 1 { 92 t.Errorf("Expected pos to be 1, got %d", pos) 93 } 94 95 res := make([]byte, 6) 96 n, err = obj.Read(res) 97 if err != nil { 98 t.Fatal(err) 99 } 100 if string(res) != "esting" { 101 t.Errorf(`Expected res to be "esting", got %q`, res) 102 } 103 if n != 6 { 104 t.Errorf("Expected n to be 6, got %d", n) 105 } 106 107 n, err = obj.Read(res) 108 if err != io.EOF { 109 t.Error("Expected io.EOF, go nil") 110 } 111 if n != 0 { 112 t.Errorf("Expected n to be 0, got %d", n) 113 } 114 115 pos, err = obj.Tell() 116 if err != nil { 117 t.Fatal(err) 118 } 119 if pos != 7 { 120 t.Errorf("Expected pos to be 7, got %d", pos) 121 } 122 123 err = obj.Truncate(1) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 pos, err = obj.Seek(-1, 2) 129 if err != nil { 130 t.Fatal(err) 131 } 132 if pos != 0 { 133 t.Errorf("Expected pos to be 0, got %d", pos) 134 } 135 136 res = make([]byte, 2) 137 n, err = obj.Read(res) 138 if err != io.EOF { 139 t.Errorf("Expected err to be io.EOF, got %v", err) 140 } 141 if n != 1 { 142 t.Errorf("Expected n to be 1, got %d", n) 143 } 144 if res[0] != 't' { 145 t.Errorf("Expected res[0] to be 't', got %v", res[0]) 146 } 147 148 err = obj.Close() 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 err = lo.Unlink(ctx, id) 154 if err != nil { 155 t.Fatal(err) 156 } 157 158 _, err = lo.Open(ctx, id, pgx.LargeObjectModeRead) 159 if e, ok := err.(*pgconn.PgError); !ok || e.Code != "42704" { 160 t.Errorf("Expected undefined_object error (42704), got %#v", err) 161 } 162 } 163 164 func TestLargeObjectsMultipleTransactions(t *testing.T) { 165 // We use a very short limit to test chunking logic. 166 pgx.SetMaxLargeObjectMessageLength(t, 2) 167 168 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 169 defer cancel() 170 171 conn, err := pgx.Connect(ctx, os.Getenv("PGX_TEST_DATABASE")) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 pgxtest.SkipCockroachDB(t, conn, "Server does support large objects") 177 178 tx, err := conn.Begin(ctx) 179 if err != nil { 180 t.Fatal(err) 181 } 182 183 lo := tx.LargeObjects() 184 185 id, err := lo.Create(ctx, 0) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 obj, err := lo.Open(ctx, id, pgx.LargeObjectModeWrite) 191 if err != nil { 192 t.Fatal(err) 193 } 194 195 n, err := obj.Write([]byte("testing")) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if n != 7 { 200 t.Errorf("Expected n to be 7, got %d", n) 201 } 202 203 // Commit the first transaction 204 err = tx.Commit(ctx) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 // IMPORTANT: Use the same connection for another query 210 query := `select n from generate_series(1,10) n` 211 rows, err := conn.Query(ctx, query) 212 if err != nil { 213 t.Fatal(err) 214 } 215 rows.Close() 216 217 // Start a new transaction 218 tx2, err := conn.Begin(ctx) 219 if err != nil { 220 t.Fatal(err) 221 } 222 223 lo2 := tx2.LargeObjects() 224 225 // Reopen the large object in the new transaction 226 obj2, err := lo2.Open(ctx, id, pgx.LargeObjectModeRead|pgx.LargeObjectModeWrite) 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 pos, err := obj2.Seek(1, 0) 232 if err != nil { 233 t.Fatal(err) 234 } 235 if pos != 1 { 236 t.Errorf("Expected pos to be 1, got %d", pos) 237 } 238 239 res := make([]byte, 6) 240 n, err = obj2.Read(res) 241 if err != nil { 242 t.Fatal(err) 243 } 244 if string(res) != "esting" { 245 t.Errorf(`Expected res to be "esting", got %q`, res) 246 } 247 if n != 6 { 248 t.Errorf("Expected n to be 6, got %d", n) 249 } 250 251 n, err = obj2.Read(res) 252 if err != io.EOF { 253 t.Error("Expected io.EOF, go nil") 254 } 255 if n != 0 { 256 t.Errorf("Expected n to be 0, got %d", n) 257 } 258 259 pos, err = obj2.Tell() 260 if err != nil { 261 t.Fatal(err) 262 } 263 if pos != 7 { 264 t.Errorf("Expected pos to be 7, got %d", pos) 265 } 266 267 err = obj2.Truncate(1) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 pos, err = obj2.Seek(-1, 2) 273 if err != nil { 274 t.Fatal(err) 275 } 276 if pos != 0 { 277 t.Errorf("Expected pos to be 0, got %d", pos) 278 } 279 280 res = make([]byte, 2) 281 n, err = obj2.Read(res) 282 if err != io.EOF { 283 t.Errorf("Expected err to be io.EOF, got %v", err) 284 } 285 if n != 1 { 286 t.Errorf("Expected n to be 1, got %d", n) 287 } 288 if res[0] != 't' { 289 t.Errorf("Expected res[0] to be 't', got %v", res[0]) 290 } 291 292 err = obj2.Close() 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 err = lo2.Unlink(ctx, id) 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 _, err = lo2.Open(ctx, id, pgx.LargeObjectModeRead) 303 if e, ok := err.(*pgconn.PgError); !ok || e.Code != "42704" { 304 t.Errorf("Expected undefined_object error (42704), got %#v", err) 305 } 306 }