Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
559cbd0132 | ||
|
|
733cb78284 | ||
|
|
479ef2b786 | ||
|
|
073ad38bc0 | ||
|
|
aa1ef99295 | ||
|
|
1a056c1aff | ||
|
|
c575677088 |
+2
-2
@@ -46,12 +46,12 @@
|
|||||||
<p>
|
<p>
|
||||||
<pre>
|
<pre>
|
||||||
$ export TOKEN=c033-b79f-7fa1
|
$ 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>
|
</p>
|
||||||
<h2>Deleting a repository</h2>
|
<h2>Deleting a repository</h2>
|
||||||
<p>Using the appropriate token for the route you want to delete:</p>
|
<p>Using the appropriate token for the route you want to delete:</p>
|
||||||
<pre>
|
<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>
|
<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>
|
<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>
|
||||||
|
|||||||
+25
-24
@@ -17,7 +17,7 @@ import (
|
|||||||
const window = 5 * time.Minute
|
const window = 5 * time.Minute
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ func TestAdd(t *testing.T) {
|
|||||||
ts := httptest.NewServer(sm)
|
ts := httptest.NewServer(sm)
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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)
|
resp, err := http.Get(ts.URL)
|
||||||
@@ -109,7 +109,7 @@ func TestAdd(t *testing.T) {
|
|||||||
|
|
||||||
good := fmt.Sprintf("%s/foo", ur.Host)
|
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)
|
t.Fatalf("did not find package for %s; should have posted a valid package", good)
|
||||||
}
|
}
|
||||||
p, err := db.Package(good)
|
p, err := db.Package(good)
|
||||||
@@ -163,7 +163,7 @@ func TestAdd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidPath(t *testing.T) {
|
func TestInvalidPath(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ func TestInvalidPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCannotDuplicateExistingPath(t *testing.T) {
|
func TestCannotDuplicateExistingPath(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ func TestCannotDuplicateExistingPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCannotAddExistingSubPath(t *testing.T) {
|
func TestCannotAddExistingSubPath(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failure to add user: %v", err)
|
t.Errorf("failure to add user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -289,7 +289,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMissingRepo(t *testing.T) {
|
func TestMissingRepo(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ func TestMissingRepo(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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)
|
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||||
@@ -322,7 +322,7 @@ func TestMissingRepo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBadJson(t *testing.T) {
|
func TestBadJson(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ func TestBadJson(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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)
|
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||||
@@ -355,7 +355,7 @@ func TestBadJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNoAuth(t *testing.T) {
|
func TestNoAuth(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -384,7 +384,7 @@ func TestNoAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBadVcs(t *testing.T) {
|
func TestBadVcs(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -396,7 +396,7 @@ func TestBadVcs(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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)
|
u := fmt.Sprintf("%s/foo", ts.URL)
|
||||||
@@ -415,7 +415,7 @@ func TestBadVcs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnsupportedMethod(t *testing.T) {
|
func TestUnsupportedMethod(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -427,7 +427,7 @@ func TestUnsupportedMethod(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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)
|
url := fmt.Sprintf("%s/foo", ts.URL)
|
||||||
@@ -447,7 +447,7 @@ func TestUnsupportedMethod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -459,8 +459,9 @@ func TestDelete(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failure to add user: %v", err)
|
t.Errorf("failure to add user: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("%v", tok)
|
t.Logf("%v", tok)
|
||||||
if len(db.Pkgs()) != 0 {
|
if len(db.Pkgs()) != 0 {
|
||||||
t.Fatalf("started with something in it; got %d, want %d", len(db.Pkgs()), 0)
|
t.Fatalf("started with something in it; got %d, want %d", len(db.Pkgs()), 0)
|
||||||
@@ -511,7 +512,7 @@ func TestDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSingleGet(t *testing.T) {
|
func TestSingleGet(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -523,10 +524,10 @@ func TestSingleGet(t *testing.T) {
|
|||||||
|
|
||||||
tok, err := db.addUser("sm@example.org")
|
tok, err := db.addUser("sm@example.org")
|
||||||
if err != nil {
|
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 {
|
if err := db.NSForToken(ns, tok); err != nil {
|
||||||
t.Fatalf("could not initialize namespace %q for user %q: %v", ns, tok, err)
|
t.Fatalf("could not initialize namespace %q for user %q: %v", ns, tok, err)
|
||||||
@@ -565,7 +566,7 @@ func TestSingleGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRegister(t *testing.T) {
|
func TestRegister(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -618,7 +619,7 @@ func TestRegister(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRoundTrip(t *testing.T) {
|
func TestRoundTrip(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
@@ -686,7 +687,7 @@ func TestRoundTrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestForgot(t *testing.T) {
|
func TestForgot(t *testing.T) {
|
||||||
db, done := testDB(t)
|
db, done := TestDB(t)
|
||||||
if db == nil {
|
if db == nil {
|
||||||
t.Fatalf("could not create temp db")
|
t.Fatalf("could not create temp db")
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-70
@@ -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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -45,6 +5,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"mcquay.me/vain"
|
"mcquay.me/vain"
|
||||||
@@ -52,7 +14,7 @@ import (
|
|||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = "vaind [init] <dbname>"
|
const usage = "vaind <dbname>"
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
Port int
|
Port int
|
||||||
@@ -72,38 +34,12 @@ type config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.SetFlags(log.Lshortfile)
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", usage)
|
fmt.Fprintf(os.Stderr, "%s\n", usage)
|
||||||
os.Exit(1)
|
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.NewDB(os.Args[2])
|
|
||||||
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{
|
c := &config{
|
||||||
Port: 4040,
|
Port: 4040,
|
||||||
EmailTimeout: 5 * time.Minute,
|
EmailTimeout: 5 * time.Minute,
|
||||||
@@ -131,10 +67,27 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("%+v", c)
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "problem initializing mailer: %v", err)
|
fmt.Fprintf(os.Stderr, "problem initializing mailer: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -1,342 +1,258 @@
|
|||||||
package vain
|
package vain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
// for side effects
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
|
|
||||||
verrors "mcquay.me/vain/errors"
|
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
|
// NewMemDB returns a functional MemDB.
|
||||||
// a vain database.
|
func NewMemDB(p string) (*MemDB, error) {
|
||||||
type DB struct {
|
m := &MemDB{
|
||||||
conn *sqlx.DB
|
filename: p,
|
||||||
}
|
|
||||||
|
|
||||||
// NewDB opens a sqlite3 file, sets options, and reports errors.
|
Users: map[Email]User{},
|
||||||
func NewDB(path string) (*DB, error) {
|
TokToEmail: map[Token]Email{},
|
||||||
conn, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc", path))
|
|
||||||
if _, err := conn.Exec("PRAGMA foreign_keys = ON"); err != nil {
|
Packages: map[path]Package{},
|
||||||
return nil, err
|
Namespaces: map[namespace]Email{},
|
||||||
}
|
}
|
||||||
return &DB{conn}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init runs the embedded sql to initialize tables.
|
f, err := os.Open(p)
|
||||||
func (db *DB) Init() error {
|
|
||||||
content, err := vsql.Asset("sql/init.sql")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// file doesn't exist yet
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
_, err = db.conn.Exec(string(content))
|
err = json.NewDecoder(f).Decode(m)
|
||||||
return err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the underlying connection.
|
// MemDB implements an in-memory, and disk-backed database for a vain server.
|
||||||
func (db *DB) Close() error {
|
type MemDB struct {
|
||||||
return db.conn.Close()
|
filename string
|
||||||
}
|
|
||||||
|
|
||||||
// AddPackage adds p into packages table.
|
l sync.RWMutex
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemovePackage removes package with given path
|
Users map[Email]User
|
||||||
func (db *DB) RemovePackage(path string) error {
|
TokToEmail map[Token]Email
|
||||||
_, err := db.conn.Exec("DELETE FROM packages WHERE path = ?", path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pkgs returns all packages from the database
|
Packages map[path]Package
|
||||||
func (db *DB) Pkgs() []Package {
|
Namespaces map[namespace]Email
|
||||||
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 = ?", 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 = ?", path)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return r, verrors.HTTP{
|
|
||||||
Message: fmt.Sprintf("couldn't find package %q", path),
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NSForToken creates an entry namespaces with a relation to the token.
|
// 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
|
var err error
|
||||||
txn, err := db.conn.Beginx()
|
if longest.Path == "" {
|
||||||
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 = ?", 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 = ? AND namespaces.ns = ?", 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:
|
|
||||||
err = verrors.HTTP{
|
err = verrors.HTTP{
|
||||||
Message: fmt.Sprintf("not authorized against namespace %q", ns),
|
Message: fmt.Sprintf("couldn't find package %q", pth),
|
||||||
Code: http.StatusUnauthorized,
|
Code: http.StatusNotFound,
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = verrors.HTTP{
|
|
||||||
Message: fmt.Sprintf("inconsistent db; found %d results with ns (%s) with token (%s)", count, ns, tok),
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.
|
// Register adds email to the database, returning an error if there was one.
|
||||||
func (db *DB) Register(email string) (string, error) {
|
func (m *MemDB) Register(e Email) (Token, error) {
|
||||||
var err error
|
m.l.Lock()
|
||||||
txn, err := db.conn.Beginx()
|
defer m.l.Unlock()
|
||||||
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 _, ok := m.Users[e]; ok {
|
||||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE email = ?", email); err != nil {
|
|
||||||
return "", verrors.HTTP{
|
return "", verrors.HTTP{
|
||||||
Message: fmt.Sprintf("could not search for email %q in db: %v", email, err),
|
Message: fmt.Sprintf("duplicate email %q", e),
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != 0 {
|
|
||||||
return "", verrors.HTTP{
|
|
||||||
Message: fmt.Sprintf("duplicate email %q", email),
|
|
||||||
Code: http.StatusConflict,
|
Code: http.StatusConflict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tok := FreshToken()
|
tok := FreshToken()
|
||||||
_, err = txn.Exec(
|
m.Users[e] = User{
|
||||||
"INSERT INTO users(email, token, requested) VALUES (?, ?, ?)",
|
Email: e,
|
||||||
email,
|
token: tok,
|
||||||
tok,
|
Requested: time.Now(),
|
||||||
time.Now(),
|
}
|
||||||
)
|
m.TokToEmail[tok] = e
|
||||||
return tok, err
|
return tok, m.flush(m.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm modifies the user with the given token. Used on register confirmation.
|
// Confirm modifies the user with the given token. Used on register confirmation.
|
||||||
func (db *DB) Confirm(token string) (string, error) {
|
func (m *MemDB) Confirm(tok Token) (Token, error) {
|
||||||
var err error
|
m.l.Lock()
|
||||||
txn, err := db.conn.Beginx()
|
defer m.l.Unlock()
|
||||||
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
|
e, ok := m.TokToEmail[tok]
|
||||||
if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE token = ?", token); err != nil {
|
if !ok {
|
||||||
return "", verrors.HTTP{
|
return "", verrors.HTTP{
|
||||||
Message: fmt.Sprintf("could not perform search for user with token %q in db: %v", token, err),
|
Message: fmt.Sprintf("bad token: %s", tok),
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != 1 {
|
|
||||||
return "", verrors.HTTP{
|
|
||||||
Message: fmt.Sprintf("bad token: %s", token),
|
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken := FreshToken()
|
delete(m.TokToEmail, tok)
|
||||||
|
tok = FreshToken()
|
||||||
_, err = txn.Exec(
|
u, ok := m.Users[e]
|
||||||
"UPDATE users SET token = ?, registered = 1 WHERE token = ?",
|
if !ok {
|
||||||
newToken,
|
|
||||||
token,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", verrors.HTTP{
|
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,
|
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) {
|
// Forgot is used fetch a user's token. It implements rudimentary rate
|
||||||
txn, err := db.conn.Beginx()
|
// limiting.
|
||||||
if err != nil {
|
func (m *MemDB) Forgot(e Email, window time.Duration) (Token, error) {
|
||||||
return "", verrors.HTTP{
|
m.l.Lock()
|
||||||
Message: fmt.Sprintf("problem creating transaction: %v", err),
|
defer m.l.Unlock()
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
txn.Rollback()
|
|
||||||
} else {
|
|
||||||
txn.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
out := struct {
|
u, ok := m.Users[e]
|
||||||
Token string
|
if !ok {
|
||||||
Requested time.Time
|
|
||||||
}{}
|
|
||||||
if err = txn.Get(&out, "SELECT token, requested FROM users WHERE email = ?", email); err != nil {
|
|
||||||
return "", verrors.HTTP{
|
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,
|
Code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if out.Requested.After(time.Now()) {
|
if u.Requested.After(time.Now()) {
|
||||||
return "", verrors.HTTP{
|
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,
|
Code: http.StatusTooManyRequests,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = txn.Exec("UPDATE users SET requested = ? WHERE email = ?", 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 {
|
if err != nil {
|
||||||
return "", verrors.HTTP{
|
return err
|
||||||
Message: fmt.Sprintf("could not update last requested time for %q: %v", email, err),
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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()
|
tok := FreshToken()
|
||||||
_, err := db.conn.Exec(
|
|
||||||
"INSERT INTO users(email, token, requested) VALUES (?, ?, ?)",
|
m.l.Lock()
|
||||||
email,
|
m.Users[e] = User{
|
||||||
tok,
|
Email: e,
|
||||||
time.Now(),
|
token: tok,
|
||||||
)
|
Requested: time.Now(),
|
||||||
return tok, err
|
}
|
||||||
|
m.TokToEmail[tok] = e
|
||||||
|
m.l.Unlock()
|
||||||
|
|
||||||
|
return tok, m.flush(m.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) user(email string) (User, error) {
|
func (m *MemDB) user(e Email) (User, error) {
|
||||||
u := User{}
|
m.l.Lock()
|
||||||
err := db.conn.Get(
|
u, ok := m.Users[e]
|
||||||
&u,
|
m.l.Unlock()
|
||||||
"SELECT email, token, registered, requested FROM users WHERE email = ?",
|
var err error
|
||||||
email,
|
if !ok {
|
||||||
)
|
err = verrors.HTTP{
|
||||||
if err == sql.ErrNoRows {
|
Message: fmt.Sprintf("couldn't find user %q", e),
|
||||||
return User{}, verrors.HTTP{
|
|
||||||
Message: fmt.Sprintf("could not find requested user's email: %q: %v", email, err),
|
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package vain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPartialPackage(t *testing.T) {
|
||||||
|
db, done := TestDB(t)
|
||||||
|
if db == nil {
|
||||||
|
t.Fatalf("could not create temp db")
|
||||||
|
}
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
paths := []path{
|
||||||
|
"a/b",
|
||||||
|
"a/c",
|
||||||
|
"a/d/c",
|
||||||
|
"a/d/e",
|
||||||
|
|
||||||
|
"f/b/c/d",
|
||||||
|
"f/b/c/e",
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
p, err := db.Package(test.pth)
|
||||||
|
|
||||||
|
if got, want := p, test.pkg; got != want {
|
||||||
|
t.Errorf("bad package fetched: got %+v, want %+v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := err
|
||||||
|
want := test.err
|
||||||
|
if (got == nil) != (want == nil) {
|
||||||
|
t.Errorf("unexpected error; got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
package vain
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ type Mailer interface {
|
|||||||
Send(to mail.Address, subject, msg string) error
|
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.
|
// 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 {
|
if _, err := mail.ParseAddress(from); err != nil {
|
||||||
return nil, fmt.Errorf("can't parse an email address for 'from': %v", err)
|
return nil, fmt.Errorf("can't parse an email address for 'from': %v", err)
|
||||||
}
|
}
|
||||||
r := &Email{
|
r := &Mail{
|
||||||
host: host,
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
from: from,
|
from: from,
|
||||||
@@ -26,16 +26,16 @@ func NewEmail(from, host string, port int) (*Email, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email stores information required to use smtp.
|
// Mail stores information required to use smtp.
|
||||||
type Email struct {
|
type Mail struct {
|
||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
from string
|
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.
|
//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))
|
c, err := smtp.Dial(fmt.Sprintf("%s:%d", e.host, e.port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't dial mail server: %v", err)
|
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
|
## installation
|
||||||
|
|
||||||
$ go get mcquay.me/vain/cmd/vaind
|
```bash
|
||||||
|
$ go get mcquay.me/vain/cmd/vaind
|
||||||
|
```
|
||||||
|
|
||||||
## running server
|
## running server
|
||||||
|
|
||||||
$ vaind
|
```bash
|
||||||
|
$ VAIN_FROM=me@example.org vaind vain.db
|
||||||
|
```
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/elazarl/go-bindata-assetfs"
|
"github.com/elazarl/go-bindata-assetfs"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
verrors "mcquay.me/vain/errors"
|
verrors "mcquay.me/vain/errors"
|
||||||
|
"mcquay.me/vain/metrics"
|
||||||
"mcquay.me/vain/static"
|
"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) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer metrics.Time()()
|
||||||
if req.Method == "GET" {
|
if req.Method == "GET" {
|
||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
if _, ok := req.Form["go-get"]; !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
if req.URL.Path == "/" {
|
if req.URL.Path == "/" {
|
||||||
@@ -67,6 +74,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
p, err := s.db.Package(req.Host + req.URL.Path)
|
p, err := s.db.Package(req.Host + req.URL.Path)
|
||||||
if err := verrors.ToHTTP(err); err != nil {
|
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)
|
http.Error(w, err.Message, err.Code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -91,7 +99,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
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)
|
http.Error(w, err.Message, err.Code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -104,6 +113,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
p := Package{}
|
p := Package{}
|
||||||
if err := json.NewDecoder(req.Body).Decode(&p); err != nil {
|
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)
|
http.Error(w, fmt.Sprintf("unable to parse json from body: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -130,12 +140,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
p := fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
|
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)
|
http.Error(w, fmt.Sprintf("package %q not found", p), http.StatusNotFound)
|
||||||
return
|
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)
|
http.Error(w, fmt.Sprintf("unable to delete package: %v", err), http.StatusInternalServerError)
|
||||||
return
|
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) {
|
func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer metrics.Time()()
|
||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
email, ok := req.Form["email"]
|
email, ok := req.Form["email"]
|
||||||
if !ok || len(email) != 1 {
|
if !ok || len(email) != 1 {
|
||||||
@@ -158,8 +169,9 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tok, err := s.db.Register(addr.Address)
|
tok, err := s.db.Register(Email(addr.Address))
|
||||||
if err := verrors.ToHTTP(err); err != nil {
|
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)
|
http.Error(w, err.Message, err.Code)
|
||||||
return
|
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) {
|
func (s *Server) confirm(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer metrics.Time()()
|
||||||
tok := req.URL.Path[len(prefix["confirm"]):]
|
tok := req.URL.Path[len(prefix["confirm"]):]
|
||||||
tok = strings.TrimRight(tok, "/")
|
tok = strings.TrimRight(tok, "/")
|
||||||
if tok == "" {
|
if tok == "" {
|
||||||
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
|
http.Error(w, "must provide one email parameter", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tok, err := s.db.Confirm(tok)
|
ttok, err := s.db.Confirm(Token(tok))
|
||||||
if err := verrors.ToHTTP(err); err != nil {
|
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)
|
http.Error(w, err.Message, err.Code)
|
||||||
return
|
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) {
|
func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer metrics.Time()()
|
||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
email, ok := req.Form["email"]
|
email, ok := req.Form["email"]
|
||||||
if !ok || len(email) != 1 {
|
if !ok || len(email) != 1 {
|
||||||
@@ -216,8 +231,9 @@ func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
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 {
|
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)
|
http.Error(w, err.Message, err.Code)
|
||||||
return
|
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) {
|
func (s *Server) pkgs(w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer metrics.Time()()
|
||||||
w.Header().Set("Content-type", "application/json")
|
w.Header().Set("Content-type", "application/json")
|
||||||
json.NewEncoder(w).Encode(s.db.Pkgs())
|
json.NewEncoder(w).Encode(s.db.Pkgs())
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRoutes(sm *http.ServeMux, s *Server) {
|
func addRoutes(sm *http.ServeMux, s *Server) {
|
||||||
sm.Handle("/", s)
|
sm.Handle("/", s)
|
||||||
|
sm.Handle("/metrics", prometheus.Handler())
|
||||||
|
|
||||||
if s.static == "" {
|
if s.static == "" {
|
||||||
sm.Handle(
|
sm.Handle(
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE users (
|
|
||||||
email TEXT PRIMARY KEY,
|
|
||||||
token TEXT UNIQUE,
|
|
||||||
registered boolean DEFAULT 0,
|
|
||||||
requested DATETIME
|
|
||||||
);
|
|
||||||
|
|
||||||
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\x8f\xc1\x4e\x84\x30\x14\x45\xf7\xfd\x8a\xb7\x64\x12\x17\xee\x5d\xd5\xf2\x26\x21\x32\xa8\x9d\x92\xc8\xb2\xc2\x0b\x12\xa0\x45\x0a\x7e\xbf\x0d\x4d\x15\xe3\xd0\x5d\x7b\x6f\x7a\xce\x15\x12\xb9\x42\x50\xfc\x31\x47\x58\x1d\xcd\x0e\x12\x06\xfe\xd0\xa8\xbb\x01\x14\xbe\x29\x78\x91\xd9\x85\xcb\x0a\x9e\xb0\xba\xdb\xb2\xc5\xf6\x64\x42\x56\x16\xd9\x6b\x89\xe1\x79\xa6\xb6\x73\x0b\xcd\xd4\xc0\xbb\xb5\x03\x69\x03\x29\x9e\x79\x99\x2b\xb8\x8f\x8d\xcf\x95\x7c\xa5\x81\xd4\x53\x55\x76\x41\x76\x7a\x60\xec\x8f\x84\xd1\x23\xb9\x49\xd7\x14\x4d\x8c\x3b\xd0\xd8\x29\x4a\x3c\xa3\xc4\x42\xe0\x35\x8c\x48\xb6\xec\x04\xcf\x85\x57\xc8\xd1\x7f\x2e\xf8\x55\xf0\xf4\x06\xcf\xa3\x7a\xdd\xfe\xd0\xbe\xea\x80\x8b\xc2\x93\xdd\x5d\x27\xbd\x7c\xfc\xdf\x1d\x05\x77\x12\xbf\x23\x12\xe3\x0e\x34\xbe\x03\x00\x00\xff\xff\xc7\xbb\x93\xa7\x7b\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: 379, mode: os.FileMode(436), modTime: time.Unix(1461818129, 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.
|
// Storer defines the db interface.
|
||||||
type Storer interface {
|
type Storer interface {
|
||||||
AddPackage(p Package) error
|
NSForToken(ns namespace, tok Token) error
|
||||||
Confirm(token string) (string, error)
|
|
||||||
NSForToken(ns string, tok string) error
|
|
||||||
Package(path string) (Package, error)
|
Package(path string) (Package, error)
|
||||||
PackageExists(path string) bool
|
AddPackage(p Package) error
|
||||||
|
RemovePackage(pth path) error
|
||||||
|
PackageExists(pth path) bool
|
||||||
Pkgs() []Package
|
Pkgs() []Package
|
||||||
Register(email string) (string, error)
|
|
||||||
RemovePackage(path string) error
|
Register(e Email) (Token, error)
|
||||||
forgot(email string, window time.Duration) (string, error)
|
Confirm(tok Token) (Token, error)
|
||||||
|
Forgot(e Email, window time.Duration) (Token, error)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-9
@@ -7,25 +7,21 @@ import (
|
|||||||
"testing"
|
"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-")
|
dir, err := ioutil.TempDir("", "vain-testing-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create tmpdir for db: %v", err)
|
t.Fatalf("could not create tmpdir for db: %v", err)
|
||||||
return nil, func() {}
|
return nil, func() {}
|
||||||
}
|
}
|
||||||
name := filepath.Join(dir, "test.db")
|
name := filepath.Join(dir, "test.json")
|
||||||
db, err := NewDB(name)
|
db, err := NewMemDB(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not create db: %v", err)
|
t.Fatalf("could not create db: %v", err)
|
||||||
return nil, func() {}
|
return nil, func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Init(); err != nil {
|
|
||||||
return nil, func() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, func() {
|
return db, func() {
|
||||||
db.Close()
|
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
t.Fatalf("could not clean up tmpdir: %v", err)
|
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
|
package vain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -14,6 +11,15 @@ import (
|
|||||||
"time"
|
"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{
|
var vcss = map[string]bool{
|
||||||
"hg": true,
|
"hg": true,
|
||||||
"git": true,
|
"git": true,
|
||||||
@@ -38,15 +44,15 @@ type Package struct {
|
|||||||
// Repo: the remote repository url
|
// Repo: the remote repository url
|
||||||
Repo string `json:"repo"`
|
Repo string `json:"repo"`
|
||||||
|
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Ns string `json:"-"`
|
Ns namespace `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User stores the information about a user including email used, their
|
// User stores the information about a user including email used, their
|
||||||
// token, whether they have registerd and the requested timestamp
|
// token, whether they have registerd and the requested timestamp
|
||||||
type User struct {
|
type User struct {
|
||||||
Email string
|
Email Email
|
||||||
Token string
|
token Token
|
||||||
Registered bool
|
Registered bool
|
||||||
Requested time.Time
|
Requested time.Time
|
||||||
}
|
}
|
||||||
@@ -84,17 +90,17 @@ func Valid(p string, packages []Package) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNamespace(path string) (string, error) {
|
func parseNamespace(path string) (namespace, error) {
|
||||||
path = strings.TrimLeft(path, "/")
|
path = strings.TrimLeft(path, "/")
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return "", errors.New("path does not contain namespace")
|
return "", errors.New("path does not contain namespace")
|
||||||
}
|
}
|
||||||
elems := strings.Split(path, "/")
|
elems := strings.Split(path, "/")
|
||||||
return elems[0], nil
|
return namespace(elems[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FreshToken returns a random token string.
|
// FreshToken returns a random token string.
|
||||||
func FreshToken() string {
|
func FreshToken() Token {
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
io.Copy(buf, io.LimitReader(rand.Reader, 6))
|
io.Copy(buf, io.LimitReader(rand.Reader, 6))
|
||||||
s := hex.EncodeToString(buf.Bytes())
|
s := hex.EncodeToString(buf.Bytes())
|
||||||
@@ -102,5 +108,5 @@ func FreshToken() string {
|
|||||||
for i := 0; i < len(s)/4; i++ {
|
for i := 0; i < len(s)/4; i++ {
|
||||||
r = append(r, s[i*4:(i+1)*4])
|
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) {
|
func TestNamespaceParsing(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
want string
|
want namespace
|
||||||
err error
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
+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!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
*.db
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: required
|
|
||||||
dist: trusty
|
|
||||||
go:
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
||||||
before_install:
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx
|
|
||||||
- go test -v . -tags "libsqlite3"
|
|
||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Yasuhiro Matsumoto
|
|
||||||
|
|
||||||
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.
|
|
||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
go-sqlite3
|
|
||||||
==========
|
|
||||||
|
|
||||||
[](https://travis-ci.org/mattn/go-sqlite3)
|
|
||||||
[](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
|
|
||||||
[](http://godoc.org/github.com/mattn/go-sqlite3)
|
|
||||||
|
|
||||||
Description
|
|
||||||
-----------
|
|
||||||
|
|
||||||
sqlite3 driver conforming to the built-in database/sql interface
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
This package can be installed with the go get command:
|
|
||||||
|
|
||||||
go get github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
_go-sqlite3_ is *cgo* package.
|
|
||||||
If you want to build your app using go-sqlite3, you need gcc.
|
|
||||||
However, if you install _go-sqlite3_ with `go install github.com/mattn/go-sqlite3`, you don't need gcc to build your app anymore.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
Examples can be found under the `./_example` directory
|
|
||||||
|
|
||||||
FAQ
|
|
||||||
---
|
|
||||||
|
|
||||||
* Want to build go-sqlite3 with libsqlite3 on my linux.
|
|
||||||
|
|
||||||
Use `go build --tags "libsqlite3 linux"`
|
|
||||||
|
|
||||||
* Want to build go-sqlite3 with libsqlite3 on OS X.
|
|
||||||
|
|
||||||
Install sqlite3 from homebrew: `brew install sqlite3`
|
|
||||||
|
|
||||||
Use `go build --tags "libsqlite3 darwin"`
|
|
||||||
|
|
||||||
* Want to build go-sqlite3 with icu extension.
|
|
||||||
|
|
||||||
Use `go build --tags "icu"`
|
|
||||||
|
|
||||||
* Can't build go-sqlite3 on windows 64bit.
|
|
||||||
|
|
||||||
> Probably, you are using go 1.0, go1.0 has a problem when it comes to compiling/linking on windows 64bit.
|
|
||||||
> See: https://github.com/mattn/go-sqlite3/issues/27
|
|
||||||
|
|
||||||
* Getting insert error while query is opened.
|
|
||||||
|
|
||||||
> You can pass some arguments into the connection string, for example, a URI.
|
|
||||||
> See: https://github.com/mattn/go-sqlite3/issues/39
|
|
||||||
|
|
||||||
* Do you want to cross compile? mingw on Linux or Mac?
|
|
||||||
|
|
||||||
> See: https://github.com/mattn/go-sqlite3/issues/106
|
|
||||||
> See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html
|
|
||||||
|
|
||||||
* Want to get time.Time with current locale
|
|
||||||
|
|
||||||
Use `loc=auto` in SQLite3 filename schema like `file:foo.db?loc=auto`.
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
MIT: http://mattn.mit-license.org/2012
|
|
||||||
|
|
||||||
sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h
|
|
||||||
|
|
||||||
The -binding suffix was added to avoid build failures under gccgo.
|
|
||||||
|
|
||||||
In this repository, those files are an amalgamation of code that was copied from SQLite3. The license of that code is the same as the license of SQLite3.
|
|
||||||
|
|
||||||
Author
|
|
||||||
------
|
|
||||||
|
|
||||||
Yasuhiro Matsumoto (a.k.a mattn)
|
|
||||||
-133
@@ -1,133 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
|
|
||||||
sqlite "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Computes x^y
|
|
||||||
func pow(x, y int64) int64 {
|
|
||||||
return int64(math.Pow(float64(x), float64(y)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes the bitwise exclusive-or of all its arguments
|
|
||||||
func xor(xs ...int64) int64 {
|
|
||||||
var ret int64
|
|
||||||
for _, x := range xs {
|
|
||||||
ret ^= x
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a random number. It's actually deterministic here because
|
|
||||||
// we don't seed the RNG, but it's an example of a non-pure function
|
|
||||||
// from SQLite's POV.
|
|
||||||
func getrand() int64 {
|
|
||||||
return rand.Int63()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes the standard deviation of a GROUPed BY set of values
|
|
||||||
type stddev struct {
|
|
||||||
xs []int64
|
|
||||||
// Running average calculation
|
|
||||||
sum int64
|
|
||||||
n int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStddev() *stddev { return &stddev{} }
|
|
||||||
|
|
||||||
func (s *stddev) Step(x int64) {
|
|
||||||
s.xs = append(s.xs, x)
|
|
||||||
s.sum += x
|
|
||||||
s.n++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stddev) Done() float64 {
|
|
||||||
mean := float64(s.sum) / float64(s.n)
|
|
||||||
var sqDiff []float64
|
|
||||||
for _, x := range s.xs {
|
|
||||||
sqDiff = append(sqDiff, math.Pow(float64(x)-mean, 2))
|
|
||||||
}
|
|
||||||
var dev float64
|
|
||||||
for _, x := range sqDiff {
|
|
||||||
dev += x
|
|
||||||
}
|
|
||||||
dev /= float64(len(sqDiff))
|
|
||||||
return math.Sqrt(dev)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sql.Register("sqlite3_custom", &sqlite.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite.SQLiteConn) error {
|
|
||||||
if err := conn.RegisterFunc("pow", pow, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := conn.RegisterFunc("xor", xor, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := conn.RegisterFunc("rand", getrand, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := conn.RegisterAggregator("stddev", newStddev, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3_custom", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to open database:", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
var i int64
|
|
||||||
err = db.QueryRow("SELECT pow(2,3)").Scan(&i)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("POW query error:", err)
|
|
||||||
}
|
|
||||||
fmt.Println("pow(2,3) =", i) // 8
|
|
||||||
|
|
||||||
err = db.QueryRow("SELECT xor(1,2,3,4,5,6)").Scan(&i)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("XOR query error:", err)
|
|
||||||
}
|
|
||||||
fmt.Println("xor(1,2,3,4,5) =", i) // 7
|
|
||||||
|
|
||||||
err = db.QueryRow("SELECT rand()").Scan(&i)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("RAND query error:", err)
|
|
||||||
}
|
|
||||||
fmt.Println("rand() =", i) // pseudorandom
|
|
||||||
|
|
||||||
_, err = db.Exec("create table foo (department integer, profits integer)")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to create table:", err)
|
|
||||||
}
|
|
||||||
_, err = db.Exec("insert into foo values (1, 10), (1, 20), (1, 45), (2, 42), (2, 115)")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to insert records:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query("select department, stddev(profits) from foo group by department")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("STDDEV query error:", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var dept int64
|
|
||||||
var dev float64
|
|
||||||
if err := rows.Scan(&dept, &dev); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("dept=%d stddev=%f\n", dept, dev)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-71
@@ -1,71 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sqlite3conn := []*sqlite3.SQLiteConn{}
|
|
||||||
sql.Register("sqlite3_with_hook_example",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
sqlite3conn = append(sqlite3conn, conn)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
os.Remove("./foo.db")
|
|
||||||
os.Remove("./bar.db")
|
|
||||||
|
|
||||||
destDb, err := sql.Open("sqlite3_with_hook_example", "./foo.db")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer destDb.Close()
|
|
||||||
destDb.Ping()
|
|
||||||
|
|
||||||
_, err = destDb.Exec("create table foo(id int, value text)")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = destDb.Exec("insert into foo values(1, 'foo')")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = destDb.Exec("insert into foo values(2, 'bar')")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = destDb.Query("select * from foo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
srcDb, err := sql.Open("sqlite3_with_hook_example", "./bar.db")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer srcDb.Close()
|
|
||||||
srcDb.Ping()
|
|
||||||
|
|
||||||
bk, err := sqlite3conn[1].Backup("main", sqlite3conn[0], "main")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = bk.Step(-1)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = destDb.Query("select * from foo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = destDb.Exec("insert into foo values(3, 'bar')")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bk.Finish()
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
EXE=extension.exe
|
|
||||||
EXT=sqlite3_mod_regexp.dll
|
|
||||||
RM=cmd /c del
|
|
||||||
LDFLAG=
|
|
||||||
else
|
|
||||||
EXE=extension
|
|
||||||
EXT=sqlite3_mod_regexp.so
|
|
||||||
RM=rm
|
|
||||||
LDFLAG=-fPIC
|
|
||||||
endif
|
|
||||||
|
|
||||||
all : $(EXE) $(EXT)
|
|
||||||
|
|
||||||
$(EXE) : extension.go
|
|
||||||
go build $<
|
|
||||||
|
|
||||||
$(EXT) : sqlite3_mod_regexp.c
|
|
||||||
gcc $(LDFLAG) -shared -o $@ $< -lsqlite3 -lpcre
|
|
||||||
|
|
||||||
clean :
|
|
||||||
@-$(RM) $(EXE) $(EXT)
|
|
||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sql.Register("sqlite3_with_extensions",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
Extensions: []string{
|
|
||||||
"sqlite3_mod_regexp",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Force db to make a new connection in pool
|
|
||||||
// by putting the original in a transaction
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer tx.Commit()
|
|
||||||
|
|
||||||
// New connection works (hopefully!)
|
|
||||||
rows, err := db.Query("select 'hello world' where 'hello world' regexp '^hello.*d$'")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var helloworld string
|
|
||||||
rows.Scan(&helloworld)
|
|
||||||
fmt.Println(helloworld)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
#include <pcre.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sqlite3ext.h>
|
|
||||||
|
|
||||||
SQLITE_EXTENSION_INIT1
|
|
||||||
static void regexp_func(sqlite3_context *context, int argc, sqlite3_value **argv) {
|
|
||||||
if (argc >= 2) {
|
|
||||||
const char *target = (const char *)sqlite3_value_text(argv[1]);
|
|
||||||
const char *pattern = (const char *)sqlite3_value_text(argv[0]);
|
|
||||||
const char* errstr = NULL;
|
|
||||||
int erroff = 0;
|
|
||||||
int vec[500];
|
|
||||||
int n, rc;
|
|
||||||
pcre* re = pcre_compile(pattern, 0, &errstr, &erroff, NULL);
|
|
||||||
rc = pcre_exec(re, NULL, target, strlen(target), 0, 0, vec, 500);
|
|
||||||
if (rc <= 0) {
|
|
||||||
sqlite3_result_error(context, errstr, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sqlite3_result_int(context, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
__declspec(dllexport)
|
|
||||||
#endif
|
|
||||||
int sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
|
|
||||||
SQLITE_EXTENSION_INIT2(api);
|
|
||||||
return sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8, (void*)db, regexp_func, NULL, NULL);
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
EXE=extension.exe
|
|
||||||
EXT=sqlite3_mod_vtable.dll
|
|
||||||
RM=cmd /c del
|
|
||||||
LIBCURL=-lcurldll
|
|
||||||
LDFLAG=
|
|
||||||
else
|
|
||||||
EXE=extension
|
|
||||||
EXT=sqlite3_mod_vtable.so
|
|
||||||
RM=rm
|
|
||||||
LDFLAG=-fPIC
|
|
||||||
LIBCURL=-lcurl
|
|
||||||
endif
|
|
||||||
|
|
||||||
all : $(EXE) $(EXT)
|
|
||||||
|
|
||||||
$(EXE) : extension.go
|
|
||||||
go build $<
|
|
||||||
|
|
||||||
$(EXT) : sqlite3_mod_vtable.cc
|
|
||||||
g++ $(LDFLAG) -shared -o $@ $< -lsqlite3 $(LIBCURL)
|
|
||||||
|
|
||||||
clean :
|
|
||||||
@-$(RM) $(EXE) $(EXT)
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sql.Register("sqlite3_with_extensions",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
Extensions: []string{
|
|
||||||
"sqlite3_mod_vtable",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3_with_extensions", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
db.Exec("create virtual table repo using github(id, full_name, description, html_url)")
|
|
||||||
|
|
||||||
rows, err := db.Query("select id, full_name, description, html_url from repo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var id, full_name, description, html_url string
|
|
||||||
rows.Scan(&id, &full_name, &description, &html_url)
|
|
||||||
fmt.Printf("%s: %s\n\t%s\n\t%s\n\n", id, full_name, description, html_url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1040
File diff suppressed because it is too large
Load Diff
-238
@@ -1,238 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#include <sqlite3ext.h>
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#include "picojson.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# define EXPORT __declspec(dllexport)
|
|
||||||
#else
|
|
||||||
# define EXPORT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SQLITE_EXTENSION_INIT1;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char* data; // response data from server
|
|
||||||
size_t size; // response size of data
|
|
||||||
} MEMFILE;
|
|
||||||
|
|
||||||
MEMFILE*
|
|
||||||
memfopen() {
|
|
||||||
MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
|
|
||||||
if (mf) {
|
|
||||||
mf->data = NULL;
|
|
||||||
mf->size = 0;
|
|
||||||
}
|
|
||||||
return mf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
memfclose(MEMFILE* mf) {
|
|
||||||
if (mf->data) free(mf->data);
|
|
||||||
free(mf);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
|
|
||||||
MEMFILE* mf = (MEMFILE*) stream;
|
|
||||||
int block = size * nmemb;
|
|
||||||
if (!mf) return block; // through
|
|
||||||
if (!mf->data)
|
|
||||||
mf->data = (char*) malloc(block);
|
|
||||||
else
|
|
||||||
mf->data = (char*) realloc(mf->data, mf->size + block);
|
|
||||||
if (mf->data) {
|
|
||||||
memcpy(mf->data + mf->size, ptr, block);
|
|
||||||
mf->size += block;
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
char*
|
|
||||||
memfstrdup(MEMFILE* mf) {
|
|
||||||
char* buf;
|
|
||||||
if (mf->size == 0) return NULL;
|
|
||||||
buf = (char*) malloc(mf->size + 1);
|
|
||||||
memcpy(buf, mf->data, mf->size);
|
|
||||||
buf[mf->size] = 0;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_connect(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **c) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "CREATE TABLE " << argv[0]
|
|
||||||
<< "(id int, full_name text, description text, html_url text)";
|
|
||||||
int rc = sqlite3_declare_vtab(db, ss.str().c_str());
|
|
||||||
*ppVTab = (sqlite3_vtab *) sqlite3_malloc(sizeof(sqlite3_vtab));
|
|
||||||
memset(*ppVTab, 0, sizeof(sqlite3_vtab));
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_create(sqlite3 *db, void *pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char **c) {
|
|
||||||
return my_connect(db, pAux, argc, argv, ppVTab, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int my_disconnect(sqlite3_vtab *pVTab) {
|
|
||||||
sqlite3_free(pVTab);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_destroy(sqlite3_vtab *pVTab) {
|
|
||||||
sqlite3_free(pVTab);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
sqlite3_vtab_cursor base;
|
|
||||||
int index;
|
|
||||||
picojson::value* rows;
|
|
||||||
} cursor;
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
|
|
||||||
MEMFILE* mf;
|
|
||||||
CURL* curl;
|
|
||||||
char* json;
|
|
||||||
CURLcode res = CURLE_OK;
|
|
||||||
char error[CURL_ERROR_SIZE] = {0};
|
|
||||||
char* cert_file = getenv("SSL_CERT_FILE");
|
|
||||||
|
|
||||||
mf = memfopen();
|
|
||||||
curl = curl_easy_init();
|
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.29.0");
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repositories");
|
|
||||||
if (cert_file)
|
|
||||||
curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
|
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
|
|
||||||
res = curl_easy_perform(curl);
|
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
if (res != CURLE_OK) {
|
|
||||||
std::cerr << error << std::endl;
|
|
||||||
return SQLITE_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
picojson::value* v = new picojson::value;
|
|
||||||
std::string err;
|
|
||||||
picojson::parse(*v, mf->data, mf->data + mf->size, &err);
|
|
||||||
memfclose(mf);
|
|
||||||
|
|
||||||
if (!err.empty()) {
|
|
||||||
delete v;
|
|
||||||
std::cerr << err << std::endl;
|
|
||||||
return SQLITE_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor *c = (cursor *)sqlite3_malloc(sizeof(cursor));
|
|
||||||
c->rows = v;
|
|
||||||
c->index = 0;
|
|
||||||
*ppCursor = &c->base;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_close(cursor *c) {
|
|
||||||
delete c->rows;
|
|
||||||
sqlite3_free(c);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_filter(cursor *c, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
|
|
||||||
c->index = 0;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_next(cursor *c) {
|
|
||||||
c->index++;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_eof(cursor *c) {
|
|
||||||
return c->index >= c->rows->get<picojson::array>().size() ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_column(cursor *c, sqlite3_context *ctxt, int i) {
|
|
||||||
picojson::value v = c->rows->get<picojson::array>()[c->index];
|
|
||||||
picojson::object row = v.get<picojson::object>();
|
|
||||||
const char* p = NULL;
|
|
||||||
switch (i) {
|
|
||||||
case 0:
|
|
||||||
p = row["id"].to_str().c_str();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
p = row["full_name"].to_str().c_str();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
p = row["description"].to_str().c_str();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
p = row["html_url"].to_str().c_str();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
sqlite3_result_text(ctxt, strdup(p), strlen(p), free);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_rowid(cursor *c, sqlite3_int64 *pRowid) {
|
|
||||||
*pRowid = c->index;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
my_bestindex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const sqlite3_module module = {
|
|
||||||
0,
|
|
||||||
my_create,
|
|
||||||
my_connect,
|
|
||||||
my_bestindex,
|
|
||||||
my_disconnect,
|
|
||||||
my_destroy,
|
|
||||||
my_open,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *)) my_close,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *, int, char const *, int, sqlite3_value **)) my_filter,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *)) my_next,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *)) my_eof,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *, sqlite3_context *, int)) my_column,
|
|
||||||
(int (*)(sqlite3_vtab_cursor *, sqlite3_int64 *)) my_rowid,
|
|
||||||
NULL, // my_update
|
|
||||||
NULL, // my_begin
|
|
||||||
NULL, // my_sync
|
|
||||||
NULL, // my_commit
|
|
||||||
NULL, // my_rollback
|
|
||||||
NULL, // my_findfunction
|
|
||||||
NULL, // my_rename
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
destructor(void *arg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
EXPORT int
|
|
||||||
sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
|
|
||||||
SQLITE_EXTENSION_INIT2(api);
|
|
||||||
sqlite3_create_module_v2(db, "github", &module, NULL, destructor);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
os.Remove("./foo.db")
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", "./foo.db")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
sqlStmt := `
|
|
||||||
create table foo (id integer not null primary key, name text);
|
|
||||||
delete from foo;
|
|
||||||
`
|
|
||||||
_, err = db.Exec(sqlStmt)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("%q: %s\n", err, sqlStmt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
stmt, err := tx.Prepare("insert into foo(id, name) values(?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
_, err = stmt.Exec(i, fmt.Sprintf("こんにちわ世界%03d", i))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx.Commit()
|
|
||||||
|
|
||||||
rows, err := db.Query("select id, name from foo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var id int
|
|
||||||
var name string
|
|
||||||
err = rows.Scan(&id, &name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(id, name)
|
|
||||||
}
|
|
||||||
err = rows.Err()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err = db.Prepare("select name from foo where id = ?")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
var name string
|
|
||||||
err = stmt.QueryRow("3").Scan(&name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(name)
|
|
||||||
|
|
||||||
_, err = db.Exec("delete from foo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("insert into foo(id, name) values(1, 'foo'), (2, 'bar'), (3, 'baz')")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err = db.Query("select id, name from foo")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var id int
|
|
||||||
var name string
|
|
||||||
err = rows.Scan(&id, &name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(id, name)
|
|
||||||
}
|
|
||||||
err = rows.Err()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SQLiteBackup struct {
|
|
||||||
b *C.sqlite3_backup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Backup(dest string, conn *SQLiteConn, src string) (*SQLiteBackup, error) {
|
|
||||||
destptr := C.CString(dest)
|
|
||||||
defer C.free(unsafe.Pointer(destptr))
|
|
||||||
srcptr := C.CString(src)
|
|
||||||
defer C.free(unsafe.Pointer(srcptr))
|
|
||||||
|
|
||||||
if b := C.sqlite3_backup_init(c.db, destptr, conn.db, srcptr); b != nil {
|
|
||||||
bb := &SQLiteBackup{b: b}
|
|
||||||
runtime.SetFinalizer(bb, (*SQLiteBackup).Finish)
|
|
||||||
return bb, nil
|
|
||||||
}
|
|
||||||
return nil, c.lastError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backs up for one step. Calls the underlying `sqlite3_backup_step` function.
|
|
||||||
// This function returns a boolean indicating if the backup is done and
|
|
||||||
// an error signalling any other error. Done is returned if the underlying C
|
|
||||||
// function returns SQLITE_DONE (Code 101)
|
|
||||||
func (b *SQLiteBackup) Step(p int) (bool, error) {
|
|
||||||
ret := C.sqlite3_backup_step(b.b, C.int(p))
|
|
||||||
if ret == C.SQLITE_DONE {
|
|
||||||
return true, nil
|
|
||||||
} else if ret != 0 && ret != C.SQLITE_LOCKED && ret != C.SQLITE_BUSY {
|
|
||||||
return false, Error{Code: ErrNo(ret)}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SQLiteBackup) Remaining() int {
|
|
||||||
return int(C.sqlite3_backup_remaining(b.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SQLiteBackup) PageCount() int {
|
|
||||||
return int(C.sqlite3_backup_pagecount(b.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SQLiteBackup) Finish() error {
|
|
||||||
return b.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SQLiteBackup) Close() error {
|
|
||||||
ret := C.sqlite3_backup_finish(b.b)
|
|
||||||
if ret != 0 {
|
|
||||||
return Error{Code: ErrNo(ret)}
|
|
||||||
}
|
|
||||||
b.b = nil
|
|
||||||
runtime.SetFinalizer(b, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-336
@@ -1,336 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
// You can't export a Go function to C and have definitions in the C
|
|
||||||
// preamble in the same file, so we have to have callbackTrampoline in
|
|
||||||
// its own file. Because we need a separate file anyway, the support
|
|
||||||
// code for SQLite custom functions is in here.
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
void _sqlite3_result_text(sqlite3_context* ctx, const char* s);
|
|
||||||
void _sqlite3_result_blob(sqlite3_context* ctx, const void* b, int l);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
//export callbackTrampoline
|
|
||||||
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
fi := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*functionInfo)
|
|
||||||
fi.Call(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export stepTrampoline
|
|
||||||
func stepTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
ai := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*aggInfo)
|
|
||||||
ai.Step(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export doneTrampoline
|
|
||||||
func doneTrampoline(ctx *C.sqlite3_context) {
|
|
||||||
handle := uintptr(C.sqlite3_user_data(ctx))
|
|
||||||
ai := lookupHandle(handle).(*aggInfo)
|
|
||||||
ai.Done(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use handles to avoid passing Go pointers to C.
|
|
||||||
|
|
||||||
type handleVal struct {
|
|
||||||
db *SQLiteConn
|
|
||||||
val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var handleLock sync.Mutex
|
|
||||||
var handleVals = make(map[uintptr]handleVal)
|
|
||||||
var handleIndex uintptr = 100
|
|
||||||
|
|
||||||
func newHandle(db *SQLiteConn, v interface{}) uintptr {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
i := handleIndex
|
|
||||||
handleIndex++
|
|
||||||
handleVals[i] = handleVal{db, v}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupHandle(handle uintptr) interface{} {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
r, ok := handleVals[handle]
|
|
||||||
if !ok {
|
|
||||||
if handle >= 100 && handle < handleIndex {
|
|
||||||
panic("deleted handle")
|
|
||||||
} else {
|
|
||||||
panic("invalid handle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.val
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteHandles(db *SQLiteConn) {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
for handle, val := range handleVals {
|
|
||||||
if val.db == db {
|
|
||||||
delete(handleVals, handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is only here so that tests can refer to it.
|
|
||||||
type callbackArgRaw C.sqlite3_value
|
|
||||||
|
|
||||||
type callbackArgConverter func(*C.sqlite3_value) (reflect.Value, error)
|
|
||||||
|
|
||||||
type callbackArgCast struct {
|
|
||||||
f callbackArgConverter
|
|
||||||
typ reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c callbackArgCast) Run(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
val, err := c.f(v)
|
|
||||||
if err != nil {
|
|
||||||
return reflect.Value{}, err
|
|
||||||
}
|
|
||||||
if !val.Type().ConvertibleTo(c.typ) {
|
|
||||||
return reflect.Value{}, fmt.Errorf("cannot convert %s to %s", val.Type(), c.typ)
|
|
||||||
}
|
|
||||||
return val.Convert(c.typ), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgInt64(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_INTEGER {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be an INTEGER")
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(int64(C.sqlite3_value_int64(v))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgBool(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_INTEGER {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be an INTEGER")
|
|
||||||
}
|
|
||||||
i := int64(C.sqlite3_value_int64(v))
|
|
||||||
val := false
|
|
||||||
if i != 0 {
|
|
||||||
val = true
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgFloat64(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_FLOAT {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be a FLOAT")
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(float64(C.sqlite3_value_double(v))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgBytes(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
p := C.sqlite3_value_blob(v)
|
|
||||||
return reflect.ValueOf(C.GoBytes(p, l)), nil
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
c := unsafe.Pointer(C.sqlite3_value_text(v))
|
|
||||||
return reflect.ValueOf(C.GoBytes(c, l)), nil
|
|
||||||
default:
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be BLOB or TEXT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgString(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
p := (*C.char)(C.sqlite3_value_blob(v))
|
|
||||||
return reflect.ValueOf(C.GoStringN(p, l)), nil
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
c := (*C.char)(unsafe.Pointer(C.sqlite3_value_text(v)))
|
|
||||||
return reflect.ValueOf(C.GoString(c)), nil
|
|
||||||
default:
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be BLOB or TEXT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgGeneric(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_INTEGER:
|
|
||||||
return callbackArgInt64(v)
|
|
||||||
case C.SQLITE_FLOAT:
|
|
||||||
return callbackArgFloat64(v)
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
return callbackArgString(v)
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
return callbackArgBytes(v)
|
|
||||||
case C.SQLITE_NULL:
|
|
||||||
// Interpret NULL as a nil byte slice.
|
|
||||||
var ret []byte
|
|
||||||
return reflect.ValueOf(ret), nil
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArg(typ reflect.Type) (callbackArgConverter, error) {
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Interface:
|
|
||||||
if typ.NumMethod() != 0 {
|
|
||||||
return nil, errors.New("the only supported interface type is interface{}")
|
|
||||||
}
|
|
||||||
return callbackArgGeneric, nil
|
|
||||||
case reflect.Slice:
|
|
||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
|
||||||
}
|
|
||||||
return callbackArgBytes, nil
|
|
||||||
case reflect.String:
|
|
||||||
return callbackArgString, nil
|
|
||||||
case reflect.Bool:
|
|
||||||
return callbackArgBool, nil
|
|
||||||
case reflect.Int64:
|
|
||||||
return callbackArgInt64, nil
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
c := callbackArgCast{callbackArgInt64, typ}
|
|
||||||
return c.Run, nil
|
|
||||||
case reflect.Float64:
|
|
||||||
return callbackArgFloat64, nil
|
|
||||||
case reflect.Float32:
|
|
||||||
c := callbackArgCast{callbackArgFloat64, typ}
|
|
||||||
return c.Run, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("don't know how to convert to %s", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackConvertArgs(argv []*C.sqlite3_value, converters []callbackArgConverter, variadic callbackArgConverter) ([]reflect.Value, error) {
|
|
||||||
var args []reflect.Value
|
|
||||||
|
|
||||||
if len(argv) < len(converters) {
|
|
||||||
return nil, fmt.Errorf("function requires at least %d arguments", len(converters))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, arg := range argv[:len(converters)] {
|
|
||||||
v, err := converters[i](arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if variadic != nil {
|
|
||||||
for _, arg := range argv[len(converters):] {
|
|
||||||
v, err := variadic(arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type callbackRetConverter func(*C.sqlite3_context, reflect.Value) error
|
|
||||||
|
|
||||||
func callbackRetInteger(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
switch v.Type().Kind() {
|
|
||||||
case reflect.Int64:
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
v = v.Convert(reflect.TypeOf(int64(0)))
|
|
||||||
case reflect.Bool:
|
|
||||||
b := v.Interface().(bool)
|
|
||||||
if b {
|
|
||||||
v = reflect.ValueOf(int64(1))
|
|
||||||
} else {
|
|
||||||
v = reflect.ValueOf(int64(0))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot convert %s to INTEGER", v.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
C.sqlite3_result_int64(ctx, C.sqlite3_int64(v.Interface().(int64)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetFloat(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
switch v.Type().Kind() {
|
|
||||||
case reflect.Float64:
|
|
||||||
case reflect.Float32:
|
|
||||||
v = v.Convert(reflect.TypeOf(float64(0)))
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot convert %s to FLOAT", v.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
C.sqlite3_result_double(ctx, C.double(v.Interface().(float64)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetBlob(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
if v.Type().Kind() != reflect.Slice || v.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return fmt.Errorf("cannot convert %s to BLOB", v.Type())
|
|
||||||
}
|
|
||||||
i := v.Interface()
|
|
||||||
if i == nil || len(i.([]byte)) == 0 {
|
|
||||||
C.sqlite3_result_null(ctx)
|
|
||||||
} else {
|
|
||||||
bs := i.([]byte)
|
|
||||||
C._sqlite3_result_blob(ctx, unsafe.Pointer(&bs[0]), C.int(len(bs)))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
if v.Type().Kind() != reflect.String {
|
|
||||||
return fmt.Errorf("cannot convert %s to TEXT", v.Type())
|
|
||||||
}
|
|
||||||
C._sqlite3_result_text(ctx, C.CString(v.Interface().(string)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Slice:
|
|
||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
|
||||||
}
|
|
||||||
return callbackRetBlob, nil
|
|
||||||
case reflect.String:
|
|
||||||
return callbackRetText, nil
|
|
||||||
case reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
return callbackRetInteger, nil
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return callbackRetFloat, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("don't know how to convert to %s", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackError(ctx *C.sqlite3_context, err error) {
|
|
||||||
cstr := C.CString(err.Error())
|
|
||||||
defer C.free(unsafe.Pointer(cstr))
|
|
||||||
C.sqlite3_result_error(ctx, cstr, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test support code. Tests are not allowed to import "C", so we can't
|
|
||||||
// declare any functions that use C.sqlite3_value.
|
|
||||||
func callbackSyntheticForTests(v reflect.Value, err error) callbackArgConverter {
|
|
||||||
return func(*C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-97
@@ -1,97 +0,0 @@
|
|||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCallbackArgCast(t *testing.T) {
|
|
||||||
intConv := callbackSyntheticForTests(reflect.ValueOf(int64(math.MaxInt64)), nil)
|
|
||||||
floatConv := callbackSyntheticForTests(reflect.ValueOf(float64(math.MaxFloat64)), nil)
|
|
||||||
errConv := callbackSyntheticForTests(reflect.Value{}, errors.New("test"))
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
f callbackArgConverter
|
|
||||||
o reflect.Value
|
|
||||||
}{
|
|
||||||
{intConv, reflect.ValueOf(int8(-1))},
|
|
||||||
{intConv, reflect.ValueOf(int16(-1))},
|
|
||||||
{intConv, reflect.ValueOf(int32(-1))},
|
|
||||||
{intConv, reflect.ValueOf(uint8(math.MaxUint8))},
|
|
||||||
{intConv, reflect.ValueOf(uint16(math.MaxUint16))},
|
|
||||||
{intConv, reflect.ValueOf(uint32(math.MaxUint32))},
|
|
||||||
// Special case, int64->uint64 is only 1<<63 - 1, not 1<<64 - 1
|
|
||||||
{intConv, reflect.ValueOf(uint64(math.MaxInt64))},
|
|
||||||
{floatConv, reflect.ValueOf(float32(math.Inf(1)))},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
conv := callbackArgCast{test.f, test.o.Type()}
|
|
||||||
val, err := conv.Run(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't convert to %s: %s", test.o.Type(), err)
|
|
||||||
} else if !reflect.DeepEqual(val.Interface(), test.o.Interface()) {
|
|
||||||
t.Errorf("Unexpected result from converting to %s: got %v, want %v", test.o.Type(), val.Interface(), test.o.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conv := callbackArgCast{errConv, reflect.TypeOf(int8(0))}
|
|
||||||
_, err := conv.Run(nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error during callbackArgCast, but got none")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallbackConverters(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
v interface{}
|
|
||||||
err bool
|
|
||||||
}{
|
|
||||||
// Unfortunately, we can't tell which converter was returned,
|
|
||||||
// but we can at least check which types can be converted.
|
|
||||||
{[]byte{0}, false},
|
|
||||||
{"text", false},
|
|
||||||
{true, false},
|
|
||||||
{int8(0), false},
|
|
||||||
{int16(0), false},
|
|
||||||
{int32(0), false},
|
|
||||||
{int64(0), false},
|
|
||||||
{uint8(0), false},
|
|
||||||
{uint16(0), false},
|
|
||||||
{uint32(0), false},
|
|
||||||
{uint64(0), false},
|
|
||||||
{int(0), false},
|
|
||||||
{uint(0), false},
|
|
||||||
{float64(0), false},
|
|
||||||
{float32(0), false},
|
|
||||||
|
|
||||||
{func() {}, true},
|
|
||||||
{complex64(complex(0, 0)), true},
|
|
||||||
{complex128(complex(0, 0)), true},
|
|
||||||
{struct{}{}, true},
|
|
||||||
{map[string]string{}, true},
|
|
||||||
{[]string{}, true},
|
|
||||||
{(*int8)(nil), true},
|
|
||||||
{make(chan int), true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := callbackArg(reflect.TypeOf(test.v))
|
|
||||||
if test.err && err == nil {
|
|
||||||
t.Errorf("Expected an error when converting %s, got no error", reflect.TypeOf(test.v))
|
|
||||||
} else if !test.err && err != nil {
|
|
||||||
t.Errorf("Expected converter when converting %s, got error: %s", reflect.TypeOf(test.v), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
_, err := callbackRet(reflect.TypeOf(test.v))
|
|
||||||
if test.err && err == nil {
|
|
||||||
t.Errorf("Expected an error when converting %s, got no error", reflect.TypeOf(test.v))
|
|
||||||
} else if !test.err && err != nil {
|
|
||||||
t.Errorf("Expected converter when converting %s, got error: %s", reflect.TypeOf(test.v), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
Package sqlite3 provides interface to SQLite3 databases.
|
|
||||||
|
|
||||||
This works as a driver for database/sql.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
|
|
||||||
go get github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
Supported Types
|
|
||||||
|
|
||||||
Currently, go-sqlite3 supports the following data types.
|
|
||||||
|
|
||||||
+------------------------------+
|
|
||||||
|go | sqlite3 |
|
|
||||||
|----------|-------------------|
|
|
||||||
|nil | null |
|
|
||||||
|int | integer |
|
|
||||||
|int64 | integer |
|
|
||||||
|float64 | float |
|
|
||||||
|bool | integer |
|
|
||||||
|[]byte | blob |
|
|
||||||
|string | text |
|
|
||||||
|time.Time | timestamp/datetime|
|
|
||||||
+------------------------------+
|
|
||||||
|
|
||||||
SQLite3 Extension
|
|
||||||
|
|
||||||
You can write your own extension module for sqlite3. For example, below is an
|
|
||||||
extension for a Regexp matcher operation.
|
|
||||||
|
|
||||||
#include <pcre.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sqlite3ext.h>
|
|
||||||
|
|
||||||
SQLITE_EXTENSION_INIT1
|
|
||||||
static void regexp_func(sqlite3_context *context, int argc, sqlite3_value **argv) {
|
|
||||||
if (argc >= 2) {
|
|
||||||
const char *target = (const char *)sqlite3_value_text(argv[1]);
|
|
||||||
const char *pattern = (const char *)sqlite3_value_text(argv[0]);
|
|
||||||
const char* errstr = NULL;
|
|
||||||
int erroff = 0;
|
|
||||||
int vec[500];
|
|
||||||
int n, rc;
|
|
||||||
pcre* re = pcre_compile(pattern, 0, &errstr, &erroff, NULL);
|
|
||||||
rc = pcre_exec(re, NULL, target, strlen(target), 0, 0, vec, 500);
|
|
||||||
if (rc <= 0) {
|
|
||||||
sqlite3_result_error(context, errstr, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sqlite3_result_int(context, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
__declspec(dllexport)
|
|
||||||
#endif
|
|
||||||
int sqlite3_extension_init(sqlite3 *db, char **errmsg,
|
|
||||||
const sqlite3_api_routines *api) {
|
|
||||||
SQLITE_EXTENSION_INIT2(api);
|
|
||||||
return sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8,
|
|
||||||
(void*)db, regexp_func, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
It needs to be built as a so/dll shared library. And you need to register
|
|
||||||
the extension module like below.
|
|
||||||
|
|
||||||
sql.Register("sqlite3_with_extensions",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
Extensions: []string{
|
|
||||||
"sqlite3_mod_regexp",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Then, you can use this extension.
|
|
||||||
|
|
||||||
rows, err := db.Query("select text from mytable where name regexp '^golang'")
|
|
||||||
|
|
||||||
Connection Hook
|
|
||||||
|
|
||||||
You can hook and inject your code when the connection is established. database/sql
|
|
||||||
doesn't provide a way to get native go-sqlite3 interfaces. So if you want,
|
|
||||||
you need to set ConnectHook and get the SQLiteConn.
|
|
||||||
|
|
||||||
sql.Register("sqlite3_with_hook_example",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
sqlite3conn = append(sqlite3conn, conn)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Go SQlite3 Extensions
|
|
||||||
|
|
||||||
If you want to register Go functions as SQLite extension functions,
|
|
||||||
call RegisterFunction from ConnectHook.
|
|
||||||
|
|
||||||
regex = func(re, s string) (bool, error) {
|
|
||||||
return regexp.MatchString(re, s)
|
|
||||||
}
|
|
||||||
sql.Register("sqlite3_with_go_func",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
return conn.RegisterFunc("regexp", regex, true)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
See the documentation of RegisterFunc for more details.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package sqlite3
|
|
||||||
-128
@@ -1,128 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
type ErrNo int
|
|
||||||
|
|
||||||
const ErrNoMask C.int = 0xff
|
|
||||||
|
|
||||||
type ErrNoExtended int
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Code ErrNo /* The error code returned by SQLite */
|
|
||||||
ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */
|
|
||||||
err string /* The error string returned by sqlite3_errmsg(),
|
|
||||||
this usually contains more specific details. */
|
|
||||||
}
|
|
||||||
|
|
||||||
// result codes from http://www.sqlite.org/c3ref/c_abort.html
|
|
||||||
var (
|
|
||||||
ErrError = ErrNo(1) /* SQL error or missing database */
|
|
||||||
ErrInternal = ErrNo(2) /* Internal logic error in SQLite */
|
|
||||||
ErrPerm = ErrNo(3) /* Access permission denied */
|
|
||||||
ErrAbort = ErrNo(4) /* Callback routine requested an abort */
|
|
||||||
ErrBusy = ErrNo(5) /* The database file is locked */
|
|
||||||
ErrLocked = ErrNo(6) /* A table in the database is locked */
|
|
||||||
ErrNomem = ErrNo(7) /* A malloc() failed */
|
|
||||||
ErrReadonly = ErrNo(8) /* Attempt to write a readonly database */
|
|
||||||
ErrInterrupt = ErrNo(9) /* Operation terminated by sqlite3_interrupt() */
|
|
||||||
ErrIoErr = ErrNo(10) /* Some kind of disk I/O error occurred */
|
|
||||||
ErrCorrupt = ErrNo(11) /* The database disk image is malformed */
|
|
||||||
ErrNotFound = ErrNo(12) /* Unknown opcode in sqlite3_file_control() */
|
|
||||||
ErrFull = ErrNo(13) /* Insertion failed because database is full */
|
|
||||||
ErrCantOpen = ErrNo(14) /* Unable to open the database file */
|
|
||||||
ErrProtocol = ErrNo(15) /* Database lock protocol error */
|
|
||||||
ErrEmpty = ErrNo(16) /* Database is empty */
|
|
||||||
ErrSchema = ErrNo(17) /* The database schema changed */
|
|
||||||
ErrTooBig = ErrNo(18) /* String or BLOB exceeds size limit */
|
|
||||||
ErrConstraint = ErrNo(19) /* Abort due to constraint violation */
|
|
||||||
ErrMismatch = ErrNo(20) /* Data type mismatch */
|
|
||||||
ErrMisuse = ErrNo(21) /* Library used incorrectly */
|
|
||||||
ErrNoLFS = ErrNo(22) /* Uses OS features not supported on host */
|
|
||||||
ErrAuth = ErrNo(23) /* Authorization denied */
|
|
||||||
ErrFormat = ErrNo(24) /* Auxiliary database format error */
|
|
||||||
ErrRange = ErrNo(25) /* 2nd parameter to sqlite3_bind out of range */
|
|
||||||
ErrNotADB = ErrNo(26) /* File opened that is not a database file */
|
|
||||||
ErrNotice = ErrNo(27) /* Notifications from sqlite3_log() */
|
|
||||||
ErrWarning = ErrNo(28) /* Warnings from sqlite3_log() */
|
|
||||||
)
|
|
||||||
|
|
||||||
func (err ErrNo) Error() string {
|
|
||||||
return Error{Code: err}.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrNo) Extend(by int) ErrNoExtended {
|
|
||||||
return ErrNoExtended(int(err) | (by << 8))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrNoExtended) Error() string {
|
|
||||||
return Error{Code: ErrNo(C.int(err) & ErrNoMask), ExtendedCode: err}.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err Error) Error() string {
|
|
||||||
if err.err != "" {
|
|
||||||
return err.err
|
|
||||||
}
|
|
||||||
return errorString(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html
|
|
||||||
var (
|
|
||||||
ErrIoErrRead = ErrIoErr.Extend(1)
|
|
||||||
ErrIoErrShortRead = ErrIoErr.Extend(2)
|
|
||||||
ErrIoErrWrite = ErrIoErr.Extend(3)
|
|
||||||
ErrIoErrFsync = ErrIoErr.Extend(4)
|
|
||||||
ErrIoErrDirFsync = ErrIoErr.Extend(5)
|
|
||||||
ErrIoErrTruncate = ErrIoErr.Extend(6)
|
|
||||||
ErrIoErrFstat = ErrIoErr.Extend(7)
|
|
||||||
ErrIoErrUnlock = ErrIoErr.Extend(8)
|
|
||||||
ErrIoErrRDlock = ErrIoErr.Extend(9)
|
|
||||||
ErrIoErrDelete = ErrIoErr.Extend(10)
|
|
||||||
ErrIoErrBlocked = ErrIoErr.Extend(11)
|
|
||||||
ErrIoErrNoMem = ErrIoErr.Extend(12)
|
|
||||||
ErrIoErrAccess = ErrIoErr.Extend(13)
|
|
||||||
ErrIoErrCheckReservedLock = ErrIoErr.Extend(14)
|
|
||||||
ErrIoErrLock = ErrIoErr.Extend(15)
|
|
||||||
ErrIoErrClose = ErrIoErr.Extend(16)
|
|
||||||
ErrIoErrDirClose = ErrIoErr.Extend(17)
|
|
||||||
ErrIoErrSHMOpen = ErrIoErr.Extend(18)
|
|
||||||
ErrIoErrSHMSize = ErrIoErr.Extend(19)
|
|
||||||
ErrIoErrSHMLock = ErrIoErr.Extend(20)
|
|
||||||
ErrIoErrSHMMap = ErrIoErr.Extend(21)
|
|
||||||
ErrIoErrSeek = ErrIoErr.Extend(22)
|
|
||||||
ErrIoErrDeleteNoent = ErrIoErr.Extend(23)
|
|
||||||
ErrIoErrMMap = ErrIoErr.Extend(24)
|
|
||||||
ErrIoErrGetTempPath = ErrIoErr.Extend(25)
|
|
||||||
ErrIoErrConvPath = ErrIoErr.Extend(26)
|
|
||||||
ErrLockedSharedCache = ErrLocked.Extend(1)
|
|
||||||
ErrBusyRecovery = ErrBusy.Extend(1)
|
|
||||||
ErrBusySnapshot = ErrBusy.Extend(2)
|
|
||||||
ErrCantOpenNoTempDir = ErrCantOpen.Extend(1)
|
|
||||||
ErrCantOpenIsDir = ErrCantOpen.Extend(2)
|
|
||||||
ErrCantOpenFullPath = ErrCantOpen.Extend(3)
|
|
||||||
ErrCantOpenConvPath = ErrCantOpen.Extend(4)
|
|
||||||
ErrCorruptVTab = ErrCorrupt.Extend(1)
|
|
||||||
ErrReadonlyRecovery = ErrReadonly.Extend(1)
|
|
||||||
ErrReadonlyCantLock = ErrReadonly.Extend(2)
|
|
||||||
ErrReadonlyRollback = ErrReadonly.Extend(3)
|
|
||||||
ErrReadonlyDbMoved = ErrReadonly.Extend(4)
|
|
||||||
ErrAbortRollback = ErrAbort.Extend(2)
|
|
||||||
ErrConstraintCheck = ErrConstraint.Extend(1)
|
|
||||||
ErrConstraintCommitHook = ErrConstraint.Extend(2)
|
|
||||||
ErrConstraintForeignKey = ErrConstraint.Extend(3)
|
|
||||||
ErrConstraintFunction = ErrConstraint.Extend(4)
|
|
||||||
ErrConstraintNotNull = ErrConstraint.Extend(5)
|
|
||||||
ErrConstraintPrimaryKey = ErrConstraint.Extend(6)
|
|
||||||
ErrConstraintTrigger = ErrConstraint.Extend(7)
|
|
||||||
ErrConstraintUnique = ErrConstraint.Extend(8)
|
|
||||||
ErrConstraintVTab = ErrConstraint.Extend(9)
|
|
||||||
ErrConstraintRowId = ErrConstraint.Extend(10)
|
|
||||||
ErrNoticeRecoverWAL = ErrNotice.Extend(1)
|
|
||||||
ErrNoticeRecoverRollback = ErrNotice.Extend(2)
|
|
||||||
ErrWarningAutoIndex = ErrWarning.Extend(1)
|
|
||||||
)
|
|
||||||
-242
@@ -1,242 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSimpleError(t *testing.T) {
|
|
||||||
e := ErrError.Error()
|
|
||||||
if e != "SQL logic error or missing database" {
|
|
||||||
t.Error("wrong error code:" + e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCorruptDbErrors(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "sqlite3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
dbFileName := path.Join(dirName, "test.db")
|
|
||||||
f, err := os.Create(dbFileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
f.Write([]byte{1, 2, 3, 4, 5})
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", dbFileName)
|
|
||||||
if err == nil {
|
|
||||||
_, err = db.Exec("drop table foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
sqliteErr := err.(Error)
|
|
||||||
if sqliteErr.Code != ErrNotADB {
|
|
||||||
t.Error("wrong error code for corrupted DB")
|
|
||||||
}
|
|
||||||
if err.Error() == "" {
|
|
||||||
t.Error("wrong error string for corrupted DB")
|
|
||||||
}
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSqlLogicErrors(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "sqlite3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
dbFileName := path.Join(dirName, "test.db")
|
|
||||||
db, err := sql.Open("sqlite3", dbFileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const expectedErr = "table Foo already exists"
|
|
||||||
_, err = db.Exec("CREATE TABLE Foo (id INTEGER PRIMARY KEY)")
|
|
||||||
if err.Error() != expectedErr {
|
|
||||||
t.Errorf("Unexpected error: %s, expected %s", err.Error(), expectedErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtendedErrorCodes_ForeignKey(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "sqlite3-err")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
dbFileName := path.Join(dirName, "test.db")
|
|
||||||
db, err := sql.Open("sqlite3", dbFileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("PRAGMA foreign_keys=ON;")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("PRAGMA foreign_keys=ON: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`CREATE TABLE Foo (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
value INTEGER NOT NULL,
|
|
||||||
ref INTEGER NULL REFERENCES Foo (id),
|
|
||||||
UNIQUE(value)
|
|
||||||
);`)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO Foo (ref, value) VALUES (100, 100);")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("No error!")
|
|
||||||
} else {
|
|
||||||
sqliteErr := err.(Error)
|
|
||||||
if sqliteErr.Code != ErrConstraint {
|
|
||||||
t.Errorf("Wrong basic error code: %d != %d",
|
|
||||||
sqliteErr.Code, ErrConstraint)
|
|
||||||
}
|
|
||||||
if sqliteErr.ExtendedCode != ErrConstraintForeignKey {
|
|
||||||
t.Errorf("Wrong extended error code: %d != %d",
|
|
||||||
sqliteErr.ExtendedCode, ErrConstraintForeignKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtendedErrorCodes_NotNull(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "sqlite3-err")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
dbFileName := path.Join(dirName, "test.db")
|
|
||||||
db, err := sql.Open("sqlite3", dbFileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("PRAGMA foreign_keys=ON;")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("PRAGMA foreign_keys=ON: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`CREATE TABLE Foo (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
value INTEGER NOT NULL,
|
|
||||||
ref INTEGER NULL REFERENCES Foo (id),
|
|
||||||
UNIQUE(value)
|
|
||||||
);`)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := db.Exec("INSERT INTO Foo (value) VALUES (100);")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Creating first row: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Retrieving last insert id: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO Foo (ref) VALUES (?);", id)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("No error!")
|
|
||||||
} else {
|
|
||||||
sqliteErr := err.(Error)
|
|
||||||
if sqliteErr.Code != ErrConstraint {
|
|
||||||
t.Errorf("Wrong basic error code: %d != %d",
|
|
||||||
sqliteErr.Code, ErrConstraint)
|
|
||||||
}
|
|
||||||
if sqliteErr.ExtendedCode != ErrConstraintNotNull {
|
|
||||||
t.Errorf("Wrong extended error code: %d != %d",
|
|
||||||
sqliteErr.ExtendedCode, ErrConstraintNotNull)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtendedErrorCodes_Unique(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "sqlite3-err")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
dbFileName := path.Join(dirName, "test.db")
|
|
||||||
db, err := sql.Open("sqlite3", dbFileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("PRAGMA foreign_keys=ON;")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("PRAGMA foreign_keys=ON: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec(`CREATE TABLE Foo (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
value INTEGER NOT NULL,
|
|
||||||
ref INTEGER NULL REFERENCES Foo (id),
|
|
||||||
UNIQUE(value)
|
|
||||||
);`)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := db.Exec("INSERT INTO Foo (value) VALUES (100);")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Creating first row: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Retrieving last insert id: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO Foo (ref, value) VALUES (?, 100);", id)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("No error!")
|
|
||||||
} else {
|
|
||||||
sqliteErr := err.(Error)
|
|
||||||
if sqliteErr.Code != ErrConstraint {
|
|
||||||
t.Errorf("Wrong basic error code: %d != %d",
|
|
||||||
sqliteErr.Code, ErrConstraint)
|
|
||||||
}
|
|
||||||
if sqliteErr.ExtendedCode != ErrConstraintUnique {
|
|
||||||
t.Errorf("Wrong extended error code: %d != %d",
|
|
||||||
sqliteErr.ExtendedCode, ErrConstraintUnique)
|
|
||||||
}
|
|
||||||
extended := sqliteErr.Code.Extend(3).Error()
|
|
||||||
expected := "constraint failed"
|
|
||||||
if extended != expected {
|
|
||||||
t.Errorf("Wrong basic error code: %q != %q",
|
|
||||||
extended, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-189319
File diff suppressed because it is too large
Load Diff
-8733
File diff suppressed because it is too large
Load Diff
-1006
File diff suppressed because it is too large
Load Diff
-130
@@ -1,130 +0,0 @@
|
|||||||
// Copyright (C) 2015 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFTS3(t *testing.T) {
|
|
||||||
tempFilename := TempFilename(t)
|
|
||||||
defer os.Remove(tempFilename)
|
|
||||||
db, err := sql.Open("sqlite3", tempFilename)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to open database:", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("DROP TABLE foo")
|
|
||||||
_, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts3(id INTEGER PRIMARY KEY, value TEXT)")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `今日の 晩御飯は 天麩羅よ`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert value:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 2, `今日は いい 天気だ`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert value:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT id, value FROM foo WHERE value MATCH '今日* 天*'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to query foo table:", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var id int
|
|
||||||
var value string
|
|
||||||
|
|
||||||
if err := rows.Scan(&id, &value); err != nil {
|
|
||||||
t.Error("Unable to scan results:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if id == 1 && value != `今日の 晩御飯は 天麩羅よ` {
|
|
||||||
t.Error("Value for id 1 should be `今日の 晩御飯は 天麩羅よ`, but:", value)
|
|
||||||
} else if id == 2 && value != `今日は いい 天気だ` {
|
|
||||||
t.Error("Value for id 2 should be `今日は いい 天気だ`, but:", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err = db.Query("SELECT value FROM foo WHERE value MATCH '今日* 天麩羅*'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to query foo table:", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var value string
|
|
||||||
if !rows.Next() {
|
|
||||||
t.Fatal("Result should be only one")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rows.Scan(&value); err != nil {
|
|
||||||
t.Fatal("Unable to scan results:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value != `今日の 晩御飯は 天麩羅よ` {
|
|
||||||
t.Fatal("Value should be `今日の 晩御飯は 天麩羅よ`, but:", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows.Next() {
|
|
||||||
t.Fatal("Result should be only one")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFTS4(t *testing.T) {
|
|
||||||
tempFilename := TempFilename(t)
|
|
||||||
defer os.Remove(tempFilename)
|
|
||||||
db, err := sql.Open("sqlite3", tempFilename)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to open database:", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
_, err = db.Exec("DROP TABLE foo")
|
|
||||||
_, err = db.Exec("CREATE VIRTUAL TABLE foo USING fts4(tokenize=unicode61, id INTEGER PRIMARY KEY, value TEXT)")
|
|
||||||
switch {
|
|
||||||
case err != nil && err.Error() == "unknown tokenizer: unicode61":
|
|
||||||
t.Skip("FTS4 not supported")
|
|
||||||
case err != nil:
|
|
||||||
t.Fatal("Failed to create table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.Exec("INSERT INTO foo(id, value) VALUES(?, ?)", 1, `février`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Failed to insert value:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query("SELECT value FROM foo WHERE value MATCH 'fevrier'")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to query foo table:", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var value string
|
|
||||||
if !rows.Next() {
|
|
||||||
t.Fatal("Result should be only one")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rows.Scan(&value); err != nil {
|
|
||||||
t.Fatal("Unable to scan results:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value != `février` {
|
|
||||||
t.Fatal("Value should be `février`, but:", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows.Next() {
|
|
||||||
t.Fatal("Result should be only one")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build fts5
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS5
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build icu
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -licuuc -licui18n
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_ICU
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build json1
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build libsqlite3
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DUSE_LIBSQLITE3
|
|
||||||
#cgo linux LDFLAGS: -lsqlite3
|
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build !sqlite_omit_load_extension
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *SQLiteConn) loadExtensions(extensions []string) error {
|
|
||||||
rv := C.sqlite3_enable_load_extension(c.db, 1)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, extension := range extensions {
|
|
||||||
cext := C.CString(extension)
|
|
||||||
defer C.free(unsafe.Pointer(cext))
|
|
||||||
rv = C.sqlite3_load_extension(c.db, cext, nil, nil)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = C.sqlite3_enable_load_extension(c.db, 0)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
|
|
||||||
rv := C.sqlite3_enable_load_extension(c.db, 1)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
clib := C.CString(lib)
|
|
||||||
defer C.free(unsafe.Pointer(clib))
|
|
||||||
centry := C.CString(entry)
|
|
||||||
defer C.free(unsafe.Pointer(centry))
|
|
||||||
|
|
||||||
rv = C.sqlite3_load_extension(c.db, clib, centry, nil)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = C.sqlite3_enable_load_extension(c.db, 0)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build sqlite_omit_load_extension
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_OMIT_LOAD_EXTENSION
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *SQLiteConn) loadExtensions(extensions []string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user