1package couchdb
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7
8	"github.com/go-kivik/couchdb/chttp"
9	"github.com/go-kivik/kivik"
10	"github.com/go-kivik/kivik/driver"
11	"github.com/go-kivik/kivik/errors"
12)
13
14var findNotImplemented = errors.Status(kivik.StatusNotImplemented, "kivik: Find interface not implemented prior to CouchDB 2.0.0")
15
16func (d *db) CreateIndex(ctx context.Context, ddoc, name string, index interface{}) error {
17	if d.client.noFind || d.client.Compat == CompatCouch16 {
18		return findNotImplemented
19	}
20	indexObj, err := deJSONify(index)
21	if err != nil {
22		return err
23	}
24	parameters := struct {
25		Index interface{} `json:"index"`
26		Ddoc  string      `json:"ddoc,omitempty"`
27		Name  string      `json:"name,omitempty"`
28	}{
29		Index: indexObj,
30		Ddoc:  ddoc,
31		Name:  name,
32	}
33	opts := &chttp.Options{
34		Body: chttp.EncodeBody(parameters),
35	}
36	_, err = d.Client.DoError(ctx, kivik.MethodPost, d.path("_index", nil), opts)
37	return err
38}
39
40func (d *db) GetIndexes(ctx context.Context) ([]driver.Index, error) {
41	if d.client.noFind || d.client.Compat == CompatCouch16 {
42		return nil, findNotImplemented
43	}
44	var result struct {
45		Indexes []driver.Index `json:"indexes"`
46	}
47	_, err := d.Client.DoJSON(ctx, kivik.MethodGet, d.path("_index", nil), nil, &result)
48	return result.Indexes, err
49}
50
51func (d *db) DeleteIndex(ctx context.Context, ddoc, name string) error {
52	if d.client.noFind || d.client.Compat == CompatCouch16 {
53		return findNotImplemented
54	}
55	if ddoc == "" {
56		return missingArg("ddoc")
57	}
58	if name == "" {
59		return missingArg("name")
60	}
61	path := fmt.Sprintf("_index/%s/json/%s", ddoc, name)
62	_, err := d.Client.DoError(ctx, kivik.MethodDelete, d.path(path, nil), nil)
63	return err
64}
65
66func (d *db) Find(ctx context.Context, query interface{}) (driver.Rows, error) {
67	if d.client.noFind || d.client.Compat == CompatCouch16 {
68		return nil, findNotImplemented
69	}
70	opts := &chttp.Options{
71		Body: chttp.EncodeBody(query),
72	}
73	resp, err := d.Client.DoReq(ctx, kivik.MethodPost, d.path("_find", nil), opts)
74	if err != nil {
75		return nil, err
76	}
77	if err = chttp.ResponseError(resp); err != nil {
78		return nil, err
79	}
80	return newRows(resp.Body), nil
81}
82
83type queryPlan struct {
84	DBName   string                 `json:"dbname"`
85	Index    map[string]interface{} `json:"index"`
86	Selector map[string]interface{} `json:"selector"`
87	Options  map[string]interface{} `json:"opts"`
88	Limit    int64                  `json:"limit"`
89	Skip     int64                  `json:"skip"`
90	Fields   fields                 `json:"fields"`
91	Range    map[string]interface{} `json:"range"`
92}
93
94type fields []interface{}
95
96func (f *fields) UnmarshalJSON(data []byte) error {
97	if string(data) == `"all_fields"` {
98		return nil
99	}
100	var i []interface{}
101	if err := json.Unmarshal(data, &i); err != nil {
102		return err
103	}
104	newFields := make([]interface{}, len(i))
105	copy(newFields, i)
106	*f = newFields
107	return nil
108}
109
110func (d *db) Explain(ctx context.Context, query interface{}) (*driver.QueryPlan, error) {
111	if d.client.noFind || d.client.Compat == CompatCouch16 {
112		return nil, findNotImplemented
113	}
114	opts := &chttp.Options{
115		Body: chttp.EncodeBody(query),
116	}
117	var plan queryPlan
118	if _, err := d.Client.DoJSON(ctx, kivik.MethodPost, d.path("_explain", nil), opts, &plan); err != nil {
119		return nil, err
120	}
121	return &driver.QueryPlan{
122		DBName:   plan.DBName,
123		Index:    plan.Index,
124		Selector: plan.Selector,
125		Options:  plan.Options,
126		Limit:    plan.Limit,
127		Skip:     plan.Skip,
128		Fields:   plan.Fields,
129		Range:    plan.Range,
130	}, nil
131}
132