Initial switch to vgo
parent
4c2fdc9870
commit
7c647655bd
|
@ -1,24 +0,0 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/julienschmidt/httprouter"
|
||||
packages = ["."]
|
||||
revision = "8c199fb6259ffc1af525cc3ad52ee60ba8359669"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"oid"
|
||||
]
|
||||
revision = "88edab0803230a3898347e77b474f8c1820a1f20"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "b3e3efe225a51bfb6bccb4fc6484744ac523afdcb173a4960e2b628b154ceb39"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
38
Gopkg.toml
38
Gopkg.toml
|
@ -1,38 +0,0 @@
|
|||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/julienschmidt/httprouter"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
|
@ -0,0 +1,6 @@
|
|||
module "github.com/serverwentdown/short"
|
||||
|
||||
require (
|
||||
"github.com/julienschmidt/httprouter" v0.0.0-20150626000000-8c199fb6259f
|
||||
"github.com/lib/pq" v0.0.0-20180201184707-88edab080323
|
||||
)
|
2
main.go
2
main.go
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package main // import "github.com/serverwentdown/short"
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// Based on the path package, Copyright 2009 The Go Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
package httprouter
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var cleanTests = []struct {
|
||||
path, result string
|
||||
}{
|
||||
// Already clean
|
||||
{"/", "/"},
|
||||
{"/abc", "/abc"},
|
||||
{"/a/b/c", "/a/b/c"},
|
||||
{"/abc/", "/abc/"},
|
||||
{"/a/b/c/", "/a/b/c/"},
|
||||
|
||||
// missing root
|
||||
{"", "/"},
|
||||
{"abc", "/abc"},
|
||||
{"abc/def", "/abc/def"},
|
||||
{"a/b/c", "/a/b/c"},
|
||||
|
||||
// Remove doubled slash
|
||||
{"//", "/"},
|
||||
{"/abc//", "/abc/"},
|
||||
{"/abc/def//", "/abc/def/"},
|
||||
{"/a/b/c//", "/a/b/c/"},
|
||||
{"/abc//def//ghi", "/abc/def/ghi"},
|
||||
{"//abc", "/abc"},
|
||||
{"///abc", "/abc"},
|
||||
{"//abc//", "/abc/"},
|
||||
|
||||
// Remove . elements
|
||||
{".", "/"},
|
||||
{"./", "/"},
|
||||
{"/abc/./def", "/abc/def"},
|
||||
{"/./abc/def", "/abc/def"},
|
||||
{"/abc/.", "/abc/"},
|
||||
|
||||
// Remove .. elements
|
||||
{"..", "/"},
|
||||
{"../", "/"},
|
||||
{"../../", "/"},
|
||||
{"../..", "/"},
|
||||
{"../../abc", "/abc"},
|
||||
{"/abc/def/ghi/../jkl", "/abc/def/jkl"},
|
||||
{"/abc/def/../ghi/../jkl", "/abc/jkl"},
|
||||
{"/abc/def/..", "/abc"},
|
||||
{"/abc/def/../..", "/"},
|
||||
{"/abc/def/../../..", "/"},
|
||||
{"/abc/def/../../..", "/"},
|
||||
{"/abc/def/../../../ghi/jkl/../../../mno", "/mno"},
|
||||
|
||||
// Combinations
|
||||
{"abc/./../def", "/def"},
|
||||
{"abc//./../def", "/def"},
|
||||
{"abc/../../././../def", "/def"},
|
||||
}
|
||||
|
||||
func TestPathClean(t *testing.T) {
|
||||
for _, test := range cleanTests {
|
||||
if s := CleanPath(test.path); s != test.result {
|
||||
t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
|
||||
}
|
||||
if s := CleanPath(test.result); s != test.result {
|
||||
t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathCleanMallocs(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping malloc count in short mode")
|
||||
}
|
||||
if runtime.GOMAXPROCS(0) > 1 {
|
||||
t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
|
||||
return
|
||||
}
|
||||
|
||||
for _, test := range cleanTests {
|
||||
allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
|
||||
if allocs > 0 {
|
||||
t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
package httprouter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockResponseWriter struct{}
|
||||
|
||||
func (m *mockResponseWriter) Header() (h http.Header) {
|
||||
return http.Header{}
|
||||
}
|
||||
|
||||
func (m *mockResponseWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (m *mockResponseWriter) WriteString(s string) (n int, err error) {
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func (m *mockResponseWriter) WriteHeader(int) {}
|
||||
|
||||
func TestParams(t *testing.T) {
|
||||
ps := Params{
|
||||
Param{"param1", "value1"},
|
||||
Param{"param2", "value2"},
|
||||
Param{"param3", "value3"},
|
||||
}
|
||||
for i := range ps {
|
||||
if val := ps.ByName(ps[i].Key); val != ps[i].Value {
|
||||
t.Errorf("Wrong value for %s: Got %s; Want %s", ps[i].Key, val, ps[i].Value)
|
||||
}
|
||||
}
|
||||
if val := ps.ByName("noKey"); val != "" {
|
||||
t.Errorf("Expected empty string for not found key; got: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouter(t *testing.T) {
|
||||
router := New()
|
||||
|
||||
routed := false
|
||||
router.Handle("GET", "/user/:name", func(w http.ResponseWriter, r *http.Request, ps Params) {
|
||||
routed = true
|
||||
want := Params{Param{"name", "gopher"}}
|
||||
if !reflect.DeepEqual(ps, want) {
|
||||
t.Fatalf("wrong wildcard values: want %v, got %v", want, ps)
|
||||
}
|
||||
})
|
||||
|
||||
w := new(mockResponseWriter)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/user/gopher", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if !routed {
|
||||
t.Fatal("routing failed")
|
||||
}
|
||||
}
|
||||
|
||||
type handlerStruct struct {
|
||||
handeled *bool
|
||||
}
|
||||
|
||||
func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
*h.handeled = true
|
||||
}
|
||||
|
||||
func TestRouterAPI(t *testing.T) {
|
||||
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
|
||||
|
||||
httpHandler := handlerStruct{&handler}
|
||||
|
||||
router := New()
|
||||
router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
get = true
|
||||
})
|
||||
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
head = true
|
||||
})
|
||||
router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
options = true
|
||||
})
|
||||
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
post = true
|
||||
})
|
||||
router.PUT("/PUT", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
put = true
|
||||
})
|
||||
router.PATCH("/PATCH", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
patch = true
|
||||
})
|
||||
router.DELETE("/DELETE", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
delete = true
|
||||
})
|
||||
router.Handler("GET", "/Handler", httpHandler)
|
||||
router.HandlerFunc("GET", "/HandlerFunc", func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerFunc = true
|
||||
})
|
||||
|
||||
w := new(mockResponseWriter)
|
||||
|
||||
r, _ := http.NewRequest("GET", "/GET", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !get {
|
||||
t.Error("routing GET failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("HEAD", "/GET", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !head {
|
||||
t.Error("routing HEAD failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("OPTIONS", "/GET", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !options {
|
||||
t.Error("routing OPTIONS failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("POST", "/POST", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !post {
|
||||
t.Error("routing POST failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("PUT", "/PUT", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !put {
|
||||
t.Error("routing PUT failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("PATCH", "/PATCH", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !patch {
|
||||
t.Error("routing PATCH failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("DELETE", "/DELETE", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !delete {
|
||||
t.Error("routing DELETE failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("GET", "/Handler", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !handler {
|
||||
t.Error("routing Handler failed")
|
||||
}
|
||||
|
||||
r, _ = http.NewRequest("GET", "/HandlerFunc", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !handlerFunc {
|
||||
t.Error("routing HandlerFunc failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterRoot(t *testing.T) {
|
||||
router := New()
|
||||
recv := catchPanic(func() {
|
||||
router.GET("noSlashRoot", nil)
|
||||
})
|
||||
if recv == nil {
|
||||
t.Fatal("registering path not beginning with '/' did not panic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterNotAllowed(t *testing.T) {
|
||||
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
|
||||
|
||||
router := New()
|
||||
router.POST("/path", handlerFunc)
|
||||
|
||||
// Test not allowed
|
||||
r, _ := http.NewRequest("GET", "/path", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
if !(w.Code == http.StatusMethodNotAllowed) {
|
||||
t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
responseText := "custom method"
|
||||
router.MethodNotAllowed = func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
w.Write([]byte(responseText))
|
||||
}
|
||||
router.ServeHTTP(w, r)
|
||||
if got := w.Body.String(); !(got == responseText) {
|
||||
t.Errorf("unexpected response got %q want %q", got, responseText)
|
||||
}
|
||||
if w.Code != http.StatusTeapot {
|
||||
t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterNotFound(t *testing.T) {
|
||||
handlerFunc := func(_ http.ResponseWriter, _ *http.Request, _ Params) {}
|
||||
|
||||
router := New()
|
||||
router.GET("/path", handlerFunc)
|
||||
router.GET("/dir/", handlerFunc)
|
||||
router.GET("/", handlerFunc)
|
||||
|
||||
testRoutes := []struct {
|
||||
route string
|
||||
code int
|
||||
header string
|
||||
}{
|
||||
{"/path/", 301, "map[Location:[/path]]"}, // TSR -/
|
||||
{"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/
|
||||
{"", 301, "map[Location:[/]]"}, // TSR +/
|
||||
{"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case
|
||||
{"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case
|
||||
{"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/
|
||||
{"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/
|
||||
{"/../path", 301, "map[Location:[/path]]"}, // CleanPath
|
||||
{"/nope", 404, ""}, // NotFound
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
r, _ := http.NewRequest("GET", tr.route, nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
if !(w.Code == tr.code && (w.Code == 404 || fmt.Sprint(w.Header()) == tr.header)) {
|
||||
t.Errorf("NotFound handling route %s failed: Code=%d, Header=%v", tr.route, w.Code, w.Header())
|
||||
}
|
||||
}
|
||||
|
||||
// Test custom not found handler
|
||||
var notFound bool
|
||||
router.NotFound = func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(404)
|
||||
notFound = true
|
||||
}
|
||||
r, _ := http.NewRequest("GET", "/nope", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
if !(w.Code == 404 && notFound == true) {
|
||||
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
|
||||
}
|
||||
|
||||
// Test other method than GET (want 307 instead of 301)
|
||||
router.PATCH("/path", handlerFunc)
|
||||
r, _ = http.NewRequest("PATCH", "/path/", nil)
|
||||
w = httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
if !(w.Code == 307 && fmt.Sprint(w.Header()) == "map[Location:[/path]]") {
|
||||
t.Errorf("Custom NotFound handler failed: Code=%d, Header=%v", w.Code, w.Header())
|
||||
}
|
||||
|
||||
// Test special case where no node for the prefix "/" exists
|
||||
router = New()
|
||||
router.GET("/a", handlerFunc)
|
||||
r, _ = http.NewRequest("GET", "/", nil)
|
||||
w = httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
if !(w.Code == 404) {
|
||||
t.Errorf("NotFound handling route / failed: Code=%d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterPanicHandler(t *testing.T) {
|
||||
router := New()
|
||||
panicHandled := false
|
||||
|
||||
router.PanicHandler = func(rw http.ResponseWriter, r *http.Request, p interface{}) {
|
||||
panicHandled = true
|
||||
}
|
||||
|
||||
router.Handle("PUT", "/user/:name", func(_ http.ResponseWriter, _ *http.Request, _ Params) {
|
||||
panic("oops!")
|
||||
})
|
||||
|
||||
w := new(mockResponseWriter)
|
||||
req, _ := http.NewRequest("PUT", "/user/gopher", nil)
|
||||
|
||||
defer func() {
|
||||
if rcv := recover(); rcv != nil {
|
||||
t.Fatal("handling panic failed")
|
||||
}
|
||||
}()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if !panicHandled {
|
||||
t.Fatal("simulating failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterLookup(t *testing.T) {
|
||||
routed := false
|
||||
wantHandle := func(_ http.ResponseWriter, _ *http.Request, _ Params) {
|
||||
routed = true
|
||||
}
|
||||
wantParams := Params{Param{"name", "gopher"}}
|
||||
|
||||
router := New()
|
||||
|
||||
// try empty router first
|
||||
handle, _, tsr := router.Lookup("GET", "/nope")
|
||||
if handle != nil {
|
||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
||||
}
|
||||
if tsr {
|
||||
t.Error("Got wrong TSR recommendation!")
|
||||
}
|
||||
|
||||
// insert route and try again
|
||||
router.GET("/user/:name", wantHandle)
|
||||
|
||||
handle, params, tsr := router.Lookup("GET", "/user/gopher")
|
||||
if handle == nil {
|
||||
t.Fatal("Got no handle!")
|
||||
} else {
|
||||
handle(nil, nil, nil)
|
||||
if !routed {
|
||||
t.Fatal("Routing failed!")
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(params, wantParams) {
|
||||
t.Fatalf("Wrong parameter values: want %v, got %v", wantParams, params)
|
||||
}
|
||||
|
||||
handle, _, tsr = router.Lookup("GET", "/user/gopher/")
|
||||
if handle != nil {
|
||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
||||
}
|
||||
if !tsr {
|
||||
t.Error("Got no TSR recommendation!")
|
||||
}
|
||||
|
||||
handle, _, tsr = router.Lookup("GET", "/nope")
|
||||
if handle != nil {
|
||||
t.Fatalf("Got handle for unregistered pattern: %v", handle)
|
||||
}
|
||||
if tsr {
|
||||
t.Error("Got wrong TSR recommendation!")
|
||||
}
|
||||
}
|
||||
|
||||
type mockFileSystem struct {
|
||||
opened bool
|
||||
}
|
||||
|
||||
func (mfs *mockFileSystem) Open(name string) (http.File, error) {
|
||||
mfs.opened = true
|
||||
return nil, errors.New("this is just a mock")
|
||||
}
|
||||
|
||||
func TestRouterServeFiles(t *testing.T) {
|
||||
router := New()
|
||||
mfs := &mockFileSystem{}
|
||||
|
||||
recv := catchPanic(func() {
|
||||
router.ServeFiles("/noFilepath", mfs)
|
||||
})
|
||||
if recv == nil {
|
||||
t.Fatal("registering path not ending with '*filepath' did not panic")
|
||||
}
|
||||
|
||||
router.ServeFiles("/*filepath", mfs)
|
||||
w := new(mockResponseWriter)
|
||||
r, _ := http.NewRequest("GET", "/favicon.ico", nil)
|
||||
router.ServeHTTP(w, r)
|
||||
if !mfs.opened {
|
||||
t.Error("serving file failed")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,611 @@
|
|||
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
package httprouter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func printChildren(n *node, prefix string) {
|
||||
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handle, n.wildChild, n.nType)
|
||||
for l := len(n.path); l > 0; l-- {
|
||||
prefix += " "
|
||||
}
|
||||
for _, child := range n.children {
|
||||
printChildren(child, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Used as a workaround since we can't compare functions or their adresses
|
||||
var fakeHandlerValue string
|
||||
|
||||
func fakeHandler(val string) Handle {
|
||||
return func(http.ResponseWriter, *http.Request, Params) {
|
||||
fakeHandlerValue = val
|
||||
}
|
||||
}
|
||||
|
||||
type testRequests []struct {
|
||||
path string
|
||||
nilHandler bool
|
||||
route string
|
||||
ps Params
|
||||
}
|
||||
|
||||
func checkRequests(t *testing.T, tree *node, requests testRequests) {
|
||||
for _, request := range requests {
|
||||
handler, ps, _ := tree.getValue(request.path)
|
||||
|
||||
if handler == nil {
|
||||
if !request.nilHandler {
|
||||
t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path)
|
||||
}
|
||||
} else if request.nilHandler {
|
||||
t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path)
|
||||
} else {
|
||||
handler(nil, nil, nil)
|
||||
if fakeHandlerValue != request.route {
|
||||
t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ps, request.ps) {
|
||||
t.Errorf("Params mismatch for route '%s'", request.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkPriorities(t *testing.T, n *node) uint32 {
|
||||
var prio uint32
|
||||
for i := range n.children {
|
||||
prio += checkPriorities(t, n.children[i])
|
||||
}
|
||||
|
||||
if n.handle != nil {
|
||||
prio++
|
||||
}
|
||||
|
||||
if n.priority != prio {
|
||||
t.Errorf(
|
||||
"priority mismatch for node '%s': is %d, should be %d",
|
||||
n.path, n.priority, prio,
|
||||
)
|
||||
}
|
||||
|
||||
return prio
|
||||
}
|
||||
|
||||
func checkMaxParams(t *testing.T, n *node) uint8 {
|
||||
var maxParams uint8
|
||||
for i := range n.children {
|
||||
params := checkMaxParams(t, n.children[i])
|
||||
if params > maxParams {
|
||||
maxParams = params
|
||||
}
|
||||
}
|
||||
if n.nType != static && !n.wildChild {
|
||||
maxParams++
|
||||
}
|
||||
|
||||
if n.maxParams != maxParams {
|
||||
t.Errorf(
|
||||
"maxParams mismatch for node '%s': is %d, should be %d",
|
||||
n.path, n.maxParams, maxParams,
|
||||
)
|
||||
}
|
||||
|
||||
return maxParams
|
||||
}
|
||||
|
||||
func TestCountParams(t *testing.T) {
|
||||
if countParams("/path/:param1/static/*catch-all") != 2 {
|
||||
t.Fail()
|
||||
}
|
||||
if countParams(strings.Repeat("/:param", 256)) != 255 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeAddAndGet(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/hi",
|
||||
"/contact",
|
||||
"/co",
|
||||
"/c",
|
||||
"/a",
|
||||
"/ab",
|
||||
"/doc/",
|
||||
"/doc/go_faq.html",
|
||||
"/doc/go1.html",
|
||||
"/α",
|
||||
"/β",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/a", false, "/a", nil},
|
||||
{"/", true, "", nil},
|
||||
{"/hi", false, "/hi", nil},
|
||||
{"/contact", false, "/contact", nil},
|
||||
{"/co", false, "/co", nil},
|
||||
{"/con", true, "", nil}, // key mismatch
|
||||
{"/cona", true, "", nil}, // key mismatch
|
||||
{"/no", true, "", nil}, // no matching child
|
||||
{"/ab", false, "/ab", nil},
|
||||
{"/α", false, "/α", nil},
|
||||
{"/β", false, "/β", nil},
|
||||
})
|
||||
|
||||
checkPriorities(t, tree)
|
||||
checkMaxParams(t, tree)
|
||||
}
|
||||
|
||||
func TestTreeWildcard(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/",
|
||||
"/cmd/:tool/:sub",
|
||||
"/cmd/:tool/",
|
||||
"/src/*filepath",
|
||||
"/search/",
|
||||
"/search/:query",
|
||||
"/user_:name",
|
||||
"/user_:name/about",
|
||||
"/files/:dir/*filepath",
|
||||
"/doc/",
|
||||
"/doc/go_faq.html",
|
||||
"/doc/go1.html",
|
||||
"/info/:user/public",
|
||||
"/info/:user/project/:project",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||
{"/cmd/test", true, "", Params{Param{"tool", "test"}}},
|
||||
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{"tool", "test"}, Param{"sub", "3"}}},
|
||||
{"/src/", false, "/src/*filepath", Params{Param{"filepath", "/"}}},
|
||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
|
||||
{"/search/", false, "/search/", nil},
|
||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
||||
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
||||
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
|
||||
{"/user_gopher/about", false, "/user_:name/about", Params{Param{"name", "gopher"}}},
|
||||
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{"dir", "js"}, Param{"filepath", "/inc/framework.js"}}},
|
||||
{"/info/gordon/public", false, "/info/:user/public", Params{Param{"user", "gordon"}}},
|
||||
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{"user", "gordon"}, Param{"project", "go"}}},
|
||||
})
|
||||
|
||||
checkPriorities(t, tree)
|
||||
checkMaxParams(t, tree)
|
||||
}
|
||||
|
||||
func catchPanic(testFunc func()) (recv interface{}) {
|
||||
defer func() {
|
||||
recv = recover()
|
||||
}()
|
||||
|
||||
testFunc()
|
||||
return
|
||||
}
|
||||
|
||||
type testRoute struct {
|
||||
path string
|
||||
conflict bool
|
||||
}
|
||||
|
||||
func testRoutes(t *testing.T, routes []testRoute) {
|
||||
tree := &node{}
|
||||
|
||||
for _, route := range routes {
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route.path, nil)
|
||||
})
|
||||
|
||||
if route.conflict {
|
||||
if recv == nil {
|
||||
t.Errorf("no panic for conflicting route '%s'", route.path)
|
||||
}
|
||||
} else if recv != nil {
|
||||
t.Errorf("unexpected panic for route '%s': %v", route.path, recv)
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
}
|
||||
|
||||
func TestTreeWildcardConflict(t *testing.T) {
|
||||
routes := []testRoute{
|
||||
{"/cmd/:tool/:sub", false},
|
||||
{"/cmd/vet", true},
|
||||
{"/src/*filepath", false},
|
||||
{"/src/*filepathx", true},
|
||||
{"/src/", true},
|
||||
{"/src1/", false},
|
||||
{"/src1/*filepath", true},
|
||||
{"/src2*filepath", true},
|
||||
{"/search/:query", false},
|
||||
{"/search/invalid", true},
|
||||
{"/user_:name", false},
|
||||
{"/user_x", true},
|
||||
{"/user_:name", false},
|
||||
{"/id:id", false},
|
||||
{"/id/:id", true},
|
||||
}
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeChildConflict(t *testing.T) {
|
||||
routes := []testRoute{
|
||||
{"/cmd/vet", false},
|
||||
{"/cmd/:tool/:sub", true},
|
||||
{"/src/AUTHORS", false},
|
||||
{"/src/*filepath", true},
|
||||
{"/user_x", false},
|
||||
{"/user_:name", true},
|
||||
{"/id/:id", false},
|
||||
{"/id:id", true},
|
||||
{"/:id", true},
|
||||
{"/*filepath", true},
|
||||
}
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeDupliatePath(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/",
|
||||
"/doc/",
|
||||
"/src/*filepath",
|
||||
"/search/:query",
|
||||
"/user_:name",
|
||||
}
|
||||
for _, route := range routes {
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
})
|
||||
if recv != nil {
|
||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
||||
}
|
||||
|
||||
// Add again
|
||||
recv = catchPanic(func() {
|
||||
tree.addRoute(route, nil)
|
||||
})
|
||||
if recv == nil {
|
||||
t.Fatalf("no panic while inserting duplicate route '%s", route)
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
checkRequests(t, tree, testRequests{
|
||||
{"/", false, "/", nil},
|
||||
{"/doc/", false, "/doc/", nil},
|
||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}},
|
||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}},
|
||||
{"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}},
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmptyWildcardName(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/user:",
|
||||
"/user:/",
|
||||
"/cmd/:/",
|
||||
"/src/*",
|
||||
}
|
||||
for _, route := range routes {
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route, nil)
|
||||
})
|
||||
if recv == nil {
|
||||
t.Fatalf("no panic while inserting route with empty wildcard name '%s", route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeCatchAllConflict(t *testing.T) {
|
||||
routes := []testRoute{
|
||||
{"/src/*filepath/x", true},
|
||||
{"/src2/", false},
|
||||
{"/src2/*filepath/x", true},
|
||||
}
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeCatchAllConflictRoot(t *testing.T) {
|
||||
routes := []testRoute{
|
||||
{"/", false},
|
||||
{"/*filepath", true},
|
||||
}
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeDoubleWildcard(t *testing.T) {
|
||||
const panicMsg = "only one wildcard per path segment is allowed"
|
||||
|
||||
routes := [...]string{
|
||||
"/:foo:bar",
|
||||
"/:foo:bar/",
|
||||
"/:foo*bar",
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
tree := &node{}
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route, nil)
|
||||
})
|
||||
|
||||
if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
|
||||
t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*func TestTreeDuplicateWildcard(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/:id/:name/:id",
|
||||
}
|
||||
for _, route := range routes {
|
||||
...
|
||||
}
|
||||
}*/
|
||||
|
||||
func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/hi",
|
||||
"/b/",
|
||||
"/search/:query",
|
||||
"/cmd/:tool/",
|
||||
"/src/*filepath",
|
||||
"/x",
|
||||
"/x/y",
|
||||
"/y/",
|
||||
"/y/z",
|
||||
"/0/:id",
|
||||
"/0/:id/1",
|
||||
"/1/:id/",
|
||||
"/1/:id/2",
|
||||
"/aa",
|
||||
"/a/",
|
||||
"/doc",
|
||||
"/doc/go_faq.html",
|
||||
"/doc/go1.html",
|
||||
"/no/a",
|
||||
"/no/b",
|
||||
"/api/hello/:name",
|
||||
}
|
||||
for _, route := range routes {
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
})
|
||||
if recv != nil {
|
||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
||||
}
|
||||
}
|
||||
|
||||
//printChildren(tree, "")
|
||||
|
||||
tsrRoutes := [...]string{
|
||||
"/hi/",
|
||||
"/b",
|
||||
"/search/gopher/",
|
||||
"/cmd/vet",
|
||||
"/src",
|
||||
"/x/",
|
||||
"/y",
|
||||
"/0/go/",
|
||||
"/1/go",
|
||||
"/a",
|
||||
"/doc/",
|
||||
}
|
||||
for _, route := range tsrRoutes {
|
||||
handler, _, tsr := tree.getValue(route)
|
||||
if handler != nil {
|
||||
t.Fatalf("non-nil handler for TSR route '%s", route)
|
||||
} else if !tsr {
|
||||
t.Errorf("expected TSR recommendation for route '%s'", route)
|
||||
}
|
||||
}
|
||||
|
||||
noTsrRoutes := [...]string{
|
||||
"/",
|
||||
"/no",
|
||||
"/no/",
|
||||
"/_",
|
||||
"/_/",
|
||||
"/api/world/abc",
|
||||
}
|
||||
for _, route := range noTsrRoutes {
|
||||
handler, _, tsr := tree.getValue(route)
|
||||
if handler != nil {
|
||||
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
||||
} else if tsr {
|
||||
t.Errorf("expected no TSR recommendation for route '%s'", route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeFindCaseInsensitivePath(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
"/hi",
|
||||
"/b/",
|
||||
"/ABC/",
|
||||
"/search/:query",
|
||||
"/cmd/:tool/",
|
||||
"/src/*filepath",
|
||||
"/x",
|
||||
"/x/y",
|
||||
"/y/",
|
||||
"/y/z",
|
||||
"/0/:id",
|
||||
"/0/:id/1",
|
||||
"/1/:id/",
|
||||
"/1/:id/2",
|
||||
"/aa",
|
||||
"/a/",
|
||||
"/doc",
|
||||
"/doc/go_faq.html",
|
||||
"/doc/go1.html",
|
||||
"/doc/go/away",
|
||||
"/no/a",
|
||||
"/no/b",
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
recv := catchPanic(func() {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
})
|
||||
if recv != nil {
|
||||
t.Fatalf("panic inserting route '%s': %v", route, recv)
|
||||
}
|
||||
}
|
||||
|
||||
// Check out == in for all registered routes
|
||||
// With fixTrailingSlash = true
|
||||
for _, route := range routes {
|
||||
out, found := tree.findCaseInsensitivePath(route, true)
|
||||
if !found {
|
||||
t.Errorf("Route '%s' not found!", route)
|
||||
} else if string(out) != route {
|
||||
t.Errorf("Wrong result for route '%s': %s", route, string(out))
|
||||
}
|
||||
}
|
||||
// With fixTrailingSlash = false
|
||||
for _, route := range routes {
|
||||
out, found := tree.findCaseInsensitivePath(route, false)
|
||||
if !found {
|
||||
t.Errorf("Route '%s' not found!", route)
|
||||
} else if string(out) != route {
|
||||
t.Errorf("Wrong result for route '%s': %s", route, string(out))
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
out string
|
||||
found bool
|
||||
slash bool
|
||||
}{
|
||||
{"/HI", "/hi", true, false},
|
||||
{"/HI/", "/hi", true, true},
|
||||
{"/B", "/b/", true, true},
|
||||
{"/B/", "/b/", true, false},
|
||||
{"/abc", "/ABC/", true, true},
|
||||
{"/abc/", "/ABC/", true, false},
|
||||
{"/aBc", "/ABC/", true, true},
|
||||
{"/aBc/", "/ABC/", true, false},
|
||||
{"/abC", "/ABC/", true, true},
|
||||
{"/abC/", "/ABC/", true, false},
|
||||
{"/SEARCH/QUERY", "/search/QUERY", true, false},
|
||||
{"/SEARCH/QUERY/", "/search/QUERY", true, true},
|
||||
{"/CMD/TOOL/", "/cmd/TOOL/", true, false},
|
||||
{"/CMD/TOOL", "/cmd/TOOL/", true, true},
|
||||
{"/SRC/FILE/PATH", "/src/FILE/PATH", true, false},
|
||||
{"/x/Y", "/x/y", true, false},
|
||||
{"/x/Y/", "/x/y", true, true},
|
||||
{"/X/y", "/x/y", true, false},
|
||||
{"/X/y/", "/x/y", true, true},
|
||||
{"/X/Y", "/x/y", true, false},
|
||||
{"/X/Y/", "/x/y", true, true},
|
||||
{"/Y/", "/y/", true, false},
|
||||
{"/Y", "/y/", true, true},
|
||||
{"/Y/z", "/y/z", true, false},
|
||||
{"/Y/z/", "/y/z", true, true},
|
||||
{"/Y/Z", "/y/z", true, false},
|
||||
{"/Y/Z/", "/y/z", true, true},
|
||||
{"/y/Z", "/y/z", true, false},
|
||||
{"/y/Z/", "/y/z", true, true},
|
||||
{"/Aa", "/aa", true, false},
|
||||
{"/Aa/", "/aa", true, true},
|
||||
{"/AA", "/aa", true, false},
|
||||
{"/AA/", "/aa", true, true},
|
||||
{"/aA", "/aa", true, false},
|
||||
{"/aA/", "/aa", true, true},
|
||||
{"/A/", "/a/", true, false},
|
||||
{"/A", "/a/", true, true},
|
||||
{"/DOC", "/doc", true, false},
|
||||
{"/DOC/", "/doc", true, true},
|
||||
{"/NO", "", false, true},
|
||||
{"/DOC/GO", "", false, true},
|
||||
}
|
||||
// With fixTrailingSlash = true
|
||||
for _, test := range tests {
|
||||
out, found := tree.findCaseInsensitivePath(test.in, true)
|
||||
if found != test.found || (found && (string(out) != test.out)) {
|
||||
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
|
||||
test.in, string(out), found, test.out, test.found)
|
||||
return
|
||||
}
|
||||
}
|
||||
// With fixTrailingSlash = false
|
||||
for _, test := range tests {
|
||||
out, found := tree.findCaseInsensitivePath(test.in, false)
|
||||
if test.slash {
|
||||
if found { // test needs a trailingSlash fix. It must not be found!
|
||||
t.Errorf("Found without fixTrailingSlash: %s; got %s", test.in, string(out))
|
||||
}
|
||||
} else {
|
||||
if found != test.found || (found && (string(out) != test.out)) {
|
||||
t.Errorf("Wrong result for '%s': got %s, %t; want %s, %t",
|
||||
test.in, string(out), found, test.out, test.found)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeInvalidNodeType(t *testing.T) {
|
||||
const panicMsg = "invalid node type"
|
||||
|
||||
tree := &node{}
|
||||
tree.addRoute("/", fakeHandler("/"))
|
||||
tree.addRoute("/:page", fakeHandler("/:page"))
|
||||
|
||||
// set invalid node type
|
||||
tree.children[0].nType = 42
|
||||
|
||||
// normal lookup
|
||||
recv := catchPanic(func() {
|
||||
tree.getValue("/test")
|
||||
})
|
||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||
}
|
||||
|
||||
// case-insensitive lookup
|
||||
recv = catchPanic(func() {
|
||||
tree.findCaseInsensitivePath("/test", true)
|
||||
})
|
||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,435 @@
|
|||
// +build go1.1
|
||||
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
var (
|
||||
selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
|
||||
selectSeriesQuery = "SELECT generate_series(1, 100)"
|
||||
)
|
||||
|
||||
func BenchmarkSelectString(b *testing.B) {
|
||||
var result string
|
||||
benchQuery(b, selectStringQuery, &result)
|
||||
}
|
||||
|
||||
func BenchmarkSelectSeries(b *testing.B) {
|
||||
var result int
|
||||
benchQuery(b, selectSeriesQuery, &result)
|
||||
}
|
||||
|
||||
func benchQuery(b *testing.B, query string, result interface{}) {
|
||||
b.StopTimer()
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchQueryLoop(b, db, query, result)
|
||||
}
|
||||
}
|
||||
|
||||
func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(result)
|
||||
if err != nil {
|
||||
b.Fatal("failed to scan", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reading from circularConn yields content[:prefixLen] once, followed by
|
||||
// content[prefixLen:] over and over again. It never returns EOF.
|
||||
type circularConn struct {
|
||||
content string
|
||||
prefixLen int
|
||||
pos int
|
||||
net.Conn // for all other net.Conn methods that will never be called
|
||||
}
|
||||
|
||||
func (r *circularConn) Read(b []byte) (n int, err error) {
|
||||
n = copy(b, r.content[r.pos:])
|
||||
r.pos += n
|
||||
if r.pos >= len(r.content) {
|
||||
r.pos = r.prefixLen
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
|
||||
|
||||
func (r *circularConn) Close() error { return nil }
|
||||
|
||||
func fakeConn(content string, prefixLen int) *conn {
|
||||
c := &circularConn{content: content, prefixLen: prefixLen}
|
||||
return &conn{buf: bufio.NewReader(c), c: c}
|
||||
}
|
||||
|
||||
// This benchmark is meant to be the same as BenchmarkSelectString, but takes
|
||||
// out some of the factors this package can't control. The numbers are less noisy,
|
||||
// but also the costs of network communication aren't accurately represented.
|
||||
func BenchmarkMockSelectString(b *testing.B) {
|
||||
b.StopTimer()
|
||||
// taken from a recorded run of BenchmarkSelectString
|
||||
// See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html
|
||||
const response = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"2\x00\x00\x00\x04" +
|
||||
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"3\x00\x00\x00\x04" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(response, 0)
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchMockQuery(b, c, selectStringQuery)
|
||||
}
|
||||
}
|
||||
|
||||
var seriesRowData = func() string {
|
||||
var buf bytes.Buffer
|
||||
for i := 1; i <= 100; i++ {
|
||||
digits := byte(2)
|
||||
if i >= 100 {
|
||||
digits = 3
|
||||
} else if i < 10 {
|
||||
digits = 1
|
||||
}
|
||||
buf.WriteString("D\x00\x00\x00")
|
||||
buf.WriteByte(10 + digits)
|
||||
buf.WriteString("\x00\x01\x00\x00\x00")
|
||||
buf.WriteByte(digits)
|
||||
buf.WriteString(strconv.Itoa(i))
|
||||
}
|
||||
return buf.String()
|
||||
}()
|
||||
|
||||
func BenchmarkMockSelectSeries(b *testing.B) {
|
||||
b.StopTimer()
|
||||
var response = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"2\x00\x00\x00\x04" +
|
||||
seriesRowData +
|
||||
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"3\x00\x00\x00\x04" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(response, 0)
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchMockQuery(b, c, selectSeriesQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func benchMockQuery(b *testing.B, c *conn, query string) {
|
||||
stmt, err := c.Prepare(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.Query(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var dest [1]driver.Value
|
||||
for {
|
||||
if err := rows.Next(dest[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPreparedSelectString(b *testing.B) {
|
||||
var result string
|
||||
benchPreparedQuery(b, selectStringQuery, &result)
|
||||
}
|
||||
|
||||
func BenchmarkPreparedSelectSeries(b *testing.B) {
|
||||
var result int
|
||||
benchPreparedQuery(b, selectSeriesQuery, &result)
|
||||
}
|
||||
|
||||
func benchPreparedQuery(b *testing.B, query string, result interface{}) {
|
||||
b.StopTimer()
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
stmt, err := db.Prepare(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedQueryLoop(b, db, stmt, result)
|
||||
}
|
||||
}
|
||||
|
||||
func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
|
||||
rows, err := stmt.Query()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if !rows.Next() {
|
||||
rows.Close()
|
||||
b.Fatal("no rows")
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&result)
|
||||
if err != nil {
|
||||
b.Fatal("failed to scan")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See the comment for BenchmarkMockSelectString.
|
||||
func BenchmarkMockPreparedSelectString(b *testing.B) {
|
||||
b.StopTimer()
|
||||
const parseResponse = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
const responses = parseResponse +
|
||||
"2\x00\x00\x00\x04" +
|
||||
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(responses, len(parseResponse))
|
||||
|
||||
stmt, err := c.Prepare(selectStringQuery)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedMockQuery(b, c, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMockPreparedSelectSeries(b *testing.B) {
|
||||
b.StopTimer()
|
||||
const parseResponse = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
var responses = parseResponse +
|
||||
"2\x00\x00\x00\x04" +
|
||||
seriesRowData +
|
||||
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(responses, len(parseResponse))
|
||||
|
||||
stmt, err := c.Prepare(selectSeriesQuery)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedMockQuery(b, c, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
|
||||
rows, err := stmt.Query(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var dest [1]driver.Value
|
||||
for {
|
||||
if err := rows.Next(dest[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeInt64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, int64(1234), oid.T_int8)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeFloat64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, 3.14159, oid.T_float8)
|
||||
}
|
||||
}
|
||||
|
||||
var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
func BenchmarkEncodeByteaHex(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
|
||||
}
|
||||
}
|
||||
func BenchmarkEncodeByteaEscape(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeBool(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, true, oid.T_bool)
|
||||
}
|
||||
}
|
||||
|
||||
var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
|
||||
|
||||
func BenchmarkEncodeTimestamptz(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz)
|
||||
}
|
||||
}
|
||||
|
||||
var testIntBytes = []byte("1234")
|
||||
|
||||
func BenchmarkDecodeInt64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
var testFloatBytes = []byte("3.14159")
|
||||
|
||||
func BenchmarkDecodeFloat64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
var testBoolBytes = []byte{'t'}
|
||||
|
||||
func BenchmarkDecodeBool(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeBool(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
rows, err := db.Query("select true")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
|
||||
|
||||
func BenchmarkDecodeTimestamptz(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
|
||||
oldProcs := runtime.GOMAXPROCS(0)
|
||||
defer runtime.GOMAXPROCS(oldProcs)
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
globalLocationCache = newLocationCache()
|
||||
|
||||
f := func(wg *sync.WaitGroup, loops int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < loops; i++ {
|
||||
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
b.ResetTimer()
|
||||
for j := 0; j < 10; j++ {
|
||||
wg.Add(1)
|
||||
go f(wg, b.N/10)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkLocationCache(b *testing.B) {
|
||||
globalLocationCache = newLocationCache()
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalLocationCache.getLocation(rand.Intn(10000))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLocationCacheMultiThread(b *testing.B) {
|
||||
oldProcs := runtime.GOMAXPROCS(0)
|
||||
defer runtime.GOMAXPROCS(oldProcs)
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
globalLocationCache = newLocationCache()
|
||||
|
||||
f := func(wg *sync.WaitGroup, loops int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < loops; i++ {
|
||||
globalLocationCache.getLocation(rand.Intn(10000))
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
b.ResetTimer()
|
||||
for j := 0; j < 10; j++ {
|
||||
wg.Add(1)
|
||||
go f(wg, b.N/10)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Stress test the performance of parsing results from the wire.
|
||||
func BenchmarkResultParsing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
_, err := db.Exec("BEGIN")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
res, err := db.Query("SELECT generate_series(1, 50000)")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
res.Close()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,463 @@
|
|||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCopyInStmt(t *testing.T) {
|
||||
stmt := CopyIn("table name")
|
||||
if stmt != `COPY "table name" () FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyIn("table name", "column 1", "column 2")
|
||||
if stmt != `COPY "table name" ("column 1", "column 2") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyIn(`table " name """`, `co"lumn""`)
|
||||
if stmt != `COPY "table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInSchemaStmt(t *testing.T) {
|
||||
stmt := CopyInSchema("schema name", "table name")
|
||||
if stmt != `COPY "schema name"."table name" () FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyInSchema("schema name", "table name", "column 1", "column 2")
|
||||
if stmt != `COPY "schema name"."table name" ("column 1", "column 2") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyInSchema(`schema " name """`, `table " name """`, `co"lumn""`)
|
||||
if stmt != `COPY "schema "" name """"""".`+
|
||||
`"table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInMultipleValues(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
longString := strings.Repeat("#", 500)
|
||||
|
||||
for i := 0; i < 500; i++ {
|
||||
_, err = stmt.Exec(int64(i), longString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 500 {
|
||||
t.Fatalf("expected 500 items, not %d", num)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInRaiseStmtTrigger(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
var exists int
|
||||
err := db.QueryRow("SELECT 1 FROM pg_language WHERE lanname = 'plpgsql'").Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
t.Skip("language PL/PgSQL does not exist; skipping TestCopyInRaiseStmtTrigger")
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec(`
|
||||
CREATE OR REPLACE FUNCTION pg_temp.temptest()
|
||||
RETURNS trigger AS
|
||||
$BODY$ begin
|
||||
raise notice 'Hello world';
|
||||
return new;
|
||||
end $BODY$
|
||||
LANGUAGE plpgsql`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec(`
|
||||
CREATE TRIGGER temptest_trigger
|
||||
BEFORE INSERT
|
||||
ON temp
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE pg_temp.temptest()`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
longString := strings.Repeat("#", 500)
|
||||
|
||||
_, err = stmt.Exec(int64(1), longString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 1 {
|
||||
t.Fatalf("expected 1 items, not %d", num)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInTypes(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER, text VARCHAR, blob BYTEA, nothing VARCHAR)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "num", "text", "blob", "nothing"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(int64(1234567890), "Héllö\n ☃!\r\t\\", []byte{0, 255, 9, 10, 13}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
var text string
|
||||
var blob []byte
|
||||
var nothing sql.NullString
|
||||
|
||||
err = txn.QueryRow("SELECT * FROM temp").Scan(&num, &text, &blob, ¬hing)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 1234567890 {
|
||||
t.Fatal("unexpected result", num)
|
||||
}
|
||||
if text != "Héllö\n ☃!\r\t\\" {
|
||||
t.Fatal("unexpected result", text)
|
||||
}
|
||||
if !bytes.Equal(blob, []byte{0, 255, 9, 10, 13}) {
|
||||
t.Fatal("unexpected result", blob)
|
||||
}
|
||||
if nothing.Valid {
|
||||
t.Fatal("unexpected result", nothing.String)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInWrongType(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "num"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec("Héllö\n ☃!\r\t\\")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if pge := err.(*Error); pge.Code.Name() != "invalid_text_representation" {
|
||||
t.Fatalf("expected 'invalid input syntax for integer' error, got %s (%+v)", pge.Code.Name(), pge)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyOutsideOfTxnError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.Prepare(CopyIn("temp", "num"))
|
||||
if err == nil {
|
||||
t.Fatal("COPY outside of transaction did not return an error")
|
||||
}
|
||||
if err != errCopyNotSupportedOutsideTxn {
|
||||
t.Fatalf("expected %s, got %s", err, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInBinaryError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = txn.Prepare("COPY temp (num) FROM STDIN WITH binary")
|
||||
if err != errBinaryCopyNotSupported {
|
||||
t.Fatalf("expected %s, got %+v", errBinaryCopyNotSupported, err)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = txn.Prepare("COPY temp (num) TO STDOUT")
|
||||
if err != errCopyToNotSupported {
|
||||
t.Fatalf("expected %s, got %+v", errCopyToNotSupported, err)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopySyntaxError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Prepare("COPY ")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if pge := err.(*Error); pge.Code.Name() != "syntax_error" {
|
||||
t.Fatalf("expected syntax error, got %s (%+v)", pge.Code.Name(), pge)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for connection errors in copyin.resploop()
|
||||
func TestCopyRespLoopConnectionError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
var pid int
|
||||
err = txn.QueryRow("SELECT pg_backend_pid()").Scan(&pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = db.Exec("SELECT pg_terminate_backend($1)", pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if getServerVersion(t, db) < 90500 {
|
||||
// We have to try and send something over, since postgres before
|
||||
// version 9.5 won't process SIGTERMs while it's waiting for
|
||||
// CopyData/CopyEnd messages; see tcop/postgres.c.
|
||||
_, err = stmt.Exec(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, err = stmt.Exec()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
pge, ok := err.(*Error)
|
||||
if !ok {
|
||||
if err == driver.ErrBadConn {
|
||||
// likely an EPIPE
|
||||
} else {
|
||||
t.Fatalf("expected *pq.Error or driver.ErrBadConn, got %+#v", err)
|
||||
}
|
||||
} else if pge.Code.Name() != "admin_shutdown" {
|
||||
t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name())
|
||||
}
|
||||
|
||||
_ = stmt.Close()
|
||||
}
|
||||
|
||||
func BenchmarkCopyIn(b *testing.B) {
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = stmt.Exec(int64(i), "hello world!")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if num != b.N {
|
||||
b.Fatalf("expected %d items, not %d", b.N, num)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,763 @@
|
|||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
func TestScanTimestamp(t *testing.T) {
|
||||
var nt NullTime
|
||||
tn := time.Now()
|
||||
nt.Scan(tn)
|
||||
if !nt.Valid {
|
||||
t.Errorf("Expected Valid=false")
|
||||
}
|
||||
if nt.Time != tn {
|
||||
t.Errorf("Time value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanNilTimestamp(t *testing.T) {
|
||||
var nt NullTime
|
||||
nt.Scan(nil)
|
||||
if nt.Valid {
|
||||
t.Errorf("Expected Valid=false")
|
||||
}
|
||||
}
|
||||
|
||||
var timeTests = []struct {
|
||||
str string
|
||||
timeval time.Time
|
||||
}{
|
||||
{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"0001-12-31 BC", time.Date(0, time.December, 31, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03 BC", time.Date(-2000, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", -(7*60*60+42*60)))},
|
||||
{"2001-02-03 04:05:06-07:30:09", 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", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", 7*60*60))},
|
||||
{"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||
{"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"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)},
|
||||
{"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"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)},
|
||||
{"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
{"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
}
|
||||
|
||||
// Test that parsing the string results in the expected value.
|
||||
func TestParseTs(t *testing.T) {
|
||||
for i, tt := range timeTests {
|
||||
val, err := ParseTimestamp(nil, tt.str)
|
||||
if err != nil {
|
||||
t.Errorf("%d: got error: %v", i, err)
|
||||
} else if val.String() != tt.timeval.String() {
|
||||
t.Errorf("%d: expected to parse %q into %q; got %q",
|
||||
i, tt.str, tt.timeval, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var timeErrorTests = []string{
|
||||
"BC",
|
||||
" BC",
|
||||
"2001",
|
||||
"2001-2-03",
|
||||
"2001-02-3",
|
||||
"2001-02-03 ",
|
||||
"2001-02-03 B",
|
||||
"2001-02-03 04",
|
||||
"2001-02-03 04:",
|
||||
"2001-02-03 04:05",
|
||||
"2001-02-03 04:05 B",
|
||||
"2001-02-03 04:05 BC",
|
||||
"2001-02-03 04:05:",
|
||||
"2001-02-03 04:05:6",
|
||||
"2001-02-03 04:05:06 B",
|
||||
"2001-02-03 04:05:06BC",
|
||||
"2001-02-03 04:05:06.123 B",
|
||||
}
|
||||
|
||||
// Test that parsing the string results in an error.
|
||||
func TestParseTsErrors(t *testing.T) {
|
||||
for i, tt := range timeErrorTests {
|
||||
_, err := ParseTimestamp(nil, tt)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected an error from parsing: %v", i, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now test that sending the value into the database and parsing it back
|
||||
// returns the same time.Time value.
|
||||
func TestEncodeAndParseTs(t *testing.T) {
|
||||
db, err := openTestConnConninfo("timezone='Etc/UTC'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for i, tt := range timeTests {
|
||||
var dbstr string
|
||||
err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
|
||||
if err != nil {
|
||||
t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := ParseTimestamp(nil, dbstr)
|
||||
if err != nil {
|
||||
t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
|
||||
continue
|
||||
}
|
||||
val = val.In(tt.timeval.Location())
|
||||
if val.String() != tt.timeval.String() {
|
||||
t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatTimeTests = []struct {
|
||||
time time.Time
|
||||
expected string
|
||||
}{
|
||||
{time.Time{}, "0001-01-01 00:00:00Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03 04:05:06.123456789Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03 04:05:06.123456789+02:00"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03 04:05:06.123456789-06:00"},
|
||||
{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"},
|
||||
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03 04:05:06.123456789+02:00"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03 04:05:06.123456789-06:00"},
|
||||
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03 04:05:06.123456789Z BC"},
|
||||
{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"},
|
||||
{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"},
|
||||
|
||||
{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"},
|
||||
{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"},
|
||||
}
|
||||
|
||||
func TestFormatTs(t *testing.T) {
|
||||
for i, tt := range formatTimeTests {
|
||||
val := string(formatTs(tt.time))
|
||||
if val != tt.expected {
|
||||
t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatTsBackend(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
var str string
|
||||
err := db.QueryRow("SELECT '2001-02-03T04:05:06.007-08:09:10'::time::text").Scan(&str)
|
||||
if err == nil {
|
||||
t.Fatalf("PostgreSQL is accepting an ISO timestamp input for time")
|
||||
}
|
||||
|
||||
for i, tt := range formatTimeTests {
|
||||
for _, typ := range []string{"date", "time", "timetz", "timestamp", "timestamptz"} {
|
||||
err = db.QueryRow("SELECT $1::"+typ+"::text", tt.time).Scan(&str)
|
||||
if err != nil {
|
||||
t.Errorf("%d: incorrect time format for %v on the backend: %v", i, typ, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampWithTimeZone(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// try several different locations, all included in Go's zoneinfo.zip
|
||||
for _, locName := range []string{
|
||||
"UTC",
|
||||
"America/Chicago",
|
||||
"America/New_York",
|
||||
"Australia/Darwin",
|
||||
"Australia/Perth",
|
||||
} {
|
||||
loc, err := time.LoadLocation(locName)
|
||||
if err != nil {
|
||||
t.Logf("Could not load time zone %s - skipping", locName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Postgres timestamps have a resolution of 1 microsecond, so don't
|
||||
// use the full range of the Nanosecond argument
|
||||
refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
|
||||
|
||||
for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
|
||||
// Switch Postgres's timezone to test different output timestamp formats
|
||||
_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var gotTime time.Time
|
||||
row := tx.QueryRow("select $1::timestamp with time zone", refTime)
|
||||
err = row.Scan(&gotTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !refTime.Equal(gotTime) {
|
||||
t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
|
||||
}
|
||||
|
||||
// check that the time zone is set correctly based on TimeZone
|
||||
pgLoc, err := time.LoadLocation(pgTimeZone)
|
||||
if err != nil {
|
||||
t.Logf("Could not load time zone %s - skipping", pgLoc)
|
||||
continue
|
||||
}
|
||||
translated := refTime.In(pgLoc)
|
||||
if translated.String() != gotTime.String() {
|
||||
t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampWithOutTimezone(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
test := func(ts, pgts string) {
|
||||
r, err := db.Query("SELECT $1::timestamp", pgts)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not run query: %v", err)
|
||||
}
|
||||
|
||||
if !r.Next() {
|
||||
t.Fatal("Expected at least one row")
|
||||
}
|
||||
|
||||
var result time.Time
|
||||
err = r.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatalf("Did not expect error scanning row: %v", err)
|
||||
}
|
||||
|
||||
expected, err := time.Parse(time.RFC3339, ts)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse test time literal: %v", err)
|
||||
}
|
||||
|
||||
if !result.Equal(expected) {
|
||||
t.Fatalf("Expected time to match %v: got mismatch %v",
|
||||
expected, result)
|
||||
}
|
||||
|
||||
if r.Next() {
|
||||
t.Fatal("Expected only one row")
|
||||
}
|
||||
}
|
||||
|
||||
test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
|
||||
|
||||
// Test higher precision time
|
||||
test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
|
||||
}
|
||||
|
||||
func TestInfinityTimestamp(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
var err error
|
||||
var resultT time.Time
|
||||
|
||||
expectedErrorStrPrefix := `sql: Scan error on column index 0: unsupported`
|
||||
type testCases []struct {
|
||||
Query string
|
||||
Param string
|
||||
ExpectedErrStrPrefix string
|
||||
ExpectedVal interface{}
|
||||
}
|
||||
tc := testCases{
|
||||
{"SELECT $1::timestamp", "-infinity", expectedErrorStrPrefix, "-infinity"},
|
||||
{"SELECT $1::timestamptz", "-infinity", expectedErrorStrPrefix, "-infinity"},
|
||||
{"SELECT $1::timestamp", "infinity", expectedErrorStrPrefix, "infinity"},
|
||||
{"SELECT $1::timestamptz", "infinity", expectedErrorStrPrefix, "infinity"},
|
||||
}
|
||||
// try to assert []byte to time.Time
|
||||
for _, q := range tc {
|
||||
err = db.QueryRow(q.Query, q.Param).Scan(&resultT)
|
||||
if !strings.HasPrefix(err.Error(), q.ExpectedErrStrPrefix) {
|
||||
t.Errorf("Scanning -/+infinity, expected error to have prefix %q, got %q", q.ExpectedErrStrPrefix, err)
|
||||
}
|
||||
}
|
||||
// yield []byte
|
||||
for _, q := range tc {
|
||||
var resultI interface{}
|
||||
err = db.QueryRow(q.Query, q.Param).Scan(&resultI)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -/+infinity, expected no error, got %q", err)
|
||||
}
|
||||
result, ok := resultI.([]byte)
|
||||
if !ok {
|
||||
t.Errorf("Scanning -/+infinity, expected []byte, got %#v", resultI)
|
||||
}
|
||||
if string(result) != q.ExpectedVal {
|
||||
t.Errorf("Scanning -/+infinity, expected %q, got %q", q.ExpectedVal, result)
|
||||
}
|
||||
}
|
||||
|
||||
y1500 := time.Date(1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
y2500 := time.Date(2500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
EnableInfinityTs(y1500, y2500)
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp", "infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y2500) {
|
||||
t.Errorf("Scanning infinity, expected %q, got %q", y2500, resultT)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamptz", "infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y2500) {
|
||||
t.Errorf("Scanning Infinity, expected time %q, got %q", y2500, resultT.String())
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp", "-infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y1500) {
|
||||
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamptz", "-infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y1500) {
|
||||
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||
}
|
||||
|
||||
ym1500 := time.Date(-1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
y11500 := time.Date(11500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
var s string
|
||||
err = db.QueryRow("SELECT $1::timestamp::text", ym1500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "-infinity" {
|
||||
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||
}
|
||||
err = db.QueryRow("SELECT $1::timestamptz::text", ym1500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "-infinity" {
|
||||
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp::text", y11500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "infinity" {
|
||||
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||
}
|
||||
err = db.QueryRow("SELECT $1::timestamptz::text", y11500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "infinity" {
|
||||
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||
}
|
||||
|
||||
disableInfinityTs()
|
||||
|
||||
var panicErrorString string
|
||||
func() {
|
||||
defer func() {
|
||||
panicErrorString, _ = recover().(string)
|
||||
}()
|
||||
EnableInfinityTs(y2500, y1500)
|
||||
}()
|
||||
if panicErrorString != infinityTsNegativeMustBeSmaller {
|
||||
t.Errorf("Expected error, %q, got %q", infinityTsNegativeMustBeSmaller, panicErrorString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWithNul(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
hello0world := string("hello\x00world")
|
||||
_, err := db.Query("SELECT $1::text", &hello0world)
|
||||
if err == nil {
|
||||
t.Fatal("Postgres accepts a string with nul in it; " +
|
||||
"injection attacks may be plausible")
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteSliceToText(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte("hello world")
|
||||
row := db.QueryRow("SELECT $1::text", b)
|
||||
|
||||
var result []byte
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(result) != string(b) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToBytea(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := "hello world"
|
||||
row := db.QueryRow("SELECT $1::bytea", b)
|
||||
|
||||
var result []byte
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(result, []byte(b)) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextByteSliceToUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
|
||||
row := db.QueryRow("SELECT $1::uuid", b)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22P03" {
|
||||
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != string(b) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryByteSlicetoUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte{'\xa0', '\xee', '\xbc', '\x99',
|
||||
'\x9c', '\x0b',
|
||||
'\x4e', '\xf8',
|
||||
'\xbb', '\x00', '\x6b',
|
||||
'\xb9', '\xbd', '\x38', '\x0a', '\x11'}
|
||||
row := db.QueryRow("SELECT $1::uuid", b)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != string("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
} else {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22021" {
|
||||
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
s := "a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"
|
||||
row := db.QueryRow("SELECT $1::uuid", s)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != s {
|
||||
t.Fatalf("expected %v but got %v", s, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextByteSliceToInt(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
expected := 12345678
|
||||
b := []byte(fmt.Sprintf("%d", expected))
|
||||
row := db.QueryRow("SELECT $1::int", b)
|
||||
|
||||
var result int
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22P03" {
|
||||
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("expected %v but got %v", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryByteSliceToInt(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
expected := 12345678
|
||||
b := []byte{'\x00', '\xbc', '\x61', '\x4e'}
|
||||
row := db.QueryRow("SELECT $1::int", b)
|
||||
|
||||
var result int
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("expected %v but got %v", expected, result)
|
||||
}
|
||||
} else {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22021" {
|
||||
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextDecodeIntoString(t *testing.T) {
|
||||
input := []byte("hello world")
|
||||
want := string(input)
|
||||
for _, typ := range []oid.Oid{oid.T_char, oid.T_varchar, oid.T_text} {
|
||||
got := decode(¶meterStatus{}, input, typ, formatText)
|
||||
if got != want {
|
||||
t.Errorf("invalid string decoding output for %T(%+v), got %v but expected %v", typ, typ, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaOutputFormatEncoding(t *testing.T) {
|
||||
input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
|
||||
want := []byte("\\x5c78000102fffe6162636465666730313233")
|
||||
got := encode(¶meterStatus{serverVersion: 90000}, input, oid.T_bytea)
|
||||
if !bytes.Equal(want, got) {
|
||||
t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
|
||||
}
|
||||
|
||||
want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
|
||||
got = encode(¶meterStatus{serverVersion: 84000}, input, oid.T_bytea)
|
||||
if !bytes.Equal(want, got) {
|
||||
t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaOutputFormats(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
// skip
|
||||
return
|
||||
}
|
||||
|
||||
testByteaOutputFormat := func(f string, usePrepared bool) {
|
||||
expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
|
||||
sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
|
||||
|
||||
var data []byte
|
||||
|
||||
// use a txn to avoid relying on getting the same connection
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("SET LOCAL bytea_output TO " + f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var rows *sql.Rows
|
||||
var stmt *sql.Stmt
|
||||
if usePrepared {
|
||||
stmt, err = txn.Prepare(sqlQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows, err = stmt.Query()
|
||||
} else {
|
||||
// use Query; QueryRow would hide the actual error
|
||||
rows, err = txn.Query(sqlQuery)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !rows.Next() {
|
||||
if rows.Err() != nil {
|
||||
t.Fatal(rows.Err())
|
||||
}
|
||||
t.Fatal("shouldn't happen")
|
||||
}
|
||||
err = rows.Scan(&data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = rows.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt != nil {
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(data, expectedData) {
|
||||
t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
|
||||
}
|
||||
}
|
||||
|
||||
testByteaOutputFormat("hex", false)
|
||||
testByteaOutputFormat("escape", false)
|
||||
testByteaOutputFormat("hex", true)
|
||||
testByteaOutputFormat("escape", true)
|
||||
}
|
||||
|
||||
func TestAppendEncodedText(t *testing.T) {
|
||||
var buf []byte
|
||||
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10))
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, 42.0000000001)
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, "hello\tworld")
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
|
||||
|
||||
if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
|
||||
t.Fatal(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendEscapedText(t *testing.T) {
|
||||
if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendEscapedTextExistingBuffer(t *testing.T) {
|
||||
buf := []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
buf = []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
buf = []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendEscapedText(b *testing.B) {
|
||||
longString := ""
|
||||
for i := 0; i < 100; i++ {
|
||||
longString += "123456789\n"
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
appendEscapedText(nil, longString)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
|
||||
longString := ""
|
||||
for i := 0; i < 100; i++ {
|
||||
longString += "1234567890"
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
appendEscapedText(nil, longString)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
// +build go1.8
|
||||
|
||||
package pq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMultipleSimpleQuery(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("select 1; set time zone default; select 2; select 3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var i int
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Fatalf("expected 1, got %d", i)
|
||||
}
|
||||
}
|
||||
if !rows.NextResultSet() {
|
||||
t.Fatal("expected more result sets", rows.Err())
|
||||
}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 2 {
|
||||
t.Fatalf("expected 2, got %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that if we ignore a result we can still query.
|
||||
|
||||
rows, err = db.Query("select 4; select 5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 4 {
|
||||
t.Fatalf("expected 4, got %d", i)
|
||||
}
|
||||
}
|
||||
if !rows.NextResultSet() {
|
||||
t.Fatal("expected more result sets", rows.Err())
|
||||
}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&i); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 5 {
|
||||
t.Fatalf("expected 5, got %d", i)
|
||||
}
|
||||
}
|
||||
if rows.NextResultSet() {
|
||||
t.Fatal("unexpected result set")
|
||||
}
|
||||
}
|
||||
|
||||
const contextRaceIterations = 100
|
||||
|
||||
func TestContextCancelExec(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Delay execution for just a bit until db.ExecContext has begun.
|
||||
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||
|
||||
// Not canceled until after the exec has started.
|
||||
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Context is already canceled, so error should come before execution.
|
||||
if _, err := db.ExecContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "context canceled" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < contextRaceIterations; i++ {
|
||||
func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if _, err := db.ExecContext(ctx, "select 1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := db.Exec("select 1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextCancelQuery(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Delay execution for just a bit until db.QueryContext has begun.
|
||||
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||
|
||||
// Not canceled until after the exec has started.
|
||||
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Context is already canceled, so error should come before execution.
|
||||
if _, err := db.QueryContext(ctx, "select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "context canceled" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < contextRaceIterations; i++ {
|
||||
func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
rows, err := db.QueryContext(ctx, "select 1")
|
||||
cancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := rows.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if rows, err := db.Query("select 1"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := rows.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssue617 tests that a failed query in QueryContext doesn't lead to a
|
||||
// goroutine leak.
|
||||
func TestIssue617(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
const N = 10
|
||||
|
||||
numGoroutineStart := runtime.NumGoroutine()
|
||||
for i := 0; i < N; i++ {
|
||||
func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, err := db.QueryContext(ctx, `SELECT * FROM DOESNOTEXIST`)
|
||||
pqErr, _ := err.(*Error)
|
||||
// Expecting "pq: relation \"doesnotexist\" does not exist" error.
|
||||
if err == nil || pqErr == nil || pqErr.Code != "42P01" {
|
||||
t.Fatalf("expected undefined table error, got %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
numGoroutineFinish := runtime.NumGoroutine()
|
||||
|
||||
// We use N/2 and not N because the GC and other actors may increase or
|
||||
// decrease the number of goroutines.
|
||||
if numGoroutineFinish-numGoroutineStart >= N/2 {
|
||||
t.Errorf("goroutine leak detected, was %d, now %d", numGoroutineStart, numGoroutineFinish)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextCancelBegin(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Delay execution for just a bit until tx.Exec has begun.
|
||||
defer time.AfterFunc(time.Millisecond*10, cancel).Stop()
|
||||
|
||||
// Not canceled until after the exec has started.
|
||||
if _, err := tx.Exec("select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "pq: canceling statement due to user request" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Transaction is canceled, so expect an error.
|
||||
if _, err := tx.Query("select pg_sleep(1)"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err != sql.ErrTxDone {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// Context is canceled, so cannot begin a transaction.
|
||||
if _, err := db.BeginTx(ctx, nil); err == nil {
|
||||
t.Fatal("expected error")
|
||||
} else if err.Error() != "context canceled" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
for i := 0; i < contextRaceIterations; i++ {
|
||||
func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
cancel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if tx, err := db.Begin(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxOptions(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
|
||||
tests := []struct {
|
||||
level sql.IsolationLevel
|
||||
isolation string
|
||||
}{
|
||||
{
|
||||
level: sql.LevelDefault,
|
||||
isolation: "",
|
||||
},
|
||||
{
|
||||
level: sql.LevelReadUncommitted,
|
||||
isolation: "read uncommitted",
|
||||
},
|
||||
{
|
||||
level: sql.LevelReadCommitted,
|
||||
isolation: "read committed",
|
||||
},
|
||||
{
|
||||
level: sql.LevelRepeatableRead,
|
||||
isolation: "repeatable read",
|
||||
},
|
||||
{
|
||||
level: sql.LevelSerializable,
|
||||
isolation: "serializable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, ro := range []bool{true, false} {
|
||||
tx, err := db.BeginTx(ctx, &sql.TxOptions{
|
||||
Isolation: test.level,
|
||||
ReadOnly: ro,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var isolation string
|
||||
err = tx.QueryRow("select current_setting('transaction_isolation')").Scan(&isolation)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if test.isolation != "" && isolation != test.isolation {
|
||||
t.Errorf("wrong isolation level: %s != %s", isolation, test.isolation)
|
||||
}
|
||||
|
||||
var isRO string
|
||||
err = tx.QueryRow("select current_setting('transaction_read_only')").Scan(&isRO)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ro != (isRO == "on") {
|
||||
t.Errorf("read/[write,only] not set: %t != %s for level %s",
|
||||
ro, isRO, test.isolation)
|
||||
}
|
||||
|
||||
tx.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
_, err := db.BeginTx(ctx, &sql.TxOptions{
|
||||
Isolation: sql.LevelLinearizable,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected LevelLinearizable to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "isolation level not supported") {
|
||||
t.Errorf("Expected error to mention isolation level, got %q", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package pq
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIssue494(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
query := `CREATE TEMP TABLE t (i INT PRIMARY KEY)`
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := txn.Prepare(CopyIn("t", "i")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := txn.Query("SELECT 1"); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,570 @@
|
|||
package pq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errNilNotification = errors.New("nil notification")
|
||||
|
||||
func expectNotification(t *testing.T, ch <-chan *Notification, relname string, extra string) error {
|
||||
select {
|
||||
case n := <-ch:
|
||||
if n == nil {
|
||||
return errNilNotification
|
||||
}
|
||||
if n.Channel != relname || n.Extra != extra {
|
||||
return fmt.Errorf("unexpected notification %v", n)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(1500 * time.Millisecond):
|
||||
return fmt.Errorf("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
|
||||
select {
|
||||
case n := <-ch:
|
||||
return fmt.Errorf("unexpected notification %v", n)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error {
|
||||
select {
|
||||
case e := <-eventch:
|
||||
if e != et {
|
||||
return fmt.Errorf("unexpected event %v", e)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(1500 * time.Millisecond):
|
||||
panic("expectEvent timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
|
||||
select {
|
||||
case e := <-eventch:
|
||||
return fmt.Errorf("unexpected event %v", e)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) {
|
||||
datname := os.Getenv("PGDATABASE")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
|
||||
if datname == "" {
|
||||
os.Setenv("PGDATABASE", "pqgotest")
|
||||
}
|
||||
|
||||
if sslmode == "" {
|
||||
os.Setenv("PGSSLMODE", "disable")
|
||||
}
|
||||
|
||||
notificationChan := make(chan *Notification)
|
||||
l, err := NewListenerConn("", notificationChan)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return l, notificationChan
|
||||
}
|
||||
|
||||
func TestNewListenerConn(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
}
|
||||
|
||||
func TestConnListen(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnUnlisten(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err = l.Unlisten("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, channel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnUnlistenAll(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err = l.UnlistenAll()
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, channel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnClose(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != errListenerConnClosed {
|
||||
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnPing(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
err := l.Ping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Ping()
|
||||
if err != errListenerConnClosed {
|
||||
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for deadlock where a query fails while another one is queued
|
||||
func TestConnExecDeadlock(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||
wg.Done()
|
||||
}()
|
||||
runtime.Gosched()
|
||||
go func() {
|
||||
l.ExecSimpleQuery("SELECT 1")
|
||||
wg.Done()
|
||||
}()
|
||||
// give the two goroutines some time to get into position
|
||||
runtime.Gosched()
|
||||
// calls Close on the net.Conn; equivalent to a network failure
|
||||
l.Close()
|
||||
|
||||
defer time.AfterFunc(10*time.Second, func() {
|
||||
panic("timed out")
|
||||
}).Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Test for ListenerConn being closed while a slow query is executing
|
||||
func TestListenerConnCloseWhileQueryIsExecuting(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
sent, err := l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||
if sent {
|
||||
panic("expected sent=false")
|
||||
}
|
||||
// could be any of a number of errors
|
||||
if err == nil {
|
||||
panic("expected error")
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
// give the above goroutine some time to get into position
|
||||
runtime.Gosched()
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer time.AfterFunc(10*time.Second, func() {
|
||||
panic("timed out")
|
||||
}).Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestNotifyExtra(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
t.Skip("skipping NOTIFY payload test since the server does not appear to support it")
|
||||
}
|
||||
|
||||
l, channel := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test, 'something'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "something")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// create a new test listener and also set the timeouts
|
||||
func newTestListenerTimeout(t *testing.T, min time.Duration, max time.Duration) (*Listener, <-chan ListenerEventType) {
|
||||
datname := os.Getenv("PGDATABASE")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
|
||||
if datname == "" {
|
||||
os.Setenv("PGDATABASE", "pqgotest")
|
||||
}
|
||||
|
||||
if sslmode == "" {
|
||||
os.Setenv("PGSSLMODE", "disable")
|
||||
}
|
||||
|
||||
eventch := make(chan ListenerEventType, 16)
|
||||
l := NewListener("", min, max, func(t ListenerEventType, err error) { eventch <- t })
|
||||
err := expectEvent(t, eventch, ListenerEventConnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l, eventch
|
||||
}
|
||||
|
||||
func newTestListener(t *testing.T) (*Listener, <-chan ListenerEventType) {
|
||||
return newTestListenerTimeout(t, time.Hour, time.Hour)
|
||||
}
|
||||
|
||||
func TestListenerListen(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerUnlisten(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Unlisten("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, l.Notify)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerUnlistenAll(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.UnlistenAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, l.Notify)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerFailedQuery(t *testing.T) {
|
||||
l, eventch := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// shouldn't cause a disconnect
|
||||
ok, err := l.cn.ExecSimpleQuery("SELECT error")
|
||||
if !ok {
|
||||
t.Fatalf("could not send query to server: %v", err)
|
||||
}
|
||||
_, ok = err.(PGError)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
err = expectNoEvent(t, eventch)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should still work
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerReconnect(t *testing.T) {
|
||||
l, eventch := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// kill the connection and make sure it comes back up
|
||||
ok, err := l.cn.ExecSimpleQuery("SELECT pg_terminate_backend(pg_backend_pid())")
|
||||
if ok {
|
||||
t.Fatalf("could not kill the connection: %v", err)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
err = expectEvent(t, eventch, ListenerEventDisconnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = expectEvent(t, eventch, ListenerEventReconnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should still work
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should get nil after Reconnected
|
||||
err = expectNotification(t, l.Notify, "", "")
|
||||
if err != errNilNotification {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerClose(t *testing.T) {
|
||||
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != errListenerClosed {
|
||||
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerPing(t *testing.T) {
|
||||
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Ping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Ping()
|
||||
if err != errListenerClosed {
|
||||
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
// +build go1.8
|
||||
|
||||
package pq
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
func TestDataTypeName(t *testing.T) {
|
||||
tts := []struct {
|
||||
typ oid.Oid
|
||||
name string
|
||||
}{
|
||||
{oid.T_int8, "INT8"},
|
||||
{oid.T_int4, "INT4"},
|
||||
{oid.T_int2, "INT2"},
|
||||
{oid.T_varchar, "VARCHAR"},
|
||||
{oid.T_text, "TEXT"},
|
||||
{oid.T_bool, "BOOL"},
|
||||
{oid.T_numeric, "NUMERIC"},
|
||||
{oid.T_date, "DATE"},
|
||||
{oid.T_time, "TIME"},
|
||||
{oid.T_timetz, "TIMETZ"},
|
||||
{oid.T_timestamp, "TIMESTAMP"},
|
||||
{oid.T_timestamptz, "TIMESTAMPTZ"},
|
||||
{oid.T_bytea, "BYTEA"},
|
||||
}
|
||||
|
||||
for i, tt := range tts {
|
||||
dt := fieldDesc{OID: tt.typ}
|
||||
if name := dt.Name(); name != tt.name {
|
||||
t.Errorf("(%d) got: %s want: %s", i, name, tt.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataType(t *testing.T) {
|
||||
tts := []struct {
|
||||
typ oid.Oid
|
||||
kind reflect.Kind
|
||||
}{
|
||||
{oid.T_int8, reflect.Int64},
|
||||
{oid.T_int4, reflect.Int32},
|
||||
{oid.T_int2, reflect.Int16},
|
||||
{oid.T_varchar, reflect.String},
|
||||
{oid.T_text, reflect.String},
|
||||
{oid.T_bool, reflect.Bool},
|
||||
{oid.T_date, reflect.Struct},
|
||||
{oid.T_time, reflect.Struct},
|
||||
{oid.T_timetz, reflect.Struct},
|
||||
{oid.T_timestamp, reflect.Struct},
|
||||
{oid.T_timestamptz, reflect.Struct},
|
||||
{oid.T_bytea, reflect.Slice},
|
||||
}
|
||||
|
||||
for i, tt := range tts {
|
||||
dt := fieldDesc{OID: tt.typ}
|
||||
if kind := dt.Type().Kind(); kind != tt.kind {
|
||||
t.Errorf("(%d) got: %s want: %s", i, kind, tt.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataTypeLength(t *testing.T) {
|
||||
tts := []struct {
|
||||
typ oid.Oid
|
||||
len int
|
||||
mod int
|
||||
length int64
|
||||
ok bool
|
||||
}{
|
||||
{oid.T_int4, 0, -1, 0, false},
|
||||
{oid.T_varchar, 65535, 9, 5, true},
|
||||
{oid.T_text, 65535, -1, math.MaxInt64, true},
|
||||
{oid.T_bytea, 65535, -1, math.MaxInt64, true},
|
||||
}
|
||||
|
||||
for i, tt := range tts {
|
||||
dt := fieldDesc{OID: tt.typ, Len: tt.len, Mod: tt.mod}
|
||||
if l, k := dt.Length(); k != tt.ok || l != tt.length {
|
||||
t.Errorf("(%d) got: %d, %t want: %d, %t", i, l, k, tt.length, tt.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataTypePrecisionScale(t *testing.T) {
|
||||
tts := []struct {
|
||||
typ oid.Oid
|
||||
mod int
|
||||
precision, scale int64
|
||||
ok bool
|
||||
}{
|
||||
{oid.T_int4, -1, 0, 0, false},
|
||||
{oid.T_numeric, 589830, 9, 2, true},
|
||||
{oid.T_text, -1, 0, 0, false},
|
||||
}
|
||||
|
||||
for i, tt := range tts {
|
||||
dt := fieldDesc{OID: tt.typ, Mod: tt.mod}
|
||||
p, s, k := dt.PrecisionScale()
|
||||
if k != tt.ok {
|
||||
t.Errorf("(%d) got: %t want: %t", i, k, tt.ok)
|
||||
}
|
||||
if p != tt.precision {
|
||||
t.Errorf("(%d) wrong precision got: %d want: %d", i, p, tt.precision)
|
||||
}
|
||||
if s != tt.scale {
|
||||
t.Errorf("(%d) wrong scale got: %d want: %d", i, s, tt.scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowsColumnTypes(t *testing.T) {
|
||||
columnTypesTests := []struct {
|
||||
Name string
|
||||
TypeName string
|
||||
Length struct {
|
||||
Len int64
|
||||
OK bool
|
||||
}
|
||||
DecimalSize struct {
|
||||
Precision int64
|
||||
Scale int64
|
||||
OK bool
|
||||
}
|
||||
ScanType reflect.Type
|
||||
}{
|
||||
{
|
||||
Name: "a",
|
||||
TypeName: "INT4",
|
||||
Length: struct {
|
||||
Len int64
|
||||
OK bool
|
||||
}{
|
||||
Len: 0,
|
||||
OK: false,
|
||||
},
|
||||
DecimalSize: struct {
|
||||
Precision int64
|
||||
Scale int64
|
||||
OK bool
|
||||
}{
|
||||
Precision: 0,
|
||||
Scale: 0,
|
||||
OK: false,
|
||||
},
|
||||
ScanType: reflect.TypeOf(int32(0)),
|
||||
}, {
|
||||
Name: "bar",
|
||||
TypeName: "TEXT",
|
||||
Length: struct {
|
||||
Len int64
|
||||
OK bool
|
||||
}{
|
||||
Len: math.MaxInt64,
|
||||
OK: true,
|
||||
},
|
||||
DecimalSize: struct {
|
||||
Precision int64
|
||||
Scale int64
|
||||
OK bool
|
||||
}{
|
||||
Precision: 0,
|
||||
Scale: 0,
|
||||
OK: false,
|
||||
},
|
||||
ScanType: reflect.TypeOf(""),
|
||||
},
|
||||
}
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT 1 AS a, text 'bar' AS bar, 1.28::numeric(9, 2) AS dec")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
columns, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(columns) != 3 {
|
||||
t.Errorf("expected 3 columns found %d", len(columns))
|
||||
}
|
||||
|
||||
for i, tt := range columnTypesTests {
|
||||
c := columns[i]
|
||||
if c.Name() != tt.Name {
|
||||
t.Errorf("(%d) got: %s, want: %s", i, c.Name(), tt.Name)
|
||||
}
|
||||
if c.DatabaseTypeName() != tt.TypeName {
|
||||
t.Errorf("(%d) got: %s, want: %s", i, c.DatabaseTypeName(), tt.TypeName)
|
||||
}
|
||||
l, ok := c.Length()
|
||||
if l != tt.Length.Len {
|
||||
t.Errorf("(%d) got: %d, want: %d", i, l, tt.Length.Len)
|
||||
}
|
||||
if ok != tt.Length.OK {
|
||||
t.Errorf("(%d) got: %t, want: %t", i, ok, tt.Length.OK)
|
||||
}
|
||||
p, s, ok := c.DecimalSize()
|
||||
if p != tt.DecimalSize.Precision {
|
||||
t.Errorf("(%d) got: %d, want: %d", i, p, tt.DecimalSize.Precision)
|
||||
}
|
||||
if s != tt.DecimalSize.Scale {
|
||||
t.Errorf("(%d) got: %d, want: %d", i, s, tt.DecimalSize.Scale)
|
||||
}
|
||||
if ok != tt.DecimalSize.OK {
|
||||
t.Errorf("(%d) got: %t, want: %t", i, ok, tt.DecimalSize.OK)
|
||||
}
|
||||
if c.ScanType() != tt.ScanType {
|
||||
t.Errorf("(%d) got: %v, want: %v", i, c.ScanType(), tt.ScanType)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
package pq
|
||||
|
||||
// This file contains SSL tests
|
||||
|
||||
import (
|
||||
_ "crypto/sha256"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func maybeSkipSSLTests(t *testing.T) {
|
||||
// Require some special variables for testing certificates
|
||||
if os.Getenv("PQSSLCERTTEST_PATH") == "" {
|
||||
t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests")
|
||||
}
|
||||
|
||||
value := os.Getenv("PQGOSSLTESTS")
|
||||
if value == "" || value == "0" {
|
||||
t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests")
|
||||
} else if value != "1" {
|
||||
t.Fatalf("unexpected value %q for PQGOSSLTESTS", value)
|
||||
}
|
||||
}
|
||||
|
||||
func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) {
|
||||
db, err := openTestConnConninfo(conninfo)
|
||||
if err != nil {
|
||||
// should never fail
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Do something with the connection to see whether it's working or not.
|
||||
tx, err := db.Begin()
|
||||
if err == nil {
|
||||
return db, tx.Rollback()
|
||||
}
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func checkSSLSetup(t *testing.T, conninfo string) {
|
||||
_, err := openSSLConn(t, conninfo)
|
||||
if pge, ok := err.(*Error); ok {
|
||||
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect over SSL and run a simple query to test the basics
|
||||
func TestSSLConnection(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
db, err := openSSLConn(t, "sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows, err := db.Query("SELECT 1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// Test sslmode=verify-full
|
||||
func TestSSLVerifyFull(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Not OK according to the system CA
|
||||
_, err := openSSLConn(t, "host=postgres sslmode=verify-full user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok := err.(x509.UnknownAuthorityError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
// No match on Common Name
|
||||
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok = err.(x509.HostnameError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.HostnameError, got %#+v", err)
|
||||
}
|
||||
// OK
|
||||
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test sslmode=require sslrootcert=rootCertPath
|
||||
func TestSSLRequireWithRootCert(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
bogusRootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "bogus_root.crt")
|
||||
bogusRootCert := "sslrootcert=" + bogusRootCertPath + " "
|
||||
|
||||
// Not OK according to the bogus CA
|
||||
_, err := openSSLConn(t, bogusRootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok := err.(x509.UnknownAuthorityError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err)
|
||||
}
|
||||
|
||||
nonExistentCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "non_existent.crt")
|
||||
nonExistentCert := "sslrootcert=" + nonExistentCertPath + " "
|
||||
|
||||
// No match on Common Name, but that's OK because we're not validating anything.
|
||||
_, err = openSSLConn(t, nonExistentCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
|
||||
// No match on Common Name, but that's OK because we're not validating the CN.
|
||||
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Everything OK
|
||||
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test sslmode=verify-ca
|
||||
func TestSSLVerifyCA(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Not OK according to the system CA
|
||||
{
|
||||
_, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest")
|
||||
if _, ok := err.(x509.UnknownAuthorityError); !ok {
|
||||
t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Still not OK according to the system CA; empty sslrootcert is treated as unspecified.
|
||||
{
|
||||
_, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest sslrootcert=''")
|
||||
if _, ok := err.(x509.UnknownAuthorityError); !ok {
|
||||
t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err)
|
||||
}
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
// No match on Common Name, but that's OK
|
||||
if _, err := openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Everything OK
|
||||
if _, err := openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate over SSL using client certificates
|
||||
func TestSSLClientCertificates(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
const baseinfo = "sslmode=require user=pqgosslcert"
|
||||
|
||||
// Certificate not specified, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo)
|
||||
if pge, ok := err.(*Error); ok {
|
||||
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty certificate specified, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo+" sslcert=''")
|
||||
if pge, ok := err.(*Error); ok {
|
||||
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Non-existent certificate specified, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo+" sslcert=/tmp/filedoesnotexist")
|
||||
if pge, ok := err.(*Error); ok {
|
||||
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||
t.Fatalf("unexpected error code '%s'", pge.Code.Name())
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected %T, got %v", (*Error)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
certpath, ok := os.LookupEnv("PQSSLCERTTEST_PATH")
|
||||
if !ok {
|
||||
t.Fatalf("PQSSLCERTTEST_PATH not present in environment")
|
||||
}
|
||||
|
||||
sslcert := filepath.Join(certpath, "postgresql.crt")
|
||||
|
||||
// Cert present, key not specified, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert)
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Cert present, empty key specified, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=''")
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Cert present, non-existent key, should fail
|
||||
{
|
||||
_, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=/tmp/filedoesnotexist")
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err)
|
||||
}
|
||||
}
|
||||
|
||||
// Key has wrong permissions (passing the cert as the key), should fail
|
||||
if _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslcert); err != ErrSSLKeyHasWorldPermissions {
|
||||
t.Fatalf("expected %s, got %#+v", ErrSSLKeyHasWorldPermissions, err)
|
||||
}
|
||||
|
||||
sslkey := filepath.Join(certpath, "postgresql.key")
|
||||
|
||||
// Should work
|
||||
if db, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslkey); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
rows, err := db.Query("SELECT 1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package pq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleParseURL(t *testing.T) {
|
||||
expected := "host=hostname.remote"
|
||||
str, err := ParseURL("postgres://hostname.remote")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv6LoopbackParseURL(t *testing.T) {
|
||||
expected := "host=::1 port=1234"
|
||||
str, err := ParseURL("postgres://[::1]:1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullParseURL(t *testing.T) {
|
||||
expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username`
|
||||
str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %s\n- %s", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidProtocolParseURL(t *testing.T) {
|
||||
_, err := ParseURL("http://hostname.remote")
|
||||
switch err {
|
||||
case nil:
|
||||
t.Fatal("Expected an error from parsing invalid protocol")
|
||||
default:
|
||||
msg := "invalid connection protocol: http"
|
||||
if err.Error() != msg {
|
||||
t.Fatalf("Unexpected error message:\n+ %s\n- %s",
|
||||
err.Error(), msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinimalURL(t *testing.T) {
|
||||
cs, err := ParseURL("postgres://")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if cs != "" {
|
||||
t.Fatalf("expected blank connection string, got: %q", cs)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package pq
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecodeUUIDBinaryError(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := decodeUUIDBinary([]byte{0x12, 0x34})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, got none")
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), "pq:") {
|
||||
t.Errorf("Expected error to start with %q, got %q", "pq:", err.Error())
|
||||
}
|
||||
if !strings.Contains(err.Error(), "bad length: 2") {
|
||||
t.Errorf("Expected error to contain length, got %q", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeUUIDBinary(b *testing.B) {
|
||||
x := []byte{0x03, 0xa3, 0x52, 0x2f, 0x89, 0x28, 0x49, 0x87, 0x84, 0xd6, 0x93, 0x7b, 0x36, 0xec, 0x27, 0x6f}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
decodeUUIDBinary(x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeUUIDBackend(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
var s = "a0ecc91d-a13f-4fe4-9fce-7e09777cc70a"
|
||||
var scanned interface{}
|
||||
|
||||
err := db.QueryRow(`SELECT $1::uuid`, s).Scan(&scanned)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(scanned, []byte(s)) {
|
||||
t.Errorf("Expected []byte(%q), got %T(%q)", s, scanned, scanned)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
MODULE VERSION
|
||||
github.com/serverwentdown/short -
|
||||
github.com/julienschmidt/httprouter v0.0.0-20150626000000-8c199fb6259f
|
||||
github.com/lib/pq v0.0.0-20180201184707-88edab080323
|
Loading…
Reference in New Issue