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  }