update
1. add cron module 2. add pool module 3. add tcgplayer background job 4. add card price model
This commit is contained in:
parent
6a41584aa2
commit
273278d322
3
go.mod
3
go.mod
@ -1,7 +1,7 @@
|
|||||||
module git.trj.tw/golang/mtgbot
|
module git.trj.tw/golang/mtgbot
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189
|
git.trj.tw/golang/utils v0.0.0-20190116070516-b266ebeb2d82
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
||||||
github.com/gin-gonic/gin v1.3.0
|
github.com/gin-gonic/gin v1.3.0
|
||||||
@ -13,6 +13,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||||
github.com/stretchr/testify v1.2.2 // indirect
|
github.com/stretchr/testify v1.2.2 // indirect
|
||||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 // indirect
|
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 // indirect
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -1,5 +1,7 @@
|
|||||||
git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189 h1:KHFlWRVFd5NIsnnFSy090+k6kT7KRotxUPTsho6F9lk=
|
git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189 h1:KHFlWRVFd5NIsnnFSy090+k6kT7KRotxUPTsho6F9lk=
|
||||||
git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189/go.mod h1:yE+qbsUsijCTdwsaQRkPT1CXYk7ftMzXsCaaYx/0QI0=
|
git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189/go.mod h1:yE+qbsUsijCTdwsaQRkPT1CXYk7ftMzXsCaaYx/0QI0=
|
||||||
|
git.trj.tw/golang/utils v0.0.0-20190116070516-b266ebeb2d82 h1:to19C8t9z6z4b1EMnMm8JIB4qDBCkBjEAhHH5yopSZ0=
|
||||||
|
git.trj.tw/golang/utils v0.0.0-20190116070516-b266ebeb2d82/go.mod h1:yE+qbsUsijCTdwsaQRkPT1CXYk7ftMzXsCaaYx/0QI0=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
||||||
@ -26,6 +28,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
|
||||||
|
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4=
|
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4=
|
||||||
|
48
models/card_price.go
Normal file
48
models/card_price.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CardPrice -
|
||||||
|
type CardPrice struct {
|
||||||
|
Card string `db:"card" cc:"card"`
|
||||||
|
PriceN float32 `db:"price_n" cc:"price_n"`
|
||||||
|
PriceF float32 `db:"price_f" cc:"price_f"`
|
||||||
|
Ctime time.Time `db:"ctime" cc:"ctime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsertCardPriceOpts struct {
|
||||||
|
Data []CardPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertCardPrice -
|
||||||
|
func InsertCardPrice(arg InsertCardPriceOpts) (err error) {
|
||||||
|
if len(arg.Data) == 0 {
|
||||||
|
return errors.New("data list is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := make([]CardPrice, 0, len(arg.Data))
|
||||||
|
|
||||||
|
for _, v := range arg.Data {
|
||||||
|
if len(v.Card) > 0 {
|
||||||
|
tmp = append(tmp, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `insert into "public"."card_price" ("card", "price_n", "price_f", "ctime") values `
|
||||||
|
insStr := ""
|
||||||
|
|
||||||
|
for i := 0; i < len(tmp); i++ {
|
||||||
|
if len(insStr) > 0 {
|
||||||
|
insStr += ","
|
||||||
|
}
|
||||||
|
|
||||||
|
insStr += " (:card, :price_n, :price_f, now()) "
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = x.NamedExec(query+insStr, tmp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ type Cards struct {
|
|||||||
Types []string `db:"types" cc:"types"`
|
Types []string `db:"types" cc:"types"`
|
||||||
Subtypes []string `db:"subtypes" cc:"subtypes"`
|
Subtypes []string `db:"subtypes" cc:"subtypes"`
|
||||||
Supertypes []string `db:"supertypes" cc:"supertypes"`
|
Supertypes []string `db:"supertypes" cc:"supertypes"`
|
||||||
|
TCGPlayerID int `db:"tcgplayerid" cc:"tcgplayerid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// order opts
|
// order opts
|
||||||
@ -104,7 +106,7 @@ func ListCards(arg ListCardsOpts) (cards []*Cards, err error) {
|
|||||||
arg.Offset = 0
|
arg.Offset = 0
|
||||||
}
|
}
|
||||||
query := `select c.id, c.name, c.cmc, c.mana_cost, c.text, c.layout, c.image_url, c.loyalty,
|
query := `select c.id, c.name, c.cmc, c.mana_cost, c.text, c.layout, c.image_url, c.loyalty,
|
||||||
c.type, c.number, c.power, c.toughness, c.set from "public"."cards" c`
|
c.type, c.number, c.power, c.toughness, c.set, c.tcgplayerid from "public"."cards" c`
|
||||||
where := struct {
|
where := struct {
|
||||||
Name string `db:"name"`
|
Name string `db:"name"`
|
||||||
CMC float32 `db:"cmc"`
|
CMC float32 `db:"cmc"`
|
||||||
@ -144,3 +146,18 @@ func ListCards(arg ListCardsOpts) (cards []*Cards, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCardByTCGPlayerIDs -
|
||||||
|
func GetCardByTCGPlayerIDs(ids []int) (cards []*Cards, err error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return nil, errors.New("ids list is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `select c.id, c.name, c.cmc, c.mana_cost, c.text, c.layout, c.image_url, c.loyalty,
|
||||||
|
c.type, c.number, c.power, c.toughness, c.set, c.tcgplayerid from "public"."cards" c where c.tcgplayerid in (?)`
|
||||||
|
err = x.Select(&cards, query, ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ type Sets struct {
|
|||||||
Code string `db:"code" cc:"code"`
|
Code string `db:"code" cc:"code"`
|
||||||
Name string `db:"name" cc:"name"`
|
Name string `db:"name" cc:"name"`
|
||||||
ReleaseDate time.Time `db:"release_date" cc:"release_date"`
|
ReleaseDate time.Time `db:"release_date" cc:"release_date"`
|
||||||
|
TCGPlayerID int `db:"tcgplayerid" cc:"tcgplayerid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListSetsOpts -
|
// ListSetsOpts -
|
||||||
|
@ -285,5 +285,48 @@ type TCGPlayerProductPrice struct {
|
|||||||
|
|
||||||
// ListGroupProductPrice -
|
// ListGroupProductPrice -
|
||||||
func (p *TCGPlayer) ListGroupProductPrice(groupID int) (prices []*TCGPlayerProductPrice, err error) {
|
func (p *TCGPlayer) ListGroupProductPrice(groupID int) (prices []*TCGPlayerProductPrice, err error) {
|
||||||
return
|
apiURL, err := p.getAPIURL(fmt.Sprintf("/pricing/group/%d", groupID), true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStruct := struct {
|
||||||
|
TCGPlayerAPIRes
|
||||||
|
Results []*TCGPlayerProductPrice `json:"results"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
reqObj := RequestObject{
|
||||||
|
URL: apiURL,
|
||||||
|
Headers: p.getHeader(),
|
||||||
|
Method: "GET",
|
||||||
|
}
|
||||||
|
req, err := GetRequest(reqObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respByte, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(respByte, &tmpStruct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStruct.Success != true {
|
||||||
|
if len(tmpStruct.Errors) > 0 {
|
||||||
|
return nil, errors.New(tmpStruct.Errors[0])
|
||||||
|
}
|
||||||
|
return nil, errors.New("get product price fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpStruct.Results, nil
|
||||||
}
|
}
|
||||||
|
12
modules/background/background.go
Normal file
12
modules/background/background.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package background
|
||||||
|
|
||||||
|
import "github.com/robfig/cron"
|
||||||
|
|
||||||
|
var c *cron.Cron
|
||||||
|
|
||||||
|
// SetCronJob -
|
||||||
|
func SetCronJob() {
|
||||||
|
c = cron.New()
|
||||||
|
|
||||||
|
c.Start()
|
||||||
|
}
|
108
modules/background/tcgplayer.go
Normal file
108
modules/background/tcgplayer.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package background
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.trj.tw/golang/mtgbot/models"
|
||||||
|
"git.trj.tw/golang/mtgbot/modules/apiact"
|
||||||
|
"git.trj.tw/golang/mtgbot/modules/pool"
|
||||||
|
"git.trj.tw/golang/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runMTGPriceJob() {
|
||||||
|
api, err := apiact.NewTCGApi("v1.19.0")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = api.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
numSets, err := models.CountSets(models.ListSetsOpts{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sets, err := models.ListSets(models.ListSetsOpts{
|
||||||
|
Limit: numSets,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = sets
|
||||||
|
|
||||||
|
pool := pool.NewPool(5)
|
||||||
|
|
||||||
|
for _, set := range sets {
|
||||||
|
pool.Add()
|
||||||
|
go getPriceFromAPI(pool, api, set.TCGPlayerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPriceFromAPI(pool *pool.Pool, api *apiact.TCGPlayer, groupid int) {
|
||||||
|
defer pool.Done()
|
||||||
|
|
||||||
|
prices, err := api.ListGroupProductPrice(groupid)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := make([]int, 0, len(prices))
|
||||||
|
|
||||||
|
type cPrice struct {
|
||||||
|
CardID string
|
||||||
|
PriceN float32
|
||||||
|
PriceF float32
|
||||||
|
}
|
||||||
|
|
||||||
|
cardMap := make(map[int]cPrice)
|
||||||
|
|
||||||
|
for _, v := range prices {
|
||||||
|
if utils.SliceIndex(len(ids), func(idx int) bool { return ids[idx] == v.ProductID }) == -1 {
|
||||||
|
ids = append(ids, v.ProductID)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := cardMap[v.ProductID]
|
||||||
|
if !ok {
|
||||||
|
s = cPrice{}
|
||||||
|
}
|
||||||
|
if v.SubTypeName == "Normal" {
|
||||||
|
s.PriceN = v.MidPrice
|
||||||
|
} else {
|
||||||
|
s.PriceF = v.MidPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
cardMap[v.ProductID] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
cards, err := models.GetCardByTCGPlayerIDs(ids)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range cards {
|
||||||
|
if s, ok := cardMap[v.TCGPlayerID]; ok {
|
||||||
|
s.CardID = v.ID
|
||||||
|
cardMap[v.TCGPlayerID] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insOpts := models.InsertCardPriceOpts{}
|
||||||
|
|
||||||
|
insOpts.Data = make([]models.CardPrice, 0, len(cardMap))
|
||||||
|
|
||||||
|
for _, v := range cardMap {
|
||||||
|
if len(v.CardID) > 0 {
|
||||||
|
insOpts.Data = append(insOpts.Data, models.CardPrice{
|
||||||
|
Card: v.CardID,
|
||||||
|
PriceN: v.PriceN,
|
||||||
|
PriceF: v.PriceF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
models.InsertCardPrice(insOpts)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
38
modules/pool/pool.go
Normal file
38
modules/pool/pool.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// Pool -
|
||||||
|
type Pool struct {
|
||||||
|
queue chan bool
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPool -
|
||||||
|
func NewPool(size int) *Pool {
|
||||||
|
if size < 1 {
|
||||||
|
size = 1
|
||||||
|
}
|
||||||
|
p := &Pool{
|
||||||
|
queue: make(chan bool, size),
|
||||||
|
wg: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add -
|
||||||
|
func (p *Pool) Add() {
|
||||||
|
p.queue <- true
|
||||||
|
p.wg.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done -
|
||||||
|
func (p *Pool) Done() {
|
||||||
|
<-p.queue
|
||||||
|
p.wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait -
|
||||||
|
func (p *Pool) Wait() {
|
||||||
|
p.wg.Wait()
|
||||||
|
}
|
1
schema/20190116-1.sql
Normal file
1
schema/20190116-1.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
alter table card_price drop constraint card_price_pk;
|
@ -2,7 +2,8 @@
|
|||||||
"versions": [
|
"versions": [
|
||||||
{"file": "main.sql", "version": 1},
|
{"file": "main.sql", "version": 1},
|
||||||
{"file": "20190106-1.sql", "version": 2},
|
{"file": "20190106-1.sql", "version": 2},
|
||||||
{"file": "20190115-1.sql", "version": 3}
|
{"file": "20190115-1.sql", "version": 3},
|
||||||
|
{"file": "20190116-1.sql", "version": 4}
|
||||||
],
|
],
|
||||||
"test": []
|
"test": []
|
||||||
}
|
}
|
||||||
|
10
vendor/git.trj.tw/golang/utils/utils.go
vendored
10
vendor/git.trj.tw/golang/utils/utils.go
vendored
@ -169,3 +169,13 @@ func CheckExists(filePath string, allowDir bool) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SliceIndex -
|
||||||
|
func SliceIndex(limit int, predicate func(idx int) bool) int {
|
||||||
|
for i := 0; i < limit; i++ {
|
||||||
|
if predicate(i) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
24
vendor/github.com/jmoiron/sqlx/.gitignore
generated
vendored
Normal file
24
vendor/github.com/jmoiron/sqlx/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
tags
|
||||||
|
environ
|
27
vendor/github.com/jmoiron/sqlx/.travis.yml
generated
vendored
Normal file
27
vendor/github.com/jmoiron/sqlx/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# vim: ft=yaml sw=2 ts=2
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
# enable database services
|
||||||
|
services:
|
||||||
|
- mysql
|
||||||
|
- postgresql
|
||||||
|
|
||||||
|
# create test database
|
||||||
|
before_install:
|
||||||
|
- mysql -e 'CREATE DATABASE IF NOT EXISTS sqlxtest;'
|
||||||
|
- psql -c 'create database sqlxtest;' -U postgres
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- export SQLX_MYSQL_DSN="travis:@/sqlxtest?parseTime=true"
|
||||||
|
- export SQLX_POSTGRES_DSN="postgres://postgres:@localhost/sqlxtest?sslmode=disable"
|
||||||
|
- export SQLX_SQLITE_DSN="$HOME/sqlxtest.db"
|
||||||
|
|
||||||
|
# go versions to test
|
||||||
|
go:
|
||||||
|
- "1.8"
|
||||||
|
- "1.9"
|
||||||
|
- "1.10.x"
|
||||||
|
|
||||||
|
# run tests w/ coverage
|
||||||
|
script:
|
||||||
|
- travis_retry $GOPATH/bin/goveralls -service=travis-ci
|
23
vendor/github.com/jmoiron/sqlx/LICENSE
generated
vendored
Normal file
23
vendor/github.com/jmoiron/sqlx/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2013, Jason Moiron
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
187
vendor/github.com/jmoiron/sqlx/README.md
generated
vendored
Normal file
187
vendor/github.com/jmoiron/sqlx/README.md
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# sqlx
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/jmoiron/sqlx.svg?branch=master)](https://travis-ci.org/jmoiron/sqlx) [![Coverage Status](https://coveralls.io/repos/github/jmoiron/sqlx/badge.svg?branch=master)](https://coveralls.io/github/jmoiron/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
|
||||||
|
|
||||||
|
sqlx is a library which provides a set of extensions on go's standard
|
||||||
|
`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
|
||||||
|
et al. all leave the underlying interfaces untouched, so that their interfaces
|
||||||
|
are a superset on the standard ones. This makes it relatively painless to
|
||||||
|
integrate existing codebases using database/sql with sqlx.
|
||||||
|
|
||||||
|
Major additional concepts are:
|
||||||
|
|
||||||
|
* Marshal rows into structs (with embedded struct support), maps, and slices
|
||||||
|
* Named parameter support including prepared statements
|
||||||
|
* `Get` and `Select` to go quickly from query to struct/slice
|
||||||
|
|
||||||
|
In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
|
||||||
|
there is also some [standard documentation](http://jmoiron.github.io/sqlx/) that
|
||||||
|
explains how to use `database/sql` along with sqlx.
|
||||||
|
|
||||||
|
## Recent Changes
|
||||||
|
|
||||||
|
* The [introduction](https://github.com/jmoiron/sqlx/pull/387) of `sql.ColumnType` sets the required minimum Go version to 1.8.
|
||||||
|
|
||||||
|
* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
|
||||||
|
|
||||||
|
This breaks backwards compatibility, but it's in a way that is trivially fixable
|
||||||
|
(`s/JsonText/JSONText/g`). The `types` package is both experimental and not in
|
||||||
|
active development currently.
|
||||||
|
|
||||||
|
* Using Go 1.6 and below with `types.JSONText` and `types.GzippedText` can be _potentially unsafe_, **especially** when used with common auto-scan sqlx idioms like `Select` and `Get`. See [golang bug #13905](https://github.com/golang/go/issues/13905).
|
||||||
|
|
||||||
|
### Backwards Compatibility
|
||||||
|
|
||||||
|
There is no Go1-like promise of absolute stability, but I take the issue seriously
|
||||||
|
and will maintain the library in a compatible state unless vital bugs prevent me
|
||||||
|
from doing so. Since [#59](https://github.com/jmoiron/sqlx/issues/59) and
|
||||||
|
[#60](https://github.com/jmoiron/sqlx/issues/60) necessitated breaking behavior,
|
||||||
|
a wider API cleanup was done at the time of fixing. It's possible this will happen
|
||||||
|
in future; if it does, a git tag will be provided for users requiring the old
|
||||||
|
behavior to continue to use it until such a time as they can migrate.
|
||||||
|
|
||||||
|
## install
|
||||||
|
|
||||||
|
go get github.com/jmoiron/sqlx
|
||||||
|
|
||||||
|
## issues
|
||||||
|
|
||||||
|
Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
|
||||||
|
`Columns()` does not fully qualify column names in queries like:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
making a struct or map destination ambiguous. Use `AS` in your queries
|
||||||
|
to give columns distinct names, `rows.Scan` to scan them manually, or
|
||||||
|
`SliceScan` to get a slice of results.
|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
|
Below is an example which shows some common use cases for sqlx. Check
|
||||||
|
[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
|
||||||
|
usage.
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var schema = `
|
||||||
|
CREATE TABLE person (
|
||||||
|
first_name text,
|
||||||
|
last_name text,
|
||||||
|
email text
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE place (
|
||||||
|
country text,
|
||||||
|
city text NULL,
|
||||||
|
telcode integer
|
||||||
|
)`
|
||||||
|
|
||||||
|
type Person struct {
|
||||||
|
FirstName string `db:"first_name"`
|
||||||
|
LastName string `db:"last_name"`
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Place struct {
|
||||||
|
Country string
|
||||||
|
City sql.NullString
|
||||||
|
TelCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// this Pings the database trying to connect, panics on error
|
||||||
|
// use sqlx.Open() for sql.Open() semantics
|
||||||
|
db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec the schema or fail; multi-statement Exec behavior varies between
|
||||||
|
// database drivers; pq will exec them all, sqlite3 won't, ymmv
|
||||||
|
db.MustExec(schema)
|
||||||
|
|
||||||
|
tx := db.MustBegin()
|
||||||
|
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
|
||||||
|
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
|
||||||
|
tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
|
||||||
|
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
|
||||||
|
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
|
||||||
|
// Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
|
||||||
|
tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
// Query the database, storing results in a []Person (wrapped in []interface{})
|
||||||
|
people := []Person{}
|
||||||
|
db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
|
||||||
|
jason, john := people[0], people[1]
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n%#v", jason, john)
|
||||||
|
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
|
||||||
|
// Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
|
||||||
|
|
||||||
|
// You can also get a single result, a la QueryRow
|
||||||
|
jason = Person{}
|
||||||
|
err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
|
||||||
|
fmt.Printf("%#v\n", jason)
|
||||||
|
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
|
||||||
|
|
||||||
|
// if you have null fields and use SELECT *, you must use sql.Null* in your struct
|
||||||
|
places := []Place{}
|
||||||
|
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
usa, singsing, honkers := places[0], places[1], places[2]
|
||||||
|
|
||||||
|
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:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
|
||||||
|
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
|
||||||
|
|
||||||
|
// Loop through rows using only one struct
|
||||||
|
place := Place{}
|
||||||
|
rows, err := db.Queryx("SELECT * FROM place")
|
||||||
|
for rows.Next() {
|
||||||
|
err := rows.StructScan(&place)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%#v\n", place)
|
||||||
|
}
|
||||||
|
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
|
||||||
|
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
|
||||||
|
// Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
|
||||||
|
|
||||||
|
// Named queries, using `:name` as the bindvar. Automatic bindvar support
|
||||||
|
// which takes into account the dbtype based on the driverName on sqlx.Open/Connect
|
||||||
|
_, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
|
||||||
|
map[string]interface{}{
|
||||||
|
"first": "Bin",
|
||||||
|
"last": "Smuth",
|
||||||
|
"email": "bensmith@allblacks.nz",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Selects Mr. Smith from the database
|
||||||
|
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
|
||||||
|
// as the name -> db mapping, so struct fields are lowercased and the `db` tag
|
||||||
|
// is taken into consideration.
|
||||||
|
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
217
vendor/github.com/jmoiron/sqlx/bind.go
generated
vendored
Normal file
217
vendor/github.com/jmoiron/sqlx/bind.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx/reflectx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bindvar types supported by Rebind, BindMap and BindStruct.
|
||||||
|
const (
|
||||||
|
UNKNOWN = iota
|
||||||
|
QUESTION
|
||||||
|
DOLLAR
|
||||||
|
NAMED
|
||||||
|
AT
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindType returns the bindtype for a given database given a drivername.
|
||||||
|
func BindType(driverName string) int {
|
||||||
|
switch driverName {
|
||||||
|
case "postgres", "pgx", "pq-timeouts", "cloudsqlpostgres":
|
||||||
|
return DOLLAR
|
||||||
|
case "mysql":
|
||||||
|
return QUESTION
|
||||||
|
case "sqlite3":
|
||||||
|
return QUESTION
|
||||||
|
case "oci8", "ora", "goracle":
|
||||||
|
return NAMED
|
||||||
|
case "sqlserver":
|
||||||
|
return AT
|
||||||
|
}
|
||||||
|
return UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this should be able to be tolerant of escaped ?'s in queries without
|
||||||
|
// losing much speed, and should be to avoid confusion.
|
||||||
|
|
||||||
|
// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
|
||||||
|
func Rebind(bindType int, query string) string {
|
||||||
|
switch bindType {
|
||||||
|
case QUESTION, UNKNOWN:
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add space enough for 10 params before we have to allocate
|
||||||
|
rqb := make([]byte, 0, len(query)+10)
|
||||||
|
|
||||||
|
var i, j int
|
||||||
|
|
||||||
|
for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") {
|
||||||
|
rqb = append(rqb, query[:i]...)
|
||||||
|
|
||||||
|
switch bindType {
|
||||||
|
case DOLLAR:
|
||||||
|
rqb = append(rqb, '$')
|
||||||
|
case NAMED:
|
||||||
|
rqb = append(rqb, ':', 'a', 'r', 'g')
|
||||||
|
case AT:
|
||||||
|
rqb = append(rqb, '@', 'p')
|
||||||
|
}
|
||||||
|
|
||||||
|
j++
|
||||||
|
rqb = strconv.AppendInt(rqb, int64(j), 10)
|
||||||
|
|
||||||
|
query = query[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(rqb, query...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
|
||||||
|
// much simpler and should be more resistant to odd unicode, but it is twice as
|
||||||
|
// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
|
||||||
|
// problems arise with its somewhat naive handling of unicode.
|
||||||
|
func rebindBuff(bindType int, query string) string {
|
||||||
|
if bindType != DOLLAR {
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 0, len(query))
|
||||||
|
rqb := bytes.NewBuffer(b)
|
||||||
|
j := 1
|
||||||
|
for _, r := range query {
|
||||||
|
if r == '?' {
|
||||||
|
rqb.WriteRune('$')
|
||||||
|
rqb.WriteString(strconv.Itoa(j))
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
rqb.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rqb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// In expands slice values in args, returning the modified query string
|
||||||
|
// and a new arg list that can be executed by a database. The `query` should
|
||||||
|
// use the `?` bindVar. The return value uses the `?` bindVar.
|
||||||
|
func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||||
|
// argMeta stores reflect.Value and length for slices and
|
||||||
|
// the value itself for non-slice arguments
|
||||||
|
type argMeta struct {
|
||||||
|
v reflect.Value
|
||||||
|
i interface{}
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
var flatArgsCount int
|
||||||
|
var anySlices bool
|
||||||
|
|
||||||
|
meta := make([]argMeta, len(args))
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
if a, ok := arg.(driver.Valuer); ok {
|
||||||
|
arg, _ = a.Value()
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(arg)
|
||||||
|
t := reflectx.Deref(v.Type())
|
||||||
|
|
||||||
|
// []byte is a driver.Value type so it should not be expanded
|
||||||
|
if t.Kind() == reflect.Slice && t != reflect.TypeOf([]byte{}) {
|
||||||
|
meta[i].length = v.Len()
|
||||||
|
meta[i].v = v
|
||||||
|
|
||||||
|
anySlices = true
|
||||||
|
flatArgsCount += meta[i].length
|
||||||
|
|
||||||
|
if meta[i].length == 0 {
|
||||||
|
return "", nil, errors.New("empty slice passed to 'in' query")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
meta[i].i = arg
|
||||||
|
flatArgsCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't do any parsing if there aren't any slices; note that this means
|
||||||
|
// some errors that we might have caught below will not be returned.
|
||||||
|
if !anySlices {
|
||||||
|
return query, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newArgs := make([]interface{}, 0, flatArgsCount)
|
||||||
|
buf := make([]byte, 0, len(query)+len(", ?")*flatArgsCount)
|
||||||
|
|
||||||
|
var arg, offset int
|
||||||
|
|
||||||
|
for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
|
||||||
|
if arg >= len(meta) {
|
||||||
|
// if an argument wasn't passed, lets return an error; this is
|
||||||
|
// not actually how database/sql Exec/Query works, but since we are
|
||||||
|
// creating an argument list programmatically, we want to be able
|
||||||
|
// to catch these programmer errors earlier.
|
||||||
|
return "", nil, errors.New("number of bindVars exceeds arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
argMeta := meta[arg]
|
||||||
|
arg++
|
||||||
|
|
||||||
|
// not a slice, continue.
|
||||||
|
// our questionmark will either be written before the next expansion
|
||||||
|
// of a slice or after the loop when writing the rest of the query
|
||||||
|
if argMeta.length == 0 {
|
||||||
|
offset = offset + i + 1
|
||||||
|
newArgs = append(newArgs, argMeta.i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// write everything up to and including our ? character
|
||||||
|
buf = append(buf, query[:offset+i+1]...)
|
||||||
|
|
||||||
|
for si := 1; si < argMeta.length; si++ {
|
||||||
|
buf = append(buf, ", ?"...)
|
||||||
|
}
|
||||||
|
|
||||||
|
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
|
||||||
|
|
||||||
|
// slice the query and reset the offset. this avoids some bookkeeping for
|
||||||
|
// the write after the loop
|
||||||
|
query = query[offset+i+1:]
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = append(buf, query...)
|
||||||
|
|
||||||
|
if arg < len(meta) {
|
||||||
|
return "", nil, errors.New("number of bindVars less than number arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf), newArgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
|
||||||
|
switch val := v.Interface().(type) {
|
||||||
|
case []interface{}:
|
||||||
|
args = append(args, val...)
|
||||||
|
case []int:
|
||||||
|
for i := range val {
|
||||||
|
args = append(args, val[i])
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
for i := range val {
|
||||||
|
args = append(args, val[i])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for si := 0; si < vlen; si++ {
|
||||||
|
args = append(args, v.Index(si).Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
12
vendor/github.com/jmoiron/sqlx/doc.go
generated
vendored
Normal file
12
vendor/github.com/jmoiron/sqlx/doc.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Package sqlx provides general purpose extensions to database/sql.
|
||||||
|
//
|
||||||
|
// It is intended to seamlessly wrap database/sql and provide convenience
|
||||||
|
// methods which are useful in the development of database driven applications.
|
||||||
|
// None of the underlying database/sql methods are changed. Instead all extended
|
||||||
|
// behavior is implemented through new methods defined on wrapper types.
|
||||||
|
//
|
||||||
|
// Additions include scanning into structs, named query support, rebinding
|
||||||
|
// queries for different drivers, convenient shorthands for common error handling
|
||||||
|
// and more.
|
||||||
|
//
|
||||||
|
package sqlx
|
7
vendor/github.com/jmoiron/sqlx/go.mod
generated
vendored
Normal file
7
vendor/github.com/jmoiron/sqlx/go.mod
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module github.com/jmoiron/sqlx
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0
|
||||||
|
github.com/lib/pq v1.0.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0
|
||||||
|
)
|
6
vendor/github.com/jmoiron/sqlx/go.sum
generated
vendored
Normal file
6
vendor/github.com/jmoiron/sqlx/go.sum
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
356
vendor/github.com/jmoiron/sqlx/named.go
generated
vendored
Normal file
356
vendor/github.com/jmoiron/sqlx/named.go
generated
vendored
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
package sqlx
|
||||||
|
|
||||||
|
// Named Query Support
|
||||||
|
//
|
||||||
|
// * BindMap - bind query bindvars to map/struct args
|
||||||
|
// * NamedExec, NamedQuery - named query w/ struct or map
|
||||||
|
// * NamedStmt - a pre-compiled named query which is a prepared statement
|
||||||
|
//
|
||||||
|
// Internal Interfaces:
|
||||||
|
//
|
||||||
|
// * compileNamedQuery - rebind a named query, returning a query and list of names
|
||||||
|
// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
|
||||||
|
//
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx/reflectx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NamedStmt is a prepared statement that executes named queries. Prepare it
|
||||||
|
// how you would execute a NamedQuery, but pass in a struct or map when executing.
|
||||||
|
type NamedStmt struct {
|
||||||
|
Params []string
|
||||||
|
QueryString string
|
||||||
|
Stmt *Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the named statement.
|
||||||
|
func (n *NamedStmt) Close() error {
|
||||||
|
return n.Stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes a named statement using the struct passed.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return *new(sql.Result), err
|
||||||
|
}
|
||||||
|
return n.Stmt.Exec(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query executes a named statement using the struct argument, returning rows.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n.Stmt.Query(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRow executes a named statement against the database. Because sqlx cannot
|
||||||
|
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
|
||||||
|
// returns a *sqlx.Row instead.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryRow(arg interface{}) *Row {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return &Row{err: err}
|
||||||
|
}
|
||||||
|
return n.Stmt.QueryRowx(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExec execs a NamedStmt, panicing on error
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
|
||||||
|
res, err := n.Exec(arg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queryx using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
|
||||||
|
r, err := n.Query(arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
|
||||||
|
// an alias for QueryRow.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
|
||||||
|
return n.QueryRow(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
|
||||||
|
rows, err := n.Queryx(arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if something happens here, we want to make sure the rows are Closed
|
||||||
|
defer rows.Close()
|
||||||
|
return scanAll(rows, dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
|
||||||
|
r := n.QueryRowx(arg)
|
||||||
|
return r.scanAny(dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsafe creates an unsafe version of the NamedStmt
|
||||||
|
func (n *NamedStmt) Unsafe() *NamedStmt {
|
||||||
|
r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
|
||||||
|
r.Stmt.unsafe = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// A union interface of preparer and binder, required to be able to prepare
|
||||||
|
// named statements (as the bindtype must be determined).
|
||||||
|
type namedPreparer interface {
|
||||||
|
Preparer
|
||||||
|
binder
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
|
||||||
|
bindType := BindType(p.DriverName())
|
||||||
|
q, args, err := compileNamedQuery([]byte(query), bindType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt, err := Preparex(p, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &NamedStmt{
|
||||||
|
QueryString: q,
|
||||||
|
Params: args,
|
||||||
|
Stmt: stmt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
||||||
|
if maparg, ok := arg.(map[string]interface{}); ok {
|
||||||
|
return bindMapArgs(names, maparg)
|
||||||
|
}
|
||||||
|
return bindArgs(names, arg, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// private interface to generate a list of interfaces from a given struct
|
||||||
|
// type, given a list of names to pull out of the struct. Used by public
|
||||||
|
// BindStruct interface.
|
||||||
|
func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
|
||||||
|
arglist := make([]interface{}, 0, len(names))
|
||||||
|
|
||||||
|
// grab the indirected value of arg
|
||||||
|
v := reflect.ValueOf(arg)
|
||||||
|
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
|
||||||
|
if len(t) == 0 {
|
||||||
|
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflectx.FieldByIndexesReadOnly(v, t)
|
||||||
|
arglist = append(arglist, val.Interface())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return arglist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// like bindArgs, but for maps.
|
||||||
|
func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
|
||||||
|
arglist := make([]interface{}, 0, len(names))
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
val, ok := arg[name]
|
||||||
|
if !ok {
|
||||||
|
return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
|
||||||
|
}
|
||||||
|
arglist = append(arglist, val)
|
||||||
|
}
|
||||||
|
return arglist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStruct binds a named parameter query with fields from a struct argument.
|
||||||
|
// The rules for binding field names to parameter names follow the same
|
||||||
|
// conventions as for StructScan, including obeying the `db` struct tags.
|
||||||
|
func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
||||||
|
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
||||||
|
if err != nil {
|
||||||
|
return "", []interface{}{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arglist, err := bindArgs(names, arg, m)
|
||||||
|
if err != nil {
|
||||||
|
return "", []interface{}{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bound, arglist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindMap binds a named parameter query with a map of arguments.
|
||||||
|
func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
|
||||||
|
bound, names, err := compileNamedQuery([]byte(query), bindType)
|
||||||
|
if err != nil {
|
||||||
|
return "", []interface{}{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arglist, err := bindMapArgs(names, args)
|
||||||
|
return bound, arglist, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Compilation of Named Queries
|
||||||
|
|
||||||
|
// Allow digits and letters in bind params; additionally runes are
|
||||||
|
// checked against underscores, meaning that bind params can have be
|
||||||
|
// alphanumeric with underscores. Mind the difference between unicode
|
||||||
|
// digits and numbers, where '5' is a digit but '五' is not.
|
||||||
|
var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
|
||||||
|
|
||||||
|
// FIXME: this function isn't safe for unicode named params, as a failing test
|
||||||
|
// can testify. This is not a regression but a failure of the original code
|
||||||
|
// as well. It should be modified to range over runes in a string rather than
|
||||||
|
// bytes, even though this is less convenient and slower. Hopefully the
|
||||||
|
// addition of the prepared NamedStmt (which will only do this once) will make
|
||||||
|
// up for the slightly slower ad-hoc NamedExec/NamedQuery.
|
||||||
|
|
||||||
|
// compile a NamedQuery into an unbound query (using the '?' bindvar) and
|
||||||
|
// a list of names.
|
||||||
|
func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
|
||||||
|
names = make([]string, 0, 10)
|
||||||
|
rebound := make([]byte, 0, len(qs))
|
||||||
|
|
||||||
|
inName := false
|
||||||
|
last := len(qs) - 1
|
||||||
|
currentVar := 1
|
||||||
|
name := make([]byte, 0, 10)
|
||||||
|
|
||||||
|
for i, b := range qs {
|
||||||
|
// a ':' while we're in a name is an error
|
||||||
|
if b == ':' {
|
||||||
|
// if this is the second ':' in a '::' escape sequence, append a ':'
|
||||||
|
if inName && i > 0 && qs[i-1] == ':' {
|
||||||
|
rebound = append(rebound, ':')
|
||||||
|
inName = false
|
||||||
|
continue
|
||||||
|
} else if inName {
|
||||||
|
err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
|
||||||
|
return query, names, err
|
||||||
|
}
|
||||||
|
inName = true
|
||||||
|
name = []byte{}
|
||||||
|
} else if inName && i > 0 && b == '=' {
|
||||||
|
rebound = append(rebound, ':', '=')
|
||||||
|
inName = false
|
||||||
|
continue
|
||||||
|
// if we're in a name, and this is an allowed character, continue
|
||||||
|
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
|
||||||
|
// append the byte to the name if we are in a name and not on the last byte
|
||||||
|
name = append(name, b)
|
||||||
|
// if we're in a name and it's not an allowed character, the name is done
|
||||||
|
} else if inName {
|
||||||
|
inName = false
|
||||||
|
// if this is the final byte of the string and it is part of the name, then
|
||||||
|
// make sure to add it to the name
|
||||||
|
if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
||||||
|
name = append(name, b)
|
||||||
|
}
|
||||||
|
// add the string representation to the names list
|
||||||
|
names = append(names, string(name))
|
||||||
|
// add a proper bindvar for the bindType
|
||||||
|
switch bindType {
|
||||||
|
// oracle only supports named type bind vars even for positional
|
||||||
|
case NAMED:
|
||||||
|
rebound = append(rebound, ':')
|
||||||
|
rebound = append(rebound, name...)
|
||||||
|
case QUESTION, UNKNOWN:
|
||||||
|
rebound = append(rebound, '?')
|
||||||
|
case DOLLAR:
|
||||||
|
rebound = append(rebound, '$')
|
||||||
|
for _, b := range strconv.Itoa(currentVar) {
|
||||||
|
rebound = append(rebound, byte(b))
|
||||||
|
}
|
||||||
|
currentVar++
|
||||||
|
case AT:
|
||||||
|
rebound = append(rebound, '@', 'p')
|
||||||
|
for _, b := range strconv.Itoa(currentVar) {
|
||||||
|
rebound = append(rebound, byte(b))
|
||||||
|
}
|
||||||
|
currentVar++
|
||||||
|
}
|
||||||
|
// add this byte to string unless it was not part of the name
|
||||||
|
if i != last {
|
||||||
|
rebound = append(rebound, b)
|
||||||
|
} else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
|
||||||
|
rebound = append(rebound, b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is a normal byte and should just go onto the rebound query
|
||||||
|
rebound = append(rebound, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(rebound), names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindNamed binds a struct or a map to a query with named parameters.
|
||||||
|
// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
|
||||||
|
func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
|
||||||
|
return bindNamedMapper(bindType, query, arg, mapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Named takes a query using named parameters and an argument and
|
||||||
|
// returns a new query with a list of args that can be executed by
|
||||||
|
// a database. The return value uses the `?` bindvar.
|
||||||
|
func Named(query string, arg interface{}) (string, []interface{}, error) {
|
||||||
|
return bindNamedMapper(QUESTION, query, arg, mapper())
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
|
||||||
|
if maparg, ok := arg.(map[string]interface{}); ok {
|
||||||
|
return bindMap(bindType, query, maparg)
|
||||||
|
}
|
||||||
|
return bindStruct(bindType, query, arg, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedQuery binds a named query and then runs Query on the result using the
|
||||||
|
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
|
||||||
|
// map[string]interface{} types.
|
||||||
|
func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
|
||||||
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Queryx(q, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedExec uses BindStruct to get a query executable by the driver and
|
||||||
|
// then runs Exec on the result. Returns an error from the binding
|
||||||
|
// or the query excution itself.
|
||||||
|
func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
|
||||||
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Exec(q, args...)
|
||||||
|
}
|
132
vendor/github.com/jmoiron/sqlx/named_context.go
generated
vendored
Normal file
132
vendor/github.com/jmoiron/sqlx/named_context.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A union interface of contextPreparer and binder, required to be able to
|
||||||
|
// prepare named statements with context (as the bindtype must be determined).
|
||||||
|
type namedPreparerContext interface {
|
||||||
|
PreparerContext
|
||||||
|
binder
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) {
|
||||||
|
bindType := BindType(p.DriverName())
|
||||||
|
q, args, err := compileNamedQuery([]byte(query), bindType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt, err := PreparexContext(ctx, p, q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &NamedStmt{
|
||||||
|
QueryString: q,
|
||||||
|
Params: args,
|
||||||
|
Stmt: stmt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecContext executes a named statement using the struct passed.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return *new(sql.Result), err
|
||||||
|
}
|
||||||
|
return n.Stmt.ExecContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryContext executes a named statement using the struct argument, returning rows.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n.Stmt.QueryContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowContext executes a named statement against the database. Because sqlx cannot
|
||||||
|
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
|
||||||
|
// returns a *sqlx.Row instead.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row {
|
||||||
|
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
|
||||||
|
if err != nil {
|
||||||
|
return &Row{err: err}
|
||||||
|
}
|
||||||
|
return n.Stmt.QueryRowxContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExecContext execs a NamedStmt, panicing on error
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result {
|
||||||
|
res, err := n.ExecContext(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryxContext using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) {
|
||||||
|
r, err := n.QueryContext(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is
|
||||||
|
// an alias for QueryRow.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row {
|
||||||
|
return n.QueryRowContext(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectContext using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error {
|
||||||
|
rows, err := n.QueryxContext(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if something happens here, we want to make sure the rows are Closed
|
||||||
|
defer rows.Close()
|
||||||
|
return scanAll(rows, dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext using this NamedStmt
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error {
|
||||||
|
r := n.QueryRowxContext(ctx, arg)
|
||||||
|
return r.scanAny(dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedQueryContext binds a named query and then runs Query on the result using the
|
||||||
|
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
|
||||||
|
// map[string]interface{} types.
|
||||||
|
func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) {
|
||||||
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.QueryxContext(ctx, q, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedExecContext uses BindStruct to get a query executable by the driver and
|
||||||
|
// then runs Exec on the result. Returns an error from the binding
|
||||||
|
// or the query excution itself.
|
||||||
|
func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) {
|
||||||
|
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.ExecContext(ctx, q, args...)
|
||||||
|
}
|
17
vendor/github.com/jmoiron/sqlx/reflectx/README.md
generated
vendored
Normal file
17
vendor/github.com/jmoiron/sqlx/reflectx/README.md
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# reflectx
|
||||||
|
|
||||||
|
The sqlx package has special reflect needs. In particular, it needs to:
|
||||||
|
|
||||||
|
* be able to map a name to a field
|
||||||
|
* understand embedded structs
|
||||||
|
* understand mapping names to fields by a particular tag
|
||||||
|
* user specified name -> field mapping functions
|
||||||
|
|
||||||
|
These behaviors mimic the behaviors by the standard library marshallers and also the
|
||||||
|
behavior of standard Go accessors.
|
||||||
|
|
||||||
|
The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
|
||||||
|
addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
|
||||||
|
tags in the ways that are vital to most marshallers, and they are slow.
|
||||||
|
|
||||||
|
This reflectx package extends reflect to achieve these goals.
|
441
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
generated
vendored
Normal file
441
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
generated
vendored
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
// Package reflectx implements extensions to the standard reflect lib suitable
|
||||||
|
// for implementing marshalling and unmarshalling packages. The main Mapper type
|
||||||
|
// allows for Go-compatible named attribute access, including accessing embedded
|
||||||
|
// struct attributes and the ability to use functions and struct tags to
|
||||||
|
// customize field names.
|
||||||
|
//
|
||||||
|
package reflectx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A FieldInfo is metadata for a struct field.
|
||||||
|
type FieldInfo struct {
|
||||||
|
Index []int
|
||||||
|
Path string
|
||||||
|
Field reflect.StructField
|
||||||
|
Zero reflect.Value
|
||||||
|
Name string
|
||||||
|
Options map[string]string
|
||||||
|
Embedded bool
|
||||||
|
Children []*FieldInfo
|
||||||
|
Parent *FieldInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// A StructMap is an index of field metadata for a struct.
|
||||||
|
type StructMap struct {
|
||||||
|
Tree *FieldInfo
|
||||||
|
Index []*FieldInfo
|
||||||
|
Paths map[string]*FieldInfo
|
||||||
|
Names map[string]*FieldInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByPath returns a *FieldInfo for a given string path.
|
||||||
|
func (f StructMap) GetByPath(path string) *FieldInfo {
|
||||||
|
return f.Paths[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByTraversal returns a *FieldInfo for a given integer path. It is
|
||||||
|
// analogous to reflect.FieldByIndex, but using the cached traversal
|
||||||
|
// rather than re-executing the reflect machinery each time.
|
||||||
|
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
|
||||||
|
if len(index) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tree := f.Tree
|
||||||
|
for _, i := range index {
|
||||||
|
if i >= len(tree.Children) || tree.Children[i] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tree = tree.Children[i]
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapper is a general purpose mapper of names to struct fields. A Mapper
|
||||||
|
// behaves like most marshallers in the standard library, obeying a field tag
|
||||||
|
// for name mapping but also providing a basic transform function.
|
||||||
|
type Mapper struct {
|
||||||
|
cache map[reflect.Type]*StructMap
|
||||||
|
tagName string
|
||||||
|
tagMapFunc func(string) string
|
||||||
|
mapFunc func(string) string
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMapper returns a new mapper using the tagName as its struct field tag.
|
||||||
|
// If tagName is the empty string, it is ignored.
|
||||||
|
func NewMapper(tagName string) *Mapper {
|
||||||
|
return &Mapper{
|
||||||
|
cache: make(map[reflect.Type]*StructMap),
|
||||||
|
tagName: tagName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMapperTagFunc returns a new mapper which contains a mapper for field names
|
||||||
|
// AND a mapper for tag values. This is useful for tags like json which can
|
||||||
|
// have values like "name,omitempty".
|
||||||
|
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
|
||||||
|
return &Mapper{
|
||||||
|
cache: make(map[reflect.Type]*StructMap),
|
||||||
|
tagName: tagName,
|
||||||
|
mapFunc: mapFunc,
|
||||||
|
tagMapFunc: tagMapFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMapperFunc returns a new mapper which optionally obeys a field tag and
|
||||||
|
// a struct field name mapper func given by f. Tags will take precedence, but
|
||||||
|
// for any other field, the mapped name will be f(field.Name)
|
||||||
|
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
|
||||||
|
return &Mapper{
|
||||||
|
cache: make(map[reflect.Type]*StructMap),
|
||||||
|
tagName: tagName,
|
||||||
|
mapFunc: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeMap returns a mapping of field strings to int slices representing
|
||||||
|
// the traversal down the struct to reach the field.
|
||||||
|
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
|
||||||
|
m.mutex.Lock()
|
||||||
|
mapping, ok := m.cache[t]
|
||||||
|
if !ok {
|
||||||
|
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
|
||||||
|
m.cache[t] = mapping
|
||||||
|
}
|
||||||
|
m.mutex.Unlock()
|
||||||
|
return mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldMap returns the mapper's mapping of field names to reflect values. Panics
|
||||||
|
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
|
||||||
|
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
mustBe(v, reflect.Struct)
|
||||||
|
|
||||||
|
r := map[string]reflect.Value{}
|
||||||
|
tm := m.TypeMap(v.Type())
|
||||||
|
for tagName, fi := range tm.Names {
|
||||||
|
r[tagName] = FieldByIndexes(v, fi.Index)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldByName returns a field by its mapped name as a reflect.Value.
|
||||||
|
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
|
||||||
|
// Returns zero Value if the name is not found.
|
||||||
|
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
mustBe(v, reflect.Struct)
|
||||||
|
|
||||||
|
tm := m.TypeMap(v.Type())
|
||||||
|
fi, ok := tm.Names[name]
|
||||||
|
if !ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return FieldByIndexes(v, fi.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldsByName returns a slice of values corresponding to the slice of names
|
||||||
|
// for the value. Panics if v's Kind is not Struct or v is not Indirectable
|
||||||
|
// to a struct Kind. Returns zero Value for each name not found.
|
||||||
|
func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
mustBe(v, reflect.Struct)
|
||||||
|
|
||||||
|
tm := m.TypeMap(v.Type())
|
||||||
|
vals := make([]reflect.Value, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
fi, ok := tm.Names[name]
|
||||||
|
if !ok {
|
||||||
|
vals = append(vals, *new(reflect.Value))
|
||||||
|
} else {
|
||||||
|
vals = append(vals, FieldByIndexes(v, fi.Index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraversalsByName returns a slice of int slices which represent the struct
|
||||||
|
// traversals for each mapped name. Panics if t is not a struct or Indirectable
|
||||||
|
// to a struct. Returns empty int slice for each name not found.
|
||||||
|
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
|
||||||
|
r := make([][]int, 0, len(names))
|
||||||
|
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
|
||||||
|
if i == nil {
|
||||||
|
r = append(r, []int{})
|
||||||
|
} else {
|
||||||
|
r = append(r, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
|
||||||
|
// each name and the struct traversal represented by that name. Panics if t is not
|
||||||
|
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
|
||||||
|
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
|
||||||
|
t = Deref(t)
|
||||||
|
mustBe(t, reflect.Struct)
|
||||||
|
tm := m.TypeMap(t)
|
||||||
|
for i, name := range names {
|
||||||
|
fi, ok := tm.Names[name]
|
||||||
|
if !ok {
|
||||||
|
if err := fn(i, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := fn(i, fi.Index); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldByIndexes returns a value for the field given by the struct traversal
|
||||||
|
// for the given value.
|
||||||
|
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
|
||||||
|
for _, i := range indexes {
|
||||||
|
v = reflect.Indirect(v).Field(i)
|
||||||
|
// if this is a pointer and it's nil, allocate a new value and set it
|
||||||
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
alloc := reflect.New(Deref(v.Type()))
|
||||||
|
v.Set(alloc)
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Map && v.IsNil() {
|
||||||
|
v.Set(reflect.MakeMap(v.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldByIndexesReadOnly returns a value for a particular struct traversal,
|
||||||
|
// but is not concerned with allocating nil pointers because the value is
|
||||||
|
// going to be used for reading and not setting.
|
||||||
|
func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
|
||||||
|
for _, i := range indexes {
|
||||||
|
v = reflect.Indirect(v).Field(i)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deref is Indirect for reflect.Types
|
||||||
|
func Deref(t reflect.Type) reflect.Type {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- helpers & utilities --
|
||||||
|
|
||||||
|
type kinder interface {
|
||||||
|
Kind() reflect.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustBe checks a value against a kind, panicing with a reflect.ValueError
|
||||||
|
// if the kind isn't that which is required.
|
||||||
|
func mustBe(v kinder, expected reflect.Kind) {
|
||||||
|
if k := v.Kind(); k != expected {
|
||||||
|
panic(&reflect.ValueError{Method: methodName(), Kind: k})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodName returns the caller of the function calling methodName
|
||||||
|
func methodName() string {
|
||||||
|
pc, _, _, _ := runtime.Caller(2)
|
||||||
|
f := runtime.FuncForPC(pc)
|
||||||
|
if f == nil {
|
||||||
|
return "unknown method"
|
||||||
|
}
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeQueue struct {
|
||||||
|
t reflect.Type
|
||||||
|
fi *FieldInfo
|
||||||
|
pp string // Parent path
|
||||||
|
}
|
||||||
|
|
||||||
|
// A copying append that creates a new slice each time.
|
||||||
|
func apnd(is []int, i int) []int {
|
||||||
|
x := make([]int, len(is)+1)
|
||||||
|
for p, n := range is {
|
||||||
|
x[p] = n
|
||||||
|
}
|
||||||
|
x[len(x)-1] = i
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapf func(string) string
|
||||||
|
|
||||||
|
// parseName parses the tag and the target name for the given field using
|
||||||
|
// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
|
||||||
|
// field's name to a target name, and tagMapFunc for mapping the tag to
|
||||||
|
// a target name.
|
||||||
|
func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
|
||||||
|
// first, set the fieldName to the field's name
|
||||||
|
fieldName = field.Name
|
||||||
|
// if a mapFunc is set, use that to override the fieldName
|
||||||
|
if mapFunc != nil {
|
||||||
|
fieldName = mapFunc(fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's no tag to look for, return the field name
|
||||||
|
if tagName == "" {
|
||||||
|
return "", fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this tag is not set using the normal convention in the tag,
|
||||||
|
// then return the fieldname.. this check is done because according
|
||||||
|
// to the reflect documentation:
|
||||||
|
// If the tag does not have the conventional format,
|
||||||
|
// the value returned by Get is unspecified.
|
||||||
|
// which doesn't sound great.
|
||||||
|
if !strings.Contains(string(field.Tag), tagName+":") {
|
||||||
|
return "", fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point we're fairly sure that we have a tag, so lets pull it out
|
||||||
|
tag = field.Tag.Get(tagName)
|
||||||
|
|
||||||
|
// if we have a mapper function, call it on the whole tag
|
||||||
|
// XXX: this is a change from the old version, which pulled out the name
|
||||||
|
// before the tagMapFunc could be run, but I think this is the right way
|
||||||
|
if tagMapFunc != nil {
|
||||||
|
tag = tagMapFunc(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, split the options from the name
|
||||||
|
parts := strings.Split(tag, ",")
|
||||||
|
fieldName = parts[0]
|
||||||
|
|
||||||
|
return tag, fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOptions parses options out of a tag string, skipping the name
|
||||||
|
func parseOptions(tag string) map[string]string {
|
||||||
|
parts := strings.Split(tag, ",")
|
||||||
|
options := make(map[string]string, len(parts))
|
||||||
|
if len(parts) > 1 {
|
||||||
|
for _, opt := range parts[1:] {
|
||||||
|
// short circuit potentially expensive split op
|
||||||
|
if strings.Contains(opt, "=") {
|
||||||
|
kv := strings.Split(opt, "=")
|
||||||
|
options[kv[0]] = kv[1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
options[opt] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
|
||||||
|
// tagMapFunc to determine the canonical names of fields.
|
||||||
|
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
|
||||||
|
m := []*FieldInfo{}
|
||||||
|
|
||||||
|
root := &FieldInfo{}
|
||||||
|
queue := []typeQueue{}
|
||||||
|
queue = append(queue, typeQueue{Deref(t), root, ""})
|
||||||
|
|
||||||
|
QueueLoop:
|
||||||
|
for len(queue) != 0 {
|
||||||
|
// pop the first item off of the queue
|
||||||
|
tq := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
|
// ignore recursive field
|
||||||
|
for p := tq.fi.Parent; p != nil; p = p.Parent {
|
||||||
|
if tq.fi.Field.Type == p.Field.Type {
|
||||||
|
continue QueueLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nChildren := 0
|
||||||
|
if tq.t.Kind() == reflect.Struct {
|
||||||
|
nChildren = tq.t.NumField()
|
||||||
|
}
|
||||||
|
tq.fi.Children = make([]*FieldInfo, nChildren)
|
||||||
|
|
||||||
|
// iterate through all of its fields
|
||||||
|
for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
|
||||||
|
|
||||||
|
f := tq.t.Field(fieldPos)
|
||||||
|
|
||||||
|
// parse the tag and the target name using the mapping options for this field
|
||||||
|
tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
|
||||||
|
|
||||||
|
// if the name is "-", disabled via a tag, skip it
|
||||||
|
if name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fi := FieldInfo{
|
||||||
|
Field: f,
|
||||||
|
Name: name,
|
||||||
|
Zero: reflect.New(f.Type).Elem(),
|
||||||
|
Options: parseOptions(tag),
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the path is empty this path is just the name
|
||||||
|
if tq.pp == "" {
|
||||||
|
fi.Path = fi.Name
|
||||||
|
} else {
|
||||||
|
fi.Path = tq.pp + "." + fi.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip unexported fields
|
||||||
|
if len(f.PkgPath) != 0 && !f.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// bfs search of anonymous embedded structs
|
||||||
|
if f.Anonymous {
|
||||||
|
pp := tq.pp
|
||||||
|
if tag != "" {
|
||||||
|
pp = fi.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.Embedded = true
|
||||||
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||||
|
nChildren := 0
|
||||||
|
ft := Deref(f.Type)
|
||||||
|
if ft.Kind() == reflect.Struct {
|
||||||
|
nChildren = ft.NumField()
|
||||||
|
}
|
||||||
|
fi.Children = make([]*FieldInfo, nChildren)
|
||||||
|
queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
|
||||||
|
} else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
|
||||||
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||||
|
fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
|
||||||
|
queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
||||||
|
fi.Parent = tq.fi
|
||||||
|
tq.fi.Children[fieldPos] = &fi
|
||||||
|
m = append(m, &fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
|
||||||
|
for _, fi := range flds.Index {
|
||||||
|
flds.Paths[fi.Path] = fi
|
||||||
|
if fi.Name != "" && !fi.Embedded {
|
||||||
|
flds.Names[fi.Path] = fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flds
|
||||||
|
}
|
1045
vendor/github.com/jmoiron/sqlx/sqlx.go
generated
vendored
Normal file
1045
vendor/github.com/jmoiron/sqlx/sqlx.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
346
vendor/github.com/jmoiron/sqlx/sqlx_context.go
generated
vendored
Normal file
346
vendor/github.com/jmoiron/sqlx/sqlx_context.go
generated
vendored
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package sqlx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConnectContext to a database and verify with a ping.
|
||||||
|
func ConnectContext(ctx context.Context, driverName, dataSourceName string) (*DB, error) {
|
||||||
|
db, err := Open(driverName, dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
err = db.PingContext(ctx)
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryerContext is an interface used by GetContext and SelectContext
|
||||||
|
type QueryerContext interface {
|
||||||
|
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
|
||||||
|
QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparerContext is an interface used by PreparexContext.
|
||||||
|
type PreparerContext interface {
|
||||||
|
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecerContext is an interface used by MustExecContext and LoadFileContext
|
||||||
|
type ExecerContext interface {
|
||||||
|
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtContext is a union interface which can bind, query, and exec, with Context
|
||||||
|
// used by NamedQueryContext and NamedExecContext.
|
||||||
|
type ExtContext interface {
|
||||||
|
binder
|
||||||
|
QueryerContext
|
||||||
|
ExecerContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectContext executes a query using the provided Queryer, and StructScans
|
||||||
|
// each row into dest, which must be a slice. If the slice elements are
|
||||||
|
// scannable, then the result set must have only one column. Otherwise,
|
||||||
|
// StructScan is used. The *sql.Rows are closed automatically.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
rows, err := q.QueryxContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if something happens here, we want to make sure the rows are Closed
|
||||||
|
defer rows.Close()
|
||||||
|
return scanAll(rows, dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparexContext prepares a statement.
|
||||||
|
//
|
||||||
|
// The provided context is used for the preparation of the statement, not for
|
||||||
|
// the execution of the statement.
|
||||||
|
func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) {
|
||||||
|
s, err := p.PrepareContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext does a QueryRow using the provided Queryer, and scans the
|
||||||
|
// resulting row to dest. If dest is scannable, the result must only have one
|
||||||
|
// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like
|
||||||
|
// row.Scan would. Any placeholder parameters are replaced with supplied args.
|
||||||
|
// An error is returned if the result set is empty.
|
||||||
|
func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
r := q.QueryRowxContext(ctx, query, args...)
|
||||||
|
return r.scanAny(dest, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFileContext exec's every statement in a file (as a single call to Exec).
|
||||||
|
// LoadFileContext may return a nil *sql.Result if errors are encountered
|
||||||
|
// locating or reading the file at path. LoadFile reads the entire file into
|
||||||
|
// memory, so it is not suitable for loading large data dumps, but can be useful
|
||||||
|
// for initializing schemas or loading indexes.
|
||||||
|
//
|
||||||
|
// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3
|
||||||
|
// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting
|
||||||
|
// this by requiring something with DriverName() and then attempting to split the
|
||||||
|
// queries will be difficult to get right, and its current driver-specific behavior
|
||||||
|
// is deemed at least not complex in its incorrectness.
|
||||||
|
func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) {
|
||||||
|
realpath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
contents, err := ioutil.ReadFile(realpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := e.ExecContext(ctx, string(contents))
|
||||||
|
return &res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExecContext execs the query using e and panics if there was an error.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result {
|
||||||
|
res, err := e.ExecContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareNamedContext returns an sqlx.NamedStmt
|
||||||
|
func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
|
||||||
|
return prepareNamedContext(ctx, db, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedQueryContext using this DB.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (db *DB) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) {
|
||||||
|
return NamedQueryContext(ctx, db, query, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedExecContext using this DB.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (db *DB) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
|
||||||
|
return NamedExecContext(ctx, db, query, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectContext using this DB.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return SelectContext(ctx, db, dest, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext using this DB.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
// An error is returned if the result set is empty.
|
||||||
|
func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return GetContext(ctx, db, dest, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
|
||||||
|
//
|
||||||
|
// The provided context is used for the preparation of the statement, not for
|
||||||
|
// the execution of the statement.
|
||||||
|
func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
|
||||||
|
return PreparexContext(ctx, db, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryxContext queries the database and returns an *sqlx.Rows.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||||
|
r, err := db.DB.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowxContext queries the database and returns an *sqlx.Row.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||||
|
rows, err := db.DB.QueryContext(ctx, query, args...)
|
||||||
|
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead
|
||||||
|
// of an *sql.Tx.
|
||||||
|
//
|
||||||
|
// The provided context is used until the transaction is committed or rolled
|
||||||
|
// back. If the context is canceled, the sql package will roll back the
|
||||||
|
// transaction. Tx.Commit will return an error if the context provided to
|
||||||
|
// MustBeginContext is canceled.
|
||||||
|
func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx {
|
||||||
|
tx, err := db.BeginTxx(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExecContext (panic) runs MustExec using this database.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
|
||||||
|
return MustExecContext(ctx, db, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
|
||||||
|
// *sql.Tx.
|
||||||
|
//
|
||||||
|
// The provided context is used until the transaction is committed or rolled
|
||||||
|
// back. If the context is canceled, the sql package will roll back the
|
||||||
|
// transaction. Tx.Commit will return an error if the context provided to
|
||||||
|
// BeginxContext is canceled.
|
||||||
|
func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
|
||||||
|
tx, err := db.DB.BeginTx(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtxContext returns a version of the prepared statement which runs within a
|
||||||
|
// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.
|
||||||
|
func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
|
||||||
|
var s *sql.Stmt
|
||||||
|
switch v := stmt.(type) {
|
||||||
|
case Stmt:
|
||||||
|
s = v.Stmt
|
||||||
|
case *Stmt:
|
||||||
|
s = v.Stmt
|
||||||
|
case *sql.Stmt:
|
||||||
|
s = v
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
|
||||||
|
}
|
||||||
|
return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedStmtContext returns a version of the prepared statement which runs
|
||||||
|
// within a transaction.
|
||||||
|
func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {
|
||||||
|
return &NamedStmt{
|
||||||
|
QueryString: stmt.QueryString,
|
||||||
|
Params: stmt.Params,
|
||||||
|
Stmt: tx.StmtxContext(ctx, stmt.Stmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
|
||||||
|
//
|
||||||
|
// The provided context is used for the preparation of the statement, not for
|
||||||
|
// the execution of the statement.
|
||||||
|
func (tx *Tx) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
|
||||||
|
return PreparexContext(ctx, tx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareNamedContext returns an sqlx.NamedStmt
|
||||||
|
func (tx *Tx) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
|
||||||
|
return prepareNamedContext(ctx, tx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExecContext runs MustExecContext within a transaction.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
|
||||||
|
return MustExecContext(ctx, tx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryxContext within a transaction and context.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (tx *Tx) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||||
|
r, err := tx.Tx.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectContext within a transaction and context.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (tx *Tx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return SelectContext(ctx, tx, dest, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext within a transaction and context.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
// An error is returned if the result set is empty.
|
||||||
|
func (tx *Tx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
|
||||||
|
return GetContext(ctx, tx, dest, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowxContext within a transaction and context.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (tx *Tx) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||||
|
rows, err := tx.Tx.QueryContext(ctx, query, args...)
|
||||||
|
return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamedExecContext using this Tx.
|
||||||
|
// Any named placeholder parameters are replaced with fields from arg.
|
||||||
|
func (tx *Tx) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
|
||||||
|
return NamedExecContext(ctx, tx, query, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectContext using the prepared statement.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error {
|
||||||
|
return SelectContext(ctx, &qStmt{s}, dest, "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContext using the prepared statement.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
// An error is returned if the result set is empty.
|
||||||
|
func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error {
|
||||||
|
return GetContext(ctx, &qStmt{s}, dest, "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustExecContext (panic) using this statement. Note that the query portion of
|
||||||
|
// the error output will be blank, as Stmt does not expose its query.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result {
|
||||||
|
return MustExecContext(ctx, &qStmt{s}, "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRowxContext using this statement.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row {
|
||||||
|
qs := &qStmt{s}
|
||||||
|
return qs.QueryRowxContext(ctx, "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryxContext using this statement.
|
||||||
|
// Any placeholder parameters are replaced with supplied args.
|
||||||
|
func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) {
|
||||||
|
qs := &qStmt{s}
|
||||||
|
return qs.QueryxContext(ctx, "", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return q.Stmt.QueryContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
|
||||||
|
r, err := q.Stmt.QueryContext(ctx, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
|
||||||
|
rows, err := q.Stmt.QueryContext(ctx, args...)
|
||||||
|
return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return q.Stmt.ExecContext(ctx, args...)
|
||||||
|
}
|
4
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
4
vendor/github.com/lib/pq/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.db
|
||||||
|
*.test
|
||||||
|
*~
|
||||||
|
*.swp
|
86
vendor/github.com/lib/pq/.travis.sh
generated
vendored
Normal file
86
vendor/github.com/lib/pq/.travis.sh
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
client_configure() {
|
||||||
|
sudo chmod 600 $PQSSLCERTTEST_PATH/postgresql.key
|
||||||
|
}
|
||||||
|
|
||||||
|
pgdg_repository() {
|
||||||
|
local sourcelist='sources.list.d/postgresql.list'
|
||||||
|
|
||||||
|
curl -sS 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' | sudo apt-key add -
|
||||||
|
echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PGVERSION | sudo tee "/etc/apt/$sourcelist"
|
||||||
|
sudo apt-get -o Dir::Etc::sourcelist="$sourcelist" -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0' update
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_configure() {
|
||||||
|
sudo tee /etc/postgresql/$PGVERSION/main/pg_hba.conf > /dev/null <<-config
|
||||||
|
local all all trust
|
||||||
|
hostnossl all pqgossltest 127.0.0.1/32 reject
|
||||||
|
hostnossl all pqgosslcert 127.0.0.1/32 reject
|
||||||
|
hostssl all pqgossltest 127.0.0.1/32 trust
|
||||||
|
hostssl all pqgosslcert 127.0.0.1/32 cert
|
||||||
|
host all all 127.0.0.1/32 trust
|
||||||
|
hostnossl all pqgossltest ::1/128 reject
|
||||||
|
hostnossl all pqgosslcert ::1/128 reject
|
||||||
|
hostssl all pqgossltest ::1/128 trust
|
||||||
|
hostssl all pqgosslcert ::1/128 cert
|
||||||
|
host all all ::1/128 trust
|
||||||
|
config
|
||||||
|
|
||||||
|
xargs sudo install -o postgres -g postgres -m 600 -t /var/lib/postgresql/$PGVERSION/main/ <<-certificates
|
||||||
|
certs/root.crt
|
||||||
|
certs/server.crt
|
||||||
|
certs/server.key
|
||||||
|
certificates
|
||||||
|
|
||||||
|
sort -VCu <<-versions ||
|
||||||
|
$PGVERSION
|
||||||
|
9.2
|
||||||
|
versions
|
||||||
|
sudo tee -a /etc/postgresql/$PGVERSION/main/postgresql.conf > /dev/null <<-config
|
||||||
|
ssl_ca_file = 'root.crt'
|
||||||
|
ssl_cert_file = 'server.crt'
|
||||||
|
ssl_key_file = 'server.key'
|
||||||
|
config
|
||||||
|
|
||||||
|
echo 127.0.0.1 postgres | sudo tee -a /etc/hosts > /dev/null
|
||||||
|
|
||||||
|
sudo service postgresql restart
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_install() {
|
||||||
|
xargs sudo apt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confnew' install <<-packages
|
||||||
|
postgresql-$PGVERSION
|
||||||
|
postgresql-server-dev-$PGVERSION
|
||||||
|
postgresql-contrib-$PGVERSION
|
||||||
|
packages
|
||||||
|
}
|
||||||
|
|
||||||
|
postgresql_uninstall() {
|
||||||
|
sudo service postgresql stop
|
||||||
|
xargs sudo apt-get -y --purge remove <<-packages
|
||||||
|
libpq-dev
|
||||||
|
libpq5
|
||||||
|
postgresql
|
||||||
|
postgresql-client-common
|
||||||
|
postgresql-common
|
||||||
|
packages
|
||||||
|
sudo rm -rf /var/lib/postgresql
|
||||||
|
}
|
||||||
|
|
||||||
|
megacheck_install() {
|
||||||
|
# Lock megacheck version at $MEGACHECK_VERSION to prevent spontaneous
|
||||||
|
# new error messages in old code.
|
||||||
|
go get -d honnef.co/go/tools/...
|
||||||
|
git -C $GOPATH/src/honnef.co/go/tools/ checkout $MEGACHECK_VERSION
|
||||||
|
go install honnef.co/go/tools/cmd/megacheck
|
||||||
|
megacheck --version
|
||||||
|
}
|
||||||
|
|
||||||
|
golint_install() {
|
||||||
|
go get github.com/golang/lint/golint
|
||||||
|
}
|
||||||
|
|
||||||
|
$1
|
50
vendor/github.com/lib/pq/.travis.yml
generated
vendored
Normal file
50
vendor/github.com/lib/pq/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
sudo: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- PGUSER=postgres
|
||||||
|
- PQGOSSLTESTS=1
|
||||||
|
- PQSSLCERTTEST_PATH=$PWD/certs
|
||||||
|
- PGHOST=127.0.0.1
|
||||||
|
- MEGACHECK_VERSION=2017.2.2
|
||||||
|
matrix:
|
||||||
|
- PGVERSION=10
|
||||||
|
- PGVERSION=9.6
|
||||||
|
- PGVERSION=9.5
|
||||||
|
- PGVERSION=9.4
|
||||||
|
- PGVERSION=9.3
|
||||||
|
- PGVERSION=9.2
|
||||||
|
- PGVERSION=9.1
|
||||||
|
- PGVERSION=9.0
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- ./.travis.sh postgresql_uninstall
|
||||||
|
- ./.travis.sh pgdg_repository
|
||||||
|
- ./.travis.sh postgresql_install
|
||||||
|
- ./.travis.sh postgresql_configure
|
||||||
|
- ./.travis.sh client_configure
|
||||||
|
- ./.travis.sh megacheck_install
|
||||||
|
- ./.travis.sh golint_install
|
||||||
|
- go get golang.org/x/tools/cmd/goimports
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- createdb pqgotest
|
||||||
|
- createuser -DRS pqgossltest
|
||||||
|
- createuser -DRS pqgosslcert
|
||||||
|
|
||||||
|
script:
|
||||||
|
- >
|
||||||
|
goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
|
||||||
|
- go vet ./...
|
||||||
|
- megacheck -go 1.8 ./...
|
||||||
|
- golint ./...
|
||||||
|
- PQTEST_BINARY_PARAMETERS=no go test -race -v ./...
|
||||||
|
- PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...
|
29
vendor/github.com/lib/pq/CONTRIBUTING.md
generated
vendored
Normal file
29
vendor/github.com/lib/pq/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
## Contributing to pq
|
||||||
|
|
||||||
|
`pq` has a backlog of pull requests, but contributions are still very
|
||||||
|
much welcome. You can help with patch review, submitting bug reports,
|
||||||
|
or adding new functionality. There is no formal style guide, but
|
||||||
|
please conform to the style of existing code and general Go formatting
|
||||||
|
conventions when submitting patches.
|
||||||
|
|
||||||
|
### Patch review
|
||||||
|
|
||||||
|
Help review existing open pull requests by commenting on the code or
|
||||||
|
proposed functionality.
|
||||||
|
|
||||||
|
### Bug reports
|
||||||
|
|
||||||
|
We appreciate any bug reports, but especially ones with self-contained
|
||||||
|
(doesn't depend on code outside of pq), minimal (can't be simplified
|
||||||
|
further) test cases. It's especially helpful if you can submit a pull
|
||||||
|
request with just the failing test case (you'll probably want to
|
||||||
|
pattern it after the tests in
|
||||||
|
[conn_test.go](https://github.com/lib/pq/blob/master/conn_test.go).
|
||||||
|
|
||||||
|
### New functionality
|
||||||
|
|
||||||
|
There are a number of pending patches for new functionality, so
|
||||||
|
additional feature patches will take a while to merge. Still, patches
|
||||||
|
are generally reviewed based on usefulness and complexity in addition
|
||||||
|
to time-in-queue, so if you have a knockout idea, take a shot. Feel
|
||||||
|
free to open an issue discussion your proposed patch beforehand.
|
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
8
vendor/github.com/lib/pq/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Copyright (c) 2011-2013, 'pq' Contributors
|
||||||
|
Portions Copyright (C) 2011 Blake Mizerany
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
95
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
95
vendor/github.com/lib/pq/README.md
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# pq - A pure Go postgres driver for Go's database/sql package
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/lib/pq?status.svg)](https://godoc.org/github.com/lib/pq)
|
||||||
|
[![Build Status](https://travis-ci.org/lib/pq.svg?branch=master)](https://travis-ci.org/lib/pq)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/lib/pq
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
For detailed documentation and basic usage examples, please see the package
|
||||||
|
documentation at <http://godoc.org/github.com/lib/pq>.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. See [TESTS.md](TESTS.md) for more details.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* SSL
|
||||||
|
* Handles bad connections for `database/sql`
|
||||||
|
* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`)
|
||||||
|
* Scan binary blobs correctly (i.e. `bytea`)
|
||||||
|
* Package for `hstore` support
|
||||||
|
* COPY FROM support
|
||||||
|
* pq.ParseURL for converting urls to connection strings for sql.Open.
|
||||||
|
* Many libpq compatible environment variables
|
||||||
|
* Unix socket support
|
||||||
|
* Notifications: `LISTEN`/`NOTIFY`
|
||||||
|
* pgpass support
|
||||||
|
|
||||||
|
## Future / Things you can help with
|
||||||
|
|
||||||
|
* Better COPY FROM / COPY TO (see discussion in #181)
|
||||||
|
|
||||||
|
## Thank you (alphabetical)
|
||||||
|
|
||||||
|
Some of these contributors are from the original library `bmizerany/pq.go` whose
|
||||||
|
code still exists in here.
|
||||||
|
|
||||||
|
* Andy Balholm (andybalholm)
|
||||||
|
* Ben Berkert (benburkert)
|
||||||
|
* Benjamin Heatwole (bheatwole)
|
||||||
|
* Bill Mill (llimllib)
|
||||||
|
* Bjørn Madsen (aeons)
|
||||||
|
* Blake Gentry (bgentry)
|
||||||
|
* Brad Fitzpatrick (bradfitz)
|
||||||
|
* Charlie Melbye (cmelbye)
|
||||||
|
* Chris Bandy (cbandy)
|
||||||
|
* Chris Gilling (cgilling)
|
||||||
|
* Chris Walsh (cwds)
|
||||||
|
* Dan Sosedoff (sosedoff)
|
||||||
|
* Daniel Farina (fdr)
|
||||||
|
* Eric Chlebek (echlebek)
|
||||||
|
* Eric Garrido (minusnine)
|
||||||
|
* Eric Urban (hydrogen18)
|
||||||
|
* Everyone at The Go Team
|
||||||
|
* Evan Shaw (edsrzf)
|
||||||
|
* Ewan Chou (coocood)
|
||||||
|
* Fazal Majid (fazalmajid)
|
||||||
|
* Federico Romero (federomero)
|
||||||
|
* Fumin (fumin)
|
||||||
|
* Gary Burd (garyburd)
|
||||||
|
* Heroku (heroku)
|
||||||
|
* James Pozdena (jpoz)
|
||||||
|
* Jason McVetta (jmcvetta)
|
||||||
|
* Jeremy Jay (pbnjay)
|
||||||
|
* Joakim Sernbrant (serbaut)
|
||||||
|
* John Gallagher (jgallagher)
|
||||||
|
* Jonathan Rudenberg (titanous)
|
||||||
|
* Joël Stemmer (jstemmer)
|
||||||
|
* Kamil Kisiel (kisielk)
|
||||||
|
* Kelly Dunn (kellydunn)
|
||||||
|
* Keith Rarick (kr)
|
||||||
|
* Kir Shatrov (kirs)
|
||||||
|
* Lann Martin (lann)
|
||||||
|
* Maciek Sakrejda (uhoh-itsmaciek)
|
||||||
|
* Marc Brinkmann (mbr)
|
||||||
|
* Marko Tiikkaja (johto)
|
||||||
|
* Matt Newberry (MattNewberry)
|
||||||
|
* Matt Robenolt (mattrobenolt)
|
||||||
|
* Martin Olsen (martinolsen)
|
||||||
|
* Mike Lewis (mikelikespie)
|
||||||
|
* Nicolas Patry (Narsil)
|
||||||
|
* Oliver Tonnhofer (olt)
|
||||||
|
* Patrick Hayes (phayes)
|
||||||
|
* Paul Hammond (paulhammond)
|
||||||
|
* Ryan Smith (ryandotsmith)
|
||||||
|
* Samuel Stauffer (samuel)
|
||||||
|
* Timothée Peignier (cyberdelia)
|
||||||
|
* Travis Cline (tmc)
|
||||||
|
* TruongSinh Tran-Nguyen (truongsinh)
|
||||||
|
* Yaismel Miranda (ympons)
|
||||||
|
* notedit (notedit)
|
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
33
vendor/github.com/lib/pq/TESTS.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Tests
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
`go test` is used for testing. A running PostgreSQL
|
||||||
|
server is required, with the ability to log in. The
|
||||||
|
database to connect to test with is "pqgotest," on
|
||||||
|
"localhost" but these can be overridden using [environment
|
||||||
|
variables](https://www.postgresql.org/docs/9.3/static/libpq-envars.html).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
PGHOST=/run/postgresql go test
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
A benchmark suite can be run as part of the tests:
|
||||||
|
|
||||||
|
go test -bench .
|
||||||
|
|
||||||
|
## Example setup (Docker)
|
||||||
|
|
||||||
|
Run a postgres container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --expose 5432:5432 postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
PGHOST=localhost PGPORT=5432 PGUSER=postgres PGSSLMODE=disable PGDATABASE=postgres go test
|
||||||
|
```
|
756
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
756
vendor/github.com/lib/pq/array.go
generated
vendored
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeByteSlice = reflect.TypeOf([]byte{})
|
||||||
|
var typeDriverValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
var typeSQLScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||||
|
|
||||||
|
// Array returns the optimal driver.Valuer and sql.Scanner for an array or
|
||||||
|
// slice of any dimension.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// db.Query(`SELECT * FROM t WHERE id = ANY($1)`, pq.Array([]int{235, 401}))
|
||||||
|
//
|
||||||
|
// var x []sql.NullInt64
|
||||||
|
// db.QueryRow('SELECT ARRAY[235, 401]').Scan(pq.Array(&x))
|
||||||
|
//
|
||||||
|
// Scanning multi-dimensional arrays is not supported. Arrays where the lower
|
||||||
|
// bound is not one (such as `[0:0]={1}') are not supported.
|
||||||
|
func Array(a interface{}) interface {
|
||||||
|
driver.Valuer
|
||||||
|
sql.Scanner
|
||||||
|
} {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case []bool:
|
||||||
|
return (*BoolArray)(&a)
|
||||||
|
case []float64:
|
||||||
|
return (*Float64Array)(&a)
|
||||||
|
case []int64:
|
||||||
|
return (*Int64Array)(&a)
|
||||||
|
case []string:
|
||||||
|
return (*StringArray)(&a)
|
||||||
|
|
||||||
|
case *[]bool:
|
||||||
|
return (*BoolArray)(a)
|
||||||
|
case *[]float64:
|
||||||
|
return (*Float64Array)(a)
|
||||||
|
case *[]int64:
|
||||||
|
return (*Int64Array)(a)
|
||||||
|
case *[]string:
|
||||||
|
return (*StringArray)(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenericArray{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayDelimiter may be optionally implemented by driver.Valuer or sql.Scanner
|
||||||
|
// to override the array delimiter used by GenericArray.
|
||||||
|
type ArrayDelimiter interface {
|
||||||
|
// ArrayDelimiter returns the delimiter character(s) for this element's type.
|
||||||
|
ArrayDelimiter() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolArray represents a one-dimensional array of the PostgreSQL boolean type.
|
||||||
|
type BoolArray []bool
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *BoolArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to BoolArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BoolArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "BoolArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(BoolArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if len(v) != 1 {
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
switch v[0] {
|
||||||
|
case 't':
|
||||||
|
b[i] = true
|
||||||
|
case 'f':
|
||||||
|
b[i] = false
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: could not parse boolean array index %d: invalid boolean %q", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a BoolArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be exactly two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1+2*n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[2*i] = ','
|
||||||
|
if a[i] {
|
||||||
|
b[1+2*i] = 't'
|
||||||
|
} else {
|
||||||
|
b[1+2*i] = 'f'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[2*n] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteaArray represents a one-dimensional array of the PostgreSQL bytea type.
|
||||||
|
type ByteaArray [][]byte
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *ByteaArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to ByteaArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ByteaArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "ByteaArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(ByteaArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
b[i], err = parseBytea(v)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse bytea array index %d: %s", i, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface. It uses the "hex" format which
|
||||||
|
// is only supported on PostgreSQL 9.0 or newer.
|
||||||
|
func (a ByteaArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// 3*N bytes of hex formatting, and N-1 bytes of delimiters.
|
||||||
|
size := 1 + 6*n
|
||||||
|
for _, x := range a {
|
||||||
|
size += hex.EncodedLen(len(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, size)
|
||||||
|
|
||||||
|
for i, s := 0, b; i < n; i++ {
|
||||||
|
o := copy(s, `,"\\x`)
|
||||||
|
o += hex.Encode(s[o:], a[i])
|
||||||
|
s[o] = '"'
|
||||||
|
s = s[o+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = '{'
|
||||||
|
b[size-1] = '}'
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Array represents a one-dimensional array of the PostgreSQL double
|
||||||
|
// precision type.
|
||||||
|
type Float64Array []float64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Float64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Float64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Float64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Float64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Float64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseFloat(string(v), 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Float64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendFloat(b, a[0], 'f', -1, 64)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendFloat(b, a[i], 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericArray implements the driver.Valuer and sql.Scanner interfaces for
|
||||||
|
// an array or slice of any dimension.
|
||||||
|
type GenericArray struct{ A interface{} }
|
||||||
|
|
||||||
|
func (GenericArray) evaluateDestination(rt reflect.Type) (reflect.Type, func([]byte, reflect.Value) error, string) {
|
||||||
|
var assign func([]byte, reflect.Value) error
|
||||||
|
var del = ","
|
||||||
|
|
||||||
|
// TODO calculate the assign function for other types
|
||||||
|
// TODO repeat this section on the element type of arrays or slices (multidimensional)
|
||||||
|
{
|
||||||
|
if reflect.PtrTo(rt).Implements(typeSQLScanner) {
|
||||||
|
// dest is always addressable because it is an element of a slice.
|
||||||
|
assign = func(src []byte, dest reflect.Value) (err error) {
|
||||||
|
ss := dest.Addr().Interface().(sql.Scanner)
|
||||||
|
if src == nil {
|
||||||
|
err = ss.Scan(nil)
|
||||||
|
} else {
|
||||||
|
err = ss.Scan(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
goto FoundType
|
||||||
|
}
|
||||||
|
|
||||||
|
assign = func([]byte, reflect.Value) error {
|
||||||
|
return fmt.Errorf("pq: scanning to %s is not implemented; only sql.Scanner", rt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FoundType:
|
||||||
|
|
||||||
|
if ad, ok := reflect.Zero(rt).Interface().(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt, assign, del
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a GenericArray) Scan(src interface{}) error {
|
||||||
|
dpv := reflect.ValueOf(a.A)
|
||||||
|
switch {
|
||||||
|
case dpv.Kind() != reflect.Ptr:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
case dpv.IsNil():
|
||||||
|
return fmt.Errorf("pq: destination %T is nil", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
dv := dpv.Elem()
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("pq: destination %T is not a pointer to array or slice", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src, dv)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src), dv)
|
||||||
|
case nil:
|
||||||
|
if dv.Kind() == reflect.Slice {
|
||||||
|
dv.Set(reflect.Zero(dv.Type()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to %s", src, dv.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a GenericArray) scanBytes(src []byte, dv reflect.Value) error {
|
||||||
|
dtype, assign, del := a.evaluateDestination(dv.Type().Elem())
|
||||||
|
dims, elems, err := parseArray(src, []byte(del))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow multidimensional
|
||||||
|
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return fmt.Errorf("pq: scanning from multidimensional ARRAY%s is not implemented",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Treat a zero-dimensional array like an array with a single dimension of zero.
|
||||||
|
if len(dims) == 0 {
|
||||||
|
dims = append(dims, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rt := 0, dv.Type(); i < len(dims); i, rt = i+1, rt.Elem() {
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
case reflect.Array:
|
||||||
|
if rt.Len() != dims[i] {
|
||||||
|
return fmt.Errorf("pq: cannot convert ARRAY%s to %s",
|
||||||
|
strings.Replace(fmt.Sprint(dims), " ", "][", -1), dv.Type())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO handle multidimensional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values := reflect.MakeSlice(reflect.SliceOf(dtype), len(elems), len(elems))
|
||||||
|
for i, e := range elems {
|
||||||
|
if err := assign(e, values.Index(i)); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle multidimensional
|
||||||
|
|
||||||
|
switch dv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
dv.Set(values.Slice(0, dims[0]))
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < dims[0]; i++ {
|
||||||
|
dv.Index(i).Set(values.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a GenericArray) Value() (driver.Value, error) {
|
||||||
|
if a.A == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(a.A)
|
||||||
|
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case reflect.Array:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: Unable to convert %T to array", a.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 0, 1+2*n)
|
||||||
|
|
||||||
|
b, _, err := appendArray(b, rv, n)
|
||||||
|
return string(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Array represents a one-dimensional array of the PostgreSQL integer types.
|
||||||
|
type Int64Array []int64
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *Int64Array) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to Int64Array", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Int64Array) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "Int64Array")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(Int64Array, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i], err = strconv.ParseInt(string(v), 10, 64); err != nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a Int64Array) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, N bytes of values,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+2*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = strconv.AppendInt(b, a[0], 10)
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = strconv.AppendInt(b, a[i], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray represents a one-dimensional array of the PostgreSQL character types.
|
||||||
|
type StringArray []string
|
||||||
|
|
||||||
|
// Scan implements the sql.Scanner interface.
|
||||||
|
func (a *StringArray) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case []byte:
|
||||||
|
return a.scanBytes(src)
|
||||||
|
case string:
|
||||||
|
return a.scanBytes([]byte(src))
|
||||||
|
case nil:
|
||||||
|
*a = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("pq: cannot convert %T to StringArray", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *StringArray) scanBytes(src []byte) error {
|
||||||
|
elems, err := scanLinearArray(src, []byte{','}, "StringArray")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *a != nil && len(elems) == 0 {
|
||||||
|
*a = (*a)[:0]
|
||||||
|
} else {
|
||||||
|
b := make(StringArray, len(elems))
|
||||||
|
for i, v := range elems {
|
||||||
|
if b[i] = string(v); v == nil {
|
||||||
|
return fmt.Errorf("pq: parsing array element index %d: cannot convert nil to string", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*a = b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver.Valuer interface.
|
||||||
|
func (a StringArray) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := len(a); n > 0 {
|
||||||
|
// There will be at least two curly brackets, 2*N bytes of quotes,
|
||||||
|
// and N-1 bytes of delimiters.
|
||||||
|
b := make([]byte, 1, 1+3*n)
|
||||||
|
b[0] = '{'
|
||||||
|
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[0]))
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, ',')
|
||||||
|
b = appendArrayQuotedBytes(b, []byte(a[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(append(b, '}')), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArray appends rv to the buffer, returning the extended buffer and
|
||||||
|
// the delimiter used between elements.
|
||||||
|
//
|
||||||
|
// It panics when n <= 0 or rv's Kind is not reflect.Array nor reflect.Slice.
|
||||||
|
func appendArray(b []byte, rv reflect.Value, n int) ([]byte, string, error) {
|
||||||
|
var del string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
b = append(b, '{')
|
||||||
|
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(0)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
b = append(b, del...)
|
||||||
|
if b, del, err = appendArrayElement(b, rv.Index(i)); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(b, '}'), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendArrayElement appends rv to the buffer, returning the extended buffer
|
||||||
|
// and the delimiter to use before the next element.
|
||||||
|
//
|
||||||
|
// When rv's Kind is neither reflect.Array nor reflect.Slice, it is converted
|
||||||
|
// using driver.DefaultParameterConverter and the resulting []byte or string
|
||||||
|
// is double-quoted.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func appendArrayElement(b []byte, rv reflect.Value) ([]byte, string, error) {
|
||||||
|
if k := rv.Kind(); k == reflect.Array || k == reflect.Slice {
|
||||||
|
if t := rv.Type(); t != typeByteSlice && !t.Implements(typeDriverValuer) {
|
||||||
|
if n := rv.Len(); n > 0 {
|
||||||
|
return appendArray(b, rv, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = ","
|
||||||
|
var err error
|
||||||
|
var iv interface{} = rv.Interface()
|
||||||
|
|
||||||
|
if ad, ok := iv.(ArrayDelimiter); ok {
|
||||||
|
del = ad.ArrayDelimiter()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iv, err = driver.DefaultParameterConverter.ConvertValue(iv); err != nil {
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := iv.(type) {
|
||||||
|
case nil:
|
||||||
|
return append(b, "NULL"...), del, nil
|
||||||
|
case []byte:
|
||||||
|
return appendArrayQuotedBytes(b, v), del, nil
|
||||||
|
case string:
|
||||||
|
return appendArrayQuotedBytes(b, []byte(v)), del, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = appendValue(b, iv)
|
||||||
|
return b, del, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendArrayQuotedBytes(b, v []byte) []byte {
|
||||||
|
b = append(b, '"')
|
||||||
|
for {
|
||||||
|
i := bytes.IndexAny(v, `"\`)
|
||||||
|
if i < 0 {
|
||||||
|
b = append(b, v...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, v[:i]...)
|
||||||
|
}
|
||||||
|
b = append(b, '\\', v[i])
|
||||||
|
v = v[i+1:]
|
||||||
|
}
|
||||||
|
return append(b, '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendValue(b []byte, v driver.Value) ([]byte, error) {
|
||||||
|
return append(b, encode(nil, v, 0)...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseArray extracts the dimensions and elements of an array represented in
|
||||||
|
// text format. Only representations emitted by the backend are supported.
|
||||||
|
// Notably, whitespace around brackets and delimiters is significant, and NULL
|
||||||
|
// is case-sensitive.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||||
|
func parseArray(src, del []byte) (dims []int, elems [][]byte, err error) {
|
||||||
|
var depth, i int
|
||||||
|
|
||||||
|
if len(src) < 1 || src[0] != '{' {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '{', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Open:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
depth++
|
||||||
|
i++
|
||||||
|
case '}':
|
||||||
|
elems = make([][]byte, 0)
|
||||||
|
goto Close
|
||||||
|
default:
|
||||||
|
break Open
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dims = make([]int, i)
|
||||||
|
|
||||||
|
Element:
|
||||||
|
for i < len(src) {
|
||||||
|
switch src[i] {
|
||||||
|
case '{':
|
||||||
|
if depth == len(dims) {
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
depth++
|
||||||
|
dims[depth-1] = 0
|
||||||
|
i++
|
||||||
|
case '"':
|
||||||
|
var elem = []byte{}
|
||||||
|
var escape bool
|
||||||
|
for i++; i < len(src); i++ {
|
||||||
|
if escape {
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
switch src[i] {
|
||||||
|
default:
|
||||||
|
elem = append(elem, src[i])
|
||||||
|
case '\\':
|
||||||
|
escape = true
|
||||||
|
case '"':
|
||||||
|
elems = append(elems, elem)
|
||||||
|
i++
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for start := i; i < len(src); i++ {
|
||||||
|
if bytes.HasPrefix(src[i:], del) || src[i] == '}' {
|
||||||
|
elem := src[start:i]
|
||||||
|
if len(elem) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
if bytes.Equal(elem, []byte("NULL")) {
|
||||||
|
elem = nil
|
||||||
|
}
|
||||||
|
elems = append(elems, elem)
|
||||||
|
break Element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i < len(src) {
|
||||||
|
if bytes.HasPrefix(src[i:], del) && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
i += len(del)
|
||||||
|
goto Element
|
||||||
|
} else if src[i] == '}' && depth > 0 {
|
||||||
|
dims[depth-1]++
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Close:
|
||||||
|
for i < len(src) {
|
||||||
|
if src[i] == '}' && depth > 0 {
|
||||||
|
depth--
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("pq: unable to parse array; unexpected %q at offset %d", src[i], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth > 0 {
|
||||||
|
err = fmt.Errorf("pq: unable to parse array; expected %q at offset %d", '}', i)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for _, d := range dims {
|
||||||
|
if (len(elems) % d) != 0 {
|
||||||
|
err = fmt.Errorf("pq: multidimensional arrays must have elements with matching dimensions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinearArray(src, del []byte, typ string) (elems [][]byte, err error) {
|
||||||
|
dims, elems, err := parseArray(src, del)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dims) > 1 {
|
||||||
|
return nil, fmt.Errorf("pq: cannot convert ARRAY%s to %s", strings.Replace(fmt.Sprint(dims), " ", "][", -1), typ)
|
||||||
|
}
|
||||||
|
return elems, err
|
||||||
|
}
|
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
91
vendor/github.com/lib/pq/buf.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readBuf []byte
|
||||||
|
|
||||||
|
func (b *readBuf) int32() (n int) {
|
||||||
|
n = int(int32(binary.BigEndian.Uint32(*b)))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) oid() (n oid.Oid) {
|
||||||
|
n = oid.Oid(binary.BigEndian.Uint32(*b))
|
||||||
|
*b = (*b)[4:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B: this is actually an unsigned 16-bit integer, unlike int32
|
||||||
|
func (b *readBuf) int16() (n int) {
|
||||||
|
n = int(binary.BigEndian.Uint16(*b))
|
||||||
|
*b = (*b)[2:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) string() string {
|
||||||
|
i := bytes.IndexByte(*b, 0)
|
||||||
|
if i < 0 {
|
||||||
|
errorf("invalid message format; expected string terminator")
|
||||||
|
}
|
||||||
|
s := (*b)[:i]
|
||||||
|
*b = (*b)[i+1:]
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) next(n int) (v []byte) {
|
||||||
|
v = (*b)[:n]
|
||||||
|
*b = (*b)[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) byte() byte {
|
||||||
|
return b.next(1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeBuf struct {
|
||||||
|
buf []byte
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int32(n int) {
|
||||||
|
x := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(x, uint32(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) int16(n int) {
|
||||||
|
x := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(x, uint16(n))
|
||||||
|
b.buf = append(b.buf, x...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) string(s string) {
|
||||||
|
b.buf = append(b.buf, (s + "\000")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) byte(c byte) {
|
||||||
|
b.buf = append(b.buf, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) bytes(v []byte) {
|
||||||
|
b.buf = append(b.buf, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) wrap() []byte {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
return b.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) next(c byte) {
|
||||||
|
p := b.buf[b.pos:]
|
||||||
|
binary.BigEndian.PutUint32(p, uint32(len(p)))
|
||||||
|
b.pos = len(b.buf) + 1
|
||||||
|
b.buf = append(b.buf, c, 0, 0, 0, 0)
|
||||||
|
}
|
1854
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
1854
vendor/github.com/lib/pq/conn.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
131
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
131
vendor/github.com/lib/pq/conn_go18.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implement the "QueryerContext" interface
|
||||||
|
func (cn *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
finish := cn.watchCancel(ctx)
|
||||||
|
r, err := cn.query(query, list)
|
||||||
|
if err != nil {
|
||||||
|
if finish != nil {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.finish = finish
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ExecerContext" interface
|
||||||
|
func (cn *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
list := make([]driver.Value, len(args))
|
||||||
|
for i, nv := range args {
|
||||||
|
list[i] = nv.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn.Exec(query, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the "ConnBeginTx" interface
|
||||||
|
func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
var mode string
|
||||||
|
|
||||||
|
switch sql.IsolationLevel(opts.Isolation) {
|
||||||
|
case sql.LevelDefault:
|
||||||
|
// Don't touch mode: use the server's default
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ UNCOMMITTED"
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
mode = " ISOLATION LEVEL READ COMMITTED"
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
mode = " ISOLATION LEVEL REPEATABLE READ"
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
mode = " ISOLATION LEVEL SERIALIZABLE"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("pq: isolation level not supported: %d", opts.Isolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ReadOnly {
|
||||||
|
mode += " READ ONLY"
|
||||||
|
} else {
|
||||||
|
mode += " READ WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := cn.begin(mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn.txnFinish = cn.watchCancel(ctx)
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
|
if done := ctx.Done(); done != nil {
|
||||||
|
finished := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
_ = cn.cancel()
|
||||||
|
finished <- struct{}{}
|
||||||
|
case <-finished:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
case finished <- struct{}{}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *conn) cancel() error {
|
||||||
|
c, err := dial(cn.dialer, cn.opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
{
|
||||||
|
can := conn{
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
err = can.ssl(cn.opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := can.writeBuf(0)
|
||||||
|
w.int32(80877102) // cancel request code
|
||||||
|
w.int32(cn.processID)
|
||||||
|
w.int32(cn.secretKey)
|
||||||
|
|
||||||
|
if err := can.sendStartupPacket(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read until EOF to ensure that the server received the cancel.
|
||||||
|
{
|
||||||
|
_, err := io.Copy(ioutil.Discard, c)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
43
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
43
vendor/github.com/lib/pq/connector.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector represents a fixed configuration for the pq driver with a given
|
||||||
|
// name. Connector satisfies the database/sql/driver Connector interface and
|
||||||
|
// can be used to create any number of DB Conn's via the database/sql OpenDB
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
type connector struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect returns a connection to the database using the fixed configuration
|
||||||
|
// of this Connector. Context is not used.
|
||||||
|
func (c *connector) Connect(_ context.Context) (driver.Conn, error) {
|
||||||
|
return (&Driver{}).Open(c.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver returnst the underlying driver of this Connector.
|
||||||
|
func (c *connector) Driver() driver.Driver {
|
||||||
|
return &Driver{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Connector = &connector{}
|
||||||
|
|
||||||
|
// NewConnector returns a connector for the pq driver in a fixed configuration
|
||||||
|
// with the given name. The returned connector can be used to create any number
|
||||||
|
// of equivalent Conn's. The returned connector is intended to be used with
|
||||||
|
// database/sql.OpenDB.
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
|
func NewConnector(name string) (driver.Connector, error) {
|
||||||
|
return &connector{name: name}, nil
|
||||||
|
}
|
282
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
282
vendor/github.com/lib/pq/copy.go
generated
vendored
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCopyInClosed = errors.New("pq: copyin statement has already been closed")
|
||||||
|
errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
|
||||||
|
errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
|
||||||
|
errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
|
||||||
|
errCopyInProgress = errors.New("pq: COPY in progress")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyIn creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare(). The target table should be visible in search_path.
|
||||||
|
func CopyIn(table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInSchema creates a COPY FROM statement which can be prepared with
|
||||||
|
// Tx.Prepare().
|
||||||
|
func CopyInSchema(schema, table string, columns ...string) string {
|
||||||
|
stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " ("
|
||||||
|
for i, col := range columns {
|
||||||
|
if i != 0 {
|
||||||
|
stmt += ", "
|
||||||
|
}
|
||||||
|
stmt += QuoteIdentifier(col)
|
||||||
|
}
|
||||||
|
stmt += ") FROM STDIN"
|
||||||
|
return stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
type copyin struct {
|
||||||
|
cn *conn
|
||||||
|
buffer []byte
|
||||||
|
rowData chan []byte
|
||||||
|
done chan bool
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
sync.Mutex // guards err
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciBufferSize = 64 * 1024
|
||||||
|
|
||||||
|
// flush buffer before the buffer is filled up and needs reallocation
|
||||||
|
const ciBufferFlushSize = 63 * 1024
|
||||||
|
|
||||||
|
func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
|
||||||
|
if !cn.isInTransaction() {
|
||||||
|
return nil, errCopyNotSupportedOutsideTxn
|
||||||
|
}
|
||||||
|
|
||||||
|
ci := ©in{
|
||||||
|
cn: cn,
|
||||||
|
buffer: make([]byte, 0, ciBufferSize),
|
||||||
|
rowData: make(chan []byte),
|
||||||
|
done: make(chan bool, 1),
|
||||||
|
}
|
||||||
|
// add CopyData identifier + 4 bytes for message length
|
||||||
|
ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
|
||||||
|
|
||||||
|
b := cn.writeBuf('Q')
|
||||||
|
b.string(q)
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
awaitCopyInResponse:
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'G':
|
||||||
|
if r.byte() != 0 {
|
||||||
|
err = errBinaryCopyNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
}
|
||||||
|
go ci.resploop()
|
||||||
|
return ci, nil
|
||||||
|
case 'H':
|
||||||
|
err = errCopyToNotSupported
|
||||||
|
break awaitCopyInResponse
|
||||||
|
case 'E':
|
||||||
|
err = parseError(r)
|
||||||
|
case 'Z':
|
||||||
|
if err == nil {
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unexpected ReadyForQuery in response to COPY")
|
||||||
|
}
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unknown response for copy query: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// something went wrong, abort COPY before we return
|
||||||
|
b = cn.writeBuf('f')
|
||||||
|
b.string(err.Error())
|
||||||
|
cn.send(b)
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, r := cn.recv1()
|
||||||
|
switch t {
|
||||||
|
case 'c', 'C', 'E':
|
||||||
|
case 'Z':
|
||||||
|
// correctly aborted, we're done
|
||||||
|
cn.processReadyForQuery(r)
|
||||||
|
return nil, err
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
errorf("unknown response for CopyFail: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) flush(buf []byte) {
|
||||||
|
// set message length (without message identifier)
|
||||||
|
binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
|
||||||
|
|
||||||
|
_, err := ci.cn.c.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) resploop() {
|
||||||
|
for {
|
||||||
|
var r readBuf
|
||||||
|
t, err := ci.cn.recvMessage(&r)
|
||||||
|
if err != nil {
|
||||||
|
ci.setBad()
|
||||||
|
ci.setError(err)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case 'C':
|
||||||
|
// complete
|
||||||
|
case 'N':
|
||||||
|
// NoticeResponse
|
||||||
|
case 'Z':
|
||||||
|
ci.cn.processReadyForQuery(&r)
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
case 'E':
|
||||||
|
err := parseError(&r)
|
||||||
|
ci.setError(err)
|
||||||
|
default:
|
||||||
|
ci.setBad()
|
||||||
|
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||||
|
ci.done <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) setBad() {
|
||||||
|
ci.Lock()
|
||||||
|
ci.cn.bad = true
|
||||||
|
ci.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) isBad() bool {
|
||||||
|
ci.Lock()
|
||||||
|
b := ci.cn.bad
|
||||||
|
ci.Unlock()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) isErrorSet() bool {
|
||||||
|
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) {
|
||||||
|
ci.Lock()
|
||||||
|
if ci.err == nil {
|
||||||
|
ci.err = err
|
||||||
|
}
|
||||||
|
ci.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) NumInput() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec inserts values into the COPY stream. The insert is asynchronous
|
||||||
|
// and Exec can return errors from previous Exec calls to the same
|
||||||
|
// COPY stmt.
|
||||||
|
//
|
||||||
|
// You need to call Exec(nil) to sync the COPY stream and to get any
|
||||||
|
// errors from pending data, since Stmt.Close() doesn't return errors
|
||||||
|
// to the user.
|
||||||
|
func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
|
||||||
|
if ci.closed {
|
||||||
|
return nil, errCopyInClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if ci.isBad() {
|
||||||
|
return nil, driver.ErrBadConn
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if ci.isErrorSet() {
|
||||||
|
return nil, ci.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil, ci.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
numValues := len(v)
|
||||||
|
for i, value := range v {
|
||||||
|
ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value)
|
||||||
|
if i < numValues-1 {
|
||||||
|
ci.buffer = append(ci.buffer, '\t')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.buffer = append(ci.buffer, '\n')
|
||||||
|
|
||||||
|
if len(ci.buffer) > ciBufferFlushSize {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
// reset buffer, keep bytes for message identifier and length
|
||||||
|
ci.buffer = ci.buffer[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.RowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *copyin) Close() (err error) {
|
||||||
|
if ci.closed { // Don't do anything, we're already closed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ci.closed = true
|
||||||
|
|
||||||
|
if ci.isBad() {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
defer ci.cn.errRecover(&err)
|
||||||
|
|
||||||
|
if len(ci.buffer) > 0 {
|
||||||
|
ci.flush(ci.buffer)
|
||||||
|
}
|
||||||
|
// Avoid touching the scratch buffer as resploop could be using it.
|
||||||
|
err = ci.cn.sendSimpleMessage('c')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ci.done
|
||||||
|
ci.cn.inCopy = false
|
||||||
|
|
||||||
|
if ci.isErrorSet() {
|
||||||
|
err = ci.err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
245
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
245
vendor/github.com/lib/pq/doc.go
generated
vendored
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
In most cases clients will use the database/sql package instead of
|
||||||
|
using this package directly. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
age := 21
|
||||||
|
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
|
||||||
|
…
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also connect to a database using a URL. For example:
|
||||||
|
|
||||||
|
connStr := "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full"
|
||||||
|
db, err := sql.Open("postgres", connStr)
|
||||||
|
|
||||||
|
|
||||||
|
Connection String Parameters
|
||||||
|
|
||||||
|
|
||||||
|
Similarly to libpq, when establishing a connection using pq you are expected to
|
||||||
|
supply a connection string containing zero or more parameters.
|
||||||
|
A subset of the connection parameters supported by libpq are also supported by pq.
|
||||||
|
Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem)
|
||||||
|
directly in the connection string. This is different from libpq, which does not allow
|
||||||
|
run-time parameters in the connection string, instead requiring you to supply
|
||||||
|
them in the options parameter.
|
||||||
|
|
||||||
|
For compatibility with libpq, the following special connection parameters are
|
||||||
|
supported:
|
||||||
|
|
||||||
|
* dbname - The name of the database to connect to
|
||||||
|
* user - The user to sign in as
|
||||||
|
* password - The user's password
|
||||||
|
* host - The host to connect to. Values that start with / are for unix
|
||||||
|
domain sockets. (default is localhost)
|
||||||
|
* port - The port to bind to. (default is 5432)
|
||||||
|
* sslmode - Whether or not to use SSL (default is require, this is not
|
||||||
|
the default for libpq)
|
||||||
|
* fallback_application_name - An application_name to fall back to if one isn't provided.
|
||||||
|
* connect_timeout - Maximum wait for connection, in seconds. Zero or
|
||||||
|
not specified means wait indefinitely.
|
||||||
|
* sslcert - Cert file location. The file must contain PEM encoded data.
|
||||||
|
* sslkey - Key file location. The file must contain PEM encoded data.
|
||||||
|
* sslrootcert - The location of the root certificate file. The file
|
||||||
|
must contain PEM encoded data.
|
||||||
|
|
||||||
|
Valid values for sslmode are:
|
||||||
|
|
||||||
|
* disable - No SSL
|
||||||
|
* require - Always SSL (skip verification)
|
||||||
|
* verify-ca - Always SSL (verify that the certificate presented by the
|
||||||
|
server was signed by a trusted CA)
|
||||||
|
* verify-full - Always SSL (verify that the certification presented by
|
||||||
|
the server was signed by a trusted CA and the server host name
|
||||||
|
matches the one in the certificate)
|
||||||
|
|
||||||
|
See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
|
for more information about connection string parameters.
|
||||||
|
|
||||||
|
Use single quotes for values that contain whitespace:
|
||||||
|
|
||||||
|
"user=pqgotest password='with spaces'"
|
||||||
|
|
||||||
|
A backslash will escape the next character in values:
|
||||||
|
|
||||||
|
"user=space\ man password='it\'s valid'"
|
||||||
|
|
||||||
|
Note that the connection parameter client_encoding (which sets the
|
||||||
|
text encoding for the connection) may be set but must be "UTF8",
|
||||||
|
matching with the same rules as Postgres. It is an error to provide
|
||||||
|
any other value.
|
||||||
|
|
||||||
|
In addition to the parameters listed above, any run-time parameter that can be
|
||||||
|
set at backend start time can be set in the connection string. For more
|
||||||
|
information, see
|
||||||
|
http://www.postgresql.org/docs/current/static/runtime-config.html.
|
||||||
|
|
||||||
|
Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html
|
||||||
|
supported by libpq are also supported by pq. If any of the environment
|
||||||
|
variables not supported by pq are set, pq will panic during connection
|
||||||
|
establishment. Environment variables have a lower precedence than explicitly
|
||||||
|
provided connection parameters.
|
||||||
|
|
||||||
|
The pgpass mechanism as described in http://www.postgresql.org/docs/current/static/libpq-pgpass.html
|
||||||
|
is supported, but on Windows PGPASSFILE must be specified explicitly.
|
||||||
|
|
||||||
|
|
||||||
|
Queries
|
||||||
|
|
||||||
|
|
||||||
|
database/sql does not dictate any specific format for parameter
|
||||||
|
markers in query strings, and pq uses the Postgres-native ordinal markers,
|
||||||
|
as shown above. The same marker can be reused for the same parameter:
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1
|
||||||
|
OR age BETWEEN $2 AND $2 + 3`, "orange", 64)
|
||||||
|
|
||||||
|
pq does not support the LastInsertId() method of the Result type in database/sql.
|
||||||
|
To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres
|
||||||
|
RETURNING clause with a standard Query or QueryRow call:
|
||||||
|
|
||||||
|
var userid int
|
||||||
|
err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age)
|
||||||
|
VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid)
|
||||||
|
|
||||||
|
For more details on RETURNING, see the Postgres documentation:
|
||||||
|
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-insert.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-update.html
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-delete.html
|
||||||
|
|
||||||
|
For additional instructions on querying see the documentation for the database/sql package.
|
||||||
|
|
||||||
|
|
||||||
|
Data Types
|
||||||
|
|
||||||
|
|
||||||
|
Parameters pass through driver.DefaultParameterConverter before they are handled
|
||||||
|
by this package. When the binary_parameters connection option is enabled,
|
||||||
|
[]byte values are sent directly to the backend as data in binary format.
|
||||||
|
|
||||||
|
This package returns the following types for values from the PostgreSQL backend:
|
||||||
|
|
||||||
|
- integer types smallint, integer, and bigint are returned as int64
|
||||||
|
- floating-point types real and double precision are returned as float64
|
||||||
|
- character types char, varchar, and text are returned as string
|
||||||
|
- temporal types date, time, timetz, timestamp, and timestamptz are
|
||||||
|
returned as time.Time
|
||||||
|
- the boolean type is returned as bool
|
||||||
|
- the bytea type is returned as []byte
|
||||||
|
|
||||||
|
All other types are returned directly from the backend as []byte values in text format.
|
||||||
|
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
|
||||||
|
pq may return errors of type *pq.Error which can be interrogated for error details:
|
||||||
|
|
||||||
|
if err, ok := err.(*pq.Error); ok {
|
||||||
|
fmt.Println("pq error:", err.Code.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
See the pq.Error type for details.
|
||||||
|
|
||||||
|
|
||||||
|
Bulk imports
|
||||||
|
|
||||||
|
You can perform bulk imports by preparing a statement returned by pq.CopyIn (or
|
||||||
|
pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement
|
||||||
|
handle can then be repeatedly "executed" to copy data into the target table.
|
||||||
|
After all data has been processed you should call Exec() once with no arguments
|
||||||
|
to flush all buffered data. Any call to Exec() might return an error which
|
||||||
|
should be handled appropriately, but because of the internal buffering an error
|
||||||
|
returned by Exec() might not be related to the data passed in the call that
|
||||||
|
failed.
|
||||||
|
|
||||||
|
CopyIn uses COPY FROM internally. It is not possible to COPY outside of an
|
||||||
|
explicit transaction in pq.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
txn, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
_, err = stmt.Exec(user.Name, int64(user.Age))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = txn.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Notifications
|
||||||
|
|
||||||
|
|
||||||
|
PostgreSQL supports a simple publish/subscribe model over database
|
||||||
|
connections. See http://www.postgresql.org/docs/current/static/sql-notify.html
|
||||||
|
for more information about the general mechanism.
|
||||||
|
|
||||||
|
To start listening for notifications, you first have to open a new connection
|
||||||
|
to the database by calling NewListener. This connection can not be used for
|
||||||
|
anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
|
||||||
|
channel"; once a notification channel is open, a notification generated on that
|
||||||
|
channel will effect a send on the Listener.Notify channel. A notification
|
||||||
|
channel will remain open until Unlisten is called, though connection loss might
|
||||||
|
result in some notifications being lost. To solve this problem, Listener sends
|
||||||
|
a nil pointer over the Notify channel any time the connection is re-established
|
||||||
|
following a connection loss. The application can get information about the
|
||||||
|
state of the underlying connection by setting an event callback in the call to
|
||||||
|
NewListener.
|
||||||
|
|
||||||
|
A single Listener can safely be used from concurrent goroutines, which means
|
||||||
|
that there is often no need to create more than one Listener in your
|
||||||
|
application. However, a Listener is always connected to a single database, so
|
||||||
|
you will need to create a new Listener instance for every database you want to
|
||||||
|
receive notifications in.
|
||||||
|
|
||||||
|
The channel name in both Listen and Unlisten is case sensitive, and can contain
|
||||||
|
any characters legal in an identifier (see
|
||||||
|
http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||||
|
for more information). Note that the channel name will be truncated to 63
|
||||||
|
bytes by the PostgreSQL server.
|
||||||
|
|
||||||
|
You can find a complete, working example of Listener usage at
|
||||||
|
http://godoc.org/github.com/lib/pq/example/listen.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package pq
|
603
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
603
vendor/github.com/lib/pq/encode.go
generated
vendored
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func binaryEncode(parameterStatus *parameterStatus, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case []byte:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return encode(parameterStatus, x, oid.T_unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(nil, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(nil, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
case string:
|
||||||
|
if pgtypOid == oid.T_bytea {
|
||||||
|
return encodeBytea(parameterStatus.serverVersion, []byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(nil, v)
|
||||||
|
case time.Time:
|
||||||
|
return formatTs(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid, f format) interface{} {
|
||||||
|
switch f {
|
||||||
|
case formatBinary:
|
||||||
|
return binaryDecode(parameterStatus, s, typ)
|
||||||
|
case formatText:
|
||||||
|
return textDecode(parameterStatus, s, typ)
|
||||||
|
default:
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func binaryDecode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
|
||||||
|
switch typ {
|
||||||
|
case oid.T_bytea:
|
||||||
|
return s
|
||||||
|
case oid.T_int8:
|
||||||
|
return int64(binary.BigEndian.Uint64(s))
|
||||||
|
case oid.T_int4:
|
||||||
|
return int64(int32(binary.BigEndian.Uint32(s)))
|
||||||
|
case oid.T_int2:
|
||||||
|
return int64(int16(binary.BigEndian.Uint16(s)))
|
||||||
|
case oid.T_uuid:
|
||||||
|
b, err := decodeUUIDBinary(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorf("don't know how to decode binary parameter of type %d", uint32(typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
return parseTs(nil, string(s))
|
||||||
|
case oid.T_time:
|
||||||
|
return mustParse("15:04:05", typ, s)
|
||||||
|
case oid.T_timetz:
|
||||||
|
return mustParse("15:04:05-07", typ, s)
|
||||||
|
case oid.T_bool:
|
||||||
|
return s[0] == 't'
|
||||||
|
case oid.T_int8, oid.T_int4, oid.T_int2:
|
||||||
|
i, err := strconv.ParseInt(string(s), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
case oid.T_float4, oid.T_float8:
|
||||||
|
bits := 64
|
||||||
|
if typ == oid.T_float4 {
|
||||||
|
bits = 32
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(string(s), bits)
|
||||||
|
if err != nil {
|
||||||
|
errorf("%s", err)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendEncodedText encodes item in text format as required by COPY
|
||||||
|
// and appends to buf
|
||||||
|
func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte {
|
||||||
|
switch v := x.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.AppendInt(buf, v, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.AppendFloat(buf, v, 'f', -1, 64)
|
||||||
|
case []byte:
|
||||||
|
encodedBytea := encodeBytea(parameterStatus.serverVersion, v)
|
||||||
|
return appendEscapedText(buf, string(encodedBytea))
|
||||||
|
case string:
|
||||||
|
return appendEscapedText(buf, v)
|
||||||
|
case bool:
|
||||||
|
return strconv.AppendBool(buf, v)
|
||||||
|
case time.Time:
|
||||||
|
return append(buf, formatTs(v)...)
|
||||||
|
case nil:
|
||||||
|
return append(buf, "\\N"...)
|
||||||
|
default:
|
||||||
|
errorf("encode: unknown type for %T", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendEscapedText(buf []byte, text string) []byte {
|
||||||
|
escapeNeeded := false
|
||||||
|
startPos := 0
|
||||||
|
var c byte
|
||||||
|
|
||||||
|
// check if we need to escape
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
|
||||||
|
escapeNeeded = true
|
||||||
|
startPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !escapeNeeded {
|
||||||
|
return append(buf, text...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy till first char to escape, iterate the rest
|
||||||
|
result := append(buf, text[:startPos]...)
|
||||||
|
for i := startPos; i < len(text); i++ {
|
||||||
|
c = text[i]
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
case '\n':
|
||||||
|
result = append(result, '\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
result = append(result, '\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
result = append(result, '\\', 't')
|
||||||
|
default:
|
||||||
|
result = append(result, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParse(f string, typ oid.Oid, s []byte) time.Time {
|
||||||
|
str := string(s)
|
||||||
|
|
||||||
|
// check for a 30-minute-offset timezone
|
||||||
|
if (typ == oid.T_timestamptz || typ == oid.T_timetz) &&
|
||||||
|
str[len(str)-3] == ':' {
|
||||||
|
f += ":00"
|
||||||
|
}
|
||||||
|
t, err := time.Parse(f, str)
|
||||||
|
if err != nil {
|
||||||
|
errorf("decode: %s", err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidTimestamp = errors.New("invalid timestamp")
|
||||||
|
|
||||||
|
type timestampParser struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) expect(str string, char byte, pos int) {
|
||||||
|
if p.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pos+1 > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := str[pos]; c != char && p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected '%v' at position %v; got '%v'", char, pos, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *timestampParser) mustAtoi(str string, begin int, end int) int {
|
||||||
|
if p.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if begin < 0 || end < 0 || begin > end || end > len(str) {
|
||||||
|
p.err = errInvalidTimestamp
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
result, err := strconv.Atoi(str[begin:end])
|
||||||
|
if err != nil {
|
||||||
|
if p.err == nil {
|
||||||
|
p.err = fmt.Errorf("expected number; got '%v'", str)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The location cache caches the time zones typically used by the client.
|
||||||
|
type locationCache struct {
|
||||||
|
cache map[int]*time.Location
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// All connections share the same list of timezones. Benchmarking shows that
|
||||||
|
// 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 = newLocationCache()
|
||||||
|
|
||||||
|
func newLocationCache() *locationCache {
|
||||||
|
return &locationCache{cache: make(map[int]*time.Location)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the cached timezone for the specified offset, creating and caching
|
||||||
|
// it if necessary.
|
||||||
|
func (c *locationCache) getLocation(offset int) *time.Location {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
location, ok := c.cache[offset]
|
||||||
|
if !ok {
|
||||||
|
location = time.FixedZone("", offset)
|
||||||
|
c.cache[offset] = location
|
||||||
|
}
|
||||||
|
|
||||||
|
return location
|
||||||
|
}
|
||||||
|
|
||||||
|
var infinityTsEnabled = false
|
||||||
|
var infinityTsNegative time.Time
|
||||||
|
var infinityTsPositive time.Time
|
||||||
|
|
||||||
|
const (
|
||||||
|
infinityTsEnabledAlready = "pq: infinity timestamp enabled already"
|
||||||
|
infinityTsNegativeMustBeSmaller = "pq: infinity timestamp: negative value must be smaller (before) than positive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
if !negative.Before(positive) {
|
||||||
|
panic(infinityTsNegativeMustBeSmaller)
|
||||||
|
}
|
||||||
|
infinityTsEnabled = true
|
||||||
|
infinityTsNegative = negative
|
||||||
|
infinityTsPositive = positive
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Testing might want to toggle infinityTsEnabled
|
||||||
|
*/
|
||||||
|
func disableInfinityTs() {
|
||||||
|
infinityTsEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a time function specific to the Postgres default DateStyle
|
||||||
|
// setting ("ISO, MDY"), the only one we currently support. This
|
||||||
|
// accounts for the discrepancies between the parsing available with
|
||||||
|
// time.Parse and the Postgres date formatting quirks.
|
||||||
|
func parseTs(currentLocation *time.Location, str string) interface{} {
|
||||||
|
switch str {
|
||||||
|
case "-infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsNegative
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
case "infinity":
|
||||||
|
if infinityTsEnabled {
|
||||||
|
return infinityTsPositive
|
||||||
|
}
|
||||||
|
return []byte(str)
|
||||||
|
}
|
||||||
|
t, err := ParseTimestamp(currentLocation, str)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
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{}
|
||||||
|
|
||||||
|
monSep := strings.IndexRune(str, '-')
|
||||||
|
// this is Gregorian year, not ISO Year
|
||||||
|
// In Gregorian system, the year 1 BC is followed by AD 1
|
||||||
|
year := p.mustAtoi(str, 0, monSep)
|
||||||
|
daySep := monSep + 3
|
||||||
|
month := p.mustAtoi(str, monSep+1, daySep)
|
||||||
|
p.expect(str, '-', daySep)
|
||||||
|
timeSep := daySep + 3
|
||||||
|
day := p.mustAtoi(str, daySep+1, timeSep)
|
||||||
|
|
||||||
|
minLen := monSep + len("01-01") + 1
|
||||||
|
|
||||||
|
isBC := strings.HasSuffix(str, " BC")
|
||||||
|
if isBC {
|
||||||
|
minLen += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
var hour, minute, second int
|
||||||
|
if len(str) > minLen {
|
||||||
|
p.expect(str, ' ', timeSep)
|
||||||
|
minSep := timeSep + 3
|
||||||
|
p.expect(str, ':', minSep)
|
||||||
|
hour = p.mustAtoi(str, timeSep+1, minSep)
|
||||||
|
secSep := minSep + 3
|
||||||
|
p.expect(str, ':', secSep)
|
||||||
|
minute = p.mustAtoi(str, minSep+1, secSep)
|
||||||
|
secEnd := secSep + 3
|
||||||
|
second = p.mustAtoi(str, secSep+1, secEnd)
|
||||||
|
}
|
||||||
|
remainderIdx := monSep + len("01-01 00:00:00") + 1
|
||||||
|
// Three optional (but ordered) sections follow: the
|
||||||
|
// fractional seconds, the time zone offset, and the BC
|
||||||
|
// designation. We set them up here and adjust the other
|
||||||
|
// offsets if the preceding sections exist.
|
||||||
|
|
||||||
|
nanoSec := 0
|
||||||
|
tzOff := 0
|
||||||
|
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == '.' {
|
||||||
|
fracStart := remainderIdx + 1
|
||||||
|
fracOff := strings.IndexAny(str[fracStart:], "-+ ")
|
||||||
|
if fracOff < 0 {
|
||||||
|
fracOff = len(str) - fracStart
|
||||||
|
}
|
||||||
|
fracSec := p.mustAtoi(str, fracStart, fracStart+fracOff)
|
||||||
|
nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
|
||||||
|
|
||||||
|
remainderIdx += fracOff + 1
|
||||||
|
}
|
||||||
|
if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart] == '-' || str[tzStart] == '+') {
|
||||||
|
// time zone separator is always '-' or '+' (UTC is +00)
|
||||||
|
var tzSign int
|
||||||
|
switch c := str[tzStart]; c {
|
||||||
|
case '-':
|
||||||
|
tzSign = -1
|
||||||
|
case '+':
|
||||||
|
tzSign = +1
|
||||||
|
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 remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzMin = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) && str[remainderIdx] == ':' {
|
||||||
|
tzSec = p.mustAtoi(str, remainderIdx+1, remainderIdx+3)
|
||||||
|
remainderIdx += 3
|
||||||
|
}
|
||||||
|
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||||
|
}
|
||||||
|
var isoYear int
|
||||||
|
|
||||||
|
if isBC {
|
||||||
|
isoYear = 1 - year
|
||||||
|
remainderIdx += 3
|
||||||
|
} else {
|
||||||
|
isoYear = year
|
||||||
|
}
|
||||||
|
if remainderIdx < len(str) {
|
||||||
|
return time.Time{}, fmt.Errorf("expected end of input, got %v", str[remainderIdx:])
|
||||||
|
}
|
||||||
|
t := time.Date(isoYear, time.Month(month), day,
|
||||||
|
hour, minute, second, nanoSec,
|
||||||
|
globalLocationCache.getLocation(tzOff))
|
||||||
|
|
||||||
|
if currentLocation != nil {
|
||||||
|
// Set the location of the returned Time based on the session's
|
||||||
|
// TimeZone value, but only if the local time zone database agrees with
|
||||||
|
// the remote database on the offset.
|
||||||
|
lt := t.In(currentLocation)
|
||||||
|
_, newOff := lt.Zone()
|
||||||
|
if newOff == tzOff {
|
||||||
|
t = lt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatTs formats t into a format postgres understands.
|
||||||
|
func formatTs(t time.Time) []byte {
|
||||||
|
if infinityTsEnabled {
|
||||||
|
// t <= -infinity : ! (t > -infinity)
|
||||||
|
if !t.After(infinityTsNegative) {
|
||||||
|
return []byte("-infinity")
|
||||||
|
}
|
||||||
|
// t >= infinity : ! (!t < infinity)
|
||||||
|
if !t.Before(infinityTsPositive) {
|
||||||
|
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
|
||||||
|
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("2006-01-02 15:04:05.999999999Z07:00"))
|
||||||
|
|
||||||
|
_, offset := t.Zone()
|
||||||
|
offset = offset % 60
|
||||||
|
if offset != 0 {
|
||||||
|
// RFC3339Nano already printed the minus sign
|
||||||
|
if offset < 0 {
|
||||||
|
offset = -offset
|
||||||
|
}
|
||||||
|
|
||||||
|
b = append(b, ':')
|
||||||
|
if offset < 10 {
|
||||||
|
b = append(b, '0')
|
||||||
|
}
|
||||||
|
b = strconv.AppendInt(b, int64(offset), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
b = append(b, " BC"...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a bytea value received from the server. Both "hex" and the legacy
|
||||||
|
// "escape" format are supported.
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bytea_output = escape
|
||||||
|
for len(s) > 0 {
|
||||||
|
if s[0] == '\\' {
|
||||||
|
// escaped '\\'
|
||||||
|
if len(s) >= 2 && s[1] == '\\' {
|
||||||
|
result = append(result, '\\')
|
||||||
|
s = s[2:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// '\\' followed by an octal number
|
||||||
|
if len(s) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid bytea sequence %v", s)
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse bytea value: %s", err.Error())
|
||||||
|
}
|
||||||
|
result = append(result, byte(r))
|
||||||
|
s = s[4:]
|
||||||
|
} else {
|
||||||
|
// We hit an unescaped, raw byte. Try to read in as many as
|
||||||
|
// possible in one go.
|
||||||
|
i := bytes.IndexByte(s, '\\')
|
||||||
|
if i == -1 {
|
||||||
|
result = append(result, s...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result = append(result, s[:i]...)
|
||||||
|
s = s[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBytea(serverVersion int, v []byte) (result []byte) {
|
||||||
|
if serverVersion >= 90000 {
|
||||||
|
// Use the hex format if we know that the server supports it
|
||||||
|
result = make([]byte, 2+hex.EncodedLen(len(v)))
|
||||||
|
result[0] = '\\'
|
||||||
|
result[1] = 'x'
|
||||||
|
hex.Encode(result[2:], v)
|
||||||
|
} else {
|
||||||
|
// .. or resort to "escape"
|
||||||
|
for _, b := range v {
|
||||||
|
if b == '\\' {
|
||||||
|
result = append(result, '\\', '\\')
|
||||||
|
} else if b < 0x20 || b > 0x7e {
|
||||||
|
result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
|
||||||
|
} else {
|
||||||
|
result = append(result, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NullTime represents a time.Time that may be null. NullTime implements the
|
||||||
|
// sql.Scanner interface so it can be used as a scan destination, similar to
|
||||||
|
// sql.NullString.
|
||||||
|
type NullTime struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the Scanner interface.
|
||||||
|
func (nt *NullTime) Scan(value interface{}) error {
|
||||||
|
nt.Time, nt.Valid = value.(time.Time)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nt NullTime) Value() (driver.Value, error) {
|
||||||
|
if !nt.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nt.Time, nil
|
||||||
|
}
|
515
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
515
vendor/github.com/lib/pq/error.go
generated
vendored
Normal file
@ -0,0 +1,515 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error severities
|
||||||
|
const (
|
||||||
|
Efatal = "FATAL"
|
||||||
|
Epanic = "PANIC"
|
||||||
|
Ewarning = "WARNING"
|
||||||
|
Enotice = "NOTICE"
|
||||||
|
Edebug = "DEBUG"
|
||||||
|
Einfo = "INFO"
|
||||||
|
Elog = "LOG"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents an error communicating with the server.
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields
|
||||||
|
type Error struct {
|
||||||
|
Severity string
|
||||||
|
Code ErrorCode
|
||||||
|
Message string
|
||||||
|
Detail string
|
||||||
|
Hint string
|
||||||
|
Position string
|
||||||
|
InternalPosition string
|
||||||
|
InternalQuery string
|
||||||
|
Where string
|
||||||
|
Schema string
|
||||||
|
Table string
|
||||||
|
Column string
|
||||||
|
DataTypeName string
|
||||||
|
Constraint string
|
||||||
|
File string
|
||||||
|
Line string
|
||||||
|
Routine string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode is a five-character error code.
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
// Name returns a more human friendly rendering of the error code, namely the
|
||||||
|
// "condition name".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Name() string {
|
||||||
|
return errorCodeNames[ec]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorClass is only the class part of an error code.
|
||||||
|
type ErrorClass string
|
||||||
|
|
||||||
|
// Name returns the condition name of an error class. It is equivalent to the
|
||||||
|
// condition name of the "standard" error code (i.e. the one having the last
|
||||||
|
// three characters "000").
|
||||||
|
func (ec ErrorClass) Name() string {
|
||||||
|
return errorCodeNames[ErrorCode(ec+"000")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class returns the error class, e.g. "28".
|
||||||
|
//
|
||||||
|
// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
|
||||||
|
// details.
|
||||||
|
func (ec ErrorCode) Class() ErrorClass {
|
||||||
|
return ErrorClass(ec[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorCodeNames is a mapping between the five-character error codes and the
|
||||||
|
// human readable "condition names". It is derived from the list at
|
||||||
|
// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
|
||||||
|
var errorCodeNames = map[ErrorCode]string{
|
||||||
|
// Class 00 - Successful Completion
|
||||||
|
"00000": "successful_completion",
|
||||||
|
// Class 01 - Warning
|
||||||
|
"01000": "warning",
|
||||||
|
"0100C": "dynamic_result_sets_returned",
|
||||||
|
"01008": "implicit_zero_bit_padding",
|
||||||
|
"01003": "null_value_eliminated_in_set_function",
|
||||||
|
"01007": "privilege_not_granted",
|
||||||
|
"01006": "privilege_not_revoked",
|
||||||
|
"01004": "string_data_right_truncation",
|
||||||
|
"01P01": "deprecated_feature",
|
||||||
|
// Class 02 - No Data (this is also a warning class per the SQL standard)
|
||||||
|
"02000": "no_data",
|
||||||
|
"02001": "no_additional_dynamic_result_sets_returned",
|
||||||
|
// Class 03 - SQL Statement Not Yet Complete
|
||||||
|
"03000": "sql_statement_not_yet_complete",
|
||||||
|
// Class 08 - Connection Exception
|
||||||
|
"08000": "connection_exception",
|
||||||
|
"08003": "connection_does_not_exist",
|
||||||
|
"08006": "connection_failure",
|
||||||
|
"08001": "sqlclient_unable_to_establish_sqlconnection",
|
||||||
|
"08004": "sqlserver_rejected_establishment_of_sqlconnection",
|
||||||
|
"08007": "transaction_resolution_unknown",
|
||||||
|
"08P01": "protocol_violation",
|
||||||
|
// Class 09 - Triggered Action Exception
|
||||||
|
"09000": "triggered_action_exception",
|
||||||
|
// Class 0A - Feature Not Supported
|
||||||
|
"0A000": "feature_not_supported",
|
||||||
|
// Class 0B - Invalid Transaction Initiation
|
||||||
|
"0B000": "invalid_transaction_initiation",
|
||||||
|
// Class 0F - Locator Exception
|
||||||
|
"0F000": "locator_exception",
|
||||||
|
"0F001": "invalid_locator_specification",
|
||||||
|
// Class 0L - Invalid Grantor
|
||||||
|
"0L000": "invalid_grantor",
|
||||||
|
"0LP01": "invalid_grant_operation",
|
||||||
|
// Class 0P - Invalid Role Specification
|
||||||
|
"0P000": "invalid_role_specification",
|
||||||
|
// Class 0Z - Diagnostics Exception
|
||||||
|
"0Z000": "diagnostics_exception",
|
||||||
|
"0Z002": "stacked_diagnostics_accessed_without_active_handler",
|
||||||
|
// Class 20 - Case Not Found
|
||||||
|
"20000": "case_not_found",
|
||||||
|
// Class 21 - Cardinality Violation
|
||||||
|
"21000": "cardinality_violation",
|
||||||
|
// Class 22 - Data Exception
|
||||||
|
"22000": "data_exception",
|
||||||
|
"2202E": "array_subscript_error",
|
||||||
|
"22021": "character_not_in_repertoire",
|
||||||
|
"22008": "datetime_field_overflow",
|
||||||
|
"22012": "division_by_zero",
|
||||||
|
"22005": "error_in_assignment",
|
||||||
|
"2200B": "escape_character_conflict",
|
||||||
|
"22022": "indicator_overflow",
|
||||||
|
"22015": "interval_field_overflow",
|
||||||
|
"2201E": "invalid_argument_for_logarithm",
|
||||||
|
"22014": "invalid_argument_for_ntile_function",
|
||||||
|
"22016": "invalid_argument_for_nth_value_function",
|
||||||
|
"2201F": "invalid_argument_for_power_function",
|
||||||
|
"2201G": "invalid_argument_for_width_bucket_function",
|
||||||
|
"22018": "invalid_character_value_for_cast",
|
||||||
|
"22007": "invalid_datetime_format",
|
||||||
|
"22019": "invalid_escape_character",
|
||||||
|
"2200D": "invalid_escape_octet",
|
||||||
|
"22025": "invalid_escape_sequence",
|
||||||
|
"22P06": "nonstandard_use_of_escape_character",
|
||||||
|
"22010": "invalid_indicator_parameter_value",
|
||||||
|
"22023": "invalid_parameter_value",
|
||||||
|
"2201B": "invalid_regular_expression",
|
||||||
|
"2201W": "invalid_row_count_in_limit_clause",
|
||||||
|
"2201X": "invalid_row_count_in_result_offset_clause",
|
||||||
|
"22009": "invalid_time_zone_displacement_value",
|
||||||
|
"2200C": "invalid_use_of_escape_character",
|
||||||
|
"2200G": "most_specific_type_mismatch",
|
||||||
|
"22004": "null_value_not_allowed",
|
||||||
|
"22002": "null_value_no_indicator_parameter",
|
||||||
|
"22003": "numeric_value_out_of_range",
|
||||||
|
"2200H": "sequence_generator_limit_exceeded",
|
||||||
|
"22026": "string_data_length_mismatch",
|
||||||
|
"22001": "string_data_right_truncation",
|
||||||
|
"22011": "substring_error",
|
||||||
|
"22027": "trim_error",
|
||||||
|
"22024": "unterminated_c_string",
|
||||||
|
"2200F": "zero_length_character_string",
|
||||||
|
"22P01": "floating_point_exception",
|
||||||
|
"22P02": "invalid_text_representation",
|
||||||
|
"22P03": "invalid_binary_representation",
|
||||||
|
"22P04": "bad_copy_file_format",
|
||||||
|
"22P05": "untranslatable_character",
|
||||||
|
"2200L": "not_an_xml_document",
|
||||||
|
"2200M": "invalid_xml_document",
|
||||||
|
"2200N": "invalid_xml_content",
|
||||||
|
"2200S": "invalid_xml_comment",
|
||||||
|
"2200T": "invalid_xml_processing_instruction",
|
||||||
|
// Class 23 - Integrity Constraint Violation
|
||||||
|
"23000": "integrity_constraint_violation",
|
||||||
|
"23001": "restrict_violation",
|
||||||
|
"23502": "not_null_violation",
|
||||||
|
"23503": "foreign_key_violation",
|
||||||
|
"23505": "unique_violation",
|
||||||
|
"23514": "check_violation",
|
||||||
|
"23P01": "exclusion_violation",
|
||||||
|
// Class 24 - Invalid Cursor State
|
||||||
|
"24000": "invalid_cursor_state",
|
||||||
|
// Class 25 - Invalid Transaction State
|
||||||
|
"25000": "invalid_transaction_state",
|
||||||
|
"25001": "active_sql_transaction",
|
||||||
|
"25002": "branch_transaction_already_active",
|
||||||
|
"25008": "held_cursor_requires_same_isolation_level",
|
||||||
|
"25003": "inappropriate_access_mode_for_branch_transaction",
|
||||||
|
"25004": "inappropriate_isolation_level_for_branch_transaction",
|
||||||
|
"25005": "no_active_sql_transaction_for_branch_transaction",
|
||||||
|
"25006": "read_only_sql_transaction",
|
||||||
|
"25007": "schema_and_data_statement_mixing_not_supported",
|
||||||
|
"25P01": "no_active_sql_transaction",
|
||||||
|
"25P02": "in_failed_sql_transaction",
|
||||||
|
// Class 26 - Invalid SQL Statement Name
|
||||||
|
"26000": "invalid_sql_statement_name",
|
||||||
|
// Class 27 - Triggered Data Change Violation
|
||||||
|
"27000": "triggered_data_change_violation",
|
||||||
|
// Class 28 - Invalid Authorization Specification
|
||||||
|
"28000": "invalid_authorization_specification",
|
||||||
|
"28P01": "invalid_password",
|
||||||
|
// Class 2B - Dependent Privilege Descriptors Still Exist
|
||||||
|
"2B000": "dependent_privilege_descriptors_still_exist",
|
||||||
|
"2BP01": "dependent_objects_still_exist",
|
||||||
|
// Class 2D - Invalid Transaction Termination
|
||||||
|
"2D000": "invalid_transaction_termination",
|
||||||
|
// Class 2F - SQL Routine Exception
|
||||||
|
"2F000": "sql_routine_exception",
|
||||||
|
"2F005": "function_executed_no_return_statement",
|
||||||
|
"2F002": "modifying_sql_data_not_permitted",
|
||||||
|
"2F003": "prohibited_sql_statement_attempted",
|
||||||
|
"2F004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 34 - Invalid Cursor Name
|
||||||
|
"34000": "invalid_cursor_name",
|
||||||
|
// Class 38 - External Routine Exception
|
||||||
|
"38000": "external_routine_exception",
|
||||||
|
"38001": "containing_sql_not_permitted",
|
||||||
|
"38002": "modifying_sql_data_not_permitted",
|
||||||
|
"38003": "prohibited_sql_statement_attempted",
|
||||||
|
"38004": "reading_sql_data_not_permitted",
|
||||||
|
// Class 39 - External Routine Invocation Exception
|
||||||
|
"39000": "external_routine_invocation_exception",
|
||||||
|
"39001": "invalid_sqlstate_returned",
|
||||||
|
"39004": "null_value_not_allowed",
|
||||||
|
"39P01": "trigger_protocol_violated",
|
||||||
|
"39P02": "srf_protocol_violated",
|
||||||
|
// Class 3B - Savepoint Exception
|
||||||
|
"3B000": "savepoint_exception",
|
||||||
|
"3B001": "invalid_savepoint_specification",
|
||||||
|
// Class 3D - Invalid Catalog Name
|
||||||
|
"3D000": "invalid_catalog_name",
|
||||||
|
// Class 3F - Invalid Schema Name
|
||||||
|
"3F000": "invalid_schema_name",
|
||||||
|
// Class 40 - Transaction Rollback
|
||||||
|
"40000": "transaction_rollback",
|
||||||
|
"40002": "transaction_integrity_constraint_violation",
|
||||||
|
"40001": "serialization_failure",
|
||||||
|
"40003": "statement_completion_unknown",
|
||||||
|
"40P01": "deadlock_detected",
|
||||||
|
// Class 42 - Syntax Error or Access Rule Violation
|
||||||
|
"42000": "syntax_error_or_access_rule_violation",
|
||||||
|
"42601": "syntax_error",
|
||||||
|
"42501": "insufficient_privilege",
|
||||||
|
"42846": "cannot_coerce",
|
||||||
|
"42803": "grouping_error",
|
||||||
|
"42P20": "windowing_error",
|
||||||
|
"42P19": "invalid_recursion",
|
||||||
|
"42830": "invalid_foreign_key",
|
||||||
|
"42602": "invalid_name",
|
||||||
|
"42622": "name_too_long",
|
||||||
|
"42939": "reserved_name",
|
||||||
|
"42804": "datatype_mismatch",
|
||||||
|
"42P18": "indeterminate_datatype",
|
||||||
|
"42P21": "collation_mismatch",
|
||||||
|
"42P22": "indeterminate_collation",
|
||||||
|
"42809": "wrong_object_type",
|
||||||
|
"42703": "undefined_column",
|
||||||
|
"42883": "undefined_function",
|
||||||
|
"42P01": "undefined_table",
|
||||||
|
"42P02": "undefined_parameter",
|
||||||
|
"42704": "undefined_object",
|
||||||
|
"42701": "duplicate_column",
|
||||||
|
"42P03": "duplicate_cursor",
|
||||||
|
"42P04": "duplicate_database",
|
||||||
|
"42723": "duplicate_function",
|
||||||
|
"42P05": "duplicate_prepared_statement",
|
||||||
|
"42P06": "duplicate_schema",
|
||||||
|
"42P07": "duplicate_table",
|
||||||
|
"42712": "duplicate_alias",
|
||||||
|
"42710": "duplicate_object",
|
||||||
|
"42702": "ambiguous_column",
|
||||||
|
"42725": "ambiguous_function",
|
||||||
|
"42P08": "ambiguous_parameter",
|
||||||
|
"42P09": "ambiguous_alias",
|
||||||
|
"42P10": "invalid_column_reference",
|
||||||
|
"42611": "invalid_column_definition",
|
||||||
|
"42P11": "invalid_cursor_definition",
|
||||||
|
"42P12": "invalid_database_definition",
|
||||||
|
"42P13": "invalid_function_definition",
|
||||||
|
"42P14": "invalid_prepared_statement_definition",
|
||||||
|
"42P15": "invalid_schema_definition",
|
||||||
|
"42P16": "invalid_table_definition",
|
||||||
|
"42P17": "invalid_object_definition",
|
||||||
|
// Class 44 - WITH CHECK OPTION Violation
|
||||||
|
"44000": "with_check_option_violation",
|
||||||
|
// Class 53 - Insufficient Resources
|
||||||
|
"53000": "insufficient_resources",
|
||||||
|
"53100": "disk_full",
|
||||||
|
"53200": "out_of_memory",
|
||||||
|
"53300": "too_many_connections",
|
||||||
|
"53400": "configuration_limit_exceeded",
|
||||||
|
// Class 54 - Program Limit Exceeded
|
||||||
|
"54000": "program_limit_exceeded",
|
||||||
|
"54001": "statement_too_complex",
|
||||||
|
"54011": "too_many_columns",
|
||||||
|
"54023": "too_many_arguments",
|
||||||
|
// Class 55 - Object Not In Prerequisite State
|
||||||
|
"55000": "object_not_in_prerequisite_state",
|
||||||
|
"55006": "object_in_use",
|
||||||
|
"55P02": "cant_change_runtime_param",
|
||||||
|
"55P03": "lock_not_available",
|
||||||
|
// Class 57 - Operator Intervention
|
||||||
|
"57000": "operator_intervention",
|
||||||
|
"57014": "query_canceled",
|
||||||
|
"57P01": "admin_shutdown",
|
||||||
|
"57P02": "crash_shutdown",
|
||||||
|
"57P03": "cannot_connect_now",
|
||||||
|
"57P04": "database_dropped",
|
||||||
|
// Class 58 - System Error (errors external to PostgreSQL itself)
|
||||||
|
"58000": "system_error",
|
||||||
|
"58030": "io_error",
|
||||||
|
"58P01": "undefined_file",
|
||||||
|
"58P02": "duplicate_file",
|
||||||
|
// Class F0 - Configuration File Error
|
||||||
|
"F0000": "config_file_error",
|
||||||
|
"F0001": "lock_file_exists",
|
||||||
|
// Class HV - Foreign Data Wrapper Error (SQL/MED)
|
||||||
|
"HV000": "fdw_error",
|
||||||
|
"HV005": "fdw_column_name_not_found",
|
||||||
|
"HV002": "fdw_dynamic_parameter_value_needed",
|
||||||
|
"HV010": "fdw_function_sequence_error",
|
||||||
|
"HV021": "fdw_inconsistent_descriptor_information",
|
||||||
|
"HV024": "fdw_invalid_attribute_value",
|
||||||
|
"HV007": "fdw_invalid_column_name",
|
||||||
|
"HV008": "fdw_invalid_column_number",
|
||||||
|
"HV004": "fdw_invalid_data_type",
|
||||||
|
"HV006": "fdw_invalid_data_type_descriptors",
|
||||||
|
"HV091": "fdw_invalid_descriptor_field_identifier",
|
||||||
|
"HV00B": "fdw_invalid_handle",
|
||||||
|
"HV00C": "fdw_invalid_option_index",
|
||||||
|
"HV00D": "fdw_invalid_option_name",
|
||||||
|
"HV090": "fdw_invalid_string_length_or_buffer_length",
|
||||||
|
"HV00A": "fdw_invalid_string_format",
|
||||||
|
"HV009": "fdw_invalid_use_of_null_pointer",
|
||||||
|
"HV014": "fdw_too_many_handles",
|
||||||
|
"HV001": "fdw_out_of_memory",
|
||||||
|
"HV00P": "fdw_no_schemas",
|
||||||
|
"HV00J": "fdw_option_name_not_found",
|
||||||
|
"HV00K": "fdw_reply_handle",
|
||||||
|
"HV00Q": "fdw_schema_not_found",
|
||||||
|
"HV00R": "fdw_table_not_found",
|
||||||
|
"HV00L": "fdw_unable_to_create_execution",
|
||||||
|
"HV00M": "fdw_unable_to_create_reply",
|
||||||
|
"HV00N": "fdw_unable_to_establish_connection",
|
||||||
|
// Class P0 - PL/pgSQL Error
|
||||||
|
"P0000": "plpgsql_error",
|
||||||
|
"P0001": "raise_exception",
|
||||||
|
"P0002": "no_data_found",
|
||||||
|
"P0003": "too_many_rows",
|
||||||
|
// Class XX - Internal Error
|
||||||
|
"XX000": "internal_error",
|
||||||
|
"XX001": "data_corrupted",
|
||||||
|
"XX002": "index_corrupted",
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseError(r *readBuf) *Error {
|
||||||
|
err := new(Error)
|
||||||
|
for t := r.byte(); t != 0; t = r.byte() {
|
||||||
|
msg := r.string()
|
||||||
|
switch t {
|
||||||
|
case 'S':
|
||||||
|
err.Severity = msg
|
||||||
|
case 'C':
|
||||||
|
err.Code = ErrorCode(msg)
|
||||||
|
case 'M':
|
||||||
|
err.Message = msg
|
||||||
|
case 'D':
|
||||||
|
err.Detail = msg
|
||||||
|
case 'H':
|
||||||
|
err.Hint = msg
|
||||||
|
case 'P':
|
||||||
|
err.Position = msg
|
||||||
|
case 'p':
|
||||||
|
err.InternalPosition = msg
|
||||||
|
case 'q':
|
||||||
|
err.InternalQuery = msg
|
||||||
|
case 'W':
|
||||||
|
err.Where = msg
|
||||||
|
case 's':
|
||||||
|
err.Schema = msg
|
||||||
|
case 't':
|
||||||
|
err.Table = msg
|
||||||
|
case 'c':
|
||||||
|
err.Column = msg
|
||||||
|
case 'd':
|
||||||
|
err.DataTypeName = msg
|
||||||
|
case 'n':
|
||||||
|
err.Constraint = msg
|
||||||
|
case 'F':
|
||||||
|
err.File = msg
|
||||||
|
case 'L':
|
||||||
|
err.Line = msg
|
||||||
|
case 'R':
|
||||||
|
err.Routine = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal returns true if the Error Severity is fatal.
|
||||||
|
func (err *Error) Fatal() bool {
|
||||||
|
return err.Severity == Efatal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements the legacy PGError interface. New code should use the fields
|
||||||
|
// of the Error struct directly.
|
||||||
|
func (err *Error) Get(k byte) (v string) {
|
||||||
|
switch k {
|
||||||
|
case 'S':
|
||||||
|
return err.Severity
|
||||||
|
case 'C':
|
||||||
|
return string(err.Code)
|
||||||
|
case 'M':
|
||||||
|
return err.Message
|
||||||
|
case 'D':
|
||||||
|
return err.Detail
|
||||||
|
case 'H':
|
||||||
|
return err.Hint
|
||||||
|
case 'P':
|
||||||
|
return err.Position
|
||||||
|
case 'p':
|
||||||
|
return err.InternalPosition
|
||||||
|
case 'q':
|
||||||
|
return err.InternalQuery
|
||||||
|
case 'W':
|
||||||
|
return err.Where
|
||||||
|
case 's':
|
||||||
|
return err.Schema
|
||||||
|
case 't':
|
||||||
|
return err.Table
|
||||||
|
case 'c':
|
||||||
|
return err.Column
|
||||||
|
case 'd':
|
||||||
|
return err.DataTypeName
|
||||||
|
case 'n':
|
||||||
|
return err.Constraint
|
||||||
|
case 'F':
|
||||||
|
return err.File
|
||||||
|
case 'L':
|
||||||
|
return err.Line
|
||||||
|
case 'R':
|
||||||
|
return err.Routine
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err Error) Error() string {
|
||||||
|
return "pq: " + err.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// PGError is an interface used by previous versions of pq. It is provided
|
||||||
|
// only to support legacy code. New code should use the Error type.
|
||||||
|
type PGError interface {
|
||||||
|
Error() string
|
||||||
|
Fatal() bool
|
||||||
|
Get(k byte) (v string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorf(s string, args ...interface{}) {
|
||||||
|
panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ainar-g) Rename to errorf after removing panics.
|
||||||
|
func fmterrorf(s string, args ...interface{}) error {
|
||||||
|
return fmt.Errorf("pq: %s", fmt.Sprintf(s, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRecoverNoErrBadConn(err *error) {
|
||||||
|
e := recover()
|
||||||
|
if e == nil {
|
||||||
|
// Do nothing
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
*err, ok = e.(error)
|
||||||
|
if !ok {
|
||||||
|
*err = fmt.Errorf("pq: unexpected error: %#v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) errRecover(err *error) {
|
||||||
|
e := recover()
|
||||||
|
switch v := e.(type) {
|
||||||
|
case nil:
|
||||||
|
// Do nothing
|
||||||
|
case runtime.Error:
|
||||||
|
c.bad = true
|
||||||
|
panic(v)
|
||||||
|
case *Error:
|
||||||
|
if v.Fatal() {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
case *net.OpError:
|
||||||
|
c.bad = true
|
||||||
|
*err = v
|
||||||
|
case error:
|
||||||
|
if v == io.EOF || v.(error).Error() == "remote error: handshake failure" {
|
||||||
|
*err = driver.ErrBadConn
|
||||||
|
} else {
|
||||||
|
*err = v
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
c.bad = true
|
||||||
|
panic(fmt.Sprintf("unknown error: %#v", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any time we return ErrBadConn, we need to remember it since *Tx doesn't
|
||||||
|
// mark the connection bad in database/sql.
|
||||||
|
if *err == driver.ErrBadConn {
|
||||||
|
c.bad = true
|
||||||
|
}
|
||||||
|
}
|
1
vendor/github.com/lib/pq/go.mod
generated
vendored
Normal file
1
vendor/github.com/lib/pq/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
module github.com/lib/pq
|
797
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
797
vendor/github.com/lib/pq/notify.go
generated
vendored
Normal file
@ -0,0 +1,797 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
// This module contains support for Postgres LISTEN/NOTIFY.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notification represents a single notification from the database.
|
||||||
|
type Notification struct {
|
||||||
|
// Process ID (PID) of the notifying postgres backend.
|
||||||
|
BePid int
|
||||||
|
// Name of the channel the notification was sent on.
|
||||||
|
Channel string
|
||||||
|
// Payload, or the empty string if unspecified.
|
||||||
|
Extra string
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvNotification(r *readBuf) *Notification {
|
||||||
|
bePid := r.int32()
|
||||||
|
channel := r.string()
|
||||||
|
extra := r.string()
|
||||||
|
|
||||||
|
return &Notification{bePid, channel, extra}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
connStateIdle int32 = iota
|
||||||
|
connStateExpectResponse
|
||||||
|
connStateExpectReadyForQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
typ byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
|
||||||
|
|
||||||
|
// ListenerConn is a low-level interface for waiting for notifications. You
|
||||||
|
// should use Listener instead.
|
||||||
|
type ListenerConn struct {
|
||||||
|
// guards cn and err
|
||||||
|
connectionLock sync.Mutex
|
||||||
|
cn *conn
|
||||||
|
err error
|
||||||
|
|
||||||
|
connState int32
|
||||||
|
|
||||||
|
// the sending goroutine will be holding this lock
|
||||||
|
senderLock sync.Mutex
|
||||||
|
|
||||||
|
notificationChan chan<- *Notification
|
||||||
|
|
||||||
|
replyChan chan message
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListenerConn creates a new ListenerConn. Use NewListener instead.
|
||||||
|
func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
return newDialListenerConn(defaultDialer{}, name, notificationChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDialListenerConn(d Dialer, name string, c chan<- *Notification) (*ListenerConn, error) {
|
||||||
|
cn, err := DialOpen(d, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &ListenerConn{
|
||||||
|
cn: cn.(*conn),
|
||||||
|
notificationChan: c,
|
||||||
|
connState: connStateIdle,
|
||||||
|
replyChan: make(chan message, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.listenerConnMain()
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only allow one goroutine at a time to be running a query on the
|
||||||
|
// connection for various reasons, so the goroutine sending on the connection
|
||||||
|
// must be holding senderLock.
|
||||||
|
//
|
||||||
|
// Returns an error if an unrecoverable error has occurred and the ListenerConn
|
||||||
|
// should be abandoned.
|
||||||
|
func (l *ListenerConn) acquireSenderLock() error {
|
||||||
|
// we must acquire senderLock first to avoid deadlocks; see ExecSimpleQuery
|
||||||
|
l.senderLock.Lock()
|
||||||
|
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListenerConn) releaseSenderLock() {
|
||||||
|
l.senderLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setState advances the protocol state to newState. Returns false if moving
|
||||||
|
// to that state from the current state is not allowed.
|
||||||
|
func (l *ListenerConn) setState(newState int32) bool {
|
||||||
|
var expectedState int32
|
||||||
|
|
||||||
|
switch newState {
|
||||||
|
case connStateIdle:
|
||||||
|
expectedState = connStateExpectReadyForQuery
|
||||||
|
case connStateExpectResponse:
|
||||||
|
expectedState = connStateIdle
|
||||||
|
case connStateExpectReadyForQuery:
|
||||||
|
expectedState = connStateExpectResponse
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
|
||||||
|
}
|
||||||
|
|
||||||
|
return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic is here: receive messages from the postgres backend, forward
|
||||||
|
// notifications and query replies and keep the internal state in sync with the
|
||||||
|
// protocol state. Returns when the connection has been lost, is about to go
|
||||||
|
// away or should be discarded because we couldn't agree on the state with the
|
||||||
|
// server backend.
|
||||||
|
func (l *ListenerConn) listenerConnLoop() (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
r := &readBuf{}
|
||||||
|
for {
|
||||||
|
t, err := l.cn.recvMessage(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case 'A':
|
||||||
|
// recvNotification copies all the data so we don't need to worry
|
||||||
|
// about the scratch buffer being overwritten.
|
||||||
|
l.notificationChan <- recvNotification(r)
|
||||||
|
|
||||||
|
case 'T', 'D':
|
||||||
|
// only used by tests; ignore
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// We might receive an ErrorResponse even when not in a query; it
|
||||||
|
// is expected that the server will close the connection after
|
||||||
|
// that, but we should make sure that the error we display is the
|
||||||
|
// one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
return parseError(r)
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, parseError(r)}
|
||||||
|
|
||||||
|
case 'C', 'I':
|
||||||
|
if !l.setState(connStateExpectReadyForQuery) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected CommandComplete")
|
||||||
|
}
|
||||||
|
// ExecSimpleQuery doesn't need to know about this message
|
||||||
|
|
||||||
|
case 'Z':
|
||||||
|
if !l.setState(connStateIdle) {
|
||||||
|
// protocol out of sync
|
||||||
|
return fmt.Errorf("unexpected ReadyForQuery")
|
||||||
|
}
|
||||||
|
l.replyChan <- message{t, nil}
|
||||||
|
|
||||||
|
case 'N', 'S':
|
||||||
|
// ignore
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the main routine for the goroutine receiving on the database
|
||||||
|
// connection. Most of the main logic is in listenerConnLoop.
|
||||||
|
func (l *ListenerConn) listenerConnMain() {
|
||||||
|
err := l.listenerConnLoop()
|
||||||
|
|
||||||
|
// listenerConnLoop terminated; we're done, but we still have to clean up.
|
||||||
|
// Make sure nobody tries to start any new queries by making sure the err
|
||||||
|
// pointer is set. It is important that we do not overwrite its value; a
|
||||||
|
// connection could be closed by either this goroutine or one sending on
|
||||||
|
// the connection -- whoever closes the connection is assumed to have the
|
||||||
|
// more meaningful error message (as the other one will probably get
|
||||||
|
// net.errClosed), so that goroutine sets the error we expose while the
|
||||||
|
// other error is discarded. If the connection is lost while two
|
||||||
|
// goroutines are operating on the socket, it probably doesn't matter which
|
||||||
|
// error we expose so we don't try to do anything more complex.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.cn.Close()
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
|
||||||
|
// There might be a query in-flight; make sure nobody's waiting for a
|
||||||
|
// response to it, since there's not going to be one.
|
||||||
|
close(l.replyChan)
|
||||||
|
|
||||||
|
// let the listener know we're done
|
||||||
|
close(l.notificationChan)
|
||||||
|
|
||||||
|
// this ListenerConn is done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen sends a LISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten sends an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll sends an `UNLISTEN *` query to the server. See ExecSimpleQuery.
|
||||||
|
func (l *ListenerConn) UnlistenAll() (bool, error) {
|
||||||
|
return l.ExecSimpleQuery("UNLISTEN *")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil error means the
|
||||||
|
// connection has failed and should be abandoned.
|
||||||
|
func (l *ListenerConn) Ping() error {
|
||||||
|
sent, err := l.ExecSimpleQuery("")
|
||||||
|
if !sent {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// shouldn't happen
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to send a query on the connection. Returns an error if sending the
|
||||||
|
// query failed, and the caller should initiate closure of this connection.
|
||||||
|
// The caller must be holding senderLock (see acquireSenderLock and
|
||||||
|
// releaseSenderLock).
|
||||||
|
func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
|
||||||
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
|
// must set connection state before sending the query
|
||||||
|
if !l.setState(connStateExpectResponse) {
|
||||||
|
panic("two queries running at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't use l.cn.writeBuf here because it uses the scratch buffer which
|
||||||
|
// might get overwritten by listenerConnLoop.
|
||||||
|
b := &writeBuf{
|
||||||
|
buf: []byte("Q\x00\x00\x00\x00"),
|
||||||
|
pos: 1,
|
||||||
|
}
|
||||||
|
b.string(q)
|
||||||
|
l.cn.send(b)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecSimpleQuery executes a "simple query" (i.e. one with no bindable
|
||||||
|
// parameters) on the connection. The possible return values are:
|
||||||
|
// 1) "executed" is true; the query was executed to completion on the
|
||||||
|
// database server. If the query failed, err will be set to the error
|
||||||
|
// returned by the database, otherwise err will be nil.
|
||||||
|
// 2) If "executed" is false, the query could not be executed on the remote
|
||||||
|
// server. err will be non-nil.
|
||||||
|
//
|
||||||
|
// After a call to ExecSimpleQuery has returned an executed=false value, the
|
||||||
|
// connection has either been closed or will be closed shortly thereafter, and
|
||||||
|
// all subsequently executed queries will return an error.
|
||||||
|
func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
|
||||||
|
if err = l.acquireSenderLock(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer l.releaseSenderLock()
|
||||||
|
|
||||||
|
err = l.sendSimpleQuery(q)
|
||||||
|
if err != nil {
|
||||||
|
// We can't know what state the protocol is in, so we need to abandon
|
||||||
|
// this connection.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
// Set the error pointer if it hasn't been set already; see
|
||||||
|
// listenerConnMain.
|
||||||
|
if l.err == nil {
|
||||||
|
l.err = err
|
||||||
|
}
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
l.cn.c.Close()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we just wait for a reply..
|
||||||
|
for {
|
||||||
|
m, ok := <-l.replyChan
|
||||||
|
if !ok {
|
||||||
|
// We lost the connection to server, don't bother waiting for a
|
||||||
|
// a response. err should have been set already.
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
err := l.err
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
switch m.typ {
|
||||||
|
case 'Z':
|
||||||
|
// sanity check
|
||||||
|
if m.err != nil {
|
||||||
|
panic("m.err != nil")
|
||||||
|
}
|
||||||
|
// done; err might or might not be set
|
||||||
|
return true, err
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// sanity check
|
||||||
|
if m.err == nil {
|
||||||
|
panic("m.err == nil")
|
||||||
|
}
|
||||||
|
// server responded with an error; ReadyForQuery to follow
|
||||||
|
err = m.err
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (l *ListenerConn) Close() error {
|
||||||
|
l.connectionLock.Lock()
|
||||||
|
if l.err != nil {
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
return errListenerConnClosed
|
||||||
|
}
|
||||||
|
l.err = errListenerConnClosed
|
||||||
|
l.connectionLock.Unlock()
|
||||||
|
// We can't send anything on the connection without holding senderLock.
|
||||||
|
// Simply close the net.Conn to wake up everyone operating on it.
|
||||||
|
return l.cn.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the reason the connection was closed. It is not safe to call
|
||||||
|
// this function until l.Notify has been closed.
|
||||||
|
func (l *ListenerConn) Err() error {
|
||||||
|
return l.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errListenerClosed = errors.New("pq: Listener has been closed")
|
||||||
|
|
||||||
|
// ErrChannelAlreadyOpen is returned from Listen when a channel is already
|
||||||
|
// open.
|
||||||
|
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||||
|
|
||||||
|
// ErrChannelNotOpen is returned from Unlisten when a channel is not open.
|
||||||
|
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||||
|
|
||||||
|
// ListenerEventType is an enumeration of listener event types.
|
||||||
|
type ListenerEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ListenerEventConnected is emitted only when the database connection
|
||||||
|
// has been initially initialized. The err argument of the callback
|
||||||
|
// will always be nil.
|
||||||
|
ListenerEventConnected ListenerEventType = iota
|
||||||
|
|
||||||
|
// ListenerEventDisconnected is emitted after a database connection has
|
||||||
|
// been lost, either because of an error or because Close has been
|
||||||
|
// called. The err argument will be set to the reason the database
|
||||||
|
// connection was lost.
|
||||||
|
ListenerEventDisconnected
|
||||||
|
|
||||||
|
// ListenerEventReconnected is emitted after a database connection has
|
||||||
|
// been re-established after connection loss. The err argument of the
|
||||||
|
// callback will always be nil. After this event has been emitted, a
|
||||||
|
// nil pq.Notification is sent on the Listener.Notify channel.
|
||||||
|
ListenerEventReconnected
|
||||||
|
|
||||||
|
// ListenerEventConnectionAttemptFailed is emitted after a connection
|
||||||
|
// to the database was attempted, but failed. The err argument will be
|
||||||
|
// set to an error describing why the connection attempt did not
|
||||||
|
// succeed.
|
||||||
|
ListenerEventConnectionAttemptFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventCallbackType is the event callback type. See also ListenerEventType
|
||||||
|
// constants' documentation.
|
||||||
|
type EventCallbackType func(event ListenerEventType, err error)
|
||||||
|
|
||||||
|
// Listener provides an interface for listening to notifications from a
|
||||||
|
// PostgreSQL database. For general usage information, see section
|
||||||
|
// "Notifications".
|
||||||
|
//
|
||||||
|
// Listener can safely be used from concurrently running goroutines.
|
||||||
|
type Listener struct {
|
||||||
|
// Channel for receiving notifications from the database. In some cases a
|
||||||
|
// nil value will be sent. See section "Notifications" above.
|
||||||
|
Notify chan *Notification
|
||||||
|
|
||||||
|
name string
|
||||||
|
minReconnectInterval time.Duration
|
||||||
|
maxReconnectInterval time.Duration
|
||||||
|
dialer Dialer
|
||||||
|
eventCallback EventCallbackType
|
||||||
|
|
||||||
|
lock sync.Mutex
|
||||||
|
isClosed bool
|
||||||
|
reconnectCond *sync.Cond
|
||||||
|
cn *ListenerConn
|
||||||
|
connNotificationChan <-chan *Notification
|
||||||
|
channels map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
|
||||||
|
//
|
||||||
|
// name should be set to a connection string to be used to establish the
|
||||||
|
// database connection (see section "Connection String Parameters" above).
|
||||||
|
//
|
||||||
|
// minReconnectInterval controls the duration to wait before trying to
|
||||||
|
// re-establish the database connection after connection loss. After each
|
||||||
|
// consecutive failure this interval is doubled, until maxReconnectInterval is
|
||||||
|
// reached. Successfully completing the connection establishment procedure
|
||||||
|
// resets the interval back to minReconnectInterval.
|
||||||
|
//
|
||||||
|
// The last parameter eventCallback can be set to a function which will be
|
||||||
|
// called by the Listener when the state of the underlying database connection
|
||||||
|
// changes. This callback will be called by the goroutine which dispatches the
|
||||||
|
// notifications over the Notify channel, so you should try to avoid doing
|
||||||
|
// potentially time-consuming operations from the callback.
|
||||||
|
func NewListener(name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
return NewDialListener(defaultDialer{}, name, minReconnectInterval, maxReconnectInterval, eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDialListener is like NewListener but it takes a Dialer.
|
||||||
|
func NewDialListener(d Dialer,
|
||||||
|
name string,
|
||||||
|
minReconnectInterval time.Duration,
|
||||||
|
maxReconnectInterval time.Duration,
|
||||||
|
eventCallback EventCallbackType) *Listener {
|
||||||
|
|
||||||
|
l := &Listener{
|
||||||
|
name: name,
|
||||||
|
minReconnectInterval: minReconnectInterval,
|
||||||
|
maxReconnectInterval: maxReconnectInterval,
|
||||||
|
dialer: d,
|
||||||
|
eventCallback: eventCallback,
|
||||||
|
|
||||||
|
channels: make(map[string]struct{}),
|
||||||
|
|
||||||
|
Notify: make(chan *Notification, 32),
|
||||||
|
}
|
||||||
|
l.reconnectCond = sync.NewCond(&l.lock)
|
||||||
|
|
||||||
|
go l.listenerMain()
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationChannel 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
|
||||||
|
// function will block until an acknowledgement has been received from the
|
||||||
|
// server. Note that Listener automatically re-establishes the connection
|
||||||
|
// after connection loss, so this function may block indefinitely if the
|
||||||
|
// connection can not be re-established.
|
||||||
|
//
|
||||||
|
// Listen will only fail in three conditions:
|
||||||
|
// 1) The channel is already open. The returned error will be
|
||||||
|
// ErrChannelAlreadyOpen.
|
||||||
|
// 2) The query was executed on the remote server, but PostgreSQL returned an
|
||||||
|
// error message in response to the query. The returned error will be a
|
||||||
|
// pq.Error containing the information the server supplied.
|
||||||
|
// 3) Close is called on the Listener before the request could be completed.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Listen(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// The server allows you to issue a LISTEN on a channel which is already
|
||||||
|
// open, but it seems useful to be able to detect this case to spot for
|
||||||
|
// mistakes in application logic. If the application genuinely does't
|
||||||
|
// care, it can check the exported error and ignore it.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if exists {
|
||||||
|
return ErrChannelAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// If gotResponse is true but error is set, the query was executed on
|
||||||
|
// the remote server, but resulted in an error. This should be
|
||||||
|
// relatively rare, so it's fine if we just pass the error to our
|
||||||
|
// caller. However, if gotResponse is false, we could not complete the
|
||||||
|
// query on the remote server and our underlying connection is about
|
||||||
|
// to go away, so we only add relname to l.channels, and wait for
|
||||||
|
// resync() to take care of the rest.
|
||||||
|
gotResponse, err := l.cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.channels[channel] = struct{}{}
|
||||||
|
for l.cn == nil {
|
||||||
|
l.reconnectCond.Wait()
|
||||||
|
// we let go of the mutex for a while
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlisten removes a channel from the Listener's channel list. Returns
|
||||||
|
// ErrChannelNotOpen if the Listener is not listening on the specified channel.
|
||||||
|
// Returns immediately with no error if there is no connection. Note that you
|
||||||
|
// might still get notifications for this channel even after Unlisten has
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// The channel name is case-sensitive.
|
||||||
|
func (l *Listener) Unlisten(channel string) error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly to LISTEN, this is not an error in Postgres, but it seems
|
||||||
|
// useful to distinguish from the normal conditions.
|
||||||
|
_, exists := l.channels[channel]
|
||||||
|
if !exists {
|
||||||
|
return ErrChannelNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.Unlisten(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
delete(l.channels, channel)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlistenAll removes all channels from the Listener's channel list. Returns
|
||||||
|
// immediately with no error if there is no connection. Note that you might
|
||||||
|
// still get notifications for any of the deleted channels even after
|
||||||
|
// UnlistenAll has returned.
|
||||||
|
func (l *Listener) UnlistenAll() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
// Similarly to Listen (see comment in that function), the caller
|
||||||
|
// should only be bothered with an error if it came from the backend as
|
||||||
|
// a response to our query.
|
||||||
|
gotResponse, err := l.cn.UnlistenAll()
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother waiting for resync if there's no connection.
|
||||||
|
l.channels = make(map[string]struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping the remote server to make sure it's alive. Non-nil return value means
|
||||||
|
// that there is no active connection.
|
||||||
|
func (l *Listener) Ping() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
if l.cn == nil {
|
||||||
|
return errors.New("no connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.cn.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after losing the server connection. Returns l.cn.Err(), which
|
||||||
|
// should have the reason the connection was lost.
|
||||||
|
func (l *Listener) disconnectCleanup() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
// sanity check; can't look at Err() until the channel has been closed
|
||||||
|
select {
|
||||||
|
case _, ok := <-l.connNotificationChan:
|
||||||
|
if ok {
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("connNotificationChan not closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.cn.Err()
|
||||||
|
l.cn.Close()
|
||||||
|
l.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the list of channels we want to be listening on with the server
|
||||||
|
// after the connection has been established.
|
||||||
|
func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
|
||||||
|
doneChan := make(chan error)
|
||||||
|
go func(notificationChan <-chan *Notification) {
|
||||||
|
for channel := range l.channels {
|
||||||
|
// If we got a response, return that error to our caller as it's
|
||||||
|
// going to be more descriptive than cn.Err().
|
||||||
|
gotResponse, err := cn.Listen(channel)
|
||||||
|
if gotResponse && err != nil {
|
||||||
|
doneChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't reach the server, wait for notificationChan to
|
||||||
|
// close and then return the error message from the connection, as
|
||||||
|
// per ListenerConn's interface.
|
||||||
|
if err != nil {
|
||||||
|
for range notificationChan {
|
||||||
|
}
|
||||||
|
doneChan <- cn.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneChan <- nil
|
||||||
|
}(notificationChan)
|
||||||
|
|
||||||
|
// Ignore notifications while synchronization is going on to avoid
|
||||||
|
// deadlocks. We have to send a nil notification over Notify anyway as
|
||||||
|
// we can't possibly know which notifications (if any) were lost while
|
||||||
|
// the connection was down, so there's no reason to try and process
|
||||||
|
// these messages at all.
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _, ok := <-notificationChan:
|
||||||
|
if !ok {
|
||||||
|
notificationChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case err := <-doneChan:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller should NOT be holding l.lock
|
||||||
|
func (l *Listener) closed() bool {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
return l.isClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) connect() error {
|
||||||
|
notificationChan := make(chan *Notification, 32)
|
||||||
|
cn, err := newDialListenerConn(l.dialer, l.name, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
err = l.resync(cn, notificationChan)
|
||||||
|
if err != nil {
|
||||||
|
cn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.cn = cn
|
||||||
|
l.connNotificationChan = notificationChan
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close disconnects the Listener from the database and shuts it down.
|
||||||
|
// Subsequent calls to its methods will return an error. Close returns an
|
||||||
|
// error if the connection has already been closed.
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.lock.Lock()
|
||||||
|
defer l.lock.Unlock()
|
||||||
|
|
||||||
|
if l.isClosed {
|
||||||
|
return errListenerClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.cn != nil {
|
||||||
|
l.cn.Close()
|
||||||
|
}
|
||||||
|
l.isClosed = true
|
||||||
|
|
||||||
|
// Unblock calls to Listen()
|
||||||
|
l.reconnectCond.Broadcast()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) emitEvent(event ListenerEventType, err error) {
|
||||||
|
if l.eventCallback != nil {
|
||||||
|
l.eventCallback(event, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic here: maintain a connection to the server when possible, wait
|
||||||
|
// for notifications and emit events.
|
||||||
|
func (l *Listener) listenerConnLoop() {
|
||||||
|
var nextReconnect time.Time
|
||||||
|
|
||||||
|
reconnectInterval := l.minReconnectInterval
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
err := l.connect()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventConnectionAttemptFailed, err)
|
||||||
|
|
||||||
|
time.Sleep(reconnectInterval)
|
||||||
|
reconnectInterval *= 2
|
||||||
|
if reconnectInterval > l.maxReconnectInterval {
|
||||||
|
reconnectInterval = l.maxReconnectInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextReconnect.IsZero() {
|
||||||
|
l.emitEvent(ListenerEventConnected, nil)
|
||||||
|
} else {
|
||||||
|
l.emitEvent(ListenerEventReconnected, nil)
|
||||||
|
l.Notify <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnectInterval = l.minReconnectInterval
|
||||||
|
nextReconnect = time.Now().Add(reconnectInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
notification, ok := <-l.connNotificationChan
|
||||||
|
if !ok {
|
||||||
|
// lost connection, loop again
|
||||||
|
break
|
||||||
|
}
|
||||||
|
l.Notify <- notification
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.disconnectCleanup()
|
||||||
|
if l.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.emitEvent(ListenerEventDisconnected, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Until(nextReconnect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) listenerMain() {
|
||||||
|
l.listenerConnLoop()
|
||||||
|
close(l.Notify)
|
||||||
|
}
|
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
6
vendor/github.com/lib/pq/oid/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Package oid contains OID constants
|
||||||
|
// as defined by the Postgres server.
|
||||||
|
package oid
|
||||||
|
|
||||||
|
// Oid is a Postgres Object ID.
|
||||||
|
type Oid uint32
|
93
vendor/github.com/lib/pq/oid/gen.go
generated
vendored
Normal file
93
vendor/github.com/lib/pq/oid/gen.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
// Generate the table of OID values
|
||||||
|
// Run with 'go run gen.go'.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OID represent a postgres Object Identifier Type.
|
||||||
|
type OID struct {
|
||||||
|
ID int
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns an upper case version of the oid type.
|
||||||
|
func (o OID) Name() string {
|
||||||
|
return strings.ToUpper(o.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
datname := os.Getenv("PGDATABASE")
|
||||||
|
sslmode := os.Getenv("PGSSLMODE")
|
||||||
|
|
||||||
|
if datname == "" {
|
||||||
|
os.Setenv("PGDATABASE", "pqgotest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslmode == "" {
|
||||||
|
os.Setenv("PGSSLMODE", "disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT typname, oid
|
||||||
|
FROM pg_type WHERE oid < 10000
|
||||||
|
ORDER BY oid;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
oids := make([]*OID, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var oid OID
|
||||||
|
if err = rows.Scan(&oid.Type, &oid.ID); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
oids = append(oids, &oid)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("gofmt")
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
w, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.Create("types.go")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd.Stdout = f
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, "// Code generated by gen.go. DO NOT EDIT.")
|
||||||
|
fmt.Fprintln(w, "\npackage oid")
|
||||||
|
fmt.Fprintln(w, "const (")
|
||||||
|
for _, oid := range oids {
|
||||||
|
fmt.Fprintf(w, "T_%s Oid = %d\n", oid.Type, oid.ID)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, ")")
|
||||||
|
fmt.Fprintln(w, "var TypeName = map[Oid]string{")
|
||||||
|
for _, oid := range oids {
|
||||||
|
fmt.Fprintf(w, "T_%s: \"%s\",\n", oid.Type, oid.Name())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, "}")
|
||||||
|
w.Close()
|
||||||
|
cmd.Wait()
|
||||||
|
}
|
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
343
vendor/github.com/lib/pq/oid/types.go
generated
vendored
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
// Code generated by gen.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
package oid
|
||||||
|
|
||||||
|
const (
|
||||||
|
T_bool Oid = 16
|
||||||
|
T_bytea Oid = 17
|
||||||
|
T_char Oid = 18
|
||||||
|
T_name Oid = 19
|
||||||
|
T_int8 Oid = 20
|
||||||
|
T_int2 Oid = 21
|
||||||
|
T_int2vector Oid = 22
|
||||||
|
T_int4 Oid = 23
|
||||||
|
T_regproc Oid = 24
|
||||||
|
T_text Oid = 25
|
||||||
|
T_oid Oid = 26
|
||||||
|
T_tid Oid = 27
|
||||||
|
T_xid Oid = 28
|
||||||
|
T_cid Oid = 29
|
||||||
|
T_oidvector Oid = 30
|
||||||
|
T_pg_ddl_command Oid = 32
|
||||||
|
T_pg_type Oid = 71
|
||||||
|
T_pg_attribute Oid = 75
|
||||||
|
T_pg_proc Oid = 81
|
||||||
|
T_pg_class Oid = 83
|
||||||
|
T_json Oid = 114
|
||||||
|
T_xml Oid = 142
|
||||||
|
T__xml Oid = 143
|
||||||
|
T_pg_node_tree Oid = 194
|
||||||
|
T__json Oid = 199
|
||||||
|
T_smgr Oid = 210
|
||||||
|
T_index_am_handler Oid = 325
|
||||||
|
T_point Oid = 600
|
||||||
|
T_lseg Oid = 601
|
||||||
|
T_path Oid = 602
|
||||||
|
T_box Oid = 603
|
||||||
|
T_polygon Oid = 604
|
||||||
|
T_line Oid = 628
|
||||||
|
T__line Oid = 629
|
||||||
|
T_cidr Oid = 650
|
||||||
|
T__cidr Oid = 651
|
||||||
|
T_float4 Oid = 700
|
||||||
|
T_float8 Oid = 701
|
||||||
|
T_abstime Oid = 702
|
||||||
|
T_reltime Oid = 703
|
||||||
|
T_tinterval Oid = 704
|
||||||
|
T_unknown Oid = 705
|
||||||
|
T_circle Oid = 718
|
||||||
|
T__circle Oid = 719
|
||||||
|
T_money Oid = 790
|
||||||
|
T__money Oid = 791
|
||||||
|
T_macaddr Oid = 829
|
||||||
|
T_inet Oid = 869
|
||||||
|
T__bool Oid = 1000
|
||||||
|
T__bytea Oid = 1001
|
||||||
|
T__char Oid = 1002
|
||||||
|
T__name Oid = 1003
|
||||||
|
T__int2 Oid = 1005
|
||||||
|
T__int2vector Oid = 1006
|
||||||
|
T__int4 Oid = 1007
|
||||||
|
T__regproc Oid = 1008
|
||||||
|
T__text Oid = 1009
|
||||||
|
T__tid Oid = 1010
|
||||||
|
T__xid Oid = 1011
|
||||||
|
T__cid Oid = 1012
|
||||||
|
T__oidvector Oid = 1013
|
||||||
|
T__bpchar Oid = 1014
|
||||||
|
T__varchar Oid = 1015
|
||||||
|
T__int8 Oid = 1016
|
||||||
|
T__point Oid = 1017
|
||||||
|
T__lseg Oid = 1018
|
||||||
|
T__path Oid = 1019
|
||||||
|
T__box Oid = 1020
|
||||||
|
T__float4 Oid = 1021
|
||||||
|
T__float8 Oid = 1022
|
||||||
|
T__abstime Oid = 1023
|
||||||
|
T__reltime Oid = 1024
|
||||||
|
T__tinterval Oid = 1025
|
||||||
|
T__polygon Oid = 1027
|
||||||
|
T__oid Oid = 1028
|
||||||
|
T_aclitem Oid = 1033
|
||||||
|
T__aclitem Oid = 1034
|
||||||
|
T__macaddr Oid = 1040
|
||||||
|
T__inet Oid = 1041
|
||||||
|
T_bpchar Oid = 1042
|
||||||
|
T_varchar Oid = 1043
|
||||||
|
T_date Oid = 1082
|
||||||
|
T_time Oid = 1083
|
||||||
|
T_timestamp Oid = 1114
|
||||||
|
T__timestamp Oid = 1115
|
||||||
|
T__date Oid = 1182
|
||||||
|
T__time Oid = 1183
|
||||||
|
T_timestamptz Oid = 1184
|
||||||
|
T__timestamptz Oid = 1185
|
||||||
|
T_interval Oid = 1186
|
||||||
|
T__interval Oid = 1187
|
||||||
|
T__numeric Oid = 1231
|
||||||
|
T_pg_database Oid = 1248
|
||||||
|
T__cstring Oid = 1263
|
||||||
|
T_timetz Oid = 1266
|
||||||
|
T__timetz Oid = 1270
|
||||||
|
T_bit Oid = 1560
|
||||||
|
T__bit Oid = 1561
|
||||||
|
T_varbit Oid = 1562
|
||||||
|
T__varbit Oid = 1563
|
||||||
|
T_numeric Oid = 1700
|
||||||
|
T_refcursor Oid = 1790
|
||||||
|
T__refcursor Oid = 2201
|
||||||
|
T_regprocedure Oid = 2202
|
||||||
|
T_regoper Oid = 2203
|
||||||
|
T_regoperator Oid = 2204
|
||||||
|
T_regclass Oid = 2205
|
||||||
|
T_regtype Oid = 2206
|
||||||
|
T__regprocedure Oid = 2207
|
||||||
|
T__regoper Oid = 2208
|
||||||
|
T__regoperator Oid = 2209
|
||||||
|
T__regclass Oid = 2210
|
||||||
|
T__regtype Oid = 2211
|
||||||
|
T_record Oid = 2249
|
||||||
|
T_cstring Oid = 2275
|
||||||
|
T_any Oid = 2276
|
||||||
|
T_anyarray Oid = 2277
|
||||||
|
T_void Oid = 2278
|
||||||
|
T_trigger Oid = 2279
|
||||||
|
T_language_handler Oid = 2280
|
||||||
|
T_internal Oid = 2281
|
||||||
|
T_opaque Oid = 2282
|
||||||
|
T_anyelement Oid = 2283
|
||||||
|
T__record Oid = 2287
|
||||||
|
T_anynonarray Oid = 2776
|
||||||
|
T_pg_authid Oid = 2842
|
||||||
|
T_pg_auth_members Oid = 2843
|
||||||
|
T__txid_snapshot Oid = 2949
|
||||||
|
T_uuid Oid = 2950
|
||||||
|
T__uuid Oid = 2951
|
||||||
|
T_txid_snapshot Oid = 2970
|
||||||
|
T_fdw_handler Oid = 3115
|
||||||
|
T_pg_lsn Oid = 3220
|
||||||
|
T__pg_lsn Oid = 3221
|
||||||
|
T_tsm_handler Oid = 3310
|
||||||
|
T_anyenum Oid = 3500
|
||||||
|
T_tsvector Oid = 3614
|
||||||
|
T_tsquery Oid = 3615
|
||||||
|
T_gtsvector Oid = 3642
|
||||||
|
T__tsvector Oid = 3643
|
||||||
|
T__gtsvector Oid = 3644
|
||||||
|
T__tsquery Oid = 3645
|
||||||
|
T_regconfig Oid = 3734
|
||||||
|
T__regconfig Oid = 3735
|
||||||
|
T_regdictionary Oid = 3769
|
||||||
|
T__regdictionary Oid = 3770
|
||||||
|
T_jsonb Oid = 3802
|
||||||
|
T__jsonb Oid = 3807
|
||||||
|
T_anyrange Oid = 3831
|
||||||
|
T_event_trigger Oid = 3838
|
||||||
|
T_int4range Oid = 3904
|
||||||
|
T__int4range Oid = 3905
|
||||||
|
T_numrange Oid = 3906
|
||||||
|
T__numrange Oid = 3907
|
||||||
|
T_tsrange Oid = 3908
|
||||||
|
T__tsrange Oid = 3909
|
||||||
|
T_tstzrange Oid = 3910
|
||||||
|
T__tstzrange Oid = 3911
|
||||||
|
T_daterange Oid = 3912
|
||||||
|
T__daterange Oid = 3913
|
||||||
|
T_int8range Oid = 3926
|
||||||
|
T__int8range Oid = 3927
|
||||||
|
T_pg_shseclabel Oid = 4066
|
||||||
|
T_regnamespace Oid = 4089
|
||||||
|
T__regnamespace Oid = 4090
|
||||||
|
T_regrole Oid = 4096
|
||||||
|
T__regrole Oid = 4097
|
||||||
|
)
|
||||||
|
|
||||||
|
var TypeName = map[Oid]string{
|
||||||
|
T_bool: "BOOL",
|
||||||
|
T_bytea: "BYTEA",
|
||||||
|
T_char: "CHAR",
|
||||||
|
T_name: "NAME",
|
||||||
|
T_int8: "INT8",
|
||||||
|
T_int2: "INT2",
|
||||||
|
T_int2vector: "INT2VECTOR",
|
||||||
|
T_int4: "INT4",
|
||||||
|
T_regproc: "REGPROC",
|
||||||
|
T_text: "TEXT",
|
||||||
|
T_oid: "OID",
|
||||||
|
T_tid: "TID",
|
||||||
|
T_xid: "XID",
|
||||||
|
T_cid: "CID",
|
||||||
|
T_oidvector: "OIDVECTOR",
|
||||||
|
T_pg_ddl_command: "PG_DDL_COMMAND",
|
||||||
|
T_pg_type: "PG_TYPE",
|
||||||
|
T_pg_attribute: "PG_ATTRIBUTE",
|
||||||
|
T_pg_proc: "PG_PROC",
|
||||||
|
T_pg_class: "PG_CLASS",
|
||||||
|
T_json: "JSON",
|
||||||
|
T_xml: "XML",
|
||||||
|
T__xml: "_XML",
|
||||||
|
T_pg_node_tree: "PG_NODE_TREE",
|
||||||
|
T__json: "_JSON",
|
||||||
|
T_smgr: "SMGR",
|
||||||
|
T_index_am_handler: "INDEX_AM_HANDLER",
|
||||||
|
T_point: "POINT",
|
||||||
|
T_lseg: "LSEG",
|
||||||
|
T_path: "PATH",
|
||||||
|
T_box: "BOX",
|
||||||
|
T_polygon: "POLYGON",
|
||||||
|
T_line: "LINE",
|
||||||
|
T__line: "_LINE",
|
||||||
|
T_cidr: "CIDR",
|
||||||
|
T__cidr: "_CIDR",
|
||||||
|
T_float4: "FLOAT4",
|
||||||
|
T_float8: "FLOAT8",
|
||||||
|
T_abstime: "ABSTIME",
|
||||||
|
T_reltime: "RELTIME",
|
||||||
|
T_tinterval: "TINTERVAL",
|
||||||
|
T_unknown: "UNKNOWN",
|
||||||
|
T_circle: "CIRCLE",
|
||||||
|
T__circle: "_CIRCLE",
|
||||||
|
T_money: "MONEY",
|
||||||
|
T__money: "_MONEY",
|
||||||
|
T_macaddr: "MACADDR",
|
||||||
|
T_inet: "INET",
|
||||||
|
T__bool: "_BOOL",
|
||||||
|
T__bytea: "_BYTEA",
|
||||||
|
T__char: "_CHAR",
|
||||||
|
T__name: "_NAME",
|
||||||
|
T__int2: "_INT2",
|
||||||
|
T__int2vector: "_INT2VECTOR",
|
||||||
|
T__int4: "_INT4",
|
||||||
|
T__regproc: "_REGPROC",
|
||||||
|
T__text: "_TEXT",
|
||||||
|
T__tid: "_TID",
|
||||||
|
T__xid: "_XID",
|
||||||
|
T__cid: "_CID",
|
||||||
|
T__oidvector: "_OIDVECTOR",
|
||||||
|
T__bpchar: "_BPCHAR",
|
||||||
|
T__varchar: "_VARCHAR",
|
||||||
|
T__int8: "_INT8",
|
||||||
|
T__point: "_POINT",
|
||||||
|
T__lseg: "_LSEG",
|
||||||
|
T__path: "_PATH",
|
||||||
|
T__box: "_BOX",
|
||||||
|
T__float4: "_FLOAT4",
|
||||||
|
T__float8: "_FLOAT8",
|
||||||
|
T__abstime: "_ABSTIME",
|
||||||
|
T__reltime: "_RELTIME",
|
||||||
|
T__tinterval: "_TINTERVAL",
|
||||||
|
T__polygon: "_POLYGON",
|
||||||
|
T__oid: "_OID",
|
||||||
|
T_aclitem: "ACLITEM",
|
||||||
|
T__aclitem: "_ACLITEM",
|
||||||
|
T__macaddr: "_MACADDR",
|
||||||
|
T__inet: "_INET",
|
||||||
|
T_bpchar: "BPCHAR",
|
||||||
|
T_varchar: "VARCHAR",
|
||||||
|
T_date: "DATE",
|
||||||
|
T_time: "TIME",
|
||||||
|
T_timestamp: "TIMESTAMP",
|
||||||
|
T__timestamp: "_TIMESTAMP",
|
||||||
|
T__date: "_DATE",
|
||||||
|
T__time: "_TIME",
|
||||||
|
T_timestamptz: "TIMESTAMPTZ",
|
||||||
|
T__timestamptz: "_TIMESTAMPTZ",
|
||||||
|
T_interval: "INTERVAL",
|
||||||
|
T__interval: "_INTERVAL",
|
||||||
|
T__numeric: "_NUMERIC",
|
||||||
|
T_pg_database: "PG_DATABASE",
|
||||||
|
T__cstring: "_CSTRING",
|
||||||
|
T_timetz: "TIMETZ",
|
||||||
|
T__timetz: "_TIMETZ",
|
||||||
|
T_bit: "BIT",
|
||||||
|
T__bit: "_BIT",
|
||||||
|
T_varbit: "VARBIT",
|
||||||
|
T__varbit: "_VARBIT",
|
||||||
|
T_numeric: "NUMERIC",
|
||||||
|
T_refcursor: "REFCURSOR",
|
||||||
|
T__refcursor: "_REFCURSOR",
|
||||||
|
T_regprocedure: "REGPROCEDURE",
|
||||||
|
T_regoper: "REGOPER",
|
||||||
|
T_regoperator: "REGOPERATOR",
|
||||||
|
T_regclass: "REGCLASS",
|
||||||
|
T_regtype: "REGTYPE",
|
||||||
|
T__regprocedure: "_REGPROCEDURE",
|
||||||
|
T__regoper: "_REGOPER",
|
||||||
|
T__regoperator: "_REGOPERATOR",
|
||||||
|
T__regclass: "_REGCLASS",
|
||||||
|
T__regtype: "_REGTYPE",
|
||||||
|
T_record: "RECORD",
|
||||||
|
T_cstring: "CSTRING",
|
||||||
|
T_any: "ANY",
|
||||||
|
T_anyarray: "ANYARRAY",
|
||||||
|
T_void: "VOID",
|
||||||
|
T_trigger: "TRIGGER",
|
||||||
|
T_language_handler: "LANGUAGE_HANDLER",
|
||||||
|
T_internal: "INTERNAL",
|
||||||
|
T_opaque: "OPAQUE",
|
||||||
|
T_anyelement: "ANYELEMENT",
|
||||||
|
T__record: "_RECORD",
|
||||||
|
T_anynonarray: "ANYNONARRAY",
|
||||||
|
T_pg_authid: "PG_AUTHID",
|
||||||
|
T_pg_auth_members: "PG_AUTH_MEMBERS",
|
||||||
|
T__txid_snapshot: "_TXID_SNAPSHOT",
|
||||||
|
T_uuid: "UUID",
|
||||||
|
T__uuid: "_UUID",
|
||||||
|
T_txid_snapshot: "TXID_SNAPSHOT",
|
||||||
|
T_fdw_handler: "FDW_HANDLER",
|
||||||
|
T_pg_lsn: "PG_LSN",
|
||||||
|
T__pg_lsn: "_PG_LSN",
|
||||||
|
T_tsm_handler: "TSM_HANDLER",
|
||||||
|
T_anyenum: "ANYENUM",
|
||||||
|
T_tsvector: "TSVECTOR",
|
||||||
|
T_tsquery: "TSQUERY",
|
||||||
|
T_gtsvector: "GTSVECTOR",
|
||||||
|
T__tsvector: "_TSVECTOR",
|
||||||
|
T__gtsvector: "_GTSVECTOR",
|
||||||
|
T__tsquery: "_TSQUERY",
|
||||||
|
T_regconfig: "REGCONFIG",
|
||||||
|
T__regconfig: "_REGCONFIG",
|
||||||
|
T_regdictionary: "REGDICTIONARY",
|
||||||
|
T__regdictionary: "_REGDICTIONARY",
|
||||||
|
T_jsonb: "JSONB",
|
||||||
|
T__jsonb: "_JSONB",
|
||||||
|
T_anyrange: "ANYRANGE",
|
||||||
|
T_event_trigger: "EVENT_TRIGGER",
|
||||||
|
T_int4range: "INT4RANGE",
|
||||||
|
T__int4range: "_INT4RANGE",
|
||||||
|
T_numrange: "NUMRANGE",
|
||||||
|
T__numrange: "_NUMRANGE",
|
||||||
|
T_tsrange: "TSRANGE",
|
||||||
|
T__tsrange: "_TSRANGE",
|
||||||
|
T_tstzrange: "TSTZRANGE",
|
||||||
|
T__tstzrange: "_TSTZRANGE",
|
||||||
|
T_daterange: "DATERANGE",
|
||||||
|
T__daterange: "_DATERANGE",
|
||||||
|
T_int8range: "INT8RANGE",
|
||||||
|
T__int8range: "_INT8RANGE",
|
||||||
|
T_pg_shseclabel: "PG_SHSECLABEL",
|
||||||
|
T_regnamespace: "REGNAMESPACE",
|
||||||
|
T__regnamespace: "_REGNAMESPACE",
|
||||||
|
T_regrole: "REGROLE",
|
||||||
|
T__regrole: "_REGROLE",
|
||||||
|
}
|
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
93
vendor/github.com/lib/pq/rows.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq/oid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerSize = 4
|
||||||
|
|
||||||
|
type fieldDesc struct {
|
||||||
|
// The object ID of the data type.
|
||||||
|
OID oid.Oid
|
||||||
|
// The data type size (see pg_type.typlen).
|
||||||
|
// Note that negative values denote variable-width types.
|
||||||
|
Len int
|
||||||
|
// The type modifier (see pg_attribute.atttypmod).
|
||||||
|
// The meaning of the modifier is type-specific.
|
||||||
|
Mod int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Type() reflect.Type {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_int8:
|
||||||
|
return reflect.TypeOf(int64(0))
|
||||||
|
case oid.T_int4:
|
||||||
|
return reflect.TypeOf(int32(0))
|
||||||
|
case oid.T_int2:
|
||||||
|
return reflect.TypeOf(int16(0))
|
||||||
|
case oid.T_varchar, oid.T_text:
|
||||||
|
return reflect.TypeOf("")
|
||||||
|
case oid.T_bool:
|
||||||
|
return reflect.TypeOf(false)
|
||||||
|
case oid.T_date, oid.T_time, oid.T_timetz, oid.T_timestamp, oid.T_timestamptz:
|
||||||
|
return reflect.TypeOf(time.Time{})
|
||||||
|
case oid.T_bytea:
|
||||||
|
return reflect.TypeOf([]byte(nil))
|
||||||
|
default:
|
||||||
|
return reflect.TypeOf(new(interface{})).Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Name() string {
|
||||||
|
return oid.TypeName[fd.OID]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) Length() (length int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_text, oid.T_bytea:
|
||||||
|
return math.MaxInt64, true
|
||||||
|
case oid.T_varchar, oid.T_bpchar:
|
||||||
|
return int64(fd.Mod - headerSize), true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fd fieldDesc) PrecisionScale() (precision, scale int64, ok bool) {
|
||||||
|
switch fd.OID {
|
||||||
|
case oid.T_numeric, oid.T__numeric:
|
||||||
|
mod := fd.Mod - headerSize
|
||||||
|
precision = int64((mod >> 16) & 0xffff)
|
||||||
|
scale = int64(mod & 0xffff)
|
||||||
|
return precision, scale, true
|
||||||
|
default:
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeScanType returns the value type that can be used to scan types into.
|
||||||
|
func (rs *rows) ColumnTypeScanType(index int) reflect.Type {
|
||||||
|
return rs.colTyps[index].Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeDatabaseTypeName return the database system type name.
|
||||||
|
func (rs *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||||
|
return rs.colTyps[index].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypeLength returns the length of the column type if the column is a
|
||||||
|
// variable length type. If the column is not a variable length type ok
|
||||||
|
// should return false.
|
||||||
|
func (rs *rows) ColumnTypeLength(index int) (length int64, ok bool) {
|
||||||
|
return rs.colTyps[index].Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTypePrecisionScale should return the precision and scale for decimal
|
||||||
|
// types. If not applicable, ok should be false.
|
||||||
|
func (rs *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
|
||||||
|
return rs.colTyps[index].PrecisionScale()
|
||||||
|
}
|
169
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
169
vendor/github.com/lib/pq/ssl.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
|
||||||
|
// related settings. The function is nil when no upgrade should take place.
|
||||||
|
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
|
||||||
|
verifyCaOnly := false
|
||||||
|
tlsConf := tls.Config{}
|
||||||
|
switch mode := o["sslmode"]; mode {
|
||||||
|
// "require" is the default.
|
||||||
|
case "", "require":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
|
||||||
|
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
|
||||||
|
//
|
||||||
|
// Note: For backwards compatibility with earlier versions of
|
||||||
|
// PostgreSQL, if a root CA file exists, the behavior of
|
||||||
|
// sslmode=require will be the same as that of verify-ca, meaning the
|
||||||
|
// server certificate is validated against the CA. Relying on this
|
||||||
|
// behavior is discouraged, and applications that need certificate
|
||||||
|
// validation should always use verify-ca or verify-full.
|
||||||
|
if sslrootcert, ok := o["sslrootcert"]; ok {
|
||||||
|
if _, err := os.Stat(sslrootcert); err == nil {
|
||||||
|
verifyCaOnly = true
|
||||||
|
} else {
|
||||||
|
delete(o, "sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "verify-ca":
|
||||||
|
// We must skip TLS's own verification since it requires full
|
||||||
|
// verification since Go 1.3.
|
||||||
|
tlsConf.InsecureSkipVerify = true
|
||||||
|
verifyCaOnly = true
|
||||||
|
case "verify-full":
|
||||||
|
tlsConf.ServerName = o["host"]
|
||||||
|
case "disable":
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sslClientCertificates(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = sslCertificateAuthority(&tlsConf, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sslRenegotiation(&tlsConf)
|
||||||
|
|
||||||
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
|
client := tls.Client(conn, &tlsConf)
|
||||||
|
if verifyCaOnly {
|
||||||
|
err := sslVerifyCertificateAuthority(client, &tlsConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslClientCertificates adds the certificate specified in the "sslcert" and
|
||||||
|
// "sslkey" settings, or if they aren't set, from the .postgresql directory
|
||||||
|
// in the user's home directory. The configured files must exist and have
|
||||||
|
// the correct permissions.
|
||||||
|
func sslClientCertificates(tlsConf *tls.Config, o values) error {
|
||||||
|
// user.Current() might fail when cross-compiling. We have to ignore the
|
||||||
|
// error and continue without home directory defaults, since we wouldn't
|
||||||
|
// know from where to load them.
|
||||||
|
user, _ := user.Current()
|
||||||
|
|
||||||
|
// In libpq, the client certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
|
||||||
|
sslcert := o["sslcert"]
|
||||||
|
if len(sslcert) == 0 && user != nil {
|
||||||
|
sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045
|
||||||
|
if len(sslcert) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054
|
||||||
|
if _, err := os.Stat(sslcert); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// In libpq, the ssl key is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222
|
||||||
|
sslkey := o["sslkey"]
|
||||||
|
if len(sslkey) == 0 && user != nil {
|
||||||
|
sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sslkey) > 0 {
|
||||||
|
if err := sslKeyPermissions(sslkey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
|
||||||
|
func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
|
||||||
|
// In libpq, the root certificate is only loaded if the setting is not blank.
|
||||||
|
//
|
||||||
|
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
|
||||||
|
if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 {
|
||||||
|
tlsConf.RootCAs = x509.NewCertPool()
|
||||||
|
|
||||||
|
cert, err := ioutil.ReadFile(sslrootcert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
|
||||||
|
return fmterrorf("couldn't parse pem in sslrootcert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
|
||||||
|
// verifies the presented certificate against the CA, i.e. the one specified in
|
||||||
|
// sslrootcert or the system CA if sslrootcert was not specified.
|
||||||
|
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certs := client.ConnectionState().PeerCertificates
|
||||||
|
opts := x509.VerifyOptions{
|
||||||
|
DNSName: client.ConnectionState().ServerName,
|
||||||
|
Intermediates: x509.NewCertPool(),
|
||||||
|
Roots: tlsConf.RootCAs,
|
||||||
|
}
|
||||||
|
for i, cert := range certs {
|
||||||
|
if i == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts.Intermediates.AddCert(cert)
|
||||||
|
}
|
||||||
|
_, err = certs[0].Verify(opts)
|
||||||
|
return err
|
||||||
|
}
|
14
vendor/github.com/lib/pq/ssl_go1.7.go
generated
vendored
Normal file
14
vendor/github.com/lib/pq/ssl_go1.7.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||||
|
// the default configuration of older versions has it enabled. Redshift
|
||||||
|
// also initiates renegotiations and cannot be reconfigured.
|
||||||
|
func sslRenegotiation(conf *tls.Config) {
|
||||||
|
conf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
}
|
20
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
20
vendor/github.com/lib/pq/ssl_permissions.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(sslkey string) error {
|
||||||
|
info, err := os.Stat(sslkey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.Mode().Perm()&0077 != 0 {
|
||||||
|
return ErrSSLKeyHasWorldPermissions
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
8
vendor/github.com/lib/pq/ssl_renegotiation.go
generated
vendored
Normal file
8
vendor/github.com/lib/pq/ssl_renegotiation.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
// Renegotiation is not supported by crypto/tls until Go 1.7.
|
||||||
|
func sslRenegotiation(*tls.Config) {}
|
9
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
9
vendor/github.com/lib/pq/ssl_windows.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
|
||||||
|
// The key file should have very little access.
|
||||||
|
//
|
||||||
|
// libpq does not check key file permissions on Windows.
|
||||||
|
func sslKeyPermissions(string) error { return nil }
|
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
76
vendor/github.com/lib/pq/url.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
nurl "net/url"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
|
||||||
|
// connection string to sql.Open() is now supported:
|
||||||
|
//
|
||||||
|
// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
|
||||||
|
//
|
||||||
|
// It remains exported here for backwards-compatibility.
|
||||||
|
//
|
||||||
|
// ParseURL converts a url to a connection string for driver.Open.
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// converts to:
|
||||||
|
//
|
||||||
|
// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
|
||||||
|
//
|
||||||
|
// A minimal example:
|
||||||
|
//
|
||||||
|
// "postgres://"
|
||||||
|
//
|
||||||
|
// This will be blank, causing driver.Open to use all of the defaults
|
||||||
|
func ParseURL(url string) (string, error) {
|
||||||
|
u, err := nurl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "postgres" && u.Scheme != "postgresql" {
|
||||||
|
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvs []string
|
||||||
|
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
|
||||||
|
accrue := func(k, v string) {
|
||||||
|
if v != "" {
|
||||||
|
kvs = append(kvs, k+"="+escaper.Replace(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
v := u.User.Username()
|
||||||
|
accrue("user", v)
|
||||||
|
|
||||||
|
v, _ = u.User.Password()
|
||||||
|
accrue("password", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
|
accrue("host", u.Host)
|
||||||
|
} else {
|
||||||
|
accrue("host", host)
|
||||||
|
accrue("port", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Path != "" {
|
||||||
|
accrue("dbname", u.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
for k := range q {
|
||||||
|
accrue(k, q.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(kvs) // Makes testing easier (not a performance concern)
|
||||||
|
return strings.Join(kvs, " "), nil
|
||||||
|
}
|
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
24
vendor/github.com/lib/pq/user_posix.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
|
||||||
|
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris rumprun
|
||||||
|
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return u.Username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name := os.Getenv("USER")
|
||||||
|
if name != "" {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
27
vendor/github.com/lib/pq/user_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Package pq is a pure Go Postgres driver for the database/sql package.
|
||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform Windows user name lookup identically to libpq.
|
||||||
|
//
|
||||||
|
// The PostgreSQL code makes use of the legacy Win32 function
|
||||||
|
// GetUserName, and that function has not been imported into stock Go.
|
||||||
|
// GetUserNameEx is available though, the difference being that a
|
||||||
|
// wider range of names are available. To get the output to be the
|
||||||
|
// same as GetUserName, only the base (or last) component of the
|
||||||
|
// result is returned.
|
||||||
|
func userCurrent() (string, error) {
|
||||||
|
pw_name := make([]uint16, 128)
|
||||||
|
pwname_size := uint32(len(pw_name)) - 1
|
||||||
|
err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
|
||||||
|
if err != nil {
|
||||||
|
return "", ErrCouldNotDetectUsername
|
||||||
|
}
|
||||||
|
s := syscall.UTF16ToString(pw_name)
|
||||||
|
u := filepath.Base(s)
|
||||||
|
return u, nil
|
||||||
|
}
|
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
23
vendor/github.com/lib/pq/uuid.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package pq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeUUIDBinary interprets the binary format of a uuid, returning it in text format.
|
||||||
|
func decodeUUIDBinary(src []byte) ([]byte, error) {
|
||||||
|
if len(src) != 16 {
|
||||||
|
return nil, fmt.Errorf("pq: unable to decode uuid; bad length: %d", len(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := make([]byte, 36)
|
||||||
|
dst[8], dst[13], dst[18], dst[23] = '-', '-', '-', '-'
|
||||||
|
hex.Encode(dst[0:], src[0:4])
|
||||||
|
hex.Encode(dst[9:], src[4:6])
|
||||||
|
hex.Encode(dst[14:], src[6:8])
|
||||||
|
hex.Encode(dst[19:], src[8:10])
|
||||||
|
hex.Encode(dst[24:], src[10:16])
|
||||||
|
|
||||||
|
return dst, nil
|
||||||
|
}
|
22
vendor/github.com/robfig/cron/.gitignore
generated
vendored
Normal file
22
vendor/github.com/robfig/cron/.gitignore
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
1
vendor/github.com/robfig/cron/.travis.yml
generated
vendored
Normal file
1
vendor/github.com/robfig/cron/.travis.yml
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
language: go
|
21
vendor/github.com/robfig/cron/LICENSE
generated
vendored
Normal file
21
vendor/github.com/robfig/cron/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
Copyright (C) 2012 Rob Figueiredo
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
MIT LICENSE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
6
vendor/github.com/robfig/cron/README.md
generated
vendored
Normal file
6
vendor/github.com/robfig/cron/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron)
|
||||||
|
[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron)
|
||||||
|
|
||||||
|
# cron
|
||||||
|
|
||||||
|
Documentation here: https://godoc.org/github.com/robfig/cron
|
27
vendor/github.com/robfig/cron/constantdelay.go
generated
vendored
Normal file
27
vendor/github.com/robfig/cron/constantdelay.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
|
||||||
|
// It does not support jobs more frequent than once a second.
|
||||||
|
type ConstantDelaySchedule struct {
|
||||||
|
Delay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every returns a crontab Schedule that activates once every duration.
|
||||||
|
// Delays of less than a second are not supported (will round up to 1 second).
|
||||||
|
// Any fields less than a Second are truncated.
|
||||||
|
func Every(duration time.Duration) ConstantDelaySchedule {
|
||||||
|
if duration < time.Second {
|
||||||
|
duration = time.Second
|
||||||
|
}
|
||||||
|
return ConstantDelaySchedule{
|
||||||
|
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next time this should be run.
|
||||||
|
// This rounds so that the next activation time will be on the second.
|
||||||
|
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
|
||||||
|
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||||
|
}
|
259
vendor/github.com/robfig/cron/cron.go
generated
vendored
Normal file
259
vendor/github.com/robfig/cron/cron.go
generated
vendored
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cron keeps track of any number of entries, invoking the associated func as
|
||||||
|
// specified by the schedule. It may be started, stopped, and the entries may
|
||||||
|
// be inspected while running.
|
||||||
|
type Cron struct {
|
||||||
|
entries []*Entry
|
||||||
|
stop chan struct{}
|
||||||
|
add chan *Entry
|
||||||
|
snapshot chan []*Entry
|
||||||
|
running bool
|
||||||
|
ErrorLog *log.Logger
|
||||||
|
location *time.Location
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job is an interface for submitted cron jobs.
|
||||||
|
type Job interface {
|
||||||
|
Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Schedule describes a job's duty cycle.
|
||||||
|
type Schedule interface {
|
||||||
|
// Return the next activation time, later than the given time.
|
||||||
|
// Next is invoked initially, and then each time the job is run.
|
||||||
|
Next(time.Time) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry consists of a schedule and the func to execute on that schedule.
|
||||||
|
type Entry struct {
|
||||||
|
// The schedule on which this job should be run.
|
||||||
|
Schedule Schedule
|
||||||
|
|
||||||
|
// The next time the job will run. This is the zero time if Cron has not been
|
||||||
|
// started or this entry's schedule is unsatisfiable
|
||||||
|
Next time.Time
|
||||||
|
|
||||||
|
// The last time this job was run. This is the zero time if the job has never
|
||||||
|
// been run.
|
||||||
|
Prev time.Time
|
||||||
|
|
||||||
|
// The Job to run.
|
||||||
|
Job Job
|
||||||
|
}
|
||||||
|
|
||||||
|
// byTime is a wrapper for sorting the entry array by time
|
||||||
|
// (with zero time at the end).
|
||||||
|
type byTime []*Entry
|
||||||
|
|
||||||
|
func (s byTime) Len() int { return len(s) }
|
||||||
|
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s byTime) Less(i, j int) bool {
|
||||||
|
// Two zero times should return false.
|
||||||
|
// Otherwise, zero is "greater" than any other time.
|
||||||
|
// (To sort it at the end of the list.)
|
||||||
|
if s[i].Next.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[j].Next.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s[i].Next.Before(s[j].Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Cron job runner, in the Local time zone.
|
||||||
|
func New() *Cron {
|
||||||
|
return NewWithLocation(time.Now().Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithLocation returns a new Cron job runner.
|
||||||
|
func NewWithLocation(location *time.Location) *Cron {
|
||||||
|
return &Cron{
|
||||||
|
entries: nil,
|
||||||
|
add: make(chan *Entry),
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
snapshot: make(chan []*Entry),
|
||||||
|
running: false,
|
||||||
|
ErrorLog: nil,
|
||||||
|
location: location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper that turns a func() into a cron.Job
|
||||||
|
type FuncJob func()
|
||||||
|
|
||||||
|
func (f FuncJob) Run() { f() }
|
||||||
|
|
||||||
|
// AddFunc adds a func to the Cron to be run on the given schedule.
|
||||||
|
func (c *Cron) AddFunc(spec string, cmd func()) error {
|
||||||
|
return c.AddJob(spec, FuncJob(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddJob adds a Job to the Cron to be run on the given schedule.
|
||||||
|
func (c *Cron) AddJob(spec string, cmd Job) error {
|
||||||
|
schedule, err := Parse(spec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Schedule(schedule, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule adds a Job to the Cron to be run on the given schedule.
|
||||||
|
func (c *Cron) Schedule(schedule Schedule, cmd Job) {
|
||||||
|
entry := &Entry{
|
||||||
|
Schedule: schedule,
|
||||||
|
Job: cmd,
|
||||||
|
}
|
||||||
|
if !c.running {
|
||||||
|
c.entries = append(c.entries, entry)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.add <- entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries returns a snapshot of the cron entries.
|
||||||
|
func (c *Cron) Entries() []*Entry {
|
||||||
|
if c.running {
|
||||||
|
c.snapshot <- nil
|
||||||
|
x := <-c.snapshot
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return c.entrySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location gets the time zone location
|
||||||
|
func (c *Cron) Location() *time.Location {
|
||||||
|
return c.location
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the cron scheduler in its own go-routine, or no-op if already started.
|
||||||
|
func (c *Cron) Start() {
|
||||||
|
if c.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.running = true
|
||||||
|
go c.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the cron scheduler, or no-op if already running.
|
||||||
|
func (c *Cron) Run() {
|
||||||
|
if c.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.running = true
|
||||||
|
c.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cron) runWithRecovery(j Job) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
c.logf("cron: panic running job: %v\n%s", r, buf)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
j.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the scheduler. this is private just due to the need to synchronize
|
||||||
|
// access to the 'running' state variable.
|
||||||
|
func (c *Cron) run() {
|
||||||
|
// Figure out the next activation times for each entry.
|
||||||
|
now := c.now()
|
||||||
|
for _, entry := range c.entries {
|
||||||
|
entry.Next = entry.Schedule.Next(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Determine the next entry to run.
|
||||||
|
sort.Sort(byTime(c.entries))
|
||||||
|
|
||||||
|
var timer *time.Timer
|
||||||
|
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
|
||||||
|
// If there are no entries yet, just sleep - it still handles new entries
|
||||||
|
// and stop requests.
|
||||||
|
timer = time.NewTimer(100000 * time.Hour)
|
||||||
|
} else {
|
||||||
|
timer = time.NewTimer(c.entries[0].Next.Sub(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case now = <-timer.C:
|
||||||
|
now = now.In(c.location)
|
||||||
|
// Run every entry whose next time was less than now
|
||||||
|
for _, e := range c.entries {
|
||||||
|
if e.Next.After(now) || e.Next.IsZero() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
go c.runWithRecovery(e.Job)
|
||||||
|
e.Prev = e.Next
|
||||||
|
e.Next = e.Schedule.Next(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
case newEntry := <-c.add:
|
||||||
|
timer.Stop()
|
||||||
|
now = c.now()
|
||||||
|
newEntry.Next = newEntry.Schedule.Next(now)
|
||||||
|
c.entries = append(c.entries, newEntry)
|
||||||
|
|
||||||
|
case <-c.snapshot:
|
||||||
|
c.snapshot <- c.entrySnapshot()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case <-c.stop:
|
||||||
|
timer.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs an error to stderr or to the configured error log
|
||||||
|
func (c *Cron) logf(format string, args ...interface{}) {
|
||||||
|
if c.ErrorLog != nil {
|
||||||
|
c.ErrorLog.Printf(format, args...)
|
||||||
|
} else {
|
||||||
|
log.Printf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
|
||||||
|
func (c *Cron) Stop() {
|
||||||
|
if !c.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.stop <- struct{}{}
|
||||||
|
c.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// entrySnapshot returns a copy of the current cron entry list.
|
||||||
|
func (c *Cron) entrySnapshot() []*Entry {
|
||||||
|
entries := []*Entry{}
|
||||||
|
for _, e := range c.entries {
|
||||||
|
entries = append(entries, &Entry{
|
||||||
|
Schedule: e.Schedule,
|
||||||
|
Next: e.Next,
|
||||||
|
Prev: e.Prev,
|
||||||
|
Job: e.Job,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// now returns current time in c location
|
||||||
|
func (c *Cron) now() time.Time {
|
||||||
|
return time.Now().In(c.location)
|
||||||
|
}
|
129
vendor/github.com/robfig/cron/doc.go
generated
vendored
Normal file
129
vendor/github.com/robfig/cron/doc.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
Package cron implements a cron spec parser and job runner.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
Callers may register Funcs to be invoked on a given schedule. Cron will run
|
||||||
|
them in their own goroutines.
|
||||||
|
|
||||||
|
c := cron.New()
|
||||||
|
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
|
||||||
|
c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
|
||||||
|
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
|
||||||
|
c.Start()
|
||||||
|
..
|
||||||
|
// Funcs are invoked in their own goroutine, asynchronously.
|
||||||
|
...
|
||||||
|
// Funcs may also be added to a running Cron
|
||||||
|
c.AddFunc("@daily", func() { fmt.Println("Every day") })
|
||||||
|
..
|
||||||
|
// Inspect the cron job entries' next and previous run times.
|
||||||
|
inspect(c.Entries())
|
||||||
|
..
|
||||||
|
c.Stop() // Stop the scheduler (does not stop any jobs already running).
|
||||||
|
|
||||||
|
CRON Expression Format
|
||||||
|
|
||||||
|
A cron expression represents a set of times, using 6 space-separated fields.
|
||||||
|
|
||||||
|
Field name | Mandatory? | Allowed values | Allowed special characters
|
||||||
|
---------- | ---------- | -------------- | --------------------------
|
||||||
|
Seconds | Yes | 0-59 | * / , -
|
||||||
|
Minutes | Yes | 0-59 | * / , -
|
||||||
|
Hours | Yes | 0-23 | * / , -
|
||||||
|
Day of month | Yes | 1-31 | * / , - ?
|
||||||
|
Month | Yes | 1-12 or JAN-DEC | * / , -
|
||||||
|
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
|
||||||
|
|
||||||
|
Note: Month and Day-of-week field values are case insensitive. "SUN", "Sun",
|
||||||
|
and "sun" are equally accepted.
|
||||||
|
|
||||||
|
Special Characters
|
||||||
|
|
||||||
|
Asterisk ( * )
|
||||||
|
|
||||||
|
The asterisk indicates that the cron expression will match for all values of the
|
||||||
|
field; e.g., using an asterisk in the 5th field (month) would indicate every
|
||||||
|
month.
|
||||||
|
|
||||||
|
Slash ( / )
|
||||||
|
|
||||||
|
Slashes are used to describe increments of ranges. For example 3-59/15 in the
|
||||||
|
1st field (minutes) would indicate the 3rd minute of the hour and every 15
|
||||||
|
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
|
||||||
|
that is, an increment over the largest possible range of the field. The form
|
||||||
|
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
|
||||||
|
increment until the end of that specific range. It does not wrap around.
|
||||||
|
|
||||||
|
Comma ( , )
|
||||||
|
|
||||||
|
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
|
||||||
|
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
|
||||||
|
|
||||||
|
Hyphen ( - )
|
||||||
|
|
||||||
|
Hyphens are used to define ranges. For example, 9-17 would indicate every
|
||||||
|
hour between 9am and 5pm inclusive.
|
||||||
|
|
||||||
|
Question mark ( ? )
|
||||||
|
|
||||||
|
Question mark may be used instead of '*' for leaving either day-of-month or
|
||||||
|
day-of-week blank.
|
||||||
|
|
||||||
|
Predefined schedules
|
||||||
|
|
||||||
|
You may use one of several pre-defined schedules in place of a cron expression.
|
||||||
|
|
||||||
|
Entry | Description | Equivalent To
|
||||||
|
----- | ----------- | -------------
|
||||||
|
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
|
||||||
|
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
|
||||||
|
@weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0
|
||||||
|
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
|
||||||
|
@hourly | Run once an hour, beginning of hour | 0 0 * * * *
|
||||||
|
|
||||||
|
Intervals
|
||||||
|
|
||||||
|
You may also schedule a job to execute at fixed intervals, starting at the time it's added
|
||||||
|
or cron is run. This is supported by formatting the cron spec like this:
|
||||||
|
|
||||||
|
@every <duration>
|
||||||
|
|
||||||
|
where "duration" is a string accepted by time.ParseDuration
|
||||||
|
(http://golang.org/pkg/time/#ParseDuration).
|
||||||
|
|
||||||
|
For example, "@every 1h30m10s" would indicate a schedule that activates after
|
||||||
|
1 hour, 30 minutes, 10 seconds, and then every interval after that.
|
||||||
|
|
||||||
|
Note: The interval does not take the job runtime into account. For example,
|
||||||
|
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
|
||||||
|
it will have only 2 minutes of idle time between each run.
|
||||||
|
|
||||||
|
Time zones
|
||||||
|
|
||||||
|
All interpretation and scheduling is done in the machine's local time zone (as
|
||||||
|
provided by the Go time package (http://www.golang.org/pkg/time).
|
||||||
|
|
||||||
|
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
|
||||||
|
not be run!
|
||||||
|
|
||||||
|
Thread safety
|
||||||
|
|
||||||
|
Since the Cron service runs concurrently with the calling code, some amount of
|
||||||
|
care must be taken to ensure proper synchronization.
|
||||||
|
|
||||||
|
All cron methods are designed to be correctly synchronized as long as the caller
|
||||||
|
ensures that invocations have a clear happens-before ordering between them.
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
|
||||||
|
Cron entries are stored in an array, sorted by their next activation time. Cron
|
||||||
|
sleeps until the next job is due to be run.
|
||||||
|
|
||||||
|
Upon waking:
|
||||||
|
- it runs each entry that is active on that second
|
||||||
|
- it calculates the next run times for the jobs that were run
|
||||||
|
- it re-sorts the array of entries by next activation time.
|
||||||
|
- it goes to sleep until the soonest job.
|
||||||
|
*/
|
||||||
|
package cron
|
380
vendor/github.com/robfig/cron/parser.go
generated
vendored
Normal file
380
vendor/github.com/robfig/cron/parser.go
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration options for creating a parser. Most options specify which
|
||||||
|
// fields should be included, while others enable features. If a field is not
|
||||||
|
// included the parser will assume a default value. These options do not change
|
||||||
|
// the order fields are parse in.
|
||||||
|
type ParseOption int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Second ParseOption = 1 << iota // Seconds field, default 0
|
||||||
|
Minute // Minutes field, default 0
|
||||||
|
Hour // Hours field, default 0
|
||||||
|
Dom // Day of month field, default *
|
||||||
|
Month // Month field, default *
|
||||||
|
Dow // Day of week field, default *
|
||||||
|
DowOptional // Optional day of week field, default *
|
||||||
|
Descriptor // Allow descriptors such as @monthly, @weekly, etc.
|
||||||
|
)
|
||||||
|
|
||||||
|
var places = []ParseOption{
|
||||||
|
Second,
|
||||||
|
Minute,
|
||||||
|
Hour,
|
||||||
|
Dom,
|
||||||
|
Month,
|
||||||
|
Dow,
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = []string{
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"0",
|
||||||
|
"*",
|
||||||
|
"*",
|
||||||
|
"*",
|
||||||
|
}
|
||||||
|
|
||||||
|
// A custom Parser that can be configured.
|
||||||
|
type Parser struct {
|
||||||
|
options ParseOption
|
||||||
|
optionals int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a custom Parser with custom options.
|
||||||
|
//
|
||||||
|
// // Standard parser without descriptors
|
||||||
|
// specParser := NewParser(Minute | Hour | Dom | Month | Dow)
|
||||||
|
// sched, err := specParser.Parse("0 0 15 */3 *")
|
||||||
|
//
|
||||||
|
// // Same as above, just excludes time fields
|
||||||
|
// subsParser := NewParser(Dom | Month | Dow)
|
||||||
|
// sched, err := specParser.Parse("15 */3 *")
|
||||||
|
//
|
||||||
|
// // Same as above, just makes Dow optional
|
||||||
|
// subsParser := NewParser(Dom | Month | DowOptional)
|
||||||
|
// sched, err := specParser.Parse("15 */3")
|
||||||
|
//
|
||||||
|
func NewParser(options ParseOption) Parser {
|
||||||
|
optionals := 0
|
||||||
|
if options&DowOptional > 0 {
|
||||||
|
options |= Dow
|
||||||
|
optionals++
|
||||||
|
}
|
||||||
|
return Parser{options, optionals}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a new crontab schedule representing the given spec.
|
||||||
|
// It returns a descriptive error if the spec is not valid.
|
||||||
|
// It accepts crontab specs and features configured by NewParser.
|
||||||
|
func (p Parser) Parse(spec string) (Schedule, error) {
|
||||||
|
if len(spec) == 0 {
|
||||||
|
return nil, fmt.Errorf("Empty spec string")
|
||||||
|
}
|
||||||
|
if spec[0] == '@' && p.options&Descriptor > 0 {
|
||||||
|
return parseDescriptor(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out how many fields we need
|
||||||
|
max := 0
|
||||||
|
for _, place := range places {
|
||||||
|
if p.options&place > 0 {
|
||||||
|
max++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
min := max - p.optionals
|
||||||
|
|
||||||
|
// Split fields on whitespace
|
||||||
|
fields := strings.Fields(spec)
|
||||||
|
|
||||||
|
// Validate number of fields
|
||||||
|
if count := len(fields); count < min || count > max {
|
||||||
|
if min == max {
|
||||||
|
return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in missing fields
|
||||||
|
fields = expandFields(fields, p.options)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
field := func(field string, r bounds) uint64 {
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var bits uint64
|
||||||
|
bits, err = getField(field, r)
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
second = field(fields[0], seconds)
|
||||||
|
minute = field(fields[1], minutes)
|
||||||
|
hour = field(fields[2], hours)
|
||||||
|
dayofmonth = field(fields[3], dom)
|
||||||
|
month = field(fields[4], months)
|
||||||
|
dayofweek = field(fields[5], dow)
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: second,
|
||||||
|
Minute: minute,
|
||||||
|
Hour: hour,
|
||||||
|
Dom: dayofmonth,
|
||||||
|
Month: month,
|
||||||
|
Dow: dayofweek,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandFields(fields []string, options ParseOption) []string {
|
||||||
|
n := 0
|
||||||
|
count := len(fields)
|
||||||
|
expFields := make([]string, len(places))
|
||||||
|
copy(expFields, defaults)
|
||||||
|
for i, place := range places {
|
||||||
|
if options&place > 0 {
|
||||||
|
expFields[i] = fields[n]
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n == count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expFields
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardParser = NewParser(
|
||||||
|
Minute | Hour | Dom | Month | Dow | Descriptor,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseStandard returns a new crontab schedule representing the given standardSpec
|
||||||
|
// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always
|
||||||
|
// pass 5 entries representing: minute, hour, day of month, month and day of week,
|
||||||
|
// in that order. It returns a descriptive error if the spec is not valid.
|
||||||
|
//
|
||||||
|
// It accepts
|
||||||
|
// - Standard crontab specs, e.g. "* * * * ?"
|
||||||
|
// - Descriptors, e.g. "@midnight", "@every 1h30m"
|
||||||
|
func ParseStandard(standardSpec string) (Schedule, error) {
|
||||||
|
return standardParser.Parse(standardSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultParser = NewParser(
|
||||||
|
Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse returns a new crontab schedule representing the given spec.
|
||||||
|
// It returns a descriptive error if the spec is not valid.
|
||||||
|
//
|
||||||
|
// It accepts
|
||||||
|
// - Full crontab specs, e.g. "* * * * * ?"
|
||||||
|
// - Descriptors, e.g. "@midnight", "@every 1h30m"
|
||||||
|
func Parse(spec string) (Schedule, error) {
|
||||||
|
return defaultParser.Parse(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getField returns an Int with the bits set representing all of the times that
|
||||||
|
// the field represents or error parsing field value. A "field" is a comma-separated
|
||||||
|
// list of "ranges".
|
||||||
|
func getField(field string, r bounds) (uint64, error) {
|
||||||
|
var bits uint64
|
||||||
|
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
|
||||||
|
for _, expr := range ranges {
|
||||||
|
bit, err := getRange(expr, r)
|
||||||
|
if err != nil {
|
||||||
|
return bits, err
|
||||||
|
}
|
||||||
|
bits |= bit
|
||||||
|
}
|
||||||
|
return bits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRange returns the bits indicated by the given expression:
|
||||||
|
// number | number "-" number [ "/" number ]
|
||||||
|
// or error parsing range.
|
||||||
|
func getRange(expr string, r bounds) (uint64, error) {
|
||||||
|
var (
|
||||||
|
start, end, step uint
|
||||||
|
rangeAndStep = strings.Split(expr, "/")
|
||||||
|
lowAndHigh = strings.Split(rangeAndStep[0], "-")
|
||||||
|
singleDigit = len(lowAndHigh) == 1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
var extra uint64
|
||||||
|
if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
|
||||||
|
start = r.min
|
||||||
|
end = r.max
|
||||||
|
extra = starBit
|
||||||
|
} else {
|
||||||
|
start, err = parseIntOrName(lowAndHigh[0], r.names)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch len(lowAndHigh) {
|
||||||
|
case 1:
|
||||||
|
end = start
|
||||||
|
case 2:
|
||||||
|
end, err = parseIntOrName(lowAndHigh[1], r.names)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("Too many hyphens: %s", expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(rangeAndStep) {
|
||||||
|
case 1:
|
||||||
|
step = 1
|
||||||
|
case 2:
|
||||||
|
step, err = mustParseInt(rangeAndStep[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling: "N/step" means "N-max/step".
|
||||||
|
if singleDigit {
|
||||||
|
end = r.max
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("Too many slashes: %s", expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < r.min {
|
||||||
|
return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
|
||||||
|
}
|
||||||
|
if end > r.max {
|
||||||
|
return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
|
||||||
|
}
|
||||||
|
if start > end {
|
||||||
|
return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
|
||||||
|
}
|
||||||
|
if step == 0 {
|
||||||
|
return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBits(start, end, step) | extra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIntOrName returns the (possibly-named) integer contained in expr.
|
||||||
|
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
|
||||||
|
if names != nil {
|
||||||
|
if namedInt, ok := names[strings.ToLower(expr)]; ok {
|
||||||
|
return namedInt, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mustParseInt(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustParseInt parses the given expression as an int or returns an error.
|
||||||
|
func mustParseInt(expr string) (uint, error) {
|
||||||
|
num, err := strconv.Atoi(expr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)
|
||||||
|
}
|
||||||
|
if num < 0 {
|
||||||
|
return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(num), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBits sets all bits in the range [min, max], modulo the given step size.
|
||||||
|
func getBits(min, max, step uint) uint64 {
|
||||||
|
var bits uint64
|
||||||
|
|
||||||
|
// If step is 1, use shifts.
|
||||||
|
if step == 1 {
|
||||||
|
return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, use a simple loop.
|
||||||
|
for i := min; i <= max; i += step {
|
||||||
|
bits |= 1 << i
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
// all returns all bits within the given bounds. (plus the star bit)
|
||||||
|
func all(r bounds) uint64 {
|
||||||
|
return getBits(r.min, r.max, 1) | starBit
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
|
||||||
|
func parseDescriptor(descriptor string) (Schedule, error) {
|
||||||
|
switch descriptor {
|
||||||
|
case "@yearly", "@annually":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: 1 << dom.min,
|
||||||
|
Month: 1 << months.min,
|
||||||
|
Dow: all(dow),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@monthly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: 1 << dom.min,
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@weekly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: 1 << dow.min,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@daily", "@midnight":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: 1 << hours.min,
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case "@hourly":
|
||||||
|
return &SpecSchedule{
|
||||||
|
Second: 1 << seconds.min,
|
||||||
|
Minute: 1 << minutes.min,
|
||||||
|
Hour: all(hours),
|
||||||
|
Dom: all(dom),
|
||||||
|
Month: all(months),
|
||||||
|
Dow: all(dow),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const every = "@every "
|
||||||
|
if strings.HasPrefix(descriptor, every) {
|
||||||
|
duration, err := time.ParseDuration(descriptor[len(every):])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)
|
||||||
|
}
|
||||||
|
return Every(duration), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)
|
||||||
|
}
|
158
vendor/github.com/robfig/cron/spec.go
generated
vendored
Normal file
158
vendor/github.com/robfig/cron/spec.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
|
||||||
|
// traditional crontab specification. It is computed initially and stored as bit sets.
|
||||||
|
type SpecSchedule struct {
|
||||||
|
Second, Minute, Hour, Dom, Month, Dow uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// bounds provides a range of acceptable values (plus a map of name to value).
|
||||||
|
type bounds struct {
|
||||||
|
min, max uint
|
||||||
|
names map[string]uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// The bounds for each field.
|
||||||
|
var (
|
||||||
|
seconds = bounds{0, 59, nil}
|
||||||
|
minutes = bounds{0, 59, nil}
|
||||||
|
hours = bounds{0, 23, nil}
|
||||||
|
dom = bounds{1, 31, nil}
|
||||||
|
months = bounds{1, 12, map[string]uint{
|
||||||
|
"jan": 1,
|
||||||
|
"feb": 2,
|
||||||
|
"mar": 3,
|
||||||
|
"apr": 4,
|
||||||
|
"may": 5,
|
||||||
|
"jun": 6,
|
||||||
|
"jul": 7,
|
||||||
|
"aug": 8,
|
||||||
|
"sep": 9,
|
||||||
|
"oct": 10,
|
||||||
|
"nov": 11,
|
||||||
|
"dec": 12,
|
||||||
|
}}
|
||||||
|
dow = bounds{0, 6, map[string]uint{
|
||||||
|
"sun": 0,
|
||||||
|
"mon": 1,
|
||||||
|
"tue": 2,
|
||||||
|
"wed": 3,
|
||||||
|
"thu": 4,
|
||||||
|
"fri": 5,
|
||||||
|
"sat": 6,
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Set the top bit if a star was included in the expression.
|
||||||
|
starBit = 1 << 63
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next returns the next time this schedule is activated, greater than the given
|
||||||
|
// time. If no time can be found to satisfy the schedule, return the zero time.
|
||||||
|
func (s *SpecSchedule) Next(t time.Time) time.Time {
|
||||||
|
// General approach:
|
||||||
|
// For Month, Day, Hour, Minute, Second:
|
||||||
|
// Check if the time value matches. If yes, continue to the next field.
|
||||||
|
// If the field doesn't match the schedule, then increment the field until it matches.
|
||||||
|
// While incrementing the field, a wrap-around brings it back to the beginning
|
||||||
|
// of the field list (since it is necessary to re-verify previous field
|
||||||
|
// values)
|
||||||
|
|
||||||
|
// Start at the earliest possible time (the upcoming second).
|
||||||
|
t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||||
|
|
||||||
|
// This flag indicates whether a field has been incremented.
|
||||||
|
added := false
|
||||||
|
|
||||||
|
// If no time is found within five years, return zero.
|
||||||
|
yearLimit := t.Year() + 5
|
||||||
|
|
||||||
|
WRAP:
|
||||||
|
if t.Year() > yearLimit {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first applicable month.
|
||||||
|
// If it's this month, then do nothing.
|
||||||
|
for 1<<uint(t.Month())&s.Month == 0 {
|
||||||
|
// If we have to add a month, reset the other parts to 0.
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
// Otherwise, set the date at the beginning (since the current time is irrelevant).
|
||||||
|
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
t = t.AddDate(0, 1, 0)
|
||||||
|
|
||||||
|
// Wrapped around.
|
||||||
|
if t.Month() == time.January {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get a day in that month.
|
||||||
|
for !dayMatches(s, t) {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
t = t.AddDate(0, 0, 1)
|
||||||
|
|
||||||
|
if t.Day() == 1 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Hour())&s.Hour == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Hour)
|
||||||
|
|
||||||
|
if t.Hour() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Minute())&s.Minute == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = t.Truncate(time.Minute)
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Minute)
|
||||||
|
|
||||||
|
if t.Minute() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for 1<<uint(t.Second())&s.Second == 0 {
|
||||||
|
if !added {
|
||||||
|
added = true
|
||||||
|
t = t.Truncate(time.Second)
|
||||||
|
}
|
||||||
|
t = t.Add(1 * time.Second)
|
||||||
|
|
||||||
|
if t.Second() == 0 {
|
||||||
|
goto WRAP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
||||||
|
// restrictions are satisfied by the given time.
|
||||||
|
func dayMatches(s *SpecSchedule, t time.Time) bool {
|
||||||
|
var (
|
||||||
|
domMatch bool = 1<<uint(t.Day())&s.Dom > 0
|
||||||
|
dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
|
||||||
|
)
|
||||||
|
if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
|
||||||
|
return domMatch && dowMatch
|
||||||
|
}
|
||||||
|
return domMatch || dowMatch
|
||||||
|
}
|
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
@ -1,4 +1,4 @@
|
|||||||
# git.trj.tw/golang/utils v0.0.0-20181219032659-b1a8bce04189
|
# git.trj.tw/golang/utils v0.0.0-20190116070516-b266ebeb2d82
|
||||||
git.trj.tw/golang/utils
|
git.trj.tw/golang/utils
|
||||||
# github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7
|
# github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7
|
||||||
github.com/gin-contrib/sse
|
github.com/gin-contrib/sse
|
||||||
@ -9,14 +9,22 @@ github.com/gin-gonic/gin/json
|
|||||||
github.com/gin-gonic/gin/render
|
github.com/gin-gonic/gin/render
|
||||||
# github.com/golang/protobuf v1.2.0
|
# github.com/golang/protobuf v1.2.0
|
||||||
github.com/golang/protobuf/proto
|
github.com/golang/protobuf/proto
|
||||||
|
# github.com/jmoiron/sqlx v1.2.0
|
||||||
|
github.com/jmoiron/sqlx
|
||||||
|
github.com/jmoiron/sqlx/reflectx
|
||||||
# github.com/json-iterator/go v1.1.5
|
# github.com/json-iterator/go v1.1.5
|
||||||
github.com/json-iterator/go
|
github.com/json-iterator/go
|
||||||
|
# github.com/lib/pq v1.0.0
|
||||||
|
github.com/lib/pq
|
||||||
|
github.com/lib/pq/oid
|
||||||
# github.com/mattn/go-isatty v0.0.4
|
# github.com/mattn/go-isatty v0.0.4
|
||||||
github.com/mattn/go-isatty
|
github.com/mattn/go-isatty
|
||||||
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||||
github.com/modern-go/concurrent
|
github.com/modern-go/concurrent
|
||||||
# github.com/modern-go/reflect2 v1.0.1
|
# github.com/modern-go/reflect2 v1.0.1
|
||||||
github.com/modern-go/reflect2
|
github.com/modern-go/reflect2
|
||||||
|
# github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||||
|
github.com/robfig/cron
|
||||||
# github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2
|
# github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2
|
||||||
github.com/ugorji/go/codec
|
github.com/ugorji/go/codec
|
||||||
# golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6
|
# golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6
|
||||||
|
Loading…
Reference in New Issue
Block a user