Update all dependencies

This commit is contained in:
Dan Sosedoff 2015-01-09 21:43:53 -06:00
parent e27ad3f9b1
commit d8cf69c8e9
38 changed files with 602 additions and 215 deletions

19
Godeps/Godeps.json generated
View File

@ -9,26 +9,27 @@
}, },
{ {
"ImportPath": "github.com/gin-gonic/gin", "ImportPath": "github.com/gin-gonic/gin",
"Comment": "v0.4-8-g3b079bb", "Comment": "v0.4-16-g28b9ff9",
"Rev": "3b079bb6f79ae501e73a5401c41b589f6616b61e" "Rev": "28b9ff9e3495dabeaea2da86c100effbf1a68346"
}, },
{ {
"ImportPath": "github.com/jessevdk/go-flags", "ImportPath": "github.com/jessevdk/go-flags",
"Comment": "v1-265-g1c87b97", "Comment": "v1-285-g1679536",
"Rev": "1c87b9727cd7c200be13a5d8280d7ea514469235" "Rev": "1679536dcc895411a9f5848d9a0250be7856448c"
}, },
{ {
"ImportPath": "github.com/jmoiron/sqlx", "ImportPath": "github.com/jmoiron/sqlx",
"Comment": "sqlx-v1.0-49-g79215a1", "Comment": "sqlx-v1.0-61-gb468c08",
"Rev": "79215a1536dd456d2a5afa112479b4f67fbe9f21" "Rev": "b468c08552f4efac78b94708eb040170a8184c47"
}, },
{ {
"ImportPath": "github.com/julienschmidt/httprouter", "ImportPath": "github.com/julienschmidt/httprouter",
"Rev": "46807412fe50aaceb73bb57061c2230fd26a1640" "Rev": "b55664b9e92004aebb7f19a19a9d06271f3a41fc"
}, },
{ {
"ImportPath": "github.com/lib/pq", "ImportPath": "github.com/lib/pq",
"Rev": "7175accbed18058468c07811f76440d6e8d7cf19" "Comment": "go1.0-cutoff-13-g19eeca3",
"Rev": "19eeca3e30d2577b1761db471ec130810e67f532"
}, },
{ {
"ImportPath": "github.com/mitchellh/go-homedir", "ImportPath": "github.com/mitchellh/go-homedir",
@ -36,7 +37,7 @@
}, },
{ {
"ImportPath": "github.com/stretchr/testify/assert", "ImportPath": "github.com/stretchr/testify/assert",
"Rev": "faedd6eb633a4cd77c15f6c9856608701e40d9a2" "Rev": "2eaa4b48b8954bf871229043b8064ca156a542a5"
} }
] ]
} }

View File

@ -287,6 +287,22 @@ func main() {
``` ```
####Serving static files
Use Engine.ServeFiles(path string, root http.FileSystem):
```go
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
// Listen and server on 0.0.0.0:8080
r.Run(":8080")
}
```
Note: this will use `httpNotFound` instead of the Router's `NotFound` handler.
####HTML rendering ####HTML rendering
Using LoadHTMLTemplates() Using LoadHTMLTemplates()

View File

@ -155,7 +155,7 @@ func ensureNotPointer(obj interface{}) {
} }
} }
func Validate(obj interface{}) error { func Validate(obj interface{}, parents ...string) error {
typ := reflect.TypeOf(obj) typ := reflect.TypeOf(obj)
val := reflect.ValueOf(obj) val := reflect.ValueOf(obj)
@ -180,12 +180,19 @@ func Validate(obj interface{}) error {
if strings.Index(field.Tag.Get("binding"), "required") > -1 { if strings.Index(field.Tag.Get("binding"), "required") > -1 {
fieldType := field.Type.Kind() fieldType := field.Type.Kind()
if fieldType == reflect.Struct { if fieldType == reflect.Struct {
err := Validate(fieldValue) if reflect.DeepEqual(zero, fieldValue) {
return errors.New("Required " + field.Name)
}
err := Validate(fieldValue, field.Name)
if err != nil { if err != nil {
return err return err
} }
} else if reflect.DeepEqual(zero, fieldValue) { } else if reflect.DeepEqual(zero, fieldValue) {
return errors.New("Required " + field.Name) if len(parents) > 0 {
return errors.New("Required " + field.Name + " on " + parents[0])
} else {
return errors.New("Required " + field.Name)
}
} else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct { } else if fieldType == reflect.Slice && field.Type.Elem().Kind() == reflect.Struct {
err := Validate(fieldValue) err := Validate(fieldValue)
if err != nil { if err != nil {

View File

@ -186,7 +186,7 @@ func (c *Context) MustGet(key string) interface{} {
} }
/************************************/ /************************************/
/******** ENCOGING MANAGEMENT********/ /******** ENCODING MANAGEMENT********/
/************************************/ /************************************/
// This function checks the Content-Type to select a binding engine automatically, // This function checks the Content-Type to select a binding engine automatically,

View File

@ -234,7 +234,7 @@ func (c *completion) complete(args []string) []Completion {
if opt != nil { if opt != nil {
// Completion for the argument of 'opt' // Completion for the argument of 'opt'
ret = c.completeValue(opt.value, "", lastarg) ret = c.completeValue(opt.value, "", lastarg)
} else if argumentIsOption(lastarg) { } else if argumentStartsOption(lastarg) {
// Complete the option // Complete the option
prefix, optname, islong := stripOptionPrefix(lastarg) prefix, optname, islong := stripOptionPrefix(lastarg)
optname, split, argument := splitOption(prefix, optname, islong) optname, split, argument := splitOption(prefix, optname, islong)

View File

@ -12,10 +12,22 @@ const (
defaultNameArgDelimiter = '=' defaultNameArgDelimiter = '='
) )
func argumentIsOption(arg string) bool { func argumentStartsOption(arg string) bool {
return len(arg) > 0 && arg[0] == '-' return len(arg) > 0 && arg[0] == '-'
} }
func argumentIsOption(arg string) bool {
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
return true
}
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
return true
}
return false
}
// stripOptionPrefix returns the option without the prefix and whether or // stripOptionPrefix returns the option without the prefix and whether or
// not the option is a long option or not. // not the option is a long option or not.
func stripOptionPrefix(optname string) (prefix string, name string, islong bool) { func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {

View File

@ -12,10 +12,26 @@ const (
defaultNameArgDelimiter = ':' defaultNameArgDelimiter = ':'
) )
func argumentStartsOption(arg string) bool {
return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/')
}
func argumentIsOption(arg string) bool { func argumentIsOption(arg string) bool {
// Windows-style options allow front slash for the option // Windows-style options allow front slash for the option
// delimiter. // delimiter.
return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/') if len(arg) > 1 && arg[0] == '/' {
return true
}
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
return true
}
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
return true
}
return false
} }
// stripOptionPrefix returns the option without the prefix and whether or // stripOptionPrefix returns the option without the prefix and whether or

View File

@ -24,9 +24,37 @@ type Parser struct {
// NamespaceDelimiter separates group namespaces and option long names // NamespaceDelimiter separates group namespaces and option long names
NamespaceDelimiter string NamespaceDelimiter string
// UnknownOptionsHandler is a function which gets called when the parser
// encounters an unknown option. The function receives the unknown option
// name, a SplitArgument which specifies its value if set with an argument
// separator, and the remaining command line arguments.
// It should return a new list of remaining arguments to continue parsing,
// or an error to indicate a parse failure.
UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error)
internalError error internalError error
} }
// SplitArgument represents the argument value of an option that was passed using
// an argument separator.
type SplitArgument interface {
// String returns the option's value as a string, and a boolean indicating
// if the option was present.
Value() (string, bool)
}
type strArgument struct {
value *string
}
func (s strArgument) Value() (string, bool) {
if s.value == nil {
return "", false
}
return *s.value, true
}
// Options provides parser options that change the behavior of the option // Options provides parser options that change the behavior of the option
// parser. // parser.
type Options uint type Options uint
@ -204,13 +232,22 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) {
ignoreUnknown := (p.Options & IgnoreUnknown) != None ignoreUnknown := (p.Options & IgnoreUnknown) != None
parseErr := wrapError(err) parseErr := wrapError(err)
if !(parseErr.Type == ErrUnknownFlag && ignoreUnknown) { if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) {
s.err = parseErr s.err = parseErr
break break
} }
if ignoreUnknown { if ignoreUnknown {
s.addArgs(arg) s.addArgs(arg)
} else if p.UnknownOptionHandler != nil {
modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args)
if err != nil {
s.err = err
break
}
s.args = modifiedArgs
} }
} }
} }

View File

@ -170,8 +170,11 @@ func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg
arg = *argument arg = *argument
} else { } else {
arg = s.pop() arg = s.pop()
if argumentIsOption(arg) { if argumentIsOption(arg) {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg) return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
} else if p.Options&PassDoubleDash != 0 && arg == "--" {
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
} }
} }

View File

@ -1,6 +1,7 @@
package flags package flags
import ( import (
"fmt"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
@ -312,6 +313,7 @@ func TestOptionAsArgument(t *testing.T) {
expectError bool expectError bool
errType ErrorType errType ErrorType
errMsg string errMsg string
rest []string
}{ }{
{ {
// short option must not be accepted as argument // short option must not be accepted as argument
@ -327,10 +329,25 @@ func TestOptionAsArgument(t *testing.T) {
errType: ErrExpectedArgument, errType: ErrExpectedArgument,
errMsg: "expected argument for flag `--string-slice', but got option `--other-option'", errMsg: "expected argument for flag `--string-slice', but got option `--other-option'",
}, },
{
// long option must not be accepted as argument
args: []string{"--string-slice", "--"},
expectError: true,
errType: ErrExpectedArgument,
errMsg: "expected argument for flag `--string-slice', but got double dash `--'",
},
{ {
// quoted and appended option should be accepted as argument (even if it looks like an option) // quoted and appended option should be accepted as argument (even if it looks like an option)
args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""}, args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""},
}, },
{
// Accept any single character arguments including '-'
args: []string{"--string-slice", "-"},
},
{
args: []string{"-o", "-", "-"},
rest: []string{"-", "-"},
},
} }
var opts struct { var opts struct {
StringSlice []string `long:"string-slice"` StringSlice []string `long:"string-slice"`
@ -341,7 +358,74 @@ func TestOptionAsArgument(t *testing.T) {
if test.expectError { if test.expectError {
assertParseFail(t, test.errType, test.errMsg, &opts, test.args...) assertParseFail(t, test.errType, test.errMsg, &opts, test.args...)
} else { } else {
assertParseSuccess(t, &opts, test.args...) args := assertParseSuccess(t, &opts, test.args...)
assertStringArray(t, args, test.rest)
} }
} }
} }
func TestUnknownFlagHandler(t *testing.T) {
var opts struct {
Flag1 string `long:"flag1"`
Flag2 string `long:"flag2"`
}
p := NewParser(&opts, None)
var unknownFlag1 string
var unknownFlag2 bool
var unknownFlag3 string
// Set up a callback to intercept unknown options during parsing
p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) {
if option == "unknownFlag1" {
if argValue, ok := arg.Value(); ok {
unknownFlag1 = argValue
return args, nil
}
// consume a value from remaining args list
unknownFlag1 = args[0]
return args[1:], nil
} else if option == "unknownFlag2" {
// treat this one as a bool switch, don't consume any args
unknownFlag2 = true
return args, nil
} else if option == "unknownFlag3" {
if argValue, ok := arg.Value(); ok {
unknownFlag3 = argValue
return args, nil
}
// consume a value from remaining args list
unknownFlag3 = args[0]
return args[1:], nil
}
return args, fmt.Errorf("Unknown flag: %v", option)
}
// Parse args containing some unknown flags, verify that
// our callback can handle all of them
_, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"})
if err != nil {
assertErrorf(t, "Parser returned unexpected error %v", err)
}
assertString(t, opts.Flag1, "stuff")
assertString(t, opts.Flag2, "foo")
assertString(t, unknownFlag1, "blah")
assertString(t, unknownFlag3, "baz")
if !unknownFlag2 {
assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
}
// Parse args with unknown flags that callback doesn't handle, verify it returns error
_, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
if err == nil {
assertErrorf(t, "Parser should have returned error, but returned nil")
}
}

View File

@ -141,12 +141,12 @@ func main() {
// if you have null fields and use SELECT *, you must use sql.Null* in your struct // if you have null fields and use SELECT *, you must use sql.Null* in your struct
places := []Place{} places := []Place{}
err := db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC") err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil { if err != nil {
fmt.Printf(err) fmt.Println(err)
return return
} }
usa, singsing, honkers = places[0], places[1], places[2] usa, singsing, honkers := places[0], places[1], places[2]
fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers) fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1} // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
@ -159,7 +159,7 @@ func main() {
for rows.Next() { for rows.Next() {
err := rows.StructScan(&place) err := rows.StructScan(&place)
if err != nil { if err != nil {
log.Fataln(err) log.Fatalln(err)
} }
fmt.Printf("%#v\n", place) fmt.Printf("%#v\n", place)
} }
@ -177,12 +177,12 @@ func main() {
}) })
// Selects Mr. Smith from the database // Selects Mr. Smith from the database
rows, err := db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"}) rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
// Named queries can also use structs. Their bind names follow the same rules // Named queries can also use structs. Their bind names follow the same rules
// as the name -> db mapping, so struct fields are lowercased and the `db` tag // as the name -> db mapping, so struct fields are lowercased and the `db` tag
// is taken into consideration. // is taken into consideration.
rows, err := db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason) rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
} }
``` ```

View File

@ -16,11 +16,11 @@ const (
// BindType returns the bindtype for a given database given a drivername. // BindType returns the bindtype for a given database given a drivername.
func BindType(driverName string) int { func BindType(driverName string) int {
switch driverName { switch driverName {
case "postgres": case "postgres", "pgx":
return DOLLAR return DOLLAR
case "mysql": case "mysql":
return QUESTION return QUESTION
case "sqlite": case "sqlite3":
return QUESTION return QUESTION
case "oci8": case "oci8":
return NAMED return NAMED

View File

@ -151,7 +151,6 @@ func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{
fields := m.TraversalsByName(v.Type(), names) fields := m.TraversalsByName(v.Type(), names)
for i, t := range fields { for i, t := range fields {
if len(t) == 0 { if len(t) == 0 {
fmt.Println(fields, names)
return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg) return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
} }
val := reflectx.FieldByIndexesReadOnly(v, t) val := reflectx.FieldByIndexesReadOnly(v, t)

View File

@ -137,6 +137,9 @@ func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
alloc := reflect.New(Deref(v.Type())) alloc := reflect.New(Deref(v.Type()))
v.Set(alloc) v.Set(alloc)
} }
if v.Kind() == reflect.Map && v.IsNil() {
v.Set(reflect.MakeMap(v.Type()))
}
} }
return v return v
} }

View File

@ -12,9 +12,12 @@ package sqlx
import ( import (
"database/sql" "database/sql"
"database/sql/driver"
"encoding/json"
"fmt" "fmt"
"log" "log"
"os" "os"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -1124,6 +1127,75 @@ func TestBindMap(t *testing.T) {
} }
} }
// Test for #117, embedded nil maps
type Message struct {
Text string `db:"string"`
Properties PropertyMap // Stored as JSON in the database
}
type PropertyMap map[string]string
// Implement driver.Valuer and sql.Scanner interfaces on PropertyMap
func (p PropertyMap) Value() (driver.Value, error) {
if len(p) == 0 {
return nil, nil
}
return json.Marshal(p)
}
func (p PropertyMap) Scan(src interface{}) error {
v := reflect.ValueOf(src)
if !v.IsValid() || v.IsNil() {
return nil
}
if data, ok := src.([]byte); ok {
return json.Unmarshal(data, &p)
}
return fmt.Errorf("Could not not decode type %T -> %T", src, p)
}
func TestEmbeddedMaps(t *testing.T) {
var schema = Schema{
create: `
CREATE TABLE message (
string text,
properties text
);`,
drop: `drop table message;`,
}
RunWithSchema(schema, t, func(db *DB, t *testing.T) {
messages := []Message{
{"Hello, World", PropertyMap{"one": "1", "two": "2"}},
{"Thanks, Joy", PropertyMap{"pull": "request"}},
}
q1 := `INSERT INTO message (string, properties) VALUES (:string, :properties)`
for _, m := range messages {
_, err := db.NamedExec(q1, m)
if err != nil {
t.Error(err)
}
}
var count int
err := db.Get(&count, "SELECT count(*) FROM message")
if err != nil {
t.Error(err)
}
if count != len(messages) {
t.Errorf("Expected %d messages in DB, found %d", len(messages), count)
}
var m Message
err = db.Get(&m, "SELECT * FROM message LIMIT 1")
if err != nil {
t.Error(err)
}
if m.Properties == nil {
t.Error("Expected m.Properties to not be nil, but it was.")
}
})
}
func TestBindStruct(t *testing.T) { func TestBindStruct(t *testing.T) {
var err error var err error

View File

@ -3,4 +3,5 @@ go:
- 1.1 - 1.1
- 1.2 - 1.2
- 1.3 - 1.3
- 1.4
- tip - tip

View File

@ -189,8 +189,9 @@ for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers).
Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/), Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/),
it's very easy! it's very easy!
Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks-building-upon-httprouter). Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks--co-based-on-httprouter).
### Multi-domain / Sub-domains
Here is a quick example: Does your server serve multiple domains / hosts? Here is a quick example: Does your server serve multiple domains / hosts?
You want to use sub-domains? You want to use sub-domains?
Define a router per host! Define a router per host!
@ -228,7 +229,69 @@ func main() {
} }
``` ```
## Web Frameworks building upon HttpRouter ### Basic Authentication
Another quick example: Basic Authentification (RFC 2617) for handles:
```go
package main
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
"strings"
)
func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
const basicAuthPrefix string = "Basic "
// Get the Basic Authentication credentials
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, basicAuthPrefix) {
// Check credentials
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
if err == nil {
pair := bytes.SplitN(payload, []byte(":"), 2)
if len(pair) == 2 && bytes.Equal(pair[0], user) && bytes.Equal(pair[1], pass) {
// Delegate request to the given handle
h(w, r, ps)
return
}
}
}
// Request Basic Authentication otherwise
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Not protected!\n")
}
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Protected!\n")
}
func main() {
user := []byte("gordon")
pass := []byte("secret!")
router := httprouter.New()
router.GET("/", Index)
router.GET("/protected/", BasicAuth(Protected, user, pass))
log.Fatal(http.ListenAndServe(":8080", router))
}
```
## Web Frameworks & Co based on HttpRouter
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package: If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance * [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine * [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow

View File

@ -159,6 +159,11 @@ func (r *Router) GET(path string, handle Handle) {
r.Handle("GET", path, handle) r.Handle("GET", path, handle)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func (r *Router) HEAD(path string, handle Handle) {
r.Handle("HEAD", path, handle)
}
// POST is a shortcut for router.Handle("POST", path, handle) // POST is a shortcut for router.Handle("POST", path, handle)
func (r *Router) POST(path string, handle Handle) { func (r *Router) POST(path string, handle Handle) {
r.Handle("POST", path, handle) r.Handle("POST", path, handle)
@ -284,7 +289,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
if tsr && r.RedirectTrailingSlash { if tsr && r.RedirectTrailingSlash {
if path[len(path)-1] == '/' { if len(path) > 1 && path[len(path)-1] == '/' {
req.URL.Path = path[:len(path)-1] req.URL.Path = path[:len(path)-1]
} else { } else {
req.URL.Path = path + "/" req.URL.Path = path + "/"

View File

@ -76,7 +76,7 @@ func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
func TestRouterAPI(t *testing.T) { func TestRouterAPI(t *testing.T) {
var get, post, put, patch, delete, handler, handlerFunc bool var get, head, post, put, patch, delete, handler, handlerFunc bool
httpHandler := handlerStruct{&handler} httpHandler := handlerStruct{&handler}
@ -84,6 +84,9 @@ func TestRouterAPI(t *testing.T) {
router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) { router.GET("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
get = true get = true
}) })
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
head = true
})
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) { router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
post = true post = true
}) })
@ -109,6 +112,12 @@ func TestRouterAPI(t *testing.T) {
t.Error("routing GET failed") 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("POST", "/POST", nil) r, _ = http.NewRequest("POST", "/POST", nil)
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
if !post { if !post {
@ -162,6 +171,7 @@ func TestRouterNotFound(t *testing.T) {
router := New() router := New()
router.GET("/path", handlerFunc) router.GET("/path", handlerFunc)
router.GET("/dir/", handlerFunc) router.GET("/dir/", handlerFunc)
router.GET("/", handlerFunc)
testRoutes := []struct { testRoutes := []struct {
route string route string
@ -170,6 +180,7 @@ func TestRouterNotFound(t *testing.T) {
}{ }{
{"/path/", 301, "map[Location:[/path]]"}, // TSR -/ {"/path/", 301, "map[Location:[/path]]"}, // TSR -/
{"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/
{"", 301, "map[Location:[/]]"}, // TSR +/
{"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case
{"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case
{"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/

View File

@ -1,11 +1,10 @@
language: go language: go
go: go:
- 1.0.2
- 1.0.3
- 1.1 - 1.1
- 1.2 - 1.2
- 1.3 - 1.3
- 1.4
- tip - tip
before_install: before_install:
@ -46,6 +45,7 @@ env:
- PQGOSSLTESTS=1 - PQGOSSLTESTS=1
- PQSSLCERTTEST_PATH=$PWD/certs - PQSSLCERTTEST_PATH=$PWD/certs
matrix: matrix:
- PGVERSION=9.4
- PGVERSION=9.3 - PGVERSION=9.3
- PGVERSION=9.2 - PGVERSION=9.2
- PGVERSION=9.1 - PGVERSION=9.1

View File

@ -58,6 +58,7 @@ code still exists in here.
* Charlie Melbye (cmelbye) * Charlie Melbye (cmelbye)
* Chris Bandy (cbandy) * Chris Bandy (cbandy)
* Chris Walsh (cwds) * Chris Walsh (cwds)
* Dan Sosedoff (sosedoff)
* Daniel Farina (fdr) * Daniel Farina (fdr)
* Eric Chlebek (echlebek) * Eric Chlebek (echlebek)
* Everyone at The Go Team * Everyone at The Go Team
@ -72,6 +73,7 @@ code still exists in here.
* Jeremy Jay (pbnjay) * Jeremy Jay (pbnjay)
* Joakim Sernbrant (serbaut) * Joakim Sernbrant (serbaut)
* John Gallagher (jgallagher) * John Gallagher (jgallagher)
* Jonathan Rudenberg (titanous)
* Joël Stemmer (jstemmer) * Joël Stemmer (jstemmer)
* Kamil Kisiel (kisielk) * Kamil Kisiel (kisielk)
* Kelly Dunn (kellydunn) * Kelly Dunn (kellydunn)
@ -92,4 +94,5 @@ code still exists in here.
* Ryan Smith (ryandotsmith) * Ryan Smith (ryandotsmith)
* Samuel Stauffer (samuel) * Samuel Stauffer (samuel)
* Timothée Peignier (cyberdelia) * Timothée Peignier (cyberdelia)
* TruongSinh Tran-Nguyen (truongsinh)
* notedit (notedit) * notedit (notedit)

View File

@ -35,7 +35,6 @@ func BenchmarkSelectSeries(b *testing.B) {
} }
func benchQuery(b *testing.B, query string, result interface{}) { func benchQuery(b *testing.B, query string, result interface{}) {
b.Skip("current pq database-backed benchmarks are inconsistent")
b.StopTimer() b.StopTimer()
db := openTestConn(b) db := openTestConn(b)
defer db.Close() defer db.Close()
@ -183,7 +182,6 @@ func BenchmarkPreparedSelectSeries(b *testing.B) {
} }
func benchPreparedQuery(b *testing.B, query string, result interface{}) { func benchPreparedQuery(b *testing.B, query string, result interface{}) {
b.Skip("current pq database-backed benchmarks are inconsistent")
b.StopTimer() b.StopTimer()
db := openTestConn(b) db := openTestConn(b)
defer db.Close() defer db.Close()

View File

@ -30,6 +30,7 @@ var (
ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction") ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction")
ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server") ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server")
ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less.") ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less.")
ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly.")
) )
type drv struct{} type drv struct{}
@ -71,6 +72,7 @@ func (s transactionStatus) String() string {
default: default:
errorf("unknown transactionStatus %d", s) errorf("unknown transactionStatus %d", s)
} }
panic("not reached") panic("not reached")
} }
@ -490,7 +492,6 @@ func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err
errorf("unknown response for simple query: %q", t) errorf("unknown response for simple query: %q", t)
} }
} }
panic("not reached")
} }
func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) { func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
@ -543,7 +544,6 @@ func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
errorf("unknown response for simple query: %q", t) errorf("unknown response for simple query: %q", t)
} }
} }
panic("not reached")
} }
func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err error) { func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err error) {
@ -587,8 +587,6 @@ func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err error) {
errorf("unexpected describe rows response: %q", t) errorf("unexpected describe rows response: %q", t)
} }
} }
panic("not reached")
} }
func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) { func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) {
@ -766,8 +764,6 @@ func (cn *conn) recv() (t byte, r *readBuf) {
return return
} }
} }
panic("not reached")
} }
// recv1Buf is exactly equivalent to recv1, except it uses a buffer supplied by // recv1Buf is exactly equivalent to recv1, except it uses a buffer supplied by
@ -788,8 +784,6 @@ func (cn *conn) recv1Buf(r *readBuf) byte {
return t return t
} }
} }
panic("not reached")
} }
// recv1 receives a message from the backend, panicking if an error occurs // recv1 receives a message from the backend, panicking if an error occurs
@ -854,9 +848,9 @@ func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) {
} }
certs := client.ConnectionState().PeerCertificates certs := client.ConnectionState().PeerCertificates
opts := x509.VerifyOptions{ opts := x509.VerifyOptions{
DNSName: client.ConnectionState().ServerName, DNSName: client.ConnectionState().ServerName,
Intermediates: x509.NewCertPool(), Intermediates: x509.NewCertPool(),
Roots: tlsConf.RootCAs, Roots: tlsConf.RootCAs,
} }
for i, cert := range certs { for i, cert := range certs {
if i == 0 { if i == 0 {
@ -870,7 +864,6 @@ func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) {
} }
} }
// This function sets up SSL client certificates based on either the "sslkey" // This function sets up SSL client certificates based on either the "sslkey"
// and "sslcert" settings (possibly set via the environment variables PGSSLKEY // and "sslcert" settings (possibly set via the environment variables PGSSLKEY
// and PGSSLCERT, respectively), or if they aren't set, from the .postgresql // and PGSSLCERT, respectively), or if they aren't set, from the .postgresql
@ -884,7 +877,7 @@ func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) {
sslkey := o.Get("sslkey") sslkey := o.Get("sslkey")
sslcert := o.Get("sslcert") sslcert := o.Get("sslcert")
if sslkey != "" && sslcert != "" { if sslkey != "" && sslcert != "" {
// If the user has set a sslkey and sslcert, they *must* exist. // If the user has set an sslkey and sslcert, they *must* exist.
missingOk = false missingOk = false
} else { } else {
// Automatically load certificates from ~/.postgresql. // Automatically load certificates from ~/.postgresql.
@ -901,7 +894,7 @@ func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) {
missingOk = true missingOk = true
} }
// Check that both files exist, and report the error or stop depending on // Check that both files exist, and report the error or stop, depending on
// which behaviour we want. Note that we don't do any more extensive // which behaviour we want. Note that we don't do any more extensive
// checks than this (such as checking that the paths aren't directories); // checks than this (such as checking that the paths aren't directories);
// LoadX509KeyPair() will take care of the rest. // LoadX509KeyPair() will take care of the rest.
@ -948,7 +941,7 @@ func (cn *conn) setupSSLCA(tlsConf *tls.Config, o values) {
} }
} }
// isDriverSetting returns true iff a setting is purely for the configuring the // isDriverSetting returns true iff a setting is purely for configuring the
// driver's options and should not be sent to the server in the connection // driver's options and should not be sent to the server in the connection
// startup packet. // startup packet.
func isDriverSetting(key string) bool { func isDriverSetting(key string) bool {
@ -967,7 +960,6 @@ func isDriverSetting(key string) bool {
default: default:
return false return false
} }
panic("not reached")
} }
func (cn *conn) startup(o values) { func (cn *conn) startup(o values) {
@ -1124,8 +1116,6 @@ func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) {
errorf("unknown exec response: %q", t) errorf("unknown exec response: %q", t)
} }
} }
panic("not reached")
} }
func (st *stmt) exec(v []driver.Value) { func (st *stmt) exec(v []driver.Value) {
@ -1287,7 +1277,6 @@ func (rs *rows) Close() error {
return err return err
} }
} }
panic("not reached")
} }
func (rs *rows) Columns() []string { func (rs *rows) Columns() []string {
@ -1337,8 +1326,6 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
errorf("unexpected message after execute: %q", t) errorf("unexpected message after execute: %q", t)
} }
} }
panic("not reached")
} }
// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be // QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be

View File

@ -7,8 +7,6 @@ import (
"io" "io"
"os" "os"
"reflect" "reflect"
"runtime"
"strings"
"testing" "testing"
"time" "time"
) )
@ -56,12 +54,6 @@ func getServerVersion(t *testing.T, db *sql.DB) int {
} }
func TestReconnect(t *testing.T) { func TestReconnect(t *testing.T) {
if runtime.Version() == "go1.0.2" {
fmt.Println("Skipping failing test; " +
"fixed in database/sql on go1.0.3+")
return
}
db1 := openTestConn(t) db1 := openTestConn(t)
defer db1.Close() defer db1.Close()
tx, err := db1.Begin() tx, err := db1.Begin()
@ -838,11 +830,6 @@ func TestIssue196(t *testing.T) {
// Test that any CommandComplete messages sent before the query results are // Test that any CommandComplete messages sent before the query results are
// ignored. // ignored.
func TestIssue282(t *testing.T) { func TestIssue282(t *testing.T) {
if strings.HasPrefix(runtime.Version(), "go1.0") {
fmt.Println("Skipping test due to lack of Queryer implementation")
return
}
db := openTestConn(t) db := openTestConn(t)
defer db.Close() defer db.Close()
@ -878,6 +865,60 @@ func TestReadFloatPrecision(t *testing.T) {
} }
} }
func TestXactMultiStmt(t *testing.T) {
// minified test case based on bug reports from
// pico303@gmail.com and rangelspam@gmail.com
t.Skip("Skipping failing test")
db := openTestConn(t)
defer db.Close()
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Commit()
rows, err := tx.Query("select 1")
if err != nil {
t.Fatal(err)
}
if rows.Next() {
var val int32
if err = rows.Scan(&val); err != nil {
t.Fatal(err)
}
} else {
t.Fatal("Expected at least one row in first query in xact")
}
rows2, err := tx.Query("select 2")
if err != nil {
t.Fatal(err)
}
if rows2.Next() {
var val2 int32
if err := rows2.Scan(&val2); err != nil {
t.Fatal(err)
}
} else {
t.Fatal("Expected at least one row in second query in xact")
}
if err = rows.Err(); err != nil {
t.Fatal(err)
}
if err = rows2.Err(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
}
var envParseTests = []struct { var envParseTests = []struct {
Expected map[string]string Expected map[string]string
Env []string Env []string
@ -1168,14 +1209,15 @@ func TestRuntimeParameters(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer db.Close()
// application_name didn't exist before 9.0 // application_name didn't exist before 9.0
if test.param == "application_name" && getServerVersion(t, db) < 90000 { if test.param == "application_name" && getServerVersion(t, db) < 90000 {
db.Close()
continue continue
} }
tryGetParameterValue := func() (value string, outcome RuntimeTestResult) { tryGetParameterValue := func() (value string, outcome RuntimeTestResult) {
defer db.Close()
row := db.QueryRow("SELECT current_setting($1)", test.param) row := db.QueryRow("SELECT current_setting($1)", test.param)
err = row.Scan(&value) err = row.Scan(&value)
if err != nil { if err != nil {

View File

@ -1,61 +0,0 @@
// +build go1.1
package pq
import (
"testing"
)
func TestXactMultiStmt(t *testing.T) {
// minified test case based on bug reports from
// pico303@gmail.com and rangelspam@gmail.com
t.Skip("Skipping failing test")
db := openTestConn(t)
defer db.Close()
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Commit()
rows, err := tx.Query("select 1")
if err != nil {
t.Fatal(err)
}
if rows.Next() {
var val int32
if err = rows.Scan(&val); err != nil {
t.Fatal(err)
}
} else {
t.Fatal("Expected at least one row in first query in xact")
}
rows2, err := tx.Query("select 2")
if err != nil {
t.Fatal(err)
}
if rows2.Next() {
var val2 int32
if err := rows2.Scan(&val2); err != nil {
t.Fatal(err)
}
} else {
t.Fatal("Expected at least one row in second query in xact")
}
if err = rows.Err(); err != nil {
t.Fatal(err)
}
if err = rows2.Err(); err != nil {
t.Fatal(err)
}
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
}

View File

@ -4,7 +4,8 @@ import (
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"errors" "errors"
"sync/atomic" "fmt"
"sync"
) )
var ( var (
@ -48,9 +49,10 @@ type copyin struct {
rowData chan []byte rowData chan []byte
done chan bool done chan bool
closed bool closed bool
err error
errorset int32 sync.Mutex // guards err
err error
} }
const ciBufferSize = 64 * 1024 const ciBufferSize = 64 * 1024
@ -67,7 +69,7 @@ func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
cn: cn, cn: cn,
buffer: make([]byte, 0, ciBufferSize), buffer: make([]byte, 0, ciBufferSize),
rowData: make(chan []byte), rowData: make(chan []byte),
done: make(chan bool), done: make(chan bool, 1),
} }
// add CopyData identifier + 4 bytes for message length // add CopyData identifier + 4 bytes for message length
ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0) ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
@ -123,8 +125,6 @@ awaitCopyInResponse:
errorf("unknown response for CopyFail: %q", t) errorf("unknown response for CopyFail: %q", t)
} }
} }
panic("not reached")
} }
func (ci *copyin) flush(buf []byte) { func (ci *copyin) flush(buf []byte) {
@ -139,31 +139,48 @@ func (ci *copyin) flush(buf []byte) {
func (ci *copyin) resploop() { func (ci *copyin) resploop() {
for { for {
t, r := ci.cn.recv1() var r readBuf
t, err := ci.cn.recvMessage(&r)
if err != nil {
ci.cn.bad = true
ci.setError(err)
ci.done <- true
return
}
switch t { switch t {
case 'C': case 'C':
// complete // complete
case 'Z': case 'Z':
ci.cn.processReadyForQuery(r) ci.cn.processReadyForQuery(&r)
ci.done <- true ci.done <- true
return return
case 'E': case 'E':
err := parseError(r) err := parseError(&r)
ci.setError(err) ci.setError(err)
default: default:
ci.cn.bad = true ci.cn.bad = true
errorf("unknown response: %q", t) ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
ci.done <- true
return
} }
} }
} }
func (ci *copyin) isErrorSet() bool { func (ci *copyin) isErrorSet() bool {
return atomic.LoadInt32(&ci.errorset) != 0 ci.Lock()
isSet := (ci.err != nil)
ci.Unlock()
return isSet
} }
// setError() sets ci.err if one has not been set already. Caller must not be
// holding ci.Mutex.
func (ci *copyin) setError(err error) { func (ci *copyin) setError(err error) {
ci.err = err ci.Lock()
atomic.StoreInt32(&ci.errorset, 1) if ci.err == nil {
ci.err = err
}
ci.Unlock()
} }
func (ci *copyin) NumInput() int { func (ci *copyin) NumInput() int {

View File

@ -275,6 +275,62 @@ func TestCopySyntaxError(t *testing.T) {
} }
} }
// 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)
}
_, err = db.Exec("SELECT pg_terminate_backend($1)", pid)
if err != nil {
t.Fatal(err)
}
// We have to try and send something over, since postgres 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 {
t.Fatalf("expected *pq.Error, got %+#v", err)
} else if pge.Code.Name() != "admin_shutdown" {
t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name())
}
err = stmt.Close()
if err != nil {
t.Fatal(err)
}
}
func BenchmarkCopyIn(b *testing.B) { func BenchmarkCopyIn(b *testing.B) {
db := openTestConn(b) db := openTestConn(b)
defer db.Close() defer db.Close()

View File

@ -218,6 +218,8 @@ func (c *locationCache) getLocation(offset int) *time.Location {
// time.Parse and the Postgres date formatting quirks. // time.Parse and the Postgres date formatting quirks.
func parseTs(currentLocation *time.Location, str string) (result time.Time) { func parseTs(currentLocation *time.Location, str string) (result time.Time) {
monSep := strings.IndexRune(str, '-') monSep := strings.IndexRune(str, '-')
// this is Gregorian year, not ISO Year
// In Gregorian system, the year 1 BC is followed by AD 1
year := mustAtoi(str[:monSep]) year := mustAtoi(str[:monSep])
daySep := monSep + 3 daySep := monSep + 3
month := mustAtoi(str[monSep+1 : daySep]) month := mustAtoi(str[monSep+1 : daySep])
@ -245,7 +247,6 @@ func parseTs(currentLocation *time.Location, str string) (result time.Time) {
nanoSec := 0 nanoSec := 0
tzOff := 0 tzOff := 0
bcSign := 1
if remainderIdx < len(str) && str[remainderIdx:remainderIdx+1] == "." { if remainderIdx < len(str) && str[remainderIdx:remainderIdx+1] == "." {
fracStart := remainderIdx + 1 fracStart := remainderIdx + 1
@ -281,14 +282,17 @@ func parseTs(currentLocation *time.Location, str string) (result time.Time) {
} }
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
} }
var isoYear int
if remainderIdx < len(str) && str[remainderIdx:remainderIdx+3] == " BC" { if remainderIdx < len(str) && str[remainderIdx:remainderIdx+3] == " BC" {
bcSign = -1 isoYear = 1 - year
remainderIdx += 3 remainderIdx += 3
} else {
isoYear = year
} }
if remainderIdx < len(str) { if remainderIdx < len(str) {
errorf("expected end of input, got %v", str[remainderIdx:]) errorf("expected end of input, got %v", str[remainderIdx:])
} }
t := time.Date(bcSign*year, time.Month(month), day, t := time.Date(isoYear, time.Month(month), day,
hour, minute, second, nanoSec, hour, minute, second, nanoSec,
globalLocationCache.getLocation(tzOff)) globalLocationCache.getLocation(tzOff))
@ -312,8 +316,16 @@ func formatTs(t time.Time) (b []byte) {
b = []byte(t.Format(time.RFC3339Nano)) b = []byte(t.Format(time.RFC3339Nano))
// Need to send dates before 0001 A.D. with " BC" suffix, instead of the // Need to send dates before 0001 A.D. with " BC" suffix, instead of the
// minus sign preferred by Go. // minus sign preferred by Go.
if b[0] == '-' { // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
b = append(b[1:], ' ', 'B', 'C') bc := false
if t.Year() <= 0 {
// flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
t = t.AddDate((-t.Year())*2+1, 0, 0)
bc = true
}
b = []byte(t.Format(time.RFC3339Nano))
if bc {
b = append(b, " BC"...)
} }
_, offset := t.Zone() _, offset := t.Zone()

View File

@ -30,8 +30,8 @@ func TestScanNilTimestamp(t *testing.T) {
} }
var timeTests = []struct { var timeTests = []struct {
str string str string
timeval time.Time timeval time.Time
}{ }{
{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))}, {"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))}, {"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
@ -57,11 +57,17 @@ var timeTests = []struct {
time.FixedZone("", -(7*60*60+30*60+9)))}, 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, {"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
time.FixedZone("", 7*60*60))}, time.FixedZone("", 7*60*60))},
//{"10000-02-03 04:05:06 BC", time.Date(-10000, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))}, {"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
{"0010-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))},
{"0010-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,
{"0010-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
time.FixedZone("", -7*60*60))}, 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))},
} }
// Helper function for the two tests below // Helper function for the two tests below

View File

@ -38,8 +38,7 @@ func TestHstore(t *testing.T) {
// quitely create hstore if it doesn't exist // quitely create hstore if it doesn't exist
_, err := db.Exec("CREATE EXTENSION IF NOT EXISTS hstore") _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS hstore")
if err != nil { if err != nil {
t.Log("Skipping hstore tests - hstore extension create failed. " + err.Error()) t.Skipf("Skipping hstore tests - hstore extension create failed: %s", err.Error())
return
} }
hs := Hstore{} hs := Hstore{}

View File

@ -170,8 +170,6 @@ func (l *ListenerConn) listenerConnLoop() (err error) {
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t) return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
} }
} }
panic("not reached")
} }
// This is the main routine for the goroutine receiving on the database // This is the main routine for the goroutine receiving on the database
@ -318,8 +316,6 @@ func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
return false, fmt.Errorf("unknown response for simple query: %q", m.typ) return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
} }
} }
panic("not reached")
} }
func (l *ListenerConn) Close() error { func (l *ListenerConn) Close() error {
@ -428,6 +424,13 @@ func NewListener(name string,
return l return l
} }
// Returns the notification channel for this listener. This is the same
// channel as Notify, and will not be recreated during the life time of the
// Listener.
func (l *Listener) NotificationChannel() <-chan *Notification {
return l.Notify
}
// Listen starts listening for notifications on a channel. Calls to this // Listen starts listening for notifications on a channel. Calls to this
// function will block until an acknowledgement has been received from the // function will block until an acknowledgement has been received from the
// server. Note that Listener automatically re-establishes the connection // server. Note that Listener automatically re-establishes the connection
@ -631,8 +634,6 @@ func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notificatio
return err return err
} }
} }
panic("not reached")
} }
// caller should NOT be holding l.lock // caller should NOT be holding l.lock

View File

@ -24,7 +24,6 @@ func expectNotification(t *testing.T, ch <-chan *Notification, relname string, e
case <-time.After(1500 * time.Millisecond): case <-time.After(1500 * time.Millisecond):
return fmt.Errorf("timeout") return fmt.Errorf("timeout")
} }
panic("not reached")
} }
func expectNoNotification(t *testing.T, ch <-chan *Notification) error { func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
@ -34,7 +33,6 @@ func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
return nil return nil
} }
panic("not reached")
} }
func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error { func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error {
@ -47,7 +45,6 @@ func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEven
case <-time.After(1500 * time.Millisecond): case <-time.After(1500 * time.Millisecond):
return fmt.Errorf("timeout") return fmt.Errorf("timeout")
} }
panic("not reached")
} }
func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error { func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
@ -57,7 +54,6 @@ func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
return nil return nil
} }
panic("not reached")
} }
func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) { func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) {
@ -219,8 +215,7 @@ func TestNotifyExtra(t *testing.T) {
defer db.Close() defer db.Close()
if getServerVersion(t, db) < 90000 { if getServerVersion(t, db) < 90000 {
t.Log("skipping test due to old PG version") t.Skip("skipping NOTIFY payload test since the server does not appear to support it")
return
} }
l, channel := newTestListenerConn(t) l, channel := newTestListenerConn(t)

View File

@ -12,21 +12,18 @@ import (
"testing" "testing"
) )
func shouldSkipSSLTests(t *testing.T) bool { func maybeSkipSSLTests(t *testing.T) {
// Require some special variables for testing certificates // Require some special variables for testing certificates
if os.Getenv("PQSSLCERTTEST_PATH") == "" { if os.Getenv("PQSSLCERTTEST_PATH") == "" {
return true t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests")
} }
value := os.Getenv("PQGOSSLTESTS") value := os.Getenv("PQGOSSLTESTS")
if value == "" || value == "0" { if value == "" || value == "0" {
return true t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests")
} else if value == "1" { } else if value != "1" {
return false
} else {
t.Fatalf("unexpected value %q for PQGOSSLTESTS", value) t.Fatalf("unexpected value %q for PQGOSSLTESTS", value)
} }
panic("not reached")
} }
func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) { func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) {
@ -48,16 +45,13 @@ func checkSSLSetup(t *testing.T, conninfo string) {
db, err := openSSLConn(t, conninfo) db, err := openSSLConn(t, conninfo)
if err == nil { if err == nil {
db.Close() db.Close()
t.Fatal("expected error with conninfo=%q", conninfo) t.Fatalf("expected error with conninfo=%q", conninfo)
} }
} }
// Connect over SSL and run a simple query to test the basics // Connect over SSL and run a simple query to test the basics
func TestSSLConnection(t *testing.T) { func TestSSLConnection(t *testing.T) {
if shouldSkipSSLTests(t) { maybeSkipSSLTests(t)
t.Log("skipping SSL test")
return
}
// Environment sanity check: should fail without SSL // Environment sanity check: should fail without SSL
checkSSLSetup(t, "sslmode=disable user=pqgossltest") checkSSLSetup(t, "sslmode=disable user=pqgossltest")
@ -74,10 +68,7 @@ func TestSSLConnection(t *testing.T) {
// Test sslmode=verify-full // Test sslmode=verify-full
func TestSSLVerifyFull(t *testing.T) { func TestSSLVerifyFull(t *testing.T) {
if shouldSkipSSLTests(t) { maybeSkipSSLTests(t)
t.Log("skipping SSL test")
return
}
// Environment sanity check: should fail without SSL // Environment sanity check: should fail without SSL
checkSSLSetup(t, "sslmode=disable user=pqgossltest") checkSSLSetup(t, "sslmode=disable user=pqgossltest")
@ -94,7 +85,7 @@ func TestSSLVerifyFull(t *testing.T) {
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
rootCert := "sslrootcert=" + rootCertPath + " " rootCert := "sslrootcert=" + rootCertPath + " "
// No match on Common Name // No match on Common Name
_, err = openSSLConn(t, rootCert + "host=127.0.0.1 sslmode=verify-full user=pqgossltest") _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest")
if err == nil { if err == nil {
t.Fatal("expected error") t.Fatal("expected error")
} }
@ -103,7 +94,7 @@ func TestSSLVerifyFull(t *testing.T) {
t.Fatalf("expected x509.HostnameError, got %#+v", err) t.Fatalf("expected x509.HostnameError, got %#+v", err)
} }
// OK // OK
_, err = openSSLConn(t, rootCert + "host=postgres sslmode=verify-full user=pqgossltest") _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -111,10 +102,7 @@ func TestSSLVerifyFull(t *testing.T) {
// Test sslmode=verify-ca // Test sslmode=verify-ca
func TestSSLVerifyCA(t *testing.T) { func TestSSLVerifyCA(t *testing.T) {
if shouldSkipSSLTests(t) { maybeSkipSSLTests(t)
t.Log("skipping SSL test")
return
}
// Environment sanity check: should fail without SSL // Environment sanity check: should fail without SSL
checkSSLSetup(t, "sslmode=disable user=pqgossltest") checkSSLSetup(t, "sslmode=disable user=pqgossltest")
@ -131,18 +119,17 @@ func TestSSLVerifyCA(t *testing.T) {
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
rootCert := "sslrootcert=" + rootCertPath + " " rootCert := "sslrootcert=" + rootCertPath + " "
// No match on Common Name, but that's OK // No match on Common Name, but that's OK
_, err = openSSLConn(t, rootCert + "host=127.0.0.1 sslmode=verify-ca user=pqgossltest") _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Everything OK // Everything OK
_, err = openSSLConn(t, rootCert + "host=postgres sslmode=verify-ca user=pqgossltest") _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
func getCertConninfo(t *testing.T, source string) string { func getCertConninfo(t *testing.T, source string) string {
var sslkey string var sslkey string
var sslcert string var sslcert string
@ -170,10 +157,7 @@ func getCertConninfo(t *testing.T, source string) string {
// Authenticate over SSL using client certificates // Authenticate over SSL using client certificates
func TestSSLClientCertificates(t *testing.T) { func TestSSLClientCertificates(t *testing.T) {
if shouldSkipSSLTests(t) { maybeSkipSSLTests(t)
t.Log("skipping SSL test")
return
}
// Environment sanity check: should fail without SSL // Environment sanity check: should fail without SSL
checkSSLSetup(t, "sslmode=disable user=pqgossltest") checkSSLSetup(t, "sslmode=disable user=pqgossltest")
@ -205,10 +189,7 @@ func TestSSLClientCertificates(t *testing.T) {
// Test errors with ssl certificates // Test errors with ssl certificates
func TestSSLClientCertificatesMissingFiles(t *testing.T) { func TestSSLClientCertificatesMissingFiles(t *testing.T) {
if shouldSkipSSLTests(t) { maybeSkipSSLTests(t)
t.Log("skipping SSL test")
return
}
// Environment sanity check: should fail without SSL // Environment sanity check: should fail without SSL
checkSSLSetup(t, "sslmode=disable user=pqgossltest") checkSSLSetup(t, "sslmode=disable user=pqgossltest")

View File

@ -1,15 +1,24 @@
// Package pq is a pure Go Postgres driver for the database/sql package. // Package pq is a pure Go Postgres driver for the database/sql package.
// +build darwin freebsd linux nacl netbsd openbsd solaris // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package pq package pq
import "os/user" import (
"os"
"os/user"
)
func userCurrent() (string, error) { func userCurrent() (string, error) {
u, err := user.Current() u, err := user.Current()
if err != nil { if err == nil {
return "", err return u.Username, nil
} }
return u.Username, nil
name := os.Getenv("USER")
if name != "" {
return name, nil
}
return "", ErrCouldNotDetectUsername
} }

View File

@ -19,7 +19,7 @@ func userCurrent() (string, error) {
pwname_size := uint32(len(pw_name)) - 1 pwname_size := uint32(len(pw_name)) - 1
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size) err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
if err != nil { if err != nil {
return "", err return "", ErrCouldNotDetectUsername
} }
s := syscall.UTF16ToString(pw_name) s := syscall.UTF16ToString(pw_name)
u := filepath.Base(s) u := filepath.Base(s)

View File

@ -37,10 +37,10 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
} }
actualType := reflect.TypeOf(actual) actualType := reflect.TypeOf(actual)
if reflect.TypeOf(actual).ConvertibleTo(reflect.TypeOf(expected)) { if actualType.ConvertibleTo(reflect.TypeOf(expected)) {
expectedValue := reflect.ValueOf(expected) expectedValue := reflect.ValueOf(expected)
// Attempt comparison after type conversion // Attempt comparison after type conversion
if actual == expectedValue.Convert(actualType).Interface() { if reflect.DeepEqual(actual, expectedValue.Convert(actualType).Interface()) {
return true return true
} }
} }

View File

@ -40,6 +40,9 @@ func TestObjectsAreEqual(t *testing.T) {
if !ObjectsAreEqual(nil, nil) { if !ObjectsAreEqual(nil, nil) {
t.Error("objectsAreEqual should return true") t.Error("objectsAreEqual should return true")
} }
if ObjectsAreEqual(map[int]int{5: 10}, map[int]int{10: 20}) {
t.Error("objectsAreEqual should return false")
}
} }
@ -94,6 +97,10 @@ func TestEqual(t *testing.T) {
if !Equal(mockT, int64(123), uint64(123)) { if !Equal(mockT, int64(123), uint64(123)) {
t.Error("Equal should return true") t.Error("Equal should return true")
} }
funcA := func() int { return 42 }
if !Equal(mockT, funcA, funcA) {
t.Error("Equal should return true")
}
} }
@ -196,6 +203,11 @@ func TestNotEqual(t *testing.T) {
if !NotEqual(mockT, nil, new(AssertionTesterConformingObject)) { if !NotEqual(mockT, nil, new(AssertionTesterConformingObject)) {
t.Error("NotEqual should return true") t.Error("NotEqual should return true")
} }
funcA := func() int { return 23 }
funcB := func() int { return 42 }
if !NotEqual(mockT, funcA, funcB) {
t.Error("NotEqual should return true")
}
if NotEqual(mockT, "Hello World", "Hello World") { if NotEqual(mockT, "Hello World", "Hello World") {
t.Error("NotEqual should return false") t.Error("NotEqual should return false")
@ -223,10 +235,10 @@ func TestContains(t *testing.T) {
mockT := new(testing.T) mockT := new(testing.T)
list := []string{"Foo", "Bar"} list := []string{"Foo", "Bar"}
complexList := []*A{ complexList := []*A{
&A{"b", "c"}, {"b", "c"},
&A{"d", "e"}, {"d", "e"},
&A{"g", "h"}, {"g", "h"},
&A{"j", "k"}, {"j", "k"},
} }
if !Contains(mockT, "Hello World", "Hello") { if !Contains(mockT, "Hello World", "Hello") {