-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathcouchdb.go
251 lines (225 loc) · 7.32 KB
/
couchdb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
// Package couchdb implements wrappers for the CouchDB HTTP API.
//
// Unless otherwise noted, all functions in this package
// can be called from more than one goroutine at the same time.
package couchdb
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strings"
)
// Client represents a remote CouchDB server.
type Client struct{ *transport }
// NewClient creates a new client object.
//
// If rawurl contains credentials, the client will authenticate
// using HTTP Basic Authentication. If rawurl has a query string,
// it is ignored.
//
// The second argument can be nil to use http.Transport,
// which should be good enough in most cases.
func NewClient(rawurl string, rt http.RoundTripper) (*Client, error) {
url, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
url.RawQuery, url.Fragment = "", ""
var auth Auth
if url.User != nil {
passwd, _ := url.User.Password()
auth = BasicAuth(url.User.Username(), passwd)
url.User = nil
}
return &Client{newTransport(url.String(), rt, auth)}, nil
}
// URL returns the URL prefix of the server.
// The url will not contain a trailing '/'.
func (c *Client) URL() string {
return c.prefix
}
// Ping can be used to check whether a server is alive.
// It sends an HTTP HEAD request to the server's URL.
func (c *Client) Ping() error {
_, err := c.closedRequest("HEAD", "/", nil)
return err
}
// SetAuth sets the authentication mechanism used by the client.
// Use SetAuth(nil) to unset any mechanism that might be in use.
// In order to verify the credentials against the server, issue any request
// after the call the SetAuth.
func (c *Client) SetAuth(a Auth) {
c.transport.setAuth(a)
}
// CreateDB creates a new database.
// The request will fail with status "412 Precondition Failed" if the database
// already exists. A valid DB object is returned in all cases, even if the
// request fails.
func (c *Client) CreateDB(name string) (*DB, error) {
if _, err := c.closedRequest("PUT", dbpath(name), nil); err != nil {
return c.DB(name), err
}
return c.DB(name), nil
}
// EnsureDB ensures that a database with the given name exists.
func (c *Client) EnsureDB(name string) (*DB, error) {
db, err := c.CreateDB(name)
if err != nil && !ErrorStatus(err, http.StatusPreconditionFailed) {
return nil, err
}
return db, nil
}
// DeleteDB deletes an existing database.
func (c *Client) DeleteDB(name string) error {
_, err := c.closedRequest("DELETE", dbpath(name), nil)
return err
}
// AllDBs returns the names of all existing databases.
func (c *Client) AllDBs() (names []string, err error) {
resp, err := c.request("GET", "/_all_dbs", nil)
if err != nil {
return names, err
}
err = readBody(resp, &names)
return names, err
}
// DB represents a remote CouchDB database.
type DB struct {
*transport
name string
}
// DB creates a database object.
// The database inherits the authentication and http.RoundTripper
// of the client. The database's actual existence is not verified.
func (c *Client) DB(name string) *DB {
return &DB{c.transport, name}
}
func (db *DB) path() *pathBuilder {
return new(pathBuilder).add(db.name)
}
// Name returns the name of a database.
func (db *DB) Name() string {
return db.name
}
var getJsonKeys = []string{"open_revs", "atts_since"}
// Get retrieves a document from the given database.
// The document is unmarshalled into the given object.
// Some fields (like _conflicts) will only be returned if the
// options require it. Please refer to the CouchDB HTTP API documentation
// for more information.
//
// http://docs.couchdb.org/en/latest/api/document/common.html?highlight=doc#get--db-docid
func (db *DB) Get(id string, doc interface{}, opts Options) error {
path, err := db.path().docID(id).options(opts, getJsonKeys)
if err != nil {
return err
}
resp, err := db.request("GET", path, nil)
if err != nil {
return err
}
return readBody(resp, &doc)
}
// Rev fetches the current revision of a document.
// It is faster than an equivalent Get request because no body
// has to be parsed.
func (db *DB) Rev(id string) (string, error) {
path := db.path().docID(id).path()
return responseRev(db.closedRequest("HEAD", path, nil))
}
// Put stores a document into the given database.
func (db *DB) Put(id string, doc interface{}, rev string) (newrev string, err error) {
path := db.path().docID(id).rev(rev)
// TODO: make it possible to stream encoder output somehow
json, err := json.Marshal(doc)
if err != nil {
return "", err
}
b := bytes.NewReader(json)
return responseRev(db.closedRequest("PUT", path, b))
}
// Delete marks a document revision as deleted.
func (db *DB) Delete(id, rev string) (newrev string, err error) {
path := db.path().docID(id).rev(rev)
return responseRev(db.closedRequest("DELETE", path, nil))
}
// Security represents database security objects.
type Security struct {
Admins Members `json:"admins"`
Members Members `json:"members"`
}
// Members represents member lists in database security objects.
type Members struct {
Names []string `json:"names,omitempty"`
Roles []string `json:"roles,omitempty"`
}
// Security retrieves the security object of a database.
func (db *DB) Security() (*Security, error) {
secobj := new(Security)
path := db.path().addRaw("_security").path()
resp, err := db.request("GET", path, nil)
if err != nil {
return nil, err
}
// The extra check for io.EOF is there because empty responses are OK.
// CouchDB returns an empty response if no security object has been set.
if err = readBody(resp, secobj); err != nil && err != io.EOF {
return nil, err
}
return secobj, nil
}
// PutSecurity sets the database security object.
func (db *DB) PutSecurity(secobj *Security) error {
json, _ := json.Marshal(secobj)
body := bytes.NewReader(json)
path := db.path().addRaw("_security").path()
_, err := db.request("PUT", path, body)
return err
}
var viewJsonKeys = []string{"startkey", "start_key", "key", "endkey", "end_key"}
// View invokes a view.
// The ddoc parameter must be the full name of the design document
// containing the view definition, including the _design/ prefix.
//
// The output of the query is unmarshalled into the given result.
// The format of the result depends on the options. Please
// refer to the CouchDB HTTP API documentation for all the possible
// options that can be set.
//
// http://docs.couchdb.org/en/latest/api/ddoc/views.html
func (db *DB) View(ddoc, view string, result interface{}, opts Options) error {
if !strings.HasPrefix(ddoc, "_design/") {
return errors.New("couchdb.View: design doc name must start with _design/")
}
path, err := db.path().docID(ddoc).addRaw("_view").add(view).options(opts, viewJsonKeys)
if err != nil {
return err
}
resp, err := db.request("GET", path, nil)
if err != nil {
return err
}
return readBody(resp, &result)
}
// AllDocs invokes the _all_docs view of a database.
//
// The output of the query is unmarshalled into the given result.
// The format of the result depends on the options. Please
// refer to the CouchDB HTTP API documentation for all the possible
// options that can be set.
//
// http://docs.couchdb.org/en/latest/api/database/bulk-api.html#db-all-docs
func (db *DB) AllDocs(result interface{}, opts Options) error {
path, err := db.path().addRaw("_all_docs").options(opts, viewJsonKeys)
if err != nil {
return err
}
resp, err := db.request("GET", path, nil)
if err != nil {
return err
}
return readBody(resp, &result)
}