...

Source file src/github.com/go-kivik/kivik/v4/find.go

Documentation: github.com/go-kivik/kivik/v4

     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 kivik
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"net/http"
    19  
    20  	"github.com/go-kivik/kivik/v4/driver"
    21  	"github.com/go-kivik/kivik/v4/int/errors"
    22  )
    23  
    24  // Find executes a query using the [_find interface]. The query must be a
    25  // string, []byte, or [encoding/json.RawMessage] value, or JSON-marshalable to a
    26  // valid valid query. The options are merged with the query, and will overwrite
    27  // any values in the query.
    28  //
    29  // This arguments this method accepts will change in Kivik 5.x, to be more
    30  // consistent with the rest of the Kivik API. See [issue #1014] for details.
    31  //
    32  // [_find interface]: https://docs.couchdb.org/en/stable/api/database/find.html
    33  // [issue #1014]: https://github.com/go-kivik/kivik/issues/1014
    34  func (db *DB) Find(ctx context.Context, query interface{}, options ...Option) *ResultSet {
    35  	if db.err != nil {
    36  		return &ResultSet{iter: errIterator(db.err)}
    37  	}
    38  	finder, ok := db.driverDB.(driver.Finder)
    39  	if !ok {
    40  		return &ResultSet{iter: errIterator(errFindNotImplemented)}
    41  	}
    42  
    43  	jsonQuery, err := toQuery(query, options...)
    44  	if err != nil {
    45  		return &ResultSet{iter: errIterator(err)}
    46  	}
    47  
    48  	endQuery, err := db.startQuery()
    49  	if err != nil {
    50  		return &ResultSet{iter: errIterator(err)}
    51  	}
    52  	rowsi, err := finder.Find(ctx, jsonQuery, multiOptions(options))
    53  	if err != nil {
    54  		endQuery()
    55  		return &ResultSet{iter: errIterator(err)}
    56  	}
    57  	return newResultSet(ctx, endQuery, rowsi)
    58  }
    59  
    60  // toQuery combines query and options into a final JSON query to be sent to the
    61  // driver.
    62  func toQuery(query interface{}, options ...Option) (json.RawMessage, error) {
    63  	var queryJSON []byte
    64  	switch t := query.(type) {
    65  	case string:
    66  		queryJSON = []byte(t)
    67  	case []byte:
    68  		queryJSON = t
    69  	case json.RawMessage:
    70  		queryJSON = t
    71  	default:
    72  		var err error
    73  		queryJSON, err = json.Marshal(query)
    74  		if err != nil {
    75  			return nil, &errors.Error{Status: http.StatusBadRequest, Err: err}
    76  		}
    77  	}
    78  	var queryObject map[string]interface{}
    79  	if err := json.Unmarshal(queryJSON, &queryObject); err != nil {
    80  		return nil, &errors.Error{Status: http.StatusBadRequest, Err: err}
    81  	}
    82  
    83  	opts := map[string]interface{}{}
    84  	multiOptions(options).Apply(opts)
    85  
    86  	for k, v := range opts {
    87  		queryObject[k] = v
    88  	}
    89  
    90  	return json.Marshal(queryObject)
    91  }
    92  
    93  // CreateIndex creates an index if it doesn't already exist. ddoc and name may
    94  // be empty, in which case they will be auto-generated.  index must be
    95  // marshalable to a valid index object, as described in the [CouchDB documentation].
    96  //
    97  // [CouchDB documentation]: http://docs.couchdb.org/en/stable/api/database/find.html#db-index
    98  func (db *DB) CreateIndex(ctx context.Context, ddoc, name string, index interface{}, options ...Option) error {
    99  	if db.err != nil {
   100  		return db.err
   101  	}
   102  	endQuery, err := db.startQuery()
   103  	if err != nil {
   104  		return err
   105  	}
   106  	defer endQuery()
   107  	if finder, ok := db.driverDB.(driver.Finder); ok {
   108  		return finder.CreateIndex(ctx, ddoc, name, index, multiOptions(options))
   109  	}
   110  	return errFindNotImplemented
   111  }
   112  
   113  // DeleteIndex deletes the requested index.
   114  func (db *DB) DeleteIndex(ctx context.Context, ddoc, name string, options ...Option) error {
   115  	if db.err != nil {
   116  		return db.err
   117  	}
   118  	endQuery, err := db.startQuery()
   119  	if err != nil {
   120  		return err
   121  	}
   122  	defer endQuery()
   123  	if finder, ok := db.driverDB.(driver.Finder); ok {
   124  		return finder.DeleteIndex(ctx, ddoc, name, multiOptions(options))
   125  	}
   126  	return errFindNotImplemented
   127  }
   128  
   129  // Index is a MonboDB-style index definition.
   130  type Index struct {
   131  	DesignDoc  string      `json:"ddoc,omitempty"`
   132  	Name       string      `json:"name"`
   133  	Type       string      `json:"type"`
   134  	Definition interface{} `json:"def"`
   135  }
   136  
   137  // GetIndexes returns the indexes defined on the current database.
   138  func (db *DB) GetIndexes(ctx context.Context, options ...Option) ([]Index, error) {
   139  	if db.err != nil {
   140  		return nil, db.err
   141  	}
   142  	endQuery, err := db.startQuery()
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	defer endQuery()
   147  	if finder, ok := db.driverDB.(driver.Finder); ok {
   148  		dIndexes, err := finder.GetIndexes(ctx, multiOptions(options))
   149  		indexes := make([]Index, len(dIndexes))
   150  		for i, index := range dIndexes {
   151  			indexes[i] = Index(index)
   152  		}
   153  		return indexes, err
   154  	}
   155  	return nil, errFindNotImplemented
   156  }
   157  
   158  // QueryPlan is the query execution plan for a query, as returned by
   159  // [DB.Explain].
   160  type QueryPlan struct {
   161  	DBName   string                 `json:"dbname"`
   162  	Index    map[string]interface{} `json:"index"`
   163  	Selector map[string]interface{} `json:"selector"`
   164  	Options  map[string]interface{} `json:"opts"`
   165  	Limit    int64                  `json:"limit"`
   166  	Skip     int64                  `json:"skip"`
   167  
   168  	// Fields is the list of fields to be returned in the result set, or
   169  	// an empty list if all fields are to be returned.
   170  	Fields []interface{}          `json:"fields"`
   171  	Range  map[string]interface{} `json:"range"`
   172  }
   173  
   174  // Explain returns the query plan for a given query. Explain takes the same
   175  // arguments as [DB.Find].
   176  func (db *DB) Explain(ctx context.Context, query interface{}, options ...Option) (*QueryPlan, error) {
   177  	if db.err != nil {
   178  		return nil, db.err
   179  	}
   180  	if explainer, ok := db.driverDB.(driver.Finder); ok {
   181  		endQuery, err := db.startQuery()
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  		defer endQuery()
   186  		plan, err := explainer.Explain(ctx, query, multiOptions(options))
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		qp := QueryPlan(*plan)
   191  		return &qp, nil
   192  	}
   193  	return nil, errFindNotImplemented
   194  }
   195  

View as plain text