Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
559cbd0132 | ||
|
|
733cb78284 | ||
|
|
479ef2b786 | ||
|
|
073ad38bc0 | ||
|
|
aa1ef99295 | ||
|
|
1a056c1aff | ||
|
|
c575677088 |
+2
-2
@@ -46,12 +46,12 @@
|
||||
<p>
|
||||
<pre>
|
||||
$ export TOKEN=c033-b79f-7fa1
|
||||
$ curl -i -H "Authorization: Bearer $TOKEN" -d '{"repo": "https://git.example.com/user/foo"}' https://vain/me/foo </pre>
|
||||
$ curl -i -H "Authorization: Bearer $TOKEN" -d '{"repo": "https://git.example.com/user/foo"}' https://go.example.com/foo </pre>
|
||||
</p>
|
||||
<h2>Deleting a repository</h2>
|
||||
<p>Using the appropriate token for the route you want to delete:</p>
|
||||
<pre>
|
||||
$ curl -i -H "Authorization: Bearer $TOKEN" -X DELETE https://vain/me/foo </pre>
|
||||
$ curl -i -H "Authorization: Bearer $TOKEN" -X DELETE https://go.example.com/foo </pre>
|
||||
|
||||
<h2>go tool</h2>
|
||||
<p> The <a href="https://golang.org/cmd/go/#hdr-Download_and_install_packages_and_dependencies">go tool</a> doesn't need any modification in order to work, e.g. if vain is hosted on <code>https://go.example.com</code>: </p> <pre>$ go get go.example.com/foo</pre>
|
||||
|
||||
+69
-70
@@ -12,25 +12,23 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
const window = 5 * time.Minute
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
ts := httptest.NewServer(sm)
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.Get(ts.URL)
|
||||
@@ -111,7 +109,7 @@ func TestAdd(t *testing.T) {
|
||||
|
||||
good := fmt.Sprintf("%s/foo", ur.Host)
|
||||
|
||||
if !db.PackageExists(good) {
|
||||
if !db.PackageExists(path(good)) {
|
||||
t.Fatalf("did not find package for %s; should have posted a valid package", good)
|
||||
}
|
||||
p, err := db.Package(good)
|
||||
@@ -165,11 +163,11 @@ func TestAdd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidPath(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -197,11 +195,11 @@ func TestInvalidPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCannotDuplicateExistingPath(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -243,11 +241,11 @@ func TestCannotDuplicateExistingPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCannotAddExistingSubPath(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -255,7 +253,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
{
|
||||
@@ -291,11 +289,11 @@ func TestCannotAddExistingSubPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMissingRepo(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -303,7 +301,7 @@ func TestMissingRepo(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||
@@ -324,11 +322,11 @@ func TestMissingRepo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBadJson(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -336,7 +334,7 @@ func TestBadJson(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||
@@ -357,11 +355,11 @@ func TestBadJson(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoAuth(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -386,11 +384,11 @@ func TestNoAuth(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBadVcs(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -398,7 +396,7 @@ func TestBadVcs(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||
@@ -417,11 +415,11 @@ func TestBadVcs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnsupportedMethod(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -429,7 +427,7 @@ func TestUnsupportedMethod(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/foo", ts.URL)
|
||||
@@ -449,11 +447,11 @@ func TestUnsupportedMethod(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, false)
|
||||
@@ -461,8 +459,9 @@ func TestDelete(t *testing.T) {
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("%v", tok)
|
||||
if len(db.Pkgs()) != 0 {
|
||||
t.Fatalf("started with something in it; got %d, want %d", len(db.Pkgs()), 0)
|
||||
@@ -513,22 +512,22 @@ func TestDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSingleGet(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
NewServer(sm, db, nil, "", window, true)
|
||||
ts := httptest.NewServer(sm)
|
||||
email := "sm@example.org"
|
||||
tok, err := db.addUser(email)
|
||||
|
||||
tok, err := db.addUser("sm@example.org")
|
||||
if err != nil {
|
||||
t.Fatalf("failure to add user: %v", err)
|
||||
t.Errorf("failure to add user: %v", err)
|
||||
}
|
||||
|
||||
ns := "foo"
|
||||
ns := namespace("foo")
|
||||
|
||||
if err := db.NSForToken(ns, tok); err != nil {
|
||||
t.Fatalf("could not initialize namespace %q for user %q: %v", ns, tok, err)
|
||||
@@ -567,11 +566,11 @@ func TestSingleGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
mm := &mockMail{}
|
||||
@@ -620,11 +619,11 @@ func TestRegister(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
mm := &mockMail{}
|
||||
@@ -688,11 +687,11 @@ func TestRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestForgot(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer db.Close()
|
||||
defer done()
|
||||
|
||||
sm := http.NewServeMux()
|
||||
mm := &mockMail{}
|
||||
|
||||
+23
-71
@@ -1,43 +1,3 @@
|
||||
/*
|
||||
vaind, a webserver for hosting go get vanity urls.
|
||||
|
||||
The go get command searches for the following header when searching for
|
||||
packages:
|
||||
|
||||
<meta name="go-import" content="import-prefix vcs repo-root">
|
||||
|
||||
this is simply a service for aggregating a collection of prefix, vcs, and
|
||||
repo-root tuples, and serving the appropriate header over http. For more
|
||||
information please refer to the documentation for the go tool found at
|
||||
https://golang.org/cmd/go/#hdr-Remote_import_paths
|
||||
|
||||
API
|
||||
|
||||
Assume an instance of vaind at example.org. In order to add a package
|
||||
example.org/foo that points at bitbucket.org/example/foo (a mercurial
|
||||
repository) POST the following json object:
|
||||
|
||||
{
|
||||
"vcs": "mercurial",
|
||||
"repo": "https://bitbucket.org/example/foo"
|
||||
}
|
||||
|
||||
to https://example.org/foo.
|
||||
|
||||
Doing so, then visiting https://example.org/foo?go-get=1 will yield a header
|
||||
that looks like:
|
||||
|
||||
|
||||
<meta name="go-import" content="example.org/foo hg https://bitbucket.org/foo">
|
||||
|
||||
|
||||
The json object sent to server can have two fields: "repo" and "vcs". "repo" is
|
||||
required; leaving off the "vcs" member defaults to "git".
|
||||
|
||||
In order to delete a package:
|
||||
|
||||
DELETE /<package name>
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -45,15 +5,16 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"mcquay.me/vain"
|
||||
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
const usage = "vaind [init] <dbname>"
|
||||
const usage = "vaind <dbname>"
|
||||
|
||||
type config struct {
|
||||
Port int
|
||||
@@ -73,38 +34,12 @@ type config struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", usage)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if os.Args[1] == "init" {
|
||||
if len(os.Args) != 3 {
|
||||
fmt.Fprintf(os.Stderr, "missing db name: %s\n", usage)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
db, err := vain.NewPGDB("localhost", os.Args[2], 32)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err := db.Init(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "problem initializing the db: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
db, err := vain.NewDB(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c := &config{
|
||||
Port: 4040,
|
||||
EmailTimeout: 5 * time.Minute,
|
||||
@@ -132,10 +67,27 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("%+v", c)
|
||||
|
||||
m, err := vain.NewEmail(c.From, c.SMTPHost, c.SMTPPort)
|
||||
db, err := vain.NewMemDB(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
s := <-sigs
|
||||
log.Printf("signal: %+v", s)
|
||||
if err := db.Sync(); err != nil {
|
||||
log.Printf("problem syncing db to disk: %+v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
m, err := vain.NewMail(c.From, c.SMTPHost, c.SMTPPort)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "problem initializing mailer: %v", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,356 +1,258 @@
|
||||
package vain
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
verrors "mcquay.me/vain/errors"
|
||||
vsql "mcquay.me/vain/sql"
|
||||
"mcquay.me/vain/metrics"
|
||||
)
|
||||
|
||||
// DB wraps a sqlx.DB connection and provides methods for interating with
|
||||
// a vain database.
|
||||
type DB struct {
|
||||
conn *sqlx.DB
|
||||
}
|
||||
// NewMemDB returns a functional MemDB.
|
||||
func NewMemDB(p string) (*MemDB, error) {
|
||||
m := &MemDB{
|
||||
filename: p,
|
||||
|
||||
// NewDB opens a sqlite3 file, sets options, and reports errors.
|
||||
func NewDB(path string) (*DB, error) {
|
||||
conn, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc", path))
|
||||
if _, err := conn.Exec("PRAGMA foreign_keys = ON"); err != nil {
|
||||
return nil, err
|
||||
Users: map[Email]User{},
|
||||
TokToEmail: map[Token]Email{},
|
||||
|
||||
Packages: map[path]Package{},
|
||||
Namespaces: map[namespace]Email{},
|
||||
}
|
||||
return &DB{conn}, err
|
||||
}
|
||||
|
||||
// NewPGDB returns a populated DB and verifies the connection is usable.
|
||||
func NewPGDB(dbhost string, dbname string, maxConn int) (*DB, error) {
|
||||
dsn := fmt.Sprintf("host=%s dbname=%s sslmode=disable", dbhost, dbname)
|
||||
|
||||
conn, err := sqlx.Open("postgres", dsn)
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// file doesn't exist yet
|
||||
return m, nil
|
||||
}
|
||||
if err := conn.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.SetMaxOpenConns(maxConn)
|
||||
return &DB{conn: conn}, nil
|
||||
err = json.NewDecoder(f).Decode(m)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Init runs the embedded sql to initialize tables.
|
||||
func (db *DB) Init() error {
|
||||
content, err := vsql.Asset("sql/init.sql")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.conn.Exec(string(content))
|
||||
return err
|
||||
}
|
||||
// MemDB implements an in-memory, and disk-backed database for a vain server.
|
||||
type MemDB struct {
|
||||
filename string
|
||||
|
||||
// Close the underlying connection.
|
||||
func (db *DB) Close() error {
|
||||
return db.conn.Close()
|
||||
}
|
||||
l sync.RWMutex
|
||||
|
||||
// AddPackage adds p into packages table.
|
||||
func (db *DB) AddPackage(p Package) error {
|
||||
_, err := db.conn.NamedExec(
|
||||
"INSERT INTO packages(vcs, repo, path, ns) VALUES (:vcs, :repo, :path, :ns)",
|
||||
&p,
|
||||
)
|
||||
return err
|
||||
}
|
||||
Users map[Email]User
|
||||
TokToEmail map[Token]Email
|
||||
|
||||
// RemovePackage removes package with given path
|
||||
func (db *DB) RemovePackage(path string) error {
|
||||
_, err := db.conn.Exec("DELETE FROM packages WHERE path = $1", path)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pkgs returns all packages from the database
|
||||
func (db *DB) Pkgs() []Package {
|
||||
r := []Package{}
|
||||
rows, err := db.conn.Queryx("SELECT * FROM packages")
|
||||
if err != nil {
|
||||
log.Printf("%+v", err)
|
||||
return nil
|
||||
}
|
||||
for rows.Next() {
|
||||
var p Package
|
||||
err = rows.StructScan(&p)
|
||||
if err != nil {
|
||||
log.Printf("%+v", err)
|
||||
return nil
|
||||
}
|
||||
r = append(r, p)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// PackageExists tells if a package with path is in the database.
|
||||
func (db *DB) PackageExists(path string) bool {
|
||||
var count int
|
||||
if err := db.conn.Get(&count, "SELECT COUNT(*) FROM packages WHERE path = $1", path); err != nil {
|
||||
log.Printf("%+v", err)
|
||||
}
|
||||
|
||||
r := false
|
||||
switch count {
|
||||
case 1:
|
||||
r = true
|
||||
default:
|
||||
log.Printf("unexpected count of packages matching %q: %d", path, count)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Package fetches the package associated with path.
|
||||
func (db *DB) Package(path string) (Package, error) {
|
||||
r := Package{}
|
||||
err := db.conn.Get(&r, "SELECT * FROM packages WHERE path = $1", path)
|
||||
if err == sql.ErrNoRows {
|
||||
return r, verrors.HTTP{
|
||||
Message: fmt.Sprintf("couldn't find package %q", path),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
Packages map[path]Package
|
||||
Namespaces map[namespace]Email
|
||||
}
|
||||
|
||||
// NSForToken creates an entry namespaces with a relation to the token.
|
||||
func (db *DB) NSForToken(ns string, tok string) error {
|
||||
func (m *MemDB) NSForToken(ns namespace, tok Token) error {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
e, ok := m.TokToEmail[tok]
|
||||
if !ok {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("User for token %q not found", tok),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
if owner, ok := m.Namespaces[ns]; !ok {
|
||||
m.Namespaces[ns] = e
|
||||
} else {
|
||||
if m.Namespaces[ns] != owner {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("not authorized against namespace %q", ns),
|
||||
Code: http.StatusUnauthorized,
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.flush(m.filename)
|
||||
}
|
||||
|
||||
// Package fetches the package associated with path.
|
||||
func (m *MemDB) Package(pth string) (Package, error) {
|
||||
m.l.RLock()
|
||||
defer m.l.RUnlock()
|
||||
|
||||
pkg, ok := m.Packages[path(pth)]
|
||||
if ok {
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
var longest Package
|
||||
for _, p := range m.Packages {
|
||||
if splitPathHasPrefix(strings.Split(pth, "/"), strings.Split(p.Path, "/")) {
|
||||
if len(p.Path) > len(longest.Path) {
|
||||
longest = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
txn, err := db.conn.Beginx()
|
||||
if err != nil {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem creating transaction: %v", err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
} else {
|
||||
txn.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
var count int
|
||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM namespaces WHERE namespaces.ns = $1", ns); err != nil {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem matching fetching namespaces matching %q", ns),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
var email string
|
||||
if err = txn.Get(&email, "SELECT email FROM users WHERE token = $1", tok); err != nil {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not find user for token %q", tok),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
if _, err = txn.Exec(
|
||||
"INSERT INTO namespaces(ns, email) VALUES ($1, $2)",
|
||||
ns,
|
||||
email,
|
||||
); err != nil {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem inserting %q into namespaces for token %q: %v", ns, tok, err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM namespaces JOIN users ON namespaces.email = users.email WHERE users.token = $1 AND namespaces.ns = $2", tok, ns); err != nil {
|
||||
return verrors.HTTP{
|
||||
Message: fmt.Sprintf("ns: %q, tok: %q; %v", ns, tok, err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 1:
|
||||
err = nil
|
||||
case 0:
|
||||
if longest.Path == "" {
|
||||
err = verrors.HTTP{
|
||||
Message: fmt.Sprintf("not authorized against namespace %q", ns),
|
||||
Code: http.StatusUnauthorized,
|
||||
}
|
||||
default:
|
||||
err = verrors.HTTP{
|
||||
Message: fmt.Sprintf("inconsistent db; found %d results with ns (%s) with token (%s)", count, ns, tok),
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: fmt.Sprintf("couldn't find package %q", pth),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
return err
|
||||
return longest, err
|
||||
}
|
||||
|
||||
// AddPackage adds p into packages table.
|
||||
func (m *MemDB) AddPackage(p Package) error {
|
||||
m.l.Lock()
|
||||
m.Packages[path(p.Path)] = p
|
||||
m.l.Unlock()
|
||||
return m.flush(m.filename)
|
||||
}
|
||||
|
||||
// RemovePackage removes package with given path
|
||||
func (m *MemDB) RemovePackage(pth path) error {
|
||||
m.l.Lock()
|
||||
delete(m.Packages, pth)
|
||||
m.l.Unlock()
|
||||
return m.flush(m.filename)
|
||||
}
|
||||
|
||||
// PackageExists tells if a package with path is in the database.
|
||||
func (m *MemDB) PackageExists(pth path) bool {
|
||||
m.l.RLock()
|
||||
_, ok := m.Packages[path(pth)]
|
||||
m.l.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Pkgs returns all packages from the database
|
||||
func (m *MemDB) Pkgs() []Package {
|
||||
ps := []Package{}
|
||||
m.l.RLock()
|
||||
for _, p := range m.Packages {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
m.l.RUnlock()
|
||||
return ps
|
||||
}
|
||||
|
||||
// Register adds email to the database, returning an error if there was one.
|
||||
func (db *DB) Register(email string) (string, error) {
|
||||
var err error
|
||||
txn, err := db.conn.Beginx()
|
||||
if err != nil {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem creating transaction: %v", err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
} else {
|
||||
txn.Commit()
|
||||
}
|
||||
}()
|
||||
func (m *MemDB) Register(e Email) (Token, error) {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
var count int
|
||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE email = $1", email); err != nil {
|
||||
if _, ok := m.Users[e]; ok {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not search for email %q in db: %v", email, err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("duplicate email %q", email),
|
||||
Message: fmt.Sprintf("duplicate email %q", e),
|
||||
Code: http.StatusConflict,
|
||||
}
|
||||
}
|
||||
|
||||
tok := FreshToken()
|
||||
_, err = txn.Exec(
|
||||
"INSERT INTO users(email, token, requested) VALUES ($1, $2, $3)",
|
||||
email,
|
||||
tok,
|
||||
time.Now(),
|
||||
)
|
||||
return tok, err
|
||||
m.Users[e] = User{
|
||||
Email: e,
|
||||
token: tok,
|
||||
Requested: time.Now(),
|
||||
}
|
||||
m.TokToEmail[tok] = e
|
||||
return tok, m.flush(m.filename)
|
||||
}
|
||||
|
||||
// Confirm modifies the user with the given token. Used on register confirmation.
|
||||
func (db *DB) Confirm(token string) (string, error) {
|
||||
var err error
|
||||
txn, err := db.conn.Beginx()
|
||||
if err != nil {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem creating transaction: %v", err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
} else {
|
||||
txn.Commit()
|
||||
}
|
||||
}()
|
||||
func (m *MemDB) Confirm(tok Token) (Token, error) {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
var count int
|
||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE token = $1", token); err != nil {
|
||||
e, ok := m.TokToEmail[tok]
|
||||
if !ok {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not perform search for user with token %q in db: %v", token, err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
if count != 1 {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("bad token: %s", token),
|
||||
Message: fmt.Sprintf("bad token: %s", tok),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
newToken := FreshToken()
|
||||
|
||||
_, err = txn.Exec(
|
||||
"UPDATE users SET token = $1, registered = true WHERE token = $2",
|
||||
newToken,
|
||||
token,
|
||||
)
|
||||
if err != nil {
|
||||
delete(m.TokToEmail, tok)
|
||||
tok = FreshToken()
|
||||
u, ok := m.Users[e]
|
||||
if !ok {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("couldn't update user with token %q: %v", token, err),
|
||||
Message: fmt.Sprintf("inconsistent db; found email for token %q, but no user for email %q", tok, e),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return newToken, nil
|
||||
u.token = tok
|
||||
m.Users[e] = u
|
||||
m.TokToEmail[tok] = e
|
||||
|
||||
return tok, m.flush(m.filename)
|
||||
}
|
||||
|
||||
func (db *DB) forgot(email string, window time.Duration) (string, error) {
|
||||
txn, err := db.conn.Beginx()
|
||||
if err != nil {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("problem creating transaction: %v", err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
txn.Rollback()
|
||||
} else {
|
||||
txn.Commit()
|
||||
}
|
||||
}()
|
||||
// Forgot is used fetch a user's token. It implements rudimentary rate
|
||||
// limiting.
|
||||
func (m *MemDB) Forgot(e Email, window time.Duration) (Token, error) {
|
||||
m.l.Lock()
|
||||
defer m.l.Unlock()
|
||||
|
||||
out := struct {
|
||||
Token string
|
||||
Requested time.Time
|
||||
}{}
|
||||
if err = txn.Get(&out, "SELECT token, requested FROM users WHERE email = $1", email); err != nil {
|
||||
u, ok := m.Users[e]
|
||||
if !ok {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not find email %q in db", email),
|
||||
Message: fmt.Sprintf("could not find email %q in db", e),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
if out.Requested.After(time.Now()) {
|
||||
if u.Requested.After(time.Now()) {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("rate limit hit for %q; try again in %0.2f mins", email, out.Requested.Sub(time.Now()).Minutes()),
|
||||
Message: fmt.Sprintf("rate limit hit for %q; try again in %0.2f mins", u.Email, u.Requested.Sub(time.Now()).Minutes()),
|
||||
Code: http.StatusTooManyRequests,
|
||||
}
|
||||
}
|
||||
_, err = txn.Exec("UPDATE users SET requested = $1 WHERE email = $2", time.Now().Add(window), email)
|
||||
|
||||
return u.token, nil
|
||||
}
|
||||
|
||||
// Sync takes a lock, and flushes the data to disk.
|
||||
func (m *MemDB) Sync() error {
|
||||
m.l.RLock()
|
||||
defer m.l.RUnlock()
|
||||
|
||||
return m.flush(m.filename)
|
||||
}
|
||||
|
||||
// flush writes to disk, but expects the user to have taken the lock.
|
||||
func (m *MemDB) flush(p string) error {
|
||||
defer metrics.DBTime("flush")()
|
||||
f, err := os.Create(p)
|
||||
if err != nil {
|
||||
return "", verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not update last requested time for %q: %v", email, err),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
return err
|
||||
}
|
||||
return out.Token, nil
|
||||
return json.NewEncoder(f).Encode(&m)
|
||||
}
|
||||
|
||||
func (db *DB) addUser(email string) (string, error) {
|
||||
func (m *MemDB) addUser(e Email) (Token, error) {
|
||||
tok := FreshToken()
|
||||
_, err := db.conn.Exec(
|
||||
"INSERT INTO users(email, token, requested) VALUES ($1, $2, $3)",
|
||||
email,
|
||||
tok,
|
||||
time.Now(),
|
||||
)
|
||||
return tok, err
|
||||
|
||||
m.l.Lock()
|
||||
m.Users[e] = User{
|
||||
Email: e,
|
||||
token: tok,
|
||||
Requested: time.Now(),
|
||||
}
|
||||
m.TokToEmail[tok] = e
|
||||
m.l.Unlock()
|
||||
|
||||
return tok, m.flush(m.filename)
|
||||
}
|
||||
|
||||
func (db *DB) user(email string) (User, error) {
|
||||
u := User{}
|
||||
err := db.conn.Get(
|
||||
&u,
|
||||
"SELECT email, token, registered, requested FROM users WHERE email = $1",
|
||||
email,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return User{}, verrors.HTTP{
|
||||
Message: fmt.Sprintf("could not find requested user's email: %q: %v", email, err),
|
||||
func (m *MemDB) user(e Email) (User, error) {
|
||||
m.l.Lock()
|
||||
u, ok := m.Users[e]
|
||||
m.l.Unlock()
|
||||
var err error
|
||||
if !ok {
|
||||
err = verrors.HTTP{
|
||||
Message: fmt.Sprintf("couldn't find user %q", e),
|
||||
Code: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
+62
-59
@@ -2,76 +2,79 @@ package vain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
type TestDB struct {
|
||||
DB
|
||||
name string
|
||||
}
|
||||
func TestPartialPackage(t *testing.T) {
|
||||
db, done := TestDB(t)
|
||||
if db == nil {
|
||||
t.Fatalf("could not create temp db")
|
||||
}
|
||||
defer done()
|
||||
|
||||
func NewTestDB(name string) (*TestDB, error) {
|
||||
pgdb, err := NewPGDB("localhost", "postgres", 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("problem connecting to admin database: %v", err)
|
||||
}
|
||||
if _, err := pgdb.conn.Exec(fmt.Sprintf("CREATE DATABASE %s", pq.QuoteIdentifier(name))); err != nil {
|
||||
return nil, fmt.Errorf("problem creating test database:: %v", err)
|
||||
}
|
||||
pgdb.conn.Close()
|
||||
paths := []path{
|
||||
"a/b",
|
||||
"a/c",
|
||||
"a/d/c",
|
||||
"a/d/e",
|
||||
|
||||
db, err := NewPGDB("localhost", name, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't connect to fresh db %q: %v", name, err)
|
||||
}
|
||||
if err := db.Init(); err != nil {
|
||||
return nil, fmt.Errorf("couldn't initialize db: %v", err)
|
||||
"f/b/c/d",
|
||||
"f/b/c/e",
|
||||
}
|
||||
|
||||
r := &TestDB{
|
||||
name: name,
|
||||
DB: DB{
|
||||
conn: db.conn,
|
||||
for _, p := range paths {
|
||||
db.Packages[p] = Package{Path: string(p)}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pth string
|
||||
pkg Package
|
||||
err error
|
||||
}{
|
||||
// obvious
|
||||
{
|
||||
pth: "a/b",
|
||||
pkg: db.Packages["a/b"],
|
||||
},
|
||||
{
|
||||
pth: "a/d/c",
|
||||
pkg: db.Packages["a/d/c"],
|
||||
},
|
||||
|
||||
// here we exercise the code that matches closest submatch
|
||||
{
|
||||
pth: "a/b/c",
|
||||
pkg: db.Packages["a/b"],
|
||||
},
|
||||
{
|
||||
pth: "f/b/c/d/e/f/g",
|
||||
pkg: db.Packages["f/b/c/d"],
|
||||
},
|
||||
|
||||
// some errors
|
||||
{
|
||||
pth: "foo",
|
||||
err: errors.New("shouldn't find"),
|
||||
},
|
||||
|
||||
{
|
||||
pth: "a/d/f",
|
||||
err: errors.New("shouldn't find"),
|
||||
},
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (tdb *TestDB) Close() error {
|
||||
errs := []string{}
|
||||
if err := tdb.conn.Close(); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("problem closing connection to temp db: %v", err))
|
||||
}
|
||||
if err := tdb.Drop(); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("problem cleaning up temporary db %q: %v", tdb.name, err))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.New(strings.Join(errs, " & "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, test := range tests {
|
||||
p, err := db.Package(test.pth)
|
||||
|
||||
func (tdb *TestDB) Drop() error {
|
||||
pgdb, err := NewPGDB("localhost", "postgres", 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("problem connecting to admin database: %v", err)
|
||||
}
|
||||
defer pgdb.conn.Close()
|
||||
if _, err := pgdb.conn.Exec(fmt.Sprintf("DROP DATABASE %s", pq.QuoteIdentifier(tdb.name))); err != nil {
|
||||
return fmt.Errorf("problem dropping test database:: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if got, want := p, test.pkg; got != want {
|
||||
t.Errorf("bad package fetched: got %+v, want %+v", got, want)
|
||||
}
|
||||
|
||||
func TestFoo(t *testing.T) {
|
||||
db, err := NewTestDB(uuid.New())
|
||||
if err != nil {
|
||||
t.Errorf("couldn't connect to fresh db : %v", err)
|
||||
got := err
|
||||
want := test.err
|
||||
if (got == nil) != (want == nil) {
|
||||
t.Errorf("unexpected error; got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1,21 @@
|
||||
/*
|
||||
Package vain implements a vanity service for use by the the go tool.
|
||||
|
||||
|
||||
The executable, cmd/vaind, is located in the respective subdirectory. vaind,
|
||||
a webserver for hosting go get vanity urls.
|
||||
|
||||
The go get command searches for the following header when searching for
|
||||
packages:
|
||||
|
||||
<meta name="go-import" content="import-prefix vcs repo-root">
|
||||
|
||||
this is simply a service for aggregating a collection of prefix, vcs, and
|
||||
repo-root tuples, and serving the appropriate header over http. For more
|
||||
information please refer to the documentation for the go tool found at
|
||||
https://golang.org/cmd/go/#hdr-Remote_import_paths
|
||||
|
||||
For instructions on how to use this service, build the daemon, run it, and
|
||||
visit the root url.
|
||||
*/
|
||||
package vain
|
||||
|
||||
@@ -12,13 +12,13 @@ type Mailer interface {
|
||||
Send(to mail.Address, subject, msg string) error
|
||||
}
|
||||
|
||||
// NewEmail returns *Email struct to be able to send smtp
|
||||
// NewMail returns *Send struct to be able to send smtp
|
||||
// or an error if it can't correctly parse the email address.
|
||||
func NewEmail(from, host string, port int) (*Email, error) {
|
||||
func NewMail(from, host string, port int) (*Mail, error) {
|
||||
if _, err := mail.ParseAddress(from); err != nil {
|
||||
return nil, fmt.Errorf("can't parse an email address for 'from': %v", err)
|
||||
}
|
||||
r := &Email{
|
||||
r := &Mail{
|
||||
host: host,
|
||||
port: port,
|
||||
from: from,
|
||||
@@ -26,16 +26,16 @@ func NewEmail(from, host string, port int) (*Email, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Email stores information required to use smtp.
|
||||
type Email struct {
|
||||
// Mail stores information required to use smtp.
|
||||
type Mail struct {
|
||||
host string
|
||||
port int
|
||||
from string
|
||||
}
|
||||
|
||||
// Send sends a smtp email using the host and port in the Email struct and
|
||||
// Send sends a smtp email using the host and port in the Mail struct and
|
||||
//returns an error if there was a problem sending the email.
|
||||
func (e Email) Send(to mail.Address, subject, msg string) error {
|
||||
func (e Mail) Send(to mail.Address, subject, msg string) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", e.host, e.port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't dial mail server: %v", err)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// Errors tracks http status codes for problematic requests.
|
||||
Errors = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "errors_total",
|
||||
Help: "Number of upstream errors",
|
||||
},
|
||||
[]string{"status"},
|
||||
)
|
||||
|
||||
// Func tracks time spent in a function.
|
||||
Func = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "function_microseconds",
|
||||
Help: "function timing.",
|
||||
},
|
||||
[]string{"route"},
|
||||
)
|
||||
|
||||
// DB tracks timing of interactions with the file system.
|
||||
DB = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Name: "db_microseconds",
|
||||
Help: "db timing.",
|
||||
},
|
||||
[]string{"what"},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(Errors)
|
||||
prometheus.MustRegister(Func)
|
||||
prometheus.MustRegister(DB)
|
||||
}
|
||||
|
||||
// Time is a function that makes it simple to add one-line timings to function
|
||||
// calls.
|
||||
func Time() func() {
|
||||
start := time.Now()
|
||||
return func() {
|
||||
elapsed := time.Since(start)
|
||||
pc := make([]uintptr, 10)
|
||||
runtime.Callers(2, pc)
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
|
||||
Func.WithLabelValues(f.Name()).Observe(float64(elapsed / time.Microsecond))
|
||||
}
|
||||
}
|
||||
|
||||
// DBTime makes it simple to add one-line timings to db interactions.
|
||||
func DBTime(name string) func() {
|
||||
start := time.Now()
|
||||
return func() {
|
||||
elapsed := time.Since(start)
|
||||
DB.WithLabelValues(name).Observe(float64(elapsed / time.Microsecond))
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,12 @@
|
||||
|
||||
## installation
|
||||
|
||||
$ go get mcquay.me/vain/cmd/vaind
|
||||
```bash
|
||||
$ go get mcquay.me/vain/cmd/vaind
|
||||
```
|
||||
|
||||
## running server
|
||||
|
||||
$ vaind
|
||||
```bash
|
||||
$ VAIN_FROM=me@example.org vaind vain.db
|
||||
```
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
verrors "mcquay.me/vain/errors"
|
||||
"mcquay.me/vain/metrics"
|
||||
"mcquay.me/vain/static"
|
||||
)
|
||||
|
||||
@@ -52,10 +54,15 @@ func NewServer(sm *http.ServeMux, store Storer, m Mailer, static string, emailTi
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
defer metrics.Time()()
|
||||
if req.Method == "GET" {
|
||||
req.ParseForm()
|
||||
if _, ok := req.Form["go-get"]; !ok {
|
||||
http.Redirect(w, req, prefix["static"], http.StatusTemporaryRedirect)
|
||||
route := prefix["static"]
|
||||
if p, err := s.db.Package(req.Host + req.URL.Path); err == nil {
|
||||
route = p.Repo
|
||||
}
|
||||
http.Redirect(w, req, route, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
if req.URL.Path == "/" {
|
||||
@@ -67,6 +74,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
} else {
|
||||
p, err := s.db.Package(req.Host + req.URL.Path)
|
||||
if err := verrors.ToHTTP(err); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
|
||||
http.Error(w, err.Message, err.Code)
|
||||
return
|
||||
}
|
||||
@@ -91,7 +99,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := verrors.ToHTTP(s.db.NSForToken(ns, tok)); err != nil {
|
||||
if err := verrors.ToHTTP(s.db.NSForToken(ns, Token(tok))); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
|
||||
http.Error(w, err.Message, err.Code)
|
||||
return
|
||||
}
|
||||
@@ -104,6 +113,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
p := Package{}
|
||||
if err := json.NewDecoder(req.Body).Decode(&p); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest))).Add(1)
|
||||
http.Error(w, fmt.Sprintf("unable to parse json from body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -130,12 +140,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
case "DELETE":
|
||||
p := fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
|
||||
if !s.db.PackageExists(p) {
|
||||
if !s.db.PackageExists(path(p)) {
|
||||
http.Error(w, fmt.Sprintf("package %q not found", p), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.db.RemovePackage(p); err != nil {
|
||||
if err := s.db.RemovePackage(path(p)); err != nil {
|
||||
http.Error(w, fmt.Sprintf("unable to delete package: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -145,6 +155,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
||||
defer metrics.Time()()
|
||||
req.ParseForm()
|
||||
email, ok := req.Form["email"]
|
||||
if !ok || len(email) != 1 {
|
||||
@@ -158,8 +169,9 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tok, err := s.db.Register(addr.Address)
|
||||
tok, err := s.db.Register(Email(addr.Address))
|
||||
if err := verrors.ToHTTP(err); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
|
||||
http.Error(w, err.Message, err.Code)
|
||||
return
|
||||
}
|
||||
@@ -188,21 +200,24 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) confirm(w http.ResponseWriter, req *http.Request) {
|
||||
defer metrics.Time()()
|
||||
tok := req.URL.Path[len(prefix["confirm"]):]
|
||||
tok = strings.TrimRight(tok, "/")
|
||||
if tok == "" {
|
||||
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tok, err := s.db.Confirm(tok)
|
||||
ttok, err := s.db.Confirm(Token(tok))
|
||||
if err := verrors.ToHTTP(err); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
|
||||
http.Error(w, err.Message, err.Code)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "new token: %s\n", tok)
|
||||
fmt.Fprintf(w, "new token: %s\n", ttok)
|
||||
}
|
||||
|
||||
func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
|
||||
defer metrics.Time()()
|
||||
req.ParseForm()
|
||||
email, ok := req.Form["email"]
|
||||
if !ok || len(email) != 1 {
|
||||
@@ -216,8 +231,9 @@ func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tok, err := s.db.forgot(addr.Address, s.emailTimeout)
|
||||
tok, err := s.db.Forgot(Email(addr.Address), s.emailTimeout)
|
||||
if err := verrors.ToHTTP(err); err != nil {
|
||||
metrics.Errors.WithLabelValues(fmt.Sprintf("%d: %s", err.Code, http.StatusText(err.Code))).Add(1)
|
||||
http.Error(w, err.Message, err.Code)
|
||||
return
|
||||
}
|
||||
@@ -245,12 +261,14 @@ func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) pkgs(w http.ResponseWriter, req *http.Request) {
|
||||
defer metrics.Time()()
|
||||
w.Header().Set("Content-type", "application/json")
|
||||
json.NewEncoder(w).Encode(s.db.Pkgs())
|
||||
}
|
||||
|
||||
func addRoutes(sm *http.ServeMux, s *Server) {
|
||||
sm.Handle("/", s)
|
||||
sm.Handle("/metrics", prometheus.Handler())
|
||||
|
||||
if s.static == "" {
|
||||
sm.Handle(
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
CREATE TABLE users (
|
||||
email TEXT PRIMARY KEY,
|
||||
token TEXT UNIQUE,
|
||||
registered boolean DEFAULT false,
|
||||
requested TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE namespaces (
|
||||
ns TEXT PRIMARY KEY,
|
||||
email TEXT REFERENCES users(email) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE packages (
|
||||
vcs TEXT,
|
||||
repo TEXT,
|
||||
path TEXT UNIQUE,
|
||||
ns TEXT REFERENCES namespaces(ns) ON DELETE CASCADE
|
||||
);
|
||||
-237
@@ -1,237 +0,0 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// sql/init.sql
|
||||
// DO NOT EDIT!
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _sqlInitSql = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x74\x90\xc1\x6e\x83\x30\x10\x44\xef\x7c\xc5\x1c\x41\xea\x1f\xf4\xe4\xc2\x46\x42\x35\x26\x35\x8b\xda\x1c\x5d\xb2\x4d\xa3\x10\x43\x31\xe9\xf7\x57\x0a\x22\x25\x8a\x38\xda\x33\xd2\xbc\xb7\xa9\x25\xc5\x04\x56\x2f\x9a\x70\x09\x32\x04\xc4\x11\x00\xc8\xd9\x1d\x5b\x30\x7d\x30\xb6\x36\x2f\x94\xdd\xe1\x95\x76\x4f\xd7\x6c\xec\x4e\xe2\xa7\xac\x36\xf9\x5b\x4d\xd3\xf7\x20\x87\x63\x18\x65\x90\x3d\x3e\xbb\xae\x15\xe7\x91\xd1\x46\xd5\x9a\xf1\xe5\xda\x20\x73\xeb\xe7\x22\x61\x94\x3d\x38\x2f\xa8\x62\x55\x6c\x61\x4a\x86\xa9\xb5\xbe\xf5\x4d\xf9\x1e\x27\x51\xf2\x1c\x45\x77\x80\xde\x9d\x25\xf4\xae\x91\x99\xd2\x87\x15\xc4\x05\xbe\xa5\x0d\x59\x32\x29\x55\x93\x60\x7c\xcd\x12\x94\x06\x19\x69\x62\x42\xaa\xaa\x54\x65\xf4\xb8\xd7\xbb\xe6\xe4\x0e\xb7\xb5\xdf\x66\x9a\x9b\x45\xfa\x6e\xf1\xec\xdd\xf8\xfd\x78\x93\x19\x70\x01\xf1\x2f\x11\xfb\xb0\x82\xf1\x17\x00\x00\xff\xff\x04\x74\x3c\xff\x97\x01\x00\x00")
|
||||
|
||||
func sqlInitSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_sqlInitSql,
|
||||
"sql/init.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func sqlInitSql() (*asset, error) {
|
||||
bytes, err := sqlInitSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "sql/init.sql", size: 407, mode: os.FileMode(436), modTime: time.Unix(1466486246, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"sql/init.sql": sqlInitSql,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"sql": &bintree{nil, map[string]*bintree{
|
||||
"init.sql": &bintree{sqlInitSql, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
+9
-7
@@ -4,13 +4,15 @@ import "time"
|
||||
|
||||
// Storer defines the db interface.
|
||||
type Storer interface {
|
||||
AddPackage(p Package) error
|
||||
Confirm(token string) (string, error)
|
||||
NSForToken(ns string, tok string) error
|
||||
NSForToken(ns namespace, tok Token) error
|
||||
|
||||
Package(path string) (Package, error)
|
||||
PackageExists(path string) bool
|
||||
AddPackage(p Package) error
|
||||
RemovePackage(pth path) error
|
||||
PackageExists(pth path) bool
|
||||
Pkgs() []Package
|
||||
Register(email string) (string, error)
|
||||
RemovePackage(path string) error
|
||||
forgot(email string, window time.Duration) (string, error)
|
||||
|
||||
Register(e Email) (Token, error)
|
||||
Confirm(tok Token) (Token, error)
|
||||
Forgot(e Email, window time.Duration) (Token, error)
|
||||
}
|
||||
|
||||
+5
-9
@@ -7,25 +7,21 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testDB(t *testing.T) (*DB, func()) {
|
||||
// TestDB returns a populated MemDB in a temp location, as well as a function
|
||||
// to call at cleanup time.
|
||||
func TestDB(t *testing.T) (*MemDB, func()) {
|
||||
dir, err := ioutil.TempDir("", "vain-testing-")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create tmpdir for db: %v", err)
|
||||
return nil, func() {}
|
||||
}
|
||||
name := filepath.Join(dir, "test.db")
|
||||
db, err := NewDB(name)
|
||||
name := filepath.Join(dir, "test.json")
|
||||
db, err := NewMemDB(name)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create db: %v", err)
|
||||
return nil, func() {}
|
||||
}
|
||||
|
||||
if err := db.Init(); err != nil {
|
||||
return nil, func() {}
|
||||
}
|
||||
|
||||
return db, func() {
|
||||
db.Close()
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
t.Fatalf("could not clean up tmpdir: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Package vain implements a vanity service for use by the the go tool.
|
||||
//
|
||||
// The executable, cmd/vaind, is located in the respective subdirectory.
|
||||
package vain
|
||||
|
||||
import (
|
||||
@@ -14,6 +11,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Email is a vain type for storing email addresses.
|
||||
type Email string
|
||||
|
||||
// Token is a vain type for an api token.
|
||||
type Token string
|
||||
|
||||
type namespace string
|
||||
type path string
|
||||
|
||||
var vcss = map[string]bool{
|
||||
"hg": true,
|
||||
"git": true,
|
||||
@@ -38,15 +44,15 @@ type Package struct {
|
||||
// Repo: the remote repository url
|
||||
Repo string `json:"repo"`
|
||||
|
||||
Path string `json:"path"`
|
||||
Ns string `json:"-"`
|
||||
Path string `json:"path"`
|
||||
Ns namespace `json:"-"`
|
||||
}
|
||||
|
||||
// User stores the information about a user including email used, their
|
||||
// token, whether they have registerd and the requested timestamp
|
||||
type User struct {
|
||||
Email string
|
||||
Token string
|
||||
Email Email
|
||||
token Token
|
||||
Registered bool
|
||||
Requested time.Time
|
||||
}
|
||||
@@ -84,17 +90,17 @@ func Valid(p string, packages []Package) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func parseNamespace(path string) (string, error) {
|
||||
func parseNamespace(path string) (namespace, error) {
|
||||
path = strings.TrimLeft(path, "/")
|
||||
if path == "" {
|
||||
return "", errors.New("path does not contain namespace")
|
||||
}
|
||||
elems := strings.Split(path, "/")
|
||||
return elems[0], nil
|
||||
return namespace(elems[0]), nil
|
||||
}
|
||||
|
||||
// FreshToken returns a random token string.
|
||||
func FreshToken() string {
|
||||
func FreshToken() Token {
|
||||
buf := &bytes.Buffer{}
|
||||
io.Copy(buf, io.LimitReader(rand.Reader, 6))
|
||||
s := hex.EncodeToString(buf.Bytes())
|
||||
@@ -102,5 +108,5 @@ func FreshToken() string {
|
||||
for i := 0; i < len(s)/4; i++ {
|
||||
r = append(r, s[i*4:(i+1)*4])
|
||||
}
|
||||
return strings.Join(r, "-")
|
||||
return Token(strings.Join(r, "-"))
|
||||
}
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ func TestValid(t *testing.T) {
|
||||
func TestNamespaceParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
want namespace
|
||||
err error
|
||||
}{
|
||||
{
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013 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.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkInsertTargeted(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
s := NewTargeted(Targets)
|
||||
b.ResetTimer()
|
||||
for i := float64(0); i < float64(b.N); i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
|
||||
s := NewTargeted(TargetsSmallEpsilon)
|
||||
b.ResetTimer()
|
||||
for i := float64(0); i < float64(b.N); i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInsertBiased(b *testing.B) {
|
||||
s := NewLowBiased(0.01)
|
||||
b.ResetTimer()
|
||||
for i := float64(0); i < float64(b.N); i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
|
||||
s := NewLowBiased(0.0001)
|
||||
b.ResetTimer()
|
||||
for i := float64(0); i < float64(b.N); i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQuery(b *testing.B) {
|
||||
s := NewTargeted(Targets)
|
||||
for i := float64(0); i < 1e6; i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
n := float64(b.N)
|
||||
for i := float64(0); i < n; i++ {
|
||||
s.Query(i / n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQuerySmallEpsilon(b *testing.B) {
|
||||
s := NewTargeted(TargetsSmallEpsilon)
|
||||
for i := float64(0); i < 1e6; i++ {
|
||||
s.Insert(i)
|
||||
}
|
||||
b.ResetTimer()
|
||||
n := float64(b.N)
|
||||
for i := float64(0); i < n; i++ {
|
||||
s.Query(i / n)
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// +build go1.1
|
||||
|
||||
package quantile_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/beorn7/perks/quantile"
|
||||
)
|
||||
|
||||
func Example_simple() {
|
||||
ch := make(chan float64)
|
||||
go sendFloats(ch)
|
||||
|
||||
// Compute the 50th, 90th, and 99th percentile.
|
||||
q := quantile.NewTargeted(map[float64]float64{
|
||||
0.50: 0.005,
|
||||
0.90: 0.001,
|
||||
0.99: 0.0001,
|
||||
})
|
||||
for v := range ch {
|
||||
q.Insert(v)
|
||||
}
|
||||
|
||||
fmt.Println("perc50:", q.Query(0.50))
|
||||
fmt.Println("perc90:", q.Query(0.90))
|
||||
fmt.Println("perc99:", q.Query(0.99))
|
||||
fmt.Println("count:", q.Count())
|
||||
// Output:
|
||||
// perc50: 5
|
||||
// perc90: 16
|
||||
// perc99: 223
|
||||
// count: 2388
|
||||
}
|
||||
|
||||
func Example_mergeMultipleStreams() {
|
||||
// Scenario:
|
||||
// We have multiple database shards. On each shard, there is a process
|
||||
// collecting query response times from the database logs and inserting
|
||||
// them into a Stream (created via NewTargeted(0.90)), much like the
|
||||
// Simple example. These processes expose a network interface for us to
|
||||
// ask them to serialize and send us the results of their
|
||||
// Stream.Samples so we may Merge and Query them.
|
||||
//
|
||||
// NOTES:
|
||||
// * These sample sets are small, allowing us to get them
|
||||
// across the network much faster than sending the entire list of data
|
||||
// points.
|
||||
//
|
||||
// * For this to work correctly, we must supply the same quantiles
|
||||
// a priori the process collecting the samples supplied to NewTargeted,
|
||||
// even if we do not plan to query them all here.
|
||||
ch := make(chan quantile.Samples)
|
||||
getDBQuerySamples(ch)
|
||||
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
|
||||
for samples := range ch {
|
||||
q.Merge(samples)
|
||||
}
|
||||
fmt.Println("perc90:", q.Query(0.90))
|
||||
}
|
||||
|
||||
func Example_window() {
|
||||
// Scenario: We want the 90th, 95th, and 99th percentiles for each
|
||||
// minute.
|
||||
|
||||
ch := make(chan float64)
|
||||
go sendStreamValues(ch)
|
||||
|
||||
tick := time.NewTicker(1 * time.Minute)
|
||||
q := quantile.NewTargeted(map[float64]float64{
|
||||
0.90: 0.001,
|
||||
0.95: 0.0005,
|
||||
0.99: 0.0001,
|
||||
})
|
||||
for {
|
||||
select {
|
||||
case t := <-tick.C:
|
||||
flushToDB(t, q.Samples())
|
||||
q.Reset()
|
||||
case v := <-ch:
|
||||
q.Insert(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendStreamValues(ch chan float64) {
|
||||
// Use your imagination
|
||||
}
|
||||
|
||||
func flushToDB(t time.Time, samples quantile.Samples) {
|
||||
// Use your imagination
|
||||
}
|
||||
|
||||
// This is a stub for the above example. In reality this would hit the remote
|
||||
// servers via http or something like it.
|
||||
func getDBQuerySamples(ch chan quantile.Samples) {}
|
||||
|
||||
func sendFloats(ch chan<- float64) {
|
||||
f, err := os.Open("exampledata.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
b := sc.Bytes()
|
||||
v, err := strconv.ParseFloat(string(b), 64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ch <- v
|
||||
}
|
||||
if sc.Err() != nil {
|
||||
log.Fatal(sc.Err())
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
+2388
File diff suppressed because it is too large
Load Diff
+292
@@ -0,0 +1,292 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targets map[float64]float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for quantile, epsilon := range targets {
|
||||
if quantile*s.n <= r {
|
||||
f = (2 * epsilon * r) / quantile
|
||||
} else {
|
||||
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(float64(l) * q)
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
Targets = map[float64]float64{
|
||||
0.01: 0.001,
|
||||
0.10: 0.01,
|
||||
0.50: 0.05,
|
||||
0.90: 0.01,
|
||||
0.99: 0.001,
|
||||
}
|
||||
TargetsSmallEpsilon = map[float64]float64{
|
||||
0.01: 0.0001,
|
||||
0.10: 0.001,
|
||||
0.50: 0.005,
|
||||
0.90: 0.001,
|
||||
0.99: 0.0001,
|
||||
}
|
||||
LowQuantiles = []float64{0.01, 0.1, 0.5}
|
||||
HighQuantiles = []float64{0.99, 0.9, 0.5}
|
||||
)
|
||||
|
||||
const RelativeEpsilon = 0.01
|
||||
|
||||
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||
sort.Float64s(a)
|
||||
for quantile, epsilon := range Targets {
|
||||
n := float64(len(a))
|
||||
k := int(quantile * n)
|
||||
lower := int((quantile - epsilon) * n)
|
||||
if lower < 1 {
|
||||
lower = 1
|
||||
}
|
||||
upper := int(math.Ceil((quantile + epsilon) * n))
|
||||
if upper > len(a) {
|
||||
upper = len(a)
|
||||
}
|
||||
w, min, max := a[k-1], a[lower-1], a[upper-1]
|
||||
if g := s.Query(quantile); g < min || g > max {
|
||||
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||
sort.Float64s(a)
|
||||
for _, qu := range LowQuantiles {
|
||||
n := float64(len(a))
|
||||
k := int(qu * n)
|
||||
|
||||
lowerRank := int((1 - RelativeEpsilon) * qu * n)
|
||||
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
|
||||
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
|
||||
if g := s.Query(qu); g < min || g > max {
|
||||
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||
sort.Float64s(a)
|
||||
for _, qu := range HighQuantiles {
|
||||
n := float64(len(a))
|
||||
k := int(qu * n)
|
||||
|
||||
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
|
||||
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
|
||||
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
|
||||
if g := s.Query(qu); g < min || g > max {
|
||||
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func populateStream(s *Stream) []float64 {
|
||||
a := make([]float64, 0, 1e5+100)
|
||||
for i := 0; i < cap(a); i++ {
|
||||
v := rand.NormFloat64()
|
||||
// Add 5% asymmetric outliers.
|
||||
if i%20 == 0 {
|
||||
v = v*v + 1
|
||||
}
|
||||
s.Insert(v)
|
||||
a = append(a, v)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func TestTargetedQuery(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s := NewTargeted(Targets)
|
||||
a := populateStream(s)
|
||||
verifyPercsWithAbsoluteEpsilon(t, a, s)
|
||||
}
|
||||
|
||||
func TestLowBiasedQuery(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s := NewLowBiased(RelativeEpsilon)
|
||||
a := populateStream(s)
|
||||
verifyLowPercsWithRelativeEpsilon(t, a, s)
|
||||
}
|
||||
|
||||
func TestHighBiasedQuery(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s := NewHighBiased(RelativeEpsilon)
|
||||
a := populateStream(s)
|
||||
verifyHighPercsWithRelativeEpsilon(t, a, s)
|
||||
}
|
||||
|
||||
// BrokenTestTargetedMerge is broken, see Merge doc comment.
|
||||
func BrokenTestTargetedMerge(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s1 := NewTargeted(Targets)
|
||||
s2 := NewTargeted(Targets)
|
||||
a := populateStream(s1)
|
||||
a = append(a, populateStream(s2)...)
|
||||
s1.Merge(s2.Samples())
|
||||
verifyPercsWithAbsoluteEpsilon(t, a, s1)
|
||||
}
|
||||
|
||||
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
|
||||
func BrokenTestLowBiasedMerge(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s1 := NewLowBiased(RelativeEpsilon)
|
||||
s2 := NewLowBiased(RelativeEpsilon)
|
||||
a := populateStream(s1)
|
||||
a = append(a, populateStream(s2)...)
|
||||
s1.Merge(s2.Samples())
|
||||
verifyLowPercsWithRelativeEpsilon(t, a, s2)
|
||||
}
|
||||
|
||||
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
|
||||
func BrokenTestHighBiasedMerge(t *testing.T) {
|
||||
rand.Seed(42)
|
||||
s1 := NewHighBiased(RelativeEpsilon)
|
||||
s2 := NewHighBiased(RelativeEpsilon)
|
||||
a := populateStream(s1)
|
||||
a = append(a, populateStream(s2)...)
|
||||
s1.Merge(s2.Samples())
|
||||
verifyHighPercsWithRelativeEpsilon(t, a, s2)
|
||||
}
|
||||
|
||||
func TestUncompressed(t *testing.T) {
|
||||
q := NewTargeted(Targets)
|
||||
for i := 100; i > 0; i-- {
|
||||
q.Insert(float64(i))
|
||||
}
|
||||
if g := q.Count(); g != 100 {
|
||||
t.Errorf("want count 100, got %d", g)
|
||||
}
|
||||
// Before compression, Query should have 100% accuracy.
|
||||
for quantile := range Targets {
|
||||
w := quantile * 100
|
||||
if g := q.Query(quantile); g != w {
|
||||
t.Errorf("want %f, got %f", w, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUncompressedSamples(t *testing.T) {
|
||||
q := NewTargeted(map[float64]float64{0.99: 0.001})
|
||||
for i := 1; i <= 100; i++ {
|
||||
q.Insert(float64(i))
|
||||
}
|
||||
if g := q.Samples().Len(); g != 100 {
|
||||
t.Errorf("want count 100, got %d", g)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUncompressedOne(t *testing.T) {
|
||||
q := NewTargeted(map[float64]float64{0.99: 0.01})
|
||||
q.Insert(3.14)
|
||||
if g := q.Query(0.90); g != 3.14 {
|
||||
t.Error("want PI, got", g)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
|
||||
t.Errorf("want 0, got %f", g)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+8
-4
@@ -1,16 +1,19 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
@@ -25,3 +28,4 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
test: install generate-test-pbs
|
||||
go test
|
||||
|
||||
|
||||
generate-test-pbs:
|
||||
make install
|
||||
make -C testdata
|
||||
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||
make
|
||||
+2212
File diff suppressed because it is too large
Load Diff
+272
@@ -0,0 +1,272 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
testpb "github.com/golang/protobuf/proto/testdata"
|
||||
anypb "github.com/golang/protobuf/ptypes/any"
|
||||
)
|
||||
|
||||
var (
|
||||
expandedMarshaler = proto.TextMarshaler{ExpandAny: true}
|
||||
expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
|
||||
)
|
||||
|
||||
// anyEqual reports whether two messages which may be google.protobuf.Any or may
|
||||
// contain google.protobuf.Any fields are equal. We can't use proto.Equal for
|
||||
// comparison, because semantically equivalent messages may be marshaled to
|
||||
// binary in different tag order. Instead, trust that TextMarshaler with
|
||||
// ExpandAny option works and compare the text marshaling results.
|
||||
func anyEqual(got, want proto.Message) bool {
|
||||
// if messages are proto.Equal, no need to marshal.
|
||||
if proto.Equal(got, want) {
|
||||
return true
|
||||
}
|
||||
g := expandedMarshaler.Text(got)
|
||||
w := expandedMarshaler.Text(want)
|
||||
return g == w
|
||||
}
|
||||
|
||||
type golden struct {
|
||||
m proto.Message
|
||||
t, c string
|
||||
}
|
||||
|
||||
var goldenMessages = makeGolden()
|
||||
|
||||
func makeGolden() []golden {
|
||||
nested := &pb.Nested{Bunny: "Monty"}
|
||||
nb, err := proto.Marshal(nested)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m1 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m2 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m3 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m4 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}
|
||||
|
||||
any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
|
||||
proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")})
|
||||
proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar"))
|
||||
any1b, err := proto.Marshal(any1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}}
|
||||
proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")})
|
||||
any2b, err := proto.Marshal(any2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m6 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
|
||||
ManyThings: []*anypb.Any{
|
||||
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b},
|
||||
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
m1Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m2Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m3Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
["type.googleapis.com/\"/proto3_proto.Nested"]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m4Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/a/path/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m5Golden = `
|
||||
[type.googleapis.com/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
`
|
||||
m6Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 47
|
||||
name: "David"
|
||||
[testdata.Ext.more]: <
|
||||
data: "foo"
|
||||
>
|
||||
[testdata.Ext.text]: "bar"
|
||||
>
|
||||
>
|
||||
many_things: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 42
|
||||
bikeshed: GREEN
|
||||
rep_bytes: "roboto"
|
||||
[testdata.Ext.more]: <
|
||||
data: "baz"
|
||||
>
|
||||
>
|
||||
>
|
||||
many_things: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 47
|
||||
name: "David"
|
||||
[testdata.Ext.more]: <
|
||||
data: "foo"
|
||||
>
|
||||
[testdata.Ext.text]: "bar"
|
||||
>
|
||||
>
|
||||
`
|
||||
)
|
||||
return []golden{
|
||||
{m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "},
|
||||
{m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "},
|
||||
{m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "},
|
||||
{m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "},
|
||||
{m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "},
|
||||
{m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalGolden(t *testing.T) {
|
||||
for _, tt := range goldenMessages {
|
||||
if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want {
|
||||
t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want)
|
||||
}
|
||||
if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want {
|
||||
t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalGolden(t *testing.T) {
|
||||
for _, tt := range goldenMessages {
|
||||
want := tt.m
|
||||
got := proto.Clone(tt.m)
|
||||
got.Reset()
|
||||
if err := proto.UnmarshalText(tt.t, got); err != nil {
|
||||
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err)
|
||||
}
|
||||
if !anyEqual(got, want) {
|
||||
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want)
|
||||
}
|
||||
got.Reset()
|
||||
if err := proto.UnmarshalText(tt.c, got); err != nil {
|
||||
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err)
|
||||
}
|
||||
if !anyEqual(got, want) {
|
||||
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarsahlUnknownAny(t *testing.T) {
|
||||
m := &pb.Message{
|
||||
Anything: &anypb.Any{
|
||||
TypeUrl: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
}
|
||||
want := `anything: <
|
||||
type_url: "foo"
|
||||
value: "bar"
|
||||
>
|
||||
`
|
||||
got := expandedMarshaler.Text(m)
|
||||
if got != want {
|
||||
t.Errorf("got\n`%s`\nwant\n`%s`", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmbiguousAny(t *testing.T) {
|
||||
pb := &anypb.Any{}
|
||||
err := proto.UnmarshalText(`
|
||||
[type.googleapis.com/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
type_url: "ttt/proto3_proto.Nested"
|
||||
`, pb)
|
||||
t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse ambiguous Any message: %v", err)
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer deep copy and merge.
|
||||
// TODO: RawMessage.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clone returns a deep copy of a protocol buffer.
|
||||
func Clone(pb Message) Message {
|
||||
in := reflect.ValueOf(pb)
|
||||
if in.IsNil() {
|
||||
return pb
|
||||
}
|
||||
|
||||
out := reflect.New(in.Type().Elem())
|
||||
// out is empty so a merge is a deep copy.
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
return out.Interface().(Message)
|
||||
}
|
||||
|
||||
// Merge merges src into dst.
|
||||
// Required and optional fields that are set in src will be set to that value in dst.
|
||||
// Elements of repeated fields will be appended.
|
||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||
func Merge(dst, src Message) {
|
||||
in := reflect.ValueOf(src)
|
||||
out := reflect.ValueOf(dst)
|
||||
if out.IsNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if in.Type() != out.Type() {
|
||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||
panic("proto: type mismatch")
|
||||
}
|
||||
if in.IsNil() {
|
||||
// Merging nil into non-nil is a quiet no-op
|
||||
return
|
||||
}
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
}
|
||||
|
||||
func mergeStruct(out, in reflect.Value) {
|
||||
sprop := GetProperties(in.Type())
|
||||
for i := 0; i < in.NumField(); i++ {
|
||||
f := in.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||
}
|
||||
|
||||
if emIn, ok := in.Addr().Interface().(extendableProto); ok {
|
||||
emOut := out.Addr().Interface().(extendableProto)
|
||||
mergeExtension(emOut.ExtensionMap(), emIn.ExtensionMap())
|
||||
}
|
||||
|
||||
uf := in.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return
|
||||
}
|
||||
uin := uf.Bytes()
|
||||
if len(uin) > 0 {
|
||||
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||
}
|
||||
}
|
||||
|
||||
// mergeAny performs a merge between two values of the same type.
|
||||
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||
// prop is set if this is a struct field (it may be nil).
|
||||
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||
if in.Type() == protoMessageType {
|
||||
if !in.IsNil() {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||
} else {
|
||||
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
if !viaPtr && isProto3Zero(in) {
|
||||
return
|
||||
}
|
||||
out.Set(in)
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; copy non-nil values.
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
// Allocate destination if it is not set, or set to a different type.
|
||||
// Otherwise we will merge as normal.
|
||||
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||
case reflect.Map:
|
||||
if in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(in.Type()))
|
||||
}
|
||||
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||
elemKind := in.Type().Elem().Kind()
|
||||
for _, key := range in.MapKeys() {
|
||||
var val reflect.Value
|
||||
switch elemKind {
|
||||
case reflect.Ptr:
|
||||
val = reflect.New(in.Type().Elem().Elem())
|
||||
mergeAny(val, in.MapIndex(key), false, nil)
|
||||
case reflect.Slice:
|
||||
val = in.MapIndex(key)
|
||||
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||
default:
|
||||
val = in.MapIndex(key)
|
||||
}
|
||||
out.SetMapIndex(key, val)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(in.Elem().Type()))
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||
case reflect.Slice:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// []byte is a scalar bytes field, not a repeated field.
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value, and should not
|
||||
// be merged.
|
||||
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a deep copy.
|
||||
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||
// with a nil result.
|
||||
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||
return
|
||||
}
|
||||
n := in.Len()
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||
}
|
||||
switch in.Type().Elem().Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
out.Set(reflect.AppendSlice(out, in))
|
||||
default:
|
||||
for i := 0; i < n; i++ {
|
||||
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||
mergeAny(x, in.Index(i), false, nil)
|
||||
out.Set(reflect.Append(out, x))
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
mergeStruct(out, in)
|
||||
default:
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to copy %v", in)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeExtension(out, in map[int32]Extension) {
|
||||
for extNum, eIn := range in {
|
||||
eOut := Extension{desc: eIn.desc}
|
||||
if eIn.value != nil {
|
||||
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||
eOut.value = v.Interface()
|
||||
}
|
||||
if eIn.enc != nil {
|
||||
eOut.enc = make([]byte, len(eIn.enc))
|
||||
copy(eOut.enc, eIn.enc)
|
||||
}
|
||||
|
||||
out[extNum] = eOut
|
||||
}
|
||||
}
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
var cloneTestMessage = &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("niles"),
|
||||
Port: proto.Int32(9099),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ext := &pb.Ext{
|
||||
Data: proto.String("extension"),
|
||||
}
|
||||
if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil {
|
||||
panic("SetExtension: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
m := proto.Clone(cloneTestMessage).(*pb.MyMessage)
|
||||
if !proto.Equal(m, cloneTestMessage) {
|
||||
t.Errorf("Clone(%v) = %v", cloneTestMessage, m)
|
||||
}
|
||||
|
||||
// Verify it was a deep copy.
|
||||
*m.Inner.Port++
|
||||
if proto.Equal(m, cloneTestMessage) {
|
||||
t.Error("Mutating clone changed the original")
|
||||
}
|
||||
// Byte fields and repeated fields should be copied.
|
||||
if &m.Pet[0] == &cloneTestMessage.Pet[0] {
|
||||
t.Error("Pet: repeated field not copied")
|
||||
}
|
||||
if &m.Others[0] == &cloneTestMessage.Others[0] {
|
||||
t.Error("Others: repeated field not copied")
|
||||
}
|
||||
if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] {
|
||||
t.Error("Others[0].Value: bytes field not copied")
|
||||
}
|
||||
if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] {
|
||||
t.Error("RepBytes: repeated field not copied")
|
||||
}
|
||||
if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] {
|
||||
t.Error("RepBytes[0]: bytes field not copied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneNil(t *testing.T) {
|
||||
var m *pb.MyMessage
|
||||
if c := proto.Clone(m); !proto.Equal(m, c) {
|
||||
t.Errorf("Clone(%v) = %v", m, c)
|
||||
}
|
||||
}
|
||||
|
||||
var mergeTests = []struct {
|
||||
src, dst, want proto.Message
|
||||
}{
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Name: proto.String("Dave"),
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
},
|
||||
},
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("hey"),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Pet: []string{"horsey"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("niles"),
|
||||
Port: proto.Int32(9099),
|
||||
},
|
||||
Pet: []string{"bunny", "kitty"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(31415926535),
|
||||
},
|
||||
{
|
||||
// Explicitly test a src=nil field
|
||||
Inner: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("hey"),
|
||||
Connected: proto.Bool(true),
|
||||
Port: proto.Int32(9099),
|
||||
},
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(31415926535),
|
||||
},
|
||||
{},
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
RepBytes: [][]byte{[]byte("wow")},
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham")},
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||
},
|
||||
},
|
||||
// Check that a scalar bytes field replaces rather than appends.
|
||||
{
|
||||
src: &pb.OtherMessage{Value: []byte("foo")},
|
||||
dst: &pb.OtherMessage{Value: []byte("bar")},
|
||||
want: &pb.OtherMessage{Value: []byte("foo")},
|
||||
},
|
||||
{
|
||||
src: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{6: "Nigel"},
|
||||
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||
},
|
||||
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||
},
|
||||
dst: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
6: "Bruce", // should be overwritten
|
||||
7: "Andrew",
|
||||
},
|
||||
},
|
||||
want: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
6: "Nigel",
|
||||
7: "Andrew",
|
||||
},
|
||||
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||
},
|
||||
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||
},
|
||||
},
|
||||
// proto3 shouldn't merge zero values,
|
||||
// in the same way that proto2 shouldn't merge nils.
|
||||
{
|
||||
src: &proto3pb.Message{
|
||||
Name: "Aaron",
|
||||
Data: []byte(""), // zero value, but not nil
|
||||
},
|
||||
dst: &proto3pb.Message{
|
||||
HeightInCm: 176,
|
||||
Data: []byte("texas!"),
|
||||
},
|
||||
want: &proto3pb.Message{
|
||||
Name: "Aaron",
|
||||
HeightInCm: 176,
|
||||
Data: []byte("texas!"),
|
||||
},
|
||||
},
|
||||
// Oneof fields should merge by assignment.
|
||||
{
|
||||
src: &pb.Communique{
|
||||
Union: &pb.Communique_Number{41},
|
||||
},
|
||||
dst: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
want: &pb.Communique{
|
||||
Union: &pb.Communique_Number{41},
|
||||
},
|
||||
},
|
||||
// Oneof nil is the same as not set.
|
||||
{
|
||||
src: &pb.Communique{},
|
||||
dst: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
want: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
for _, m := range mergeTests {
|
||||
got := proto.Clone(m.dst)
|
||||
proto.Merge(got, m.src)
|
||||
if !proto.Equal(got, m.want) {
|
||||
t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+868
@@ -0,0 +1,868 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// errOverflow is returned when an integer is too large to be represented.
|
||||
var errOverflow = errors.New("proto: integer overflow")
|
||||
|
||||
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||
// wire type is encountered. It does not get returned to user code.
|
||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||
|
||||
// The fundamental decoders that interpret bytes on the wire.
|
||||
// Those that take integer types all return uint64 and are
|
||||
// therefore of type valueDecoder.
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||
// It returns the integer and the number of bytes consumed, or
|
||||
// zero if there is not enough.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||
// x, n already 0
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if n >= len(buf) {
|
||||
return 0, 0
|
||||
}
|
||||
b := uint64(buf[n])
|
||||
n++
|
||||
x |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, n
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
|
||||
i := p.index
|
||||
l := len(p.buf)
|
||||
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if i >= l {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
b := p.buf[i]
|
||||
i++
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
p.index = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
err = errOverflow
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed64, sfixed64, and double protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 8
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-8])
|
||||
x |= uint64(p.buf[i-7]) << 8
|
||||
x |= uint64(p.buf[i-6]) << 16
|
||||
x |= uint64(p.buf[i-5]) << 24
|
||||
x |= uint64(p.buf[i-4]) << 32
|
||||
x |= uint64(p.buf[i-3]) << 40
|
||||
x |= uint64(p.buf[i-2]) << 48
|
||||
x |= uint64(p.buf[i-1]) << 56
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed32, sfixed32, and float protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 4
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-4])
|
||||
x |= uint64(p.buf[i-3]) << 8
|
||||
x |= uint64(p.buf[i-2]) << 16
|
||||
x |= uint64(p.buf[i-1]) << 24
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint32 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||
return
|
||||
}
|
||||
|
||||
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||
// bytes, embedded messages
|
||||
|
||||
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||
// This is the format used for the bytes protocol buffer
|
||||
// type and for embedded messages.
|
||||
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||
n, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nb := int(n)
|
||||
if nb < 0 {
|
||||
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||
}
|
||||
end := p.index + nb
|
||||
if end < p.index || end > len(p.buf) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
if !alloc {
|
||||
// todo: check if can get more uses of alloc=false
|
||||
buf = p.buf[p.index:end]
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
buf = make([]byte, nb)
|
||||
copy(buf, p.buf[p.index:])
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||
// This is the format used for the proto2 string type.
|
||||
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||
buf, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||
oi := o.index
|
||||
|
||||
err := o.skip(t, tag, wire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unrecField.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ptr := structPointer_Bytes(base, unrecField)
|
||||
|
||||
// Add the skipped field to struct field
|
||||
obuf := o.buf
|
||||
|
||||
o.buf = *ptr
|
||||
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||
|
||||
o.buf = obuf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
switch wire {
|
||||
case WireVarint:
|
||||
_, err = o.DecodeVarint()
|
||||
case WireFixed64:
|
||||
_, err = o.DecodeFixed64()
|
||||
case WireBytes:
|
||||
_, err = o.DecodeRawBytes(false)
|
||||
case WireFixed32:
|
||||
_, err = o.DecodeFixed32()
|
||||
case WireStartGroup:
|
||||
for {
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fwire := int(u & 0x7)
|
||||
if fwire == WireEndGroup {
|
||||
break
|
||||
}
|
||||
ftag := int(u >> 3)
|
||||
err = o.skip(t, ftag, fwire)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The method should reset the receiver before
|
||||
// decoding starts. The argument points to data that may be
|
||||
// overwritten, so implementations should not keep references to the
|
||||
// buffer.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||
// decoded result in pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// Unmarshal resets pb before starting to unmarshal, so any
|
||||
// existing data in pb is always removed. Use UnmarshalMerge
|
||||
// to preserve and append to existing data.
|
||||
func Unmarshal(buf []byte, pb Message) error {
|
||||
pb.Reset()
|
||||
return UnmarshalMerge(buf, pb)
|
||||
}
|
||||
|
||||
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// UnmarshalMerge merges into existing data in pb.
|
||||
// Most code should use Unmarshal instead.
|
||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||
enc, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return NewBuffer(enc).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in the
|
||||
// Buffer and places the decoded result in pb. If the struct
|
||||
// underlying pb does not match the data in the buffer, the results can be
|
||||
// unpredictable.
|
||||
func (p *Buffer) Unmarshal(pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
err := u.Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||
|
||||
if collectStats {
|
||||
stats.Decode++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshalType does the work of unmarshaling a structure.
|
||||
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||
var state errorState
|
||||
required, reqFields := prop.reqCount, uint64(0)
|
||||
|
||||
var err error
|
||||
for err == nil && o.index < len(o.buf) {
|
||||
oi := o.index
|
||||
var u uint64
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wire := int(u & 0x7)
|
||||
if wire == WireEndGroup {
|
||||
if is_group {
|
||||
return nil // input is satisfied
|
||||
}
|
||||
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||
}
|
||||
tag := int(u >> 3)
|
||||
if tag <= 0 {
|
||||
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||
}
|
||||
fieldnum, ok := prop.decoderTags.get(tag)
|
||||
if !ok {
|
||||
// Maybe it's an extension?
|
||||
if prop.extendable {
|
||||
if e := structPointer_Interface(base, st).(extendableProto); isExtensionField(e, int32(tag)) {
|
||||
if err = o.skip(st, tag, wire); err == nil {
|
||||
ext := e.ExtensionMap()[int32(tag)] // may be missing
|
||||
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||
e.ExtensionMap()[int32(tag)] = ext
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Maybe it's a oneof?
|
||||
if prop.oneofUnmarshaler != nil {
|
||||
m := structPointer_Interface(base, st).(Message)
|
||||
// First return value indicates whether tag is a oneof field.
|
||||
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||
if err == ErrInternalBadWireType {
|
||||
// Map the error to something more descriptive.
|
||||
// Do the formatting here to save generated code space.
|
||||
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||
continue
|
||||
}
|
||||
p := prop.Prop[fieldnum]
|
||||
|
||||
if p.dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||
continue
|
||||
}
|
||||
dec := p.dec
|
||||
if wire != WireStartGroup && wire != p.WireType {
|
||||
if wire == WireBytes && p.packedDec != nil {
|
||||
// a packable field
|
||||
dec = p.packedDec
|
||||
} else {
|
||||
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
decErr := dec(o, p, base)
|
||||
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||
err = decErr
|
||||
}
|
||||
if err == nil && p.Required {
|
||||
// Successfully decoded a required field.
|
||||
if tag <= 64 {
|
||||
// use bitmap for fields 1-64 to catch field reuse.
|
||||
var mask uint64 = 1 << uint64(tag-1)
|
||||
if reqFields&mask == 0 {
|
||||
// new required field
|
||||
reqFields |= mask
|
||||
required--
|
||||
}
|
||||
} else {
|
||||
// This is imprecise. It can be fooled by a required field
|
||||
// with a tag > 64 that is encoded twice; that's very rare.
|
||||
// A fully correct implementation would require allocating
|
||||
// a data structure, which we would like to avoid.
|
||||
required--
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if is_group {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if state.err != nil {
|
||||
return state.err
|
||||
}
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field. If we use extra
|
||||
// CPU, we could determine the field only if the missing required field
|
||||
// has a tag <= 64 and we check reqFields.
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Individual type decoders
|
||||
// For each,
|
||||
// u is the decoded value,
|
||||
// v is a pointer to the field (pointer) in the struct
|
||||
|
||||
// Sizes of the pools to allocate inside the Buffer.
|
||||
// The goal is modest amortization and allocation
|
||||
// on at least 16-byte boundaries.
|
||||
const (
|
||||
boolPoolSize = 16
|
||||
uint32PoolSize = 8
|
||||
uint64PoolSize = 4
|
||||
)
|
||||
|
||||
// Decode a bool.
|
||||
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.bools) == 0 {
|
||||
o.bools = make([]bool, boolPoolSize)
|
||||
}
|
||||
o.bools[0] = u != 0
|
||||
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||
o.bools = o.bools[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_BoolVal(base, p.field) = u != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int32.
|
||||
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int64.
|
||||
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a string.
|
||||
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_String(base, p.field) = &s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_StringVal(base, p.field) = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bytes ([]byte).
|
||||
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_Bytes(base, p.field) = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool).
|
||||
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
*v = append(*v, u != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded bools
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
|
||||
y := *v
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y = append(y, u != 0)
|
||||
}
|
||||
|
||||
*v = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32).
|
||||
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word32Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int32s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(uint32(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64).
|
||||
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
structPointer_Word64Slice(base, p.field).Append(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word64Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int64s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of strings ([]string).
|
||||
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_StringSlice(base, p.field)
|
||||
*v = append(*v, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of slice of bytes ([][]byte).
|
||||
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BytesSlice(base, p.field)
|
||||
*v = append(*v, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a map field.
|
||||
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oi := o.index // index at the end of this map entry
|
||||
o.index -= len(raw) // move buffer back to start of map entry
|
||||
|
||||
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||
if mptr.Elem().IsNil() {
|
||||
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||
}
|
||||
v := mptr.Elem() // map[K]V
|
||||
|
||||
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||
// See enc_new_map for why.
|
||||
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||
|
||||
var valbase structPointer
|
||||
var valptr reflect.Value
|
||||
switch p.mtype.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
// []byte
|
||||
var dummy []byte
|
||||
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||
valbase = toStructPointer(valptr) // *[]byte
|
||||
case reflect.Ptr:
|
||||
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||
valbase = toStructPointer(valptr)
|
||||
default:
|
||||
// everything else
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valbase = toStructPointer(valptr.Addr()) // **V
|
||||
}
|
||||
|
||||
// Decode.
|
||||
// This parses a restricted wire format, namely the encoding of a message
|
||||
// with two fields. See enc_new_map for the format.
|
||||
for o.index < oi {
|
||||
// tagcode for key and value properties are always a single byte
|
||||
// because they have tags 1 and 2.
|
||||
tagcode := o.buf[o.index]
|
||||
o.index++
|
||||
switch tagcode {
|
||||
case p.mkeyprop.tagcode[0]:
|
||||
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||
return err
|
||||
}
|
||||
case p.mvalprop.tagcode[0]:
|
||||
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO: Should we silently skip this instead?
|
||||
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||
}
|
||||
}
|
||||
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||
if !keyelem.IsValid() {
|
||||
keyelem = reflect.Zero(p.mtype.Key())
|
||||
}
|
||||
if !valelem.IsValid() {
|
||||
valelem = reflect.Zero(p.mtype.Elem())
|
||||
}
|
||||
|
||||
v.SetMapIndex(keyelem, valelem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a group.
|
||||
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||
}
|
||||
|
||||
// Decode an embedded message.
|
||||
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||
raw, e := o.DecodeRawBytes(false)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := structPointer_Interface(bas, p.stype)
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode a slice of embedded messages.
|
||||
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, false, base)
|
||||
}
|
||||
|
||||
// Decode a slice of embedded groups.
|
||||
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, true, base)
|
||||
}
|
||||
|
||||
// Decode a slice of structs ([]*struct).
|
||||
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||
v := reflect.New(p.stype)
|
||||
bas := toStructPointer(v)
|
||||
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||
|
||||
if is_group {
|
||||
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := v.Interface()
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
+1331
File diff suppressed because it is too large
Load Diff
+276
@@ -0,0 +1,276 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer comparison.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Equal returns true iff protocol buffers a and b are equal.
|
||||
The arguments must both be pointers to protocol buffer structs.
|
||||
|
||||
Equality is defined in this way:
|
||||
- Two messages are equal iff they are the same type,
|
||||
corresponding fields are equal, unknown field sets
|
||||
are equal, and extensions sets are equal.
|
||||
- Two set scalar fields are equal iff their values are equal.
|
||||
If the fields are of a floating-point type, remember that
|
||||
NaN != x for all x, including NaN. If the message is defined
|
||||
in a proto3 .proto file, fields are not "set"; specifically,
|
||||
zero length proto3 "bytes" fields are equal (nil == {}).
|
||||
- Two repeated fields are equal iff their lengths are the same,
|
||||
and their corresponding elements are equal (a "bytes" field,
|
||||
although represented by []byte, is not a repeated field)
|
||||
- Two unset fields are equal.
|
||||
- Two unknown field sets are equal if their current
|
||||
encoded state is equal.
|
||||
- Two extension sets are equal iff they have corresponding
|
||||
elements that are pairwise equal.
|
||||
- Every other combination of things are not equal.
|
||||
|
||||
The return value is undefined if a and b are not protocol buffers.
|
||||
*/
|
||||
func Equal(a, b Message) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||
if v1.Type() != v2.Type() {
|
||||
return false
|
||||
}
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if v1.IsNil() {
|
||||
return v2.IsNil()
|
||||
}
|
||||
if v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
v1, v2 = v1.Elem(), v2.Elem()
|
||||
}
|
||||
if v1.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
return equalStruct(v1, v2)
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
func equalStruct(v1, v2 reflect.Value) bool {
|
||||
sprop := GetProperties(v1.Type())
|
||||
for i := 0; i < v1.NumField(); i++ {
|
||||
f := v1.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
f1, f2 := v1.Field(i), v2.Field(i)
|
||||
if f.Type.Kind() == reflect.Ptr {
|
||||
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||
// both unset
|
||||
continue
|
||||
} else if n1 != n2 {
|
||||
// set/unset mismatch
|
||||
return false
|
||||
}
|
||||
b1, ok := f1.Interface().(raw)
|
||||
if ok {
|
||||
b2 := f2.Interface().(raw)
|
||||
// RawMessage
|
||||
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
f1, f2 = f1.Elem(), f2.Elem()
|
||||
}
|
||||
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||
em2 := v2.FieldByName("XXX_extensions")
|
||||
if !equalExtensions(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
uf := v1.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
u1 := uf.Bytes()
|
||||
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||
if !bytes.Equal(u1, u2) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
// prop may be nil.
|
||||
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
|
||||
if v1.Type() == protoMessageType {
|
||||
m1, _ := v1.Interface().(Message)
|
||||
m2, _ := v2.Interface().(Message)
|
||||
return Equal(m1, m2)
|
||||
}
|
||||
switch v1.Kind() {
|
||||
case reflect.Bool:
|
||||
return v1.Bool() == v2.Bool()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v1.Float() == v2.Float()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v1.Int() == v2.Int()
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; compare the inner values.
|
||||
n1, n2 := v1.IsNil(), v2.IsNil()
|
||||
if n1 || n2 {
|
||||
return n1 == n2
|
||||
}
|
||||
e1, e2 := v1.Elem(), v2.Elem()
|
||||
if e1.Type() != e2.Type() {
|
||||
return false
|
||||
}
|
||||
return equalAny(e1, e2, nil)
|
||||
case reflect.Map:
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for _, key := range v1.MapKeys() {
|
||||
val2 := v2.MapIndex(key)
|
||||
if !val2.IsValid() {
|
||||
// This key was not found in the second map.
|
||||
return false
|
||||
}
|
||||
if !equalAny(v1.MapIndex(key), val2, nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Ptr:
|
||||
return equalAny(v1.Elem(), v2.Elem(), prop)
|
||||
case reflect.Slice:
|
||||
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// short circuit: []byte
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value.
|
||||
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||
}
|
||||
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !equalAny(v1.Index(i), v2.Index(i), prop) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.String:
|
||||
return v1.Interface().(string) == v2.Interface().(string)
|
||||
case reflect.Struct:
|
||||
return equalStruct(v1, v2)
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v1.Uint() == v2.Uint()
|
||||
}
|
||||
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to compare %v", v1)
|
||||
return false
|
||||
}
|
||||
|
||||
// base is the struct type that the extensions are based on.
|
||||
// em1 and em2 are extension maps.
|
||||
func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||
if len(em1) != len(em2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for extNum, e1 := range em1 {
|
||||
e2, ok := em2[extNum]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
m1, m2 := e1.value, e2.value
|
||||
|
||||
if m1 != nil && m2 != nil {
|
||||
// Both are unencoded.
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// At least one is encoded. To do a semantically correct comparison
|
||||
// we need to unmarshal them first.
|
||||
var desc *ExtensionDesc
|
||||
if m := extensionMaps[base]; m != nil {
|
||||
desc = m[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
if m1 == nil {
|
||||
m1, err = decodeExtension(e1.enc, desc)
|
||||
}
|
||||
if m2 == nil && err == nil {
|
||||
m2, err = decodeExtension(e2.enc, desc)
|
||||
}
|
||||
if err != nil {
|
||||
// The encoded form is invalid.
|
||||
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||
return false
|
||||
}
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
// Four identical base messages.
|
||||
// The init function adds extensions to some of them.
|
||||
var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)}
|
||||
|
||||
// Two messages with non-message extensions.
|
||||
var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)}
|
||||
var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)}
|
||||
|
||||
func init() {
|
||||
ext1 := &pb.Ext{Data: String("Kirk")}
|
||||
ext2 := &pb.Ext{Data: String("Picard")}
|
||||
|
||||
// messageWithExtension1a has ext1, but never marshals it.
|
||||
if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil {
|
||||
panic("SetExtension on 1a failed: " + err.Error())
|
||||
}
|
||||
|
||||
// messageWithExtension1b is the unmarshaled form of messageWithExtension1a.
|
||||
if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil {
|
||||
panic("SetExtension on 1b failed: " + err.Error())
|
||||
}
|
||||
buf, err := Marshal(messageWithExtension1b)
|
||||
if err != nil {
|
||||
panic("Marshal of 1b failed: " + err.Error())
|
||||
}
|
||||
messageWithExtension1b.Reset()
|
||||
if err := Unmarshal(buf, messageWithExtension1b); err != nil {
|
||||
panic("Unmarshal of 1b failed: " + err.Error())
|
||||
}
|
||||
|
||||
// messageWithExtension2 has ext2.
|
||||
if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil {
|
||||
panic("SetExtension on 2 failed: " + err.Error())
|
||||
}
|
||||
|
||||
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil {
|
||||
panic("SetExtension on Int32-1 failed: " + err.Error())
|
||||
}
|
||||
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil {
|
||||
panic("SetExtension on Int32-2 failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var EqualTests = []struct {
|
||||
desc string
|
||||
a, b Message
|
||||
exp bool
|
||||
}{
|
||||
{"different types", &pb.GoEnum{}, &pb.GoTestField{}, false},
|
||||
{"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true},
|
||||
{"nil vs nil", nil, nil, true},
|
||||
{"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true},
|
||||
{"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false},
|
||||
{"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false},
|
||||
|
||||
{"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false},
|
||||
{"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false},
|
||||
{"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false},
|
||||
{"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true},
|
||||
|
||||
{"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false},
|
||||
{"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false},
|
||||
{"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false},
|
||||
{"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true},
|
||||
{"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||
{"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true},
|
||||
{"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||
|
||||
{
|
||||
"nested, different",
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}},
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"nested, equal",
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||
true,
|
||||
},
|
||||
|
||||
{"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true},
|
||||
{"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true},
|
||||
{"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false},
|
||||
{
|
||||
"repeated bytes",
|
||||
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||
true,
|
||||
},
|
||||
// In proto3, []byte{} and []byte(nil) are equal.
|
||||
{"proto3 bytes, empty vs nil", &proto3pb.Message{Data: []byte{}}, &proto3pb.Message{Data: nil}, true},
|
||||
|
||||
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
|
||||
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},
|
||||
{"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false},
|
||||
|
||||
{"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true},
|
||||
{"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false},
|
||||
|
||||
{
|
||||
"message with group",
|
||||
&pb.MyMessage{
|
||||
Count: Int32(1),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: Int32(5),
|
||||
},
|
||||
},
|
||||
&pb.MyMessage{
|
||||
Count: Int32(1),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: Int32(5),
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"map same",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"map different entry",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"map different key only",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"map different value only",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"oneof same",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"oneof one nil",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"oneof different",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{Union: &pb.Communique_Name{"Bobby Tables"}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for _, tc := range EqualTests {
|
||||
if res := Equal(tc.a, tc.b); res != tc.exp {
|
||||
t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Types and routines for supporting protocol buffer extensions.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
|
||||
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||
|
||||
// ExtensionRange represents a range of message extensions for a protocol buffer.
|
||||
// Used in code generated by the protocol compiler.
|
||||
type ExtensionRange struct {
|
||||
Start, End int32 // both inclusive
|
||||
}
|
||||
|
||||
// extendableProto is an interface implemented by any protocol buffer that may be extended.
|
||||
type extendableProto interface {
|
||||
Message
|
||||
ExtensionRangeArray() []ExtensionRange
|
||||
ExtensionMap() map[int32]Extension
|
||||
}
|
||||
|
||||
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||
|
||||
// ExtensionDesc represents an extension specification.
|
||||
// Used in generated code from the protocol compiler.
|
||||
type ExtensionDesc struct {
|
||||
ExtendedType Message // nil pointer to the type that is being extended
|
||||
ExtensionType interface{} // nil pointer to the extension type
|
||||
Field int32 // field number
|
||||
Name string // fully-qualified name of extension, for text formatting
|
||||
Tag string // protobuf tag style
|
||||
}
|
||||
|
||||
func (ed *ExtensionDesc) repeated() bool {
|
||||
t := reflect.TypeOf(ed.ExtensionType)
|
||||
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
||||
}
|
||||
|
||||
// Extension represents an extension in a message.
|
||||
type Extension struct {
|
||||
// When an extension is stored in a message using SetExtension
|
||||
// only desc and value are set. When the message is marshaled
|
||||
// enc will be set to the encoded form of the message.
|
||||
//
|
||||
// When a message is unmarshaled and contains extensions, each
|
||||
// extension will have only enc set. When such an extension is
|
||||
// accessed using GetExtension (or GetExtensions) desc and value
|
||||
// will be set.
|
||||
desc *ExtensionDesc
|
||||
value interface{}
|
||||
enc []byte
|
||||
}
|
||||
|
||||
// SetRawExtension is for testing only.
|
||||
func SetRawExtension(base extendableProto, id int32, b []byte) {
|
||||
base.ExtensionMap()[id] = Extension{enc: b}
|
||||
}
|
||||
|
||||
// isExtensionField returns true iff the given field number is in an extension range.
|
||||
func isExtensionField(pb extendableProto, field int32) bool {
|
||||
for _, er := range pb.ExtensionRangeArray() {
|
||||
if er.Start <= field && field <= er.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkExtensionTypes checks that the given extension is valid for pb.
|
||||
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
||||
// Check the extended type.
|
||||
if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||
}
|
||||
// Check the range.
|
||||
if !isExtensionField(pb, extension.Field) {
|
||||
return errors.New("proto: bad extension number; not in declared ranges")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extPropKey is sufficient to uniquely identify an extension.
|
||||
type extPropKey struct {
|
||||
base reflect.Type
|
||||
field int32
|
||||
}
|
||||
|
||||
var extProp = struct {
|
||||
sync.RWMutex
|
||||
m map[extPropKey]*Properties
|
||||
}{
|
||||
m: make(map[extPropKey]*Properties),
|
||||
}
|
||||
|
||||
func extensionProperties(ed *ExtensionDesc) *Properties {
|
||||
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
|
||||
|
||||
extProp.RLock()
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
extProp.RUnlock()
|
||||
return prop
|
||||
}
|
||||
extProp.RUnlock()
|
||||
|
||||
extProp.Lock()
|
||||
defer extProp.Unlock()
|
||||
// Check again.
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
return prop
|
||||
}
|
||||
|
||||
prop := new(Properties)
|
||||
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
|
||||
extProp.m[key] = prop
|
||||
return prop
|
||||
}
|
||||
|
||||
// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m.
|
||||
func encodeExtensionMap(m map[int32]Extension) error {
|
||||
for k, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
p := NewBuffer(nil)
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.enc = p.buf
|
||||
m[k] = e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sizeExtensionMap(m map[int32]Extension) (n int) {
|
||||
for _, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
n += len(e.enc)
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
n += props.size(props, toStructPointer(x))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasExtension returns whether the given extension is present in pb.
|
||||
func HasExtension(pb extendableProto, extension *ExtensionDesc) bool {
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
_, ok := pb.ExtensionMap()[extension.Field]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ClearExtension removes the given extension from pb.
|
||||
func ClearExtension(pb extendableProto, extension *ExtensionDesc) {
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
delete(pb.ExtensionMap(), extension.Field)
|
||||
}
|
||||
|
||||
// GetExtension parses and returns the given extension of pb.
|
||||
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
||||
func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) {
|
||||
if err := checkExtensionTypes(pb, extension); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emap := pb.ExtensionMap()
|
||||
e, ok := emap[extension.Field]
|
||||
if !ok {
|
||||
// defaultExtensionValue returns the default value or
|
||||
// ErrMissingExtension if there is no default.
|
||||
return defaultExtensionValue(extension)
|
||||
}
|
||||
|
||||
if e.value != nil {
|
||||
// Already decoded. Check the descriptor, though.
|
||||
if e.desc != extension {
|
||||
// This shouldn't happen. If it does, it means that
|
||||
// GetExtension was called twice with two different
|
||||
// descriptors with the same field number.
|
||||
return nil, errors.New("proto: descriptor conflict")
|
||||
}
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
v, err := decodeExtension(e.enc, extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remember the decoded version and drop the encoded version.
|
||||
// That way it is safe to mutate what we return.
|
||||
e.value = v
|
||||
e.desc = extension
|
||||
e.enc = nil
|
||||
emap[extension.Field] = e
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
// defaultExtensionValue returns the default value for extension.
|
||||
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
props := extensionProperties(extension)
|
||||
|
||||
sf, _, err := fieldDefault(t, props)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sf == nil || sf.value == nil {
|
||||
// There is no default value.
|
||||
return nil, ErrMissingExtension
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Ptr {
|
||||
// We do not need to return a Ptr, we can directly return sf.value.
|
||||
return sf.value, nil
|
||||
}
|
||||
|
||||
// We need to return an interface{} that is a pointer to sf.value.
|
||||
value := reflect.New(t).Elem()
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
if sf.kind == reflect.Int32 {
|
||||
// We may have an int32 or an enum, but the underlying data is int32.
|
||||
// Since we can't set an int32 into a non int32 reflect.value directly
|
||||
// set it as a int32.
|
||||
value.Elem().SetInt(int64(sf.value.(int32)))
|
||||
} else {
|
||||
value.Elem().Set(reflect.ValueOf(sf.value))
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// decodeExtension decodes an extension encoded in b.
|
||||
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||
o := NewBuffer(b)
|
||||
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
|
||||
props := extensionProperties(extension)
|
||||
|
||||
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||
// Allocate a "field" to store the pointer/slice itself; the
|
||||
// pointer/slice will be stored here. We pass
|
||||
// the address of this field to props.dec.
|
||||
// This passes a zero field and a *t and lets props.dec
|
||||
// interpret it as a *struct{ x t }.
|
||||
value := reflect.New(t).Elem()
|
||||
|
||||
for {
|
||||
// Discard wire type and field number varint. It isn't needed.
|
||||
if _, err := o.DecodeVarint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.index >= len(o.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||
epb, ok := pb.(extendableProto)
|
||||
if !ok {
|
||||
err = errors.New("proto: not an extendable proto")
|
||||
return
|
||||
}
|
||||
extensions = make([]interface{}, len(es))
|
||||
for i, e := range es {
|
||||
extensions[i], err = GetExtension(epb, e)
|
||||
if err == ErrMissingExtension {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetExtension sets the specified extension of pb to the specified value.
|
||||
func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error {
|
||||
if err := checkExtensionTypes(pb, extension); err != nil {
|
||||
return err
|
||||
}
|
||||
typ := reflect.TypeOf(extension.ExtensionType)
|
||||
if typ != reflect.TypeOf(value) {
|
||||
return errors.New("proto: bad extension value type")
|
||||
}
|
||||
// nil extension values need to be caught early, because the
|
||||
// encoder can't distinguish an ErrNil due to a nil extension
|
||||
// from an ErrNil due to a missing field. Extensions are
|
||||
// always optional, so the encoder would just swallow the error
|
||||
// and drop all the extensions from the encoded message.
|
||||
if reflect.ValueOf(value).IsNil() {
|
||||
return fmt.Errorf("proto: SetExtension called with nil value of type %T", value)
|
||||
}
|
||||
|
||||
pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A global registry of extensions.
|
||||
// The generated code will register the generated descriptors by calling RegisterExtension.
|
||||
|
||||
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
|
||||
|
||||
// RegisterExtension is called from the generated code.
|
||||
func RegisterExtension(desc *ExtensionDesc) {
|
||||
st := reflect.TypeOf(desc.ExtendedType).Elem()
|
||||
m := extensionMaps[st]
|
||||
if m == nil {
|
||||
m = make(map[int32]*ExtensionDesc)
|
||||
extensionMaps[st] = m
|
||||
}
|
||||
if _, ok := m[desc.Field]; ok {
|
||||
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
|
||||
}
|
||||
m[desc.Field] = desc
|
||||
}
|
||||
|
||||
// RegisteredExtensions returns a map of the registered extensions of a
|
||||
// protocol buffer struct, indexed by the extension number.
|
||||
// The argument pb should be a nil pointer to the struct type.
|
||||
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||
}
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
func TestGetExtensionsWithMissingExtensions(t *testing.T) {
|
||||
msg := &pb.MyMessage{}
|
||||
ext1 := &pb.Ext{}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||
t.Fatalf("Could not set ext1: %s", ext1)
|
||||
}
|
||||
exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{
|
||||
pb.E_Ext_More,
|
||||
pb.E_Ext_Text,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtensions() failed: %s", err)
|
||||
}
|
||||
if exts[0] != ext1 {
|
||||
t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0])
|
||||
}
|
||||
if exts[1] != nil {
|
||||
t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtensionStability(t *testing.T) {
|
||||
check := func(m *pb.MyMessage) bool {
|
||||
ext1, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtension() failed: %s", err)
|
||||
}
|
||||
ext2, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtension() failed: %s", err)
|
||||
}
|
||||
return ext1 == ext2
|
||||
}
|
||||
msg := &pb.MyMessage{Count: proto.Int32(4)}
|
||||
ext0 := &pb.Ext{}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil {
|
||||
t.Fatalf("Could not set ext1: %s", ext0)
|
||||
}
|
||||
if !check(msg) {
|
||||
t.Errorf("GetExtension() not stable before marshaling")
|
||||
}
|
||||
bb, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal() failed: %s", err)
|
||||
}
|
||||
msg1 := &pb.MyMessage{}
|
||||
err = proto.Unmarshal(bb, msg1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal() failed: %s", err)
|
||||
}
|
||||
if !check(msg1) {
|
||||
t.Errorf("GetExtension() not stable after unmarshaling")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtensionDefaults(t *testing.T) {
|
||||
var setFloat64 float64 = 1
|
||||
var setFloat32 float32 = 2
|
||||
var setInt32 int32 = 3
|
||||
var setInt64 int64 = 4
|
||||
var setUint32 uint32 = 5
|
||||
var setUint64 uint64 = 6
|
||||
var setBool = true
|
||||
var setBool2 = false
|
||||
var setString = "Goodnight string"
|
||||
var setBytes = []byte("Goodnight bytes")
|
||||
var setEnum = pb.DefaultsMessage_TWO
|
||||
|
||||
type testcase struct {
|
||||
ext *proto.ExtensionDesc // Extension we are testing.
|
||||
want interface{} // Expected value of extension, or nil (meaning that GetExtension will fail).
|
||||
def interface{} // Expected value of extension after ClearExtension().
|
||||
}
|
||||
tests := []testcase{
|
||||
{pb.E_NoDefaultDouble, setFloat64, nil},
|
||||
{pb.E_NoDefaultFloat, setFloat32, nil},
|
||||
{pb.E_NoDefaultInt32, setInt32, nil},
|
||||
{pb.E_NoDefaultInt64, setInt64, nil},
|
||||
{pb.E_NoDefaultUint32, setUint32, nil},
|
||||
{pb.E_NoDefaultUint64, setUint64, nil},
|
||||
{pb.E_NoDefaultSint32, setInt32, nil},
|
||||
{pb.E_NoDefaultSint64, setInt64, nil},
|
||||
{pb.E_NoDefaultFixed32, setUint32, nil},
|
||||
{pb.E_NoDefaultFixed64, setUint64, nil},
|
||||
{pb.E_NoDefaultSfixed32, setInt32, nil},
|
||||
{pb.E_NoDefaultSfixed64, setInt64, nil},
|
||||
{pb.E_NoDefaultBool, setBool, nil},
|
||||
{pb.E_NoDefaultBool, setBool2, nil},
|
||||
{pb.E_NoDefaultString, setString, nil},
|
||||
{pb.E_NoDefaultBytes, setBytes, nil},
|
||||
{pb.E_NoDefaultEnum, setEnum, nil},
|
||||
{pb.E_DefaultDouble, setFloat64, float64(3.1415)},
|
||||
{pb.E_DefaultFloat, setFloat32, float32(3.14)},
|
||||
{pb.E_DefaultInt32, setInt32, int32(42)},
|
||||
{pb.E_DefaultInt64, setInt64, int64(43)},
|
||||
{pb.E_DefaultUint32, setUint32, uint32(44)},
|
||||
{pb.E_DefaultUint64, setUint64, uint64(45)},
|
||||
{pb.E_DefaultSint32, setInt32, int32(46)},
|
||||
{pb.E_DefaultSint64, setInt64, int64(47)},
|
||||
{pb.E_DefaultFixed32, setUint32, uint32(48)},
|
||||
{pb.E_DefaultFixed64, setUint64, uint64(49)},
|
||||
{pb.E_DefaultSfixed32, setInt32, int32(50)},
|
||||
{pb.E_DefaultSfixed64, setInt64, int64(51)},
|
||||
{pb.E_DefaultBool, setBool, true},
|
||||
{pb.E_DefaultBool, setBool2, true},
|
||||
{pb.E_DefaultString, setString, "Hello, string"},
|
||||
{pb.E_DefaultBytes, setBytes, []byte("Hello, bytes")},
|
||||
{pb.E_DefaultEnum, setEnum, pb.DefaultsMessage_ONE},
|
||||
}
|
||||
|
||||
checkVal := func(test testcase, msg *pb.DefaultsMessage, valWant interface{}) error {
|
||||
val, err := proto.GetExtension(msg, test.ext)
|
||||
if err != nil {
|
||||
if valWant != nil {
|
||||
return fmt.Errorf("GetExtension(): %s", err)
|
||||
}
|
||||
if want := proto.ErrMissingExtension; err != want {
|
||||
return fmt.Errorf("Unexpected error: got %v, want %v", err, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// All proto2 extension values are either a pointer to a value or a slice of values.
|
||||
ty := reflect.TypeOf(val)
|
||||
tyWant := reflect.TypeOf(test.ext.ExtensionType)
|
||||
if got, want := ty, tyWant; got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf(): got %v want %v", got, want)
|
||||
}
|
||||
tye := ty.Elem()
|
||||
tyeWant := tyWant.Elem()
|
||||
if got, want := tye, tyeWant; got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf().Elem(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
// Check the name of the type of the value.
|
||||
// If it is an enum it will be type int32 with the name of the enum.
|
||||
if got, want := tye.Name(), tye.Name(); got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf().Elem().Name(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
// Check that value is what we expect.
|
||||
// If we have a pointer in val, get the value it points to.
|
||||
valExp := val
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
valExp = reflect.ValueOf(val).Elem().Interface()
|
||||
}
|
||||
if got, want := valExp, valWant; !reflect.DeepEqual(got, want) {
|
||||
return fmt.Errorf("unexpected reflect.DeepEqual(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
setTo := func(test testcase) interface{} {
|
||||
setTo := reflect.ValueOf(test.want)
|
||||
if typ := reflect.TypeOf(test.ext.ExtensionType); typ.Kind() == reflect.Ptr {
|
||||
setTo = reflect.New(typ).Elem()
|
||||
setTo.Set(reflect.New(setTo.Type().Elem()))
|
||||
setTo.Elem().Set(reflect.ValueOf(test.want))
|
||||
}
|
||||
return setTo.Interface()
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
msg := &pb.DefaultsMessage{}
|
||||
name := test.ext.Name
|
||||
|
||||
// Check the initial value.
|
||||
if err := checkVal(test, msg, test.def); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
// Set the per-type value and check value.
|
||||
name = fmt.Sprintf("%s (set to %T %v)", name, test.want, test.want)
|
||||
if err := proto.SetExtension(msg, test.ext, setTo(test)); err != nil {
|
||||
t.Errorf("%s: SetExtension(): %v", name, err)
|
||||
continue
|
||||
}
|
||||
if err := checkVal(test, msg, test.want); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Set and check the value.
|
||||
name += " (cleared)"
|
||||
proto.ClearExtension(msg, test.ext)
|
||||
if err := checkVal(test, msg, test.def); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionsRoundTrip(t *testing.T) {
|
||||
msg := &pb.MyMessage{}
|
||||
ext1 := &pb.Ext{
|
||||
Data: proto.String("hi"),
|
||||
}
|
||||
ext2 := &pb.Ext{
|
||||
Data: proto.String("there"),
|
||||
}
|
||||
exists := proto.HasExtension(msg, pb.E_Ext_More)
|
||||
if exists {
|
||||
t.Error("Extension More present unexpectedly")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
x, ok := e.(*pb.Ext)
|
||||
if !ok {
|
||||
t.Errorf("e has type %T, expected testdata.Ext", e)
|
||||
} else if *x.Data != "there" {
|
||||
t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x)
|
||||
}
|
||||
proto.ClearExtension(msg, pb.E_Ext_More)
|
||||
if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension {
|
||||
t.Errorf("got %v, expected ErrMissingExtension", e)
|
||||
}
|
||||
if _, err := proto.GetExtension(msg, pb.E_X215); err == nil {
|
||||
t.Error("expected bad extension error, got nil")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil {
|
||||
t.Error("expected extension err")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil {
|
||||
t.Error("expected some sort of type mismatch error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilExtension(t *testing.T) {
|
||||
msg := &pb.MyMessage{
|
||||
Count: proto.Int32(1),
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_Text, proto.String("hello")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, (*pb.Ext)(nil)); err == nil {
|
||||
t.Error("expected SetExtension to fail due to a nil extension")
|
||||
} else if want := "proto: SetExtension called with nil value of type *testdata.Ext"; err.Error() != want {
|
||||
t.Errorf("expected error %v, got %v", want, err)
|
||||
}
|
||||
// Note: if the behavior of Marshal is ever changed to ignore nil extensions, update
|
||||
// this test to verify that E_Ext_Text is properly propagated through marshal->unmarshal.
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalRepeatedExtension(t *testing.T) {
|
||||
// Add a repeated extension to the result.
|
||||
tests := []struct {
|
||||
name string
|
||||
ext []*pb.ComplexExtension
|
||||
}{
|
||||
{
|
||||
"two fields",
|
||||
[]*pb.ComplexExtension{
|
||||
{First: proto.Int32(7)},
|
||||
{Second: proto.Int32(11)},
|
||||
},
|
||||
},
|
||||
{
|
||||
"repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two fields and repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{First: proto.Int32(9)},
|
||||
{Second: proto.Int32(21)},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Marshal message with a repeated extension.
|
||||
msg1 := new(pb.OtherMessage)
|
||||
err := proto.SetExtension(msg1, pb.E_RComplex, test.ext)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error setting extension: %v", test.name, err)
|
||||
}
|
||||
b, err := proto.Marshal(msg1)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error marshaling message: %v", test.name, err)
|
||||
}
|
||||
|
||||
// Unmarshal and read the merged proto.
|
||||
msg2 := new(pb.OtherMessage)
|
||||
err = proto.Unmarshal(b, msg2)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg2, pb.E_RComplex)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
|
||||
}
|
||||
ext := e.([]*pb.ComplexExtension)
|
||||
if ext == nil {
|
||||
t.Fatalf("[%s] Invalid extension", test.name)
|
||||
}
|
||||
if !reflect.DeepEqual(ext, test.ext) {
|
||||
t.Errorf("[%s] Wrong value for ComplexExtension: got: %v want: %v\n", test.name, ext, test.ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalRepeatingNonRepeatedExtension(t *testing.T) {
|
||||
// We may see multiple instances of the same extension in the wire
|
||||
// format. For example, the proto compiler may encode custom options in
|
||||
// this way. Here, we verify that we merge the extensions together.
|
||||
tests := []struct {
|
||||
name string
|
||||
ext []*pb.ComplexExtension
|
||||
}{
|
||||
{
|
||||
"two fields",
|
||||
[]*pb.ComplexExtension{
|
||||
{First: proto.Int32(7)},
|
||||
{Second: proto.Int32(11)},
|
||||
},
|
||||
},
|
||||
{
|
||||
"repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two fields and repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{First: proto.Int32(9)},
|
||||
{Second: proto.Int32(21)},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
var buf bytes.Buffer
|
||||
var want pb.ComplexExtension
|
||||
|
||||
// Generate a serialized representation of a repeated extension
|
||||
// by catenating bytes together.
|
||||
for i, e := range test.ext {
|
||||
// Merge to create the wanted proto.
|
||||
proto.Merge(&want, e)
|
||||
|
||||
// serialize the message
|
||||
msg := new(pb.OtherMessage)
|
||||
err := proto.SetExtension(msg, pb.E_Complex, e)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error setting extension %d: %v", test.name, i, err)
|
||||
}
|
||||
b, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error marshaling message %d: %v", test.name, i, err)
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
|
||||
// Unmarshal and read the merged proto.
|
||||
msg2 := new(pb.OtherMessage)
|
||||
err := proto.Unmarshal(buf.Bytes(), msg2)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg2, pb.E_Complex)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
|
||||
}
|
||||
ext := e.(*pb.ComplexExtension)
|
||||
if ext == nil {
|
||||
t.Fatalf("[%s] Invalid extension", test.name)
|
||||
}
|
||||
if !reflect.DeepEqual(*ext, want) {
|
||||
t.Errorf("[%s] Wrong value for ComplexExtension: got: %s want: %s\n", test.name, ext, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+894
@@ -0,0 +1,894 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
Package proto converts data structures to and from the wire format of
|
||||
protocol buffers. It works in concert with the Go source code generated
|
||||
for .proto files by the protocol compiler.
|
||||
|
||||
A summary of the properties of the protocol buffer interface
|
||||
for a protocol buffer variable v:
|
||||
|
||||
- Names are turned from camel_case to CamelCase for export.
|
||||
- There are no methods on v to set fields; just treat
|
||||
them as structure fields.
|
||||
- There are getters that return a field's value if set,
|
||||
and return the field's default value if unset.
|
||||
The getters work even if the receiver is a nil message.
|
||||
- The zero value for a struct is its correct initialization state.
|
||||
All desired fields must be set before marshaling.
|
||||
- A Reset() method will restore a protobuf struct to its zero state.
|
||||
- Non-repeated fields are pointers to the values; nil means unset.
|
||||
That is, optional or required field int32 f becomes F *int32.
|
||||
- Repeated fields are slices.
|
||||
- Helper functions are available to aid the setting of fields.
|
||||
msg.Foo = proto.String("hello") // set field
|
||||
- Constants are defined to hold the default values of all fields that
|
||||
have them. They have the form Default_StructName_FieldName.
|
||||
Because the getter methods handle defaulted values,
|
||||
direct use of these constants should be rare.
|
||||
- Enums are given type names and maps from names to values.
|
||||
Enum values are prefixed by the enclosing message's name, or by the
|
||||
enum's type name if it is a top-level enum. Enum types have a String
|
||||
method, and a Enum method to assist in message construction.
|
||||
- Nested messages, groups and enums have type names prefixed with the name of
|
||||
the surrounding message type.
|
||||
- Extensions are given descriptor names that start with E_,
|
||||
followed by an underscore-delimited list of the nested messages
|
||||
that contain it (if any) followed by the CamelCased name of the
|
||||
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||
and SetExtension are functions for manipulating extensions.
|
||||
- Oneof field sets are given a single field in their message,
|
||||
with distinguished wrapper types for each possible field value.
|
||||
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
The simplest way to describe this is to see an example.
|
||||
Given file test.proto, containing
|
||||
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; }
|
||||
|
||||
message Test {
|
||||
required string label = 1;
|
||||
optional int32 type = 2 [default=77];
|
||||
repeated int64 reps = 3;
|
||||
optional group OptionalGroup = 4 {
|
||||
required string RequiredField = 5;
|
||||
}
|
||||
oneof union {
|
||||
int32 number = 6;
|
||||
string name = 7;
|
||||
}
|
||||
}
|
||||
|
||||
The resulting file, test.pb.go, is:
|
||||
|
||||
package example
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
|
||||
type FOO int32
|
||||
const (
|
||||
FOO_X FOO = 17
|
||||
)
|
||||
var FOO_name = map[int32]string{
|
||||
17: "X",
|
||||
}
|
||||
var FOO_value = map[string]int32{
|
||||
"X": 17,
|
||||
}
|
||||
|
||||
func (x FOO) Enum() *FOO {
|
||||
p := new(FOO)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x FOO) String() string {
|
||||
return proto.EnumName(FOO_name, int32(x))
|
||||
}
|
||||
func (x *FOO) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(FOO_value, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = FOO(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||
// Types that are valid to be assigned to Union:
|
||||
// *Test_Number
|
||||
// *Test_Name
|
||||
Union isTest_Union `protobuf_oneof:"union"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
func (m *Test) Reset() { *m = Test{} }
|
||||
func (m *Test) String() string { return proto.CompactTextString(m) }
|
||||
func (*Test) ProtoMessage() {}
|
||||
|
||||
type isTest_Union interface {
|
||||
isTest_Union()
|
||||
}
|
||||
|
||||
type Test_Number struct {
|
||||
Number int32 `protobuf:"varint,6,opt,name=number"`
|
||||
}
|
||||
type Test_Name struct {
|
||||
Name string `protobuf:"bytes,7,opt,name=name"`
|
||||
}
|
||||
|
||||
func (*Test_Number) isTest_Union() {}
|
||||
func (*Test_Name) isTest_Union() {}
|
||||
|
||||
func (m *Test) GetUnion() isTest_Union {
|
||||
if m != nil {
|
||||
return m.Union
|
||||
}
|
||||
return nil
|
||||
}
|
||||
const Default_Test_Type int32 = 77
|
||||
|
||||
func (m *Test) GetLabel() string {
|
||||
if m != nil && m.Label != nil {
|
||||
return *m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetType() int32 {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
}
|
||||
return Default_Test_Type
|
||||
}
|
||||
|
||||
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||
if m != nil {
|
||||
return m.Optionalgroup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test_OptionalGroup struct {
|
||||
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||
}
|
||||
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
||||
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
||||
|
||||
func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||
if m != nil && m.RequiredField != nil {
|
||||
return *m.RequiredField
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetNumber() int32 {
|
||||
if x, ok := m.GetUnion().(*Test_Number); ok {
|
||||
return x.Number
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Test) GetName() string {
|
||||
if x, ok := m.GetUnion().(*Test_Name); ok {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||
}
|
||||
|
||||
To create and play with a Test object:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "./example.pb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
test := &pb.Test{
|
||||
Label: proto.String("hello"),
|
||||
Type: proto.Int32(17),
|
||||
Reps: []int64{1, 2, 3},
|
||||
Optionalgroup: &pb.Test_OptionalGroup{
|
||||
RequiredField: proto.String("good bye"),
|
||||
},
|
||||
Union: &pb.Test_Name{"fred"},
|
||||
}
|
||||
data, err := proto.Marshal(test)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
newTest := &pb.Test{}
|
||||
err = proto.Unmarshal(data, newTest)
|
||||
if err != nil {
|
||||
log.Fatal("unmarshaling error: ", err)
|
||||
}
|
||||
// Now test and newTest contain the same data.
|
||||
if test.GetLabel() != newTest.GetLabel() {
|
||||
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||
}
|
||||
// Use a type switch to determine which oneof was set.
|
||||
switch u := test.Union.(type) {
|
||||
case *pb.Test_Number: // u.Number contains the number.
|
||||
case *pb.Test_Name: // u.Name contains the string.
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
*/
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Message is implemented by generated protocol buffer messages.
|
||||
type Message interface {
|
||||
Reset()
|
||||
String() string
|
||||
ProtoMessage()
|
||||
}
|
||||
|
||||
// Stats records allocation details about the protocol buffer encoders
|
||||
// and decoders. Useful for tuning the library itself.
|
||||
type Stats struct {
|
||||
Emalloc uint64 // mallocs in encode
|
||||
Dmalloc uint64 // mallocs in decode
|
||||
Encode uint64 // number of encodes
|
||||
Decode uint64 // number of decodes
|
||||
Chit uint64 // number of cache hits
|
||||
Cmiss uint64 // number of cache misses
|
||||
Size uint64 // number of sizes
|
||||
}
|
||||
|
||||
// Set to true to enable stats collection.
|
||||
const collectStats = false
|
||||
|
||||
var stats Stats
|
||||
|
||||
// GetStats returns a copy of the global Stats structure.
|
||||
func GetStats() Stats { return stats }
|
||||
|
||||
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||
// protocol buffers. It may be reused between invocations to
|
||||
// reduce memory usage. It is not necessary to use a Buffer;
|
||||
// the global functions Marshal and Unmarshal create a
|
||||
// temporary Buffer and are fine for most applications.
|
||||
type Buffer struct {
|
||||
buf []byte // encode/decode byte stream
|
||||
index int // write point
|
||||
|
||||
// pools of basic types to amortize allocation.
|
||||
bools []bool
|
||||
uint32s []uint32
|
||||
uint64s []uint64
|
||||
|
||||
// extra pools, only used with pointer_reflect.go
|
||||
int32s []int32
|
||||
int64s []int64
|
||||
float32s []float32
|
||||
float64s []float64
|
||||
}
|
||||
|
||||
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||
// the contents of the argument slice.
|
||||
func NewBuffer(e []byte) *Buffer {
|
||||
return &Buffer{buf: e}
|
||||
}
|
||||
|
||||
// Reset resets the Buffer, ready for marshaling a new protocol buffer.
|
||||
func (p *Buffer) Reset() {
|
||||
p.buf = p.buf[0:0] // for reading/writing
|
||||
p.index = 0 // for reading
|
||||
}
|
||||
|
||||
// SetBuf replaces the internal buffer with the slice,
|
||||
// ready for unmarshaling the contents of the slice.
|
||||
func (p *Buffer) SetBuf(s []byte) {
|
||||
p.buf = s
|
||||
p.index = 0
|
||||
}
|
||||
|
||||
// Bytes returns the contents of the Buffer.
|
||||
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||
|
||||
/*
|
||||
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||
*/
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value
|
||||
// to store v and returns a pointer to it.
|
||||
func Bool(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int32 is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int32(v int32) *int32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it, but unlike Int32
|
||||
// its argument value is an int.
|
||||
func Int(v int) *int32 {
|
||||
p := new(int32)
|
||||
*p = int32(v)
|
||||
return p
|
||||
}
|
||||
|
||||
// Int64 is a helper routine that allocates a new int64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int64(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float32 is a helper routine that allocates a new float32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float32(v float32) *float32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float64 is a helper routine that allocates a new float64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float64(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint32 is a helper routine that allocates a new uint32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint32(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint64 is a helper routine that allocates a new uint64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint64(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// EnumName is a helper function to simplify printing protocol buffer enums
|
||||
// by name. Given an enum map and a value, it returns a useful string.
|
||||
func EnumName(m map[int32]string, v int32) string {
|
||||
s, ok := m[v]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
// UnmarshalJSONEnum is a helper function to simplify recovering enum int values
|
||||
// from their JSON-encoded representation. Given a map from the enum's symbolic
|
||||
// names to its int values, and a byte buffer containing the JSON-encoded
|
||||
// value, it returns an int32 that can be cast to the enum type by the caller.
|
||||
//
|
||||
// The function can deal with both JSON representations, numeric and symbolic.
|
||||
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||
if data[0] == '"' {
|
||||
// New style: enums are strings.
|
||||
var repr string
|
||||
if err := json.Unmarshal(data, &repr); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
val, ok := m[repr]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
// Old style: enums are ints.
|
||||
var val int32
|
||||
if err := json.Unmarshal(data, &val); err != nil {
|
||||
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// DebugPrint dumps the encoded data in b in a debugging format with a header
|
||||
// including the string s. Used in testing but made available for general debugging.
|
||||
func (p *Buffer) DebugPrint(s string, b []byte) {
|
||||
var u uint64
|
||||
|
||||
obuf := p.buf
|
||||
index := p.index
|
||||
p.buf = b
|
||||
p.index = 0
|
||||
depth := 0
|
||||
|
||||
fmt.Printf("\n--- %s ---\n", s)
|
||||
|
||||
out:
|
||||
for {
|
||||
for i := 0; i < depth; i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
|
||||
index := p.index
|
||||
if index == len(p.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
op, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: fetching op err %v\n", index, err)
|
||||
break out
|
||||
}
|
||||
tag := op >> 3
|
||||
wire := op & 7
|
||||
|
||||
switch wire {
|
||||
default:
|
||||
fmt.Printf("%3d: t=%3d unknown wire=%d\n",
|
||||
index, tag, wire)
|
||||
break out
|
||||
|
||||
case WireBytes:
|
||||
var r []byte
|
||||
|
||||
r, err = p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r))
|
||||
if len(r) <= 6 {
|
||||
for i := 0; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
fmt.Printf(" ..")
|
||||
for i := len(r) - 3; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
case WireFixed32:
|
||||
u, err = p.DecodeFixed32()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u)
|
||||
|
||||
case WireFixed64:
|
||||
u, err = p.DecodeFixed64()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u)
|
||||
|
||||
case WireVarint:
|
||||
u, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u)
|
||||
|
||||
case WireStartGroup:
|
||||
fmt.Printf("%3d: t=%3d start\n", index, tag)
|
||||
depth++
|
||||
|
||||
case WireEndGroup:
|
||||
depth--
|
||||
fmt.Printf("%3d: t=%3d end\n", index, tag)
|
||||
}
|
||||
}
|
||||
|
||||
if depth != 0 {
|
||||
fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
p.buf = obuf
|
||||
p.index = index
|
||||
}
|
||||
|
||||
// SetDefaults sets unset protocol buffer fields to their default values.
|
||||
// It only modifies fields that are both unset and have defined defaults.
|
||||
// It recursively sets default values in any non-nil sub-messages.
|
||||
func SetDefaults(pb Message) {
|
||||
setDefaults(reflect.ValueOf(pb), true, false)
|
||||
}
|
||||
|
||||
// v is a pointer to a struct.
|
||||
func setDefaults(v reflect.Value, recur, zeros bool) {
|
||||
v = v.Elem()
|
||||
|
||||
defaultMu.RLock()
|
||||
dm, ok := defaults[v.Type()]
|
||||
defaultMu.RUnlock()
|
||||
if !ok {
|
||||
dm = buildDefaultMessage(v.Type())
|
||||
defaultMu.Lock()
|
||||
defaults[v.Type()] = dm
|
||||
defaultMu.Unlock()
|
||||
}
|
||||
|
||||
for _, sf := range dm.scalars {
|
||||
f := v.Field(sf.index)
|
||||
if !f.IsNil() {
|
||||
// field already set
|
||||
continue
|
||||
}
|
||||
dv := sf.value
|
||||
if dv == nil && !zeros {
|
||||
// no explicit default, and don't want to set zeros
|
||||
continue
|
||||
}
|
||||
fptr := f.Addr().Interface() // **T
|
||||
// TODO: Consider batching the allocations we do here.
|
||||
switch sf.kind {
|
||||
case reflect.Bool:
|
||||
b := new(bool)
|
||||
if dv != nil {
|
||||
*b = dv.(bool)
|
||||
}
|
||||
*(fptr.(**bool)) = b
|
||||
case reflect.Float32:
|
||||
f := new(float32)
|
||||
if dv != nil {
|
||||
*f = dv.(float32)
|
||||
}
|
||||
*(fptr.(**float32)) = f
|
||||
case reflect.Float64:
|
||||
f := new(float64)
|
||||
if dv != nil {
|
||||
*f = dv.(float64)
|
||||
}
|
||||
*(fptr.(**float64)) = f
|
||||
case reflect.Int32:
|
||||
// might be an enum
|
||||
if ft := f.Type(); ft != int32PtrType {
|
||||
// enum
|
||||
f.Set(reflect.New(ft.Elem()))
|
||||
if dv != nil {
|
||||
f.Elem().SetInt(int64(dv.(int32)))
|
||||
}
|
||||
} else {
|
||||
// int32 field
|
||||
i := new(int32)
|
||||
if dv != nil {
|
||||
*i = dv.(int32)
|
||||
}
|
||||
*(fptr.(**int32)) = i
|
||||
}
|
||||
case reflect.Int64:
|
||||
i := new(int64)
|
||||
if dv != nil {
|
||||
*i = dv.(int64)
|
||||
}
|
||||
*(fptr.(**int64)) = i
|
||||
case reflect.String:
|
||||
s := new(string)
|
||||
if dv != nil {
|
||||
*s = dv.(string)
|
||||
}
|
||||
*(fptr.(**string)) = s
|
||||
case reflect.Uint8:
|
||||
// exceptional case: []byte
|
||||
var b []byte
|
||||
if dv != nil {
|
||||
db := dv.([]byte)
|
||||
b = make([]byte, len(db))
|
||||
copy(b, db)
|
||||
} else {
|
||||
b = []byte{}
|
||||
}
|
||||
*(fptr.(*[]byte)) = b
|
||||
case reflect.Uint32:
|
||||
u := new(uint32)
|
||||
if dv != nil {
|
||||
*u = dv.(uint32)
|
||||
}
|
||||
*(fptr.(**uint32)) = u
|
||||
case reflect.Uint64:
|
||||
u := new(uint64)
|
||||
if dv != nil {
|
||||
*u = dv.(uint64)
|
||||
}
|
||||
*(fptr.(**uint64)) = u
|
||||
default:
|
||||
log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ni := range dm.nested {
|
||||
f := v.Field(ni)
|
||||
// f is *T or []*T or map[T]*T
|
||||
switch f.Kind() {
|
||||
case reflect.Ptr:
|
||||
if f.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(f, recur, zeros)
|
||||
|
||||
case reflect.Slice:
|
||||
for i := 0; i < f.Len(); i++ {
|
||||
e := f.Index(i)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for _, k := range f.MapKeys() {
|
||||
e := f.MapIndex(k)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// defaults maps a protocol buffer struct type to a slice of the fields,
|
||||
// with its scalar fields set to their proto-declared non-zero default values.
|
||||
defaultMu sync.RWMutex
|
||||
defaults = make(map[reflect.Type]defaultMessage)
|
||||
|
||||
int32PtrType = reflect.TypeOf((*int32)(nil))
|
||||
)
|
||||
|
||||
// defaultMessage represents information about the default values of a message.
|
||||
type defaultMessage struct {
|
||||
scalars []scalarField
|
||||
nested []int // struct field index of nested messages
|
||||
}
|
||||
|
||||
type scalarField struct {
|
||||
index int // struct field index
|
||||
kind reflect.Kind // element type (the T in *T or []T)
|
||||
value interface{} // the proto-declared default value, or nil
|
||||
}
|
||||
|
||||
// t is a struct type.
|
||||
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
|
||||
sprop := GetProperties(t)
|
||||
for _, prop := range sprop.Prop {
|
||||
fi, ok := sprop.decoderTags.get(prop.Tag)
|
||||
if !ok {
|
||||
// XXX_unrecognized
|
||||
continue
|
||||
}
|
||||
ft := t.Field(fi).Type
|
||||
|
||||
sf, nested, err := fieldDefault(ft, prop)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Print(err)
|
||||
case nested:
|
||||
dm.nested = append(dm.nested, fi)
|
||||
case sf != nil:
|
||||
sf.index = fi
|
||||
dm.scalars = append(dm.scalars, *sf)
|
||||
}
|
||||
}
|
||||
|
||||
return dm
|
||||
}
|
||||
|
||||
// fieldDefault returns the scalarField for field type ft.
|
||||
// sf will be nil if the field can not have a default.
|
||||
// nestedMessage will be true if this is a nested message.
|
||||
// Note that sf.index is not set on return.
|
||||
func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) {
|
||||
var canHaveDefault bool
|
||||
switch ft.Kind() {
|
||||
case reflect.Ptr:
|
||||
if ft.Elem().Kind() == reflect.Struct {
|
||||
nestedMessage = true
|
||||
} else {
|
||||
canHaveDefault = true // proto2 scalar field
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Ptr:
|
||||
nestedMessage = true // repeated message
|
||||
case reflect.Uint8:
|
||||
canHaveDefault = true // bytes field
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if ft.Elem().Kind() == reflect.Ptr {
|
||||
nestedMessage = true // map with message values
|
||||
}
|
||||
}
|
||||
|
||||
if !canHaveDefault {
|
||||
if nestedMessage {
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// We now know that ft is a pointer or slice.
|
||||
sf = &scalarField{kind: ft.Elem().Kind()}
|
||||
|
||||
// scalar fields without defaults
|
||||
if !prop.HasDefault {
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// a scalar field: either *T or []byte
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
x, err := strconv.ParseBool(prop.Default)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Float32:
|
||||
x, err := strconv.ParseFloat(prop.Default, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = float32(x)
|
||||
case reflect.Float64:
|
||||
x, err := strconv.ParseFloat(prop.Default, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Int32:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = int32(x)
|
||||
case reflect.Int64:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.String:
|
||||
sf.value = prop.Default
|
||||
case reflect.Uint8:
|
||||
// []byte (not *uint8)
|
||||
sf.value = []byte(prop.Default)
|
||||
case reflect.Uint32:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = uint32(x)
|
||||
case reflect.Uint64:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
default:
|
||||
return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind())
|
||||
}
|
||||
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// Map fields may have key types of non-float scalars, strings and enums.
|
||||
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||
// If this turns out to be inefficient we can always consider other options,
|
||||
// such as doing a Schwartzian transform.
|
||||
|
||||
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||
s := mapKeySorter{
|
||||
vs: vs,
|
||||
// default Less function: textual comparison
|
||||
less: func(a, b reflect.Value) bool {
|
||||
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||
},
|
||||
}
|
||||
|
||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
||||
// numeric keys are sorted numerically.
|
||||
if len(vs) == 0 {
|
||||
return s
|
||||
}
|
||||
switch vs[0].Kind() {
|
||||
case reflect.Int32, reflect.Int64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type mapKeySorter struct {
|
||||
vs []reflect.Value
|
||||
less func(a, b reflect.Value) bool
|
||||
}
|
||||
|
||||
func (s mapKeySorter) Len() int { return len(s.vs) }
|
||||
func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] }
|
||||
func (s mapKeySorter) Less(i, j int) bool {
|
||||
return s.less(s.vs[i], s.vs[j])
|
||||
}
|
||||
|
||||
// isProto3Zero reports whether v is a zero proto3 value.
|
||||
func isProto3Zero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion1 = true
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Support for message sets.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||
// A message type ID is required for storing a protocol buffer in a message set.
|
||||
var errNoMessageTypeID = errors.New("proto does not have a message type ID")
|
||||
|
||||
// The first two types (_MessageSet_Item and messageSet)
|
||||
// model what the protocol compiler produces for the following protocol message:
|
||||
// message MessageSet {
|
||||
// repeated group Item = 1 {
|
||||
// required int32 type_id = 2;
|
||||
// required string message = 3;
|
||||
// };
|
||||
// }
|
||||
// That is the MessageSet wire format. We can't use a proto to generate these
|
||||
// because that would introduce a circular dependency between it and this package.
|
||||
|
||||
type _MessageSet_Item struct {
|
||||
TypeId *int32 `protobuf:"varint,2,req,name=type_id"`
|
||||
Message []byte `protobuf:"bytes,3,req,name=message"`
|
||||
}
|
||||
|
||||
type messageSet struct {
|
||||
Item []*_MessageSet_Item `protobuf:"group,1,rep"`
|
||||
XXX_unrecognized []byte
|
||||
// TODO: caching?
|
||||
}
|
||||
|
||||
// Make sure messageSet is a Message.
|
||||
var _ Message = (*messageSet)(nil)
|
||||
|
||||
// messageTypeIder is an interface satisfied by a protocol buffer type
|
||||
// that may be stored in a MessageSet.
|
||||
type messageTypeIder interface {
|
||||
MessageTypeId() int32
|
||||
}
|
||||
|
||||
func (ms *messageSet) find(pb Message) *_MessageSet_Item {
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
id := mti.MessageTypeId()
|
||||
for _, item := range ms.Item {
|
||||
if *item.TypeId == id {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Has(pb Message) bool {
|
||||
if ms.find(pb) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||
if item := ms.find(pb); item != nil {
|
||||
return Unmarshal(item.Message, pb)
|
||||
}
|
||||
if _, ok := pb.(messageTypeIder); !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
return nil // TODO: return error instead?
|
||||
}
|
||||
|
||||
func (ms *messageSet) Marshal(pb Message) error {
|
||||
msg, err := Marshal(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item := ms.find(pb); item != nil {
|
||||
// reuse existing item
|
||||
item.Message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
|
||||
mtid := mti.MessageTypeId()
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: &mtid,
|
||||
Message: msg,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Reset() { *ms = messageSet{} }
|
||||
func (ms *messageSet) String() string { return CompactTextString(ms) }
|
||||
func (*messageSet) ProtoMessage() {}
|
||||
|
||||
// Support for the message_set_wire_format message option.
|
||||
|
||||
func skipVarint(buf []byte) []byte {
|
||||
i := 0
|
||||
for ; buf[i]&0x80 != 0; i++ {
|
||||
}
|
||||
return buf[i+1:]
|
||||
}
|
||||
|
||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSet(m map[int32]Extension) ([]byte, error) {
|
||||
if err := encodeExtensionMap(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort extension IDs to provide a deterministic encoding.
|
||||
// See also enc_map in encode.go.
|
||||
ids := make([]int, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, int(id))
|
||||
}
|
||||
sort.Ints(ids)
|
||||
|
||||
ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||
for _, id := range ids {
|
||||
e := m[int32(id)]
|
||||
// Remove the wire type and field number varint, as well as the length varint.
|
||||
msg := skipVarint(skipVarint(e.enc))
|
||||
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: Int32(int32(id)),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
return Marshal(ms)
|
||||
}
|
||||
|
||||
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error {
|
||||
ms := new(messageSet)
|
||||
if err := Unmarshal(buf, ms); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range ms.Item {
|
||||
id := *item.TypeId
|
||||
msg := item.Message
|
||||
|
||||
// Restore wire type and field number varint, plus length varint.
|
||||
// Be careful to preserve duplicate items.
|
||||
b := EncodeVarint(uint64(id)<<3 | WireBytes)
|
||||
if ext, ok := m[id]; ok {
|
||||
// Existing data; rip off the tag and length varint
|
||||
// so we join the new data correctly.
|
||||
// We can assume that ext.enc is set because we are unmarshaling.
|
||||
o := ext.enc[len(b):] // skip wire type and field number
|
||||
_, n := DecodeVarint(o) // calculate length of length varint
|
||||
o = o[n:] // skip length varint
|
||||
msg = append(o, msg...) // join old data and new data
|
||||
}
|
||||
b = append(b, EncodeVarint(uint64(len(msg)))...)
|
||||
b = append(b, msg...)
|
||||
|
||||
m[id] = Extension{enc: b}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSetJSON(m map[int32]Extension) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
|
||||
// Process the map in key order for deterministic output.
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||
|
||||
for i, id := range ids {
|
||||
ext := m[id]
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
|
||||
msd, ok := messageSetMap[id]
|
||||
if !ok {
|
||||
// Unknown type; we can't render it, so skip it.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||
|
||||
x := ext.value
|
||||
if x == nil {
|
||||
x = reflect.New(msd.t.Elem()).Interface()
|
||||
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(d)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSetJSON(buf []byte, m map[int32]Extension) error {
|
||||
// Common-case fast path.
|
||||
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is fairly tricky, and it's not clear that it is needed.
|
||||
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||
}
|
||||
|
||||
// A global registry of types that can be used in a MessageSet.
|
||||
|
||||
var messageSetMap = make(map[int32]messageSetDesc)
|
||||
|
||||
type messageSetDesc struct {
|
||||
t reflect.Type // pointer to struct
|
||||
name string
|
||||
}
|
||||
|
||||
// RegisterMessageSetType is called from the generated code.
|
||||
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||
messageSetMap[fieldNum] = messageSetDesc{
|
||||
t: reflect.TypeOf(m),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalMessageSetWithDuplicate(t *testing.T) {
|
||||
// Check that a repeated message set entry will be concatenated.
|
||||
in := &messageSet{
|
||||
Item: []*_MessageSet_Item{
|
||||
{TypeId: Int32(12345), Message: []byte("hoo")},
|
||||
{TypeId: Int32(12345), Message: []byte("hah")},
|
||||
},
|
||||
}
|
||||
b, err := Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal: %v", err)
|
||||
}
|
||||
t.Logf("Marshaled bytes: %q", b)
|
||||
|
||||
m := make(map[int32]Extension)
|
||||
if err := UnmarshalMessageSet(b, m); err != nil {
|
||||
t.Fatalf("UnmarshalMessageSet: %v", err)
|
||||
}
|
||||
ext, ok := m[12345]
|
||||
if !ok {
|
||||
t.Fatalf("Didn't retrieve extension 12345; map is %v", m)
|
||||
}
|
||||
// Skip wire type/field number and length varints.
|
||||
got := skipVarint(skipVarint(ext.enc))
|
||||
if want := []byte("hoohah"); !bytes.Equal(got, want) {
|
||||
t.Errorf("Combined extension is %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build appengine js
|
||||
|
||||
// This file contains an implementation of proto field accesses using package reflect.
|
||||
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||
// be used on App Engine.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
// The reflect value must itself be a pointer to a struct.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer{v}
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer as an interface value.
|
||||
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||
return p.v.Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by the sequence of field indices
|
||||
// passed to reflect's FieldByIndex.
|
||||
type field []int
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return f.Index
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
var invalidField = field(nil)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool { return f != nil }
|
||||
|
||||
// field returns the given field in the struct as a reflect value.
|
||||
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||
// Special case: an extension map entry with a value of type T
|
||||
// passes a *T to the struct-handling code with a zero field,
|
||||
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||
// which has the same memory layout. We have to handle that case
|
||||
// specially, because reflect will panic if we call FieldByIndex on a
|
||||
// non-struct.
|
||||
if f == nil {
|
||||
return p.v.Elem()
|
||||
}
|
||||
|
||||
return p.v.Elem().FieldByIndex(f)
|
||||
}
|
||||
|
||||
// ifield returns the given field in the struct as an interface value.
|
||||
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||
return structPointer_field(p, f).Addr().Interface()
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return structPointer_ifield(p, f).(*[]byte)
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return structPointer_ifield(p, f).(*[][]byte)
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return structPointer_ifield(p, f).(**bool)
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return structPointer_ifield(p, f).(*bool)
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return structPointer_ifield(p, f).(*[]bool)
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return structPointer_ifield(p, f).(**string)
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return structPointer_ifield(p, f).(*string)
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return structPointer_ifield(p, f).(*[]string)
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return structPointer_field(p, f).Addr()
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
structPointer_field(p, f).Set(q.v)
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return structPointer{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||
return structPointerSlice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||
type structPointerSlice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||
func (p structPointerSlice) Append(q structPointer) {
|
||||
p.v.Set(reflect.Append(p.v, q.v))
|
||||
}
|
||||
|
||||
var (
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
float32Type = reflect.TypeOf(float32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
uint64Type = reflect.TypeOf(uint64(0))
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
)
|
||||
|
||||
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||
type word32 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Set sets p to point at a newly allocated word with bits set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int32Type:
|
||||
if len(o.int32s) == 0 {
|
||||
o.int32s = make([]int32, uint32PoolSize)
|
||||
}
|
||||
o.int32s[0] = int32(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||
o.int32s = o.int32s[1:]
|
||||
return
|
||||
case uint32Type:
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||
o.uint32s = o.uint32s[1:]
|
||||
return
|
||||
case float32Type:
|
||||
if len(o.float32s) == 0 {
|
||||
o.float32s = make([]float32, uint32PoolSize)
|
||||
}
|
||||
o.float32s[0] = math.Float32frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||
o.float32s = o.float32s[1:]
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.Set(reflect.New(t))
|
||||
p.v.Elem().SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32_Get(p word32) uint32 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||
type word32Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
switch p.v.Type() {
|
||||
case int32Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint32Type:
|
||||
p.v.SetUint(uint64(x))
|
||||
return
|
||||
case float32Type:
|
||||
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||
type word32Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word32Slice) Append(x uint32) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
elem.SetInt(int64(int32(x)))
|
||||
case reflect.Uint32:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float32:
|
||||
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word32Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word32Slice) Index(i int) uint32 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||
return word32Slice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int64Type:
|
||||
if len(o.int64s) == 0 {
|
||||
o.int64s = make([]int64, uint64PoolSize)
|
||||
}
|
||||
o.int64s[0] = int64(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||
o.int64s = o.int64s[1:]
|
||||
return
|
||||
case uint64Type:
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||
o.uint64s = o.uint64s[1:]
|
||||
return
|
||||
case float64Type:
|
||||
if len(o.float64s) == 0 {
|
||||
o.float64s = make([]float64, uint64PoolSize)
|
||||
}
|
||||
o.float64s[0] = math.Float64frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||
o.float64s = o.float64s[1:]
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
switch p.v.Type() {
|
||||
case int64Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint64Type:
|
||||
p.v.SetUint(x)
|
||||
return
|
||||
case float64Type:
|
||||
p.v.SetFloat(math.Float64frombits(x))
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
type word64Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word64Slice) Append(x uint64) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
elem.SetInt(int64(int64(x)))
|
||||
case reflect.Uint64:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float64:
|
||||
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word64Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word64Slice) Index(i int) uint64 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return uint64(elem.Uint())
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(float64(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||
return word64Slice{structPointer_field(p, f)}
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !appengine,!js
|
||||
|
||||
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||
// but Go does not allow methods on pointer types, and we must preserve
|
||||
// some pointer type for the garbage collector. We use these
|
||||
// funcs with clunky names as our poor approximation to methods.
|
||||
//
|
||||
// An alternative would be
|
||||
// type structPointer struct { p unsafe.Pointer }
|
||||
// but that does not registerize as well.
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer unsafe.Pointer
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p == nil
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer, assumed to have element type t,
|
||||
// as an interface value.
|
||||
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||
type field uintptr
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return field(f.Offset)
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
const invalidField = ^field(0)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool {
|
||||
return f != ^field(0)
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||
type structPointerSlice []structPointer
|
||||
|
||||
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||
|
||||
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||
type word32 **uint32
|
||||
|
||||
// IsNil reports whether *v is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
// Set sets *v to point at a newly allocated word set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
*p = &o.uint32s[0]
|
||||
o.uint32s = o.uint32s[1:]
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by *v.
|
||||
func word32_Get(p word32) uint32 {
|
||||
return **p
|
||||
}
|
||||
|
||||
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Val is the address of a 32-bit value field.
|
||||
type word32Val *uint32
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by p.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
return *p
|
||||
}
|
||||
|
||||
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
type word32Slice []uint32
|
||||
|
||||
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||
func (v *word32Slice) Len() int { return len(*v) }
|
||||
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||
|
||||
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 **uint64
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
*p = &o.uint64s[0]
|
||||
o.uint64s = o.uint64s[1:]
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
return **p
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val *uint64
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
return *p
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Slice is like word32Slice but for 64-bit values.
|
||||
type word64Slice []uint64
|
||||
|
||||
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||
func (v *word64Slice) Len() int { return len(*v) }
|
||||
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
+846
@@ -0,0 +1,846 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for encoding data into the wire format for protocol buffers.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const debug bool = false
|
||||
|
||||
// Constants that identify the encoding of a value on the wire.
|
||||
const (
|
||||
WireVarint = 0
|
||||
WireFixed64 = 1
|
||||
WireBytes = 2
|
||||
WireStartGroup = 3
|
||||
WireEndGroup = 4
|
||||
WireFixed32 = 5
|
||||
)
|
||||
|
||||
const startSize = 10 // initial slice/string sizes
|
||||
|
||||
// Encoders are defined in encode.go
|
||||
// An encoder outputs the full representation of a field, including its
|
||||
// tag and encoder type.
|
||||
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueEncoder encodes a single integer in a particular encoding.
|
||||
type valueEncoder func(o *Buffer, x uint64) error
|
||||
|
||||
// Sizers are defined in encode.go
|
||||
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||
// type.
|
||||
type sizer func(prop *Properties, base structPointer) int
|
||||
|
||||
// A valueSizer returns the encoded size of a single integer in a particular
|
||||
// encoding.
|
||||
type valueSizer func(x uint64) int
|
||||
|
||||
// Decoders are defined in decode.go
|
||||
// A decoder creates a value from its wire representation.
|
||||
// Unrecognized subelements are saved in unrec.
|
||||
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueDecoder decodes a single integer in a particular encoding.
|
||||
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||
|
||||
// A oneofMarshaler does the marshaling for all oneof fields in a message.
|
||||
type oneofMarshaler func(Message, *Buffer) error
|
||||
|
||||
// A oneofUnmarshaler does the unmarshaling for a oneof field in a message.
|
||||
type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error)
|
||||
|
||||
// A oneofSizer does the sizing for all oneof fields in a message.
|
||||
type oneofSizer func(Message) int
|
||||
|
||||
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||
// numbers.
|
||||
type tagMap struct {
|
||||
fastTags []int
|
||||
slowTags map[int]int
|
||||
}
|
||||
|
||||
// tagMapFastLimit is the upper bound on the tag number that will be stored in
|
||||
// the tagMap slice rather than its map.
|
||||
const tagMapFastLimit = 1024
|
||||
|
||||
func (p *tagMap) get(t int) (int, bool) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
if t >= len(p.fastTags) {
|
||||
return 0, false
|
||||
}
|
||||
fi := p.fastTags[t]
|
||||
return fi, fi >= 0
|
||||
}
|
||||
fi, ok := p.slowTags[t]
|
||||
return fi, ok
|
||||
}
|
||||
|
||||
func (p *tagMap) put(t int, fi int) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
for len(p.fastTags) < t+1 {
|
||||
p.fastTags = append(p.fastTags, -1)
|
||||
}
|
||||
p.fastTags[t] = fi
|
||||
return
|
||||
}
|
||||
if p.slowTags == nil {
|
||||
p.slowTags = make(map[int]int)
|
||||
}
|
||||
p.slowTags[t] = fi
|
||||
}
|
||||
|
||||
// StructProperties represents properties for all the fields of a struct.
|
||||
// decoderTags and decoderOrigNames should only be used by the decoder.
|
||||
type StructProperties struct {
|
||||
Prop []*Properties // properties for each field
|
||||
reqCount int // required count
|
||||
decoderTags tagMap // map from proto tag to struct field number
|
||||
decoderOrigNames map[string]int // map from original name to struct field number
|
||||
order []int // list of struct field numbers in tag order
|
||||
unrecField field // field id of the XXX_unrecognized []byte field
|
||||
extendable bool // is this an extendable proto
|
||||
|
||||
oneofMarshaler oneofMarshaler
|
||||
oneofUnmarshaler oneofUnmarshaler
|
||||
oneofSizer oneofSizer
|
||||
stype reflect.Type
|
||||
|
||||
// OneofTypes contains information about the oneof fields in this message.
|
||||
// It is keyed by the original name of a field.
|
||||
OneofTypes map[string]*OneofProperties
|
||||
}
|
||||
|
||||
// OneofProperties represents information about a specific field in a oneof.
|
||||
type OneofProperties struct {
|
||||
Type reflect.Type // pointer to generated struct type for this oneof field
|
||||
Field int // struct field number of the containing oneof in the message
|
||||
Prop *Properties
|
||||
}
|
||||
|
||||
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
|
||||
// See encode.go, (*Buffer).enc_struct.
|
||||
|
||||
func (sp *StructProperties) Len() int { return len(sp.order) }
|
||||
func (sp *StructProperties) Less(i, j int) bool {
|
||||
return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag
|
||||
}
|
||||
func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] }
|
||||
|
||||
// Properties represents the protocol-specific behavior of a single struct field.
|
||||
type Properties struct {
|
||||
Name string // name of the field, for error messages
|
||||
OrigName string // original name before protocol compiler (always set)
|
||||
JSONName string // name to use for JSON; determined by protoc
|
||||
Wire string
|
||||
WireType int
|
||||
Tag int
|
||||
Required bool
|
||||
Optional bool
|
||||
Repeated bool
|
||||
Packed bool // relevant for repeated primitives only
|
||||
Enum string // set for enum types only
|
||||
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||
oneof bool // whether this is a oneof field
|
||||
|
||||
Default string // default value
|
||||
HasDefault bool // whether an explicit default was provided
|
||||
def_uint64 uint64
|
||||
|
||||
enc encoder
|
||||
valEnc valueEncoder // set for bool and numeric types only
|
||||
field field
|
||||
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||
tagbuf [8]byte
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
isMarshaler bool
|
||||
isUnmarshaler bool
|
||||
|
||||
mtype reflect.Type // set for map types only
|
||||
mkeyprop *Properties // set for map types only
|
||||
mvalprop *Properties // set for map types only
|
||||
|
||||
size sizer
|
||||
valSize valueSizer // set for bool and numeric types only
|
||||
|
||||
dec decoder
|
||||
valDec valueDecoder // set for bool and numeric types only
|
||||
|
||||
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||
packedDec decoder
|
||||
}
|
||||
|
||||
// String formats the properties in the protobuf struct field tag style.
|
||||
func (p *Properties) String() string {
|
||||
s := p.Wire
|
||||
s = ","
|
||||
s += strconv.Itoa(p.Tag)
|
||||
if p.Required {
|
||||
s += ",req"
|
||||
}
|
||||
if p.Optional {
|
||||
s += ",opt"
|
||||
}
|
||||
if p.Repeated {
|
||||
s += ",rep"
|
||||
}
|
||||
if p.Packed {
|
||||
s += ",packed"
|
||||
}
|
||||
s += ",name=" + p.OrigName
|
||||
if p.JSONName != p.OrigName {
|
||||
s += ",json=" + p.JSONName
|
||||
}
|
||||
if p.proto3 {
|
||||
s += ",proto3"
|
||||
}
|
||||
if p.oneof {
|
||||
s += ",oneof"
|
||||
}
|
||||
if len(p.Enum) > 0 {
|
||||
s += ",enum=" + p.Enum
|
||||
}
|
||||
if p.HasDefault {
|
||||
s += ",def=" + p.Default
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||
func (p *Properties) Parse(s string) {
|
||||
// "bytes,49,opt,name=foo,def=hello!"
|
||||
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||
if len(fields) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
p.Wire = fields[0]
|
||||
switch p.Wire {
|
||||
case "varint":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeVarint
|
||||
p.valDec = (*Buffer).DecodeVarint
|
||||
p.valSize = sizeVarint
|
||||
case "fixed32":
|
||||
p.WireType = WireFixed32
|
||||
p.valEnc = (*Buffer).EncodeFixed32
|
||||
p.valDec = (*Buffer).DecodeFixed32
|
||||
p.valSize = sizeFixed32
|
||||
case "fixed64":
|
||||
p.WireType = WireFixed64
|
||||
p.valEnc = (*Buffer).EncodeFixed64
|
||||
p.valDec = (*Buffer).DecodeFixed64
|
||||
p.valSize = sizeFixed64
|
||||
case "zigzag32":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag32
|
||||
p.valDec = (*Buffer).DecodeZigzag32
|
||||
p.valSize = sizeZigzag32
|
||||
case "zigzag64":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag64
|
||||
p.valDec = (*Buffer).DecodeZigzag64
|
||||
p.valSize = sizeZigzag64
|
||||
case "bytes", "group":
|
||||
p.WireType = WireBytes
|
||||
// no numeric converter for non-numeric types
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
p.Tag, err = strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 2; i < len(fields); i++ {
|
||||
f := fields[i]
|
||||
switch {
|
||||
case f == "req":
|
||||
p.Required = true
|
||||
case f == "opt":
|
||||
p.Optional = true
|
||||
case f == "rep":
|
||||
p.Repeated = true
|
||||
case f == "packed":
|
||||
p.Packed = true
|
||||
case strings.HasPrefix(f, "name="):
|
||||
p.OrigName = f[5:]
|
||||
case strings.HasPrefix(f, "json="):
|
||||
p.JSONName = f[5:]
|
||||
case strings.HasPrefix(f, "enum="):
|
||||
p.Enum = f[5:]
|
||||
case f == "proto3":
|
||||
p.proto3 = true
|
||||
case f == "oneof":
|
||||
p.oneof = true
|
||||
case strings.HasPrefix(f, "def="):
|
||||
p.HasDefault = true
|
||||
p.Default = f[4:] // rest of string
|
||||
if i+1 < len(fields) {
|
||||
// Commas aren't escaped, and def is always last.
|
||||
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||
}
|
||||
|
||||
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||
|
||||
// Initialize the fields for encoding and decoding.
|
||||
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||
p.enc = nil
|
||||
p.dec = nil
|
||||
p.size = nil
|
||||
|
||||
switch t1 := typ; t1.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||
|
||||
// proto3 scalar types
|
||||
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_proto3_bool
|
||||
p.dec = (*Buffer).dec_proto3_bool
|
||||
p.size = size_proto3_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_proto3_int32
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32
|
||||
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_proto3_int64
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_proto3_string
|
||||
p.dec = (*Buffer).dec_proto3_string
|
||||
p.size = size_proto3_string
|
||||
|
||||
case reflect.Ptr:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_bool
|
||||
p.dec = (*Buffer).dec_bool
|
||||
p.size = size_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_int32
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_uint32
|
||||
p.dec = (*Buffer).dec_int32 // can reuse
|
||||
p.size = size_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_int64
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_string
|
||||
p.dec = (*Buffer).dec_string
|
||||
p.size = size_string
|
||||
case reflect.Struct:
|
||||
p.stype = t1.Elem()
|
||||
p.isMarshaler = isMarshaler(t1)
|
||||
p.isUnmarshaler = isUnmarshaler(t1)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_struct_message
|
||||
p.dec = (*Buffer).dec_struct_message
|
||||
p.size = size_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_struct_group
|
||||
p.dec = (*Buffer).dec_struct_group
|
||||
p.size = size_struct_group
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_bool
|
||||
p.size = size_slice_packed_bool
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_bool
|
||||
p.size = size_slice_bool
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_bool
|
||||
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||
case reflect.Int32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int32
|
||||
p.size = size_slice_packed_int32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int32
|
||||
p.size = size_slice_int32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Uint32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
case reflect.Uint8:
|
||||
p.enc = (*Buffer).enc_slice_byte
|
||||
p.dec = (*Buffer).dec_slice_byte
|
||||
p.size = size_slice_byte
|
||||
// This is a []byte, which is either a bytes field,
|
||||
// or the value of a map field. In the latter case,
|
||||
// we always encode an empty []byte, so we should not
|
||||
// use the proto3 enc/size funcs.
|
||||
// f == nil iff this is the key/value of a map field.
|
||||
if p.proto3 && f != nil {
|
||||
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||
p.size = size_proto3_slice_byte
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch t2.Bits() {
|
||||
case 32:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case 64:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
}
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_slice_string
|
||||
p.dec = (*Buffer).dec_slice_string
|
||||
p.size = size_slice_string
|
||||
case reflect.Ptr:
|
||||
switch t3 := t2.Elem(); t3.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||
break
|
||||
case reflect.Struct:
|
||||
p.stype = t2.Elem()
|
||||
p.isMarshaler = isMarshaler(t2)
|
||||
p.isUnmarshaler = isUnmarshaler(t2)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_slice_struct_message
|
||||
p.dec = (*Buffer).dec_slice_struct_message
|
||||
p.size = size_slice_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_struct_group
|
||||
p.dec = (*Buffer).dec_slice_struct_group
|
||||
p.size = size_slice_struct_group
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch t2.Elem().Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||
break
|
||||
case reflect.Uint8:
|
||||
p.enc = (*Buffer).enc_slice_slice_byte
|
||||
p.dec = (*Buffer).dec_slice_slice_byte
|
||||
p.size = size_slice_slice_byte
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
p.enc = (*Buffer).enc_new_map
|
||||
p.dec = (*Buffer).dec_new_map
|
||||
p.size = size_new_map
|
||||
|
||||
p.mtype = t1
|
||||
p.mkeyprop = &Properties{}
|
||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.mvalprop = &Properties{}
|
||||
vtype := p.mtype.Elem()
|
||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||
// The value type is not a message (*T) or bytes ([]byte),
|
||||
// so we need encoders for the pointer to this type.
|
||||
vtype = reflect.PtrTo(vtype)
|
||||
}
|
||||
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
}
|
||||
|
||||
// precalculate tag code
|
||||
wire := p.WireType
|
||||
if p.Packed {
|
||||
wire = WireBytes
|
||||
}
|
||||
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||
i := 0
|
||||
for i = 0; x > 127; i++ {
|
||||
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||
x >>= 7
|
||||
}
|
||||
p.tagbuf[i] = uint8(x)
|
||||
p.tagcode = p.tagbuf[0 : i+1]
|
||||
|
||||
if p.stype != nil {
|
||||
if lockGetProp {
|
||||
p.sprop = GetProperties(p.stype)
|
||||
} else {
|
||||
p.sprop = getPropertiesLocked(p.stype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// isMarshaler reports whether type t implements Marshaler.
|
||||
func isMarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isMarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isMarshaler")
|
||||
}
|
||||
return t.Implements(marshalerType)
|
||||
}
|
||||
|
||||
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||
func isUnmarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isUnmarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isUnmarshaler")
|
||||
}
|
||||
return t.Implements(unmarshalerType)
|
||||
}
|
||||
|
||||
// Init populates the properties from a protocol buffer struct tag.
|
||||
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||
p.init(typ, name, tag, f, true)
|
||||
}
|
||||
|
||||
func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) {
|
||||
// "bytes,49,opt,def=hello!"
|
||||
p.Name = name
|
||||
p.OrigName = name
|
||||
if f != nil {
|
||||
p.field = toField(f)
|
||||
}
|
||||
if tag == "" {
|
||||
return
|
||||
}
|
||||
p.Parse(tag)
|
||||
p.setEncAndDec(typ, f, lockGetProp)
|
||||
}
|
||||
|
||||
var (
|
||||
propertiesMu sync.RWMutex
|
||||
propertiesMap = make(map[reflect.Type]*StructProperties)
|
||||
)
|
||||
|
||||
// GetProperties returns the list of properties for the type represented by t.
|
||||
// t must represent a generated struct type of a protocol message.
|
||||
func GetProperties(t reflect.Type) *StructProperties {
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("proto: type must have kind struct")
|
||||
}
|
||||
|
||||
// Most calls to GetProperties in a long-running program will be
|
||||
// retrieving details for types we have seen before.
|
||||
propertiesMu.RLock()
|
||||
sprop, ok := propertiesMap[t]
|
||||
propertiesMu.RUnlock()
|
||||
if ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return sprop
|
||||
}
|
||||
|
||||
propertiesMu.Lock()
|
||||
sprop = getPropertiesLocked(t)
|
||||
propertiesMu.Unlock()
|
||||
return sprop
|
||||
}
|
||||
|
||||
// getPropertiesLocked requires that propertiesMu is held.
|
||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||
if prop, ok := propertiesMap[t]; ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return prop
|
||||
}
|
||||
if collectStats {
|
||||
stats.Cmiss++
|
||||
}
|
||||
|
||||
prop := new(StructProperties)
|
||||
// in case of recursive protos, fill this in now.
|
||||
propertiesMap[t] = prop
|
||||
|
||||
// build properties
|
||||
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType)
|
||||
prop.unrecField = invalidField
|
||||
prop.Prop = make([]*Properties, t.NumField())
|
||||
prop.order = make([]int, t.NumField())
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
p := new(Properties)
|
||||
name := f.Name
|
||||
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||
|
||||
if f.Name == "XXX_extensions" { // special case
|
||||
p.enc = (*Buffer).enc_map
|
||||
p.dec = nil // not needed
|
||||
p.size = size_map
|
||||
}
|
||||
if f.Name == "XXX_unrecognized" { // special case
|
||||
prop.unrecField = toField(&f)
|
||||
}
|
||||
oneof := f.Tag.Get("protobuf_oneof") != "" // special case
|
||||
prop.Prop[i] = p
|
||||
prop.order[i] = i
|
||||
if debug {
|
||||
print(i, " ", f.Name, " ", t.String(), " ")
|
||||
if p.Tag > 0 {
|
||||
print(p.String())
|
||||
}
|
||||
print("\n")
|
||||
}
|
||||
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && !oneof {
|
||||
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||
}
|
||||
}
|
||||
|
||||
// Re-order prop.order.
|
||||
sort.Sort(prop)
|
||||
|
||||
type oneofMessage interface {
|
||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||
}
|
||||
if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok {
|
||||
var oots []interface{}
|
||||
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
||||
prop.stype = t
|
||||
|
||||
// Interpret oneof metadata.
|
||||
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||
for _, oot := range oots {
|
||||
oop := &OneofProperties{
|
||||
Type: reflect.ValueOf(oot).Type(), // *T
|
||||
Prop: new(Properties),
|
||||
}
|
||||
sft := oop.Type.Elem().Field(0)
|
||||
oop.Prop.Name = sft.Name
|
||||
oop.Prop.Parse(sft.Tag.Get("protobuf"))
|
||||
// There will be exactly one interface field that
|
||||
// this new value is assignable to.
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type.Kind() != reflect.Interface {
|
||||
continue
|
||||
}
|
||||
if !oop.Type.AssignableTo(f.Type) {
|
||||
continue
|
||||
}
|
||||
oop.Field = i
|
||||
break
|
||||
}
|
||||
prop.OneofTypes[oop.Prop.OrigName] = oop
|
||||
}
|
||||
}
|
||||
|
||||
// build required counts
|
||||
// build tags
|
||||
reqCount := 0
|
||||
prop.decoderOrigNames = make(map[string]int)
|
||||
for i, p := range prop.Prop {
|
||||
if strings.HasPrefix(p.Name, "XXX_") {
|
||||
// Internal fields should not appear in tags/origNames maps.
|
||||
// They are handled specially when encoding and decoding.
|
||||
continue
|
||||
}
|
||||
if p.Required {
|
||||
reqCount++
|
||||
}
|
||||
prop.decoderTags.put(p.Tag, i)
|
||||
prop.decoderOrigNames[p.OrigName] = i
|
||||
}
|
||||
prop.reqCount = reqCount
|
||||
|
||||
return prop
|
||||
}
|
||||
|
||||
// Return the Properties object for the x[0]'th field of the structure.
|
||||
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||
if len(x) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||
return nil
|
||||
}
|
||||
prop := GetProperties(t)
|
||||
return prop.Prop[x[0]]
|
||||
}
|
||||
|
||||
// Get the address and type of a pointer to a struct from an interface.
|
||||
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||
if pb == nil {
|
||||
err = ErrNil
|
||||
return
|
||||
}
|
||||
// get the reflect type of the pointer to the struct.
|
||||
t = reflect.TypeOf(pb)
|
||||
// get the address of the struct.
|
||||
value := reflect.ValueOf(pb)
|
||||
b = toStructPointer(value)
|
||||
return
|
||||
}
|
||||
|
||||
// A global registry of enum types.
|
||||
// The generated code will register the generated maps by calling RegisterEnum.
|
||||
|
||||
var enumValueMaps = make(map[string]map[string]int32)
|
||||
|
||||
// RegisterEnum is called from the generated code to install the enum descriptor
|
||||
// maps into the global table to aid parsing text format protocol buffers.
|
||||
func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {
|
||||
if _, ok := enumValueMaps[typeName]; ok {
|
||||
panic("proto: duplicate enum registered: " + typeName)
|
||||
}
|
||||
enumValueMaps[typeName] = valueMap
|
||||
}
|
||||
|
||||
// EnumValueMap returns the mapping from names to integers of the
|
||||
// enum type enumType, or a nil if not found.
|
||||
func EnumValueMap(enumType string) map[string]int32 {
|
||||
return enumValueMaps[enumType]
|
||||
}
|
||||
|
||||
// A registry of all linked message types.
|
||||
// The string is a fully-qualified proto name ("pkg.Message").
|
||||
var (
|
||||
protoTypes = make(map[string]reflect.Type)
|
||||
revProtoTypes = make(map[reflect.Type]string)
|
||||
)
|
||||
|
||||
// RegisterType is called from generated code and maps from the fully qualified
|
||||
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||
func RegisterType(x Message, name string) {
|
||||
if _, ok := protoTypes[name]; ok {
|
||||
// TODO: Some day, make this a panic.
|
||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||
return
|
||||
}
|
||||
t := reflect.TypeOf(x)
|
||||
protoTypes[name] = t
|
||||
revProtoTypes[t] = name
|
||||
}
|
||||
|
||||
// MessageName returns the fully-qualified proto name for the given message type.
|
||||
func MessageName(x Message) string { return revProtoTypes[reflect.TypeOf(x)] }
|
||||
|
||||
// MessageType returns the message type (pointer to struct) for a named message.
|
||||
func MessageType(name string) reflect.Type { return protoTypes[name] }
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: proto3_proto/proto3.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package proto3_proto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
proto3_proto/proto3.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Message
|
||||
Nested
|
||||
MessageWithMap
|
||||
*/
|
||||
package proto3_proto
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
import google_protobuf "github.com/golang/protobuf/ptypes/any"
|
||||
import testdata "github.com/golang/protobuf/proto/testdata"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
const _ = proto.ProtoPackageIsVersion1
|
||||
|
||||
type Message_Humour int32
|
||||
|
||||
const (
|
||||
Message_UNKNOWN Message_Humour = 0
|
||||
Message_PUNS Message_Humour = 1
|
||||
Message_SLAPSTICK Message_Humour = 2
|
||||
Message_BILL_BAILEY Message_Humour = 3
|
||||
)
|
||||
|
||||
var Message_Humour_name = map[int32]string{
|
||||
0: "UNKNOWN",
|
||||
1: "PUNS",
|
||||
2: "SLAPSTICK",
|
||||
3: "BILL_BAILEY",
|
||||
}
|
||||
var Message_Humour_value = map[string]int32{
|
||||
"UNKNOWN": 0,
|
||||
"PUNS": 1,
|
||||
"SLAPSTICK": 2,
|
||||
"BILL_BAILEY": 3,
|
||||
}
|
||||
|
||||
func (x Message_Humour) String() string {
|
||||
return proto.EnumName(Message_Humour_name, int32(x))
|
||||
}
|
||||
func (Message_Humour) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
|
||||
|
||||
type Message struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
Hilarity Message_Humour `protobuf:"varint,2,opt,name=hilarity,enum=proto3_proto.Message_Humour" json:"hilarity,omitempty"`
|
||||
HeightInCm uint32 `protobuf:"varint,3,opt,name=height_in_cm,json=heightInCm" json:"height_in_cm,omitempty"`
|
||||
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
|
||||
ResultCount int64 `protobuf:"varint,7,opt,name=result_count,json=resultCount" json:"result_count,omitempty"`
|
||||
TrueScotsman bool `protobuf:"varint,8,opt,name=true_scotsman,json=trueScotsman" json:"true_scotsman,omitempty"`
|
||||
Score float32 `protobuf:"fixed32,9,opt,name=score" json:"score,omitempty"`
|
||||
Key []uint64 `protobuf:"varint,5,rep,name=key" json:"key,omitempty"`
|
||||
Nested *Nested `protobuf:"bytes,6,opt,name=nested" json:"nested,omitempty"`
|
||||
Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field,json=proto2Field" json:"proto2_field,omitempty"`
|
||||
Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value,json=proto2Value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Anything *google_protobuf.Any `protobuf:"bytes,14,opt,name=anything" json:"anything,omitempty"`
|
||||
ManyThings []*google_protobuf.Any `protobuf:"bytes,15,rep,name=many_things,json=manyThings" json:"many_things,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Message) Reset() { *m = Message{} }
|
||||
func (m *Message) String() string { return proto.CompactTextString(m) }
|
||||
func (*Message) ProtoMessage() {}
|
||||
func (*Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Message) GetNested() *Nested {
|
||||
if m != nil {
|
||||
return m.Nested
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) GetTerrain() map[string]*Nested {
|
||||
if m != nil {
|
||||
return m.Terrain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) GetProto2Field() *testdata.SubDefaults {
|
||||
if m != nil {
|
||||
return m.Proto2Field
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults {
|
||||
if m != nil {
|
||||
return m.Proto2Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) GetAnything() *google_protobuf.Any {
|
||||
if m != nil {
|
||||
return m.Anything
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) GetManyThings() []*google_protobuf.Any {
|
||||
if m != nil {
|
||||
return m.ManyThings
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Nested struct {
|
||||
Bunny string `protobuf:"bytes,1,opt,name=bunny" json:"bunny,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Nested) Reset() { *m = Nested{} }
|
||||
func (m *Nested) String() string { return proto.CompactTextString(m) }
|
||||
func (*Nested) ProtoMessage() {}
|
||||
func (*Nested) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
type MessageWithMap struct {
|
||||
ByteMapping map[bool][]byte `protobuf:"bytes,1,rep,name=byte_mapping,json=byteMapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (m *MessageWithMap) Reset() { *m = MessageWithMap{} }
|
||||
func (m *MessageWithMap) String() string { return proto.CompactTextString(m) }
|
||||
func (*MessageWithMap) ProtoMessage() {}
|
||||
func (*MessageWithMap) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *MessageWithMap) GetByteMapping() map[bool][]byte {
|
||||
if m != nil {
|
||||
return m.ByteMapping
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Message)(nil), "proto3_proto.Message")
|
||||
proto.RegisterType((*Nested)(nil), "proto3_proto.Nested")
|
||||
proto.RegisterType((*MessageWithMap)(nil), "proto3_proto.MessageWithMap")
|
||||
proto.RegisterEnum("proto3_proto.Message_Humour", Message_Humour_name, Message_Humour_value)
|
||||
}
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 593 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0xff, 0x6e, 0xd3, 0x30,
|
||||
0x10, 0xc7, 0x49, 0xdb, 0xa5, 0xd9, 0x25, 0xdd, 0x22, 0x33, 0x24, 0xaf, 0x42, 0x68, 0x14, 0x09,
|
||||
0x4d, 0xfc, 0xc8, 0x50, 0x11, 0xd2, 0x84, 0x10, 0x68, 0x1b, 0x43, 0x54, 0xeb, 0x4a, 0xe5, 0x6e,
|
||||
0x4c, 0xfc, 0x15, 0x39, 0xad, 0xdb, 0x46, 0x34, 0x4e, 0x95, 0x38, 0x48, 0x79, 0x1d, 0xde, 0x84,
|
||||
0x37, 0xc3, 0xb1, 0xd3, 0x2e, 0x9b, 0x0a, 0x7f, 0xd9, 0xbe, 0xfb, 0xdc, 0x9d, 0xef, 0x7b, 0x07,
|
||||
0xfb, 0xcb, 0x24, 0x16, 0xf1, 0x5b, 0x5f, 0x1d, 0x47, 0xfa, 0xe1, 0xa9, 0x03, 0x39, 0x55, 0x57,
|
||||
0x7b, 0x7f, 0x16, 0xc7, 0xb3, 0x05, 0xd3, 0x48, 0x90, 0x4d, 0x8f, 0x28, 0xcf, 0x35, 0xd8, 0x7e,
|
||||
0x28, 0x58, 0x2a, 0x26, 0x54, 0xd0, 0xa3, 0xe2, 0xa2, 0x8d, 0x9d, 0x3f, 0x26, 0x34, 0x2f, 0x59,
|
||||
0x9a, 0xd2, 0x19, 0x43, 0x08, 0x1a, 0x9c, 0x46, 0x0c, 0x1b, 0x07, 0xc6, 0xe1, 0x36, 0x51, 0x77,
|
||||
0x74, 0x0c, 0xd6, 0x3c, 0x5c, 0xd0, 0x24, 0x14, 0x39, 0xae, 0x49, 0xfb, 0x4e, 0xf7, 0xb1, 0x57,
|
||||
0x2d, 0xe8, 0x95, 0xc1, 0xde, 0xd7, 0x2c, 0x8a, 0xb3, 0x84, 0xac, 0x69, 0x74, 0x00, 0xce, 0x9c,
|
||||
0x85, 0xb3, 0xb9, 0xf0, 0x43, 0xee, 0x8f, 0x23, 0x5c, 0x97, 0xd1, 0x2d, 0x02, 0xda, 0xd6, 0xe3,
|
||||
0x67, 0x51, 0x51, 0xaf, 0xf8, 0x0e, 0x6e, 0x48, 0x8f, 0x43, 0xd4, 0x1d, 0x3d, 0x05, 0x27, 0x61,
|
||||
0x69, 0xb6, 0x10, 0xfe, 0x38, 0xce, 0xb8, 0xc0, 0x4d, 0xe9, 0xab, 0x13, 0x5b, 0xdb, 0xce, 0x0a,
|
||||
0x13, 0x7a, 0x06, 0x2d, 0x91, 0x64, 0xcc, 0x4f, 0xc7, 0xb1, 0x48, 0x23, 0xca, 0xb1, 0x25, 0x19,
|
||||
0x8b, 0x38, 0x85, 0x71, 0x54, 0xda, 0xd0, 0x1e, 0x6c, 0x49, 0x7f, 0xc2, 0xf0, 0xb6, 0x74, 0xd6,
|
||||
0x88, 0x7e, 0x20, 0x17, 0xea, 0x3f, 0x59, 0x8e, 0xb7, 0x0e, 0xea, 0x87, 0x0d, 0x52, 0x5c, 0xd1,
|
||||
0x2b, 0x30, 0xb9, 0x54, 0x83, 0x4d, 0xb0, 0x29, 0x41, 0xbb, 0xbb, 0x77, 0xb7, 0xbb, 0x81, 0xf2,
|
||||
0x91, 0x92, 0x41, 0x1f, 0xa0, 0x29, 0x58, 0x92, 0xd0, 0x90, 0x63, 0x90, 0x39, 0xec, 0x6e, 0x67,
|
||||
0xb3, 0x18, 0x57, 0x1a, 0x3a, 0xe7, 0x22, 0xc9, 0xc9, 0x2a, 0x44, 0x6a, 0xa9, 0x67, 0xd5, 0xf5,
|
||||
0xa7, 0x21, 0x5b, 0x4c, 0xb0, 0xad, 0x2a, 0x3e, 0xf2, 0x56, 0x73, 0xf1, 0x46, 0x59, 0xf0, 0x99,
|
||||
0x4d, 0xa9, 0xec, 0x34, 0x25, 0xb6, 0x46, 0xbf, 0x14, 0x24, 0xea, 0xad, 0x23, 0x7f, 0xd1, 0x45,
|
||||
0xc6, 0x70, 0x4b, 0x15, 0x7f, 0xbe, 0xb9, 0xf8, 0x50, 0x91, 0xdf, 0x0b, 0x50, 0x7f, 0xa0, 0x4c,
|
||||
0xa5, 0x2c, 0xe8, 0x0d, 0x58, 0x72, 0x25, 0xc4, 0x3c, 0xe4, 0x33, 0xbc, 0x53, 0xb6, 0xac, 0x77,
|
||||
0xc6, 0x5b, 0xed, 0x8c, 0x77, 0xc2, 0x73, 0xb2, 0xa6, 0xd0, 0x3b, 0xb0, 0xa5, 0xa2, 0xb9, 0xaf,
|
||||
0x5e, 0x29, 0xde, 0x55, 0xb5, 0x37, 0x07, 0x41, 0x01, 0x5e, 0x29, 0xae, 0x3d, 0x04, 0xa7, 0x2a,
|
||||
0xc3, 0x4a, 0x7b, 0xbd, 0x5c, 0x4a, 0xfb, 0x17, 0xb0, 0xa5, 0xdb, 0xa9, 0xfd, 0x47, 0x7a, 0x8d,
|
||||
0xbc, 0xaf, 0x1d, 0x1b, 0xed, 0x6b, 0x70, 0xef, 0xf7, 0xb6, 0x21, 0xeb, 0xcb, 0xbb, 0x59, 0xff,
|
||||
0x21, 0xef, 0x6d, 0xda, 0xce, 0x27, 0x30, 0xf5, 0xf2, 0x22, 0x1b, 0x9a, 0xd7, 0x83, 0x8b, 0xc1,
|
||||
0xb7, 0x9b, 0x81, 0xfb, 0x00, 0x59, 0xd0, 0x18, 0x5e, 0x0f, 0x46, 0xae, 0x81, 0x5a, 0xb0, 0x3d,
|
||||
0xea, 0x9f, 0x0c, 0x47, 0x57, 0xbd, 0xb3, 0x0b, 0xb7, 0x86, 0x76, 0xc1, 0x3e, 0xed, 0xf5, 0xfb,
|
||||
0xfe, 0xe9, 0x49, 0xaf, 0x7f, 0xfe, 0xc3, 0xad, 0x77, 0x9e, 0x80, 0xa9, 0x3f, 0x5b, 0x6c, 0x5d,
|
||||
0x90, 0x71, 0xbe, 0xfa, 0x8f, 0x7e, 0x74, 0x7e, 0x1b, 0xb0, 0x53, 0x0e, 0xe7, 0x26, 0x14, 0xf3,
|
||||
0x4b, 0xba, 0x44, 0x52, 0x9c, 0x20, 0x17, 0xcc, 0x8f, 0xe8, 0x72, 0x59, 0x4c, 0xc2, 0x50, 0xa2,
|
||||
0xbe, 0xde, 0x38, 0xd0, 0x32, 0xc6, 0x3b, 0x95, 0x01, 0x97, 0x9a, 0x2f, 0xe7, 0x1a, 0xdc, 0x5a,
|
||||
0xda, 0x1f, 0xc1, 0xbd, 0x0f, 0x54, 0xc5, 0xb1, 0xb4, 0x38, 0x7b, 0x55, 0x71, 0x9c, 0x8a, 0x0a,
|
||||
0x81, 0xa9, 0x4b, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xab, 0x07, 0xe8, 0xfe, 0x6a, 0x04, 0x00,
|
||||
0x00,
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "testdata/test.proto";
|
||||
|
||||
package proto3_proto;
|
||||
|
||||
message Message {
|
||||
enum Humour {
|
||||
UNKNOWN = 0;
|
||||
PUNS = 1;
|
||||
SLAPSTICK = 2;
|
||||
BILL_BAILEY = 3;
|
||||
}
|
||||
|
||||
string name = 1;
|
||||
Humour hilarity = 2;
|
||||
uint32 height_in_cm = 3;
|
||||
bytes data = 4;
|
||||
int64 result_count = 7;
|
||||
bool true_scotsman = 8;
|
||||
float score = 9;
|
||||
|
||||
repeated uint64 key = 5;
|
||||
Nested nested = 6;
|
||||
|
||||
map<string, Nested> terrain = 10;
|
||||
testdata.SubDefaults proto2_field = 11;
|
||||
map<string, testdata.SubDefaults> proto2_value = 13;
|
||||
|
||||
google.protobuf.Any anything = 14;
|
||||
repeated google.protobuf.Any many_things = 15;
|
||||
}
|
||||
|
||||
message Nested {
|
||||
string bunny = 1;
|
||||
}
|
||||
|
||||
message MessageWithMap {
|
||||
map<bool, bytes> byte_mapping = 1;
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
tpb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
func TestProto3ZeroValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
m proto.Message
|
||||
}{
|
||||
{"zero message", &pb.Message{}},
|
||||
{"empty bytes field", &pb.Message{Data: []byte{}}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
b, err := proto.Marshal(test.m)
|
||||
if err != nil {
|
||||
t.Errorf("%s: proto.Marshal: %v", test.desc, err)
|
||||
continue
|
||||
}
|
||||
if len(b) > 0 {
|
||||
t.Errorf("%s: Encoding is non-empty: %q", test.desc, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripProto3(t *testing.T) {
|
||||
m := &pb.Message{
|
||||
Name: "David", // (2 | 1<<3): 0x0a 0x05 "David"
|
||||
Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01
|
||||
HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01
|
||||
Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto"
|
||||
ResultCount: 47, // (0 | 7<<3): 0x38 0x2f
|
||||
TrueScotsman: true, // (0 | 8<<3): 0x40 0x01
|
||||
Score: 8.1, // (5 | 9<<3): 0x4d <8.1>
|
||||
|
||||
Key: []uint64{1, 0xdeadbeef},
|
||||
Nested: &pb.Nested{
|
||||
Bunny: "Monty",
|
||||
},
|
||||
}
|
||||
t.Logf(" m: %v", m)
|
||||
|
||||
b, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
t.Fatalf("proto.Marshal: %v", err)
|
||||
}
|
||||
t.Logf(" b: %q", b)
|
||||
|
||||
m2 := new(pb.Message)
|
||||
if err := proto.Unmarshal(b, m2); err != nil {
|
||||
t.Fatalf("proto.Unmarshal: %v", err)
|
||||
}
|
||||
t.Logf("m2: %v", m2)
|
||||
|
||||
if !proto.Equal(m, m2) {
|
||||
t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3SetDefaults(t *testing.T) {
|
||||
in := &pb.Message{
|
||||
Terrain: map[string]*pb.Nested{
|
||||
"meadow": new(pb.Nested),
|
||||
},
|
||||
Proto2Field: new(tpb.SubDefaults),
|
||||
Proto2Value: map[string]*tpb.SubDefaults{
|
||||
"badlands": new(tpb.SubDefaults),
|
||||
},
|
||||
}
|
||||
|
||||
got := proto.Clone(in).(*pb.Message)
|
||||
proto.SetDefaults(got)
|
||||
|
||||
// There are no defaults in proto3. Everything should be the zero value, but
|
||||
// we need to remember to set defaults for nested proto2 messages.
|
||||
want := &pb.Message{
|
||||
Terrain: map[string]*pb.Nested{
|
||||
"meadow": new(pb.Nested),
|
||||
},
|
||||
Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)},
|
||||
Proto2Value: map[string]*tpb.SubDefaults{
|
||||
"badlands": &tpb.SubDefaults{N: proto.Int64(7)},
|
||||
},
|
||||
}
|
||||
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want)
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This is a separate file and package from size_test.go because that one uses
|
||||
// generated messages and thus may not be in package proto without having a circular
|
||||
// dependency, whereas this file tests unexported details of size.go.
|
||||
|
||||
func TestVarintSize(t *testing.T) {
|
||||
// Check the edge cases carefully.
|
||||
testCases := []struct {
|
||||
n uint64
|
||||
size int
|
||||
}{
|
||||
{0, 1},
|
||||
{1, 1},
|
||||
{127, 1},
|
||||
{128, 2},
|
||||
{16383, 2},
|
||||
{16384, 3},
|
||||
{1<<63 - 1, 9},
|
||||
{1 << 63, 10},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
size := sizeVarint(tc.n)
|
||||
if size != tc.size {
|
||||
t.Errorf("sizeVarint(%d) = %d, want %d", tc.n, size, tc.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)}
|
||||
|
||||
// messageWithExtension2 is in equal_test.go.
|
||||
var messageWithExtension3 = &pb.MyMessage{Count: Int32(8)}
|
||||
|
||||
func init() {
|
||||
if err := SetExtension(messageWithExtension1, pb.E_Ext_More, &pb.Ext{Data: String("Abbott")}); err != nil {
|
||||
log.Panicf("SetExtension: %v", err)
|
||||
}
|
||||
if err := SetExtension(messageWithExtension3, pb.E_Ext_More, &pb.Ext{Data: String("Costello")}); err != nil {
|
||||
log.Panicf("SetExtension: %v", err)
|
||||
}
|
||||
|
||||
// Force messageWithExtension3 to have the extension encoded.
|
||||
Marshal(messageWithExtension3)
|
||||
|
||||
}
|
||||
|
||||
var SizeTests = []struct {
|
||||
desc string
|
||||
pb Message
|
||||
}{
|
||||
{"empty", &pb.OtherMessage{}},
|
||||
// Basic types.
|
||||
{"bool", &pb.Defaults{F_Bool: Bool(true)}},
|
||||
{"int32", &pb.Defaults{F_Int32: Int32(12)}},
|
||||
{"negative int32", &pb.Defaults{F_Int32: Int32(-1)}},
|
||||
{"small int64", &pb.Defaults{F_Int64: Int64(1)}},
|
||||
{"big int64", &pb.Defaults{F_Int64: Int64(1 << 20)}},
|
||||
{"negative int64", &pb.Defaults{F_Int64: Int64(-1)}},
|
||||
{"fixed32", &pb.Defaults{F_Fixed32: Uint32(71)}},
|
||||
{"fixed64", &pb.Defaults{F_Fixed64: Uint64(72)}},
|
||||
{"uint32", &pb.Defaults{F_Uint32: Uint32(123)}},
|
||||
{"uint64", &pb.Defaults{F_Uint64: Uint64(124)}},
|
||||
{"float", &pb.Defaults{F_Float: Float32(12.6)}},
|
||||
{"double", &pb.Defaults{F_Double: Float64(13.9)}},
|
||||
{"string", &pb.Defaults{F_String: String("niles")}},
|
||||
{"bytes", &pb.Defaults{F_Bytes: []byte("wowsa")}},
|
||||
{"bytes, empty", &pb.Defaults{F_Bytes: []byte{}}},
|
||||
{"sint32", &pb.Defaults{F_Sint32: Int32(65)}},
|
||||
{"sint64", &pb.Defaults{F_Sint64: Int64(67)}},
|
||||
{"enum", &pb.Defaults{F_Enum: pb.Defaults_BLUE.Enum()}},
|
||||
// Repeated.
|
||||
{"empty repeated bool", &pb.MoreRepeated{Bools: []bool{}}},
|
||||
{"repeated bool", &pb.MoreRepeated{Bools: []bool{false, true, true, false}}},
|
||||
{"packed repeated bool", &pb.MoreRepeated{BoolsPacked: []bool{false, true, true, false, true, true, true}}},
|
||||
{"repeated int32", &pb.MoreRepeated{Ints: []int32{1, 12203, 1729, -1}}},
|
||||
{"repeated int32 packed", &pb.MoreRepeated{IntsPacked: []int32{1, 12203, 1729}}},
|
||||
{"repeated int64 packed", &pb.MoreRepeated{Int64SPacked: []int64{
|
||||
// Need enough large numbers to verify that the header is counting the number of bytes
|
||||
// for the field, not the number of elements.
|
||||
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||
}}},
|
||||
{"repeated string", &pb.MoreRepeated{Strings: []string{"r", "ken", "gri"}}},
|
||||
{"repeated fixed", &pb.MoreRepeated{Fixeds: []uint32{1, 2, 3, 4}}},
|
||||
// Nested.
|
||||
{"nested", &pb.OldMessage{Nested: &pb.OldMessage_Nested{Name: String("whatever")}}},
|
||||
{"group", &pb.GroupOld{G: &pb.GroupOld_G{X: Int32(12345)}}},
|
||||
// Other things.
|
||||
{"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}},
|
||||
{"extension (unencoded)", messageWithExtension1},
|
||||
{"extension (encoded)", messageWithExtension3},
|
||||
// proto3 message
|
||||
{"proto3 empty", &proto3pb.Message{}},
|
||||
{"proto3 bool", &proto3pb.Message{TrueScotsman: true}},
|
||||
{"proto3 int64", &proto3pb.Message{ResultCount: 1}},
|
||||
{"proto3 uint32", &proto3pb.Message{HeightInCm: 123}},
|
||||
{"proto3 float", &proto3pb.Message{Score: 12.6}},
|
||||
{"proto3 string", &proto3pb.Message{Name: "Snezana"}},
|
||||
{"proto3 bytes", &proto3pb.Message{Data: []byte("wowsa")}},
|
||||
{"proto3 bytes, empty", &proto3pb.Message{Data: []byte{}}},
|
||||
{"proto3 enum", &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
|
||||
{"proto3 map field with empty bytes", &proto3pb.MessageWithMap{ByteMapping: map[bool][]byte{false: []byte{}}}},
|
||||
|
||||
{"map field", &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob", 7: "Andrew"}}},
|
||||
{"map field with message", &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{0x7001: &pb.FloatingPoint{F: Float64(2.0)}}}},
|
||||
{"map field with bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte("this time for sure")}}},
|
||||
{"map field with empty bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte{}}}},
|
||||
|
||||
{"map field with big entry", &pb.MessageWithMap{NameMapping: map[int32]string{8: strings.Repeat("x", 125)}}},
|
||||
{"map field with big key and val", &pb.MessageWithMap{StrToStr: map[string]string{strings.Repeat("x", 70): strings.Repeat("y", 70)}}},
|
||||
{"map field with big numeric key", &pb.MessageWithMap{NameMapping: map[int32]string{0xf00d: "om nom nom"}}},
|
||||
|
||||
{"oneof not set", &pb.Oneof{}},
|
||||
{"oneof bool", &pb.Oneof{Union: &pb.Oneof_F_Bool{true}}},
|
||||
{"oneof zero int32", &pb.Oneof{Union: &pb.Oneof_F_Int32{0}}},
|
||||
{"oneof big int32", &pb.Oneof{Union: &pb.Oneof_F_Int32{1 << 20}}},
|
||||
{"oneof int64", &pb.Oneof{Union: &pb.Oneof_F_Int64{42}}},
|
||||
{"oneof fixed32", &pb.Oneof{Union: &pb.Oneof_F_Fixed32{43}}},
|
||||
{"oneof fixed64", &pb.Oneof{Union: &pb.Oneof_F_Fixed64{44}}},
|
||||
{"oneof uint32", &pb.Oneof{Union: &pb.Oneof_F_Uint32{45}}},
|
||||
{"oneof uint64", &pb.Oneof{Union: &pb.Oneof_F_Uint64{46}}},
|
||||
{"oneof float", &pb.Oneof{Union: &pb.Oneof_F_Float{47.1}}},
|
||||
{"oneof double", &pb.Oneof{Union: &pb.Oneof_F_Double{48.9}}},
|
||||
{"oneof string", &pb.Oneof{Union: &pb.Oneof_F_String{"Rhythmic Fman"}}},
|
||||
{"oneof bytes", &pb.Oneof{Union: &pb.Oneof_F_Bytes{[]byte("let go")}}},
|
||||
{"oneof sint32", &pb.Oneof{Union: &pb.Oneof_F_Sint32{50}}},
|
||||
{"oneof sint64", &pb.Oneof{Union: &pb.Oneof_F_Sint64{51}}},
|
||||
{"oneof enum", &pb.Oneof{Union: &pb.Oneof_F_Enum{pb.MyMessage_BLUE}}},
|
||||
{"message for oneof", &pb.GoTestField{Label: String("k"), Type: String("v")}},
|
||||
{"oneof message", &pb.Oneof{Union: &pb.Oneof_F_Message{&pb.GoTestField{Label: String("k"), Type: String("v")}}}},
|
||||
{"oneof group", &pb.Oneof{Union: &pb.Oneof_FGroup{&pb.Oneof_F_Group{X: Int32(52)}}}},
|
||||
{"oneof largest tag", &pb.Oneof{Union: &pb.Oneof_F_Largest_Tag{1}}},
|
||||
{"multiple oneofs", &pb.Oneof{Union: &pb.Oneof_F_Int32{1}, Tormato: &pb.Oneof_Value{2}}},
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
for _, tc := range SizeTests {
|
||||
size := Size(tc.pb)
|
||||
b, err := Marshal(tc.pb)
|
||||
if err != nil {
|
||||
t.Errorf("%v: Marshal failed: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
if size != len(b) {
|
||||
t.Errorf("%v: Size(%v) = %d, want %d", tc.desc, tc.pb, size, len(b))
|
||||
t.Logf("%v: bytes: %#v", tc.desc, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
include ../../Make.protobuf
|
||||
|
||||
all: regenerate
|
||||
|
||||
regenerate:
|
||||
rm -f test.pb.go
|
||||
make test.pb.go
|
||||
|
||||
# The following rules are just aids to development. Not needed for typical testing.
|
||||
|
||||
diff: regenerate
|
||||
git diff test.pb.go
|
||||
|
||||
restore:
|
||||
cp test.pb.go.golden test.pb.go
|
||||
|
||||
preserve:
|
||||
cp test.pb.go test.pb.go.golden
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Verify that the compiler output for test.proto is unchanged.
|
||||
|
||||
package testdata
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// sum returns in string form (for easy comparison) the SHA-1 hash of the named file.
|
||||
func sum(t *testing.T, name string) string {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("sum(%q): length is %d", name, len(data))
|
||||
hash := sha1.New()
|
||||
_, err = hash.Write(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return fmt.Sprintf("% x", hash.Sum(nil))
|
||||
}
|
||||
|
||||
func run(t *testing.T, name string, args ...string) {
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGolden(t *testing.T) {
|
||||
// Compute the original checksum.
|
||||
goldenSum := sum(t, "test.pb.go")
|
||||
// Run the proto compiler.
|
||||
run(t, "protoc", "--go_out="+os.TempDir(), "test.proto")
|
||||
newFile := filepath.Join(os.TempDir(), "test.pb.go")
|
||||
defer os.Remove(newFile)
|
||||
// Compute the new checksum.
|
||||
newSum := sum(t, newFile)
|
||||
// Verify
|
||||
if newSum != goldenSum {
|
||||
run(t, "diff", "-u", "test.pb.go", newFile)
|
||||
t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go")
|
||||
}
|
||||
}
|
||||
+3992
File diff suppressed because it is too large
Load Diff
+535
@@ -0,0 +1,535 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// A feature-rich test file for the protocol compiler and libraries.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package testdata;
|
||||
|
||||
enum FOO { FOO1 = 1; };
|
||||
|
||||
message GoEnum {
|
||||
required FOO foo = 1;
|
||||
}
|
||||
|
||||
message GoTestField {
|
||||
required string Label = 1;
|
||||
required string Type = 2;
|
||||
}
|
||||
|
||||
message GoTest {
|
||||
// An enum, for completeness.
|
||||
enum KIND {
|
||||
VOID = 0;
|
||||
|
||||
// Basic types
|
||||
BOOL = 1;
|
||||
BYTES = 2;
|
||||
FINGERPRINT = 3;
|
||||
FLOAT = 4;
|
||||
INT = 5;
|
||||
STRING = 6;
|
||||
TIME = 7;
|
||||
|
||||
// Groupings
|
||||
TUPLE = 8;
|
||||
ARRAY = 9;
|
||||
MAP = 10;
|
||||
|
||||
// Table types
|
||||
TABLE = 11;
|
||||
|
||||
// Functions
|
||||
FUNCTION = 12; // last tag
|
||||
};
|
||||
|
||||
// Some typical parameters
|
||||
required KIND Kind = 1;
|
||||
optional string Table = 2;
|
||||
optional int32 Param = 3;
|
||||
|
||||
// Required, repeated and optional foreign fields.
|
||||
required GoTestField RequiredField = 4;
|
||||
repeated GoTestField RepeatedField = 5;
|
||||
optional GoTestField OptionalField = 6;
|
||||
|
||||
// Required fields of all basic types
|
||||
required bool F_Bool_required = 10;
|
||||
required int32 F_Int32_required = 11;
|
||||
required int64 F_Int64_required = 12;
|
||||
required fixed32 F_Fixed32_required = 13;
|
||||
required fixed64 F_Fixed64_required = 14;
|
||||
required uint32 F_Uint32_required = 15;
|
||||
required uint64 F_Uint64_required = 16;
|
||||
required float F_Float_required = 17;
|
||||
required double F_Double_required = 18;
|
||||
required string F_String_required = 19;
|
||||
required bytes F_Bytes_required = 101;
|
||||
required sint32 F_Sint32_required = 102;
|
||||
required sint64 F_Sint64_required = 103;
|
||||
|
||||
// Repeated fields of all basic types
|
||||
repeated bool F_Bool_repeated = 20;
|
||||
repeated int32 F_Int32_repeated = 21;
|
||||
repeated int64 F_Int64_repeated = 22;
|
||||
repeated fixed32 F_Fixed32_repeated = 23;
|
||||
repeated fixed64 F_Fixed64_repeated = 24;
|
||||
repeated uint32 F_Uint32_repeated = 25;
|
||||
repeated uint64 F_Uint64_repeated = 26;
|
||||
repeated float F_Float_repeated = 27;
|
||||
repeated double F_Double_repeated = 28;
|
||||
repeated string F_String_repeated = 29;
|
||||
repeated bytes F_Bytes_repeated = 201;
|
||||
repeated sint32 F_Sint32_repeated = 202;
|
||||
repeated sint64 F_Sint64_repeated = 203;
|
||||
|
||||
// Optional fields of all basic types
|
||||
optional bool F_Bool_optional = 30;
|
||||
optional int32 F_Int32_optional = 31;
|
||||
optional int64 F_Int64_optional = 32;
|
||||
optional fixed32 F_Fixed32_optional = 33;
|
||||
optional fixed64 F_Fixed64_optional = 34;
|
||||
optional uint32 F_Uint32_optional = 35;
|
||||
optional uint64 F_Uint64_optional = 36;
|
||||
optional float F_Float_optional = 37;
|
||||
optional double F_Double_optional = 38;
|
||||
optional string F_String_optional = 39;
|
||||
optional bytes F_Bytes_optional = 301;
|
||||
optional sint32 F_Sint32_optional = 302;
|
||||
optional sint64 F_Sint64_optional = 303;
|
||||
|
||||
// Default-valued fields of all basic types
|
||||
optional bool F_Bool_defaulted = 40 [default=true];
|
||||
optional int32 F_Int32_defaulted = 41 [default=32];
|
||||
optional int64 F_Int64_defaulted = 42 [default=64];
|
||||
optional fixed32 F_Fixed32_defaulted = 43 [default=320];
|
||||
optional fixed64 F_Fixed64_defaulted = 44 [default=640];
|
||||
optional uint32 F_Uint32_defaulted = 45 [default=3200];
|
||||
optional uint64 F_Uint64_defaulted = 46 [default=6400];
|
||||
optional float F_Float_defaulted = 47 [default=314159.];
|
||||
optional double F_Double_defaulted = 48 [default=271828.];
|
||||
optional string F_String_defaulted = 49 [default="hello, \"world!\"\n"];
|
||||
optional bytes F_Bytes_defaulted = 401 [default="Bignose"];
|
||||
optional sint32 F_Sint32_defaulted = 402 [default = -32];
|
||||
optional sint64 F_Sint64_defaulted = 403 [default = -64];
|
||||
|
||||
// Packed repeated fields (no string or bytes).
|
||||
repeated bool F_Bool_repeated_packed = 50 [packed=true];
|
||||
repeated int32 F_Int32_repeated_packed = 51 [packed=true];
|
||||
repeated int64 F_Int64_repeated_packed = 52 [packed=true];
|
||||
repeated fixed32 F_Fixed32_repeated_packed = 53 [packed=true];
|
||||
repeated fixed64 F_Fixed64_repeated_packed = 54 [packed=true];
|
||||
repeated uint32 F_Uint32_repeated_packed = 55 [packed=true];
|
||||
repeated uint64 F_Uint64_repeated_packed = 56 [packed=true];
|
||||
repeated float F_Float_repeated_packed = 57 [packed=true];
|
||||
repeated double F_Double_repeated_packed = 58 [packed=true];
|
||||
repeated sint32 F_Sint32_repeated_packed = 502 [packed=true];
|
||||
repeated sint64 F_Sint64_repeated_packed = 503 [packed=true];
|
||||
|
||||
// Required, repeated, and optional groups.
|
||||
required group RequiredGroup = 70 {
|
||||
required string RequiredField = 71;
|
||||
};
|
||||
|
||||
repeated group RepeatedGroup = 80 {
|
||||
required string RequiredField = 81;
|
||||
};
|
||||
|
||||
optional group OptionalGroup = 90 {
|
||||
required string RequiredField = 91;
|
||||
};
|
||||
}
|
||||
|
||||
// For testing skipping of unrecognized fields.
|
||||
// Numbers are all big, larger than tag numbers in GoTestField,
|
||||
// the message used in the corresponding test.
|
||||
message GoSkipTest {
|
||||
required int32 skip_int32 = 11;
|
||||
required fixed32 skip_fixed32 = 12;
|
||||
required fixed64 skip_fixed64 = 13;
|
||||
required string skip_string = 14;
|
||||
required group SkipGroup = 15 {
|
||||
required int32 group_int32 = 16;
|
||||
required string group_string = 17;
|
||||
}
|
||||
}
|
||||
|
||||
// For testing packed/non-packed decoder switching.
|
||||
// A serialized instance of one should be deserializable as the other.
|
||||
message NonPackedTest {
|
||||
repeated int32 a = 1;
|
||||
}
|
||||
|
||||
message PackedTest {
|
||||
repeated int32 b = 1 [packed=true];
|
||||
}
|
||||
|
||||
message MaxTag {
|
||||
// Maximum possible tag number.
|
||||
optional string last_field = 536870911;
|
||||
}
|
||||
|
||||
message OldMessage {
|
||||
message Nested {
|
||||
optional string name = 1;
|
||||
}
|
||||
optional Nested nested = 1;
|
||||
|
||||
optional int32 num = 2;
|
||||
}
|
||||
|
||||
// NewMessage is wire compatible with OldMessage;
|
||||
// imagine it as a future version.
|
||||
message NewMessage {
|
||||
message Nested {
|
||||
optional string name = 1;
|
||||
optional string food_group = 2;
|
||||
}
|
||||
optional Nested nested = 1;
|
||||
|
||||
// This is an int32 in OldMessage.
|
||||
optional int64 num = 2;
|
||||
}
|
||||
|
||||
// Smaller tests for ASCII formatting.
|
||||
|
||||
message InnerMessage {
|
||||
required string host = 1;
|
||||
optional int32 port = 2 [default=4000];
|
||||
optional bool connected = 3;
|
||||
}
|
||||
|
||||
message OtherMessage {
|
||||
optional int64 key = 1;
|
||||
optional bytes value = 2;
|
||||
optional float weight = 3;
|
||||
optional InnerMessage inner = 4;
|
||||
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
||||
message MyMessage {
|
||||
required int32 count = 1;
|
||||
optional string name = 2;
|
||||
optional string quote = 3;
|
||||
repeated string pet = 4;
|
||||
optional InnerMessage inner = 5;
|
||||
repeated OtherMessage others = 6;
|
||||
repeated InnerMessage rep_inner = 12;
|
||||
|
||||
enum Color {
|
||||
RED = 0;
|
||||
GREEN = 1;
|
||||
BLUE = 2;
|
||||
};
|
||||
optional Color bikeshed = 7;
|
||||
|
||||
optional group SomeGroup = 8 {
|
||||
optional int32 group_field = 9;
|
||||
}
|
||||
|
||||
// This field becomes [][]byte in the generated code.
|
||||
repeated bytes rep_bytes = 10;
|
||||
|
||||
optional double bigfloat = 11;
|
||||
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
||||
message Ext {
|
||||
extend MyMessage {
|
||||
optional Ext more = 103;
|
||||
optional string text = 104;
|
||||
optional int32 number = 105;
|
||||
}
|
||||
|
||||
optional string data = 1;
|
||||
}
|
||||
|
||||
extend MyMessage {
|
||||
repeated string greeting = 106;
|
||||
}
|
||||
|
||||
message ComplexExtension {
|
||||
optional int32 first = 1;
|
||||
optional int32 second = 2;
|
||||
repeated int32 third = 3;
|
||||
}
|
||||
|
||||
extend OtherMessage {
|
||||
optional ComplexExtension complex = 200;
|
||||
repeated ComplexExtension r_complex = 201;
|
||||
}
|
||||
|
||||
message DefaultsMessage {
|
||||
enum DefaultsEnum {
|
||||
ZERO = 0;
|
||||
ONE = 1;
|
||||
TWO = 2;
|
||||
};
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
||||
extend DefaultsMessage {
|
||||
optional double no_default_double = 101;
|
||||
optional float no_default_float = 102;
|
||||
optional int32 no_default_int32 = 103;
|
||||
optional int64 no_default_int64 = 104;
|
||||
optional uint32 no_default_uint32 = 105;
|
||||
optional uint64 no_default_uint64 = 106;
|
||||
optional sint32 no_default_sint32 = 107;
|
||||
optional sint64 no_default_sint64 = 108;
|
||||
optional fixed32 no_default_fixed32 = 109;
|
||||
optional fixed64 no_default_fixed64 = 110;
|
||||
optional sfixed32 no_default_sfixed32 = 111;
|
||||
optional sfixed64 no_default_sfixed64 = 112;
|
||||
optional bool no_default_bool = 113;
|
||||
optional string no_default_string = 114;
|
||||
optional bytes no_default_bytes = 115;
|
||||
optional DefaultsMessage.DefaultsEnum no_default_enum = 116;
|
||||
|
||||
optional double default_double = 201 [default = 3.1415];
|
||||
optional float default_float = 202 [default = 3.14];
|
||||
optional int32 default_int32 = 203 [default = 42];
|
||||
optional int64 default_int64 = 204 [default = 43];
|
||||
optional uint32 default_uint32 = 205 [default = 44];
|
||||
optional uint64 default_uint64 = 206 [default = 45];
|
||||
optional sint32 default_sint32 = 207 [default = 46];
|
||||
optional sint64 default_sint64 = 208 [default = 47];
|
||||
optional fixed32 default_fixed32 = 209 [default = 48];
|
||||
optional fixed64 default_fixed64 = 210 [default = 49];
|
||||
optional sfixed32 default_sfixed32 = 211 [default = 50];
|
||||
optional sfixed64 default_sfixed64 = 212 [default = 51];
|
||||
optional bool default_bool = 213 [default = true];
|
||||
optional string default_string = 214 [default = "Hello, string"];
|
||||
optional bytes default_bytes = 215 [default = "Hello, bytes"];
|
||||
optional DefaultsMessage.DefaultsEnum default_enum = 216 [default = ONE];
|
||||
}
|
||||
|
||||
message MyMessageSet {
|
||||
option message_set_wire_format = true;
|
||||
extensions 100 to max;
|
||||
}
|
||||
|
||||
message Empty {
|
||||
}
|
||||
|
||||
extend MyMessageSet {
|
||||
optional Empty x201 = 201;
|
||||
optional Empty x202 = 202;
|
||||
optional Empty x203 = 203;
|
||||
optional Empty x204 = 204;
|
||||
optional Empty x205 = 205;
|
||||
optional Empty x206 = 206;
|
||||
optional Empty x207 = 207;
|
||||
optional Empty x208 = 208;
|
||||
optional Empty x209 = 209;
|
||||
optional Empty x210 = 210;
|
||||
optional Empty x211 = 211;
|
||||
optional Empty x212 = 212;
|
||||
optional Empty x213 = 213;
|
||||
optional Empty x214 = 214;
|
||||
optional Empty x215 = 215;
|
||||
optional Empty x216 = 216;
|
||||
optional Empty x217 = 217;
|
||||
optional Empty x218 = 218;
|
||||
optional Empty x219 = 219;
|
||||
optional Empty x220 = 220;
|
||||
optional Empty x221 = 221;
|
||||
optional Empty x222 = 222;
|
||||
optional Empty x223 = 223;
|
||||
optional Empty x224 = 224;
|
||||
optional Empty x225 = 225;
|
||||
optional Empty x226 = 226;
|
||||
optional Empty x227 = 227;
|
||||
optional Empty x228 = 228;
|
||||
optional Empty x229 = 229;
|
||||
optional Empty x230 = 230;
|
||||
optional Empty x231 = 231;
|
||||
optional Empty x232 = 232;
|
||||
optional Empty x233 = 233;
|
||||
optional Empty x234 = 234;
|
||||
optional Empty x235 = 235;
|
||||
optional Empty x236 = 236;
|
||||
optional Empty x237 = 237;
|
||||
optional Empty x238 = 238;
|
||||
optional Empty x239 = 239;
|
||||
optional Empty x240 = 240;
|
||||
optional Empty x241 = 241;
|
||||
optional Empty x242 = 242;
|
||||
optional Empty x243 = 243;
|
||||
optional Empty x244 = 244;
|
||||
optional Empty x245 = 245;
|
||||
optional Empty x246 = 246;
|
||||
optional Empty x247 = 247;
|
||||
optional Empty x248 = 248;
|
||||
optional Empty x249 = 249;
|
||||
optional Empty x250 = 250;
|
||||
}
|
||||
|
||||
message MessageList {
|
||||
repeated group Message = 1 {
|
||||
required string name = 2;
|
||||
required int32 count = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Strings {
|
||||
optional string string_field = 1;
|
||||
optional bytes bytes_field = 2;
|
||||
}
|
||||
|
||||
message Defaults {
|
||||
enum Color {
|
||||
RED = 0;
|
||||
GREEN = 1;
|
||||
BLUE = 2;
|
||||
}
|
||||
|
||||
// Default-valued fields of all basic types.
|
||||
// Same as GoTest, but copied here to make testing easier.
|
||||
optional bool F_Bool = 1 [default=true];
|
||||
optional int32 F_Int32 = 2 [default=32];
|
||||
optional int64 F_Int64 = 3 [default=64];
|
||||
optional fixed32 F_Fixed32 = 4 [default=320];
|
||||
optional fixed64 F_Fixed64 = 5 [default=640];
|
||||
optional uint32 F_Uint32 = 6 [default=3200];
|
||||
optional uint64 F_Uint64 = 7 [default=6400];
|
||||
optional float F_Float = 8 [default=314159.];
|
||||
optional double F_Double = 9 [default=271828.];
|
||||
optional string F_String = 10 [default="hello, \"world!\"\n"];
|
||||
optional bytes F_Bytes = 11 [default="Bignose"];
|
||||
optional sint32 F_Sint32 = 12 [default=-32];
|
||||
optional sint64 F_Sint64 = 13 [default=-64];
|
||||
optional Color F_Enum = 14 [default=GREEN];
|
||||
|
||||
// More fields with crazy defaults.
|
||||
optional float F_Pinf = 15 [default=inf];
|
||||
optional float F_Ninf = 16 [default=-inf];
|
||||
optional float F_Nan = 17 [default=nan];
|
||||
|
||||
// Sub-message.
|
||||
optional SubDefaults sub = 18;
|
||||
|
||||
// Redundant but explicit defaults.
|
||||
optional string str_zero = 19 [default=""];
|
||||
}
|
||||
|
||||
message SubDefaults {
|
||||
optional int64 n = 1 [default=7];
|
||||
}
|
||||
|
||||
message RepeatedEnum {
|
||||
enum Color {
|
||||
RED = 1;
|
||||
}
|
||||
repeated Color color = 1;
|
||||
}
|
||||
|
||||
message MoreRepeated {
|
||||
repeated bool bools = 1;
|
||||
repeated bool bools_packed = 2 [packed=true];
|
||||
repeated int32 ints = 3;
|
||||
repeated int32 ints_packed = 4 [packed=true];
|
||||
repeated int64 int64s_packed = 7 [packed=true];
|
||||
repeated string strings = 5;
|
||||
repeated fixed32 fixeds = 6;
|
||||
}
|
||||
|
||||
// GroupOld and GroupNew have the same wire format.
|
||||
// GroupNew has a new field inside a group.
|
||||
|
||||
message GroupOld {
|
||||
optional group G = 101 {
|
||||
optional int32 x = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupNew {
|
||||
optional group G = 101 {
|
||||
optional int32 x = 2;
|
||||
optional int32 y = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message FloatingPoint {
|
||||
required double f = 1;
|
||||
}
|
||||
|
||||
message MessageWithMap {
|
||||
map<int32, string> name_mapping = 1;
|
||||
map<sint64, FloatingPoint> msg_mapping = 2;
|
||||
map<bool, bytes> byte_mapping = 3;
|
||||
map<string, string> str_to_str = 4;
|
||||
}
|
||||
|
||||
message Oneof {
|
||||
oneof union {
|
||||
bool F_Bool = 1;
|
||||
int32 F_Int32 = 2;
|
||||
int64 F_Int64 = 3;
|
||||
fixed32 F_Fixed32 = 4;
|
||||
fixed64 F_Fixed64 = 5;
|
||||
uint32 F_Uint32 = 6;
|
||||
uint64 F_Uint64 = 7;
|
||||
float F_Float = 8;
|
||||
double F_Double = 9;
|
||||
string F_String = 10;
|
||||
bytes F_Bytes = 11;
|
||||
sint32 F_Sint32 = 12;
|
||||
sint64 F_Sint64 = 13;
|
||||
MyMessage.Color F_Enum = 14;
|
||||
GoTestField F_Message = 15;
|
||||
group F_Group = 16 {
|
||||
optional int32 x = 17;
|
||||
}
|
||||
int32 F_Largest_Tag = 536870911;
|
||||
}
|
||||
|
||||
oneof tormato {
|
||||
int32 value = 100;
|
||||
}
|
||||
}
|
||||
|
||||
message Communique {
|
||||
optional bool make_me_cry = 1;
|
||||
|
||||
// This is a oneof, called "union".
|
||||
oneof union {
|
||||
int32 number = 5;
|
||||
string name = 6;
|
||||
bytes data = 7;
|
||||
double temp_c = 8;
|
||||
MyMessage.Color col = 9;
|
||||
Strings msg = 10;
|
||||
}
|
||||
}
|
||||
+849
@@ -0,0 +1,849 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for writing the text protocol buffer format.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
newline = []byte("\n")
|
||||
spaces = []byte(" ")
|
||||
gtNewline = []byte(">\n")
|
||||
endBraceNewline = []byte("}\n")
|
||||
backslashN = []byte{'\\', 'n'}
|
||||
backslashR = []byte{'\\', 'r'}
|
||||
backslashT = []byte{'\\', 't'}
|
||||
backslashDQ = []byte{'\\', '"'}
|
||||
backslashBS = []byte{'\\', '\\'}
|
||||
posInf = []byte("inf")
|
||||
negInf = []byte("-inf")
|
||||
nan = []byte("nan")
|
||||
)
|
||||
|
||||
type writer interface {
|
||||
io.Writer
|
||||
WriteByte(byte) error
|
||||
}
|
||||
|
||||
// textWriter is an io.Writer that tracks its indentation level.
|
||||
type textWriter struct {
|
||||
ind int
|
||||
complete bool // if the current position is a complete line
|
||||
compact bool // whether to write out as a one-liner
|
||||
w writer
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteString(s string) (n int, err error) {
|
||||
if !strings.Contains(s, "\n") {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
w.complete = false
|
||||
return io.WriteString(w.w, s)
|
||||
}
|
||||
// WriteString is typically called without newlines, so this
|
||||
// codepath and its copy are rare. We copy to avoid
|
||||
// duplicating all of Write's logic here.
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (w *textWriter) Write(p []byte) (n int, err error) {
|
||||
newlines := bytes.Count(p, newline)
|
||||
if newlines == 0 {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
n, err = w.w.Write(p)
|
||||
w.complete = false
|
||||
return n, err
|
||||
}
|
||||
|
||||
frags := bytes.SplitN(p, newline, newlines+1)
|
||||
if w.compact {
|
||||
for i, frag := range frags {
|
||||
if i > 0 {
|
||||
if err := w.w.WriteByte(' '); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
for i, frag := range frags {
|
||||
if w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if i+1 < len(frags) {
|
||||
if err := w.w.WriteByte('\n'); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
w.complete = len(frags[len(frags)-1]) == 0
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteByte(c byte) error {
|
||||
if w.compact && c == '\n' {
|
||||
c = ' '
|
||||
}
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
err := w.w.WriteByte(c)
|
||||
w.complete = c == '\n'
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *textWriter) indent() { w.ind++ }
|
||||
|
||||
func (w *textWriter) unindent() {
|
||||
if w.ind == 0 {
|
||||
log.Printf("proto: textWriter unindented too far")
|
||||
return
|
||||
}
|
||||
w.ind--
|
||||
}
|
||||
|
||||
func writeName(w *textWriter, props *Properties) error {
|
||||
if _, err := w.WriteString(props.OrigName); err != nil {
|
||||
return err
|
||||
}
|
||||
if props.Wire != "group" {
|
||||
return w.WriteByte(':')
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// raw is the interface satisfied by RawMessage.
|
||||
type raw interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
func requiresQuotes(u string) bool {
|
||||
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||
for _, ch := range u {
|
||||
switch {
|
||||
case ch == '.' || ch == '/' || ch == '_':
|
||||
continue
|
||||
case '0' <= ch && ch <= '9':
|
||||
continue
|
||||
case 'A' <= ch && ch <= 'Z':
|
||||
continue
|
||||
case 'a' <= ch && ch <= 'z':
|
||||
continue
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isAny reports whether sv is a google.protobuf.Any message
|
||||
func isAny(sv reflect.Value) bool {
|
||||
type wkt interface {
|
||||
XXX_WellKnownType() string
|
||||
}
|
||||
t, ok := sv.Addr().Interface().(wkt)
|
||||
return ok && t.XXX_WellKnownType() == "Any"
|
||||
}
|
||||
|
||||
// writeProto3Any writes an expanded google.protobuf.Any message.
|
||||
//
|
||||
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
||||
// required messages are not linked in).
|
||||
//
|
||||
// It returns (true, error) when sv was written in expanded format or an error
|
||||
// was encountered.
|
||||
func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) {
|
||||
turl := sv.FieldByName("TypeUrl")
|
||||
val := sv.FieldByName("Value")
|
||||
if !turl.IsValid() || !val.IsValid() {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
b, ok := val.Interface().([]byte)
|
||||
if !ok {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
parts := strings.Split(turl.String(), "/")
|
||||
mt := MessageType(parts[len(parts)-1])
|
||||
if mt == nil {
|
||||
return false, nil
|
||||
}
|
||||
m := reflect.New(mt.Elem())
|
||||
if err := Unmarshal(b, m.Interface().(Message)); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
w.Write([]byte("["))
|
||||
u := turl.String()
|
||||
if requiresQuotes(u) {
|
||||
writeString(w, u)
|
||||
} else {
|
||||
w.Write([]byte(u))
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("]:<"))
|
||||
} else {
|
||||
w.Write([]byte("]: <\n"))
|
||||
w.ind++
|
||||
}
|
||||
if err := tm.writeStruct(w, m.Elem()); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("> "))
|
||||
} else {
|
||||
w.ind--
|
||||
w.Write([]byte(">\n"))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||
if tm.ExpandAny && isAny(sv) {
|
||||
if canExpand, err := tm.writeProto3Any(w, sv); canExpand {
|
||||
return err
|
||||
}
|
||||
}
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
fv := sv.Field(i)
|
||||
props := sprops.Prop[i]
|
||||
name := st.Field(i).Name
|
||||
|
||||
if strings.HasPrefix(name, "XXX_") {
|
||||
// There are two XXX_ fields:
|
||||
// XXX_unrecognized []byte
|
||||
// XXX_extensions map[int32]proto.Extension
|
||||
// The first is handled here;
|
||||
// the second is handled at the bottom of this function.
|
||||
if name == "XXX_unrecognized" && !fv.IsNil() {
|
||||
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Field not filled in. This could be an optional field or
|
||||
// a required field that wasn't filled in. Either way, there
|
||||
// isn't anything we can show for it.
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||
// Repeated field that is empty, or a bytes field that is unused.
|
||||
continue
|
||||
}
|
||||
|
||||
if props.Repeated && fv.Kind() == reflect.Slice {
|
||||
// Repeated field.
|
||||
for j := 0; j < fv.Len(); j++ {
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v := fv.Index(j)
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
// A nil message in a repeated field is not valid,
|
||||
// but we can handle that more gracefully than panicking.
|
||||
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := tm.writeAny(w, v, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Map {
|
||||
// Map fields are rendered as a repeated struct with key/value fields.
|
||||
keys := fv.MapKeys()
|
||||
sort.Sort(mapKeys(keys))
|
||||
for _, key := range keys {
|
||||
val := fv.MapIndex(key)
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// open struct
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
// key
|
||||
if _, err := w.WriteString("key:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
// nil values aren't legal, but we can avoid panicking because of them.
|
||||
if val.Kind() != reflect.Ptr || !val.IsNil() {
|
||||
// value
|
||||
if _, err := w.WriteString("value:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// close struct
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
||||
// empty bytes field
|
||||
continue
|
||||
}
|
||||
if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
||||
// proto3 non-repeated scalar field; skip if zero value
|
||||
if isProto3Zero(fv) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fv.Kind() == reflect.Interface {
|
||||
// Check if it is a oneof.
|
||||
if st.Field(i).Tag.Get("protobuf_oneof") != "" {
|
||||
// fv is nil, or holds a pointer to generated struct.
|
||||
// That generated struct has exactly one field,
|
||||
// which has a protobuf struct tag.
|
||||
if fv.IsNil() {
|
||||
continue
|
||||
}
|
||||
inner := fv.Elem().Elem() // interface -> *T -> T
|
||||
tag := inner.Type().Field(0).Tag.Get("protobuf")
|
||||
props = new(Properties) // Overwrite the outer props var, but not its pointee.
|
||||
props.Parse(tag)
|
||||
// Write the value in the oneof, not the oneof itself.
|
||||
fv = inner.Field(0)
|
||||
|
||||
// Special case to cope with malformed messages gracefully:
|
||||
// If the value in the oneof is a nil pointer, don't panic
|
||||
// in writeAny.
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Use errors.New so writeAny won't render quotes.
|
||||
msg := errors.New("/* nil */")
|
||||
fv = reflect.ValueOf(&msg).Elem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if b, ok := fv.Interface().(raw); ok {
|
||||
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Enums have a String method, so writeAny will work fine.
|
||||
if err := tm.writeAny(w, fv, props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions (the XXX_extensions field).
|
||||
pv := sv.Addr()
|
||||
if pv.Type().Implements(extendableProtoType) {
|
||||
if err := tm.writeExtensions(w, pv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeRaw writes an uninterpreted raw message.
|
||||
func writeRaw(w *textWriter, b []byte) error {
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if err := writeUnknownStruct(w, b); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAny writes an arbitrary field.
|
||||
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
// Floats have special cases.
|
||||
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||
x := v.Float()
|
||||
var b []byte
|
||||
switch {
|
||||
case math.IsInf(x, 1):
|
||||
b = posInf
|
||||
case math.IsInf(x, -1):
|
||||
b = negInf
|
||||
case math.IsNaN(x):
|
||||
b = nan
|
||||
}
|
||||
if b != nil {
|
||||
_, err := w.Write(b)
|
||||
return err
|
||||
}
|
||||
// Other values are handled below.
|
||||
}
|
||||
|
||||
// We don't attempt to serialise every possible value type; only those
|
||||
// that can occur in protocol buffers.
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
// Should only be a []byte; repeated fields are handled in writeStruct.
|
||||
if err := writeString(w, string(v.Interface().([]byte))); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.String:
|
||||
if err := writeString(w, v.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Required/optional group/message.
|
||||
var bra, ket byte = '<', '>'
|
||||
if props != nil && props.Wire == "group" {
|
||||
bra, ket = '{', '}'
|
||||
}
|
||||
if err := w.WriteByte(bra); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tm.writeStruct(w, v); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte(ket); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
_, err := fmt.Fprint(w, v.Interface())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// equivalent to C's isprint.
|
||||
func isprint(c byte) bool {
|
||||
return c >= 0x20 && c < 0x7f
|
||||
}
|
||||
|
||||
// writeString writes a string in the protocol buffer text format.
|
||||
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
||||
// we treat the string as a byte sequence, and we use octal escapes.
|
||||
// These differences are to maintain interoperability with the other
|
||||
// languages' implementations of the text format.
|
||||
func writeString(w *textWriter, s string) error {
|
||||
// use WriteByte here to get any needed indent
|
||||
if err := w.WriteByte('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
// Loop over the bytes, not the runes.
|
||||
for i := 0; i < len(s); i++ {
|
||||
var err error
|
||||
// Divergence from C++: we don't escape apostrophes.
|
||||
// There's no need to escape them, and the C++ parser
|
||||
// copes with a naked apostrophe.
|
||||
switch c := s[i]; c {
|
||||
case '\n':
|
||||
_, err = w.w.Write(backslashN)
|
||||
case '\r':
|
||||
_, err = w.w.Write(backslashR)
|
||||
case '\t':
|
||||
_, err = w.w.Write(backslashT)
|
||||
case '"':
|
||||
_, err = w.w.Write(backslashDQ)
|
||||
case '\\':
|
||||
_, err = w.w.Write(backslashBS)
|
||||
default:
|
||||
if isprint(c) {
|
||||
err = w.w.WriteByte(c)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.WriteByte('"')
|
||||
}
|
||||
|
||||
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
||||
if !w.compact {
|
||||
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b := NewBuffer(data)
|
||||
for b.index < len(b.buf) {
|
||||
x, err := b.DecodeVarint()
|
||||
if err != nil {
|
||||
_, err := fmt.Fprintf(w, "/* %v */\n", err)
|
||||
return err
|
||||
}
|
||||
wire, tag := x&7, x>>3
|
||||
if wire == WireEndGroup {
|
||||
w.unindent()
|
||||
if _, err := w.Write(endBraceNewline); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprint(w, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if wire != WireStartGroup {
|
||||
if err := w.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !w.compact || wire == WireStartGroup {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch wire {
|
||||
case WireBytes:
|
||||
buf, e := b.DecodeRawBytes(false)
|
||||
if e == nil {
|
||||
_, err = fmt.Fprintf(w, "%q", buf)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", e)
|
||||
}
|
||||
case WireFixed32:
|
||||
x, err = b.DecodeFixed32()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireFixed64:
|
||||
x, err = b.DecodeFixed64()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireStartGroup:
|
||||
err = w.WriteByte('{')
|
||||
w.indent()
|
||||
case WireVarint:
|
||||
x, err = b.DecodeVarint()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
default:
|
||||
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
||||
if err == nil {
|
||||
_, err = fmt.Fprint(w, x)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type int32Slice []int32
|
||||
|
||||
func (s int32Slice) Len() int { return len(s) }
|
||||
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// writeExtensions writes all the extensions in pv.
|
||||
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
||||
func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error {
|
||||
emap := extensionMaps[pv.Type().Elem()]
|
||||
ep := pv.Interface().(extendableProto)
|
||||
|
||||
// Order the extensions by ID.
|
||||
// This isn't strictly necessary, but it will give us
|
||||
// canonical output, which will also make testing easier.
|
||||
m := ep.ExtensionMap()
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids))
|
||||
|
||||
for _, extNum := range ids {
|
||||
ext := m[extNum]
|
||||
var desc *ExtensionDesc
|
||||
if emap != nil {
|
||||
desc = emap[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
// Unknown extension.
|
||||
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pb, err := GetExtension(ep, desc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting extension: %v", err)
|
||||
}
|
||||
|
||||
// Repeated extensions will appear as a slice.
|
||||
if !desc.repeated() {
|
||||
if err := tm.writeExtension(w, desc.Name, pb); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
v := reflect.ValueOf(pb)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error {
|
||||
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *textWriter) writeIndent() {
|
||||
if !w.complete {
|
||||
return
|
||||
}
|
||||
remain := w.ind * 2
|
||||
for remain > 0 {
|
||||
n := remain
|
||||
if n > len(spaces) {
|
||||
n = len(spaces)
|
||||
}
|
||||
w.w.Write(spaces[:n])
|
||||
remain -= n
|
||||
}
|
||||
w.complete = false
|
||||
}
|
||||
|
||||
// TextMarshaler is a configurable text format marshaler.
|
||||
type TextMarshaler struct {
|
||||
Compact bool // use compact text format (one line).
|
||||
ExpandAny bool // expand google.protobuf.Any messages of known types
|
||||
}
|
||||
|
||||
// Marshal writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error {
|
||||
val := reflect.ValueOf(pb)
|
||||
if pb == nil || val.IsNil() {
|
||||
w.Write([]byte("<nil>"))
|
||||
return nil
|
||||
}
|
||||
var bw *bufio.Writer
|
||||
ww, ok := w.(writer)
|
||||
if !ok {
|
||||
bw = bufio.NewWriter(w)
|
||||
ww = bw
|
||||
}
|
||||
aw := &textWriter{
|
||||
w: ww,
|
||||
complete: true,
|
||||
compact: tm.Compact,
|
||||
}
|
||||
|
||||
if etm, ok := pb.(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = aw.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Dereference the received pointer so we don't have outer < and >.
|
||||
v := reflect.Indirect(val)
|
||||
if err := tm.writeStruct(aw, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Text is the same as Marshal, but returns the string directly.
|
||||
func (tm *TextMarshaler) Text(pb Message) string {
|
||||
var buf bytes.Buffer
|
||||
tm.Marshal(&buf, pb)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTextMarshaler = TextMarshaler{}
|
||||
compactTextMarshaler = TextMarshaler{Compact: true}
|
||||
)
|
||||
|
||||
// TODO: consider removing some of the Marshal functions below.
|
||||
|
||||
// MarshalText writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
||||
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
|
||||
|
||||
// CompactText writes a given protocol buffer in compact text format (one line).
|
||||
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// CompactTextString is the same as CompactText, but returns the string directly.
|
||||
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
|
||||
+871
@@ -0,0 +1,871 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for parsing the Text protocol buffer format.
|
||||
// TODO: message sets.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ParseError struct {
|
||||
Message string
|
||||
Line int // 1-based line number
|
||||
Offset int // 0-based byte offset from start of input
|
||||
}
|
||||
|
||||
func (p *ParseError) Error() string {
|
||||
if p.Line == 1 {
|
||||
// show offset only for first line
|
||||
return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message)
|
||||
}
|
||||
return fmt.Sprintf("line %d: %v", p.Line, p.Message)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
value string
|
||||
err *ParseError
|
||||
line int // line number
|
||||
offset int // byte number from start of input, not start of line
|
||||
unquoted string // the unquoted version of value, if it was a quoted string
|
||||
}
|
||||
|
||||
func (t *token) String() string {
|
||||
if t.err == nil {
|
||||
return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset)
|
||||
}
|
||||
return fmt.Sprintf("parse error: %v", t.err)
|
||||
}
|
||||
|
||||
type textParser struct {
|
||||
s string // remaining input
|
||||
done bool // whether the parsing is finished (success or error)
|
||||
backed bool // whether back() was called
|
||||
offset, line int
|
||||
cur token
|
||||
}
|
||||
|
||||
func newTextParser(s string) *textParser {
|
||||
p := new(textParser)
|
||||
p.s = s
|
||||
p.line = 1
|
||||
p.cur.line = 1
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||
p.cur.err = pe
|
||||
p.done = true
|
||||
return pe
|
||||
}
|
||||
|
||||
// Numbers and identifiers are matched by [-+._A-Za-z0-9]
|
||||
func isIdentOrNumberChar(c byte) bool {
|
||||
switch {
|
||||
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||
return true
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
}
|
||||
switch c {
|
||||
case '-', '+', '.', '_':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isWhitespace(c byte) bool {
|
||||
switch c {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isQuote(c byte) bool {
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *textParser) skipWhitespace() {
|
||||
i := 0
|
||||
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||
if p.s[i] == '#' {
|
||||
// comment; skip to end of line or input
|
||||
for i < len(p.s) && p.s[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
if i == len(p.s) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.s[i] == '\n' {
|
||||
p.line++
|
||||
}
|
||||
i++
|
||||
}
|
||||
p.offset += i
|
||||
p.s = p.s[i:len(p.s)]
|
||||
if len(p.s) == 0 {
|
||||
p.done = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textParser) advance() {
|
||||
// Skip whitespace
|
||||
p.skipWhitespace()
|
||||
if p.done {
|
||||
return
|
||||
}
|
||||
|
||||
// Start of non-whitespace
|
||||
p.cur.err = nil
|
||||
p.cur.offset, p.cur.line = p.offset, p.line
|
||||
p.cur.unquoted = ""
|
||||
switch p.s[0] {
|
||||
case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/':
|
||||
// Single symbol
|
||||
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||
case '"', '\'':
|
||||
// Quoted string
|
||||
i := 1
|
||||
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||
// skip escaped char
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||
p.errorf("unmatched quote")
|
||||
return
|
||||
}
|
||||
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||
if err != nil {
|
||||
p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err)
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||
p.cur.unquoted = unq
|
||||
default:
|
||||
i := 0
|
||||
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
p.errorf("unexpected byte %#x", p.s[0])
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||
}
|
||||
p.offset += len(p.cur.value)
|
||||
}
|
||||
|
||||
var (
|
||||
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||
errBadHex = errors.New("proto: bad hexadecimal")
|
||||
)
|
||||
|
||||
func unquoteC(s string, quote rune) (string, error) {
|
||||
// This is based on C++'s tokenizer.cc.
|
||||
// Despite its name, this is *not* parsing C syntax.
|
||||
// For instance, "\0" is an invalid quoted string.
|
||||
|
||||
// Avoid allocation in trivial cases.
|
||||
simple := true
|
||||
for _, r := range s {
|
||||
if r == '\\' || r == quote {
|
||||
simple = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if simple {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 3*len(s)/2)
|
||||
for len(s) > 0 {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
if r != '\\' {
|
||||
if r < utf8.RuneSelf {
|
||||
buf = append(buf, byte(r))
|
||||
} else {
|
||||
buf = append(buf, string(r)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ch, tail, err := unescape(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf = append(buf, ch...)
|
||||
s = tail
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func unescape(s string) (ch string, tail string, err error) {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
switch r {
|
||||
case 'a':
|
||||
return "\a", s, nil
|
||||
case 'b':
|
||||
return "\b", s, nil
|
||||
case 'f':
|
||||
return "\f", s, nil
|
||||
case 'n':
|
||||
return "\n", s, nil
|
||||
case 'r':
|
||||
return "\r", s, nil
|
||||
case 't':
|
||||
return "\t", s, nil
|
||||
case 'v':
|
||||
return "\v", s, nil
|
||||
case '?':
|
||||
return "?", s, nil // trigraph workaround
|
||||
case '\'', '"', '\\':
|
||||
return string(r), s, nil
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||
if len(s) < 2 {
|
||||
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||
}
|
||||
base := 8
|
||||
ss := s[:2]
|
||||
s = s[2:]
|
||||
if r == 'x' || r == 'X' {
|
||||
base = 16
|
||||
} else {
|
||||
ss = string(r) + ss
|
||||
}
|
||||
i, err := strconv.ParseUint(ss, base, 8)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string([]byte{byte(i)}), s, nil
|
||||
case 'u', 'U':
|
||||
n := 4
|
||||
if r == 'U' {
|
||||
n = 8
|
||||
}
|
||||
if len(s) < n {
|
||||
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||
}
|
||||
|
||||
bs := make([]byte, n/2)
|
||||
for i := 0; i < n; i += 2 {
|
||||
a, ok1 := unhex(s[i])
|
||||
b, ok2 := unhex(s[i+1])
|
||||
if !ok1 || !ok2 {
|
||||
return "", "", errBadHex
|
||||
}
|
||||
bs[i/2] = a<<4 | b
|
||||
}
|
||||
s = s[n:]
|
||||
return string(bs), s, nil
|
||||
}
|
||||
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||
}
|
||||
|
||||
// Adapted from src/pkg/strconv/quote.go.
|
||||
func unhex(b byte) (v byte, ok bool) {
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
return b - '0', true
|
||||
case 'a' <= b && b <= 'f':
|
||||
return b - 'a' + 10, true
|
||||
case 'A' <= b && b <= 'F':
|
||||
return b - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Back off the parser by one token. Can only be done between calls to next().
|
||||
// It makes the next advance() a no-op.
|
||||
func (p *textParser) back() { p.backed = true }
|
||||
|
||||
// Advances the parser and returns the new current token.
|
||||
func (p *textParser) next() *token {
|
||||
if p.backed || p.done {
|
||||
p.backed = false
|
||||
return &p.cur
|
||||
}
|
||||
p.advance()
|
||||
if p.done {
|
||||
p.cur.value = ""
|
||||
} else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) {
|
||||
// Look for multiple quoted strings separated by whitespace,
|
||||
// and concatenate them.
|
||||
cat := p.cur
|
||||
for {
|
||||
p.skipWhitespace()
|
||||
if p.done || !isQuote(p.s[0]) {
|
||||
break
|
||||
}
|
||||
p.advance()
|
||||
if p.cur.err != nil {
|
||||
return &p.cur
|
||||
}
|
||||
cat.value += " " + p.cur.value
|
||||
cat.unquoted += p.cur.unquoted
|
||||
}
|
||||
p.done = false // parser may have seen EOF, but we want to return cat
|
||||
p.cur = cat
|
||||
}
|
||||
return &p.cur
|
||||
}
|
||||
|
||||
func (p *textParser) consumeToken(s string) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != s {
|
||||
p.back()
|
||||
return p.errorf("expected %q, found %q", s, tok.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a RequiredNotSetError indicating which required field was not set.
|
||||
func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
if !isNil(sv.Field(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
props := sprops.Prop[i]
|
||||
if props.Required {
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)}
|
||||
}
|
||||
}
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.<unknown field name>", st)} // should not happen
|
||||
}
|
||||
|
||||
// Returns the index in the struct for the named field, as well as the parsed tag properties.
|
||||
func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) {
|
||||
i, ok := sprops.decoderOrigNames[name]
|
||||
if ok {
|
||||
return i, sprops.Prop[i], true
|
||||
}
|
||||
return -1, nil, false
|
||||
}
|
||||
|
||||
// Consume a ':' from the input stream (if the next token is a colon),
|
||||
// returning an error if a colon is needed but not present.
|
||||
func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ":" {
|
||||
// Colon is optional when the field is a group or message.
|
||||
needColon := true
|
||||
switch props.Wire {
|
||||
case "group":
|
||||
needColon = false
|
||||
case "bytes":
|
||||
// A "bytes" field is either a message, a string, or a repeated field;
|
||||
// those three become *T, *string and []T respectively, so we can check for
|
||||
// this field being a pointer to a non-string.
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
// *T or *string
|
||||
if typ.Elem().Kind() == reflect.String {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.Slice {
|
||||
// []T or []*T
|
||||
if typ.Elem().Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.String {
|
||||
// The proto3 exception is for a string field,
|
||||
// which requires a colon.
|
||||
break
|
||||
}
|
||||
needColon = false
|
||||
}
|
||||
if needColon {
|
||||
return p.errorf("expected ':', found %q", tok.value)
|
||||
}
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
reqCount := sprops.reqCount
|
||||
var reqFieldErr error
|
||||
fieldSet := make(map[string]bool)
|
||||
// A struct is a sequence of "name: value", terminated by one of
|
||||
// '>' or '}', or the end of the input. A name may also be
|
||||
// "[extension]" or "[type/url]".
|
||||
//
|
||||
// The whole struct can also be an expanded Any message, like:
|
||||
// [type/url] < ... struct contents ... >
|
||||
for {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == terminator {
|
||||
break
|
||||
}
|
||||
if tok.value == "[" {
|
||||
// Looks like an extension or an Any.
|
||||
//
|
||||
// TODO: Check whether we need to handle
|
||||
// namespace rooted names (e.g. ".something.Foo").
|
||||
extName, err := p.consumeExtName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s := strings.LastIndex(extName, "/"); s >= 0 {
|
||||
// If it contains a slash, it's an Any type URL.
|
||||
messageName := extName[s+1:]
|
||||
mt := MessageType(messageName)
|
||||
if mt == nil {
|
||||
return p.errorf("unrecognized message %q in google.protobuf.Any", messageName)
|
||||
}
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
// consume an optional colon
|
||||
if tok.value == ":" {
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
}
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
v := reflect.New(mt.Elem())
|
||||
if pe := p.readStruct(v.Elem(), terminator); pe != nil {
|
||||
return pe
|
||||
}
|
||||
b, err := Marshal(v.Interface().(Message))
|
||||
if err != nil {
|
||||
return p.errorf("failed to marshal message of type %q: %v", messageName, err)
|
||||
}
|
||||
sv.FieldByName("TypeUrl").SetString(extName)
|
||||
sv.FieldByName("Value").SetBytes(b)
|
||||
continue
|
||||
}
|
||||
|
||||
var desc *ExtensionDesc
|
||||
// This could be faster, but it's functional.
|
||||
// TODO: Do something smarter than a linear scan.
|
||||
for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) {
|
||||
if d.Name == extName {
|
||||
desc = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if desc == nil {
|
||||
return p.errorf("unrecognized extension %q", extName)
|
||||
}
|
||||
|
||||
props := &Properties{}
|
||||
props.Parse(desc.Tag)
|
||||
|
||||
typ := reflect.TypeOf(desc.ExtensionType)
|
||||
if err := p.checkForColon(props, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rep := desc.repeated()
|
||||
|
||||
// Read the extension structure, and set it in
|
||||
// the value we're constructing.
|
||||
var ext reflect.Value
|
||||
if !rep {
|
||||
ext = reflect.New(typ).Elem()
|
||||
} else {
|
||||
ext = reflect.New(typ.Elem()).Elem()
|
||||
}
|
||||
if err := p.readAny(ext, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
}
|
||||
ep := sv.Addr().Interface().(extendableProto)
|
||||
if !rep {
|
||||
SetExtension(ep, desc, ext.Interface())
|
||||
} else {
|
||||
old, err := GetExtension(ep, desc)
|
||||
var sl reflect.Value
|
||||
if err == nil {
|
||||
sl = reflect.ValueOf(old) // existing slice
|
||||
} else {
|
||||
sl = reflect.MakeSlice(typ, 0, 1)
|
||||
}
|
||||
sl = reflect.Append(sl, ext)
|
||||
SetExtension(ep, desc, sl.Interface())
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a normal, non-extension field.
|
||||
name := tok.value
|
||||
var dst reflect.Value
|
||||
fi, props, ok := structFieldByName(sprops, name)
|
||||
if ok {
|
||||
dst = sv.Field(fi)
|
||||
} else if oop, ok := sprops.OneofTypes[name]; ok {
|
||||
// It is a oneof.
|
||||
props = oop.Prop
|
||||
nv := reflect.New(oop.Type.Elem())
|
||||
dst = nv.Elem().Field(0)
|
||||
sv.Field(oop.Field).Set(nv)
|
||||
}
|
||||
if !dst.IsValid() {
|
||||
return p.errorf("unknown field name %q in %v", name, st)
|
||||
}
|
||||
|
||||
if dst.Kind() == reflect.Map {
|
||||
// Consume any colon.
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Construct the map if it doesn't already exist.
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
key := reflect.New(dst.Type().Key()).Elem()
|
||||
val := reflect.New(dst.Type().Elem()).Elem()
|
||||
|
||||
// The map entry should be this sequence of tokens:
|
||||
// < key : KEY value : VALUE >
|
||||
// Technically the "key" and "value" could come in any order,
|
||||
// but in practice they won't.
|
||||
|
||||
tok := p.next()
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
if err := p.consumeToken("key"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeToken(":"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeToken("value"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeToken(terminator); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst.SetMapIndex(key, val)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that it's not already set if it's not a repeated field.
|
||||
if !props.Repeated && fieldSet[name] {
|
||||
return p.errorf("non-repeated field %q was repeated", name)
|
||||
}
|
||||
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse into the field.
|
||||
fieldSet[name] = true
|
||||
if err := p.readAny(dst, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
} else if props.Required {
|
||||
reqCount--
|
||||
}
|
||||
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if reqCount > 0 {
|
||||
return p.missingRequiredFieldError(sv)
|
||||
}
|
||||
return reqFieldErr
|
||||
}
|
||||
|
||||
// consumeExtName consumes extension name or expanded Any type URL and the
|
||||
// following ']'. It returns the name or URL consumed.
|
||||
func (p *textParser) consumeExtName() (string, error) {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return "", tok.err
|
||||
}
|
||||
|
||||
// If extension name or type url is quoted, it's a single token.
|
||||
if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] {
|
||||
name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, p.consumeToken("]")
|
||||
}
|
||||
|
||||
// Consume everything up to "]"
|
||||
var parts []string
|
||||
for tok.value != "]" {
|
||||
parts = append(parts, tok.value)
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ""), nil
|
||||
}
|
||||
|
||||
// consumeOptionalSeparator consumes an optional semicolon or comma.
|
||||
// It is used in readStruct to provide backward compatibility.
|
||||
func (p *textParser) consumeOptionalSeparator() error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ";" && tok.value != "," {
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "" {
|
||||
return p.errorf("unexpected EOF")
|
||||
}
|
||||
|
||||
switch fv := v; fv.Kind() {
|
||||
case reflect.Slice:
|
||||
at := v.Type()
|
||||
if at.Elem().Kind() == reflect.Uint8 {
|
||||
// Special case for []byte
|
||||
if tok.value[0] != '"' && tok.value[0] != '\'' {
|
||||
// Deliberately written out here, as the error after
|
||||
// this switch statement would write "invalid []byte: ...",
|
||||
// which is not as user-friendly.
|
||||
return p.errorf("invalid string: %v", tok.value)
|
||||
}
|
||||
bytes := []byte(tok.unquoted)
|
||||
fv.Set(reflect.ValueOf(bytes))
|
||||
return nil
|
||||
}
|
||||
// Repeated field.
|
||||
if tok.value == "[" {
|
||||
// Repeated field with list notation, like [1,2,3].
|
||||
for {
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
err := p.readAny(fv.Index(fv.Len()-1), props)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "]" {
|
||||
break
|
||||
}
|
||||
if tok.value != "," {
|
||||
return p.errorf("Expected ']' or ',' found %q", tok.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// One value of the repeated field.
|
||||
p.back()
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
return p.readAny(fv.Index(fv.Len()-1), props)
|
||||
case reflect.Bool:
|
||||
// Either "true", "false", 1 or 0.
|
||||
switch tok.value {
|
||||
case "true", "1":
|
||||
fv.SetBool(true)
|
||||
return nil
|
||||
case "false", "0":
|
||||
fv.SetBool(false)
|
||||
return nil
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v := tok.value
|
||||
// Ignore 'f' for compatibility with output generated by C++, but don't
|
||||
// remove 'f' when the value is "-inf" or "inf".
|
||||
if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" {
|
||||
v = v[:len(v)-1]
|
||||
}
|
||||
if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil {
|
||||
fv.SetFloat(f)
|
||||
return nil
|
||||
}
|
||||
case reflect.Int32:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(props.Enum) == 0 {
|
||||
break
|
||||
}
|
||||
m, ok := enumValueMaps[props.Enum]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
x, ok := m[tok.value]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fv.SetInt(int64(x))
|
||||
return nil
|
||||
case reflect.Int64:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// A basic field (indirected through pointer), or a repeated message/group
|
||||
p.back()
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
return p.readAny(fv.Elem(), props)
|
||||
case reflect.String:
|
||||
if tok.value[0] == '"' || tok.value[0] == '\'' {
|
||||
fv.SetString(tok.unquoted)
|
||||
return nil
|
||||
}
|
||||
case reflect.Struct:
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "{":
|
||||
terminator = "}"
|
||||
case "<":
|
||||
terminator = ">"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
// TODO: Handle nested messages which implement encoding.TextUnmarshaler.
|
||||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(uint64(x))
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||
fv.SetUint(x)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return p.errorf("invalid %v: %v", v.Type(), tok.value)
|
||||
}
|
||||
|
||||
// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb
|
||||
// before starting to unmarshal, so any existing data in pb is always removed.
|
||||
// If a required field is not set and no other error occurs,
|
||||
// UnmarshalText returns *RequiredNotSetError.
|
||||
func UnmarshalText(s string, pb Message) error {
|
||||
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||
err := um.UnmarshalText([]byte(s))
|
||||
return err
|
||||
}
|
||||
pb.Reset()
|
||||
v := reflect.ValueOf(pb)
|
||||
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||
return pe
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+557
@@ -0,0 +1,557 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
. "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
type UnmarshalTextTest struct {
|
||||
in string
|
||||
err string // if "", no error expected
|
||||
out *MyMessage
|
||||
}
|
||||
|
||||
func buildExtStructTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
SetExtension(msg, E_Ext_More, &Ext{
|
||||
Data: String("Hello, world!"),
|
||||
})
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
func buildExtDataTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
SetExtension(msg, E_Ext_Text, String("Hello, world!"))
|
||||
SetExtension(msg, E_Ext_Number, Int32(1729))
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
func buildExtRepStringTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
var unMarshalTextTests = []UnmarshalTextTest{
|
||||
// Basic
|
||||
{
|
||||
in: " count:42\n name:\"Dave\" ",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("Dave"),
|
||||
},
|
||||
},
|
||||
|
||||
// Empty quoted string
|
||||
{
|
||||
in: `count:42 name:""`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(""),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenation with double quotes
|
||||
{
|
||||
in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenation with single quotes
|
||||
{
|
||||
in: "count:42 name: 'My name is '\n'elsewhere'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenations with mixed quotes
|
||||
{
|
||||
in: "count:42 name: 'My name is '\n\"elsewhere\"",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
{
|
||||
in: "count:42 name: \"My name is \"\n'elsewhere'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with escaped apostrophe
|
||||
{
|
||||
in: `count:42 name: "HOLIDAY - New Year\'s Day"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("HOLIDAY - New Year's Day"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with single quote
|
||||
{
|
||||
in: `count:42 name: 'Roger "The Ramster" Ramjet'`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(`Roger "The Ramster" Ramjet`),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with all the accepted special characters from the C++ test
|
||||
{
|
||||
in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with quoted backslash
|
||||
{
|
||||
in: `count:42 name: "\\'xyz"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(`\'xyz`),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with UTF-8 bytes.
|
||||
{
|
||||
in: "count:42 name: '\303\277\302\201\xAB'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("\303\277\302\201\xAB"),
|
||||
},
|
||||
},
|
||||
|
||||
// Bad quoted string
|
||||
{
|
||||
in: `inner: < host: "\0" >` + "\n",
|
||||
err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`,
|
||||
},
|
||||
|
||||
// Number too large for int64
|
||||
{
|
||||
in: "count: 1 others { key: 123456789012345678901 }",
|
||||
err: "line 1.23: invalid int64: 123456789012345678901",
|
||||
},
|
||||
|
||||
// Number too large for int32
|
||||
{
|
||||
in: "count: 1234567890123",
|
||||
err: "line 1.7: invalid int32: 1234567890123",
|
||||
},
|
||||
|
||||
// Number in hexadecimal
|
||||
{
|
||||
in: "count: 0x2beef",
|
||||
out: &MyMessage{
|
||||
Count: Int32(0x2beef),
|
||||
},
|
||||
},
|
||||
|
||||
// Number in octal
|
||||
{
|
||||
in: "count: 024601",
|
||||
out: &MyMessage{
|
||||
Count: Int32(024601),
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point number with "f" suffix
|
||||
{
|
||||
in: "count: 4 others:< weight: 17.0f >",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Others: []*OtherMessage{
|
||||
{
|
||||
Weight: Float32(17),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point positive infinity
|
||||
{
|
||||
in: "count: 4 bigfloat: inf",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Bigfloat: Float64(math.Inf(1)),
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point negative infinity
|
||||
{
|
||||
in: "count: 4 bigfloat: -inf",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Bigfloat: Float64(math.Inf(-1)),
|
||||
},
|
||||
},
|
||||
|
||||
// Number too large for float32
|
||||
{
|
||||
in: "others:< weight: 12345678901234567890123456789012345678901234567890 >",
|
||||
err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890",
|
||||
},
|
||||
|
||||
// Number posing as a quoted string
|
||||
{
|
||||
in: `inner: < host: 12 >` + "\n",
|
||||
err: `line 1.15: invalid string: 12`,
|
||||
},
|
||||
|
||||
// Quoted string posing as int32
|
||||
{
|
||||
in: `count: "12"`,
|
||||
err: `line 1.7: invalid int32: "12"`,
|
||||
},
|
||||
|
||||
// Quoted string posing a float32
|
||||
{
|
||||
in: `others:< weight: "17.4" >`,
|
||||
err: `line 1.17: invalid float32: "17.4"`,
|
||||
},
|
||||
|
||||
// Enum
|
||||
{
|
||||
in: `count:42 bikeshed: BLUE`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Bikeshed: MyMessage_BLUE.Enum(),
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated field
|
||||
{
|
||||
in: `count:42 pet: "horsey" pet:"bunny"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Pet: []string{"horsey", "bunny"},
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated field with list notation
|
||||
{
|
||||
in: `count:42 pet: ["horsey", "bunny"]`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Pet: []string{"horsey", "bunny"},
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated message with/without colon and <>/{}
|
||||
{
|
||||
in: `count:42 others:{} others{} others:<> others:{}`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Others: []*OtherMessage{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Missing colon for inner message
|
||||
{
|
||||
in: `count:42 inner < host: "cauchy.syd" >`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("cauchy.syd"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Missing colon for string field
|
||||
{
|
||||
in: `name "Dave"`,
|
||||
err: `line 1.5: expected ':', found "\"Dave\""`,
|
||||
},
|
||||
|
||||
// Missing colon for int32 field
|
||||
{
|
||||
in: `count 42`,
|
||||
err: `line 1.6: expected ':', found "42"`,
|
||||
},
|
||||
|
||||
// Missing required field
|
||||
{
|
||||
in: `name: "Pawel"`,
|
||||
err: `proto: required field "testdata.MyMessage.count" not set`,
|
||||
out: &MyMessage{
|
||||
Name: String("Pawel"),
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated non-repeated field
|
||||
{
|
||||
in: `name: "Rob" name: "Russ"`,
|
||||
err: `line 1.12: non-repeated field "name" was repeated`,
|
||||
},
|
||||
|
||||
// Group
|
||||
{
|
||||
in: `count: 17 SomeGroup { group_field: 12 }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(17),
|
||||
Somegroup: &MyMessage_SomeGroup{
|
||||
GroupField: Int32(12),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Semicolon between fields
|
||||
{
|
||||
in: `count:3;name:"Calvin"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(3),
|
||||
Name: String("Calvin"),
|
||||
},
|
||||
},
|
||||
// Comma between fields
|
||||
{
|
||||
in: `count:4,name:"Ezekiel"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Name: String("Ezekiel"),
|
||||
},
|
||||
},
|
||||
|
||||
// Extension
|
||||
buildExtStructTest(`count: 42 [testdata.Ext.more]:<data:"Hello, world!" >`),
|
||||
buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`),
|
||||
buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`),
|
||||
buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`),
|
||||
|
||||
// Big all-in-one
|
||||
{
|
||||
in: "count:42 # Meaning\n" +
|
||||
`name:"Dave" ` +
|
||||
`quote:"\"I didn't want to go.\"" ` +
|
||||
`pet:"bunny" ` +
|
||||
`pet:"kitty" ` +
|
||||
`pet:"horsey" ` +
|
||||
`inner:<` +
|
||||
` host:"footrest.syd" ` +
|
||||
` port:7001 ` +
|
||||
` connected:true ` +
|
||||
`> ` +
|
||||
`others:<` +
|
||||
` key:3735928559 ` +
|
||||
` value:"\x01A\a\f" ` +
|
||||
`> ` +
|
||||
`others:<` +
|
||||
" weight:58.9 # Atomic weight of Co\n" +
|
||||
` inner:<` +
|
||||
` host:"lesha.mtv" ` +
|
||||
` port:8002 ` +
|
||||
` >` +
|
||||
`>`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("Dave"),
|
||||
Quote: String(`"I didn't want to go."`),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &InnerMessage{
|
||||
Host: String("footrest.syd"),
|
||||
Port: Int32(7001),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
Others: []*OtherMessage{
|
||||
{
|
||||
Key: Int64(3735928559),
|
||||
Value: []byte{0x1, 'A', '\a', '\f'},
|
||||
},
|
||||
{
|
||||
Weight: Float32(58.9),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("lesha.mtv"),
|
||||
Port: Int32(8002),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalText(t *testing.T) {
|
||||
for i, test := range unMarshalTextTests {
|
||||
pb := new(MyMessage)
|
||||
err := UnmarshalText(test.in, pb)
|
||||
if test.err == "" {
|
||||
// We don't expect failure.
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: Unexpected error: %v", i, err)
|
||||
} else if !reflect.DeepEqual(pb, test.out) {
|
||||
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||
i, pb, test.out)
|
||||
}
|
||||
} else {
|
||||
// We do expect failure.
|
||||
if err == nil {
|
||||
t.Errorf("Test %d: Didn't get expected error: %v", i, test.err)
|
||||
} else if err.Error() != test.err {
|
||||
t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v",
|
||||
i, err.Error(), test.err)
|
||||
} else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) {
|
||||
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||
i, pb, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalTextCustomMessage(t *testing.T) {
|
||||
msg := &textMessage{}
|
||||
if err := UnmarshalText("custom", msg); err != nil {
|
||||
t.Errorf("Unexpected error from custom unmarshal: %v", err)
|
||||
}
|
||||
if UnmarshalText("not custom", msg) == nil {
|
||||
t.Errorf("Didn't get expected error from custom unmarshal")
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test; this caused a panic.
|
||||
func TestRepeatedEnum(t *testing.T) {
|
||||
pb := new(RepeatedEnum)
|
||||
if err := UnmarshalText("color: RED", pb); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exp := &RepeatedEnum{
|
||||
Color: []RepeatedEnum_Color{RepeatedEnum_RED},
|
||||
}
|
||||
if !Equal(pb, exp) {
|
||||
t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3TextParsing(t *testing.T) {
|
||||
m := new(proto3pb.Message)
|
||||
const in = `name: "Wallace" true_scotsman: true`
|
||||
want := &proto3pb.Message{
|
||||
Name: "Wallace",
|
||||
TrueScotsman: true,
|
||||
}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapParsing(t *testing.T) {
|
||||
m := new(MessageWithMap)
|
||||
const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` +
|
||||
`msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay
|
||||
`msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value"
|
||||
`byte_mapping:<key:true value:"so be it">`
|
||||
want := &MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
1: "Beatles",
|
||||
1234: "Feist",
|
||||
},
|
||||
MsgMapping: map[int64]*FloatingPoint{
|
||||
-4: {F: Float64(2.0)},
|
||||
-2: {F: Float64(4.0)},
|
||||
},
|
||||
ByteMapping: map[bool][]byte{
|
||||
true: []byte("so be it"),
|
||||
},
|
||||
}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneofParsing(t *testing.T) {
|
||||
const in = `name:"Shrek"`
|
||||
m := new(Communique)
|
||||
want := &Communique{Union: &Communique_Name{"Shrek"}}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
}
|
||||
|
||||
var benchInput string
|
||||
|
||||
func init() {
|
||||
benchInput = "count: 4\n"
|
||||
for i := 0; i < 1000; i++ {
|
||||
benchInput += "pet: \"fido\"\n"
|
||||
}
|
||||
|
||||
// Check it is valid input.
|
||||
pb := new(MyMessage)
|
||||
err := UnmarshalText(benchInput, pb)
|
||||
if err != nil {
|
||||
panic("Bad benchmark input: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalText(b *testing.B) {
|
||||
pb := new(MyMessage)
|
||||
for i := 0; i < b.N; i++ {
|
||||
UnmarshalText(benchInput, pb)
|
||||
}
|
||||
b.SetBytes(int64(len(benchInput)))
|
||||
}
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
// textMessage implements the methods that allow it to marshal and unmarshal
|
||||
// itself as text.
|
||||
type textMessage struct {
|
||||
}
|
||||
|
||||
func (*textMessage) MarshalText() ([]byte, error) {
|
||||
return []byte("custom"), nil
|
||||
}
|
||||
|
||||
func (*textMessage) UnmarshalText(bytes []byte) error {
|
||||
if string(bytes) != "custom" {
|
||||
return errors.New("expected 'custom'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*textMessage) Reset() {}
|
||||
func (*textMessage) String() string { return "" }
|
||||
func (*textMessage) ProtoMessage() {}
|
||||
|
||||
func newTestMessage() *pb.MyMessage {
|
||||
msg := &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
Quote: proto.String(`"I didn't want to go."`),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("footrest.syd"),
|
||||
Port: proto.Int32(7001),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(0xdeadbeef),
|
||||
Value: []byte{1, 65, 7, 12},
|
||||
},
|
||||
{
|
||||
Weight: proto.Float32(6.022),
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("lesha.mtv"),
|
||||
Port: proto.Int32(8002),
|
||||
},
|
||||
},
|
||||
},
|
||||
Bikeshed: pb.MyMessage_BLUE.Enum(),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(8),
|
||||
},
|
||||
// One normally wouldn't do this.
|
||||
// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
|
||||
XXX_unrecognized: []byte{13<<3 | 0, 4},
|
||||
}
|
||||
ext := &pb.Ext{
|
||||
Data: proto.String("Big gobs for big rats"),
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
greetings := []string{"adg", "easy", "cow"}
|
||||
if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
|
||||
b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
|
||||
proto.SetRawExtension(msg, 201, b)
|
||||
|
||||
// Extensions can be plain fields, too, so let's test that.
|
||||
b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
|
||||
proto.SetRawExtension(msg, 202, b)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
const text = `count: 42
|
||||
name: "Dave"
|
||||
quote: "\"I didn't want to go.\""
|
||||
pet: "bunny"
|
||||
pet: "kitty"
|
||||
pet: "horsey"
|
||||
inner: <
|
||||
host: "footrest.syd"
|
||||
port: 7001
|
||||
connected: true
|
||||
>
|
||||
others: <
|
||||
key: 3735928559
|
||||
value: "\001A\007\014"
|
||||
>
|
||||
others: <
|
||||
weight: 6.022
|
||||
inner: <
|
||||
host: "lesha.mtv"
|
||||
port: 8002
|
||||
>
|
||||
>
|
||||
bikeshed: BLUE
|
||||
SomeGroup {
|
||||
group_field: 8
|
||||
}
|
||||
/* 2 unknown bytes */
|
||||
13: 4
|
||||
[testdata.Ext.more]: <
|
||||
data: "Big gobs for big rats"
|
||||
>
|
||||
[testdata.greeting]: "adg"
|
||||
[testdata.greeting]: "easy"
|
||||
[testdata.greeting]: "cow"
|
||||
/* 13 unknown bytes */
|
||||
201: "\t3G skiing"
|
||||
/* 3 unknown bytes */
|
||||
202: 19
|
||||
`
|
||||
|
||||
func TestMarshalText(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, newTestMessage()); err != nil {
|
||||
t.Fatalf("proto.MarshalText: %v", err)
|
||||
}
|
||||
s := buf.String()
|
||||
if s != text {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTextCustomMessage(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, &textMessage{}); err != nil {
|
||||
t.Fatalf("proto.MarshalText: %v", err)
|
||||
}
|
||||
s := buf.String()
|
||||
if s != "custom" {
|
||||
t.Errorf("Got %q, expected %q", s, "custom")
|
||||
}
|
||||
}
|
||||
func TestMarshalTextNil(t *testing.T) {
|
||||
want := "<nil>"
|
||||
tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
|
||||
for i, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, test); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := buf.String(); got != want {
|
||||
t.Errorf("%d: got %q want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTextUnknownEnum(t *testing.T) {
|
||||
// The Color enum only specifies values 0-2.
|
||||
m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
|
||||
got := m.String()
|
||||
const want = `bikeshed:3 `
|
||||
if got != want {
|
||||
t.Errorf("\n got %q\nwant %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextOneof(t *testing.T) {
|
||||
tests := []struct {
|
||||
m proto.Message
|
||||
want string
|
||||
}{
|
||||
// zero message
|
||||
{&pb.Communique{}, ``},
|
||||
// scalar field
|
||||
{&pb.Communique{Union: &pb.Communique_Number{4}}, `number:4`},
|
||||
// message field
|
||||
{&pb.Communique{Union: &pb.Communique_Msg{
|
||||
&pb.Strings{StringField: proto.String("why hello!")},
|
||||
}}, `msg:<string_field:"why hello!" >`},
|
||||
// bad oneof (should not panic)
|
||||
{&pb.Communique{Union: &pb.Communique_Msg{nil}}, `msg:/* nil */`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := strings.TrimSpace(test.m.String())
|
||||
if got != test.want {
|
||||
t.Errorf("\n got %s\nwant %s", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalTextBuffered(b *testing.B) {
|
||||
buf := new(bytes.Buffer)
|
||||
m := newTestMessage()
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
proto.MarshalText(buf, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalTextUnbuffered(b *testing.B) {
|
||||
w := ioutil.Discard
|
||||
m := newTestMessage()
|
||||
for i := 0; i < b.N; i++ {
|
||||
proto.MarshalText(w, m)
|
||||
}
|
||||
}
|
||||
|
||||
func compact(src string) string {
|
||||
// s/[ \n]+/ /g; s/ $//;
|
||||
dst := make([]byte, len(src))
|
||||
space, comment := false, false
|
||||
j := 0
|
||||
for i := 0; i < len(src); i++ {
|
||||
if strings.HasPrefix(src[i:], "/*") {
|
||||
comment = true
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if comment && strings.HasPrefix(src[i:], "*/") {
|
||||
comment = false
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if comment {
|
||||
continue
|
||||
}
|
||||
c := src[i]
|
||||
if c == ' ' || c == '\n' {
|
||||
space = true
|
||||
continue
|
||||
}
|
||||
if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
|
||||
space = false
|
||||
}
|
||||
if c == '{' {
|
||||
space = false
|
||||
}
|
||||
if space {
|
||||
dst[j] = ' '
|
||||
j++
|
||||
space = false
|
||||
}
|
||||
dst[j] = c
|
||||
j++
|
||||
}
|
||||
if space {
|
||||
dst[j] = ' '
|
||||
j++
|
||||
}
|
||||
return string(dst[0:j])
|
||||
}
|
||||
|
||||
var compactText = compact(text)
|
||||
|
||||
func TestCompactText(t *testing.T) {
|
||||
s := proto.CompactTextString(newTestMessage())
|
||||
if s != compactText {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEscaping(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in *pb.Strings
|
||||
out string
|
||||
}{
|
||||
{
|
||||
// Test data from C++ test (TextFormatTest.StringEscape).
|
||||
// Single divergence: we don't escape apostrophes.
|
||||
&pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
|
||||
"string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
|
||||
},
|
||||
{
|
||||
// Test data from the same C++ test.
|
||||
&pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
|
||||
"string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
|
||||
},
|
||||
{
|
||||
// Some UTF-8.
|
||||
&pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
|
||||
`string_field: "\000\001\377\201"` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
var buf bytes.Buffer
|
||||
if err := proto.MarshalText(&buf, tc.in); err != nil {
|
||||
t.Errorf("proto.MarsalText: %v", err)
|
||||
continue
|
||||
}
|
||||
s := buf.String()
|
||||
if s != tc.out {
|
||||
t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check round-trip.
|
||||
pb := new(pb.Strings)
|
||||
if err := proto.UnmarshalText(s, pb); err != nil {
|
||||
t.Errorf("#%d: UnmarshalText: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !proto.Equal(pb, tc.in) {
|
||||
t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A limitedWriter accepts some output before it fails.
|
||||
// This is a proxy for something like a nearly-full or imminently-failing disk,
|
||||
// or a network connection that is about to die.
|
||||
type limitedWriter struct {
|
||||
b bytes.Buffer
|
||||
limit int
|
||||
}
|
||||
|
||||
var outOfSpace = errors.New("proto: insufficient space")
|
||||
|
||||
func (w *limitedWriter) Write(p []byte) (n int, err error) {
|
||||
var avail = w.limit - w.b.Len()
|
||||
if avail <= 0 {
|
||||
return 0, outOfSpace
|
||||
}
|
||||
if len(p) <= avail {
|
||||
return w.b.Write(p)
|
||||
}
|
||||
n, _ = w.b.Write(p[:avail])
|
||||
return n, outOfSpace
|
||||
}
|
||||
|
||||
func TestMarshalTextFailing(t *testing.T) {
|
||||
// Try lots of different sizes to exercise more error code-paths.
|
||||
for lim := 0; lim < len(text); lim++ {
|
||||
buf := new(limitedWriter)
|
||||
buf.limit = lim
|
||||
err := proto.MarshalText(buf, newTestMessage())
|
||||
// We expect a certain error, but also some partial results in the buffer.
|
||||
if err != outOfSpace {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
|
||||
}
|
||||
s := buf.b.String()
|
||||
x := text[:buf.limit]
|
||||
if s != x {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloats(t *testing.T) {
|
||||
tests := []struct {
|
||||
f float64
|
||||
want string
|
||||
}{
|
||||
{0, "0"},
|
||||
{4.7, "4.7"},
|
||||
{math.Inf(1), "inf"},
|
||||
{math.Inf(-1), "-inf"},
|
||||
{math.NaN(), "nan"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
msg := &pb.FloatingPoint{F: &test.f}
|
||||
got := strings.TrimSpace(msg.String())
|
||||
want := `f:` + test.want
|
||||
if got != want {
|
||||
t.Errorf("f=%f: got %q, want %q", test.f, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeatedNilText(t *testing.T) {
|
||||
m := &pb.MessageList{
|
||||
Message: []*pb.MessageList_Message{
|
||||
nil,
|
||||
&pb.MessageList_Message{
|
||||
Name: proto.String("Horse"),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
want := `Message <nil>
|
||||
Message {
|
||||
name: "Horse"
|
||||
}
|
||||
Message <nil>
|
||||
`
|
||||
if s := proto.MarshalTextString(m); s != want {
|
||||
t.Errorf(" got: %s\nwant: %s", s, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3Text(t *testing.T) {
|
||||
tests := []struct {
|
||||
m proto.Message
|
||||
want string
|
||||
}{
|
||||
// zero message
|
||||
{&proto3pb.Message{}, ``},
|
||||
// zero message except for an empty byte slice
|
||||
{&proto3pb.Message{Data: []byte{}}, ``},
|
||||
// trivial case
|
||||
{&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
|
||||
// empty map
|
||||
{&pb.MessageWithMap{}, ``},
|
||||
// non-empty map; map format is the same as a repeated struct,
|
||||
// and they are sorted by key (numerically for numeric keys).
|
||||
{
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{
|
||||
-1: "Negatory",
|
||||
7: "Lucky",
|
||||
1234: "Feist",
|
||||
6345789: "Otis",
|
||||
}},
|
||||
`name_mapping:<key:-1 value:"Negatory" > ` +
|
||||
`name_mapping:<key:7 value:"Lucky" > ` +
|
||||
`name_mapping:<key:1234 value:"Feist" > ` +
|
||||
`name_mapping:<key:6345789 value:"Otis" >`,
|
||||
},
|
||||
// map with nil value; not well-defined, but we shouldn't crash
|
||||
{
|
||||
&pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}},
|
||||
`msg_mapping:<key:7 >`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := strings.TrimSpace(test.m.String())
|
||||
if got != test.want {
|
||||
t.Errorf("\n got %s\nwant %s", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: github.com/golang/protobuf/ptypes/any/any.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package any is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
github.com/golang/protobuf/ptypes/any/any.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Any
|
||||
*/
|
||||
package any
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
const _ = proto.ProtoPackageIsVersion1
|
||||
|
||||
// `Any` contains an arbitrary serialized message along with a URL
|
||||
// that describes the type of the serialized message.
|
||||
//
|
||||
//
|
||||
// JSON
|
||||
// ====
|
||||
// The JSON representation of an `Any` value uses the regular
|
||||
// representation of the deserialized, embedded message, with an
|
||||
// additional field `@type` which contains the type URL. Example:
|
||||
//
|
||||
// package google.profile;
|
||||
// message Person {
|
||||
// string first_name = 1;
|
||||
// string last_name = 2;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.profile.Person",
|
||||
// "firstName": <string>,
|
||||
// "lastName": <string>
|
||||
// }
|
||||
//
|
||||
// If the embedded message type is well-known and has a custom JSON
|
||||
// representation, that representation will be embedded adding a field
|
||||
// `value` which holds the custom JSON in addition to the `@type`
|
||||
// field. Example (for message [google.protobuf.Duration][]):
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
// "value": "1.212s"
|
||||
// }
|
||||
//
|
||||
type Any struct {
|
||||
// A URL/resource name whose content describes the type of the
|
||||
// serialized message.
|
||||
//
|
||||
// For URLs which use the schema `http`, `https`, or no schema, the
|
||||
// following restrictions and interpretations apply:
|
||||
//
|
||||
// * If no schema is provided, `https` is assumed.
|
||||
// * The last segment of the URL's path must represent the fully
|
||||
// qualified name of the type (as in `path/google.protobuf.Duration`).
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Schemas other than `http`, `https` (or the empty schema) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
|
||||
// Must be valid serialized data of the above specified type.
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Any) Reset() { *m = Any{} }
|
||||
func (m *Any) String() string { return proto.CompactTextString(m) }
|
||||
func (*Any) ProtoMessage() {}
|
||||
func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
func (*Any) XXX_WellKnownType() string { return "Any" }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Any)(nil), "google.protobuf.Any")
|
||||
}
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 184 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4f, 0xcf, 0x2c, 0xc9,
|
||||
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc, 0x4b, 0xd7, 0x2f, 0x28,
|
||||
0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0x28, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x4f, 0xcc,
|
||||
0xab, 0x04, 0x61, 0x3d, 0xb0, 0xb8, 0x10, 0x7f, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x1e, 0x4c,
|
||||
0x95, 0x92, 0x19, 0x17, 0xb3, 0x63, 0x5e, 0xa5, 0x90, 0x24, 0x17, 0x07, 0x48, 0x79, 0x7c, 0x69,
|
||||
0x51, 0x8e, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x3b, 0x88, 0x1f, 0x5a, 0x94, 0x23, 0x24,
|
||||
0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0x04, 0x14, 0xe7, 0x09, 0x82, 0x70, 0x9c,
|
||||
0x8a, 0xb8, 0x84, 0x81, 0x96, 0xea, 0xa1, 0x19, 0xe7, 0xc4, 0x01, 0x34, 0x2c, 0x00, 0xc4, 0x09,
|
||||
0x60, 0x8c, 0x52, 0x25, 0xca, 0x71, 0x0b, 0x18, 0x19, 0x17, 0x31, 0x31, 0xbb, 0x07, 0x38, 0xad,
|
||||
0x62, 0x92, 0x73, 0x87, 0x98, 0x16, 0x00, 0x55, 0xa5, 0x17, 0x9e, 0x9a, 0x93, 0xe3, 0x9d, 0x97,
|
||||
0x5f, 0x9e, 0x17, 0x02, 0x52, 0x9d, 0xc4, 0x06, 0xd6, 0x6e, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff,
|
||||
0xc6, 0x4d, 0x03, 0x23, 0xf6, 0x00, 0x00, 0x00,
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package google.protobuf;
|
||||
option go_package = "github.com/golang/protobuf/ptypes/any";
|
||||
|
||||
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||
option java_package = "com.google.protobuf";
|
||||
option java_outer_classname = "AnyProto";
|
||||
option java_multiple_files = true;
|
||||
option java_generate_equals_and_hash = true;
|
||||
option objc_class_prefix = "GPB";
|
||||
|
||||
// `Any` contains an arbitrary serialized message along with a URL
|
||||
// that describes the type of the serialized message.
|
||||
//
|
||||
//
|
||||
// JSON
|
||||
// ====
|
||||
// The JSON representation of an `Any` value uses the regular
|
||||
// representation of the deserialized, embedded message, with an
|
||||
// additional field `@type` which contains the type URL. Example:
|
||||
//
|
||||
// package google.profile;
|
||||
// message Person {
|
||||
// string first_name = 1;
|
||||
// string last_name = 2;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.profile.Person",
|
||||
// "firstName": <string>,
|
||||
// "lastName": <string>
|
||||
// }
|
||||
//
|
||||
// If the embedded message type is well-known and has a custom JSON
|
||||
// representation, that representation will be embedded adding a field
|
||||
// `value` which holds the custom JSON in addition to the `@type`
|
||||
// field. Example (for message [google.protobuf.Duration][]):
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
// "value": "1.212s"
|
||||
// }
|
||||
//
|
||||
message Any {
|
||||
// A URL/resource name whose content describes the type of the
|
||||
// serialized message.
|
||||
//
|
||||
// For URLs which use the schema `http`, `https`, or no schema, the
|
||||
// following restrictions and interpretations apply:
|
||||
//
|
||||
// * If no schema is provided, `https` is assumed.
|
||||
// * The last segment of the URL's path must represent the fully
|
||||
// qualified name of the type (as in `path/google.protobuf.Duration`).
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Schemas other than `http`, `https` (or the empty schema) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
string type_url = 1;
|
||||
|
||||
// Must be valid serialized data of the above specified type.
|
||||
bytes value = 2;
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
# 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
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
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.
|
||||
|
||||
-185
@@ -1,185 +0,0 @@
|
||||
#sqlx
|
||||
|
||||
[](https://drone.io/github.com/jmoiron/sqlx/latest) [](https://godoc.org/github.com/jmoiron/sqlx) [](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
|
||||
|
||||
* 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.
|
||||
|
||||
More importantly, [golang bug #13905](https://github.com/golang/go/issues/13905)
|
||||
makes `types.JSONText` and `types.GzippedText` _potentially unsafe_, **especially**
|
||||
when used with common auto-scan sqlx idioms like `Select` and `Get`.
|
||||
|
||||
### 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 (
|
||||
_ "github.com/lib/pq"
|
||||
"database/sql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"log"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
)
|
||||
|
||||
// Bindvar types supported by Rebind, BindMap and BindStruct.
|
||||
const (
|
||||
UNKNOWN = iota
|
||||
QUESTION
|
||||
DOLLAR
|
||||
NAMED
|
||||
)
|
||||
|
||||
// BindType returns the bindtype for a given database given a drivername.
|
||||
func BindType(driverName string) int {
|
||||
switch driverName {
|
||||
case "postgres", "pgx":
|
||||
return DOLLAR
|
||||
case "mysql":
|
||||
return QUESTION
|
||||
case "sqlite3":
|
||||
return QUESTION
|
||||
case "oci8", "ora", "goracle":
|
||||
return NAMED
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
qb := []byte(query)
|
||||
// Add space enough for 10 params before we have to allocate
|
||||
rqb := make([]byte, 0, len(qb)+10)
|
||||
j := 1
|
||||
for _, b := range qb {
|
||||
if b == '?' {
|
||||
switch bindType {
|
||||
case DOLLAR:
|
||||
rqb = append(rqb, '$')
|
||||
case NAMED:
|
||||
rqb = append(rqb, ':', 'a', 'r', 'g')
|
||||
}
|
||||
for _, b := range strconv.Itoa(j) {
|
||||
rqb = append(rqb, byte(b))
|
||||
}
|
||||
j++
|
||||
} else {
|
||||
rqb = append(rqb, b)
|
||||
}
|
||||
}
|
||||
return string(rqb)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
v := reflect.ValueOf(arg)
|
||||
t := reflectx.Deref(v.Type())
|
||||
|
||||
if t.Kind() == reflect.Slice {
|
||||
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)
|
||||
|
||||
var arg, offset int
|
||||
var buf bytes.Buffer
|
||||
|
||||
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.WriteString(query[:offset+i+1])
|
||||
|
||||
newArgs = append(newArgs, argMeta.v.Index(0).Interface())
|
||||
|
||||
for si := 1; si < argMeta.length; si++ {
|
||||
buf.WriteString(", ?")
|
||||
newArgs = append(newArgs, argMeta.v.Index(si).Interface())
|
||||
}
|
||||
|
||||
// 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.WriteString(query)
|
||||
|
||||
if arg < len(meta) {
|
||||
return "", nil, errors.New("number of bindVars less than number arguments")
|
||||
}
|
||||
|
||||
return buf.String(), newArgs, nil
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// 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
|
||||
-336
@@ -1,336 +0,0 @@
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
|
||||
res, err := n.Exec(arg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Queryx using this NamedStmt
|
||||
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.
|
||||
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
|
||||
return n.QueryRow(arg)
|
||||
}
|
||||
|
||||
// Select using this NamedStmt
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
||||
fields := m.TraversalsByName(v.Type(), names)
|
||||
for i, t := range fields {
|
||||
if len(t) == 0 {
|
||||
return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
|
||||
}
|
||||
val := reflectx.FieldByIndexesReadOnly(v, t)
|
||||
arglist = append(arglist, val.Interface())
|
||||
}
|
||||
|
||||
return arglist, nil
|
||||
}
|
||||
|
||||
// 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{}
|
||||
// if we're in a name, and this is an allowed character, continue
|
||||
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(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++
|
||||
}
|
||||
// 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...)
|
||||
}
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompileQuery(t *testing.T) {
|
||||
table := []struct {
|
||||
Q, R, D, N string
|
||||
V []string
|
||||
}{
|
||||
// basic test for named parameters, invalid char ',' terminating
|
||||
{
|
||||
Q: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
|
||||
R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
|
||||
D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
|
||||
N: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
|
||||
V: []string{"name", "age", "first", "last"},
|
||||
},
|
||||
// This query tests a named parameter ending the string as well as numbers
|
||||
{
|
||||
Q: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
|
||||
R: `SELECT * FROM a WHERE first_name=? AND last_name=?`,
|
||||
D: `SELECT * FROM a WHERE first_name=$1 AND last_name=$2`,
|
||||
N: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
|
||||
V: []string{"name1", "name2"},
|
||||
},
|
||||
{
|
||||
Q: `SELECT "::foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
|
||||
R: `SELECT ":foo" FROM a WHERE first_name=? AND last_name=?`,
|
||||
D: `SELECT ":foo" FROM a WHERE first_name=$1 AND last_name=$2`,
|
||||
N: `SELECT ":foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
|
||||
V: []string{"name1", "name2"},
|
||||
},
|
||||
{
|
||||
Q: `SELECT 'a::b::c' || first_name, '::::ABC::_::' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
|
||||
R: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=? AND last_name=?`,
|
||||
D: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=$1 AND last_name=$2`,
|
||||
N: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
|
||||
V: []string{"first_name", "last_name"},
|
||||
},
|
||||
/* This unicode awareness test sadly fails, because of our byte-wise worldview.
|
||||
* We could certainly iterate by Rune instead, though it's a great deal slower,
|
||||
* it's probably the RightWay(tm)
|
||||
{
|
||||
Q: `INSERT INTO foo (a,b,c,d) VALUES (:あ, :b, :キコ, :名前)`,
|
||||
R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
|
||||
D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
|
||||
N: []string{"name", "age", "first", "last"},
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
qr, names, err := compileNamedQuery([]byte(test.Q), QUESTION)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if qr != test.R {
|
||||
t.Errorf("expected %s, got %s", test.R, qr)
|
||||
}
|
||||
if len(names) != len(test.V) {
|
||||
t.Errorf("expected %#v, got %#v", test.V, names)
|
||||
} else {
|
||||
for i, name := range names {
|
||||
if name != test.V[i] {
|
||||
t.Errorf("expected %dth name to be %s, got %s", i+1, test.V[i], name)
|
||||
}
|
||||
}
|
||||
}
|
||||
qd, _, _ := compileNamedQuery([]byte(test.Q), DOLLAR)
|
||||
if qd != test.D {
|
||||
t.Errorf("\nexpected: `%s`\ngot: `%s`", test.D, qd)
|
||||
}
|
||||
|
||||
qq, _, _ := compileNamedQuery([]byte(test.Q), NAMED)
|
||||
if qq != test.N {
|
||||
t.Errorf("\nexpected: `%s`\ngot: `%s`\n(len: %d vs %d)", test.N, qq, len(test.N), len(qq))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (t Test) Error(err error, msg ...interface{}) {
|
||||
if err != nil {
|
||||
if len(msg) == 0 {
|
||||
t.t.Error(err)
|
||||
} else {
|
||||
t.t.Error(msg...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t Test) Errorf(err error, format string, args ...interface{}) {
|
||||
if err != nil {
|
||||
t.t.Errorf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedQueries(t *testing.T) {
|
||||
RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
|
||||
loadDefaultFixture(db, t)
|
||||
test := Test{t}
|
||||
var ns *NamedStmt
|
||||
var err error
|
||||
|
||||
// Check that invalid preparations fail
|
||||
ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name")
|
||||
if err == nil {
|
||||
t.Error("Expected an error with invalid prepared statement.")
|
||||
}
|
||||
|
||||
ns, err = db.PrepareNamed("invalid sql")
|
||||
if err == nil {
|
||||
t.Error("Expected an error with invalid prepared statement.")
|
||||
}
|
||||
|
||||
// Check closing works as anticipated
|
||||
ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first_name")
|
||||
test.Error(err)
|
||||
err = ns.Close()
|
||||
test.Error(err)
|
||||
|
||||
ns, err = db.PrepareNamed(`
|
||||
SELECT first_name, last_name, email
|
||||
FROM person WHERE first_name=:first_name AND email=:email`)
|
||||
test.Error(err)
|
||||
|
||||
// test Queryx w/ uses Query
|
||||
p := Person{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"}
|
||||
|
||||
rows, err := ns.Queryx(p)
|
||||
test.Error(err)
|
||||
for rows.Next() {
|
||||
var p2 Person
|
||||
rows.StructScan(&p2)
|
||||
if p.FirstName != p2.FirstName {
|
||||
t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName)
|
||||
}
|
||||
if p.LastName != p2.LastName {
|
||||
t.Errorf("got %s, expected %s", p.LastName, p2.LastName)
|
||||
}
|
||||
if p.Email != p2.Email {
|
||||
t.Errorf("got %s, expected %s", p.Email, p2.Email)
|
||||
}
|
||||
}
|
||||
|
||||
// test Select
|
||||
people := make([]Person, 0, 5)
|
||||
err = ns.Select(&people, p)
|
||||
test.Error(err)
|
||||
|
||||
if len(people) != 1 {
|
||||
t.Errorf("got %d results, expected %d", len(people), 1)
|
||||
}
|
||||
if p.FirstName != people[0].FirstName {
|
||||
t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName)
|
||||
}
|
||||
if p.LastName != people[0].LastName {
|
||||
t.Errorf("got %s, expected %s", p.LastName, people[0].LastName)
|
||||
}
|
||||
if p.Email != people[0].Email {
|
||||
t.Errorf("got %s, expected %s", p.Email, people[0].Email)
|
||||
}
|
||||
|
||||
// test Exec
|
||||
ns, err = db.PrepareNamed(`
|
||||
INSERT INTO person (first_name, last_name, email)
|
||||
VALUES (:first_name, :last_name, :email)`)
|
||||
test.Error(err)
|
||||
|
||||
js := Person{
|
||||
FirstName: "Julien",
|
||||
LastName: "Savea",
|
||||
Email: "jsavea@ab.co.nz",
|
||||
}
|
||||
_, err = ns.Exec(js)
|
||||
test.Error(err)
|
||||
|
||||
// Make sure we can pull him out again
|
||||
p2 := Person{}
|
||||
db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email)
|
||||
if p2.Email != js.Email {
|
||||
t.Errorf("expected %s, got %s", js.Email, p2.Email)
|
||||
}
|
||||
|
||||
// test Txn NamedStmts
|
||||
tx := db.MustBegin()
|
||||
txns := tx.NamedStmt(ns)
|
||||
|
||||
// We're going to add Steven in this txn
|
||||
sl := Person{
|
||||
FirstName: "Steven",
|
||||
LastName: "Luatua",
|
||||
Email: "sluatua@ab.co.nz",
|
||||
}
|
||||
|
||||
_, err = txns.Exec(sl)
|
||||
test.Error(err)
|
||||
// then rollback...
|
||||
tx.Rollback()
|
||||
// looking for Steven after a rollback should fail
|
||||
err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
|
||||
if err != sql.ErrNoRows {
|
||||
t.Errorf("expected no rows error, got %v", err)
|
||||
}
|
||||
|
||||
// now do the same, but commit
|
||||
tx = db.MustBegin()
|
||||
txns = tx.NamedStmt(ns)
|
||||
_, err = txns.Exec(sl)
|
||||
test.Error(err)
|
||||
tx.Commit()
|
||||
|
||||
// looking for Steven after a Commit should succeed
|
||||
err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
|
||||
test.Error(err)
|
||||
if p2.Email != sl.Email {
|
||||
t.Errorf("expected %s, got %s", sl.Email, p2.Email)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
# 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 marshalers, and they are slow.
|
||||
|
||||
This reflectx package extends reflect to achieve these goals.
|
||||
-371
@@ -1,371 +0,0 @@
|
||||
// Package reflectx implements extensions to the standard reflect lib suitable
|
||||
// for implementing marshaling and unmarshaling 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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A FieldInfo is a collection of metadata about 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.
|
||||
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, optionally obeying a field tag for name
|
||||
// mapping and a function to provide a basic mapping of fields to names.
|
||||
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 which optionally obeys the field tag given
|
||||
// by tagName. 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 the 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 {
|
||||
t = Deref(t)
|
||||
mustBe(t, reflect.Struct)
|
||||
tm := m.TypeMap(t)
|
||||
|
||||
r := make([][]int, 0, len(names))
|
||||
for _, name := range names {
|
||||
fi, ok := tm.Names[name]
|
||||
if !ok {
|
||||
r = append(r, []int{})
|
||||
} else {
|
||||
r = append(r, fi.Index)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// FieldByIndexes returns a value for a particular struct traversal.
|
||||
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
|
||||
for _, i := range indexes {
|
||||
v = reflect.Indirect(v).Field(i)
|
||||
// if this is a pointer, it's possible it is nil
|
||||
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) {
|
||||
k := v.Kind()
|
||||
if k != expected {
|
||||
panic(&reflect.ValueError{Method: methodName(), Kind: k})
|
||||
}
|
||||
}
|
||||
|
||||
// methodName is 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
|
||||
}
|
||||
|
||||
// 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 func(string) string) *StructMap {
|
||||
m := []*FieldInfo{}
|
||||
|
||||
root := &FieldInfo{}
|
||||
queue := []typeQueue{}
|
||||
queue = append(queue, typeQueue{Deref(t), root, ""})
|
||||
|
||||
for len(queue) != 0 {
|
||||
// pop the first item off of the queue
|
||||
tq := queue[0]
|
||||
queue = queue[1:]
|
||||
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)
|
||||
|
||||
fi := FieldInfo{}
|
||||
fi.Field = f
|
||||
fi.Zero = reflect.New(f.Type).Elem()
|
||||
fi.Options = map[string]string{}
|
||||
|
||||
var tag, name string
|
||||
if tagName != "" && strings.Contains(string(f.Tag), tagName+":") {
|
||||
tag = f.Tag.Get(tagName)
|
||||
name = tag
|
||||
} else {
|
||||
if mapFunc != nil {
|
||||
name = mapFunc(f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
parts := strings.Split(name, ",")
|
||||
if len(parts) > 1 {
|
||||
name = parts[0]
|
||||
for _, opt := range parts[1:] {
|
||||
kv := strings.Split(opt, "=")
|
||||
if len(kv) > 1 {
|
||||
fi.Options[kv[0]] = kv[1]
|
||||
} else {
|
||||
fi.Options[kv[0]] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tagMapFunc != nil {
|
||||
tag = tagMapFunc(tag)
|
||||
}
|
||||
|
||||
fi.Name = name
|
||||
|
||||
if tq.pp == "" || (tq.pp == "" && tag == "") {
|
||||
fi.Path = fi.Name
|
||||
} else {
|
||||
fi.Path = fmt.Sprintf("%s.%s", tq.pp, fi.Name)
|
||||
}
|
||||
|
||||
// if the name is "-", disabled via a tag, skip it
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
-896
@@ -1,896 +0,0 @@
|
||||
package reflectx
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ival(v reflect.Value) int {
|
||||
return v.Interface().(int)
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
type Foo struct {
|
||||
A int
|
||||
B int
|
||||
C int
|
||||
}
|
||||
|
||||
f := Foo{1, 2, 3}
|
||||
fv := reflect.ValueOf(f)
|
||||
m := NewMapperFunc("", func(s string) string { return s })
|
||||
|
||||
v := m.FieldByName(fv, "A")
|
||||
if ival(v) != f.A {
|
||||
t.Errorf("Expecting %d, got %d", ival(v), f.A)
|
||||
}
|
||||
v = m.FieldByName(fv, "B")
|
||||
if ival(v) != f.B {
|
||||
t.Errorf("Expecting %d, got %d", f.B, ival(v))
|
||||
}
|
||||
v = m.FieldByName(fv, "C")
|
||||
if ival(v) != f.C {
|
||||
t.Errorf("Expecting %d, got %d", f.C, ival(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicEmbedded(t *testing.T) {
|
||||
type Foo struct {
|
||||
A int
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
Foo // `db:""` is implied for an embedded struct
|
||||
B int
|
||||
C int `db:"-"`
|
||||
}
|
||||
|
||||
type Baz struct {
|
||||
A int
|
||||
Bar `db:"Bar"`
|
||||
}
|
||||
|
||||
m := NewMapperFunc("db", func(s string) string { return s })
|
||||
|
||||
z := Baz{}
|
||||
z.A = 1
|
||||
z.B = 2
|
||||
z.C = 4
|
||||
z.Bar.Foo.A = 3
|
||||
|
||||
zv := reflect.ValueOf(z)
|
||||
fields := m.TypeMap(reflect.TypeOf(z))
|
||||
|
||||
if len(fields.Index) != 5 {
|
||||
t.Errorf("Expecting 5 fields")
|
||||
}
|
||||
|
||||
// for _, fi := range fields.Index {
|
||||
// log.Println(fi)
|
||||
// }
|
||||
|
||||
v := m.FieldByName(zv, "A")
|
||||
if ival(v) != z.A {
|
||||
t.Errorf("Expecting %d, got %d", z.A, ival(v))
|
||||
}
|
||||
v = m.FieldByName(zv, "Bar.B")
|
||||
if ival(v) != z.Bar.B {
|
||||
t.Errorf("Expecting %d, got %d", z.Bar.B, ival(v))
|
||||
}
|
||||
v = m.FieldByName(zv, "Bar.A")
|
||||
if ival(v) != z.Bar.Foo.A {
|
||||
t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v))
|
||||
}
|
||||
v = m.FieldByName(zv, "Bar.C")
|
||||
if _, ok := v.Interface().(int); ok {
|
||||
t.Errorf("Expecting Bar.C to not exist")
|
||||
}
|
||||
|
||||
fi := fields.GetByPath("Bar.C")
|
||||
if fi != nil {
|
||||
t.Errorf("Bar.C should not exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbeddedSimple(t *testing.T) {
|
||||
type UUID [16]byte
|
||||
type MyID struct {
|
||||
UUID
|
||||
}
|
||||
type Item struct {
|
||||
ID MyID
|
||||
}
|
||||
z := Item{}
|
||||
|
||||
m := NewMapper("db")
|
||||
m.TypeMap(reflect.TypeOf(z))
|
||||
}
|
||||
|
||||
func TestBasicEmbeddedWithTags(t *testing.T) {
|
||||
type Foo struct {
|
||||
A int `db:"a"`
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
Foo // `db:""` is implied for an embedded struct
|
||||
B int `db:"b"`
|
||||
}
|
||||
|
||||
type Baz struct {
|
||||
A int `db:"a"`
|
||||
Bar // `db:""` is implied for an embedded struct
|
||||
}
|
||||
|
||||
m := NewMapper("db")
|
||||
|
||||
z := Baz{}
|
||||
z.A = 1
|
||||
z.B = 2
|
||||
z.Bar.Foo.A = 3
|
||||
|
||||
zv := reflect.ValueOf(z)
|
||||
fields := m.TypeMap(reflect.TypeOf(z))
|
||||
|
||||
if len(fields.Index) != 5 {
|
||||
t.Errorf("Expecting 5 fields")
|
||||
}
|
||||
|
||||
// for _, fi := range fields.index {
|
||||
// log.Println(fi)
|
||||
// }
|
||||
|
||||
v := m.FieldByName(zv, "a")
|
||||
if ival(v) != z.Bar.Foo.A { // the dominant field
|
||||
t.Errorf("Expecting %d, got %d", z.Bar.Foo.A, ival(v))
|
||||
}
|
||||
v = m.FieldByName(zv, "b")
|
||||
if ival(v) != z.B {
|
||||
t.Errorf("Expecting %d, got %d", z.B, ival(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatTags(t *testing.T) {
|
||||
m := NewMapper("db")
|
||||
|
||||
type Asset struct {
|
||||
Title string `db:"title"`
|
||||
}
|
||||
type Post struct {
|
||||
Author string `db:"author,required"`
|
||||
Asset Asset `db:""`
|
||||
}
|
||||
// Post columns: (author title)
|
||||
|
||||
post := Post{Author: "Joe", Asset: Asset{Title: "Hello"}}
|
||||
pv := reflect.ValueOf(post)
|
||||
|
||||
v := m.FieldByName(pv, "author")
|
||||
if v.Interface().(string) != post.Author {
|
||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "title")
|
||||
if v.Interface().(string) != post.Asset.Title {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedStruct(t *testing.T) {
|
||||
m := NewMapper("db")
|
||||
|
||||
type Details struct {
|
||||
Active bool `db:"active"`
|
||||
}
|
||||
type Asset struct {
|
||||
Title string `db:"title"`
|
||||
Details Details `db:"details"`
|
||||
}
|
||||
type Post struct {
|
||||
Author string `db:"author,required"`
|
||||
Asset `db:"asset"`
|
||||
}
|
||||
// Post columns: (author asset.title asset.details.active)
|
||||
|
||||
post := Post{
|
||||
Author: "Joe",
|
||||
Asset: Asset{Title: "Hello", Details: Details{Active: true}},
|
||||
}
|
||||
pv := reflect.ValueOf(post)
|
||||
|
||||
v := m.FieldByName(pv, "author")
|
||||
if v.Interface().(string) != post.Author {
|
||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "title")
|
||||
if _, ok := v.Interface().(string); ok {
|
||||
t.Errorf("Expecting field to not exist")
|
||||
}
|
||||
v = m.FieldByName(pv, "asset.title")
|
||||
if v.Interface().(string) != post.Asset.Title {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "asset.details.active")
|
||||
if v.Interface().(bool) != post.Asset.Details.Active {
|
||||
t.Errorf("Expecting %v, got %v", post.Asset.Details.Active, v.Interface().(bool))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInlineStruct(t *testing.T) {
|
||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
||||
|
||||
type Employee struct {
|
||||
Name string
|
||||
ID int
|
||||
}
|
||||
type Boss Employee
|
||||
type person struct {
|
||||
Employee `db:"employee"`
|
||||
Boss `db:"boss"`
|
||||
}
|
||||
// employees columns: (employee.name employee.id boss.name boss.id)
|
||||
|
||||
em := person{Employee: Employee{Name: "Joe", ID: 2}, Boss: Boss{Name: "Dick", ID: 1}}
|
||||
ev := reflect.ValueOf(em)
|
||||
|
||||
fields := m.TypeMap(reflect.TypeOf(em))
|
||||
if len(fields.Index) != 6 {
|
||||
t.Errorf("Expecting 6 fields")
|
||||
}
|
||||
|
||||
v := m.FieldByName(ev, "employee.name")
|
||||
if v.Interface().(string) != em.Employee.Name {
|
||||
t.Errorf("Expecting %s, got %s", em.Employee.Name, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(ev, "boss.id")
|
||||
if ival(v) != em.Boss.ID {
|
||||
t.Errorf("Expecting %v, got %v", em.Boss.ID, ival(v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsEmbedded(t *testing.T) {
|
||||
m := NewMapper("db")
|
||||
|
||||
type Person struct {
|
||||
Name string `db:"name,size=64"`
|
||||
}
|
||||
type Place struct {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
type Article struct {
|
||||
Title string `db:"title"`
|
||||
}
|
||||
type PP struct {
|
||||
Person `db:"person,required"`
|
||||
Place `db:",someflag"`
|
||||
Article `db:",required"`
|
||||
}
|
||||
// PP columns: (person.name name title)
|
||||
|
||||
pp := PP{}
|
||||
pp.Person.Name = "Peter"
|
||||
pp.Place.Name = "Toronto"
|
||||
pp.Article.Title = "Best city ever"
|
||||
|
||||
fields := m.TypeMap(reflect.TypeOf(pp))
|
||||
// for i, f := range fields {
|
||||
// log.Println(i, f)
|
||||
// }
|
||||
|
||||
ppv := reflect.ValueOf(pp)
|
||||
|
||||
v := m.FieldByName(ppv, "person.name")
|
||||
if v.Interface().(string) != pp.Person.Name {
|
||||
t.Errorf("Expecting %s, got %s", pp.Person.Name, v.Interface().(string))
|
||||
}
|
||||
|
||||
v = m.FieldByName(ppv, "name")
|
||||
if v.Interface().(string) != pp.Place.Name {
|
||||
t.Errorf("Expecting %s, got %s", pp.Place.Name, v.Interface().(string))
|
||||
}
|
||||
|
||||
v = m.FieldByName(ppv, "title")
|
||||
if v.Interface().(string) != pp.Article.Title {
|
||||
t.Errorf("Expecting %s, got %s", pp.Article.Title, v.Interface().(string))
|
||||
}
|
||||
|
||||
fi := fields.GetByPath("person")
|
||||
if _, ok := fi.Options["required"]; !ok {
|
||||
t.Errorf("Expecting required option to be set")
|
||||
}
|
||||
if !fi.Embedded {
|
||||
t.Errorf("Expecting field to be embedded")
|
||||
}
|
||||
if len(fi.Index) != 1 || fi.Index[0] != 0 {
|
||||
t.Errorf("Expecting index to be [0]")
|
||||
}
|
||||
|
||||
fi = fields.GetByPath("person.name")
|
||||
if fi == nil {
|
||||
t.Errorf("Expecting person.name to exist")
|
||||
}
|
||||
if fi.Path != "person.name" {
|
||||
t.Errorf("Expecting %s, got %s", "person.name", fi.Path)
|
||||
}
|
||||
if fi.Options["size"] != "64" {
|
||||
t.Errorf("Expecting %s, got %s", "64", fi.Options["size"])
|
||||
}
|
||||
|
||||
fi = fields.GetByTraversal([]int{1, 0})
|
||||
if fi == nil {
|
||||
t.Errorf("Expecting traveral to exist")
|
||||
}
|
||||
if fi.Path != "name" {
|
||||
t.Errorf("Expecting %s, got %s", "name", fi.Path)
|
||||
}
|
||||
|
||||
fi = fields.GetByTraversal([]int{2})
|
||||
if fi == nil {
|
||||
t.Errorf("Expecting traversal to exist")
|
||||
}
|
||||
if _, ok := fi.Options["required"]; !ok {
|
||||
t.Errorf("Expecting required option to be set")
|
||||
}
|
||||
|
||||
trs := m.TraversalsByName(reflect.TypeOf(pp), []string{"person.name", "name", "title"})
|
||||
if !reflect.DeepEqual(trs, [][]int{{0, 0}, {1, 0}, {2, 0}}) {
|
||||
t.Errorf("Expecting traversal: %v", trs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPtrFields(t *testing.T) {
|
||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
||||
type Asset struct {
|
||||
Title string
|
||||
}
|
||||
type Post struct {
|
||||
*Asset `db:"asset"`
|
||||
Author string
|
||||
}
|
||||
|
||||
post := &Post{Author: "Joe", Asset: &Asset{Title: "Hiyo"}}
|
||||
pv := reflect.ValueOf(post)
|
||||
|
||||
fields := m.TypeMap(reflect.TypeOf(post))
|
||||
if len(fields.Index) != 3 {
|
||||
t.Errorf("Expecting 3 fields")
|
||||
}
|
||||
|
||||
v := m.FieldByName(pv, "asset.title")
|
||||
if v.Interface().(string) != post.Asset.Title {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset.Title, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "author")
|
||||
if v.Interface().(string) != post.Author {
|
||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedPtrFields(t *testing.T) {
|
||||
m := NewMapperTagFunc("db", strings.ToLower, nil)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
Title string
|
||||
|
||||
Owner *User `db:"owner"`
|
||||
}
|
||||
type Post struct {
|
||||
Author string
|
||||
|
||||
Asset1 *Asset `db:"asset1"`
|
||||
Asset2 *Asset `db:"asset2"`
|
||||
}
|
||||
|
||||
post := &Post{Author: "Joe", Asset1: &Asset{Title: "Hiyo", Owner: &User{"Username"}}} // Let Asset2 be nil
|
||||
pv := reflect.ValueOf(post)
|
||||
|
||||
fields := m.TypeMap(reflect.TypeOf(post))
|
||||
if len(fields.Index) != 9 {
|
||||
t.Errorf("Expecting 9 fields")
|
||||
}
|
||||
|
||||
v := m.FieldByName(pv, "asset1.title")
|
||||
if v.Interface().(string) != post.Asset1.Title {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset1.Title, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "asset1.owner.name")
|
||||
if v.Interface().(string) != post.Asset1.Owner.Name {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset1.Owner.Name, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "asset2.title")
|
||||
if v.Interface().(string) != post.Asset2.Title {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset2.Title, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "asset2.owner.name")
|
||||
if v.Interface().(string) != post.Asset2.Owner.Name {
|
||||
t.Errorf("Expecting %s, got %s", post.Asset2.Owner.Name, v.Interface().(string))
|
||||
}
|
||||
v = m.FieldByName(pv, "author")
|
||||
if v.Interface().(string) != post.Author {
|
||||
t.Errorf("Expecting %s, got %s", post.Author, v.Interface().(string))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldMap(t *testing.T) {
|
||||
type Foo struct {
|
||||
A int
|
||||
B int
|
||||
C int
|
||||
}
|
||||
|
||||
f := Foo{1, 2, 3}
|
||||
m := NewMapperFunc("db", strings.ToLower)
|
||||
|
||||
fm := m.FieldMap(reflect.ValueOf(f))
|
||||
|
||||
if len(fm) != 3 {
|
||||
t.Errorf("Expecting %d keys, got %d", 3, len(fm))
|
||||
}
|
||||
if fm["a"].Interface().(int) != 1 {
|
||||
t.Errorf("Expecting %d, got %d", 1, ival(fm["a"]))
|
||||
}
|
||||
if fm["b"].Interface().(int) != 2 {
|
||||
t.Errorf("Expecting %d, got %d", 2, ival(fm["b"]))
|
||||
}
|
||||
if fm["c"].Interface().(int) != 3 {
|
||||
t.Errorf("Expecting %d, got %d", 3, ival(fm["c"]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagNameMapping(t *testing.T) {
|
||||
type Strategy struct {
|
||||
StrategyID string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"`
|
||||
StrategyName string
|
||||
}
|
||||
|
||||
m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string {
|
||||
if strings.Contains(value, ",") {
|
||||
return strings.Split(value, ",")[0]
|
||||
}
|
||||
return value
|
||||
})
|
||||
strategy := Strategy{"1", "Alpah"}
|
||||
mapping := m.TypeMap(reflect.TypeOf(strategy))
|
||||
|
||||
for _, key := range []string{"strategy_id", "STRATEGYNAME"} {
|
||||
if fi := mapping.GetByPath(key); fi == nil {
|
||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapping(t *testing.T) {
|
||||
type Person struct {
|
||||
ID int
|
||||
Name string
|
||||
WearsGlasses bool `db:"wears_glasses"`
|
||||
}
|
||||
|
||||
m := NewMapperFunc("db", strings.ToLower)
|
||||
p := Person{1, "Jason", true}
|
||||
mapping := m.TypeMap(reflect.TypeOf(p))
|
||||
|
||||
for _, key := range []string{"id", "name", "wears_glasses"} {
|
||||
if fi := mapping.GetByPath(key); fi == nil {
|
||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
||||
}
|
||||
}
|
||||
|
||||
type SportsPerson struct {
|
||||
Weight int
|
||||
Age int
|
||||
Person
|
||||
}
|
||||
s := SportsPerson{Weight: 100, Age: 30, Person: p}
|
||||
mapping = m.TypeMap(reflect.TypeOf(s))
|
||||
for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} {
|
||||
if fi := mapping.GetByPath(key); fi == nil {
|
||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
||||
}
|
||||
}
|
||||
|
||||
type RugbyPlayer struct {
|
||||
Position int
|
||||
IsIntense bool `db:"is_intense"`
|
||||
IsAllBlack bool `db:"-"`
|
||||
SportsPerson
|
||||
}
|
||||
r := RugbyPlayer{12, true, false, s}
|
||||
mapping = m.TypeMap(reflect.TypeOf(r))
|
||||
for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} {
|
||||
if fi := mapping.GetByPath(key); fi == nil {
|
||||
t.Errorf("Expecting to find key %s in mapping but did not.", key)
|
||||
}
|
||||
}
|
||||
|
||||
if fi := mapping.GetByPath("isallblack"); fi != nil {
|
||||
t.Errorf("Expecting to ignore `IsAllBlack` field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByTraversal(t *testing.T) {
|
||||
type C struct {
|
||||
C0 int
|
||||
C1 int
|
||||
}
|
||||
type B struct {
|
||||
B0 string
|
||||
B1 *C
|
||||
}
|
||||
type A struct {
|
||||
A0 int
|
||||
A1 B
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Index []int
|
||||
ExpectedName string
|
||||
ExpectNil bool
|
||||
}{
|
||||
{
|
||||
Index: []int{0},
|
||||
ExpectedName: "A0",
|
||||
},
|
||||
{
|
||||
Index: []int{1, 0},
|
||||
ExpectedName: "B0",
|
||||
},
|
||||
{
|
||||
Index: []int{1, 1, 1},
|
||||
ExpectedName: "C1",
|
||||
},
|
||||
{
|
||||
Index: []int{3, 4, 5},
|
||||
ExpectNil: true,
|
||||
},
|
||||
{
|
||||
Index: []int{},
|
||||
ExpectNil: true,
|
||||
},
|
||||
{
|
||||
Index: nil,
|
||||
ExpectNil: true,
|
||||
},
|
||||
}
|
||||
|
||||
m := NewMapperFunc("db", func(n string) string { return n })
|
||||
tm := m.TypeMap(reflect.TypeOf(A{}))
|
||||
|
||||
for i, tc := range testCases {
|
||||
fi := tm.GetByTraversal(tc.Index)
|
||||
if tc.ExpectNil {
|
||||
if fi != nil {
|
||||
t.Errorf("%d: expected nil, got %v", i, fi)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if fi == nil {
|
||||
t.Errorf("%d: expected %s, got nil", i, tc.ExpectedName)
|
||||
continue
|
||||
}
|
||||
|
||||
if fi.Name != tc.ExpectedName {
|
||||
t.Errorf("%d: expected %s, got %s", i, tc.ExpectedName, fi.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMapperMethodsByName tests Mapper methods FieldByName and TraversalsByName
|
||||
func TestMapperMethodsByName(t *testing.T) {
|
||||
type C struct {
|
||||
C0 string
|
||||
C1 int
|
||||
}
|
||||
type B struct {
|
||||
B0 *C `db:"B0"`
|
||||
B1 C `db:"B1"`
|
||||
B2 string `db:"B2"`
|
||||
}
|
||||
type A struct {
|
||||
A0 *B `db:"A0"`
|
||||
B `db:"A1"`
|
||||
A2 int
|
||||
a3 int
|
||||
}
|
||||
|
||||
val := &A{
|
||||
A0: &B{
|
||||
B0: &C{C0: "0", C1: 1},
|
||||
B1: C{C0: "2", C1: 3},
|
||||
B2: "4",
|
||||
},
|
||||
B: B{
|
||||
B0: nil,
|
||||
B1: C{C0: "5", C1: 6},
|
||||
B2: "7",
|
||||
},
|
||||
A2: 8,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
ExpectInvalid bool
|
||||
ExpectedValue interface{}
|
||||
ExpectedIndexes []int
|
||||
}{
|
||||
{
|
||||
Name: "A0.B0.C0",
|
||||
ExpectedValue: "0",
|
||||
ExpectedIndexes: []int{0, 0, 0},
|
||||
},
|
||||
{
|
||||
Name: "A0.B0.C1",
|
||||
ExpectedValue: 1,
|
||||
ExpectedIndexes: []int{0, 0, 1},
|
||||
},
|
||||
{
|
||||
Name: "A0.B1.C0",
|
||||
ExpectedValue: "2",
|
||||
ExpectedIndexes: []int{0, 1, 0},
|
||||
},
|
||||
{
|
||||
Name: "A0.B1.C1",
|
||||
ExpectedValue: 3,
|
||||
ExpectedIndexes: []int{0, 1, 1},
|
||||
},
|
||||
{
|
||||
Name: "A0.B2",
|
||||
ExpectedValue: "4",
|
||||
ExpectedIndexes: []int{0, 2},
|
||||
},
|
||||
{
|
||||
Name: "A1.B0.C0",
|
||||
ExpectedValue: "",
|
||||
ExpectedIndexes: []int{1, 0, 0},
|
||||
},
|
||||
{
|
||||
Name: "A1.B0.C1",
|
||||
ExpectedValue: 0,
|
||||
ExpectedIndexes: []int{1, 0, 1},
|
||||
},
|
||||
{
|
||||
Name: "A1.B1.C0",
|
||||
ExpectedValue: "5",
|
||||
ExpectedIndexes: []int{1, 1, 0},
|
||||
},
|
||||
{
|
||||
Name: "A1.B1.C1",
|
||||
ExpectedValue: 6,
|
||||
ExpectedIndexes: []int{1, 1, 1},
|
||||
},
|
||||
{
|
||||
Name: "A1.B2",
|
||||
ExpectedValue: "7",
|
||||
ExpectedIndexes: []int{1, 2},
|
||||
},
|
||||
{
|
||||
Name: "A2",
|
||||
ExpectedValue: 8,
|
||||
ExpectedIndexes: []int{2},
|
||||
},
|
||||
{
|
||||
Name: "XYZ",
|
||||
ExpectInvalid: true,
|
||||
ExpectedIndexes: []int{},
|
||||
},
|
||||
{
|
||||
Name: "a3",
|
||||
ExpectInvalid: true,
|
||||
ExpectedIndexes: []int{},
|
||||
},
|
||||
}
|
||||
|
||||
// build the names array from the test cases
|
||||
names := make([]string, len(testCases))
|
||||
for i, tc := range testCases {
|
||||
names[i] = tc.Name
|
||||
}
|
||||
m := NewMapperFunc("db", func(n string) string { return n })
|
||||
v := reflect.ValueOf(val)
|
||||
values := m.FieldsByName(v, names)
|
||||
if len(values) != len(testCases) {
|
||||
t.Errorf("expected %d values, got %d", len(testCases), len(values))
|
||||
t.FailNow()
|
||||
}
|
||||
indexes := m.TraversalsByName(v.Type(), names)
|
||||
if len(indexes) != len(testCases) {
|
||||
t.Errorf("expected %d traversals, got %d", len(testCases), len(indexes))
|
||||
t.FailNow()
|
||||
}
|
||||
for i, val := range values {
|
||||
tc := testCases[i]
|
||||
traversal := indexes[i]
|
||||
if !reflect.DeepEqual(tc.ExpectedIndexes, traversal) {
|
||||
t.Errorf("%d: expected %v, got %v", tc.ExpectedIndexes, traversal)
|
||||
t.FailNow()
|
||||
}
|
||||
val = reflect.Indirect(val)
|
||||
if tc.ExpectInvalid {
|
||||
if val.IsValid() {
|
||||
t.Errorf("%d: expected zero value, got %v", i, val)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !val.IsValid() {
|
||||
t.Errorf("%d: expected valid value, got %v", i, val)
|
||||
continue
|
||||
}
|
||||
actualValue := reflect.Indirect(val).Interface()
|
||||
if !reflect.DeepEqual(tc.ExpectedValue, actualValue) {
|
||||
t.Errorf("%d: expected %v, got %v", i, tc.ExpectedValue, actualValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldByIndexes(t *testing.T) {
|
||||
type C struct {
|
||||
C0 bool
|
||||
C1 string
|
||||
C2 int
|
||||
C3 map[string]int
|
||||
}
|
||||
type B struct {
|
||||
B1 C
|
||||
B2 *C
|
||||
}
|
||||
type A struct {
|
||||
A1 B
|
||||
A2 *B
|
||||
}
|
||||
testCases := []struct {
|
||||
value interface{}
|
||||
indexes []int
|
||||
expectedValue interface{}
|
||||
readOnly bool
|
||||
}{
|
||||
{
|
||||
value: A{
|
||||
A1: B{B1: C{C0: true}},
|
||||
},
|
||||
indexes: []int{0, 0, 0},
|
||||
expectedValue: true,
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
value: A{
|
||||
A2: &B{B2: &C{C1: "answer"}},
|
||||
},
|
||||
indexes: []int{1, 1, 1},
|
||||
expectedValue: "answer",
|
||||
readOnly: true,
|
||||
},
|
||||
{
|
||||
value: &A{},
|
||||
indexes: []int{1, 1, 3},
|
||||
expectedValue: map[string]int{},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
checkResults := func(v reflect.Value) {
|
||||
if tc.expectedValue == nil {
|
||||
if !v.IsNil() {
|
||||
t.Errorf("%d: expected nil, actual %v", i, v.Interface())
|
||||
}
|
||||
} else {
|
||||
if !reflect.DeepEqual(tc.expectedValue, v.Interface()) {
|
||||
t.Errorf("%d: expected %v, actual %v", i, tc.expectedValue, v.Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkResults(FieldByIndexes(reflect.ValueOf(tc.value), tc.indexes))
|
||||
if tc.readOnly {
|
||||
checkResults(FieldByIndexesReadOnly(reflect.ValueOf(tc.value), tc.indexes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustBe(t *testing.T) {
|
||||
typ := reflect.TypeOf(E1{})
|
||||
mustBe(typ, reflect.Struct)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
valueErr, ok := r.(*reflect.ValueError)
|
||||
if !ok {
|
||||
t.Errorf("unexpected Method: %s", valueErr.Method)
|
||||
t.Error("expected panic with *reflect.ValueError")
|
||||
return
|
||||
}
|
||||
if valueErr.Method != "github.com/jmoiron/sqlx/reflectx.TestMustBe" {
|
||||
}
|
||||
if valueErr.Kind != reflect.String {
|
||||
t.Errorf("unexpected Kind: %s", valueErr.Kind)
|
||||
}
|
||||
} else {
|
||||
t.Error("expected panic")
|
||||
}
|
||||
}()
|
||||
|
||||
typ = reflect.TypeOf("string")
|
||||
mustBe(typ, reflect.Struct)
|
||||
t.Error("got here, didn't expect to")
|
||||
}
|
||||
|
||||
type E1 struct {
|
||||
A int
|
||||
}
|
||||
type E2 struct {
|
||||
E1
|
||||
B int
|
||||
}
|
||||
type E3 struct {
|
||||
E2
|
||||
C int
|
||||
}
|
||||
type E4 struct {
|
||||
E3
|
||||
D int
|
||||
}
|
||||
|
||||
func BenchmarkFieldNameL1(b *testing.B) {
|
||||
e4 := E4{D: 1}
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := reflect.ValueOf(e4)
|
||||
f := v.FieldByName("D")
|
||||
if f.Interface().(int) != 1 {
|
||||
b.Fatal("Wrong value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldNameL4(b *testing.B) {
|
||||
e4 := E4{}
|
||||
e4.A = 1
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := reflect.ValueOf(e4)
|
||||
f := v.FieldByName("A")
|
||||
if f.Interface().(int) != 1 {
|
||||
b.Fatal("Wrong value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldPosL1(b *testing.B) {
|
||||
e4 := E4{D: 1}
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := reflect.ValueOf(e4)
|
||||
f := v.Field(1)
|
||||
if f.Interface().(int) != 1 {
|
||||
b.Fatal("Wrong value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldPosL4(b *testing.B) {
|
||||
e4 := E4{}
|
||||
e4.A = 1
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := reflect.ValueOf(e4)
|
||||
f := v.Field(0)
|
||||
f = f.Field(0)
|
||||
f = f.Field(0)
|
||||
f = f.Field(0)
|
||||
if f.Interface().(int) != 1 {
|
||||
b.Fatal("Wrong value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFieldByIndexL4(b *testing.B) {
|
||||
e4 := E4{}
|
||||
e4.A = 1
|
||||
idx := []int{0, 0, 0, 0}
|
||||
for i := 0; i < b.N; i++ {
|
||||
v := reflect.ValueOf(e4)
|
||||
f := FieldByIndexes(v, idx)
|
||||
if f.Interface().(int) != 1 {
|
||||
b.Fatal("Wrong value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
-992
@@ -1,992 +0,0 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx/reflectx"
|
||||
)
|
||||
|
||||
// Although the NameMapper is convenient, in practice it should not
|
||||
// be relied on except for application code. If you are writing a library
|
||||
// that uses sqlx, you should be aware that the name mappings you expect
|
||||
// can be overridded by your user's application.
|
||||
|
||||
// NameMapper is used to map column names to struct field names. By default,
|
||||
// it uses strings.ToLower to lowercase struct field names. It can be set
|
||||
// to whatever you want, but it is encouraged to be set before sqlx is used
|
||||
// as name-to-field mappings are cached after first use on a type.
|
||||
var NameMapper = strings.ToLower
|
||||
var origMapper = reflect.ValueOf(NameMapper)
|
||||
|
||||
// Rather than creating on init, this is created when necessary so that
|
||||
// importers have time to customize the NameMapper.
|
||||
var mpr *reflectx.Mapper
|
||||
|
||||
// mapper returns a valid mapper using the configured NameMapper func.
|
||||
func mapper() *reflectx.Mapper {
|
||||
if mpr == nil {
|
||||
mpr = reflectx.NewMapperFunc("db", NameMapper)
|
||||
} else if origMapper != reflect.ValueOf(NameMapper) {
|
||||
// if NameMapper has changed, create a new mapper
|
||||
mpr = reflectx.NewMapperFunc("db", NameMapper)
|
||||
origMapper = reflect.ValueOf(NameMapper)
|
||||
}
|
||||
return mpr
|
||||
}
|
||||
|
||||
// isScannable takes the reflect.Type and the actual dest value and returns
|
||||
// whether or not it's Scannable. Something is scannable if:
|
||||
// * it is not a struct
|
||||
// * it implements sql.Scanner
|
||||
// * it has no exported fields
|
||||
func isScannable(t reflect.Type) bool {
|
||||
if reflect.PtrTo(t).Implements(_scannerInterface) {
|
||||
return true
|
||||
}
|
||||
if t.Kind() != reflect.Struct {
|
||||
return true
|
||||
}
|
||||
|
||||
// it's not important that we use the right mapper for this particular object,
|
||||
// we're only concerned on how many exported fields this struct has
|
||||
m := mapper()
|
||||
if len(m.TypeMap(t).Index) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ColScanner is an interface used by MapScan and SliceScan
|
||||
type ColScanner interface {
|
||||
Columns() ([]string, error)
|
||||
Scan(dest ...interface{}) error
|
||||
Err() error
|
||||
}
|
||||
|
||||
// Queryer is an interface used by Get and Select
|
||||
type Queryer interface {
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Queryx(query string, args ...interface{}) (*Rows, error)
|
||||
QueryRowx(query string, args ...interface{}) *Row
|
||||
}
|
||||
|
||||
// Execer is an interface used by MustExec and LoadFile
|
||||
type Execer interface {
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
// Binder is an interface for something which can bind queries (Tx, DB)
|
||||
type binder interface {
|
||||
DriverName() string
|
||||
Rebind(string) string
|
||||
BindNamed(string, interface{}) (string, []interface{}, error)
|
||||
}
|
||||
|
||||
// Ext is a union interface which can bind, query, and exec, used by
|
||||
// NamedQuery and NamedExec.
|
||||
type Ext interface {
|
||||
binder
|
||||
Queryer
|
||||
Execer
|
||||
}
|
||||
|
||||
// Preparer is an interface used by Preparex.
|
||||
type Preparer interface {
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// determine if any of our extensions are unsafe
|
||||
func isUnsafe(i interface{}) bool {
|
||||
switch v := i.(type) {
|
||||
case Row:
|
||||
return v.unsafe
|
||||
case *Row:
|
||||
return v.unsafe
|
||||
case Rows:
|
||||
return v.unsafe
|
||||
case *Rows:
|
||||
return v.unsafe
|
||||
case NamedStmt:
|
||||
return v.Stmt.unsafe
|
||||
case *NamedStmt:
|
||||
return v.Stmt.unsafe
|
||||
case Stmt:
|
||||
return v.unsafe
|
||||
case *Stmt:
|
||||
return v.unsafe
|
||||
case qStmt:
|
||||
return v.unsafe
|
||||
case *qStmt:
|
||||
return v.unsafe
|
||||
case DB:
|
||||
return v.unsafe
|
||||
case *DB:
|
||||
return v.unsafe
|
||||
case Tx:
|
||||
return v.unsafe
|
||||
case *Tx:
|
||||
return v.unsafe
|
||||
case sql.Rows, *sql.Rows:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func mapperFor(i interface{}) *reflectx.Mapper {
|
||||
switch i.(type) {
|
||||
case DB:
|
||||
return i.(DB).Mapper
|
||||
case *DB:
|
||||
return i.(*DB).Mapper
|
||||
case Tx:
|
||||
return i.(Tx).Mapper
|
||||
case *Tx:
|
||||
return i.(*Tx).Mapper
|
||||
default:
|
||||
return mapper()
|
||||
}
|
||||
}
|
||||
|
||||
var _scannerInterface = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||
var _valuerInterface = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
|
||||
// Row is a reimplementation of sql.Row in order to gain access to the underlying
|
||||
// sql.Rows.Columns() data, necessary for StructScan.
|
||||
type Row struct {
|
||||
err error
|
||||
unsafe bool
|
||||
rows *sql.Rows
|
||||
Mapper *reflectx.Mapper
|
||||
}
|
||||
|
||||
// Scan is a fixed implementation of sql.Row.Scan, which does not discard the
|
||||
// underlying error from the internal rows object if it exists.
|
||||
func (r *Row) Scan(dest ...interface{}) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// TODO(bradfitz): for now we need to defensively clone all
|
||||
// []byte that the driver returned (not permitting
|
||||
// *RawBytes in Rows.Scan), since we're about to close
|
||||
// the Rows in our defer, when we return from this function.
|
||||
// the contract with the driver.Next(...) interface is that it
|
||||
// can return slices into read-only temporary memory that's
|
||||
// only valid until the next Scan/Close. But the TODO is that
|
||||
// for a lot of drivers, this copy will be unnecessary. We
|
||||
// should provide an optional interface for drivers to
|
||||
// implement to say, "don't worry, the []bytes that I return
|
||||
// from Next will not be modified again." (for instance, if
|
||||
// they were obtained from the network anyway) But for now we
|
||||
// don't care.
|
||||
defer r.rows.Close()
|
||||
for _, dp := range dest {
|
||||
if _, ok := dp.(*sql.RawBytes); ok {
|
||||
return errors.New("sql: RawBytes isn't allowed on Row.Scan")
|
||||
}
|
||||
}
|
||||
|
||||
if !r.rows.Next() {
|
||||
if err := r.rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
err := r.rows.Scan(dest...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure the query can be processed to completion with no errors.
|
||||
if err := r.rows.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Columns returns the underlying sql.Rows.Columns(), or the deferred error usually
|
||||
// returned by Row.Scan()
|
||||
func (r *Row) Columns() ([]string, error) {
|
||||
if r.err != nil {
|
||||
return []string{}, r.err
|
||||
}
|
||||
return r.rows.Columns()
|
||||
}
|
||||
|
||||
// Err returns the error encountered while scanning.
|
||||
func (r *Row) Err() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// DB is a wrapper around sql.DB which keeps track of the driverName upon Open,
|
||||
// used mostly to automatically bind named queries using the right bindvars.
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
driverName string
|
||||
unsafe bool
|
||||
Mapper *reflectx.Mapper
|
||||
}
|
||||
|
||||
// NewDb returns a new sqlx DB wrapper for a pre-existing *sql.DB. The
|
||||
// driverName of the original database is required for named query support.
|
||||
func NewDb(db *sql.DB, driverName string) *DB {
|
||||
return &DB{DB: db, driverName: driverName, Mapper: mapper()}
|
||||
}
|
||||
|
||||
// DriverName returns the driverName passed to the Open function for this DB.
|
||||
func (db *DB) DriverName() string {
|
||||
return db.driverName
|
||||
}
|
||||
|
||||
// Open is the same as sql.Open, but returns an *sqlx.DB instead.
|
||||
func Open(driverName, dataSourceName string) (*DB, error) {
|
||||
db, err := sql.Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DB{DB: db, driverName: driverName, Mapper: mapper()}, err
|
||||
}
|
||||
|
||||
// MustOpen is the same as sql.Open, but returns an *sqlx.DB instead and panics on error.
|
||||
func MustOpen(driverName, dataSourceName string) *DB {
|
||||
db, err := Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// MapperFunc sets a new mapper for this db using the default sqlx struct tag
|
||||
// and the provided mapper function.
|
||||
func (db *DB) MapperFunc(mf func(string) string) {
|
||||
db.Mapper = reflectx.NewMapperFunc("db", mf)
|
||||
}
|
||||
|
||||
// Rebind transforms a query from QUESTION to the DB driver's bindvar type.
|
||||
func (db *DB) Rebind(query string) string {
|
||||
return Rebind(BindType(db.driverName), query)
|
||||
}
|
||||
|
||||
// Unsafe returns a version of DB which will silently succeed to scan when
|
||||
// columns in the SQL result have no fields in the destination struct.
|
||||
// sqlx.Stmt and sqlx.Tx which are created from this DB will inherit its
|
||||
// safety behavior.
|
||||
func (db *DB) Unsafe() *DB {
|
||||
return &DB{DB: db.DB, driverName: db.driverName, unsafe: true, Mapper: db.Mapper}
|
||||
}
|
||||
|
||||
// BindNamed binds a query using the DB driver's bindvar type.
|
||||
func (db *DB) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
|
||||
return bindNamedMapper(BindType(db.driverName), query, arg, db.Mapper)
|
||||
}
|
||||
|
||||
// NamedQuery using this DB.
|
||||
func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error) {
|
||||
return NamedQuery(db, query, arg)
|
||||
}
|
||||
|
||||
// NamedExec using this DB.
|
||||
func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error) {
|
||||
return NamedExec(db, query, arg)
|
||||
}
|
||||
|
||||
// Select using this DB.
|
||||
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
|
||||
return Select(db, dest, query, args...)
|
||||
}
|
||||
|
||||
// Get using this DB.
|
||||
func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
|
||||
return Get(db, dest, query, args...)
|
||||
}
|
||||
|
||||
// MustBegin starts a transaction, and panics on error. Returns an *sqlx.Tx instead
|
||||
// of an *sql.Tx.
|
||||
func (db *DB) MustBegin() *Tx {
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// Beginx begins a transaction and returns an *sqlx.Tx instead of an *sql.Tx.
|
||||
func (db *DB) Beginx() (*Tx, error) {
|
||||
tx, err := db.DB.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||
}
|
||||
|
||||
// Queryx queries the database and returns an *sqlx.Rows.
|
||||
func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := db.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
|
||||
}
|
||||
|
||||
// QueryRowx queries the database and returns an *sqlx.Row.
|
||||
func (db *DB) QueryRowx(query string, args ...interface{}) *Row {
|
||||
rows, err := db.DB.Query(query, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
|
||||
}
|
||||
|
||||
// MustExec (panic) runs MustExec using this database.
|
||||
func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
|
||||
return MustExec(db, query, args...)
|
||||
}
|
||||
|
||||
// Preparex returns an sqlx.Stmt instead of a sql.Stmt
|
||||
func (db *DB) Preparex(query string) (*Stmt, error) {
|
||||
return Preparex(db, query)
|
||||
}
|
||||
|
||||
// PrepareNamed returns an sqlx.NamedStmt
|
||||
func (db *DB) PrepareNamed(query string) (*NamedStmt, error) {
|
||||
return prepareNamed(db, query)
|
||||
}
|
||||
|
||||
// Tx is an sqlx wrapper around sql.Tx with extra functionality
|
||||
type Tx struct {
|
||||
*sql.Tx
|
||||
driverName string
|
||||
unsafe bool
|
||||
Mapper *reflectx.Mapper
|
||||
}
|
||||
|
||||
// DriverName returns the driverName used by the DB which began this transaction.
|
||||
func (tx *Tx) DriverName() string {
|
||||
return tx.driverName
|
||||
}
|
||||
|
||||
// Rebind a query within a transaction's bindvar type.
|
||||
func (tx *Tx) Rebind(query string) string {
|
||||
return Rebind(BindType(tx.driverName), query)
|
||||
}
|
||||
|
||||
// Unsafe returns a version of Tx which will silently succeed to scan when
|
||||
// columns in the SQL result have no fields in the destination struct.
|
||||
func (tx *Tx) Unsafe() *Tx {
|
||||
return &Tx{Tx: tx.Tx, driverName: tx.driverName, unsafe: true, Mapper: tx.Mapper}
|
||||
}
|
||||
|
||||
// BindNamed binds a query within a transaction's bindvar type.
|
||||
func (tx *Tx) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
|
||||
return bindNamedMapper(BindType(tx.driverName), query, arg, tx.Mapper)
|
||||
}
|
||||
|
||||
// NamedQuery within a transaction.
|
||||
func (tx *Tx) NamedQuery(query string, arg interface{}) (*Rows, error) {
|
||||
return NamedQuery(tx, query, arg)
|
||||
}
|
||||
|
||||
// NamedExec a named query within a transaction.
|
||||
func (tx *Tx) NamedExec(query string, arg interface{}) (sql.Result, error) {
|
||||
return NamedExec(tx, query, arg)
|
||||
}
|
||||
|
||||
// Select within a transaction.
|
||||
func (tx *Tx) Select(dest interface{}, query string, args ...interface{}) error {
|
||||
return Select(tx, dest, query, args...)
|
||||
}
|
||||
|
||||
// Queryx within a transaction.
|
||||
func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := tx.Tx.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
|
||||
}
|
||||
|
||||
// QueryRowx within a transaction.
|
||||
func (tx *Tx) QueryRowx(query string, args ...interface{}) *Row {
|
||||
rows, err := tx.Tx.Query(query, args...)
|
||||
return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
|
||||
}
|
||||
|
||||
// Get within a transaction.
|
||||
func (tx *Tx) Get(dest interface{}, query string, args ...interface{}) error {
|
||||
return Get(tx, dest, query, args...)
|
||||
}
|
||||
|
||||
// MustExec runs MustExec within a transaction.
|
||||
func (tx *Tx) MustExec(query string, args ...interface{}) sql.Result {
|
||||
return MustExec(tx, query, args...)
|
||||
}
|
||||
|
||||
// Preparex a statement within a transaction.
|
||||
func (tx *Tx) Preparex(query string) (*Stmt, error) {
|
||||
return Preparex(tx, query)
|
||||
}
|
||||
|
||||
// Stmtx 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) Stmtx(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
|
||||
case *sql.Stmt:
|
||||
s = v
|
||||
default:
|
||||
panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
|
||||
}
|
||||
return &Stmt{Stmt: tx.Stmt(s), Mapper: tx.Mapper}
|
||||
}
|
||||
|
||||
// NamedStmt returns a version of the prepared statement which runs within a transaction.
|
||||
func (tx *Tx) NamedStmt(stmt *NamedStmt) *NamedStmt {
|
||||
return &NamedStmt{
|
||||
QueryString: stmt.QueryString,
|
||||
Params: stmt.Params,
|
||||
Stmt: tx.Stmtx(stmt.Stmt),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareNamed returns an sqlx.NamedStmt
|
||||
func (tx *Tx) PrepareNamed(query string) (*NamedStmt, error) {
|
||||
return prepareNamed(tx, query)
|
||||
}
|
||||
|
||||
// Stmt is an sqlx wrapper around sql.Stmt with extra functionality
|
||||
type Stmt struct {
|
||||
*sql.Stmt
|
||||
unsafe bool
|
||||
Mapper *reflectx.Mapper
|
||||
}
|
||||
|
||||
// Unsafe returns a version of Stmt which will silently succeed to scan when
|
||||
// columns in the SQL result have no fields in the destination struct.
|
||||
func (s *Stmt) Unsafe() *Stmt {
|
||||
return &Stmt{Stmt: s.Stmt, unsafe: true, Mapper: s.Mapper}
|
||||
}
|
||||
|
||||
// Select using the prepared statement.
|
||||
func (s *Stmt) Select(dest interface{}, args ...interface{}) error {
|
||||
return Select(&qStmt{s}, dest, "", args...)
|
||||
}
|
||||
|
||||
// Get using the prepared statement.
|
||||
func (s *Stmt) Get(dest interface{}, args ...interface{}) error {
|
||||
return Get(&qStmt{s}, dest, "", args...)
|
||||
}
|
||||
|
||||
// MustExec (panic) using this statement. Note that the query portion of the error
|
||||
// output will be blank, as Stmt does not expose its query.
|
||||
func (s *Stmt) MustExec(args ...interface{}) sql.Result {
|
||||
return MustExec(&qStmt{s}, "", args...)
|
||||
}
|
||||
|
||||
// QueryRowx using this statement.
|
||||
func (s *Stmt) QueryRowx(args ...interface{}) *Row {
|
||||
qs := &qStmt{s}
|
||||
return qs.QueryRowx("", args...)
|
||||
}
|
||||
|
||||
// Queryx using this statement.
|
||||
func (s *Stmt) Queryx(args ...interface{}) (*Rows, error) {
|
||||
qs := &qStmt{s}
|
||||
return qs.Queryx("", args...)
|
||||
}
|
||||
|
||||
// qStmt is an unexposed wrapper which lets you use a Stmt as a Queryer & Execer by
|
||||
// implementing those interfaces and ignoring the `query` argument.
|
||||
type qStmt struct{ *Stmt }
|
||||
|
||||
func (q *qStmt) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
return q.Stmt.Query(args...)
|
||||
}
|
||||
|
||||
func (q *qStmt) Queryx(query string, args ...interface{}) (*Rows, error) {
|
||||
r, err := q.Stmt.Query(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
|
||||
}
|
||||
|
||||
func (q *qStmt) QueryRowx(query string, args ...interface{}) *Row {
|
||||
rows, err := q.Stmt.Query(args...)
|
||||
return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
|
||||
}
|
||||
|
||||
func (q *qStmt) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return q.Stmt.Exec(args...)
|
||||
}
|
||||
|
||||
// Rows is a wrapper around sql.Rows which caches costly reflect operations
|
||||
// during a looped StructScan
|
||||
type Rows struct {
|
||||
*sql.Rows
|
||||
unsafe bool
|
||||
Mapper *reflectx.Mapper
|
||||
// these fields cache memory use for a rows during iteration w/ structScan
|
||||
started bool
|
||||
fields [][]int
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
// SliceScan using this Rows.
|
||||
func (r *Rows) SliceScan() ([]interface{}, error) {
|
||||
return SliceScan(r)
|
||||
}
|
||||
|
||||
// MapScan using this Rows.
|
||||
func (r *Rows) MapScan(dest map[string]interface{}) error {
|
||||
return MapScan(r, dest)
|
||||
}
|
||||
|
||||
// StructScan is like sql.Rows.Scan, but scans a single Row into a single Struct.
|
||||
// Use this and iterate over Rows manually when the memory load of Select() might be
|
||||
// prohibitive. *Rows.StructScan caches the reflect work of matching up column
|
||||
// positions to fields to avoid that overhead per scan, which means it is not safe
|
||||
// to run StructScan on the same Rows instance with different struct types.
|
||||
func (r *Rows) StructScan(dest interface{}) error {
|
||||
v := reflect.ValueOf(dest)
|
||||
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
if !r.started {
|
||||
columns, err := r.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := r.Mapper
|
||||
|
||||
r.fields = m.TraversalsByName(v.Type(), columns)
|
||||
// if we are not unsafe and are missing fields, return an error
|
||||
if f, err := missingFields(r.fields); err != nil && !r.unsafe {
|
||||
return fmt.Errorf("missing destination name %s", columns[f])
|
||||
}
|
||||
r.values = make([]interface{}, len(columns))
|
||||
r.started = true
|
||||
}
|
||||
|
||||
err := fieldsByTraversal(v, r.fields, r.values, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// scan into the struct field pointers and append to our results
|
||||
err = r.Scan(r.values...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Err()
|
||||
}
|
||||
|
||||
// Connect to a database and verify with a ping.
|
||||
func Connect(driverName, dataSourceName string) (*DB, error) {
|
||||
db, err := Open(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
return db, err
|
||||
}
|
||||
err = db.Ping()
|
||||
return db, err
|
||||
}
|
||||
|
||||
// MustConnect connects to a database and panics on error.
|
||||
func MustConnect(driverName, dataSourceName string) *DB {
|
||||
db, err := Connect(driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Preparex prepares a statement.
|
||||
func Preparex(p Preparer, query string) (*Stmt, error) {
|
||||
s, err := p.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
|
||||
}
|
||||
|
||||
// Select 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.
|
||||
func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
|
||||
rows, err := q.Queryx(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)
|
||||
}
|
||||
|
||||
// Get 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.
|
||||
func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
|
||||
r := q.QueryRowx(query, args...)
|
||||
return r.scanAny(dest, false)
|
||||
}
|
||||
|
||||
// LoadFile exec's every statement in a file (as a single call to Exec).
|
||||
// LoadFile 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 LoadFile(e Execer, 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.Exec(string(contents))
|
||||
return &res, err
|
||||
}
|
||||
|
||||
// MustExec execs the query using e and panics if there was an error.
|
||||
func MustExec(e Execer, query string, args ...interface{}) sql.Result {
|
||||
res, err := e.Exec(query, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SliceScan using this Rows.
|
||||
func (r *Row) SliceScan() ([]interface{}, error) {
|
||||
return SliceScan(r)
|
||||
}
|
||||
|
||||
// MapScan using this Rows.
|
||||
func (r *Row) MapScan(dest map[string]interface{}) error {
|
||||
return MapScan(r, dest)
|
||||
}
|
||||
|
||||
func (r *Row) scanAny(dest interface{}, structOnly bool) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
defer r.rows.Close()
|
||||
|
||||
v := reflect.ValueOf(dest)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
if v.IsNil() {
|
||||
return errors.New("nil pointer passed to StructScan destination")
|
||||
}
|
||||
|
||||
base := reflectx.Deref(v.Type())
|
||||
scannable := isScannable(base)
|
||||
|
||||
if structOnly && scannable {
|
||||
return structOnlyError(base)
|
||||
}
|
||||
|
||||
columns, err := r.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if scannable && len(columns) > 1 {
|
||||
return fmt.Errorf("scannable dest type %s with >1 columns (%d) in result", base.Kind(), len(columns))
|
||||
}
|
||||
|
||||
if scannable {
|
||||
return r.Scan(dest)
|
||||
}
|
||||
|
||||
m := r.Mapper
|
||||
|
||||
fields := m.TraversalsByName(v.Type(), columns)
|
||||
// if we are not unsafe and are missing fields, return an error
|
||||
if f, err := missingFields(fields); err != nil && !r.unsafe {
|
||||
return fmt.Errorf("missing destination name %s", columns[f])
|
||||
}
|
||||
values := make([]interface{}, len(columns))
|
||||
|
||||
err = fieldsByTraversal(v, fields, values, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// scan into the struct field pointers and append to our results
|
||||
return r.Scan(values...)
|
||||
}
|
||||
|
||||
// StructScan a single Row into dest.
|
||||
func (r *Row) StructScan(dest interface{}) error {
|
||||
return r.scanAny(dest, true)
|
||||
}
|
||||
|
||||
// SliceScan a row, returning a []interface{} with values similar to MapScan.
|
||||
// This function is primarily intended for use where the number of columns
|
||||
// is not known. Because you can pass an []interface{} directly to Scan,
|
||||
// it's recommended that you do that as it will not have to allocate new
|
||||
// slices per row.
|
||||
func SliceScan(r ColScanner) ([]interface{}, error) {
|
||||
// ignore r.started, since we needn't use reflect for anything.
|
||||
columns, err := r.Columns()
|
||||
if err != nil {
|
||||
return []interface{}{}, err
|
||||
}
|
||||
|
||||
values := make([]interface{}, len(columns))
|
||||
for i := range values {
|
||||
values[i] = new(interface{})
|
||||
}
|
||||
|
||||
err = r.Scan(values...)
|
||||
|
||||
if err != nil {
|
||||
return values, err
|
||||
}
|
||||
|
||||
for i := range columns {
|
||||
values[i] = *(values[i].(*interface{}))
|
||||
}
|
||||
|
||||
return values, r.Err()
|
||||
}
|
||||
|
||||
// MapScan scans a single Row into the dest map[string]interface{}.
|
||||
// Use this to get results for SQL that might not be under your control
|
||||
// (for instance, if you're building an interface for an SQL server that
|
||||
// executes SQL from input). Please do not use this as a primary interface!
|
||||
// This will modify the map sent to it in place, so reuse the same map with
|
||||
// care. Columns which occur more than once in the result will overwrite
|
||||
// eachother!
|
||||
func MapScan(r ColScanner, dest map[string]interface{}) error {
|
||||
// ignore r.started, since we needn't use reflect for anything.
|
||||
columns, err := r.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
values := make([]interface{}, len(columns))
|
||||
for i := range values {
|
||||
values[i] = new(interface{})
|
||||
}
|
||||
|
||||
err = r.Scan(values...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, column := range columns {
|
||||
dest[column] = *(values[i].(*interface{}))
|
||||
}
|
||||
|
||||
return r.Err()
|
||||
}
|
||||
|
||||
type rowsi interface {
|
||||
Close() error
|
||||
Columns() ([]string, error)
|
||||
Err() error
|
||||
Next() bool
|
||||
Scan(...interface{}) error
|
||||
}
|
||||
|
||||
// structOnlyError returns an error appropriate for type when a non-scannable
|
||||
// struct is expected but something else is given
|
||||
func structOnlyError(t reflect.Type) error {
|
||||
isStruct := t.Kind() == reflect.Struct
|
||||
isScanner := reflect.PtrTo(t).Implements(_scannerInterface)
|
||||
if !isStruct {
|
||||
return fmt.Errorf("expected %s but got %s", reflect.Struct, t.Kind())
|
||||
}
|
||||
if isScanner {
|
||||
return fmt.Errorf("structscan expects a struct dest but the provided struct type %s implements scanner", t.Name())
|
||||
}
|
||||
return fmt.Errorf("expected a struct, but struct %s has no exported fields", t.Name())
|
||||
}
|
||||
|
||||
// scanAll scans all rows into a destination, which must be a slice of any
|
||||
// type. If the destination slice type is a Struct, then StructScan will be
|
||||
// used on each row. If the destination is some other kind of base type, then
|
||||
// each row must only have one column which can scan into that type. This
|
||||
// allows you to do something like:
|
||||
//
|
||||
// rows, _ := db.Query("select id from people;")
|
||||
// var ids []int
|
||||
// scanAll(rows, &ids, false)
|
||||
//
|
||||
// and ids will be a list of the id results. I realize that this is a desirable
|
||||
// interface to expose to users, but for now it will only be exposed via changes
|
||||
// to `Get` and `Select`. The reason that this has been implemented like this is
|
||||
// this is the only way to not duplicate reflect work in the new API while
|
||||
// maintaining backwards compatibility.
|
||||
func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
|
||||
var v, vp reflect.Value
|
||||
|
||||
value := reflect.ValueOf(dest)
|
||||
|
||||
// json.Unmarshal returns errors for these
|
||||
if value.Kind() != reflect.Ptr {
|
||||
return errors.New("must pass a pointer, not a value, to StructScan destination")
|
||||
}
|
||||
if value.IsNil() {
|
||||
return errors.New("nil pointer passed to StructScan destination")
|
||||
}
|
||||
direct := reflect.Indirect(value)
|
||||
|
||||
slice, err := baseType(value.Type(), reflect.Slice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isPtr := slice.Elem().Kind() == reflect.Ptr
|
||||
base := reflectx.Deref(slice.Elem())
|
||||
scannable := isScannable(base)
|
||||
|
||||
if structOnly && scannable {
|
||||
return structOnlyError(base)
|
||||
}
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if it's a base type make sure it only has 1 column; if not return an error
|
||||
if scannable && len(columns) > 1 {
|
||||
return fmt.Errorf("non-struct dest type %s with >1 columns (%d)", base.Kind(), len(columns))
|
||||
}
|
||||
|
||||
if !scannable {
|
||||
var values []interface{}
|
||||
var m *reflectx.Mapper
|
||||
|
||||
switch rows.(type) {
|
||||
case *Rows:
|
||||
m = rows.(*Rows).Mapper
|
||||
default:
|
||||
m = mapper()
|
||||
}
|
||||
|
||||
fields := m.TraversalsByName(base, columns)
|
||||
// if we are not unsafe and are missing fields, return an error
|
||||
if f, err := missingFields(fields); err != nil && !isUnsafe(rows) {
|
||||
return fmt.Errorf("missing destination name %s", columns[f])
|
||||
}
|
||||
values = make([]interface{}, len(columns))
|
||||
|
||||
for rows.Next() {
|
||||
// create a new struct type (which returns PtrTo) and indirect it
|
||||
vp = reflect.New(base)
|
||||
v = reflect.Indirect(vp)
|
||||
|
||||
err = fieldsByTraversal(v, fields, values, true)
|
||||
|
||||
// scan into the struct field pointers and append to our results
|
||||
err = rows.Scan(values...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPtr {
|
||||
direct.Set(reflect.Append(direct, vp))
|
||||
} else {
|
||||
direct.Set(reflect.Append(direct, v))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for rows.Next() {
|
||||
vp = reflect.New(base)
|
||||
err = rows.Scan(vp.Interface())
|
||||
// append
|
||||
if isPtr {
|
||||
direct.Set(reflect.Append(direct, vp))
|
||||
} else {
|
||||
direct.Set(reflect.Append(direct, reflect.Indirect(vp)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
// FIXME: StructScan was the very first bit of API in sqlx, and now unfortunately
|
||||
// it doesn't really feel like it's named properly. There is an incongruency
|
||||
// between this and the way that StructScan (which might better be ScanStruct
|
||||
// anyway) works on a rows object.
|
||||
|
||||
// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice.
|
||||
// StructScan will scan in the entire rows result, so if you need do not want to
|
||||
// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan.
|
||||
// If rows is sqlx.Rows, it will use its mapper, otherwise it will use the default.
|
||||
func StructScan(rows rowsi, dest interface{}) error {
|
||||
return scanAll(rows, dest, true)
|
||||
|
||||
}
|
||||
|
||||
// reflect helpers
|
||||
|
||||
func baseType(t reflect.Type, expected reflect.Kind) (reflect.Type, error) {
|
||||
t = reflectx.Deref(t)
|
||||
if t.Kind() != expected {
|
||||
return nil, fmt.Errorf("expected %s but got %s", expected, t.Kind())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// fieldsByName fills a values interface with fields from the passed value based
|
||||
// on the traversals in int. If ptrs is true, return addresses instead of values.
|
||||
// We write this instead of using FieldsByName to save allocations and map lookups
|
||||
// when iterating over many rows. Empty traversals will get an interface pointer.
|
||||
// Because of the necessity of requesting ptrs or values, it's considered a bit too
|
||||
// specialized for inclusion in reflectx itself.
|
||||
func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}, ptrs bool) error {
|
||||
v = reflect.Indirect(v)
|
||||
if v.Kind() != reflect.Struct {
|
||||
return errors.New("argument not a struct")
|
||||
}
|
||||
|
||||
for i, traversal := range traversals {
|
||||
if len(traversal) == 0 {
|
||||
values[i] = new(interface{})
|
||||
continue
|
||||
}
|
||||
f := reflectx.FieldByIndexes(v, traversal)
|
||||
if ptrs {
|
||||
values[i] = f.Addr().Interface()
|
||||
} else {
|
||||
values[i] = f.Interface()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func missingFields(transversals [][]int) (field int, err error) {
|
||||
for i, t := range transversals {
|
||||
if len(t) == 0 {
|
||||
return i, errors.New("missing field")
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
-1674
File diff suppressed because it is too large
Load Diff
-5
@@ -1,5 +0,0 @@
|
||||
# types
|
||||
|
||||
The types package provides some useful types which implement the `sql.Scanner`
|
||||
and `driver.Valuer` interfaces, suitable for use as scan and value targets with
|
||||
database/sql.
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// GzippedText is a []byte which transparently gzips data being submitted to
|
||||
// a database and ungzips data being Scanned from a database.
|
||||
type GzippedText []byte
|
||||
|
||||
// Value implements the driver.Valuer interface, gzipping the raw value of
|
||||
// this GzippedText.
|
||||
func (g GzippedText) Value() (driver.Value, error) {
|
||||
b := make([]byte, 0, len(g))
|
||||
buf := bytes.NewBuffer(b)
|
||||
w := gzip.NewWriter(buf)
|
||||
w.Write(g)
|
||||
w.Close()
|
||||
return buf.Bytes(), nil
|
||||
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface, ungzipping the value coming off
|
||||
// the wire and storing the raw result in the GzippedText.
|
||||
func (g *GzippedText) Scan(src interface{}) error {
|
||||
var source []byte
|
||||
switch src.(type) {
|
||||
case string:
|
||||
source = []byte(src.(string))
|
||||
case []byte:
|
||||
source = src.([]byte)
|
||||
default:
|
||||
return errors.New("Incompatible type for GzippedText")
|
||||
}
|
||||
reader, err := gzip.NewReader(bytes.NewReader(source))
|
||||
defer reader.Close()
|
||||
b, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*g = GzippedText(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// JSONText is a json.RawMessage, which is a []byte underneath.
|
||||
// Value() validates the json format in the source, and returns an error if
|
||||
// the json is not valid. Scan does no validation. JSONText additionally
|
||||
// implements `Unmarshal`, which unmarshals the json within to an interface{}
|
||||
type JSONText json.RawMessage
|
||||
|
||||
// MarshalJSON returns the *j as the JSON encoding of j.
|
||||
func (j *JSONText) MarshalJSON() ([]byte, error) {
|
||||
return *j, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *j to a copy of data
|
||||
func (j *JSONText) UnmarshalJSON(data []byte) error {
|
||||
if j == nil {
|
||||
return errors.New("JSONText: UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*j = append((*j)[0:0], data...)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Value returns j as a value. This does a validating unmarshal into another
|
||||
// RawMessage. If j is invalid json, it returns an error.
|
||||
func (j JSONText) Value() (driver.Value, error) {
|
||||
var m json.RawMessage
|
||||
var err = j.Unmarshal(&m)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return []byte(j), nil
|
||||
}
|
||||
|
||||
// Scan stores the src in *j. No validation is done.
|
||||
func (j *JSONText) Scan(src interface{}) error {
|
||||
var source []byte
|
||||
switch src.(type) {
|
||||
case string:
|
||||
source = []byte(src.(string))
|
||||
case []byte:
|
||||
source = src.([]byte)
|
||||
default:
|
||||
return errors.New("Incompatible type for JSONText")
|
||||
}
|
||||
*j = JSONText(append((*j)[0:0], source...))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
|
||||
func (j *JSONText) Unmarshal(v interface{}) error {
|
||||
return json.Unmarshal([]byte(*j), v)
|
||||
}
|
||||
|
||||
// Pretty printing for JSONText types
|
||||
func (j JSONText) String() string {
|
||||
return string(j)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGzipText(t *testing.T) {
|
||||
g := GzippedText("Hello, world")
|
||||
v, err := g.Value()
|
||||
if err != nil {
|
||||
t.Errorf("Was not expecting an error")
|
||||
}
|
||||
err = (&g).Scan(v)
|
||||
if err != nil {
|
||||
t.Errorf("Was not expecting an error")
|
||||
}
|
||||
if string(g) != "Hello, world" {
|
||||
t.Errorf("Was expecting the string we sent in (Hello World), got %s", string(g))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONText(t *testing.T) {
|
||||
j := JSONText(`{"foo": 1, "bar": 2}`)
|
||||
v, err := j.Value()
|
||||
if err != nil {
|
||||
t.Errorf("Was not expecting an error")
|
||||
}
|
||||
err = (&j).Scan(v)
|
||||
if err != nil {
|
||||
t.Errorf("Was not expecting an error")
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
j.Unmarshal(&m)
|
||||
|
||||
if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 {
|
||||
t.Errorf("Expected valid json but got some garbage instead? %#v", m)
|
||||
}
|
||||
|
||||
j = JSONText(`{"foo": 1, invalid, false}`)
|
||||
v, err = j.Value()
|
||||
if err == nil {
|
||||
t.Errorf("Was expecting invalid json to fail!")
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
## 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
@@ -1,8 +0,0 @@
|
||||
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.
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
# pq - A pure Go postgres driver for Go's database/sql package
|
||||
|
||||
[](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. A running PostgreSQL server is
|
||||
required, with the ability to log in. The default database to connect
|
||||
to test with is "pqgotest," but it can be overridden using environment
|
||||
variables.
|
||||
|
||||
Example:
|
||||
|
||||
PGHOST=/run/postgresql go test github.com/lib/pq
|
||||
|
||||
Optionally, a benchmark suite can be run as part of the tests:
|
||||
|
||||
PGHOST=/run/postgresql go test -bench .
|
||||
|
||||
## 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 (deafbybeheading)
|
||||
* 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)
|
||||
-435
@@ -1,435 +0,0 @@
|
||||
// +build go1.1
|
||||
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
var (
|
||||
selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
|
||||
selectSeriesQuery = "SELECT generate_series(1, 100)"
|
||||
)
|
||||
|
||||
func BenchmarkSelectString(b *testing.B) {
|
||||
var result string
|
||||
benchQuery(b, selectStringQuery, &result)
|
||||
}
|
||||
|
||||
func BenchmarkSelectSeries(b *testing.B) {
|
||||
var result int
|
||||
benchQuery(b, selectSeriesQuery, &result)
|
||||
}
|
||||
|
||||
func benchQuery(b *testing.B, query string, result interface{}) {
|
||||
b.StopTimer()
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchQueryLoop(b, db, query, result)
|
||||
}
|
||||
}
|
||||
|
||||
func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(result)
|
||||
if err != nil {
|
||||
b.Fatal("failed to scan", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reading from circularConn yields content[:prefixLen] once, followed by
|
||||
// content[prefixLen:] over and over again. It never returns EOF.
|
||||
type circularConn struct {
|
||||
content string
|
||||
prefixLen int
|
||||
pos int
|
||||
net.Conn // for all other net.Conn methods that will never be called
|
||||
}
|
||||
|
||||
func (r *circularConn) Read(b []byte) (n int, err error) {
|
||||
n = copy(b, r.content[r.pos:])
|
||||
r.pos += n
|
||||
if r.pos >= len(r.content) {
|
||||
r.pos = r.prefixLen
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
|
||||
|
||||
func (r *circularConn) Close() error { return nil }
|
||||
|
||||
func fakeConn(content string, prefixLen int) *conn {
|
||||
c := &circularConn{content: content, prefixLen: prefixLen}
|
||||
return &conn{buf: bufio.NewReader(c), c: c}
|
||||
}
|
||||
|
||||
// This benchmark is meant to be the same as BenchmarkSelectString, but takes
|
||||
// out some of the factors this package can't control. The numbers are less noisy,
|
||||
// but also the costs of network communication aren't accurately represented.
|
||||
func BenchmarkMockSelectString(b *testing.B) {
|
||||
b.StopTimer()
|
||||
// taken from a recorded run of BenchmarkSelectString
|
||||
// See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html
|
||||
const response = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"2\x00\x00\x00\x04" +
|
||||
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"3\x00\x00\x00\x04" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(response, 0)
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchMockQuery(b, c, selectStringQuery)
|
||||
}
|
||||
}
|
||||
|
||||
var seriesRowData = func() string {
|
||||
var buf bytes.Buffer
|
||||
for i := 1; i <= 100; i++ {
|
||||
digits := byte(2)
|
||||
if i >= 100 {
|
||||
digits = 3
|
||||
} else if i < 10 {
|
||||
digits = 1
|
||||
}
|
||||
buf.WriteString("D\x00\x00\x00")
|
||||
buf.WriteByte(10 + digits)
|
||||
buf.WriteString("\x00\x01\x00\x00\x00")
|
||||
buf.WriteByte(digits)
|
||||
buf.WriteString(strconv.Itoa(i))
|
||||
}
|
||||
return buf.String()
|
||||
}()
|
||||
|
||||
func BenchmarkMockSelectSeries(b *testing.B) {
|
||||
b.StopTimer()
|
||||
var response = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"2\x00\x00\x00\x04" +
|
||||
seriesRowData +
|
||||
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||
"Z\x00\x00\x00\x05I" +
|
||||
"3\x00\x00\x00\x04" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(response, 0)
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchMockQuery(b, c, selectSeriesQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func benchMockQuery(b *testing.B, c *conn, query string) {
|
||||
stmt, err := c.Prepare(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.Query(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var dest [1]driver.Value
|
||||
for {
|
||||
if err := rows.Next(dest[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPreparedSelectString(b *testing.B) {
|
||||
var result string
|
||||
benchPreparedQuery(b, selectStringQuery, &result)
|
||||
}
|
||||
|
||||
func BenchmarkPreparedSelectSeries(b *testing.B) {
|
||||
var result int
|
||||
benchPreparedQuery(b, selectSeriesQuery, &result)
|
||||
}
|
||||
|
||||
func benchPreparedQuery(b *testing.B, query string, result interface{}) {
|
||||
b.StopTimer()
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
stmt, err := db.Prepare(query)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedQueryLoop(b, db, stmt, result)
|
||||
}
|
||||
}
|
||||
|
||||
func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
|
||||
rows, err := stmt.Query()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if !rows.Next() {
|
||||
rows.Close()
|
||||
b.Fatal("no rows")
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&result)
|
||||
if err != nil {
|
||||
b.Fatal("failed to scan")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See the comment for BenchmarkMockSelectString.
|
||||
func BenchmarkMockPreparedSelectString(b *testing.B) {
|
||||
b.StopTimer()
|
||||
const parseResponse = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
const responses = parseResponse +
|
||||
"2\x00\x00\x00\x04" +
|
||||
"D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"C\x00\x00\x00\rSELECT 1\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(responses, len(parseResponse))
|
||||
|
||||
stmt, err := c.Prepare(selectStringQuery)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedMockQuery(b, c, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMockPreparedSelectSeries(b *testing.B) {
|
||||
b.StopTimer()
|
||||
const parseResponse = "1\x00\x00\x00\x04" +
|
||||
"t\x00\x00\x00\x06\x00\x00" +
|
||||
"T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
var responses = parseResponse +
|
||||
"2\x00\x00\x00\x04" +
|
||||
seriesRowData +
|
||||
"C\x00\x00\x00\x0fSELECT 100\x00" +
|
||||
"Z\x00\x00\x00\x05I"
|
||||
c := fakeConn(responses, len(parseResponse))
|
||||
|
||||
stmt, err := c.Prepare(selectSeriesQuery)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchPreparedMockQuery(b, c, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
|
||||
rows, err := stmt.Query(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var dest [1]driver.Value
|
||||
for {
|
||||
if err := rows.Next(dest[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeInt64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, int64(1234), oid.T_int8)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeFloat64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, 3.14159, oid.T_float8)
|
||||
}
|
||||
}
|
||||
|
||||
var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
func BenchmarkEncodeByteaHex(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
|
||||
}
|
||||
}
|
||||
func BenchmarkEncodeByteaEscape(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeBool(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, true, oid.T_bool)
|
||||
}
|
||||
}
|
||||
|
||||
var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
|
||||
|
||||
func BenchmarkEncodeTimestamptz(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz)
|
||||
}
|
||||
}
|
||||
|
||||
var testIntBytes = []byte("1234")
|
||||
|
||||
func BenchmarkDecodeInt64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
var testFloatBytes = []byte("3.14159")
|
||||
|
||||
func BenchmarkDecodeFloat64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
var testBoolBytes = []byte{'t'}
|
||||
|
||||
func BenchmarkDecodeBool(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeBool(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
rows, err := db.Query("select true")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
|
||||
|
||||
func BenchmarkDecodeTimestamptz(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
|
||||
oldProcs := runtime.GOMAXPROCS(0)
|
||||
defer runtime.GOMAXPROCS(oldProcs)
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
globalLocationCache = newLocationCache()
|
||||
|
||||
f := func(wg *sync.WaitGroup, loops int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < loops; i++ {
|
||||
decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
b.ResetTimer()
|
||||
for j := 0; j < 10; j++ {
|
||||
wg.Add(1)
|
||||
go f(wg, b.N/10)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkLocationCache(b *testing.B) {
|
||||
globalLocationCache = newLocationCache()
|
||||
for i := 0; i < b.N; i++ {
|
||||
globalLocationCache.getLocation(rand.Intn(10000))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLocationCacheMultiThread(b *testing.B) {
|
||||
oldProcs := runtime.GOMAXPROCS(0)
|
||||
defer runtime.GOMAXPROCS(oldProcs)
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
globalLocationCache = newLocationCache()
|
||||
|
||||
f := func(wg *sync.WaitGroup, loops int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < loops; i++ {
|
||||
globalLocationCache.getLocation(rand.Intn(10000))
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
b.ResetTimer()
|
||||
for j := 0; j < 10; j++ {
|
||||
wg.Add(1)
|
||||
go f(wg, b.N/10)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Stress test the performance of parsing results from the wire.
|
||||
func BenchmarkResultParsing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
_, err := db.Exec("BEGIN")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
res, err := db.Query("SELECT generate_series(1, 50000)")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
res.Close()
|
||||
}
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
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)
|
||||
}
|
||||
-1862
File diff suppressed because it is too large
Load Diff
-1434
File diff suppressed because it is too large
Load Diff
-267
@@ -1,267 +0,0 @@
|
||||
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")
|
||||
)
|
||||
|
||||
// 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 {
|
||||
cn.bad = true
|
||||
errorf("unexpected ReadyForQuery in response to COPY")
|
||||
}
|
||||
cn.processReadyForQuery(r)
|
||||
return nil, err
|
||||
default:
|
||||
cn.bad = true
|
||||
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:
|
||||
cn.bad = true
|
||||
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.cn.bad = true
|
||||
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.cn.bad = true
|
||||
ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
|
||||
ci.done <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.cn.bad {
|
||||
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.cn.bad {
|
||||
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
|
||||
|
||||
if ci.isErrorSet() {
|
||||
err = ci.err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
-465
@@ -1,465 +0,0 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCopyInStmt(t *testing.T) {
|
||||
var stmt string
|
||||
stmt = CopyIn("table name")
|
||||
if stmt != `COPY "table name" () FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyIn("table name", "column 1", "column 2")
|
||||
if stmt != `COPY "table name" ("column 1", "column 2") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyIn(`table " name """`, `co"lumn""`)
|
||||
if stmt != `COPY "table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInSchemaStmt(t *testing.T) {
|
||||
var stmt string
|
||||
stmt = CopyInSchema("schema name", "table name")
|
||||
if stmt != `COPY "schema name"."table name" () FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyInSchema("schema name", "table name", "column 1", "column 2")
|
||||
if stmt != `COPY "schema name"."table name" ("column 1", "column 2") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
|
||||
stmt = CopyInSchema(`schema " name """`, `table " name """`, `co"lumn""`)
|
||||
if stmt != `COPY "schema "" name """"""".`+
|
||||
`"table "" name """"""" ("co""lumn""""") FROM STDIN` {
|
||||
t.Fatal(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInMultipleValues(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
longString := strings.Repeat("#", 500)
|
||||
|
||||
for i := 0; i < 500; i++ {
|
||||
_, err = stmt.Exec(int64(i), longString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 500 {
|
||||
t.Fatalf("expected 500 items, not %d", num)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInRaiseStmtTrigger(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
var exists int
|
||||
err := db.QueryRow("SELECT 1 FROM pg_language WHERE lanname = 'plpgsql'").Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
t.Skip("language PL/PgSQL does not exist; skipping TestCopyInRaiseStmtTrigger")
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec(`
|
||||
CREATE OR REPLACE FUNCTION pg_temp.temptest()
|
||||
RETURNS trigger AS
|
||||
$BODY$ begin
|
||||
raise notice 'Hello world';
|
||||
return new;
|
||||
end $BODY$
|
||||
LANGUAGE plpgsql`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec(`
|
||||
CREATE TRIGGER temptest_trigger
|
||||
BEFORE INSERT
|
||||
ON temp
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE pg_temp.temptest()`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
longString := strings.Repeat("#", 500)
|
||||
|
||||
_, err = stmt.Exec(int64(1), longString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 1 {
|
||||
t.Fatalf("expected 1 items, not %d", num)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInTypes(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER, text VARCHAR, blob BYTEA, nothing VARCHAR)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "num", "text", "blob", "nothing"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(int64(1234567890), "Héllö\n ☃!\r\t\\", []byte{0, 255, 9, 10, 13}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
var text string
|
||||
var blob []byte
|
||||
var nothing sql.NullString
|
||||
|
||||
err = txn.QueryRow("SELECT * FROM temp").Scan(&num, &text, &blob, ¬hing)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if num != 1234567890 {
|
||||
t.Fatal("unexpected result", num)
|
||||
}
|
||||
if text != "Héllö\n ☃!\r\t\\" {
|
||||
t.Fatal("unexpected result", text)
|
||||
}
|
||||
if bytes.Compare(blob, []byte{0, 255, 9, 10, 13}) != 0 {
|
||||
t.Fatal("unexpected result", blob)
|
||||
}
|
||||
if nothing.Valid {
|
||||
t.Fatal("unexpected result", nothing.String)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInWrongType(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "num"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec("Héllö\n ☃!\r\t\\")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if pge := err.(*Error); pge.Code.Name() != "invalid_text_representation" {
|
||||
t.Fatalf("expected 'invalid input syntax for integer' error, got %s (%+v)", pge.Code.Name(), pge)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyOutsideOfTxnError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.Prepare(CopyIn("temp", "num"))
|
||||
if err == nil {
|
||||
t.Fatal("COPY outside of transaction did not return an error")
|
||||
}
|
||||
if err != errCopyNotSupportedOutsideTxn {
|
||||
t.Fatalf("expected %s, got %s", err, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyInBinaryError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = txn.Prepare("COPY temp (num) FROM STDIN WITH binary")
|
||||
if err != errBinaryCopyNotSupported {
|
||||
t.Fatalf("expected %s, got %+v", errBinaryCopyNotSupported, err)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = txn.Prepare("COPY temp (num) TO STDOUT")
|
||||
if err != errCopyToNotSupported {
|
||||
t.Fatalf("expected %s, got %+v", errCopyToNotSupported, err)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopySyntaxError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Prepare("COPY ")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if pge := err.(*Error); pge.Code.Name() != "syntax_error" {
|
||||
t.Fatalf("expected syntax error, got %s (%+v)", pge.Code.Name(), pge)
|
||||
}
|
||||
// check that the protocol is in a valid state
|
||||
err = txn.Rollback()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for connection errors in copyin.resploop()
|
||||
func TestCopyRespLoopConnectionError(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
var pid int
|
||||
err = txn.QueryRow("SELECT pg_backend_pid()").Scan(&pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int)")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = db.Exec("SELECT pg_terminate_backend($1)", pid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if getServerVersion(t, db) < 90500 {
|
||||
// We have to try and send something over, since postgres before
|
||||
// version 9.5 won't process SIGTERMs while it's waiting for
|
||||
// CopyData/CopyEnd messages; see tcop/postgres.c.
|
||||
_, err = stmt.Exec(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, err = stmt.Exec()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
pge, ok := err.(*Error)
|
||||
if !ok {
|
||||
if err == driver.ErrBadConn {
|
||||
// likely an EPIPE
|
||||
} else {
|
||||
t.Fatalf("expected *pq.Error or driver.ErrBadConn, got %+#v", err)
|
||||
}
|
||||
} else if pge.Code.Name() != "admin_shutdown" {
|
||||
t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name())
|
||||
}
|
||||
|
||||
_ = stmt.Close()
|
||||
}
|
||||
|
||||
func BenchmarkCopyIn(b *testing.B) {
|
||||
db := openTestConn(b)
|
||||
defer db.Close()
|
||||
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = stmt.Exec(int64(i), "hello world!")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var num int
|
||||
err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if num != b.N {
|
||||
b.Fatalf("expected %d items, not %d", b.N, num)
|
||||
}
|
||||
}
|
||||
-212
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
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() {
|
||||
db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")
|
||||
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:
|
||||
|
||||
db, err := sql.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full")
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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/listen_example.
|
||||
|
||||
*/
|
||||
package pq
|
||||
-579
@@ -1,579 +0,0 @@
|
||||
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{} {
|
||||
if f == formatBinary {
|
||||
return binaryDecode(parameterStatus, s, typ)
|
||||
} else {
|
||||
return textDecode(parameterStatus, s, typ)
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
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_bytea:
|
||||
return parseBytea(s)
|
||||
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 invalidTimestampErr = errors.New("invalid timestamp")
|
||||
|
||||
type timestampParser struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *timestampParser) expect(str, char string, pos int) {
|
||||
if p.err != nil {
|
||||
return
|
||||
}
|
||||
if pos+1 > len(str) {
|
||||
p.err = invalidTimestampErr
|
||||
return
|
||||
}
|
||||
if c := str[pos : pos+1]; 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 = invalidTimestampErr
|
||||
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 *locationCache = 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"
|
||||
)
|
||||
|
||||
/*
|
||||
* 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)
|
||||
|
||||
var hour, minute, second int
|
||||
if len(str) > monSep+len("01-01")+1 {
|
||||
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+1 <= len(str) && str[remainderIdx:remainderIdx+1] == "." {
|
||||
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+1 <= len(str) && (str[tzStart:tzStart+1] == "-" || str[tzStart:tzStart+1] == "+") {
|
||||
// time zone separator is always '-' or '+' (UTC is +00)
|
||||
var tzSign int
|
||||
if c := str[tzStart : tzStart+1]; c == "-" {
|
||||
tzSign = -1
|
||||
} else if c == "+" {
|
||||
tzSign = +1
|
||||
} else {
|
||||
return time.Time{}, fmt.Errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
|
||||
}
|
||||
tzHours := p.mustAtoi(str, tzStart+1, tzStart+3)
|
||||
remainderIdx += 3
|
||||
var tzMin, tzSec int
|
||||
if tzStart+4 <= len(str) && str[tzStart+3:tzStart+4] == ":" {
|
||||
tzMin = p.mustAtoi(str, tzStart+4, tzStart+6)
|
||||
remainderIdx += 3
|
||||
}
|
||||
if tzStart+7 <= len(str) && str[tzStart+6:tzStart+7] == ":" {
|
||||
tzSec = p.mustAtoi(str, tzStart+7, tzStart+9)
|
||||
remainderIdx += 3
|
||||
}
|
||||
tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
|
||||
}
|
||||
var isoYear int
|
||||
if remainderIdx+3 <= len(str) && str[remainderIdx:remainderIdx+3] == " BC" {
|
||||
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(time.RFC3339Nano))
|
||||
|
||||
_, 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) {
|
||||
if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
|
||||
// bytea_output = hex
|
||||
s = s[2:] // trim off leading "\\x"
|
||||
result = make([]byte, hex.DecodedLen(len(s)))
|
||||
_, err := hex.Decode(result, s)
|
||||
if err != nil {
|
||||
errorf("%s", err)
|
||||
}
|
||||
} 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 {
|
||||
errorf("invalid bytea sequence %v", s)
|
||||
}
|
||||
r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
|
||||
if err != nil {
|
||||
errorf("could not parse bytea value: %s", err.Error())
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
-727
@@ -1,727 +0,0 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq/oid"
|
||||
)
|
||||
|
||||
func TestScanTimestamp(t *testing.T) {
|
||||
var nt NullTime
|
||||
tn := time.Now()
|
||||
nt.Scan(tn)
|
||||
if !nt.Valid {
|
||||
t.Errorf("Expected Valid=false")
|
||||
}
|
||||
if nt.Time != tn {
|
||||
t.Errorf("Time value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanNilTimestamp(t *testing.T) {
|
||||
var nt NullTime
|
||||
nt.Scan(nil)
|
||||
if nt.Valid {
|
||||
t.Errorf("Expected Valid=false")
|
||||
}
|
||||
}
|
||||
|
||||
var timeTests = []struct {
|
||||
str string
|
||||
timeval time.Time
|
||||
}{
|
||||
{"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
|
||||
{"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", -(7*60*60+42*60)))},
|
||||
{"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", -(7*60*60+30*60+9)))},
|
||||
{"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
|
||||
time.FixedZone("", 7*60*60))},
|
||||
{"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
|
||||
{"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
|
||||
time.FixedZone("", -7*60*60))},
|
||||
{"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"0001-02-03 04:05:06.123 BC", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
|
||||
{"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"0002-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
|
||||
{"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
|
||||
{"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
{"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
|
||||
}
|
||||
|
||||
// Test that parsing the string results in the expected value.
|
||||
func TestParseTs(t *testing.T) {
|
||||
for i, tt := range timeTests {
|
||||
val, err := ParseTimestamp(nil, tt.str)
|
||||
if err != nil {
|
||||
t.Errorf("%d: got error: %v", i, err)
|
||||
} else if val.String() != tt.timeval.String() {
|
||||
t.Errorf("%d: expected to parse %q into %q; got %q",
|
||||
i, tt.str, tt.timeval, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var timeErrorTests = []string{
|
||||
"2001",
|
||||
"2001-2-03",
|
||||
"2001-02-3",
|
||||
"2001-02-03 ",
|
||||
"2001-02-03 04",
|
||||
"2001-02-03 04:",
|
||||
"2001-02-03 04:05",
|
||||
"2001-02-03 04:05:",
|
||||
"2001-02-03 04:05:6",
|
||||
"2001-02-03 04:05:06.123 B",
|
||||
}
|
||||
|
||||
// Test that parsing the string results in an error.
|
||||
func TestParseTsErrors(t *testing.T) {
|
||||
for i, tt := range timeErrorTests {
|
||||
_, err := ParseTimestamp(nil, tt)
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected an error from parsing: %v", i, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now test that sending the value into the database and parsing it back
|
||||
// returns the same time.Time value.
|
||||
func TestEncodeAndParseTs(t *testing.T) {
|
||||
db, err := openTestConnConninfo("timezone='Etc/UTC'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for i, tt := range timeTests {
|
||||
var dbstr string
|
||||
err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
|
||||
if err != nil {
|
||||
t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := ParseTimestamp(nil, dbstr)
|
||||
if err != nil {
|
||||
t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
|
||||
continue
|
||||
}
|
||||
val = val.In(tt.timeval.Location())
|
||||
if val.String() != tt.timeval.String() {
|
||||
t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatTimeTests = []struct {
|
||||
time time.Time
|
||||
expected string
|
||||
}{
|
||||
{time.Time{}, "0001-01-01T00:00:00Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03T04:05:06.123456789Z"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03T04:05:06.123456789+02:00"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03T04:05:06.123456789-06:00"},
|
||||
{time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03T04:05:06-07:30:09"},
|
||||
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00"},
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04:05:06.123456789-06:00"},
|
||||
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "0001-02-03T04:05:06.123456789Z BC"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "0001-02-03T04:05:06.123456789+02:00 BC"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "0001-02-03T04:05:06.123456789-06:00 BC"},
|
||||
|
||||
{time.Date(1, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03T04:05:06-07:30:09"},
|
||||
{time.Date(0, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "0001-02-03T04:05:06-07:30:09 BC"},
|
||||
}
|
||||
|
||||
func TestFormatTs(t *testing.T) {
|
||||
for i, tt := range formatTimeTests {
|
||||
val := string(formatTs(tt.time))
|
||||
if val != tt.expected {
|
||||
t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampWithTimeZone(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// try several different locations, all included in Go's zoneinfo.zip
|
||||
for _, locName := range []string{
|
||||
"UTC",
|
||||
"America/Chicago",
|
||||
"America/New_York",
|
||||
"Australia/Darwin",
|
||||
"Australia/Perth",
|
||||
} {
|
||||
loc, err := time.LoadLocation(locName)
|
||||
if err != nil {
|
||||
t.Logf("Could not load time zone %s - skipping", locName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Postgres timestamps have a resolution of 1 microsecond, so don't
|
||||
// use the full range of the Nanosecond argument
|
||||
refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
|
||||
|
||||
for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
|
||||
// Switch Postgres's timezone to test different output timestamp formats
|
||||
_, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var gotTime time.Time
|
||||
row := tx.QueryRow("select $1::timestamp with time zone", refTime)
|
||||
err = row.Scan(&gotTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !refTime.Equal(gotTime) {
|
||||
t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
|
||||
}
|
||||
|
||||
// check that the time zone is set correctly based on TimeZone
|
||||
pgLoc, err := time.LoadLocation(pgTimeZone)
|
||||
if err != nil {
|
||||
t.Logf("Could not load time zone %s - skipping", pgLoc)
|
||||
continue
|
||||
}
|
||||
translated := refTime.In(pgLoc)
|
||||
if translated.String() != gotTime.String() {
|
||||
t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampWithOutTimezone(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
test := func(ts, pgts string) {
|
||||
r, err := db.Query("SELECT $1::timestamp", pgts)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not run query: %v", err)
|
||||
}
|
||||
|
||||
n := r.Next()
|
||||
|
||||
if n != true {
|
||||
t.Fatal("Expected at least one row")
|
||||
}
|
||||
|
||||
var result time.Time
|
||||
err = r.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatalf("Did not expect error scanning row: %v", err)
|
||||
}
|
||||
|
||||
expected, err := time.Parse(time.RFC3339, ts)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not parse test time literal: %v", err)
|
||||
}
|
||||
|
||||
if !result.Equal(expected) {
|
||||
t.Fatalf("Expected time to match %v: got mismatch %v",
|
||||
expected, result)
|
||||
}
|
||||
|
||||
n = r.Next()
|
||||
if n != false {
|
||||
t.Fatal("Expected only one row")
|
||||
}
|
||||
}
|
||||
|
||||
test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
|
||||
|
||||
// Test higher precision time
|
||||
test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
|
||||
}
|
||||
|
||||
func TestInfinityTimestamp(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
var err error
|
||||
var resultT time.Time
|
||||
|
||||
expectedErrorStrPrefix := `sql: Scan error on column index 0: unsupported`
|
||||
type testCases []struct {
|
||||
Query string
|
||||
Param string
|
||||
ExpectedErrStrPrefix string
|
||||
ExpectedVal interface{}
|
||||
}
|
||||
tc := testCases{
|
||||
{"SELECT $1::timestamp", "-infinity", expectedErrorStrPrefix, "-infinity"},
|
||||
{"SELECT $1::timestamptz", "-infinity", expectedErrorStrPrefix, "-infinity"},
|
||||
{"SELECT $1::timestamp", "infinity", expectedErrorStrPrefix, "infinity"},
|
||||
{"SELECT $1::timestamptz", "infinity", expectedErrorStrPrefix, "infinity"},
|
||||
}
|
||||
// try to assert []byte to time.Time
|
||||
for _, q := range tc {
|
||||
err = db.QueryRow(q.Query, q.Param).Scan(&resultT)
|
||||
if !strings.HasPrefix(err.Error(), q.ExpectedErrStrPrefix) {
|
||||
t.Errorf("Scanning -/+infinity, expected error to have prefix %q, got %q", q.ExpectedErrStrPrefix, err)
|
||||
}
|
||||
}
|
||||
// yield []byte
|
||||
for _, q := range tc {
|
||||
var resultI interface{}
|
||||
err = db.QueryRow(q.Query, q.Param).Scan(&resultI)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -/+infinity, expected no error, got %q", err)
|
||||
}
|
||||
result, ok := resultI.([]byte)
|
||||
if !ok {
|
||||
t.Errorf("Scanning -/+infinity, expected []byte, got %#v", resultI)
|
||||
}
|
||||
if string(result) != q.ExpectedVal {
|
||||
t.Errorf("Scanning -/+infinity, expected %q, got %q", q.ExpectedVal, result)
|
||||
}
|
||||
}
|
||||
|
||||
y1500 := time.Date(1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
y2500 := time.Date(2500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
EnableInfinityTs(y1500, y2500)
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp", "infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y2500) {
|
||||
t.Errorf("Scanning infinity, expected %q, got %q", y2500, resultT)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamptz", "infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y2500) {
|
||||
t.Errorf("Scanning Infinity, expected time %q, got %q", y2500, resultT.String())
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp", "-infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y1500) {
|
||||
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamptz", "-infinity").Scan(&resultT)
|
||||
if err != nil {
|
||||
t.Errorf("Scanning -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if !resultT.Equal(y1500) {
|
||||
t.Errorf("Scanning -infinity, expected time %q, got %q", y1500, resultT.String())
|
||||
}
|
||||
|
||||
y_1500 := time.Date(-1500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
y11500 := time.Date(11500, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
var s string
|
||||
err = db.QueryRow("SELECT $1::timestamp::text", y_1500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "-infinity" {
|
||||
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||
}
|
||||
err = db.QueryRow("SELECT $1::timestamptz::text", y_1500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding -infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "-infinity" {
|
||||
t.Errorf("Encoding -infinity, expected %q, got %q", "-infinity", s)
|
||||
}
|
||||
|
||||
err = db.QueryRow("SELECT $1::timestamp::text", y11500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "infinity" {
|
||||
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||
}
|
||||
err = db.QueryRow("SELECT $1::timestamptz::text", y11500).Scan(&s)
|
||||
if err != nil {
|
||||
t.Errorf("Encoding infinity, expected no error, got %q", err)
|
||||
}
|
||||
if s != "infinity" {
|
||||
t.Errorf("Encoding infinity, expected %q, got %q", "infinity", s)
|
||||
}
|
||||
|
||||
disableInfinityTs()
|
||||
|
||||
var panicErrorString string
|
||||
func() {
|
||||
defer func() {
|
||||
panicErrorString, _ = recover().(string)
|
||||
}()
|
||||
EnableInfinityTs(y2500, y1500)
|
||||
}()
|
||||
if panicErrorString != infinityTsNegativeMustBeSmaller {
|
||||
t.Errorf("Expected error, %q, got %q", infinityTsNegativeMustBeSmaller, panicErrorString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringWithNul(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
hello0world := string("hello\x00world")
|
||||
_, err := db.Query("SELECT $1::text", &hello0world)
|
||||
if err == nil {
|
||||
t.Fatal("Postgres accepts a string with nul in it; " +
|
||||
"injection attacks may be plausible")
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteSliceToText(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte("hello world")
|
||||
row := db.QueryRow("SELECT $1::text", b)
|
||||
|
||||
var result []byte
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(result) != string(b) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToBytea(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := "hello world"
|
||||
row := db.QueryRow("SELECT $1::bytea", b)
|
||||
|
||||
var result []byte
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(result, []byte(b)) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextByteSliceToUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")
|
||||
row := db.QueryRow("SELECT $1::uuid", b)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22P03" {
|
||||
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != string(b) {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryByteSlicetoUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
b := []byte{'\xa0', '\xee', '\xbc', '\x99',
|
||||
'\x9c', '\x0b',
|
||||
'\x4e', '\xf8',
|
||||
'\xbb', '\x00', '\x6b',
|
||||
'\xb9', '\xbd', '\x38', '\x0a', '\x11'}
|
||||
row := db.QueryRow("SELECT $1::uuid", b)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != string("a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11") {
|
||||
t.Fatalf("expected %v but got %v", b, result)
|
||||
}
|
||||
} else {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22021" {
|
||||
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringToUUID(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
s := "a0eebc99-9c0b-4ef8-bb00-6bb9bd380a11"
|
||||
row := db.QueryRow("SELECT $1::uuid", s)
|
||||
|
||||
var result string
|
||||
err := row.Scan(&result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result != s {
|
||||
t.Fatalf("expected %v but got %v", s, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextByteSliceToInt(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
expected := 12345678
|
||||
b := []byte(fmt.Sprintf("%d", expected))
|
||||
row := db.QueryRow("SELECT $1::int", b)
|
||||
|
||||
var result int
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22P03" {
|
||||
t.Fatalf("Expected to get invalid binary encoding error (22P03), got %s", pqErr.Code)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("expected %v but got %v", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryByteSliceToInt(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
expected := 12345678
|
||||
b := []byte{'\x00', '\xbc', '\x61', '\x4e'}
|
||||
row := db.QueryRow("SELECT $1::int", b)
|
||||
|
||||
var result int
|
||||
err := row.Scan(&result)
|
||||
if forceBinaryParameters() {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result != expected {
|
||||
t.Fatalf("expected %v but got %v", expected, result)
|
||||
}
|
||||
} else {
|
||||
pqErr := err.(*Error)
|
||||
if pqErr == nil {
|
||||
t.Errorf("Expected to get error")
|
||||
} else if pqErr.Code != "22021" {
|
||||
t.Fatalf("Expected to get invalid byte sequence for encoding error (22021), got %s", pqErr.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaOutputFormatEncoding(t *testing.T) {
|
||||
input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
|
||||
want := []byte("\\x5c78000102fffe6162636465666730313233")
|
||||
got := encode(¶meterStatus{serverVersion: 90000}, input, oid.T_bytea)
|
||||
if !bytes.Equal(want, got) {
|
||||
t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
|
||||
}
|
||||
|
||||
want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
|
||||
got = encode(¶meterStatus{serverVersion: 84000}, input, oid.T_bytea)
|
||||
if !bytes.Equal(want, got) {
|
||||
t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByteaOutputFormats(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
// skip
|
||||
return
|
||||
}
|
||||
|
||||
testByteaOutputFormat := func(f string, usePrepared bool) {
|
||||
expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
|
||||
sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
|
||||
|
||||
var data []byte
|
||||
|
||||
// use a txn to avoid relying on getting the same connection
|
||||
txn, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.Rollback()
|
||||
|
||||
_, err = txn.Exec("SET LOCAL bytea_output TO " + f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var rows *sql.Rows
|
||||
var stmt *sql.Stmt
|
||||
if usePrepared {
|
||||
stmt, err = txn.Prepare(sqlQuery)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows, err = stmt.Query()
|
||||
} else {
|
||||
// use Query; QueryRow would hide the actual error
|
||||
rows, err = txn.Query(sqlQuery)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !rows.Next() {
|
||||
if rows.Err() != nil {
|
||||
t.Fatal(rows.Err())
|
||||
}
|
||||
t.Fatal("shouldn't happen")
|
||||
}
|
||||
err = rows.Scan(&data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = rows.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stmt != nil {
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(data, expectedData) {
|
||||
t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
|
||||
}
|
||||
}
|
||||
|
||||
testByteaOutputFormat("hex", false)
|
||||
testByteaOutputFormat("escape", false)
|
||||
testByteaOutputFormat("hex", true)
|
||||
testByteaOutputFormat("escape", true)
|
||||
}
|
||||
|
||||
func TestAppendEncodedText(t *testing.T) {
|
||||
var buf []byte
|
||||
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10))
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, 42.0000000001)
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, "hello\tworld")
|
||||
buf = append(buf, '\t')
|
||||
buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
|
||||
|
||||
if string(buf) != "10\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
|
||||
t.Fatal(string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendEscapedText(t *testing.T) {
|
||||
if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendEscapedTextExistingBuffer(t *testing.T) {
|
||||
var buf []byte
|
||||
buf = []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
buf = []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
buf = []byte("123\t")
|
||||
if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
|
||||
t.Fatal(string(esc))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendEscapedText(b *testing.B) {
|
||||
longString := ""
|
||||
for i := 0; i < 100; i++ {
|
||||
longString += "123456789\n"
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
appendEscapedText(nil, longString)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
|
||||
longString := ""
|
||||
for i := 0; i < 100; i++ {
|
||||
longString += "1234567890"
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
appendEscapedText(nil, longString)
|
||||
}
|
||||
}
|
||||
-508
@@ -1,508 +0,0 @@
|
||||
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",
|
||||
"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...)))
|
||||
}
|
||||
|
||||
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:
|
||||
*err = driver.ErrBadConn
|
||||
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
|
||||
}
|
||||
}
|
||||
-782
@@ -1,782 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Send a LISTEN query to the server. See ExecSimpleQuery.
|
||||
func (l *ListenerConn) Listen(channel string) (bool, error) {
|
||||
return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
|
||||
}
|
||||
|
||||
// Send an UNLISTEN query to the server. See ExecSimpleQuery.
|
||||
func (l *ListenerConn) Unlisten(channel string) (bool, error) {
|
||||
return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
|
||||
}
|
||||
|
||||
// Send `UNLISTEN *` 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
|
||||
}
|
||||
|
||||
// Execute 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
|
||||
var ErrChannelNotOpen = errors.New("pq: channel is not open")
|
||||
|
||||
type ListenerEventType int
|
||||
|
||||
const (
|
||||
// Emitted only when the database connection has been initially
|
||||
// initialized. err will always be nil.
|
||||
ListenerEventConnected ListenerEventType = iota
|
||||
|
||||
// Emitted after a database connection has been lost, either because of an
|
||||
// error or because Close has been called. err will be set to the reason
|
||||
// the database connection was lost.
|
||||
ListenerEventDisconnected
|
||||
|
||||
// Emitted after a database connection has been re-established after
|
||||
// connection loss. err will always be nil. After this event has been
|
||||
// emitted, a nil pq.Notification is sent on the Listener.Notify channel.
|
||||
ListenerEventReconnected
|
||||
|
||||
// Emitted after a connection to the database was attempted, but failed.
|
||||
// err will be set to an error describing why the connection attempt did
|
||||
// not succeed.
|
||||
ListenerEventConnectionAttemptFailed
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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
|
||||
}()
|
||||
|
||||
// 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
|
||||
|
||||
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(nextReconnect.Sub(time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) listenerMain() {
|
||||
l.listenerConnLoop()
|
||||
close(l.Notify)
|
||||
}
|
||||
-574
@@ -1,574 +0,0 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errNilNotification = errors.New("nil notification")
|
||||
|
||||
func expectNotification(t *testing.T, ch <-chan *Notification, relname string, extra string) error {
|
||||
select {
|
||||
case n := <-ch:
|
||||
if n == nil {
|
||||
return errNilNotification
|
||||
}
|
||||
if n.Channel != relname || n.Extra != extra {
|
||||
return fmt.Errorf("unexpected notification %v", n)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(1500 * time.Millisecond):
|
||||
return fmt.Errorf("timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
|
||||
select {
|
||||
case n := <-ch:
|
||||
return fmt.Errorf("unexpected notification %v", n)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error {
|
||||
select {
|
||||
case e := <-eventch:
|
||||
if e != et {
|
||||
return fmt.Errorf("unexpected event %v", e)
|
||||
}
|
||||
return nil
|
||||
case <-time.After(1500 * time.Millisecond):
|
||||
panic("expectEvent timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
|
||||
select {
|
||||
case e := <-eventch:
|
||||
return fmt.Errorf("unexpected event %v", e)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) {
|
||||
datname := os.Getenv("PGDATABASE")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
|
||||
if datname == "" {
|
||||
os.Setenv("PGDATABASE", "pqgotest")
|
||||
}
|
||||
|
||||
if sslmode == "" {
|
||||
os.Setenv("PGSSLMODE", "disable")
|
||||
}
|
||||
|
||||
notificationChan := make(chan *Notification)
|
||||
l, err := NewListenerConn("", notificationChan)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return l, notificationChan
|
||||
}
|
||||
|
||||
func TestNewListenerConn(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
}
|
||||
|
||||
func TestConnListen(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnUnlisten(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err = l.Unlisten("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, channel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnUnlistenAll(t *testing.T) {
|
||||
l, channel := newTestListenerConn(t)
|
||||
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err = l.UnlistenAll()
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, channel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnClose(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != errListenerConnClosed {
|
||||
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnPing(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
err := l.Ping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Ping()
|
||||
if err != errListenerConnClosed {
|
||||
t.Fatalf("expected errListenerConnClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for deadlock where a query fails while another one is queued
|
||||
func TestConnExecDeadlock(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||
wg.Done()
|
||||
}()
|
||||
runtime.Gosched()
|
||||
go func() {
|
||||
l.ExecSimpleQuery("SELECT 1")
|
||||
wg.Done()
|
||||
}()
|
||||
// give the two goroutines some time to get into position
|
||||
runtime.Gosched()
|
||||
// calls Close on the net.Conn; equivalent to a network failure
|
||||
l.Close()
|
||||
|
||||
var done int32 = 0
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
if atomic.LoadInt32(&done) != 1 {
|
||||
panic("timed out")
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
atomic.StoreInt32(&done, 1)
|
||||
}
|
||||
|
||||
// Test for ListenerConn being closed while a slow query is executing
|
||||
func TestListenerConnCloseWhileQueryIsExecuting(t *testing.T) {
|
||||
l, _ := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
sent, err := l.ExecSimpleQuery("SELECT pg_sleep(60)")
|
||||
if sent {
|
||||
panic("expected sent=false")
|
||||
}
|
||||
// could be any of a number of errors
|
||||
if err == nil {
|
||||
panic("expected error")
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
// give the above goroutine some time to get into position
|
||||
runtime.Gosched()
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var done int32 = 0
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
if atomic.LoadInt32(&done) != 1 {
|
||||
panic("timed out")
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
atomic.StoreInt32(&done, 1)
|
||||
}
|
||||
|
||||
func TestNotifyExtra(t *testing.T) {
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
if getServerVersion(t, db) < 90000 {
|
||||
t.Skip("skipping NOTIFY payload test since the server does not appear to support it")
|
||||
}
|
||||
|
||||
l, channel := newTestListenerConn(t)
|
||||
defer l.Close()
|
||||
|
||||
ok, err := l.Listen("notify_test")
|
||||
if !ok || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_test, 'something'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, channel, "notify_test", "something")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// create a new test listener and also set the timeouts
|
||||
func newTestListenerTimeout(t *testing.T, min time.Duration, max time.Duration) (*Listener, <-chan ListenerEventType) {
|
||||
datname := os.Getenv("PGDATABASE")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
|
||||
if datname == "" {
|
||||
os.Setenv("PGDATABASE", "pqgotest")
|
||||
}
|
||||
|
||||
if sslmode == "" {
|
||||
os.Setenv("PGSSLMODE", "disable")
|
||||
}
|
||||
|
||||
eventch := make(chan ListenerEventType, 16)
|
||||
l := NewListener("", min, max, func(t ListenerEventType, err error) { eventch <- t })
|
||||
err := expectEvent(t, eventch, ListenerEventConnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l, eventch
|
||||
}
|
||||
|
||||
func newTestListener(t *testing.T) (*Listener, <-chan ListenerEventType) {
|
||||
return newTestListenerTimeout(t, time.Hour, time.Hour)
|
||||
}
|
||||
|
||||
func TestListenerListen(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerUnlisten(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Unlisten("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, l.Notify)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerUnlistenAll(t *testing.T) {
|
||||
l, _ := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.UnlistenAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNoNotification(t, l.Notify)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerFailedQuery(t *testing.T) {
|
||||
l, eventch := newTestListener(t)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// shouldn't cause a disconnect
|
||||
ok, err := l.cn.ExecSimpleQuery("SELECT error")
|
||||
if !ok {
|
||||
t.Fatalf("could not send query to server: %v", err)
|
||||
}
|
||||
_, ok = err.(PGError)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
err = expectNoEvent(t, eventch)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should still work
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerReconnect(t *testing.T) {
|
||||
l, eventch := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
db := openTestConn(t)
|
||||
defer db.Close()
|
||||
|
||||
err := l.Listen("notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// kill the connection and make sure it comes back up
|
||||
ok, err := l.cn.ExecSimpleQuery("SELECT pg_terminate_backend(pg_backend_pid())")
|
||||
if ok {
|
||||
t.Fatalf("could not kill the connection: %v", err)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
err = expectEvent(t, eventch, ListenerEventDisconnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = expectEvent(t, eventch, ListenerEventReconnected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should still work
|
||||
_, err = db.Exec("NOTIFY notify_listen_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// should get nil after Reconnected
|
||||
err = expectNotification(t, l.Notify, "", "")
|
||||
if err != errNilNotification {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = expectNotification(t, l.Notify, "notify_listen_test", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerClose(t *testing.T) {
|
||||
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = l.Close()
|
||||
if err != errListenerClosed {
|
||||
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenerPing(t *testing.T) {
|
||||
l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
|
||||
defer l.Close()
|
||||
|
||||
err := l.Ping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = l.Ping()
|
||||
if err != errListenerClosed {
|
||||
t.Fatalf("expected errListenerClosed; got %v", err)
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
// Package oid contains OID constants
|
||||
// as defined by the Postgres server.
|
||||
package oid
|
||||
|
||||
// Oid is a Postgres Object ID.
|
||||
type Oid uint32
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
// Generate the table of OID values
|
||||
// Run with 'go run gen.go'.
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
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, "// generated by 'go run gen.go'; do not edit")
|
||||
fmt.Fprintln(w, "\npackage oid")
|
||||
fmt.Fprintln(w, "const (")
|
||||
rows, err := db.Query(`
|
||||
SELECT typname, oid
|
||||
FROM pg_type WHERE oid < 10000
|
||||
ORDER BY oid;
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var name string
|
||||
var oid int
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&name, &oid)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(w, "T_%s Oid = %d\n", name, oid)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintln(w, ")")
|
||||
w.Close()
|
||||
cmd.Wait()
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
// generated by 'go run 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_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_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_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_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
|
||||
)
|
||||
-269
@@ -1,269 +0,0 @@
|
||||
package pq
|
||||
|
||||
// This file contains SSL tests
|
||||
|
||||
import (
|
||||
_ "crypto/sha256"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func maybeSkipSSLTests(t *testing.T) {
|
||||
// Require some special variables for testing certificates
|
||||
if os.Getenv("PQSSLCERTTEST_PATH") == "" {
|
||||
t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests")
|
||||
}
|
||||
|
||||
value := os.Getenv("PQGOSSLTESTS")
|
||||
if value == "" || value == "0" {
|
||||
t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests")
|
||||
} else if value != "1" {
|
||||
t.Fatalf("unexpected value %q for PQGOSSLTESTS", value)
|
||||
}
|
||||
}
|
||||
|
||||
func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) {
|
||||
db, err := openTestConnConninfo(conninfo)
|
||||
if err != nil {
|
||||
// should never fail
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Do something with the connection to see whether it's working or not.
|
||||
tx, err := db.Begin()
|
||||
if err == nil {
|
||||
return db, tx.Rollback()
|
||||
}
|
||||
_ = db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func checkSSLSetup(t *testing.T, conninfo string) {
|
||||
db, err := openSSLConn(t, conninfo)
|
||||
if err == nil {
|
||||
db.Close()
|
||||
t.Fatalf("expected error with conninfo=%q", conninfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect over SSL and run a simple query to test the basics
|
||||
func TestSSLConnection(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
db, err := openSSLConn(t, "sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows, err := db.Query("SELECT 1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// Test sslmode=verify-full
|
||||
func TestSSLVerifyFull(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Not OK according to the system CA
|
||||
_, err := openSSLConn(t, "host=postgres sslmode=verify-full user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok := err.(x509.UnknownAuthorityError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
// No match on Common Name
|
||||
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok = err.(x509.HostnameError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.HostnameError, got %#+v", err)
|
||||
}
|
||||
// OK
|
||||
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test sslmode=require sslrootcert=rootCertPath
|
||||
func TestSSLRequireWithRootCert(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
bogusRootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "bogus_root.crt")
|
||||
bogusRootCert := "sslrootcert=" + bogusRootCertPath + " "
|
||||
|
||||
// Not OK according to the bogus CA
|
||||
_, err := openSSLConn(t, bogusRootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok := err.(x509.UnknownAuthorityError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err)
|
||||
}
|
||||
|
||||
nonExistentCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "non_existent.crt")
|
||||
nonExistentCert := "sslrootcert=" + nonExistentCertPath + " "
|
||||
|
||||
// No match on Common Name, but that's OK because we're not validating anything.
|
||||
_, err = openSSLConn(t, nonExistentCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
|
||||
// No match on Common Name, but that's OK because we're not validating the CN.
|
||||
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Everything OK
|
||||
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=require user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test sslmode=verify-ca
|
||||
func TestSSLVerifyCA(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Not OK according to the system CA
|
||||
_, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
_, ok := err.(x509.UnknownAuthorityError)
|
||||
if !ok {
|
||||
t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
|
||||
}
|
||||
|
||||
rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
|
||||
rootCert := "sslrootcert=" + rootCertPath + " "
|
||||
// No match on Common Name, but that's OK
|
||||
_, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Everything OK
|
||||
_, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getCertConninfo(t *testing.T, source string) string {
|
||||
var sslkey string
|
||||
var sslcert string
|
||||
|
||||
certpath := os.Getenv("PQSSLCERTTEST_PATH")
|
||||
|
||||
switch source {
|
||||
case "missingkey":
|
||||
sslkey = "/tmp/filedoesnotexist"
|
||||
sslcert = filepath.Join(certpath, "postgresql.crt")
|
||||
case "missingcert":
|
||||
sslkey = filepath.Join(certpath, "postgresql.key")
|
||||
sslcert = "/tmp/filedoesnotexist"
|
||||
case "certtwice":
|
||||
sslkey = filepath.Join(certpath, "postgresql.crt")
|
||||
sslcert = filepath.Join(certpath, "postgresql.crt")
|
||||
case "valid":
|
||||
sslkey = filepath.Join(certpath, "postgresql.key")
|
||||
sslcert = filepath.Join(certpath, "postgresql.crt")
|
||||
default:
|
||||
t.Fatalf("invalid source %q", source)
|
||||
}
|
||||
return fmt.Sprintf("sslmode=require user=pqgosslcert sslkey=%s sslcert=%s", sslkey, sslcert)
|
||||
}
|
||||
|
||||
// Authenticate over SSL using client certificates
|
||||
func TestSSLClientCertificates(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Should also fail without a valid certificate
|
||||
db, err := openSSLConn(t, "sslmode=require user=pqgosslcert")
|
||||
if err == nil {
|
||||
db.Close()
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
pge, ok := err.(*Error)
|
||||
if !ok {
|
||||
t.Fatal("expected pq.Error")
|
||||
}
|
||||
if pge.Code.Name() != "invalid_authorization_specification" {
|
||||
t.Fatalf("unexpected error code %q", pge.Code.Name())
|
||||
}
|
||||
|
||||
// Should work
|
||||
db, err = openSSLConn(t, getCertConninfo(t, "valid"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows, err := db.Query("SELECT 1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rows.Close()
|
||||
}
|
||||
|
||||
// Test errors with ssl certificates
|
||||
func TestSSLClientCertificatesMissingFiles(t *testing.T) {
|
||||
maybeSkipSSLTests(t)
|
||||
// Environment sanity check: should fail without SSL
|
||||
checkSSLSetup(t, "sslmode=disable user=pqgossltest")
|
||||
|
||||
// Key missing, should fail
|
||||
_, err := openSSLConn(t, getCertConninfo(t, "missingkey"))
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// should be a PathError
|
||||
_, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("expected PathError, got %#+v", err)
|
||||
}
|
||||
|
||||
// Cert missing, should fail
|
||||
_, err = openSSLConn(t, getCertConninfo(t, "missingcert"))
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
// should be a PathError
|
||||
_, ok = err.(*os.PathError)
|
||||
if !ok {
|
||||
t.Fatalf("expected PathError, got %#+v", err)
|
||||
}
|
||||
|
||||
// Key has wrong permissions, should fail
|
||||
_, err = openSSLConn(t, getCertConninfo(t, "certtwice"))
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if err != ErrSSLKeyHasWorldPermissions {
|
||||
t.Fatalf("expected ErrSSLKeyHasWorldPermissions, got %#+v", err)
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
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
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package pq
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleParseURL(t *testing.T) {
|
||||
expected := "host=hostname.remote"
|
||||
str, err := ParseURL("postgres://hostname.remote")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv6LoopbackParseURL(t *testing.T) {
|
||||
expected := "host=::1 port=1234"
|
||||
str, err := ParseURL("postgres://[::1]:1234")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullParseURL(t *testing.T) {
|
||||
expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username`
|
||||
str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if str != expected {
|
||||
t.Fatalf("unexpected result from ParseURL:\n+ %s\n- %s", str, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidProtocolParseURL(t *testing.T) {
|
||||
_, err := ParseURL("http://hostname.remote")
|
||||
switch err {
|
||||
case nil:
|
||||
t.Fatal("Expected an error from parsing invalid protocol")
|
||||
default:
|
||||
msg := "invalid connection protocol: http"
|
||||
if err.Error() != msg {
|
||||
t.Fatalf("Unexpected error message:\n+ %s\n- %s",
|
||||
err.Error(), msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinimalURL(t *testing.T) {
|
||||
cs, err := ParseURL("postgres://")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if cs != "" {
|
||||
t.Fatalf("expected blank connection string, got: %q", cs)
|
||||
}
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// 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
@@ -1,27 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+1
@@ -0,0 +1 @@
|
||||
Copyright 2012 Matt T. Proud (matt.proud@gmail.com)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
all:
|
||||
|
||||
cover:
|
||||
go test -cover -v -coverprofile=cover.dat ./...
|
||||
go tool cover -func cover.dat
|
||||
|
||||
.PHONY: cover
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
// Copyright 2013 Matt T. Proud
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pbutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
. "github.com/matttproud/golang_protobuf_extensions/testdata"
|
||||
)
|
||||
|
||||
func TestWriteDelimited(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, test := range []struct {
|
||||
msg proto.Message
|
||||
buf []byte
|
||||
n int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
msg: &Empty{},
|
||||
n: 1,
|
||||
buf: []byte{0},
|
||||
},
|
||||
{
|
||||
msg: &GoEnum{Foo: FOO_FOO1.Enum()},
|
||||
n: 3,
|
||||
buf: []byte{2, 8, 1},
|
||||
},
|
||||
{
|
||||
msg: &Strings{
|
||||
StringField: proto.String(`This is my gigantic, unhappy string. It exceeds
|
||||
the encoding size of a single byte varint. We are using it to fuzz test the
|
||||
correctness of the header decoding mechanisms, which may prove problematic.
|
||||
I expect it may. Let's hope you enjoy testing as much as we do.`),
|
||||
},
|
||||
n: 271,
|
||||
buf: []byte{141, 2, 10, 138, 2, 84, 104, 105, 115, 32, 105, 115, 32, 109,
|
||||
121, 32, 103, 105, 103, 97, 110, 116, 105, 99, 44, 32, 117, 110, 104,
|
||||
97, 112, 112, 121, 32, 115, 116, 114, 105, 110, 103, 46, 32, 32, 73,
|
||||
116, 32, 101, 120, 99, 101, 101, 100, 115, 10, 116, 104, 101, 32, 101,
|
||||
110, 99, 111, 100, 105, 110, 103, 32, 115, 105, 122, 101, 32, 111, 102,
|
||||
32, 97, 32, 115, 105, 110, 103, 108, 101, 32, 98, 121, 116, 101, 32,
|
||||
118, 97, 114, 105, 110, 116, 46, 32, 32, 87, 101, 32, 97, 114, 101, 32,
|
||||
117, 115, 105, 110, 103, 32, 105, 116, 32, 116, 111, 32, 102, 117, 122,
|
||||
122, 32, 116, 101, 115, 116, 32, 116, 104, 101, 10, 99, 111, 114, 114,
|
||||
101, 99, 116, 110, 101, 115, 115, 32, 111, 102, 32, 116, 104, 101, 32,
|
||||
104, 101, 97, 100, 101, 114, 32, 100, 101, 99, 111, 100, 105, 110, 103,
|
||||
32, 109, 101, 99, 104, 97, 110, 105, 115, 109, 115, 44, 32, 119, 104,
|
||||
105, 99, 104, 32, 109, 97, 121, 32, 112, 114, 111, 118, 101, 32, 112,
|
||||
114, 111, 98, 108, 101, 109, 97, 116, 105, 99, 46, 10, 73, 32, 101, 120,
|
||||
112, 101, 99, 116, 32, 105, 116, 32, 109, 97, 121, 46, 32, 32, 76, 101,
|
||||
116, 39, 115, 32, 104, 111, 112, 101, 32, 121, 111, 117, 32, 101, 110,
|
||||
106, 111, 121, 32, 116, 101, 115, 116, 105, 110, 103, 32, 97, 115, 32,
|
||||
109, 117, 99, 104, 32, 97, 115, 32, 119, 101, 32, 100, 111, 46},
|
||||
},
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
if n, err := WriteDelimited(&buf, test.msg); n != test.n || err != test.err {
|
||||
t.Fatalf("WriteDelimited(buf, %#v) = %v, %v; want %v, %v", test.msg, n, err, test.n, test.err)
|
||||
}
|
||||
if out := buf.Bytes(); !bytes.Equal(out, test.buf) {
|
||||
t.Fatalf("WriteDelimited(buf, %#v); buf = %v; want %v", test.msg, out, test.buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDelimited(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, test := range []struct {
|
||||
buf []byte
|
||||
msg proto.Message
|
||||
n int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
buf: []byte{0},
|
||||
msg: &Empty{},
|
||||
n: 1,
|
||||
},
|
||||
{
|
||||
n: 3,
|
||||
buf: []byte{2, 8, 1},
|
||||
msg: &GoEnum{Foo: FOO_FOO1.Enum()},
|
||||
},
|
||||
{
|
||||
buf: []byte{141, 2, 10, 138, 2, 84, 104, 105, 115, 32, 105, 115, 32, 109,
|
||||
121, 32, 103, 105, 103, 97, 110, 116, 105, 99, 44, 32, 117, 110, 104,
|
||||
97, 112, 112, 121, 32, 115, 116, 114, 105, 110, 103, 46, 32, 32, 73,
|
||||
116, 32, 101, 120, 99, 101, 101, 100, 115, 10, 116, 104, 101, 32, 101,
|
||||
110, 99, 111, 100, 105, 110, 103, 32, 115, 105, 122, 101, 32, 111, 102,
|
||||
32, 97, 32, 115, 105, 110, 103, 108, 101, 32, 98, 121, 116, 101, 32,
|
||||
118, 97, 114, 105, 110, 116, 46, 32, 32, 87, 101, 32, 97, 114, 101, 32,
|
||||
117, 115, 105, 110, 103, 32, 105, 116, 32, 116, 111, 32, 102, 117, 122,
|
||||
122, 32, 116, 101, 115, 116, 32, 116, 104, 101, 10, 99, 111, 114, 114,
|
||||
101, 99, 116, 110, 101, 115, 115, 32, 111, 102, 32, 116, 104, 101, 32,
|
||||
104, 101, 97, 100, 101, 114, 32, 100, 101, 99, 111, 100, 105, 110, 103,
|
||||
32, 109, 101, 99, 104, 97, 110, 105, 115, 109, 115, 44, 32, 119, 104,
|
||||
105, 99, 104, 32, 109, 97, 121, 32, 112, 114, 111, 118, 101, 32, 112,
|
||||
114, 111, 98, 108, 101, 109, 97, 116, 105, 99, 46, 10, 73, 32, 101, 120,
|
||||
112, 101, 99, 116, 32, 105, 116, 32, 109, 97, 121, 46, 32, 32, 76, 101,
|
||||
116, 39, 115, 32, 104, 111, 112, 101, 32, 121, 111, 117, 32, 101, 110,
|
||||
106, 111, 121, 32, 116, 101, 115, 116, 105, 110, 103, 32, 97, 115, 32,
|
||||
109, 117, 99, 104, 32, 97, 115, 32, 119, 101, 32, 100, 111, 46},
|
||||
msg: &Strings{
|
||||
StringField: proto.String(`This is my gigantic, unhappy string. It exceeds
|
||||
the encoding size of a single byte varint. We are using it to fuzz test the
|
||||
correctness of the header decoding mechanisms, which may prove problematic.
|
||||
I expect it may. Let's hope you enjoy testing as much as we do.`),
|
||||
},
|
||||
n: 271,
|
||||
},
|
||||
} {
|
||||
msg := proto.Clone(test.msg)
|
||||
msg.Reset()
|
||||
if n, err := ReadDelimited(bytes.NewBuffer(test.buf), msg); n != test.n || err != test.err {
|
||||
t.Fatalf("ReadDelimited(%v, msg) = %v, %v; want %v, %v", test.buf, n, err, test.n, test.err)
|
||||
}
|
||||
if !proto.Equal(msg, test.msg) {
|
||||
t.Fatalf("ReadDelimited(%v, msg); msg = %v; want %v", test.buf, msg, test.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndToEndValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, test := range [][]proto.Message{
|
||||
{&Empty{}},
|
||||
{&GoEnum{Foo: FOO_FOO1.Enum()}, &Empty{}, &GoEnum{Foo: FOO_FOO1.Enum()}},
|
||||
{&GoEnum{Foo: FOO_FOO1.Enum()}},
|
||||
{&Strings{
|
||||
StringField: proto.String(`This is my gigantic, unhappy string. It exceeds
|
||||
the encoding size of a single byte varint. We are using it to fuzz test the
|
||||
correctness of the header decoding mechanisms, which may prove problematic.
|
||||
I expect it may. Let's hope you enjoy testing as much as we do.`),
|
||||
}},
|
||||
} {
|
||||
var buf bytes.Buffer
|
||||
var written int
|
||||
for i, msg := range test {
|
||||
n, err := WriteDelimited(&buf, msg)
|
||||
if err != nil {
|
||||
// Assumption: TestReadDelimited and TestWriteDelimited are sufficient
|
||||
// and inputs for this test are explicitly exercised there.
|
||||
t.Fatalf("WriteDelimited(buf, %v[%d]) = ?, %v; wanted ?, nil", test, i, err)
|
||||
}
|
||||
written += n
|
||||
}
|
||||
var read int
|
||||
for i, msg := range test {
|
||||
out := proto.Clone(msg)
|
||||
out.Reset()
|
||||
n, _ := ReadDelimited(&buf, out)
|
||||
// Decide to do EOF checking?
|
||||
read += n
|
||||
if !proto.Equal(out, msg) {
|
||||
t.Fatalf("out = %v; want %v[%d] = %#v", out, test, i, msg)
|
||||
}
|
||||
}
|
||||
if read != written {
|
||||
t.Fatalf("%v read = %d; want %d", test, read, written)
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Copyright 2013 Matt T. Proud
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pbutil
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
var errInvalidVarint = errors.New("invalid varint32 encountered")
|
||||
|
||||
// ReadDelimited decodes a message from the provided length-delimited stream,
|
||||
// where the length is encoded as 32-bit varint prefix to the message body.
|
||||
// It returns the total number of bytes read and any applicable error. This is
|
||||
// roughly equivalent to the companion Java API's
|
||||
// MessageLite#parseDelimitedFrom. As per the reader contract, this function
|
||||
// calls r.Read repeatedly as required until exactly one message including its
|
||||
// prefix is read and decoded (or an error has occurred). The function never
|
||||
// reads more bytes from the stream than required. The function never returns
|
||||
// an error if a message has been read and decoded correctly, even if the end
|
||||
// of the stream has been reached in doing so. In that case, any subsequent
|
||||
// calls return (0, io.EOF).
|
||||
func ReadDelimited(r io.Reader, m proto.Message) (n int, err error) {
|
||||
// Per AbstractParser#parsePartialDelimitedFrom with
|
||||
// CodedInputStream#readRawVarint32.
|
||||
var headerBuf [binary.MaxVarintLen32]byte
|
||||
var bytesRead, varIntBytes int
|
||||
var messageLength uint64
|
||||
for varIntBytes == 0 { // i.e. no varint has been decoded yet.
|
||||
if bytesRead >= len(headerBuf) {
|
||||
return bytesRead, errInvalidVarint
|
||||
}
|
||||
// We have to read byte by byte here to avoid reading more bytes
|
||||
// than required. Each read byte is appended to what we have
|
||||
// read before.
|
||||
newBytesRead, err := r.Read(headerBuf[bytesRead : bytesRead+1])
|
||||
if newBytesRead == 0 {
|
||||
if err != nil {
|
||||
return bytesRead, err
|
||||
}
|
||||
// A Reader should not return (0, nil), but if it does,
|
||||
// it should be treated as no-op (according to the
|
||||
// Reader contract). So let's go on...
|
||||
continue
|
||||
}
|
||||
bytesRead += newBytesRead
|
||||
// Now present everything read so far to the varint decoder and
|
||||
// see if a varint can be decoded already.
|
||||
messageLength, varIntBytes = proto.DecodeVarint(headerBuf[:bytesRead])
|
||||
}
|
||||
|
||||
messageBuf := make([]byte, messageLength)
|
||||
newBytesRead, err := io.ReadFull(r, messageBuf)
|
||||
bytesRead += newBytesRead
|
||||
if err != nil {
|
||||
return bytesRead, err
|
||||
}
|
||||
|
||||
return bytesRead, proto.Unmarshal(messageBuf, m)
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright 2016 Matt T. Proud
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pbutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
)
|
||||
|
||||
func TestReadDelimitedIllegalVarint(t *testing.T) {
|
||||
t.Parallel()
|
||||
var tests = []struct {
|
||||
in []byte
|
||||
n int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
in: []byte{255, 255, 255, 255, 255},
|
||||
n: 5,
|
||||
err: errInvalidVarint,
|
||||
},
|
||||
{
|
||||
in: []byte{255, 255, 255, 255, 255, 255},
|
||||
n: 5,
|
||||
err: errInvalidVarint,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
n, err := ReadDelimited(bytes.NewReader(test.in), nil)
|
||||
if got, want := n, test.n; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %v#, ?", test.in, got, want)
|
||||
}
|
||||
if got, want := err, test.err; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", test.in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDelimitedPrematureHeader(t *testing.T) {
|
||||
t.Parallel()
|
||||
var data = []byte{128, 5} // 256 + 256 + 128
|
||||
n, err := ReadDelimited(bytes.NewReader(data[0:1]), nil)
|
||||
if got, want := n, 1; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %v#, ?", data[0:1], got, want)
|
||||
}
|
||||
if got, want := err, io.EOF; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data[0:1], got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDelimitedPrematureBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
var data = []byte{128, 5, 0, 0, 0} // 256 + 256 + 128
|
||||
n, err := ReadDelimited(bytes.NewReader(data[:]), nil)
|
||||
if got, want := n, 5; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %v#, ?", data, got, want)
|
||||
}
|
||||
if got, want := err, io.ErrUnexpectedEOF; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDelimitedPrematureHeaderIncremental(t *testing.T) {
|
||||
t.Parallel()
|
||||
var data = []byte{128, 5} // 256 + 256 + 128
|
||||
n, err := ReadDelimited(iotest.OneByteReader(bytes.NewReader(data[0:1])), nil)
|
||||
if got, want := n, 1; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %v#, ?", data[0:1], got, want)
|
||||
}
|
||||
if got, want := err, io.EOF; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data[0:1], got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDelimitedPrematureBodyIncremental(t *testing.T) {
|
||||
t.Parallel()
|
||||
var data = []byte{128, 5, 0, 0, 0} // 256 + 256 + 128
|
||||
n, err := ReadDelimited(iotest.OneByteReader(bytes.NewReader(data[:])), nil)
|
||||
if got, want := n, 5; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = %#v, ?; want = %v#, ?", data, got, want)
|
||||
}
|
||||
if got, want := err, io.ErrUnexpectedEOF; got != want {
|
||||
t.Errorf("ReadDelimited(%#v, nil) = ?, %#v; want = ?, %#v", data, got, want)
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2013 Matt T. Proud
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pbutil provides record length-delimited Protocol Buffer streaming.
|
||||
package pbutil
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user