diff options
Diffstat (limited to 'vendor/github.com/lib/pq/encode.go')
-rw-r--r-- | vendor/github.com/lib/pq/encode.go | 117 |
1 files changed, 68 insertions, 49 deletions
diff --git a/vendor/github.com/lib/pq/encode.go b/vendor/github.com/lib/pq/encode.go index 6681bd3e7..29e8f6ff7 100644 --- a/vendor/github.com/lib/pq/encode.go +++ b/vendor/github.com/lib/pq/encode.go @@ -56,10 +56,13 @@ func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) [ } func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} { - if f == formatBinary { + switch f { + case formatBinary: return binaryDecode(parameterStatus, s, typ) - } else { + case formatText: return textDecode(parameterStatus, s, typ) + default: + panic("not reached") } } @@ -83,8 +86,14 @@ func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) inter func textDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} { switch typ { + case oid.T_char, oid.T_varchar, oid.T_text: + return string(s) case oid.T_bytea: - return parseBytea(s) + b, err := parseBytea(s) + if err != nil { + errorf("%s", err) + } + return b case oid.T_timestamptz: return parseTs(parameterStatus.currentLocation, string(s)) case oid.T_timestamp, oid.T_date: @@ -195,21 +204,21 @@ func mustParse(f string, typ oid.Oid, s []byte) time.Time { return t } -var invalidTimestampErr = errors.New("invalid timestamp") +var errInvalidTimestamp = errors.New("invalid timestamp") type timestampParser struct { err error } -func (p *timestampParser) expect(str, char string, pos int) { +func (p *timestampParser) expect(str string, char byte, pos int) { if p.err != nil { return } if pos+1 > len(str) { - p.err = invalidTimestampErr + p.err = errInvalidTimestamp return } - if c := str[pos : pos+1]; c != char && p.err == nil { + if c := str[pos]; c != char && p.err == nil { p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c) } } @@ -219,7 +228,7 @@ func (p *timestampParser) mustAtoi(str string, begin int, end int) int { return 0 } if begin < 0 || end < 0 || begin > end || end > len(str) { - p.err = invalidTimestampErr + p.err = errInvalidTimestamp return 0 } result, err := strconv.Atoi(str[begin:end]) @@ -242,7 +251,7 @@ type locationCache struct { // about 5% speed could be gained by putting the cache in the connection and // losing the mutex, at the cost of a small amount of memory and a somewhat // significant increase in code complexity. -var globalLocationCache *locationCache = newLocationCache() +var globalLocationCache = newLocationCache() func newLocationCache() *locationCache { return &locationCache{cache: make(map[int]*time.Location)} @@ -272,26 +281,26 @@ const ( infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive" ) -/* - * If EnableInfinityTs is not called, "-infinity" and "infinity" will return - * []byte("-infinity") and []byte("infinity") respectively, and potentially - * cause error "sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *time.Time", - * when scanning into a time.Time value. - * - * Once EnableInfinityTs has been called, all connections created using this - * driver will decode Postgres' "-infinity" and "infinity" for "timestamp", - * "timestamp with time zone" and "date" types to the predefined minimum and - * maximum times, respectively. When encoding time.Time values, any time which - * equals or precedes the predefined minimum time will be encoded to - * "-infinity". Any values at or past the maximum time will similarly be - * encoded to "infinity". - * - * - * If EnableInfinityTs is called with negative >= positive, it will panic. - * Calling EnableInfinityTs after a connection has been established results in - * undefined behavior. If EnableInfinityTs is called more than once, it will - * panic. - */ +// EnableInfinityTs controls the handling of Postgres' "-infinity" and +// "infinity" "timestamp"s. +// +// If EnableInfinityTs is not called, "-infinity" and "infinity" will return +// []byte("-infinity") and []byte("infinity") respectively, and potentially +// cause error "sql: Scan error on column index 0: unsupported driver -> Scan +// pair: []uint8 -> *time.Time", when scanning into a time.Time value. +// +// Once EnableInfinityTs has been called, all connections created using this +// driver will decode Postgres' "-infinity" and "infinity" for "timestamp", +// "timestamp with time zone" and "date" types to the predefined minimum and +// maximum times, respectively. When encoding time.Time values, any time which +// equals or precedes the predefined minimum time will be encoded to +// "-infinity". Any values at or past the maximum time will similarly be +// encoded to "infinity". +// +// If EnableInfinityTs is called with negative >= positive, it will panic. +// Calling EnableInfinityTs after a connection has been established results in +// undefined behavior. If EnableInfinityTs is called more than once, it will +// panic. func EnableInfinityTs(negative time.Time, positive time.Time) { if infinityTsEnabled { panic(infinityTsEnabledAlready) @@ -335,6 +344,10 @@ func parseTs(currentLocation *time.Location, str string) interface{} { return t } +// ParseTimestamp parses Postgres' text format. It returns a time.Time in +// currentLocation iff that time's offset agrees with the offset sent from the +// Postgres server. Otherwise, ParseTimestamp returns a time.Time with the +// fixed offset offset provided by the Postgres server. func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, error) { p := timestampParser{} @@ -344,18 +357,18 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro year := p.mustAtoi(str, 0, monSep) daySep := monSep + 3 month := p.mustAtoi(str, monSep+1, daySep) - p.expect(str, "-", daySep) + p.expect(str, '-', daySep) timeSep := daySep + 3 day := p.mustAtoi(str, daySep+1, timeSep) var hour, minute, second int if len(str) > monSep+len("01-01")+1 { - p.expect(str, " ", timeSep) + p.expect(str, ' ', timeSep) minSep := timeSep + 3 - p.expect(str, ":", minSep) + p.expect(str, ':', minSep) hour = p.mustAtoi(str, timeSep+1, minSep) secSep := minSep + 3 - p.expect(str, ":", secSep) + p.expect(str, ':', secSep) minute = p.mustAtoi(str, minSep+1, secSep) secEnd := secSep + 3 second = p.mustAtoi(str, secSep+1, secEnd) @@ -369,7 +382,7 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro nanoSec := 0 tzOff := 0 - if remainderIdx+1 <= len(str) && str[remainderIdx:remainderIdx+1] == "." { + if remainderIdx < len(str) && str[remainderIdx] == '.' { fracStart := remainderIdx + 1 fracOff := strings.IndexAny(str[fracStart:], "-+ ") if fracOff < 0 { @@ -380,25 +393,26 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro remainderIdx += fracOff + 1 } - if tzStart := remainderIdx; tzStart+1 <= len(str) && (str[tzStart:tzStart+1] == "-" || str[tzStart:tzStart+1] == "+") { + if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') { // time zone separator is always '-' or '+' (UTC is +00) var tzSign int - if c := str[tzStart : tzStart+1]; c == "-" { + switch c := str[tzStart]; c { + case '-': tzSign = -1 - } else if c == "+" { + case '+': tzSign = +1 - } else { + default: return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c) } tzHours := p.mustAtoi(str, tzStart+1, tzStart+3) remainderIdx += 3 var tzMin, tzSec int - if tzStart+4 <= len(str) && str[tzStart+3:tzStart+4] == ":" { - tzMin = p.mustAtoi(str, tzStart+4, tzStart+6) + if remainderIdx < len(str) && str[remainderIdx] == ':' { + tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3) remainderIdx += 3 } - if tzStart+7 <= len(str) && str[tzStart+6:tzStart+7] == ":" { - tzSec = p.mustAtoi(str, tzStart+7, tzStart+9) + if remainderIdx < len(str) && str[remainderIdx] == ':' { + tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3) remainderIdx += 3 } tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec) @@ -432,7 +446,7 @@ func ParseTimestamp(currentLocation *time.Location, str string) (time.Time, erro } // formatTs formats t into a format postgres understands. -func formatTs(t time.Time) (b []byte) { +func formatTs(t time.Time) []byte { if infinityTsEnabled { // t <= -infinity : ! (t > -infinity) if !t.After(infinityTsNegative) { @@ -443,6 +457,11 @@ func formatTs(t time.Time) (b []byte) { return []byte("infinity") } } + return FormatTimestamp(t) +} + +// FormatTimestamp formats t into Postgres' text format for timestamps. +func FormatTimestamp(t time.Time) []byte { // Need to send dates before 0001 A.D. with " BC" suffix, instead of the // minus sign preferred by Go. // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on @@ -452,7 +471,7 @@ func formatTs(t time.Time) (b []byte) { t = t.AddDate((-t.Year())*2+1, 0, 0) bc = true } - b = []byte(t.Format(time.RFC3339Nano)) + b := []byte(t.Format(time.RFC3339Nano)) _, offset := t.Zone() offset = offset % 60 @@ -477,14 +496,14 @@ func formatTs(t time.Time) (b []byte) { // Parse a bytea value received from the server. Both "hex" and the legacy // "escape" format are supported. -func parseBytea(s []byte) (result []byte) { +func parseBytea(s []byte) (result []byte, err error) { if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) { // bytea_output = hex s = s[2:] // trim off leading "\\x" result = make([]byte, hex.DecodedLen(len(s))) _, err := hex.Decode(result, s) if err != nil { - errorf("%s", err) + return nil, err } } else { // bytea_output = escape @@ -499,11 +518,11 @@ func parseBytea(s []byte) (result []byte) { // '\\' followed by an octal number if len(s) < 4 { - errorf("invalid bytea sequence %v", s) + return nil, fmt.Errorf("invalid bytea sequence %v", s) } r, err := strconv.ParseInt(string(s[1:4]), 8, 9) if err != nil { - errorf("could not parse bytea value: %s", err.Error()) + return nil, fmt.Errorf("could not parse bytea value: %s", err.Error()) } result = append(result, byte(r)) s = s[4:] @@ -521,7 +540,7 @@ func parseBytea(s []byte) (result []byte) { } } - return result + return result, nil } func encodeBytea(serverVersion int, v []byte) (result []byte) { |