github.com/go-kivik/kivik/v4@v4.3.2/couchdb/client.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package couchdb 14 15 import ( 16 "bytes" 17 "context" 18 "encoding/json" 19 "fmt" 20 "io" 21 "net/http" 22 "net/url" 23 "strings" 24 25 kivik "github.com/go-kivik/kivik/v4" 26 "github.com/go-kivik/kivik/v4/couchdb/chttp" 27 "github.com/go-kivik/kivik/v4/driver" 28 internal "github.com/go-kivik/kivik/v4/int/errors" 29 ) 30 31 func (c *client) AllDBs(ctx context.Context, opts driver.Options) ([]string, error) { 32 var query url.Values 33 opts.Apply(&query) 34 var allDBs []string 35 err := c.DoJSON(ctx, http.MethodGet, "/_all_dbs", &chttp.Options{Query: query}, &allDBs) 36 return allDBs, err 37 } 38 39 func (c *client) DBExists(ctx context.Context, dbName string, _ driver.Options) (bool, error) { 40 if dbName == "" { 41 return false, missingArg("dbName") 42 } 43 _, err := c.DoError(ctx, http.MethodHead, url.PathEscape(dbName), nil) 44 if kivik.HTTPStatus(err) == http.StatusNotFound { 45 return false, nil 46 } 47 return err == nil, err 48 } 49 50 func (c *client) CreateDB(ctx context.Context, dbName string, opts driver.Options) error { 51 if dbName == "" { 52 return missingArg("dbName") 53 } 54 query := url.Values{} 55 opts.Apply(&query) 56 _, err := c.DoError(ctx, http.MethodPut, url.PathEscape(dbName), &chttp.Options{Query: query}) 57 return err 58 } 59 60 func (c *client) DestroyDB(ctx context.Context, dbName string, _ driver.Options) error { 61 if dbName == "" { 62 return missingArg("dbName") 63 } 64 _, err := c.DoError(ctx, http.MethodDelete, url.PathEscape(dbName), nil) 65 return err 66 } 67 68 func (c *client) DBUpdates(ctx context.Context, opts driver.Options) (updates driver.DBUpdates, err error) { 69 query := url.Values{} 70 opts.Apply(&query) 71 // TODO #864: Remove this default behavior for v5 72 if _, ok := query["feed"]; !ok { 73 query.Set("feed", "continuous") 74 } else if query.Get("feed") == "" { 75 delete(query, "feed") 76 } 77 if _, ok := query["since"]; !ok { 78 query.Set("since", "now") 79 } else if query.Get("since") == "" { 80 delete(query, "since") 81 } 82 if query.Get("feed") == "eventsource" { 83 return nil, &internal.Error{Status: http.StatusBadRequest, Message: "kivik: eventsource feed type not supported by CouchDB driver"} 84 } 85 resp, err := c.DoReq(ctx, http.MethodGet, "/_db_updates", &chttp.Options{Query: query}) 86 if err != nil { 87 return nil, err 88 } 89 if err := chttp.ResponseError(resp); err != nil { 90 return nil, err 91 } 92 return newUpdates(ctx, resp.Body) 93 } 94 95 type couchUpdates struct { 96 *iter 97 meta *updatesMeta 98 } 99 100 var _ driver.LastSeqer = &couchUpdates{} 101 102 type updatesMeta struct { 103 lastSeq string 104 } 105 106 func (u *updatesMeta) parseMeta(key interface{}, dec *json.Decoder) error { 107 if key == "last_seq" { 108 return dec.Decode(&u.lastSeq) 109 } 110 // Just consume the value, since we don't know what it means. 111 var discard json.RawMessage 112 return dec.Decode(&discard) 113 } 114 115 type updatesParser struct{} 116 117 var _ parser = &updatesParser{} 118 119 func (p *updatesParser) decodeItem(i interface{}, dec *json.Decoder) error { 120 return dec.Decode(i) 121 } 122 123 func (p *updatesParser) parseMeta(i interface{}, dec *json.Decoder, key string) error { 124 meta := i.(*updatesMeta) 125 return meta.parseMeta(key, dec) 126 } 127 128 func newUpdates(ctx context.Context, r io.ReadCloser) (driver.DBUpdates, error) { 129 r, feedType, err := updatesFeedType(r) 130 if err != nil { 131 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 132 } 133 134 switch feedType { 135 case feedTypeContinuous: 136 return newContinuousUpdates(ctx, r), nil 137 case feedTypeNormal: 138 return newNormalUpdates(ctx, r), nil 139 case feedTypeEmpty: 140 return newEmptyUpdates(r) 141 } 142 panic("unknown") // TODO: test 143 } 144 145 type feedType int 146 147 const ( 148 feedTypeNormal feedType = iota 149 feedTypeContinuous 150 feedTypeEmpty 151 ) 152 153 // updatesFeedType detects the type of Updates Feed (continuous, or normal) by 154 // reading the first two JSON tokens of the stream. It then returns a 155 // re-assembled reader with the results. 156 func updatesFeedType(r io.ReadCloser) (io.ReadCloser, feedType, error) { 157 buf := &bytes.Buffer{} 158 // Read just the first two tokens, to determine feed type. We use a 159 // json.Decoder rather than reading raw bytes, because there may be 160 // whitespace, and it's also nice to return a consistent error in case the 161 // body is invalid. 162 dec := json.NewDecoder(io.TeeReader(r, buf)) 163 tok, err := dec.Token() 164 if err != nil { 165 return nil, 0, err 166 } 167 if tok != json.Delim('{') { 168 return nil, 0, fmt.Errorf("kivik: unexpected JSON token %q in feed response, expected `{`", tok) 169 } 170 tok, err = dec.Token() 171 if err != nil { 172 return nil, 0, err 173 } 174 175 // restore reader 176 r = struct { 177 io.Reader 178 io.Closer 179 }{ 180 Reader: io.MultiReader(buf, r), 181 Closer: r, 182 } 183 184 switch tok { 185 case "db_name": // Continuous feed 186 return r, feedTypeContinuous, nil 187 case "results": // Normal feed 188 return r, feedTypeNormal, nil 189 case "last_seq": // Normal feed, but with no results 190 return r, feedTypeEmpty, nil 191 default: // Something unexpected 192 return nil, 0, fmt.Errorf("kivik: unexpected JSON token %q in feed response, expected `db_name` or `results`", tok) 193 } 194 } 195 196 func newNormalUpdates(ctx context.Context, r io.ReadCloser) *couchUpdates { 197 meta := &updatesMeta{} 198 return &couchUpdates{ 199 iter: newIter(ctx, meta, "results", r, &updatesParser{}), 200 meta: meta, 201 } 202 } 203 204 func newContinuousUpdates(ctx context.Context, r io.ReadCloser) *couchUpdates { 205 return &couchUpdates{ 206 iter: newIter(ctx, nil, "", r, &updatesParser{}), 207 } 208 } 209 210 func newEmptyUpdates(r io.ReadCloser) (driver.DBUpdates, error) { 211 var updates emptyUpdates 212 if err := json.NewDecoder(r).Decode(&updates); err != nil { 213 return nil, err 214 } 215 return &updates, nil 216 } 217 218 type emptyUpdates struct { 219 Lastseq string `json:"last_seq"` 220 } 221 222 var ( 223 _ driver.DBUpdates = (*emptyUpdates)(nil) 224 _ driver.LastSeqer = (*emptyUpdates)(nil) 225 ) 226 227 func (u *emptyUpdates) Close() error { return nil } 228 func (u *emptyUpdates) Next(*driver.DBUpdate) error { return io.EOF } 229 func (u *emptyUpdates) LastSeq() (string, error) { return u.Lastseq, nil } 230 231 func (u *couchUpdates) Next(update *driver.DBUpdate) error { 232 return u.iter.next(update) 233 } 234 235 func (u *couchUpdates) LastSeq() (string, error) { 236 if u.meta == nil { 237 return "", nil 238 } 239 return u.meta.lastSeq, nil 240 } 241 242 // Ping queries the /_up endpoint, and returns true if there are no errors, or 243 // if a 400 (Bad Request) is returned, and the Server: header indicates a server 244 // version prior to 2.x. 245 func (c *client) Ping(ctx context.Context) (bool, error) { 246 resp, err := c.DoError(ctx, http.MethodHead, "/_up", nil) 247 if kivik.HTTPStatus(err) == http.StatusBadRequest { 248 return strings.HasPrefix(resp.Header.Get("Server"), "CouchDB/1."), nil 249 } 250 if kivik.HTTPStatus(err) == http.StatusNotFound { 251 return false, nil 252 } 253 return err == nil, err 254 }