Browse Source

Use in-memory, on-disk-backed DB

- removed sqlite
- added stubs for MemDB
- removed sqlite3 vendor

Fixes #7

Change-Id: I97b7f274be8db5ff02d9a4e4b8f616403fd6313a
Stephen McQuay 3 years ago
parent
commit
c575677088
No known key found for this signature in database
61 changed files with 254 additions and 210214 deletions
  1. 25
    24
      api_test.go
  2. 23
    30
      cmd/vaind/main.go
  3. 160
    261
      db.go
  4. 7
    7
      mail.go
  5. 7
    7
      server.go
  6. 0
    18
      sql/init.sql
  7. 0
    237
      sql/static.go
  8. 9
    7
      storage.go
  9. 5
    9
      testing.go
  10. 17
    8
      vain.go
  11. 1
    1
      vain_test.go
  12. 0
    24
      vendor/github.com/jmoiron/sqlx/.gitignore
  13. 0
    23
      vendor/github.com/jmoiron/sqlx/LICENSE
  14. 0
    185
      vendor/github.com/jmoiron/sqlx/README.md
  15. 0
    186
      vendor/github.com/jmoiron/sqlx/bind.go
  16. 0
    12
      vendor/github.com/jmoiron/sqlx/doc.go
  17. 0
    336
      vendor/github.com/jmoiron/sqlx/named.go
  18. 0
    227
      vendor/github.com/jmoiron/sqlx/named_test.go
  19. 0
    17
      vendor/github.com/jmoiron/sqlx/reflectx/README.md
  20. 0
    371
      vendor/github.com/jmoiron/sqlx/reflectx/reflect.go
  21. 0
    896
      vendor/github.com/jmoiron/sqlx/reflectx/reflect_test.go
  22. 0
    992
      vendor/github.com/jmoiron/sqlx/sqlx.go
  23. 0
    1674
      vendor/github.com/jmoiron/sqlx/sqlx_test.go
  24. 0
    5
      vendor/github.com/jmoiron/sqlx/types/README.md
  25. 0
    106
      vendor/github.com/jmoiron/sqlx/types/types.go
  26. 0
    42
      vendor/github.com/jmoiron/sqlx/types/types_test.go
  27. 0
    3
      vendor/github.com/mattn/go-sqlite3/.gitignore
  28. 0
    13
      vendor/github.com/mattn/go-sqlite3/.travis.yml
  29. 0
    21
      vendor/github.com/mattn/go-sqlite3/LICENSE
  30. 0
    81
      vendor/github.com/mattn/go-sqlite3/README.md
  31. 0
    133
      vendor/github.com/mattn/go-sqlite3/_example/custom_func/main.go
  32. 0
    71
      vendor/github.com/mattn/go-sqlite3/_example/hook/hook.go
  33. 0
    22
      vendor/github.com/mattn/go-sqlite3/_example/mod_regexp/Makefile
  34. 0
    43
      vendor/github.com/mattn/go-sqlite3/_example/mod_regexp/extension.go
  35. 0
    31
      vendor/github.com/mattn/go-sqlite3/_example/mod_regexp/sqlite3_mod_regexp.c
  36. 0
    24
      vendor/github.com/mattn/go-sqlite3/_example/mod_vtable/Makefile
  37. 0
    36
      vendor/github.com/mattn/go-sqlite3/_example/mod_vtable/extension.go
  38. 0
    1040
      vendor/github.com/mattn/go-sqlite3/_example/mod_vtable/picojson.h
  39. 0
    238
      vendor/github.com/mattn/go-sqlite3/_example/mod_vtable/sqlite3_mod_vtable.cc
  40. 0
    106
      vendor/github.com/mattn/go-sqlite3/_example/simple/simple.go
  41. 0
    74
      vendor/github.com/mattn/go-sqlite3/backup.go
  42. 0
    336
      vendor/github.com/mattn/go-sqlite3/callback.go
  43. 0
    97
      vendor/github.com/mattn/go-sqlite3/callback_test.go
  44. 0
    112
      vendor/github.com/mattn/go-sqlite3/doc.go
  45. 0
    128
      vendor/github.com/mattn/go-sqlite3/error.go
  46. 0
    242
      vendor/github.com/mattn/go-sqlite3/error_test.go
  47. 0
    189319
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
  48. 0
    8733
      vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
  49. 0
    1006
      vendor/github.com/mattn/go-sqlite3/sqlite3.go
  50. 0
    130
      vendor/github.com/mattn/go-sqlite3/sqlite3_fts3_test.go
  51. 0
    13
      vendor/github.com/mattn/go-sqlite3/sqlite3_fts5.go
  52. 0
    13
      vendor/github.com/mattn/go-sqlite3/sqlite3_icu.go
  53. 0
    12
      vendor/github.com/mattn/go-sqlite3/sqlite3_json1.go
  54. 0
    14
      vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
  55. 0
    63
      vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
  56. 0
    23
      vendor/github.com/mattn/go-sqlite3/sqlite3_omit_load_extension.go
  57. 0
    13
      vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
  58. 0
    1350
      vendor/github.com/mattn/go-sqlite3/sqlite3_test.go
  59. 0
    409
      vendor/github.com/mattn/go-sqlite3/sqlite3_test/sqltest.go
  60. 0
    14
      vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
  61. 0
    546
      vendor/github.com/mattn/go-sqlite3/sqlite3ext.h

+ 25
- 24
api_test.go View File

@@ -17,7 +17,7 @@ import (
17 17
 const window = 5 * time.Minute
18 18
 
19 19
 func TestAdd(t *testing.T) {
20
-	db, done := testDB(t)
20
+	db, done := TestDB(t)
21 21
 	if db == nil {
22 22
 		t.Fatalf("could not create temp db")
23 23
 	}
@@ -28,7 +28,7 @@ func TestAdd(t *testing.T) {
28 28
 	ts := httptest.NewServer(sm)
29 29
 	tok, err := db.addUser("sm@example.org")
30 30
 	if err != nil {
31
-		t.Fatalf("failure to add user: %v", err)
31
+		t.Errorf("failure to add user: %v", err)
32 32
 	}
33 33
 
34 34
 	resp, err := http.Get(ts.URL)
@@ -109,7 +109,7 @@ func TestAdd(t *testing.T) {
109 109
 
110 110
 		good := fmt.Sprintf("%s/foo", ur.Host)
111 111
 
112
-		if !db.PackageExists(good) {
112
+		if !db.PackageExists(path(good)) {
113 113
 			t.Fatalf("did not find package for %s; should have posted a valid package", good)
114 114
 		}
115 115
 		p, err := db.Package(good)
@@ -163,7 +163,7 @@ func TestAdd(t *testing.T) {
163 163
 }
164 164
 
165 165
 func TestInvalidPath(t *testing.T) {
166
-	db, done := testDB(t)
166
+	db, done := TestDB(t)
167 167
 	if db == nil {
168 168
 		t.Fatalf("could not create temp db")
169 169
 	}
@@ -195,7 +195,7 @@ func TestInvalidPath(t *testing.T) {
195 195
 }
196 196
 
197 197
 func TestCannotDuplicateExistingPath(t *testing.T) {
198
-	db, done := testDB(t)
198
+	db, done := TestDB(t)
199 199
 	if db == nil {
200 200
 		t.Fatalf("could not create temp db")
201 201
 	}
@@ -241,7 +241,7 @@ func TestCannotDuplicateExistingPath(t *testing.T) {
241 241
 }
242 242
 
243 243
 func TestCannotAddExistingSubPath(t *testing.T) {
244
-	db, done := testDB(t)
244
+	db, done := TestDB(t)
245 245
 	if db == nil {
246 246
 		t.Fatalf("could not create temp db")
247 247
 	}
@@ -253,7 +253,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
253 253
 
254 254
 	tok, err := db.addUser("sm@example.org")
255 255
 	if err != nil {
256
-		t.Fatalf("failure to add user: %v", err)
256
+		t.Errorf("failure to add user: %v", err)
257 257
 	}
258 258
 
259 259
 	{
@@ -289,7 +289,7 @@ func TestCannotAddExistingSubPath(t *testing.T) {
289 289
 }
290 290
 
291 291
 func TestMissingRepo(t *testing.T) {
292
-	db, done := testDB(t)
292
+	db, done := TestDB(t)
293 293
 	if db == nil {
294 294
 		t.Fatalf("could not create temp db")
295 295
 	}
@@ -301,7 +301,7 @@ func TestMissingRepo(t *testing.T) {
301 301
 
302 302
 	tok, err := db.addUser("sm@example.org")
303 303
 	if err != nil {
304
-		t.Fatalf("failure to add user: %v", err)
304
+		t.Errorf("failure to add user: %v", err)
305 305
 	}
306 306
 
307 307
 	u := fmt.Sprintf("%s/foo", ts.URL)
@@ -322,7 +322,7 @@ func TestMissingRepo(t *testing.T) {
322 322
 }
323 323
 
324 324
 func TestBadJson(t *testing.T) {
325
-	db, done := testDB(t)
325
+	db, done := TestDB(t)
326 326
 	if db == nil {
327 327
 		t.Fatalf("could not create temp db")
328 328
 	}
@@ -334,7 +334,7 @@ func TestBadJson(t *testing.T) {
334 334
 
335 335
 	tok, err := db.addUser("sm@example.org")
336 336
 	if err != nil {
337
-		t.Fatalf("failure to add user: %v", err)
337
+		t.Errorf("failure to add user: %v", err)
338 338
 	}
339 339
 
340 340
 	u := fmt.Sprintf("%s/foo", ts.URL)
@@ -355,7 +355,7 @@ func TestBadJson(t *testing.T) {
355 355
 }
356 356
 
357 357
 func TestNoAuth(t *testing.T) {
358
-	db, done := testDB(t)
358
+	db, done := TestDB(t)
359 359
 	if db == nil {
360 360
 		t.Fatalf("could not create temp db")
361 361
 	}
@@ -384,7 +384,7 @@ func TestNoAuth(t *testing.T) {
384 384
 }
385 385
 
386 386
 func TestBadVcs(t *testing.T) {
387
-	db, done := testDB(t)
387
+	db, done := TestDB(t)
388 388
 	if db == nil {
389 389
 		t.Fatalf("could not create temp db")
390 390
 	}
@@ -396,7 +396,7 @@ func TestBadVcs(t *testing.T) {
396 396
 
397 397
 	tok, err := db.addUser("sm@example.org")
398 398
 	if err != nil {
399
-		t.Fatalf("failure to add user: %v", err)
399
+		t.Errorf("failure to add user: %v", err)
400 400
 	}
401 401
 
402 402
 	u := fmt.Sprintf("%s/foo", ts.URL)
@@ -415,7 +415,7 @@ func TestBadVcs(t *testing.T) {
415 415
 }
416 416
 
417 417
 func TestUnsupportedMethod(t *testing.T) {
418
-	db, done := testDB(t)
418
+	db, done := TestDB(t)
419 419
 	if db == nil {
420 420
 		t.Fatalf("could not create temp db")
421 421
 	}
@@ -427,7 +427,7 @@ func TestUnsupportedMethod(t *testing.T) {
427 427
 
428 428
 	tok, err := db.addUser("sm@example.org")
429 429
 	if err != nil {
430
-		t.Fatalf("failure to add user: %v", err)
430
+		t.Errorf("failure to add user: %v", err)
431 431
 	}
432 432
 
433 433
 	url := fmt.Sprintf("%s/foo", ts.URL)
@@ -447,7 +447,7 @@ func TestUnsupportedMethod(t *testing.T) {
447 447
 }
448 448
 
449 449
 func TestDelete(t *testing.T) {
450
-	db, done := testDB(t)
450
+	db, done := TestDB(t)
451 451
 	if db == nil {
452 452
 		t.Fatalf("could not create temp db")
453 453
 	}
@@ -459,8 +459,9 @@ func TestDelete(t *testing.T) {
459 459
 
460 460
 	tok, err := db.addUser("sm@example.org")
461 461
 	if err != nil {
462
-		t.Fatalf("failure to add user: %v", err)
462
+		t.Errorf("failure to add user: %v", err)
463 463
 	}
464
+
464 465
 	t.Logf("%v", tok)
465 466
 	if len(db.Pkgs()) != 0 {
466 467
 		t.Fatalf("started with something in it; got %d, want %d", len(db.Pkgs()), 0)
@@ -511,7 +512,7 @@ func TestDelete(t *testing.T) {
511 512
 }
512 513
 
513 514
 func TestSingleGet(t *testing.T) {
514
-	db, done := testDB(t)
515
+	db, done := TestDB(t)
515 516
 	if db == nil {
516 517
 		t.Fatalf("could not create temp db")
517 518
 	}
@@ -523,10 +524,10 @@ func TestSingleGet(t *testing.T) {
523 524
 
524 525
 	tok, err := db.addUser("sm@example.org")
525 526
 	if err != nil {
526
-		t.Fatalf("failure to add user: %v", err)
527
+		t.Errorf("failure to add user: %v", err)
527 528
 	}
528 529
 
529
-	ns := "foo"
530
+	ns := namespace("foo")
530 531
 
531 532
 	if err := db.NSForToken(ns, tok); err != nil {
532 533
 		t.Fatalf("could not initialize namespace %q for user %q: %v", ns, tok, err)
@@ -565,7 +566,7 @@ func TestSingleGet(t *testing.T) {
565 566
 }
566 567
 
567 568
 func TestRegister(t *testing.T) {
568
-	db, done := testDB(t)
569
+	db, done := TestDB(t)
569 570
 	if db == nil {
570 571
 		t.Fatalf("could not create temp db")
571 572
 	}
@@ -618,7 +619,7 @@ func TestRegister(t *testing.T) {
618 619
 }
619 620
 
620 621
 func TestRoundTrip(t *testing.T) {
621
-	db, done := testDB(t)
622
+	db, done := TestDB(t)
622 623
 	if db == nil {
623 624
 		t.Fatalf("could not create temp db")
624 625
 	}
@@ -686,7 +687,7 @@ func TestRoundTrip(t *testing.T) {
686 687
 }
687 688
 
688 689
 func TestForgot(t *testing.T) {
689
-	db, done := testDB(t)
690
+	db, done := TestDB(t)
690 691
 	if db == nil {
691 692
 		t.Fatalf("could not create temp db")
692 693
 	}

+ 23
- 30
cmd/vaind/main.go View File

@@ -45,6 +45,8 @@ import (
45 45
 	"log"
46 46
 	"net/http"
47 47
 	"os"
48
+	"os/signal"
49
+	"syscall"
48 50
 	"time"
49 51
 
50 52
 	"mcquay.me/vain"
@@ -52,7 +54,7 @@ import (
52 54
 	"github.com/kelseyhightower/envconfig"
53 55
 )
54 56
 
55
-const usage = "vaind [init] <dbname>"
57
+const usage = "vaind <dbname>"
56 58
 
57 59
 type config struct {
58 60
 	Port     int
@@ -72,38 +74,12 @@ type config struct {
72 74
 }
73 75
 
74 76
 func main() {
77
+	log.SetFlags(log.Lshortfile)
75 78
 	if len(os.Args) < 2 {
76 79
 		fmt.Fprintf(os.Stderr, "%s\n", usage)
77 80
 		os.Exit(1)
78 81
 	}
79 82
 
80
-	if os.Args[1] == "init" {
81
-		if len(os.Args) != 3 {
82
-			fmt.Fprintf(os.Stderr, "missing db name: %s\n", usage)
83
-			os.Exit(1)
84
-		}
85
-
86
-		db, err := vain.NewDB(os.Args[2])
87
-		if err != nil {
88
-			fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
89
-			os.Exit(1)
90
-		}
91
-		defer db.Close()
92
-
93
-		if err := db.Init(); err != nil {
94
-			fmt.Fprintf(os.Stderr, "problem initializing the db: %v\n", err)
95
-			os.Exit(1)
96
-		}
97
-
98
-		os.Exit(0)
99
-	}
100
-
101
-	db, err := vain.NewDB(os.Args[1])
102
-	if err != nil {
103
-		fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
104
-		os.Exit(1)
105
-	}
106
-
107 83
 	c := &config{
108 84
 		Port:         4040,
109 85
 		EmailTimeout: 5 * time.Minute,
@@ -131,10 +107,27 @@ func main() {
131 107
 			os.Exit(0)
132 108
 		}
133 109
 	}
134
-
135 110
 	log.Printf("%+v", c)
136 111
 
137
-	m, err := vain.NewEmail(c.From, c.SMTPHost, c.SMTPPort)
112
+	db, err := vain.NewMemDB(os.Args[1])
113
+	if err != nil {
114
+		fmt.Fprintf(os.Stderr, "couldn't open db: %v\n", err)
115
+		os.Exit(1)
116
+	}
117
+
118
+	sigs := make(chan os.Signal)
119
+	signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
120
+	go func() {
121
+		s := <-sigs
122
+		log.Printf("signal: %+v", s)
123
+		if err := db.Sync(); err != nil {
124
+			log.Printf("problem syncing db to disk: %+v", err)
125
+			os.Exit(1)
126
+		}
127
+		os.Exit(0)
128
+	}()
129
+
130
+	m, err := vain.NewMail(c.From, c.SMTPHost, c.SMTPPort)
138 131
 	if err != nil {
139 132
 		fmt.Fprintf(os.Stderr, "problem initializing mailer: %v", err)
140 133
 		os.Exit(1)

+ 160
- 261
db.go View File

@@ -1,342 +1,241 @@
1 1
 package vain
2 2
 
3 3
 import (
4
-	"database/sql"
4
+	"encoding/json"
5 5
 	"fmt"
6
-	"log"
7 6
 	"net/http"
7
+	"os"
8
+	"sync"
8 9
 	"time"
9 10
 
10
-	"github.com/jmoiron/sqlx"
11
-	// for side effects
12
-	_ "github.com/mattn/go-sqlite3"
13
-
14 11
 	verrors "mcquay.me/vain/errors"
15
-	vsql "mcquay.me/vain/sql"
16 12
 )
17 13
 
18
-// DB wraps a sqlx.DB connection and provides methods for interating with
19
-// a vain database.
20
-type DB struct {
21
-	conn *sqlx.DB
22
-}
14
+// NewMemDB returns a functional MemDB.
15
+func NewMemDB(p string) (*MemDB, error) {
16
+	m := &MemDB{
17
+		filename: p,
23 18
 
24
-// NewDB opens a sqlite3 file, sets options, and reports errors.
25
-func NewDB(path string) (*DB, error) {
26
-	conn, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc", path))
27
-	if _, err := conn.Exec("PRAGMA foreign_keys = ON"); err != nil {
28
-		return nil, err
29
-	}
30
-	return &DB{conn}, err
31
-}
19
+		Users:      map[Email]User{},
20
+		TokToEmail: map[Token]Email{},
32 21
 
33
-// Init runs the embedded sql to initialize tables.
34
-func (db *DB) Init() error {
35
-	content, err := vsql.Asset("sql/init.sql")
36
-	if err != nil {
37
-		return err
22
+		Packages:   map[path]Package{},
23
+		Namespaces: map[namespace]Email{},
38 24
 	}
39
-	_, err = db.conn.Exec(string(content))
40
-	return err
41
-}
42
-
43
-// Close the underlying connection.
44
-func (db *DB) Close() error {
45
-	return db.conn.Close()
46
-}
47
-
48
-// AddPackage adds p into packages table.
49
-func (db *DB) AddPackage(p Package) error {
50
-	_, err := db.conn.NamedExec(
51
-		"INSERT INTO packages(vcs, repo, path, ns) VALUES (:vcs, :repo, :path, :ns)",
52
-		&p,
53
-	)
54
-	return err
55
-}
56
-
57
-// RemovePackage removes package with given path
58
-func (db *DB) RemovePackage(path string) error {
59
-	_, err := db.conn.Exec("DELETE FROM packages WHERE path = ?", path)
60
-	return err
61
-}
62 25
 
63
-// Pkgs returns all packages from the database
64
-func (db *DB) Pkgs() []Package {
65
-	r := []Package{}
66
-	rows, err := db.conn.Queryx("SELECT * FROM packages")
26
+	f, err := os.Open(p)
67 27
 	if err != nil {
68
-		log.Printf("%+v", err)
69
-		return nil
70
-	}
71
-	for rows.Next() {
72
-		var p Package
73
-		err = rows.StructScan(&p)
74
-		if err != nil {
75
-			log.Printf("%+v", err)
76
-			return nil
77
-		}
78
-		r = append(r, p)
28
+		// file doesn't exist yet
29
+		return m, nil
79 30
 	}
80
-	return r
31
+	err = json.NewDecoder(f).Decode(m)
32
+	return m, err
81 33
 }
82 34
 
83
-// PackageExists tells if a package with path is in the database.
84
-func (db *DB) PackageExists(path string) bool {
85
-	var count int
86
-	if err := db.conn.Get(&count, "SELECT COUNT(*) FROM packages WHERE path = ?", path); err != nil {
87
-		log.Printf("%+v", err)
88
-	}
35
+// MemDB implements an in-memory, and disk-backed database for a vain server.
36
+type MemDB struct {
37
+	filename string
89 38
 
90
-	r := false
91
-	switch count {
92
-	case 1:
93
-		r = true
94
-	default:
95
-		log.Printf("unexpected count of packages matching %q: %d", path, count)
96
-	}
97
-	return r
98
-}
39
+	l sync.RWMutex
99 40
 
100
-// Package fetches the package associated with path.
101
-func (db *DB) Package(path string) (Package, error) {
102
-	r := Package{}
103
-	err := db.conn.Get(&r, "SELECT * FROM packages WHERE path = ?", path)
104
-	if err == sql.ErrNoRows {
105
-		return r, verrors.HTTP{
106
-			Message: fmt.Sprintf("couldn't find package %q", path),
107
-			Code:    http.StatusNotFound,
108
-		}
109
-	}
110
-	return r, err
41
+	Users      map[Email]User
42
+	TokToEmail map[Token]Email
43
+
44
+	Packages   map[path]Package
45
+	Namespaces map[namespace]Email
111 46
 }
112 47
 
113 48
 // NSForToken creates an entry namespaces with a relation to the token.
114
-func (db *DB) NSForToken(ns string, tok string) error {
115
-	var err error
116
-	txn, err := db.conn.Beginx()
117
-	if err != nil {
118
-		return verrors.HTTP{
119
-			Message: fmt.Sprintf("problem creating transaction: %v", err),
120
-			Code:    http.StatusInternalServerError,
121
-		}
122
-	}
123
-	defer func() {
124
-		if err != nil {
125
-			txn.Rollback()
126
-		} else {
127
-			txn.Commit()
128
-		}
129
-	}()
49
+func (m *MemDB) NSForToken(ns namespace, tok Token) error {
50
+	m.l.Lock()
51
+	defer m.l.Unlock()
130 52
 
131
-	var count int
132
-	if err = txn.Get(&count, "SELECT COUNT(*) FROM namespaces WHERE namespaces.ns = ?", ns); err != nil {
53
+	e, ok := m.TokToEmail[tok]
54
+	if !ok {
133 55
 		return verrors.HTTP{
134
-			Message: fmt.Sprintf("problem matching fetching namespaces matching %q", ns),
135
-			Code:    http.StatusInternalServerError,
56
+			Message: fmt.Sprintf("User for token %q not found", tok),
57
+			Code:    http.StatusNotFound,
136 58
 		}
137 59
 	}
138 60
 
139
-	if count == 0 {
140
-		var email string
141
-		if err = txn.Get(&email, "SELECT email FROM users WHERE token = $1", tok); err != nil {
61
+	if owner, ok := m.Namespaces[ns]; !ok {
62
+		m.Namespaces[ns] = e
63
+	} else {
64
+		if m.Namespaces[ns] != owner {
142 65
 			return verrors.HTTP{
143
-				Message: fmt.Sprintf("could not find user for token %q", tok),
144
-				Code:    http.StatusInternalServerError,
66
+				Message: fmt.Sprintf("not authorized against namespace %q", ns),
67
+				Code:    http.StatusUnauthorized,
145 68
 			}
146 69
 		}
147
-		if _, err = txn.Exec(
148
-			"INSERT INTO namespaces(ns, email) VALUES ($1, $2)",
149
-			ns,
150
-			email,
151
-		); err != nil {
152
-			return verrors.HTTP{
153
-				Message: fmt.Sprintf("problem inserting %q into namespaces for token %q: %v", ns, tok, err),
154
-				Code:    http.StatusInternalServerError,
155
-			}
156
-		}
157
-		return err
158
-	}
159
-
160
-	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 {
161
-		return verrors.HTTP{
162
-			Message: fmt.Sprintf("ns: %q, tok: %q; %v", ns, tok, err),
163
-			Code:    http.StatusInternalServerError,
164
-		}
165 70
 	}
71
+	return m.flush(m.filename)
72
+}
166 73
 
167
-	switch count {
168
-	case 1:
169
-		err = nil
170
-	case 0:
171
-		err = verrors.HTTP{
172
-			Message: fmt.Sprintf("not authorized against namespace %q", ns),
173
-			Code:    http.StatusUnauthorized,
174
-		}
175
-	default:
74
+// Package fetches the package associated with path.
75
+func (m *MemDB) Package(pth string) (Package, error) {
76
+	m.l.RLock()
77
+	pkg, ok := m.Packages[path(pth)]
78
+	m.l.RUnlock()
79
+	var err error
80
+	if !ok {
176 81
 		err = verrors.HTTP{
177
-			Message: fmt.Sprintf("inconsistent db; found %d results with ns (%s) with token (%s)", count, ns, tok),
178
-			Code:    http.StatusInternalServerError,
82
+			Message: fmt.Sprintf("couldn't find package %q", pth),
83
+			Code:    http.StatusNotFound,
179 84
 		}
180 85
 	}
181
-	return err
86
+	return pkg, err
182 87
 }
183 88
 
184
-// Register adds email to the database, returning an error if there was one.
185
-func (db *DB) Register(email string) (string, error) {
186
-	var err error
187
-	txn, err := db.conn.Beginx()
188
-	if err != nil {
189
-		return "", verrors.HTTP{
190
-			Message: fmt.Sprintf("problem creating transaction: %v", err),
191
-			Code:    http.StatusInternalServerError,
192
-		}
193
-	}
194
-	defer func() {
195
-		if err != nil {
196
-			txn.Rollback()
197
-		} else {
198
-			txn.Commit()
199
-		}
200
-	}()
89
+// AddPackage adds p into packages table.
90
+func (m *MemDB) AddPackage(p Package) error {
91
+	m.l.Lock()
92
+	m.Packages[path(p.Path)] = p
93
+	m.l.Unlock()
94
+	return m.flush(m.filename)
95
+}
201 96
 
202
-	var count int
203
-	if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE email = ?", email); err != nil {
204
-		return "", verrors.HTTP{
205
-			Message: fmt.Sprintf("could not search for email %q in db: %v", email, err),
206
-			Code:    http.StatusInternalServerError,
207
-		}
208
-	}
97
+// RemovePackage removes package with given path
98
+func (m *MemDB) RemovePackage(pth path) error {
99
+	m.l.Lock()
100
+	delete(m.Packages, pth)
101
+	m.l.Unlock()
102
+	return m.flush(m.filename)
103
+}
104
+
105
+// PackageExists tells if a package with path is in the database.
106
+func (m *MemDB) PackageExists(pth path) bool {
107
+	m.l.RLock()
108
+	_, ok := m.Packages[path(pth)]
109
+	m.l.RUnlock()
110
+	return ok
111
+}
209 112
 
210
-	if count != 0 {
113
+// Pkgs returns all packages from the database
114
+func (m *MemDB) Pkgs() []Package {
115
+	ps := []Package{}
116
+	m.l.RLock()
117
+	for _, p := range m.Packages {
118
+		ps = append(ps, p)
119
+	}
120
+	m.l.RUnlock()
121
+	return ps
122
+}
123
+
124
+// Register adds email to the database, returning an error if there was one.
125
+func (m *MemDB) Register(e Email) (Token, error) {
126
+	m.l.Lock()
127
+	defer m.l.Unlock()
128
+
129
+	if _, ok := m.Users[e]; ok {
211 130
 		return "", verrors.HTTP{
212
-			Message: fmt.Sprintf("duplicate email %q", email),
131
+			Message: fmt.Sprintf("duplicate email %q", e),
213 132
 			Code:    http.StatusConflict,
214 133
 		}
215 134
 	}
216 135
 
217 136
 	tok := FreshToken()
218
-	_, err = txn.Exec(
219
-		"INSERT INTO users(email, token, requested) VALUES (?, ?, ?)",
220
-		email,
221
-		tok,
222
-		time.Now(),
223
-	)
224
-	return tok, err
137
+	m.Users[e] = User{
138
+		Email:     e,
139
+		token:     tok,
140
+		Requested: time.Now(),
141
+	}
142
+	m.TokToEmail[tok] = e
143
+	return tok, m.flush(m.filename)
225 144
 }
226 145
 
227 146
 // Confirm  modifies the user with the given token. Used on register confirmation.
228
-func (db *DB) Confirm(token string) (string, error) {
229
-	var err error
230
-	txn, err := db.conn.Beginx()
231
-	if err != nil {
232
-		return "", verrors.HTTP{
233
-			Message: fmt.Sprintf("problem creating transaction: %v", err),
234
-			Code:    http.StatusInternalServerError,
235
-		}
236
-	}
237
-	defer func() {
238
-		if err != nil {
239
-			txn.Rollback()
240
-		} else {
241
-			txn.Commit()
242
-		}
243
-	}()
244
-
245
-	var count int
246
-	if err = txn.Get(&count, "SELECT COUNT(*) FROM users WHERE token = ?", token); err != nil {
247
-		return "", verrors.HTTP{
248
-			Message: fmt.Sprintf("could not perform search for user with token %q in db: %v", token, err),
249
-			Code:    http.StatusInternalServerError,
250
-		}
251
-	}
147
+func (m *MemDB) Confirm(tok Token) (Token, error) {
148
+	m.l.Lock()
149
+	defer m.l.Unlock()
252 150
 
253
-	if count != 1 {
151
+	e, ok := m.TokToEmail[tok]
152
+	if !ok {
254 153
 		return "", verrors.HTTP{
255
-			Message: fmt.Sprintf("bad token: %s", token),
154
+			Message: fmt.Sprintf("bad token: %s", tok),
256 155
 			Code:    http.StatusNotFound,
257 156
 		}
258 157
 	}
259 158
 
260
-	newToken := FreshToken()
261
-
262
-	_, err = txn.Exec(
263
-		"UPDATE users SET token = ?, registered = 1 WHERE token = ?",
264
-		newToken,
265
-		token,
266
-	)
267
-	if err != nil {
159
+	delete(m.TokToEmail, tok)
160
+	tok = FreshToken()
161
+	u, ok := m.Users[e]
162
+	if !ok {
268 163
 		return "", verrors.HTTP{
269
-			Message: fmt.Sprintf("couldn't update user with token %q: %v", token, err),
164
+			Message: fmt.Sprintf("inconsistent db; found email for token %q, but no user for email %q", tok, e),
270 165
 			Code:    http.StatusInternalServerError,
271 166
 		}
272 167
 	}
273
-	return newToken, nil
168
+	u.token = tok
169
+	m.Users[e] = u
170
+	m.TokToEmail[tok] = e
171
+
172
+	return tok, m.flush(m.filename)
274 173
 }
275 174
 
276
-func (db *DB) forgot(email string, window time.Duration) (string, error) {
277
-	txn, err := db.conn.Beginx()
278
-	if err != nil {
279
-		return "", verrors.HTTP{
280
-			Message: fmt.Sprintf("problem creating transaction: %v", err),
281
-			Code:    http.StatusInternalServerError,
282
-		}
283
-	}
284
-	defer func() {
285
-		if err != nil {
286
-			txn.Rollback()
287
-		} else {
288
-			txn.Commit()
289
-		}
290
-	}()
175
+// Forgot is used fetch a user's token. It implements rudimentary rate
176
+// limiting.
177
+func (m *MemDB) Forgot(e Email, window time.Duration) (Token, error) {
178
+	m.l.Lock()
179
+	defer m.l.Unlock()
291 180
 
292
-	out := struct {
293
-		Token     string
294
-		Requested time.Time
295
-	}{}
296
-	if err = txn.Get(&out, "SELECT token, requested FROM users WHERE email = ?", email); err != nil {
181
+	u, ok := m.Users[e]
182
+	if !ok {
297 183
 		return "", verrors.HTTP{
298
-			Message: fmt.Sprintf("could not find email %q in db", email),
184
+			Message: fmt.Sprintf("could not find email %q in db", e),
299 185
 			Code:    http.StatusNotFound,
300 186
 		}
301 187
 	}
302 188
 
303
-	if out.Requested.After(time.Now()) {
189
+	if u.Requested.After(time.Now()) {
304 190
 		return "", verrors.HTTP{
305
-			Message: fmt.Sprintf("rate limit hit for %q; try again in %0.2f mins", email, out.Requested.Sub(time.Now()).Minutes()),
191
+			Message: fmt.Sprintf("rate limit hit for %q; try again in %0.2f mins", u.Email, u.Requested.Sub(time.Now()).Minutes()),
306 192
 			Code:    http.StatusTooManyRequests,
307 193
 		}
308 194
 	}
309
-	_, err = txn.Exec("UPDATE users SET requested = ? WHERE email = ?", time.Now().Add(window), email)
195
+
196
+	return u.token, nil
197
+}
198
+
199
+// Sync takes a lock, and flushes the data to disk.
200
+func (m *MemDB) Sync() error {
201
+	m.l.RLock()
202
+	defer m.l.RUnlock()
203
+
204
+	return m.flush(m.filename)
205
+}
206
+
207
+// flush writes to disk, but expects the user to have taken the lock.
208
+func (m *MemDB) flush(p string) error {
209
+	f, err := os.Create(p)
310 210
 	if err != nil {
311
-		return "", verrors.HTTP{
312
-			Message: fmt.Sprintf("could not update last requested time for %q: %v", email, err),
313
-			Code:    http.StatusInternalServerError,
314
-		}
211
+		return err
315 212
 	}
316
-	return out.Token, nil
213
+	return json.NewEncoder(f).Encode(&m)
317 214
 }
318 215
 
319
-func (db *DB) addUser(email string) (string, error) {
216
+func (m *MemDB) addUser(e Email) (Token, error) {
320 217
 	tok := FreshToken()
321
-	_, err := db.conn.Exec(
322
-		"INSERT INTO users(email, token, requested) VALUES (?, ?, ?)",
323
-		email,
324
-		tok,
325
-		time.Now(),
326
-	)
327
-	return tok, err
218
+
219
+	m.l.Lock()
220
+	m.Users[e] = User{
221
+		Email:     e,
222
+		token:     tok,
223
+		Requested: time.Now(),
224
+	}
225
+	m.TokToEmail[tok] = e
226
+	m.l.Unlock()
227
+
228
+	return tok, m.flush(m.filename)
328 229
 }
329 230
 
330
-func (db *DB) user(email string) (User, error) {
331
-	u := User{}
332
-	err := db.conn.Get(
333
-		&u,
334
-		"SELECT email, token, registered, requested FROM users WHERE email = ?",
335
-		email,
336
-	)
337
-	if err == sql.ErrNoRows {
338
-		return User{}, verrors.HTTP{
339
-			Message: fmt.Sprintf("could not find requested user's email: %q: %v", email, err),
231
+func (m *MemDB) user(e Email) (User, error) {
232
+	m.l.Lock()
233
+	u, ok := m.Users[e]
234
+	m.l.Unlock()
235
+	var err error
236
+	if !ok {
237
+		err = verrors.HTTP{
238
+			Message: fmt.Sprintf("couldn't find user %q", e),
340 239
 			Code:    http.StatusNotFound,
341 240
 		}
342 241
 	}

+ 7
- 7
mail.go View File

@@ -12,13 +12,13 @@ type Mailer interface {
12 12
 	Send(to mail.Address, subject, msg string) error
13 13
 }
14 14
 
15
-// NewEmail returns *Email struct to be able to send smtp
15
+// NewMail returns *Send struct to be able to send smtp
16 16
 // or an error if it can't correctly parse the email address.
17
-func NewEmail(from, host string, port int) (*Email, error) {
17
+func NewMail(from, host string, port int) (*Mail, error) {
18 18
 	if _, err := mail.ParseAddress(from); err != nil {
19 19
 		return nil, fmt.Errorf("can't parse an email address for 'from': %v", err)
20 20
 	}
21
-	r := &Email{
21
+	r := &Mail{
22 22
 		host: host,
23 23
 		port: port,
24 24
 		from: from,
@@ -26,16 +26,16 @@ func NewEmail(from, host string, port int) (*Email, error) {
26 26
 	return r, nil
27 27
 }
28 28
 
29
-// Email stores information required to use smtp.
30
-type Email struct {
29
+// Mail stores information required to use smtp.
30
+type Mail struct {
31 31
 	host string
32 32
 	port int
33 33
 	from string
34 34
 }
35 35
 
36
-// Send sends a smtp email using the host and port in the Email struct and
36
+// Send sends a smtp email using the host and port in the Mail struct and
37 37
 //returns an error if there was a problem sending the email.
38
-func (e Email) Send(to mail.Address, subject, msg string) error {
38
+func (e Mail) Send(to mail.Address, subject, msg string) error {
39 39
 	c, err := smtp.Dial(fmt.Sprintf("%s:%d", e.host, e.port))
40 40
 	if err != nil {
41 41
 		return fmt.Errorf("couldn't dial mail server: %v", err)

+ 7
- 7
server.go View File

@@ -91,7 +91,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
91 91
 		return
92 92
 	}
93 93
 
94
-	if err := verrors.ToHTTP(s.db.NSForToken(ns, tok)); err != nil {
94
+	if err := verrors.ToHTTP(s.db.NSForToken(ns, Token(tok))); err != nil {
95 95
 		http.Error(w, err.Message, err.Code)
96 96
 		return
97 97
 	}
@@ -130,12 +130,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
130 130
 		}
131 131
 	case "DELETE":
132 132
 		p := fmt.Sprintf("%s/%s", req.Host, strings.Trim(req.URL.Path, "/"))
133
-		if !s.db.PackageExists(p) {
133
+		if !s.db.PackageExists(path(p)) {
134 134
 			http.Error(w, fmt.Sprintf("package %q not found", p), http.StatusNotFound)
135 135
 			return
136 136
 		}
137 137
 
138
-		if err := s.db.RemovePackage(p); err != nil {
138
+		if err := s.db.RemovePackage(path(p)); err != nil {
139 139
 			http.Error(w, fmt.Sprintf("unable to delete package: %v", err), http.StatusInternalServerError)
140 140
 			return
141 141
 		}
@@ -158,7 +158,7 @@ func (s *Server) register(w http.ResponseWriter, req *http.Request) {
158 158
 		return
159 159
 	}
160 160
 
161
-	tok, err := s.db.Register(addr.Address)
161
+	tok, err := s.db.Register(Email(addr.Address))
162 162
 	if err := verrors.ToHTTP(err); err != nil {
163 163
 		http.Error(w, err.Message, err.Code)
164 164
 		return
@@ -194,12 +194,12 @@ func (s *Server) confirm(w http.ResponseWriter, req *http.Request) {
194 194
 		http.Error(w, "must provide one email parameter", http.StatusBadRequest)
195 195
 		return
196 196
 	}
197
-	tok, err := s.db.Confirm(tok)
197
+	ttok, err := s.db.Confirm(Token(tok))
198 198
 	if err := verrors.ToHTTP(err); err != nil {
199 199
 		http.Error(w, err.Message, err.Code)
200 200
 		return
201 201
 	}
202
-	fmt.Fprintf(w, "new token: %s\n", tok)
202
+	fmt.Fprintf(w, "new token: %s\n", ttok)
203 203
 }
204 204
 
205 205
 func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
@@ -216,7 +216,7 @@ func (s *Server) forgot(w http.ResponseWriter, req *http.Request) {
216 216
 		return
217 217
 	}
218 218
 
219
-	tok, err := s.db.forgot(addr.Address, s.emailTimeout)
219
+	tok, err := s.db.Forgot(Email(addr.Address), s.emailTimeout)
220 220
 	if err := verrors.ToHTTP(err); err != nil {
221 221
 		http.Error(w, err.Message, err.Code)
222 222
 		return

+ 0
- 18
sql/init.sql View File

@@ -1,18 +0,0 @@
1
-CREATE TABLE users (
2
-    email TEXT PRIMARY KEY,
3
-    token TEXT UNIQUE,
4
-    registered boolean DEFAULT 0,
5
-    requested DATETIME
6
-);
7
-
8
-CREATE TABLE namespaces (
9
-    ns TEXT PRIMARY KEY,
10
-    email TEXT REFERENCES users(email) ON DELETE CASCADE
11
-);
12
-
13
-CREATE TABLE packages (
14
-    vcs TEXT,
15
-    repo TEXT,
16
-    path TEXT UNIQUE,
17
-    ns TEXT REFERENCES namespaces(ns) ON DELETE CASCADE
18
-);

+ 0
- 237
sql/static.go View File

@@ -1,237 +0,0 @@
1
-// Code generated by go-bindata.
2
-// sources:
3
-// sql/init.sql
4
-// DO NOT EDIT!
5
-
6
-package sql
7
-
8
-import (
9
-	"bytes"
10
-	"compress/gzip"
11
-	"fmt"
12
-	"io"
13
-	"io/ioutil"
14
-	"os"
15
-	"path/filepath"
16
-	"strings"
17
-	"time"
18
-)
19
-
20
-func bindataRead(data []byte, name string) ([]byte, error) {
21
-	gz, err := gzip.NewReader(bytes.NewBuffer(data))
22
-	if err != nil {
23
-		return nil, fmt.Errorf("Read %q: %v", name, err)
24
-	}
25
-
26
-	var buf bytes.Buffer
27
-	_, err = io.Copy(&buf, gz)
28
-	clErr := gz.Close()
29
-
30
-	if err != nil {
31
-		return nil, fmt.Errorf("Read %q: %v", name, err)
32
-	}
33
-	if clErr != nil {
34
-		return nil, err
35
-	}
36
-
37
-	return buf.Bytes(), nil
38
-}
39
-
40
-type asset struct {
41
-	bytes []byte
42
-	info  os.FileInfo
43
-}
44
-
45
-type bindataFileInfo struct {
46
-	name    string
47
-	size    int64
48
-	mode    os.FileMode
49
-	modTime time.Time
50
-}
51
-
52
-func (fi bindataFileInfo) Name() string {
53
-	return fi.name
54
-}
55
-func (fi bindataFileInfo) Size() int64 {
56
-	return fi.size
57
-}
58
-func (fi bindataFileInfo) Mode() os.FileMode {
59
-	return fi.mode
60
-}
61
-func (fi bindataFileInfo) ModTime() time.Time {
62
-	return fi.modTime
63
-}
64
-func (fi bindataFileInfo) IsDir() bool {
65
-	return false
66
-}
67
-func (fi bindataFileInfo) Sys() interface{} {
68
-	return nil
69
-}
70
-
71
-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")
72
-
73
-func sqlInitSqlBytes() ([]byte, error) {
74
-	return bindataRead(
75
-		_sqlInitSql,
76
-		"sql/init.sql",
77
-	)
78
-}
79
-
80
-func sqlInitSql() (*asset, error) {
81
-	bytes, err := sqlInitSqlBytes()
82
-	if err != nil {
83
-		return nil, err
84
-	}
85
-
86
-	info := bindataFileInfo{name: "sql/init.sql", size: 379, mode: os.FileMode(436), modTime: time.Unix(1461818129, 0)}
87
-	a := &asset{bytes: bytes, info: info}
88
-	return a, nil
89
-}
90
-
91
-// Asset loads and returns the asset for the given name.
92
-// It returns an error if the asset could not be found or
93
-// could not be loaded.
94
-func Asset(name string) ([]byte, error) {
95
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
96
-	if f, ok := _bindata[cannonicalName]; ok {
97
-		a, err := f()
98
-		if err != nil {
99
-			return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
100
-		}
101
-		return a.bytes, nil
102
-	}
103
-	return nil, fmt.Errorf("Asset %s not found", name)
104
-}
105
-
106
-// MustAsset is like Asset but panics when Asset would return an error.
107
-// It simplifies safe initialization of global variables.
108
-func MustAsset(name string) []byte {
109
-	a, err := Asset(name)
110
-	if err != nil {
111
-		panic("asset: Asset(" + name + "): " + err.Error())
112
-	}
113
-
114
-	return a
115
-}
116
-
117
-// AssetInfo loads and returns the asset info for the given name.
118
-// It returns an error if the asset could not be found or
119
-// could not be loaded.
120
-func AssetInfo(name string) (os.FileInfo, error) {
121
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
122
-	if f, ok := _bindata[cannonicalName]; ok {
123
-		a, err := f()
124
-		if err != nil {
125
-			return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
126
-		}
127
-		return a.info, nil
128
-	}
129
-	return nil, fmt.Errorf("AssetInfo %s not found", name)
130
-}
131
-
132
-// AssetNames returns the names of the assets.
133
-func AssetNames() []string {
134
-	names := make([]string, 0, len(_bindata))
135
-	for name := range _bindata {
136
-		names = append(names, name)
137
-	}
138
-	return names
139
-}
140
-
141
-// _bindata is a table, holding each asset generator, mapped to its name.
142
-var _bindata = map[string]func() (*asset, error){
143
-	"sql/init.sql": sqlInitSql,
144
-}
145
-
146
-// AssetDir returns the file names below a certain
147
-// directory embedded in the file by go-bindata.
148
-// For example if you run go-bindata on data/... and data contains the
149
-// following hierarchy:
150
-//     data/
151
-//       foo.txt
152
-//       img/
153
-//         a.png
154
-//         b.png
155
-// then AssetDir("data") would return []string{"foo.txt", "img"}
156
-// AssetDir("data/img") would return []string{"a.png", "b.png"}
157
-// AssetDir("foo.txt") and AssetDir("notexist") would return an error
158
-// AssetDir("") will return []string{"data"}.
159
-func AssetDir(name string) ([]string, error) {
160
-	node := _bintree
161
-	if len(name) != 0 {
162
-		cannonicalName := strings.Replace(name, "\\", "/", -1)
163
-		pathList := strings.Split(cannonicalName, "/")
164
-		for _, p := range pathList {
165
-			node = node.Children[p]
166
-			if node == nil {
167
-				return nil, fmt.Errorf("Asset %s not found", name)
168
-			}
169
-		}
170
-	}
171
-	if node.Func != nil {
172
-		return nil, fmt.Errorf("Asset %s not found", name)
173
-	}
174
-	rv := make([]string, 0, len(node.Children))
175
-	for childName := range node.Children {
176
-		rv = append(rv, childName)
177
-	}
178
-	return rv, nil
179
-}
180
-
181
-type bintree struct {
182
-	Func     func() (*asset, error)
183
-	Children map[string]*bintree
184
-}
185
-var _bintree = &bintree{nil, map[string]*bintree{
186
-	"sql": &bintree{nil, map[string]*bintree{
187
-		"init.sql": &bintree{sqlInitSql, map[string]*bintree{}},
188
-	}},
189
-}}
190
-
191
-// RestoreAsset restores an asset under the given directory
192
-func RestoreAsset(dir, name string) error {
193
-	data, err := Asset(name)
194
-	if err != nil {
195
-		return err
196
-	}
197
-	info, err := AssetInfo(name)
198
-	if err != nil {
199
-		return err
200
-	}
201
-	err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
202
-	if err != nil {
203
-		return err
204
-	}
205
-	err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
206
-	if err != nil {
207
-		return err
208
-	}
209
-	err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
210
-	if err != nil {
211
-		return err
212
-	}
213
-	return nil
214
-}
215
-
216
-// RestoreAssets restores an asset under the given directory recursively
217
-func RestoreAssets(dir, name string) error {
218
-	children, err := AssetDir(name)
219
-	// File
220
-	if err != nil {
221
-		return RestoreAsset(dir, name)
222
-	}
223
-	// Dir
224
-	for _, child := range children {
225
-		err = RestoreAssets(dir, filepath.Join(name, child))
226
-		if err != nil {
227
-			return err
228
-		}
229
-	}
230
-	return nil
231
-}
232
-
233
-func _filePath(dir, name string) string {
234
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
235
-	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
236
-}
237
-

+ 9
- 7
storage.go View File

@@ -4,13 +4,15 @@ import "time"
4 4
 
5 5
 // Storer defines the db interface.
6 6
 type Storer interface {
7
-	AddPackage(p Package) error
8
-	Confirm(token string) (string, error)
9
-	NSForToken(ns string, tok string) error
7
+	NSForToken(ns namespace, tok Token) error
8
+
10 9
 	Package(path string) (Package, error)
11
-	PackageExists(path string) bool
10
+	AddPackage(p Package) error
11
+	RemovePackage(pth path) error
12
+	PackageExists(pth path) bool
12 13
 	Pkgs() []Package
13
-	Register(email string) (string, error)
14
-	RemovePackage(path string) error
15
-	forgot(email string, window time.Duration) (string, error)
14
+
15
+	Register(e Email) (Token, error)
16
+	Confirm(tok Token) (Token, error)
17
+	Forgot(e Email, window time.Duration) (Token, error)
16 18
 }

+ 5
- 9
testing.go View File

@@ -7,25 +7,21 @@ import (
7 7
 	"testing"
8 8
 )
9 9
 
10
-func testDB(t *testing.T) (*DB, func()) {
10
+// TestDB returns a populated MemDB in a temp location, as well as a function
11
+// to call at cleanup time.
12
+func TestDB(t *testing.T) (*MemDB, func()) {
11 13
 	dir, err := ioutil.TempDir("", "vain-testing-")
12 14
 	if err != nil {
13 15
 		t.Fatalf("could not create tmpdir for db: %v", err)
14 16
 		return nil, func() {}
15 17
 	}
16
-	name := filepath.Join(dir, "test.db")
17
-	db, err := NewDB(name)
18
+	name := filepath.Join(dir, "test.json")
19
+	db, err := NewMemDB(name)
18 20
 	if err != nil {
19 21
 		t.Fatalf("could not create db: %v", err)
20 22
 		return nil, func() {}
21 23
 	}
22
-
23
-	if err := db.Init(); err != nil {
24
-		return nil, func() {}
25
-	}
26
-
27 24
 	return db, func() {
28
-		db.Close()
29 25
 		if err := os.RemoveAll(dir); err != nil {
30 26
 			t.Fatalf("could not clean up tmpdir: %v", err)
31 27
 		}

+ 17
- 8
vain.go View File

@@ -14,6 +14,15 @@ import (
14 14
 	"time"
15 15
 )
16 16
 
17
+// Email is a vain type for storing email addresses.
18
+type Email string
19
+
20
+// Token is a vain type for an api token.
21
+type Token string
22
+
23
+type namespace string
24
+type path string
25
+
17 26
 var vcss = map[string]bool{
18 27
 	"hg":  true,
19 28
 	"git": true,
@@ -38,15 +47,15 @@ type Package struct {
38 47
 	// Repo: the remote repository url
39 48
 	Repo string `json:"repo"`
40 49
 
41
-	Path string `json:"path"`
42
-	Ns   string `json:"-"`
50
+	Path string    `json:"path"`
51
+	Ns   namespace `json:"-"`
43 52
 }
44 53
 
45 54
 // User stores the information about a user including email used, their
46 55
 // token, whether they have registerd and the requested timestamp
47 56
 type User struct {
48
-	Email      string
49
-	Token      string
57
+	Email      Email
58
+	token      Token
50 59
 	Registered bool
51 60
 	Requested  time.Time
52 61
 }
@@ -84,17 +93,17 @@ func Valid(p string, packages []Package) bool {
84 93
 	return true
85 94
 }
86 95
 
87
-func parseNamespace(path string) (string, error) {
96
+func parseNamespace(path string) (namespace, error) {
88 97
 	path = strings.TrimLeft(path, "/")
89 98
 	if path == "" {
90 99
 		return "", errors.New("path does not contain namespace")
91 100
 	}
92 101
 	elems := strings.Split(path, "/")
93
-	return elems[0], nil
102
+	return namespace(elems[0]), nil
94 103
 }
95 104
 
96 105
 // FreshToken returns a random token string.
97
-func FreshToken() string {
106
+func FreshToken() Token {
98 107
 	buf := &bytes.Buffer{}
99 108
 	io.Copy(buf, io.LimitReader(rand.Reader, 6))
100 109
 	s := hex.EncodeToString(buf.Bytes())
@@ -102,5 +111,5 @@ func FreshToken() string {
102 111
 	for i := 0; i < len(s)/4; i++ {
103 112
 		r = append(r, s[i*4:(i+1)*4])
104 113
 	}
105
-	return strings.Join(r, "-")
114
+	return Token(strings.Join(r, "-"))
106 115
 }

+ 1
- 1
vain_test.go View File

@@ -132,7 +132,7 @@ func TestValid(t *testing.T) {
132 132
 func TestNamespaceParsing(t *testing.T) {
133 133
 	tests := []struct {
134 134
 		input string
135
-		want  string
135
+		want  namespace
136 136
 		err   error
137 137
 	}{
138 138
 		{

+ 0
- 24
vendor/github.com/jmoiron/sqlx/.gitignore View File

@@ -1,24 +0,0 @@
1
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
2
-*.o
3
-*.a
4
-*.so
5
-
6
-# Folders
7
-_obj
8
-_test
9
-
10
-# Architecture specific extensions/prefixes
11
-*.[568vq]
12
-[568vq].out
13
-
14
-*.cgo1.go
15
-*.cgo2.c
16
-_cgo_defun.c
17
-_cgo_gotypes.go
18
-_cgo_export.*
19
-
20
-_testmain.go
21
-
22
-*.exe
23
-tags
24
-environ

+ 0
- 23
vendor/github.com/jmoiron/sqlx/LICENSE View File

@@ -1,23 +0,0 @@
1
- Copyright (c) 2013, Jason Moiron
2
-
3
- Permission is hereby granted, free of charge, to any person
4
- obtaining a copy of this software and associated documentation
5
- files (the "Software"), to deal in the Software without
6
- restriction, including without limitation the rights to use,
7
- copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the
9
- Software is furnished to do so, subject to the following
10
- conditions:
11
-
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-

+ 0
- 185
vendor/github.com/jmoiron/sqlx/README.md View File

@@ -1,185 +0,0 @@
1
-#sqlx
2
-
3
-[![Build Status](https://drone.io/github.com/jmoiron/sqlx/status.png)](https://drone.io/github.com/jmoiron/sqlx/latest) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
4
-
5
-sqlx is a library which provides a set of extensions on go's standard
6
-`database/sql` library.  The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
7
-et al. all leave the underlying interfaces untouched, so that their interfaces
8
-are a superset on the standard ones.  This makes it relatively painless to
9
-integrate existing codebases using database/sql with sqlx.
10
-
11
-Major additional concepts are:
12
-
13
-* Marshal rows into structs (with embedded struct support), maps, and slices
14
-* Named parameter support including prepared statements
15
-* `Get` and `Select` to go quickly from query to struct/slice
16
-
17
-In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
18
-there is also some [standard documentation](http://jmoiron.github.io/sqlx/) that
19
-explains how to use `database/sql` along with sqlx.
20
-
21
-## Recent Changes
22
-
23
-* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
24
-
25
-This breaks backwards compatibility, but it's in a way that is trivially fixable
26
-(`s/JsonText/JSONText/g`).  The `types` package is both experimental and not in
27
-active development currently.
28
-
29
-More importantly, [golang bug #13905](https://github.com/golang/go/issues/13905)
30
-makes `types.JSONText` and `types.GzippedText` _potentially unsafe_, **especially**
31
-when used with common auto-scan sqlx idioms like `Select` and `Get`.
32
-
33
-### Backwards Compatibility
34
-
35
-There is no Go1-like promise of absolute stability, but I take the issue seriously
36
-and will maintain the library in a compatible state unless vital bugs prevent me 
37
-from doing so.  Since [#59](https://github.com/jmoiron/sqlx/issues/59) and 
38
-[#60](https://github.com/jmoiron/sqlx/issues/60) necessitated breaking behavior, 
39
-a wider API cleanup was done at the time of fixing.  It's possible this will happen
40
-in future;  if it does, a git tag will be provided for users requiring the old
41
-behavior to continue to use it until such a time as they can migrate.
42
-
43
-## install
44
-
45
-    go get github.com/jmoiron/sqlx
46
-
47
-## issues
48
-
49
-Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
50
-`Columns()` does not fully qualify column names in queries like:
51
-
52
-```sql
53
-SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
54
-```
55
-
56
-making a struct or map destination ambiguous.  Use `AS` in your queries
57
-to give columns distinct names, `rows.Scan` to scan them manually, or 
58
-`SliceScan` to get a slice of results.
59
-
60
-## usage
61
-
62
-Below is an example which shows some common use cases for sqlx.  Check 
63
-[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
64
-usage.
65
-
66
-
67
-```go
68
-package main
69
-
70
-import (
71
-    _ "github.com/lib/pq"
72
-    "database/sql"
73
-    "github.com/jmoiron/sqlx"
74
-    "log"
75
-)
76
-
77
-var schema = `
78
-CREATE TABLE person (
79
-    first_name text,
80
-    last_name text,
81
-    email text
82
-);
83
-
84
-CREATE TABLE place (
85
-    country text,
86
-    city text NULL,
87
-    telcode integer
88
-)`
89
-
90
-type Person struct {
91
-    FirstName string `db:"first_name"`
92
-    LastName  string `db:"last_name"`
93
-    Email     string
94
-}
95
-
96
-type Place struct {
97
-    Country string
98
-    City    sql.NullString
99
-    TelCode int
100
-}
101
-
102
-func main() {
103
-    // this Pings the database trying to connect, panics on error
104
-    // use sqlx.Open() for sql.Open() semantics
105
-    db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
106
-    if err != nil {
107
-        log.Fatalln(err)
108
-    }
109
-
110
-    // exec the schema or fail; multi-statement Exec behavior varies between
111
-    // database drivers;  pq will exec them all, sqlite3 won't, ymmv
112
-    db.MustExec(schema)
113
-    
114
-    tx := db.MustBegin()
115
-    tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
116
-    tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
117
-    tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
118
-    tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
119
-    tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
120
-    // 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
121
-    tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
122
-    tx.Commit()
123
-
124
-    // Query the database, storing results in a []Person (wrapped in []interface{})
125
-    people := []Person{}
126
-    db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
127
-    jason, john := people[0], people[1]
128
-
129
-    fmt.Printf("%#v\n%#v", jason, john)
130
-    // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
131
-    // Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
132
-
133
-    // You can also get a single result, a la QueryRow
134
-    jason = Person{}
135
-    err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
136
-    fmt.Printf("%#v\n", jason)
137
-    // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
138
-
139
-    // if you have null fields and use SELECT *, you must use sql.Null* in your struct
140
-    places := []Place{}
141
-    err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
142
-    if err != nil {
143
-        fmt.Println(err)
144
-        return
145
-    }
146
-    usa, singsing, honkers := places[0], places[1], places[2]
147
-    
148
-    fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
149
-    // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
150
-    // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
151
-    // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
152
-
153
-    // Loop through rows using only one struct
154
-    place := Place{}
155
-    rows, err := db.Queryx("SELECT * FROM place")
156
-    for rows.Next() {
157
-        err := rows.StructScan(&place)
158
-        if err != nil {
159
-            log.Fatalln(err)
160
-        } 
161
-        fmt.Printf("%#v\n", place)
162
-    }
163
-    // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
164
-    // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
165
-    // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
166
-
167
-    // Named queries, using `:name` as the bindvar.  Automatic bindvar support
168
-    // which takes into account the dbtype based on the driverName on sqlx.Open/Connect
169
-    _, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`, 
170
-        map[string]interface{}{
171
-            "first": "Bin",
172
-            "last": "Smuth",
173
-            "email": "bensmith@allblacks.nz",
174
-    })
175
-
176
-    // Selects Mr. Smith from the database
177
-    rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
178
-
179
-    // Named queries can also use structs.  Their bind names follow the same rules
180
-    // as the name -> db mapping, so struct fields are lowercased and the `db` tag
181
-    // is taken into consideration.
182
-    rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
183
-}
184
-```
185
-

+ 0
- 186
vendor/github.com/jmoiron/sqlx/bind.go View File

@@ -1,186 +0,0 @@
1
-package sqlx
2
-
3
-import (
4
-	"bytes"
5
-	"errors"
6
-	"reflect"
7
-	"strconv"
8
-	"strings"
9
-
10
-	"github.com/jmoiron/sqlx/reflectx"
11
-)
12
-
13
-// Bindvar types supported by Rebind, BindMap and BindStruct.
14
-const (
15
-	UNKNOWN = iota
16
-	QUESTION
17
-	DOLLAR
18
-	NAMED
19
-)
20
-
21
-// BindType returns the bindtype for a given database given a drivername.
22
-func BindType(driverName string) int {
23
-	switch driverName {
24
-	case "postgres", "pgx":
25
-		return DOLLAR
26
-	case "mysql":
27
-		return QUESTION
28
-	case "sqlite3":
29
-		return QUESTION
30
-	case "oci8", "ora", "goracle":
31
-		return NAMED
32
-	}
33
-	return UNKNOWN
34
-}
35
-
36
-// FIXME: this should be able to be tolerant of escaped ?'s in queries without
37
-// losing much speed, and should be to avoid confusion.
38
-
39
-// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
40
-func Rebind(bindType int, query string) string {
41
-	switch bindType {
42
-	case QUESTION, UNKNOWN:
43
-		return query
44
-	}
45
-
46
-	qb := []byte(query)
47
-	// Add space enough for 10 params before we have to allocate
48
-	rqb := make([]byte, 0, len(qb)+10)
49
-	j := 1
50
-	for _, b := range qb {
51
-		if b == '?' {
52
-			switch bindType {
53
-			case DOLLAR:
54
-				rqb = append(rqb, '$')
55
-			case NAMED:
56
-				rqb = append(rqb, ':', 'a', 'r', 'g')
57
-			}
58
-			for _, b := range strconv.Itoa(j) {
59
-				rqb = append(rqb, byte(b))
60
-			}
61
-			j++
62
-		} else {
63
-			rqb = append(rqb, b)
64
-		}
65
-	}
66
-	return string(rqb)
67
-}
68
-
69
-// Experimental implementation of Rebind which uses a bytes.Buffer.  The code is
70
-// much simpler and should be more resistant to odd unicode, but it is twice as
71
-// slow.  Kept here for benchmarking purposes and to possibly replace Rebind if
72
-// problems arise with its somewhat naive handling of unicode.
73
-func rebindBuff(bindType int, query string) string {
74
-	if bindType != DOLLAR {
75
-		return query
76
-	}
77
-
78
-	b := make([]byte, 0, len(query))
79
-	rqb := bytes.NewBuffer(b)
80
-	j := 1
81
-	for _, r := range query {
82
-		if r == '?' {
83
-			rqb.WriteRune('$')
84
-			rqb.WriteString(strconv.Itoa(j))
85
-			j++
86
-		} else {
87
-			rqb.WriteRune(r)
88
-		}
89
-	}
90
-
91
-	return rqb.String()
92
-}
93
-
94
-// In expands slice values in args, returning the modified query string
95
-// and a new arg list that can be executed by a database. The `query` should
96
-// use the `?` bindVar.  The return value uses the `?` bindVar.
97
-func In(query string, args ...interface{}) (string, []interface{}, error) {
98
-	// argMeta stores reflect.Value and length for slices and
99
-	// the value itself for non-slice arguments
100
-	type argMeta struct {
101
-		v      reflect.Value
102
-		i      interface{}
103
-		length int
104
-	}
105
-
106
-	var flatArgsCount int
107
-	var anySlices bool
108
-
109
-	meta := make([]argMeta, len(args))
110
-
111
-	for i, arg := range args {
112
-		v := reflect.ValueOf(arg)
113
-		t := reflectx.Deref(v.Type())
114
-
115
-		if t.Kind() == reflect.Slice {
116
-			meta[i].length = v.Len()
117
-			meta[i].v = v
118
-
119
-			anySlices = true
120
-			flatArgsCount += meta[i].length
121
-
122
-			if meta[i].length == 0 {
123
-				return "", nil, errors.New("empty slice passed to 'in' query")
124
-			}
125
-		} else {
126
-			meta[i].i = arg
127
-			flatArgsCount++
128
-		}
129
-	}
130
-
131
-	// don't do any parsing if there aren't any slices;  note that this means
132
-	// some errors that we might have caught below will not be returned.
133
-	if !anySlices {
134
-		return query, args, nil
135
-	}
136
-
137
-	newArgs := make([]interface{}, 0, flatArgsCount)
138
-
139
-	var arg, offset int
140
-	var buf bytes.Buffer
141
-
142
-	for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
143
-		if arg >= len(meta) {
144
-			// if an argument wasn't passed, lets return an error;  this is
145
-			// not actually how database/sql Exec/Query works, but since we are
146
-			// creating an argument list programmatically, we want to be able
147
-			// to catch these programmer errors earlier.
148
-			return "", nil, errors.New("number of bindVars exceeds arguments")
149
-		}
150
-
151
-		argMeta := meta[arg]
152
-		arg++
153
-
154
-		// not a slice, continue.
155
-		// our questionmark will either be written before the next expansion
156
-		// of a slice or after the loop when writing the rest of the query
157
-		if argMeta.length == 0 {
158
-			offset = offset + i + 1
159
-			newArgs = append(newArgs, argMeta.i)
160
-			continue
161
-		}
162
-
163
-		// write everything up to and including our ? character
164
-		buf.WriteString(query[:offset+i+1])
165
-
166
-		newArgs = append(newArgs, argMeta.v.Index(0).Interface())
167
-
168
-		for si := 1; si < argMeta.length; si++ {
169
-			buf.WriteString(", ?")
170
-			newArgs = append(newArgs, argMeta.v.Index(si).Interface())
171
-		}
172
-
173
-		// slice the query and reset the offset. this avoids some bookkeeping for
174
-		// the write after the loop
175
-		query = query[offset+i+1:]
176
-		offset = 0
177
-	}
178
-
179
-	buf.WriteString(query)
180
-
181
-	if arg < len(meta) {
182
-		return "", nil, errors.New("number of bindVars less than number arguments")
183
-	}
184
-
185
-	return buf.String(), newArgs, nil
186
-}

+ 0
- 12
vendor/github.com/jmoiron/sqlx/doc.go View File

@@ -1,12 +0,0 @@
1
-// Package sqlx provides general purpose extensions to database/sql.
2
-//
3
-// It is intended to seamlessly wrap database/sql and provide convenience
4
-// methods which are useful in the development of database driven applications.
5
-// None of the underlying database/sql methods are changed.  Instead all extended
6
-// behavior is implemented through new methods defined on wrapper types.
7
-//
8
-// Additions include scanning into structs, named query support, rebinding
9
-// queries for different drivers, convenient shorthands for common error handling
10
-// and more.
11
-//
12
-package sqlx

+ 0
- 336
vendor/github.com/jmoiron/sqlx/named.go View File

@@ -1,336 +0,0 @@
1
-package sqlx
2
-
3
-// Named Query Support
4
-//
5
-//  * BindMap - bind query bindvars to map/struct args
6
-//	* NamedExec, NamedQuery - named query w/ struct or map
7
-//  * NamedStmt - a pre-compiled named query which is a prepared statement
8
-//
9
-// Internal Interfaces:
10
-//
11
-//  * compileNamedQuery - rebind a named query, returning a query and list of names
12
-//  * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
13
-//
14
-import (
15
-	"database/sql"
16
-	"errors"
17
-	"fmt"
18
-	"reflect"
19
-	"strconv"
20
-	"unicode"
21
-
22
-	"github.com/jmoiron/sqlx/reflectx"
23
-)
24
-
25
-// NamedStmt is a prepared statement that executes named queries.  Prepare it
26
-// how you would execute a NamedQuery, but pass in a struct or map when executing.
27
-type NamedStmt struct {
28
-	Params      []string
29
-	QueryString string
30
-	Stmt        *Stmt
31
-}
32
-
33
-// Close closes the named statement.
34
-func (n *NamedStmt) Close() error {
35
-	return n.Stmt.Close()
36
-}
37
-
38
-// Exec executes a named statement using the struct passed.
39
-func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
40
-	args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
41
-	if err != nil {
42
-		return *new(sql.Result), err
43
-	}
44
-	return n.Stmt.Exec(args...)
45
-}
46
-
47
-// Query executes a named statement using the struct argument, returning rows.
48
-func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
49
-	args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
50
-	if err != nil {
51
-		return nil, err
52
-	}
53
-	return n.Stmt.Query(args...)
54
-}
55
-
56
-// QueryRow executes a named statement against the database.  Because sqlx cannot
57
-// create a *sql.Row with an error condition pre-set for binding errors, sqlx
58
-// returns a *sqlx.Row instead.
59
-func (n *NamedStmt) QueryRow(arg interface{}) *Row {
60
-	args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
61
-	if err != nil {
62
-		return &Row{err: err}
63
-	}
64
-	return n.Stmt.QueryRowx(args...)
65
-}
66
-
67
-// MustExec execs a NamedStmt, panicing on error
68
-func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
69
-	res, err := n.Exec(arg)
70
-	if err != nil {
71
-		panic(err)
72
-	}
73
-	return res
74
-}
75
-
76
-// Queryx using this NamedStmt
77
-func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
78
-	r, err := n.Query(arg)
79
-	if err != nil {
80
-		return nil, err
81
-	}
82
-	return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
83
-}
84
-
85
-// QueryRowx this NamedStmt.  Because of limitations with QueryRow, this is
86
-// an alias for QueryRow.
87
-func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
88
-	return n.QueryRow(arg)
89
-}
90
-
91
-// Select using this NamedStmt
92
-func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
93
-	rows, err := n.Queryx(arg)
94
-	if err != nil {
95
-		return err
96
-	}
97
-	// if something happens here, we want to make sure the rows are Closed
98
-	defer rows.Close()
99
-	return scanAll(rows, dest, false)
100
-}
101
-
102
-// Get using this NamedStmt
103
-func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
104
-	r := n.QueryRowx(arg)
105
-	return r.scanAny(dest, false)
106
-}
107
-
108
-// Unsafe creates an unsafe version of the NamedStmt
109
-func (n *NamedStmt) Unsafe() *NamedStmt {
110
-	r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
111
-	r.Stmt.unsafe = true
112
-	return r
113
-}
114
-
115
-// A union interface of preparer and binder, required to be able to prepare
116
-// named statements (as the bindtype must be determined).
117
-type namedPreparer interface {
118
-	Preparer
119
-	binder
120
-}
121
-
122
-func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
123
-	bindType := BindType(p.DriverName())
124
-	q, args, err := compileNamedQuery([]byte(query), bindType)
125
-	if err != nil {
126
-		return nil, err
127
-	}
128
-	stmt, err := Preparex(p, q)
129
-	if err != nil {
130
-		return nil, err
131
-	}
132
-	return &NamedStmt{
133
-		QueryString: q,
134
-		Params:      args,
135
-		Stmt:        stmt,
136
-	}, nil
137
-}
138
-
139
-func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
140
-	if maparg, ok := arg.(map[string]interface{}); ok {
141
-		return bindMapArgs(names, maparg)
142
-	}
143
-	return bindArgs(names, arg, m)
144
-}
145
-
146
-// private interface to generate a list of interfaces from a given struct
147
-// type, given a list of names to pull out of the struct.  Used by public
148
-// BindStruct interface.
149
-func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
150
-	arglist := make([]interface{}, 0, len(names))
151
-
152
-	// grab the indirected value of arg
153
-	v := reflect.ValueOf(arg)
154
-	for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
155
-		v = v.Elem()
156
-	}
157
-
158
-	fields := m.TraversalsByName(v.Type(), names)
159
-	for i, t := range fields {
160
-		if len(t) == 0 {
161
-			return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
162
-		}
163
-		val := reflectx.FieldByIndexesReadOnly(v, t)
164
-		arglist = append(arglist, val.Interface())
165
-	}
166
-
167
-	return arglist, nil
168
-}
169
-
170
-// like bindArgs, but for maps.
171
-func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
172
-	arglist := make([]interface{}, 0, len(names))
173
-
174
-	for _, name := range names {
175
-		val, ok := arg[name]
176
-		if !ok {
177
-			return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
178
-		}
179
-		arglist = append(arglist, val)
180
-	}
181
-	return arglist, nil
182
-}
183
-
184
-// bindStruct binds a named parameter query with fields from a struct argument.
185
-// The rules for binding field names to parameter names follow the same
186
-// conventions as for StructScan, including obeying the `db` struct tags.
187
-func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
188
-	bound, names, err := compileNamedQuery([]byte(query), bindType)
189
-	if err != nil {
190
-		return "", []interface{}{}, err
191
-	}
192
-
193
-	arglist, err := bindArgs(names, arg, m)
194
-	if err != nil {
195
-		return "", []interface{}{}, err
196
-	}
197
-
198
-	return bound, arglist, nil
199
-}
200
-
201
-// bindMap binds a named parameter query with a map of arguments.
202
-func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
203
-	bound, names, err := compileNamedQuery([]byte(query), bindType)
204
-	if err != nil {
205
-		return "", []interface{}{}, err
206
-	}
207
-
208
-	arglist, err := bindMapArgs(names, args)
209
-	return bound, arglist, err
210
-}
211
-
212
-// -- Compilation of Named Queries
213
-
214
-// Allow digits and letters in bind params;  additionally runes are
215
-// checked against underscores, meaning that bind params can have be
216
-// alphanumeric with underscores.  Mind the difference between unicode
217
-// digits and numbers, where '5' is a digit but '五' is not.
218
-var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
219
-
220
-// FIXME: this function isn't safe for unicode named params, as a failing test
221
-// can testify.  This is not a regression but a failure of the original code
222
-// as well.  It should be modified to range over runes in a string rather than
223
-// bytes, even though this is less convenient and slower.  Hopefully the
224
-// addition of the prepared NamedStmt (which will only do this once) will make
225
-// up for the slightly slower ad-hoc NamedExec/NamedQuery.
226
-
227
-// compile a NamedQuery into an unbound query (using the '?' bindvar) and
228
-// a list of names.
229
-func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
230
-	names = make([]string, 0, 10)
231
-	rebound := make([]byte, 0, len(qs))
232
-
233
-	inName := false
234
-	last := len(qs) - 1
235
-	currentVar := 1
236
-	name := make([]byte, 0, 10)
237
-
238
-	for i, b := range qs {
239
-		// a ':' while we're in a name is an error
240
-		if b == ':' {
241
-			// if this is the second ':' in a '::' escape sequence, append a ':'
242
-			if inName && i > 0 && qs[i-1] == ':' {
243
-				rebound = append(rebound, ':')
244
-				inName = false
245
-				continue
246
-			} else if inName {
247
-				err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
248
-				return query, names, err
249
-			}
250
-			inName = true
251
-			name = []byte{}
252
-			// if we're in a name, and this is an allowed character, continue
253
-		} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_') && i != last {
254
-			// append the byte to the name if we are in a name and not on the last byte
255
-			name = append(name, b)
256
-			// if we're in a name and it's not an allowed character, the name is done
257
-		} else if inName {
258
-			inName = false
259
-			// if this is the final byte of the string and it is part of the name, then
260
-			// make sure to add it to the name
261
-			if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
262
-				name = append(name, b)
263
-			}
264
-			// add the string representation to the names list
265
-			names = append(names, string(name))
266
-			// add a proper bindvar for the bindType
267
-			switch bindType {
268
-			// oracle only supports named type bind vars even for positional
269
-			case NAMED:
270
-				rebound = append(rebound, ':')
271
-				rebound = append(rebound, name...)
272
-			case QUESTION, UNKNOWN:
273
-				rebound = append(rebound, '?')
274
-			case DOLLAR:
275
-				rebound = append(rebound, '$')
276
-				for _, b := range strconv.Itoa(currentVar) {
277
-					rebound = append(rebound, byte(b))
278
-				}
279
-				currentVar++
280
-			}
281
-			// add this byte to string unless it was not part of the name
282
-			if i != last {
283
-				rebound = append(rebound, b)
284
-			} else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
285
-				rebound = append(rebound, b)
286
-			}
287
-		} else {
288
-			// this is a normal byte and should just go onto the rebound query
289
-			rebound = append(rebound, b)
290
-		}
291
-	}
292
-
293
-	return string(rebound), names, err
294
-}
295
-
296
-// BindNamed binds a struct or a map to a query with named parameters.
297
-// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
298
-func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
299
-	return bindNamedMapper(bindType, query, arg, mapper())
300
-}
301
-
302
-// Named takes a query using named parameters and an argument and
303
-// returns a new query with a list of args that can be executed by
304
-// a database.  The return value uses the `?` bindvar.
305
-func Named(query string, arg interface{}) (string, []interface{}, error) {
306
-	return bindNamedMapper(QUESTION, query, arg, mapper())
307
-}
308
-
309
-func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
310
-	if maparg, ok := arg.(map[string]interface{}); ok {
311
-		return bindMap(bindType, query, maparg)
312
-	}
313
-	return bindStruct(bindType, query, arg, m)
314
-}
315
-
316
-// NamedQuery binds a named query and then runs Query on the result using the
317
-// provided Ext (sqlx.Tx, sqlx.Db).  It works with both structs and with
318
-// map[string]interface{} types.
319
-func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
320
-	q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
321
-	if err != nil {
322
-		return nil, err
323
-	}
324
-	return e.Queryx(q, args...)
325
-}
326
-
327
-// NamedExec uses BindStruct to get a query executable by the driver and
328
-// then runs Exec on the result.  Returns an error from the binding
329
-// or the query excution itself.
330
-func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
331
-	q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
332
-	if err != nil {
333
-		return nil, err
334
-	}
335
-	return e.Exec(q, args...)
336
-}

+ 0
- 227
vendor/github.com/jmoiron/sqlx/named_test.go View File

@@ -1,227 +0,0 @@
1
-package sqlx
2
-
3
-import (
4
-	"database/sql"
5
-	"testing"
6
-)
7
-
8
-func TestCompileQuery(t *testing.T) {
9
-	table := []struct {
10
-		Q, R, D, N string
11
-		V          []string
12
-	}{
13
-		// basic test for named parameters, invalid char ',' terminating
14
-		{
15
-			Q: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
16
-			R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
17
-			D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
18
-			N: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
19
-			V: []string{"name", "age", "first", "last"},
20
-		},
21
-		// This query tests a named parameter ending the string as well as numbers
22
-		{
23
-			Q: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
24
-			R: `SELECT * FROM a WHERE first_name=? AND last_name=?`,
25
-			D: `SELECT * FROM a WHERE first_name=$1 AND last_name=$2`,
26
-			N: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
27
-			V: []string{"name1", "name2"},
28
-		},
29
-		{
30
-			Q: `SELECT "::foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
31
-			R: `SELECT ":foo" FROM a WHERE first_name=? AND last_name=?`,
32
-			D: `SELECT ":foo" FROM a WHERE first_name=$1 AND last_name=$2`,
33
-			N: `SELECT ":foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
34
-			V: []string{"name1", "name2"},
35
-		},
36
-		{
37
-			Q: `SELECT 'a::b::c' || first_name, '::::ABC::_::' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
38
-			R: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=? AND last_name=?`,
39
-			D: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=$1 AND last_name=$2`,
40
-			N: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
41
-			V: []string{"first_name", "last_name"},
42
-		},
43
-		/* This unicode awareness test sadly fails, because of our byte-wise worldview.
44
-		 * We could certainly iterate by Rune instead, though it's a great deal slower,
45
-		 * it's probably the RightWay(tm)
46
-		{
47
-			Q: `INSERT INTO foo (a,b,c,d) VALUES (:あ, :b, :キコ, :名前)`,
48
-			R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
49
-			D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
50
-			N: []string{"name", "age", "first", "last"},
51
-		},
52
-		*/
53
-	}
54
-
55
-	for _, test := range table {
56
-		qr, names, err := compileNamedQuery([]byte(test.Q), QUESTION)
57
-		if err != nil {
58
-			t.Error(err)
59
-		}
60
-		if qr != test.R {
61
-			t.Errorf("expected %s, got %s", test.R, qr)
62
-		}
63
-		if len(names) != len(test.V) {
64
-			t.Errorf("expected %#v, got %#v", test.V, names)
65
-		} else {
66
-			for i, name := range names {
67
-				if name != test.V[i] {
68
-					t.Errorf("expected %dth name to be %s, got %s", i+1, test.V[i], name)
69
-				}
70
-			}
71
-		}
72
-		qd, _, _ := compileNamedQuery([]byte(test.Q), DOLLAR)
73
-		if qd != test.D {
74
-			t.Errorf("\nexpected: `%s`\ngot:      `%s`", test.D, qd)
75
-		}
76
-
77
-		qq, _, _ := compileNamedQuery([]byte(test.Q), NAMED)
78
-		if qq != test.N {
79
-			t.Errorf("\nexpected: `%s`\ngot:      `%s`\n(len: %d vs %d)", test.N, qq, len(test.N), len(qq))
80
-		}
81
-	}
82
-}
83
-
84
-type Test struct {
85
-	t *testing.T
86
-}
87
-
88
-func (t Test) Error(err error, msg ...interface{}) {
89
-	if err != nil {
90
-		if len(msg) == 0 {
91
-			t.t.Error(err)
92
-		} else {
93
-			t.t.Error(msg...)
94
-		}
95
-	}
96
-}
97
-
98
-func (t Test) Errorf(err error, format string, args ...interface{}) {
99
-	if err != nil {
100
-		t.t.Errorf(format, args...)
101
-	}
102
-}
103
-
104
-func TestNamedQueries(t *testing.T) {
105
-	RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
106
-		loadDefaultFixture(db, t)
107
-		test := Test{t}
108
-		var ns *NamedStmt
109
-		var err error
110
-
111
-		// Check that invalid preparations fail
112
-		ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name")
113
-		if err == nil {
114
-			t.Error("Expected an error with invalid prepared statement.")
115
-		}
116
-
117
-		ns, err = db.PrepareNamed("invalid sql")
118
-		if err == nil {
119
-			t.Error("Expected an error with invalid prepared statement.")
120
-		}
121
-
122
-		// Check closing works as anticipated
123
-		ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first_name")
124
-		test.Error(err)
125
-		err = ns.Close()
126
-		test.Error(err)
127
-
128
-		ns, err = db.PrepareNamed(`
129
-			SELECT first_name, last_name, email 
130
-			FROM person WHERE first_name=:first_name AND email=:email`)
131
-		test.Error(err)
132
-
133
-		// test Queryx w/ uses Query
134
-		p := Person{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"}
135
-
136
-		rows, err := ns.Queryx(p)
137
-		test.Error(err)
138
-		for rows.Next() {
139
-			var p2 Person
140
-			rows.StructScan(&p2)
141
-			if p.FirstName != p2.FirstName {
142
-				t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName)
143
-			}
144
-			if p.LastName != p2.LastName {
145
-				t.Errorf("got %s, expected %s", p.LastName, p2.LastName)
146
-			}
147
-			if p.Email != p2.Email {
148
-				t.Errorf("got %s, expected %s", p.Email, p2.Email)
149
-			}
150
-		}
151
-
152
-		// test Select
153
-		people := make([]Person, 0, 5)
154
-		err = ns.Select(&people, p)
155
-		test.Error(err)
156
-
157
-		if len(people) != 1 {
158
-			t.Errorf("got %d results, expected %d", len(people), 1)
159
-		}
160
-		if p.FirstName != people[0].FirstName {
161
-			t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName)
162
-		}
163
-		if p.LastName != people[0].LastName {
164
-			t.Errorf("got %s, expected %s", p.LastName, people[0].LastName)
165
-		}
166
-		if p.Email != people[0].Email {
167
-			t.Errorf("got %s, expected %s", p.Email, people[0].Email)
168
-		}
169
-
170
-		// test Exec
171
-		ns, err = db.PrepareNamed(`
172
-			INSERT INTO person (first_name, last_name, email)
173
-			VALUES (:first_name, :last_name, :email)`)
174
-		test.Error(err)
175
-
176
-		js := Person{
177
-			FirstName: "Julien",
178
-			LastName:  "Savea",
179
-			Email:     "jsavea@ab.co.nz",
180
-		}
181
-		_, err = ns.Exec(js)
182
-		test.Error(err)
183
-
184
-		// Make sure we can pull him out again
185
-		p2 := Person{}
186
-		db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email)
187
-		if p2.Email != js.Email {
188
-			t.Errorf("expected %s, got %s", js.Email, p2.Email)
189
-		}
190
-
191
-		// test Txn NamedStmts
192
-		tx := db.MustBegin()
193
-		txns := tx.NamedStmt(ns)
194
-
195
-		// We're going to add Steven in this txn
196
-		sl := Person{
197
-			FirstName: "Steven",
198
-			LastName:  "Luatua",
199
-			Email:     "sluatua@ab.co.nz",
200
-		}
201
-
202
-		_, err = txns.Exec(sl)
203
-		test.Error(err)
204
-		// then rollback...
205
-		tx.Rollback()
206
-		// looking for Steven after a rollback should fail
207
-		err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
208
-		if err != sql.ErrNoRows {
209
-			t.Errorf("expected no rows error, got %v", err)
210
-		}
211
-
212
-		// now do the same, but commit
213
-		tx = db.MustBegin()
214
-		txns = tx.NamedStmt(ns)
215
-		_, err = txns.Exec(sl)
216
-		test.Error(err)
217
-		tx.Commit()
218
-
219
-		// looking for Steven after a Commit should succeed
220
-		err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
221
-		test.Error(err)
222
-		if p2.Email != sl.Email {
223
-			t.Errorf("expected %s, got %s", sl.Email, p2.Email)
224
-		}
225
-
226
-	})
227
-}

+ 0
- 17
vendor/github.com/jmoiron/sqlx/reflectx/README.md View File

@@ -1,17 +0,0 @@
1
-# reflectx
2
-
3
-The sqlx package has special reflect needs.  In particular, it needs to:
4
-
5
-* be able to map a name to a field
6
-* understand embedded structs
7
-* understand mapping names to fields by a particular tag
8
-* user specified name -> field mapping functions
9
-
10
-These behaviors mimic the behaviors by the standard library marshallers and also the
11
-behavior of standard Go accessors.
12
-
13
-The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
14
-addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
15
-tags in the ways that are vital to most marshalers, and they are slow.
16
-
17
-This reflectx package extends reflect to achieve these goals.

+ 0
- 371
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go View File

@@ -1,371 +0,0 @@
1
-// Package reflectx implements extensions to the standard reflect lib suitable
2
-// for implementing marshaling and unmarshaling packages.  The main Mapper type
3
-// allows for Go-compatible named attribute access, including accessing embedded
4
-// struct attributes and the ability to use  functions and struct tags to
5
-// customize field names.
6
-//
7
-package reflectx
8
-
9
-import (
10
-	"fmt"
11
-	"reflect"
12
-	"runtime"
13
-	"strings"
14
-	"sync"
15
-)
16
-
17
-// A FieldInfo is a collection of metadata about a struct field.
18
-type FieldInfo struct {
19
-	Index    []int
20
-	Path     string
21
-	Field    reflect.StructField
22
-	Zero     reflect.Value
23
-	Name     string
24
-	Options  map[string]string
25
-	Embedded bool
26
-	Children []*FieldInfo
27
-	Parent   *FieldInfo
28
-}
29
-
30
-// A StructMap is an index of field metadata for a struct.
31
-type StructMap struct {
32
-	Tree  *FieldInfo
33
-	Index []*FieldInfo
34
-	Paths map[string]*FieldInfo
35
-	Names map[string]*FieldInfo
36
-}
37
-
38
-// GetByPath returns a *FieldInfo for a given string path.
39
-func (f StructMap) GetByPath(path string) *FieldInfo {
40
-	return f.Paths[path]
41
-}
42
-
43
-// GetByTraversal returns a *FieldInfo for a given integer path.  It is
44
-// analogous to reflect.FieldByIndex.
45
-func (f StructMap) GetByTraversal(index []int) *FieldInfo {
46
-	if len(index) == 0 {
47
-		return nil
48
-	}
49
-
50
-	tree := f.Tree
51
-	for _, i := range index {
52
-		if i >= len(tree.Children) || tree.Children[i] == nil {
53
-			return nil
54
-		}
55
-		tree = tree.Children[i]
56
-	}
57
-	return tree
58
-}
59
-
60
-// Mapper is a general purpose mapper of names to struct fields.  A Mapper
61
-// behaves like most marshallers, optionally obeying a field tag for name
62
-// mapping and a function to provide a basic mapping of fields to names.
63
-type Mapper struct {
64
-	cache      map[reflect.Type]*StructMap
65
-	tagName    string
66
-	tagMapFunc func(string) string
67
-	mapFunc    func(string) string
68
-	mutex      sync.Mutex
69
-}
70
-
71
-// NewMapper returns a new mapper which optionally obeys the field tag given
72
-// by tagName.  If tagName is the empty string, it is ignored.
73
-func NewMapper(tagName string) *Mapper {
74
-	return &Mapper{
75
-		cache:   make(map[reflect.Type]*StructMap),
76
-		tagName: tagName,
77
-	}
78
-}
79
-
80
-// NewMapperTagFunc returns a new mapper which contains a mapper for field names
81
-// AND a mapper for tag values.  This is useful for tags like json which can
82
-// have values like "name,omitempty".
83
-func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
84
-	return &Mapper{
85
-		cache:      make(map[reflect.Type]*StructMap),
86
-		tagName:    tagName,
87
-		mapFunc:    mapFunc,
88
-		tagMapFunc: tagMapFunc,
89
-	}
90
-}
91
-
92
-// NewMapperFunc returns a new mapper which optionally obeys a field tag and
93
-// a struct field name mapper func given by f.  Tags will take precedence, but
94
-// for any other field, the mapped name will be f(field.Name)
95
-func NewMapperFunc(tagName string, f func(string) string) *Mapper {
96
-	return &Mapper{
97
-		cache:   make(map[reflect.Type]*StructMap),
98
-		tagName: tagName,
99
-		mapFunc: f,
100
-	}
101
-}
102
-
103
-// TypeMap returns a mapping of field strings to int slices representing
104
-// the traversal down the struct to reach the field.
105
-func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
106
-	m.mutex.Lock()
107
-	mapping, ok := m.cache[t]
108
-	if !ok {
109
-		mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
110
-		m.cache[t] = mapping
111
-	}
112
-	m.mutex.Unlock()
113
-	return mapping
114
-}
115
-
116
-// FieldMap returns the mapper's mapping of field names to reflect values.  Panics
117
-// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
118
-func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
119
-	v = reflect.Indirect(v)
120
-	mustBe(v, reflect.Struct)
121
-
122
-	r := map[string]reflect.Value{}
123
-	tm := m.TypeMap(v.Type())
124
-	for tagName, fi := range tm.Names {
125
-		r[tagName] = FieldByIndexes(v, fi.Index)
126
-	}
127
-	return r
128
-}
129
-
130
-// FieldByName returns a field by the its mapped name as a reflect.Value.
131
-// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
132
-// Returns zero Value if the name is not found.
133
-func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
134
-	v = reflect.Indirect(v)
135
-	mustBe(v, reflect.Struct)
136
-
137
-	tm := m.TypeMap(v.Type())
138
-	fi, ok := tm.Names[name]
139
-	if !ok {
140
-		return v
141
-	}
142
-	return FieldByIndexes(v, fi.Index)
143
-}
144
-
145
-// FieldsByName returns a slice of values corresponding to the slice of names
146
-// for the value.  Panics if v's Kind is not Struct or v is not Indirectable
147
-// to a struct Kind.  Returns zero Value for each name not found.
148
-func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
149
-	v = reflect.Indirect(v)
150
-	mustBe(v, reflect.Struct)
151
-
152
-	tm := m.TypeMap(v.Type())
153
-	vals := make([]reflect.Value, 0, len(names))
154
-	for _, name := range names {
155
-		fi, ok := tm.Names[name]
156
-		if !ok {
157
-			vals = append(vals, *new(reflect.Value))
158
-		} else {
159
-			vals = append(vals, FieldByIndexes(v, fi.Index))
160
-		}
161
-	}
162
-	return vals
163
-}
164
-
165
-// TraversalsByName returns a slice of int slices which represent the struct
166
-// traversals for each mapped name.  Panics if t is not a struct or Indirectable
167