...

Source file src/github.com/lib/pq/encode_test.go

Documentation: github.com/lib/pq

     1  package pq
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql"
     6  	"fmt"
     7  	"regexp"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/lib/pq/oid"
    12  )
    13  
    14  func TestScanTimestamp(t *testing.T) {
    15  	var nt NullTime
    16  	tn := time.Now()
    17  	nt.Scan(tn)
    18  	if !nt.Valid {
    19  		t.Errorf("Expected Valid=false")
    20  	}
    21  	if nt.Time != tn {
    22  		t.Errorf("Time value mismatch")
    23  	}
    24  }
    25  
    26  func TestScanNilTimestamp(t *testing.T) {
    27  	var nt NullTime
    28  	nt.Scan(nil)
    29  	if nt.Valid {
    30  		t.Errorf("Expected Valid=false")
    31  	}
    32  }
    33  
    34  var timeTests = []struct {
    35  	str     string
    36  	timeval time.Time
    37  }{
    38  	{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
    39  	{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
    40  	{"0001-12-31 BC", time.Date(0, time.December, 31, 0, 0, 0, 0, time.FixedZone("", 0))},
    41  	{"2001-02-03 BC", time.Date(-2000, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
    42  	{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
    43  	{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
    44  	{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
    45  	{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
    46  	{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
    47  	{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
    48  	{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
    49  	{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
    50  	{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
    51  	{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
    52  	{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
    53  	{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
    54  	{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
    55  		time.FixedZone("", -7*60*60))},
    56  	{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
    57  		time.FixedZone("", -7*60*60))},
    58  	{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
    59  		time.FixedZone("", -(7*60*60+42*60)))},
    60  	{"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
    61  		time.FixedZone("", -(7*60*60+30*60+9)))},
    62  	{"2001-02-03 04:05:06+07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
    63  		time.FixedZone("", +(7*60*60+30*60+9)))},
    64  	{"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
    65  		time.FixedZone("", 7*60*60))},
    66  	{"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
    67  	{"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
    68  	{"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
    69  		time.FixedZone("", -7*60*60))},
    70  	{"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
    71  	{"0001-02-03 04:05:06.123 BC", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
    72  	{"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
    73  	{"0002-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
    74  	{"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
    75  	{"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
    76  	{"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
    77  }
    78  
    79  // Test that parsing the string results in the expected value.
    80  func TestParseTs(t *testing.T) {
    81  	for i, tt := range timeTests {
    82  		val, err := ParseTimestamp(nil, tt.str)
    83  		if err != nil {
    84  			t.Errorf("%d: got error: %v", i, err)
    85  		} else if val.String() != tt.timeval.String() {
    86  			t.Errorf("%d: expected to parse %q into %q; got %q",
    87  				i, tt.str, tt.timeval, val)
    88  		}
    89  	}
    90  }
    91  
    92  var timeErrorTests = []string{
    93  	"BC",
    94  	" BC",
    95  	"2001",
    96  	"2001-2-03",
    97  	"2001-02-3",
    98  	"2001-02-03 ",
    99  	"2001-02-03 B",
   100  	"2001-02-03 04",
   101  	"2001-02-03 04:",
   102  	"2001-02-03 04:05",
   103  	"2001-02-03 04:05 B",
   104  	"2001-02-03 04:05 BC",
   105  	"2001-02-03 04:05:",
   106  	"2001-02-03 04:05:6",
   107  	"2001-02-03 04:05:06 B",
   108  	"2001-02-03 04:05:06BC",
   109  	"2001-02-03 04:05:06.123 B",
   110  }
   111  
   112  // Test that parsing the string results in an error.
   113  func TestParseTsErrors(t *testing.T) {
   114  	for i, tt := range timeErrorTests {
   115  		_, err := ParseTimestamp(nil, tt)
   116  		if err == nil {
   117  			t.Errorf("%d: expected an error from parsing: %v", i, tt)
   118  		}
   119  	}
   120  }
   121  
   122  // Now test that sending the value into the database and parsing it back
   123  // returns the same time.Time value.
   124  func TestEncodeAndParseTs(t *testing.T) {
   125  	db, err := openTestConnConninfo("timezone='Etc/UTC'")
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	defer db.Close()
   130  
   131  	for i, tt := range timeTests {
   132  		var dbstr string
   133  		err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
   134  		if err != nil {
   135  			t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
   136  			continue
   137  		}
   138  
   139  		val, err := ParseTimestamp(nil, dbstr)
   140  		if err != nil {
   141  			t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
   142  			continue
   143  		}
   144  		val = val.In(tt.timeval.Location())
   145  		if val.String() != tt.timeval.String() {
   146  			t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
   147  		}
   148  	}
   149  }
   150  
   151  var formatTimeTests = []struct {
   152  	time     time.Time
   153  	expected string
   154  }{
   155  	{time.Time{}, "0001-01-01 00:00:00Z"},
   156  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
   157  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
   158  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
   159  	{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
   160  
   161  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
   162  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
   163  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
   164  
   165  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
   166  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
   167  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
   168  
   169  	{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
   170  	{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
   171  }
   172  
   173  func TestFormatTs(t *testing.T) {
   174  	for i, tt := range formatTimeTests {
   175  		val := string(formatTs(tt.time))
   176  		if val != tt.expected {
   177  			t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
   178  		}
   179  	}
   180  }
   181  
   182  func TestFormatTsBackend(t *testing.T) {
   183  	db := openTestConn(t)
   184  	defer db.Close()
   185  
   186  	var str string
   187  	err := db.QueryRow("SELECT '2001-02-03T04:05:06.007-08:09:10'::time::text").Scan(&str)
   188  	if err == nil {
   189  		t.Fatalf("PostgreSQL is accepting an ISO timestamp input for time")
   190  	}
   191  
   192  	for i, tt := range formatTimeTests {
   193  		for _, typ := range []string{"date", "time", "timetz", "timestamp", "timestamptz"} {
   194  			err = db.QueryRow("SELECT $1::"+typ+"::text", tt.time).Scan(&str)
   195  			if err != nil {
   196  				t.Errorf("%d: incorrect time format for %v on the backend: %v", i, typ, err)
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  func TestTimeWithoutTimezone(t *testing.T) {
   203  	db := openTestConn(t)
   204  	defer db.Close()
   205  
   206  	tx, err := db.Begin()
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	defer tx.Rollback()
   211  
   212  	for _, tc := range []struct {
   213  		refTime      string
   214  		expectedTime time.Time
   215  	}{
   216  		{"11:59:59", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
   217  		{"24:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   218  		{"24:00:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   219  		{"24:00:00.0", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   220  		{"24:00:00.000000", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   221  	} {
   222  		t.Run(
   223  			fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
   224  			func(t *testing.T) {
   225  				var gotTime time.Time
   226  				row := tx.QueryRow("select $1::time", tc.refTime)
   227  				err = row.Scan(&gotTime)
   228  				if err != nil {
   229  					t.Fatal(err)
   230  				}
   231  
   232  				if !tc.expectedTime.Equal(gotTime) {
   233  					t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
   234  				}
   235  			},
   236  		)
   237  	}
   238  }
   239  
   240  func TestTimeWithTimezone(t *testing.T) {
   241  	db := openTestConn(t)
   242  	defer db.Close()
   243  
   244  	tx, err := db.Begin()
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	defer tx.Rollback()
   249  
   250  	for _, tc := range []struct {
   251  		refTime      string
   252  		expectedTime time.Time
   253  	}{
   254  		{"11:59:59+00:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.UTC)},
   255  		{"11:59:59+04:00", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04", 4*60*60))},
   256  		{"11:59:59+04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("+04:01:02", 4*60*60+1*60+2))},
   257  		{"11:59:59-04:01:02", time.Date(0, 1, 1, 11, 59, 59, 0, time.FixedZone("-04:01:02", -(4*60*60+1*60+2)))},
   258  		{"24:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   259  		{"24:00Z", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   260  		{"24:00-04:00", time.Date(0, 1, 2, 0, 0, 0, 0, time.FixedZone("-04", -4*60*60))},
   261  		{"24:00:00+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   262  		{"24:00:00.0+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   263  		{"24:00:00.000000+00", time.Date(0, 1, 2, 0, 0, 0, 0, time.UTC)},
   264  	} {
   265  		t.Run(
   266  			fmt.Sprintf("%s => %s", tc.refTime, tc.expectedTime.Format(time.RFC3339)),
   267  			func(t *testing.T) {
   268  				var gotTime time.Time
   269  				row := tx.QueryRow("select $1::timetz", tc.refTime)
   270  				err = row.Scan(&gotTime)
   271  				if err != nil {
   272  					t.Fatal(err)
   273  				}
   274  
   275  				if !tc.expectedTime.Equal(gotTime) {
   276  					t.Errorf("timestamps not equal: %s != %s", tc.expectedTime, gotTime)
   277  				}
   278  			},
   279  		)
   280  	}
   281  }
   282  
   283  func TestTimestampWithTimeZone(t *testing.T) {
   284  	db := openTestConn(t)
   285  	defer db.Close()
   286  
   287  	tx, err := db.Begin()
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	defer tx.Rollback()
   292  
   293  	// try several different locations, all included in Go's zoneinfo.zip
   294  	for _, locName := range []string{
   295  		"UTC",
   296  		"America/Chicago",
   297  		"America/New_York",
   298  		"Australia/Darwin",
   299  		"Australia/Perth",
   300  	} {
   301  		loc, err := time.LoadLocation(locName)
   302  		if err != nil {
   303  			t.Logf("Could not load time zone %s - skipping", locName)
   304  			continue
   305  		}
   306  
   307  		// Postgres timestamps have a resolution of 1 microsecond, so don't
   308  		// use the full range of the Nanosecond argument
   309  		refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
   310  
   311  		for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
   312  			// Switch Postgres's timezone to test different output timestamp formats
   313  			_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
   314  			if err != nil {
   315  				t.Fatal(err)
   316  			}
   317  
   318  			var gotTime time.Time
   319  			row := tx.QueryRow("select $1::timestamp with time zone", refTime)
   320  			err = row.Scan(&gotTime)
   321  			if err != nil {
   322  				t.Fatal(err)
   323  			}
   324  
   325  			if !refTime.Equal(gotTime) {
   326  				t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
   327  			}
   328  
   329  			// check that the time zone is set correctly based on TimeZone
   330  			pgLoc, err := time.LoadLocation(pgTimeZone)
   331  			if err != nil {
   332  				t.Logf("Could not load time zone %s - skipping", pgLoc)
   333  				continue
   334  			}
   335  			translated := refTime.In(pgLoc)
   336  			if translated.String() != gotTime.String() {
   337  				t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
   338  			}
   339  		}
   340  	}
   341  }
   342  
   343  func TestTimestampWithOutTimezone(t *testing.T) {
   344  	db := openTestConn(t)
   345  	defer db.Close()
   346  
   347  	test := func(ts, pgts string) {
   348  		r, err := db.Query("SELECT $1::timestamp", pgts)
   349  		if err != nil {
   350  			t.Fatalf("Could not run query: %v", err)
   351  		}
   352  
   353  		if !r.Next() {
   354  			t.Fatal("Expected at least one row")
   355  		}
   356  
   357  		var result time.Time
   358  		err = r.Scan(&result)
   359  		if err != nil {
   360  			t.Fatalf("Did not expect error scanning row: %v", err)
   361  		}
   362  
   363  		expected, err := time.Parse(time.RFC3339, ts)
   364  		if err != nil {
   365  			t.Fatalf("Could not parse test time literal: %v", err)
   366  		}
   367  
   368  		if !result.Equal(expected) {
   369  			t.Fatalf("Expected time to match %v: got mismatch %v",
   370  				expected, result)
   371  		}
   372  
   373  		if r.Next() {
   374  			t.Fatal("Expected only one row")
   375  		}
   376  	}
   377  
   378  	test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
   379  
   380  	// Test higher precision time
   381  	test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
   382  }
   383  
   384  func TestInfinityTimestamp(t *testing.T) {
   385  	db := openTestConn(t)
   386  	defer db.Close()
   387  	var err error
   388  	var resultT time.Time
   389  
   390  	expectedErrorStrRegexp := regexp.MustCompile(
   391  		`^sql: Scan error on column index 0(, name "timestamp(tz)?"|): unsupported`)
   392  
   393  	type testCases []struct {
   394  		Query                  string
   395  		Param                  string
   396  		ExpectedErrorStrRegexp *regexp.Regexp
   397  		ExpectedVal            interface{}
   398  	}
   399  	tc := testCases{
   400  		{"SELECT $1::timestamp", "-infinity", expectedErrorStrRegexp, "-infinity"},
   401  		{"SELECT $1::timestamptz", "-infinity", expectedErrorStrRegexp, "-infinity"},
   402  		{"SELECT $1::timestamp", "infinity", expectedErrorStrRegexp, "infinity"},
   403  		{"SELECT $1::timestamptz", "infinity", expectedErrorStrRegexp, "infinity"},
   404  	}
   405  	// try to assert []byte to time.Time
   406  	for _, q := range tc {
   407  		err = db.QueryRow(q.Query, q.Param).Scan(&resultT)
   408  		if err == nil || !q.ExpectedErrorStrRegexp.MatchString(err.Error()) {
   409  			t.Errorf("Scanning -/+infinity, expected error to match regexp %q, got %q",
   410  				q.ExpectedErrorStrRegexp, err)
   411  		}
   412  	}
   413  	// yield []byte
   414  	for _, q := range tc {
   415  		var resultI interface{}
   416  		err = db.QueryRow(q.Query, q.Param).Scan(&resultI)
   417  		if err != nil {
   418  			t.Errorf("Scanning -/+infinity, expected no error, got %q", err)
   419  		}
   420  		result, ok := resultI.([]byte)
   421  		if !ok {
   422  			t.Errorf("Scanning -/+infinity, expected []byte, got %#v", resultI)
   423  		}
   424  		if string(result) != q.ExpectedVal {
   425  			t.Errorf("Scanning -/+infinity, expected %q, got %q", q.ExpectedVal, result)
   426  		}
   427  	}
   428  
   429  	y1500 := time.Date(1500, time.January, 1, 0, 0, 0, 0, time.UTC)
   430  	y2500 := time.Date(2500, time.January, 1, 0, 0, 0, 0, time.UTC)
   431  	EnableInfinityTs(y1500, y2500)
   432  
   433  	err = db.QueryRow("SELECT $1::timestamp", "infinity").Scan(&resultT)
   434  	if err != nil {
   435  		t.Errorf("Scanning infinity, expected no error, got %q", err)
   436  	}
   437  	if !resultT.Equal(y2500) {
   438  		t.Errorf("Scanning infinity, expected %q, got %q", y2500, resultT)
   439  	}
   440  
   441  	err = db.QueryRow("SELECT $1::timestamptz", "infinity").Scan(&resultT)
   442  	if err != nil {
   443  		t.Errorf("Scanning infinity, expected no error, got %q", err)
   444  	}
   445  	if !resultT.Equal(y2500) {
   446  		t.Errorf("Scanning Infinity, expected time %q, got %q", y2500, resultT.String())
   447  	}
   448  
   449  	err = db.QueryRow("SELECT $1::timestamp", "-infinity").Scan(&resultT)
   450  	if err != nil {
   451  		t.Errorf("Scanning -infinity, expected no error, got %q", err)
   452  	}
   453  	if !resultT.Equal(y1500) {
   454  		t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
   455  	}
   456  
   457  	err = db.QueryRow("SELECT $1::timestamptz", "-infinity").Scan(&resultT)
   458  	if err != nil {
   459  		t.Errorf("Scanning -infinity, expected no error, got %q", err)
   460  	}
   461  	if !resultT.Equal(y1500) {
   462  		t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
   463  	}
   464  
   465  	ym1500 := time.Date(-1500, time.January, 1, 0, 0, 0, 0, time.UTC)
   466  	y11500 := time.Date(11500, time.January, 1, 0, 0, 0, 0, time.UTC)
   467  	var s string
   468  	err = db.QueryRow("SELECT $1::timestamp::text", ym1500).Scan(&s)
   469  	if err != nil {
   470  		t.Errorf("Encoding -infinity, expected no error, got %q", err)
   471  	}
   472  	if s != "-infinity" {
   473  		t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
   474  	}
   475  	err = db.QueryRow("SELECT $1::timestamptz::text", ym1500).Scan(&s)
   476  	if err != nil {
   477  		t.Errorf("Encoding -infinity, expected no error, got %q", err)
   478  	}
   479  	if s != "-infinity" {
   480  		t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
   481  	}
   482  
   483  	err = db.QueryRow("SELECT $1::timestamp::text", y11500).Scan(&s)
   484  	if err != nil {
   485  		t.Errorf("Encoding infinity, expected no error, got %q", err)
   486  	}
   487  	if s != "infinity" {
   488  		t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
   489  	}
   490  	err = db.QueryRow("SELECT $1::timestamptz::text", y11500).Scan(&s)
   491  	if err != nil {
   492  		t.Errorf("Encoding infinity, expected no error, got %q", err)
   493  	}
   494  	if s != "infinity" {
   495  		t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
   496  	}
   497  
   498  	disableInfinityTs()
   499  
   500  	var panicErrorString string
   501  	func() {
   502  		defer func() {
   503  			panicErrorString, _ = recover().(string)
   504  		}()
   505  		EnableInfinityTs(y2500, y1500)
   506  	}()
   507  	if panicErrorString != infinityTsNegativeMustBeSmaller {
   508  		t.Errorf("Expected error, %q, got %q", infinityTsNegativeMustBeSmaller, panicErrorString)
   509  	}
   510  }
   511  
   512  func TestStringWithNul(t *testing.T) {
   513  	db := openTestConn(t)
   514  	defer db.Close()
   515  
   516  	hello0world := string("hello\x00world")
   517  	_, err := db.Query("SELECT $1::text", &hello0world)
   518  	if err == nil {
   519  		t.Fatal("Postgres accepts a string with nul in it; " +
   520  			"injection attacks may be plausible")
   521  	}
   522  }
   523  
   524  func TestByteSliceToText(t *testing.T) {
   525  	db := openTestConn(t)
   526  	defer db.Close()
   527  
   528  	b := []byte("hello world")
   529  	row := db.QueryRow("SELECT $1::text", b)
   530  
   531  	var result []byte
   532  	err := row.Scan(&result)
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  
   537  	if string(result) != string(b) {
   538  		t.Fatalf("expected %v but got %v", b, result)
   539  	}
   540  }
   541  
   542  func TestStringToBytea(t *testing.T) {
   543  	db := openTestConn(t)
   544  	defer db.Close()
   545  
   546  	b := "hello world"
   547  	row := db.QueryRow("SELECT $1::bytea", b)
   548  
   549  	var result []byte
   550  	err := row.Scan(&result)
   551  	if err != nil {
   552  		t.Fatal(err)
   553  	}
   554  
   555  	if !bytes.Equal(result, []byte(b)) {
   556  		t.Fatalf("expected %v but got %v", b, result)
   557  	}
   558  }
   559  
   560  func TestTextByteSliceToUUID(t *testing.T) {
   561  	db := openTestConn(t)
   562  	defer db.Close()
   563  
   564  	b := []byte("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
   565  	row := db.QueryRow("SELECT $1::uuid", b)
   566  
   567  	var result string
   568  	err := row.Scan(&result)
   569  	if forceBinaryParameters() {
   570  		pqErr := err.(*Error)
   571  		if pqErr == nil {
   572  			t.Errorf("Expected to get error")
   573  		} else if pqErr.Code != "22P03" {
   574  			t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
   575  		}
   576  	} else {
   577  		if err != nil {
   578  			t.Fatal(err)
   579  		}
   580  
   581  		if result != string(b) {
   582  			t.Fatalf("expected %v but got %v", b, result)
   583  		}
   584  	}
   585  }
   586  
   587  func TestBinaryByteSlicetoUUID(t *testing.T) {
   588  	db := openTestConn(t)
   589  	defer db.Close()
   590  
   591  	b := []byte{'\xa0', '\xee', '\xbc', '\x99',
   592  		'\x9c', '\x0b',
   593  		'\x4e', '\xf8',
   594  		'\xbb', '\x00', '\x6b',
   595  		'\xb9', '\xbd', '\x38', '\x0a', '\x11'}
   596  	row := db.QueryRow("SELECT $1::uuid", b)
   597  
   598  	var result string
   599  	err := row.Scan(&result)
   600  	if forceBinaryParameters() {
   601  		if err != nil {
   602  			t.Fatal(err)
   603  		}
   604  
   605  		if result != string("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") {
   606  			t.Fatalf("expected %v but got %v", b, result)
   607  		}
   608  	} else {
   609  		pqErr := err.(*Error)
   610  		if pqErr == nil {
   611  			t.Errorf("Expected to get error")
   612  		} else if pqErr.Code != "22021" {
   613  			t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
   614  		}
   615  	}
   616  }
   617  
   618  func TestStringToUUID(t *testing.T) {
   619  	db := openTestConn(t)
   620  	defer db.Close()
   621  
   622  	s := "a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"
   623  	row := db.QueryRow("SELECT $1::uuid", s)
   624  
   625  	var result string
   626  	err := row.Scan(&result)
   627  	if err != nil {
   628  		t.Fatal(err)
   629  	}
   630  
   631  	if result != s {
   632  		t.Fatalf("expected %v but got %v", s, result)
   633  	}
   634  }
   635  
   636  func TestTextByteSliceToInt(t *testing.T) {
   637  	db := openTestConn(t)
   638  	defer db.Close()
   639  
   640  	expected := 12345678
   641  	b := []byte(fmt.Sprintf("%d", expected))
   642  	row := db.QueryRow("SELECT $1::int", b)
   643  
   644  	var result int
   645  	err := row.Scan(&result)
   646  	if forceBinaryParameters() {
   647  		pqErr := err.(*Error)
   648  		if pqErr == nil {
   649  			t.Errorf("Expected to get error")
   650  		} else if pqErr.Code != "22P03" {
   651  			t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
   652  		}
   653  	} else {
   654  		if err != nil {
   655  			t.Fatal(err)
   656  		}
   657  		if result != expected {
   658  			t.Fatalf("expected %v but got %v", expected, result)
   659  		}
   660  	}
   661  }
   662  
   663  func TestBinaryByteSliceToInt(t *testing.T) {
   664  	db := openTestConn(t)
   665  	defer db.Close()
   666  
   667  	expected := 12345678
   668  	b := []byte{'\x00', '\xbc', '\x61', '\x4e'}
   669  	row := db.QueryRow("SELECT $1::int", b)
   670  
   671  	var result int
   672  	err := row.Scan(&result)
   673  	if forceBinaryParameters() {
   674  		if err != nil {
   675  			t.Fatal(err)
   676  		}
   677  		if result != expected {
   678  			t.Fatalf("expected %v but got %v", expected, result)
   679  		}
   680  	} else {
   681  		pqErr := err.(*Error)
   682  		if pqErr == nil {
   683  			t.Errorf("Expected to get error")
   684  		} else if pqErr.Code != "22021" {
   685  			t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
   686  		}
   687  	}
   688  }
   689  
   690  func TestTextDecodeIntoString(t *testing.T) {
   691  	input := []byte("hello world")
   692  	want := string(input)
   693  	for _, typ := range []oid.Oid{oid.T_char, oid.T_varchar, oid.T_text} {
   694  		got := decode(&parameterStatus{}, input, typ, formatText)
   695  		if got != want {
   696  			t.Errorf("invalid string decoding output for %T(%+v), got %v but expected %v", typ, typ, got, want)
   697  		}
   698  	}
   699  }
   700  
   701  func TestByteaOutputFormatEncoding(t *testing.T) {
   702  	input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
   703  	want := []byte("\\x5c78000102fffe6162636465666730313233")
   704  	got := encode(&parameterStatus{serverVersion: 90000}, input, oid.T_bytea)
   705  	if !bytes.Equal(want, got) {
   706  		t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
   707  	}
   708  
   709  	want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
   710  	got = encode(&parameterStatus{serverVersion: 84000}, input, oid.T_bytea)
   711  	if !bytes.Equal(want, got) {
   712  		t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
   713  	}
   714  }
   715  
   716  func TestByteaOutputFormats(t *testing.T) {
   717  	db := openTestConn(t)
   718  	defer db.Close()
   719  
   720  	if getServerVersion(t, db) < 90000 {
   721  		// skip
   722  		return
   723  	}
   724  
   725  	testByteaOutputFormat := func(f string, usePrepared bool) {
   726  		expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
   727  		sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
   728  
   729  		var data []byte
   730  
   731  		// use a txn to avoid relying on getting the same connection
   732  		txn, err := db.Begin()
   733  		if err != nil {
   734  			t.Fatal(err)
   735  		}
   736  		defer txn.Rollback()
   737  
   738  		_, err = txn.Exec("SET LOCAL bytea_output TO " + f)
   739  		if err != nil {
   740  			t.Fatal(err)
   741  		}
   742  		var rows *sql.Rows
   743  		var stmt *sql.Stmt
   744  		if usePrepared {
   745  			stmt, err = txn.Prepare(sqlQuery)
   746  			if err != nil {
   747  				t.Fatal(err)
   748  			}
   749  			rows, err = stmt.Query()
   750  		} else {
   751  			// use Query; QueryRow would hide the actual error
   752  			rows, err = txn.Query(sqlQuery)
   753  		}
   754  		if err != nil {
   755  			t.Fatal(err)
   756  		}
   757  		if !rows.Next() {
   758  			if rows.Err() != nil {
   759  				t.Fatal(rows.Err())
   760  			}
   761  			t.Fatal("shouldn't happen")
   762  		}
   763  		err = rows.Scan(&data)
   764  		if err != nil {
   765  			t.Fatal(err)
   766  		}
   767  		err = rows.Close()
   768  		if err != nil {
   769  			t.Fatal(err)
   770  		}
   771  		if stmt != nil {
   772  			err = stmt.Close()
   773  			if err != nil {
   774  				t.Fatal(err)
   775  			}
   776  		}
   777  		if !bytes.Equal(data, expectedData) {
   778  			t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
   779  		}
   780  	}
   781  
   782  	testByteaOutputFormat("hex", false)
   783  	testByteaOutputFormat("escape", false)
   784  	testByteaOutputFormat("hex", true)
   785  	testByteaOutputFormat("escape", true)
   786  }
   787  
   788  func TestAppendEncodedText(t *testing.T) {
   789  	var buf []byte
   790  
   791  	buf = appendEncodedText(&parameterStatus{serverVersion: 90000}, buf, int64(10))
   792  	buf = append(buf, '\t')
   793  	buf = appendEncodedText(&parameterStatus{serverVersion: 90000}, buf, 42.0000000001)
   794  	buf = append(buf, '\t')
   795  	buf = appendEncodedText(&parameterStatus{serverVersion: 90000}, buf, "hello\tworld")
   796  	buf = append(buf, '\t')
   797  	buf = appendEncodedText(&parameterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
   798  
   799  	if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
   800  		t.Fatal(string(buf))
   801  	}
   802  }
   803  
   804  func TestAppendEscapedText(t *testing.T) {
   805  	if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
   806  		t.Fatal(string(esc))
   807  	}
   808  	if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
   809  		t.Fatal(string(esc))
   810  	}
   811  	if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
   812  		t.Fatal(string(esc))
   813  	}
   814  }
   815  
   816  func TestAppendEscapedTextExistingBuffer(t *testing.T) {
   817  	buf := []byte("123\t")
   818  	if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
   819  		t.Fatal(string(esc))
   820  	}
   821  	buf = []byte("123\t")
   822  	if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
   823  		t.Fatal(string(esc))
   824  	}
   825  	buf = []byte("123\t")
   826  	if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
   827  		t.Fatal(string(esc))
   828  	}
   829  }
   830  
   831  var formatAndParseTimestamp = []struct {
   832  	time     time.Time
   833  	expected string
   834  }{
   835  	{time.Time{}, "0001-01-01 00:00:00Z"},
   836  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
   837  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
   838  	{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
   839  	{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03 04:05:06-07:30:09"},
   840  
   841  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
   842  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
   843  	{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
   844  
   845  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
   846  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00 BC"},
   847  	{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00 BC"},
   848  
   849  	{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09"},
   850  	{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03 04:05:06-07:30:09 BC"},
   851  }
   852  
   853  func TestFormatAndParseTimestamp(t *testing.T) {
   854  	for _, val := range formatAndParseTimestamp {
   855  		formattedTime := FormatTimestamp(val.time)
   856  		parsedTime, err := ParseTimestamp(nil, string(formattedTime))
   857  
   858  		if err != nil {
   859  			t.Errorf("invalid parsing, err: %v", err.Error())
   860  		}
   861  
   862  		if val.time.UTC() != parsedTime.UTC() {
   863  			t.Errorf("invalid parsing from formatted timestamp, got %v; expected %v", parsedTime.String(), val.time.String())
   864  		}
   865  	}
   866  }
   867  
   868  func BenchmarkAppendEscapedText(b *testing.B) {
   869  	longString := ""
   870  	for i := 0; i < 100; i++ {
   871  		longString += "123456789\n"
   872  	}
   873  	for i := 0; i < b.N; i++ {
   874  		appendEscapedText(nil, longString)
   875  	}
   876  }
   877  
   878  func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
   879  	longString := ""
   880  	for i := 0; i < 100; i++ {
   881  		longString += "1234567890"
   882  	}
   883  	for i := 0; i < b.N; i++ {
   884  		appendEscapedText(nil, longString)
   885  	}
   886  }
   887  

View as plain text