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  }