Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
5.6 KiB
231 lines
5.6 KiB
package couchbase |
|
|
|
import ( |
|
"encoding/json" |
|
"errors" |
|
"fmt" |
|
"io/ioutil" |
|
"math/rand" |
|
"net/http" |
|
"net/url" |
|
"time" |
|
) |
|
|
|
// ViewRow represents a single result from a view. |
|
// |
|
// Doc is present only if include_docs was set on the request. |
|
type ViewRow struct { |
|
ID string |
|
Key interface{} |
|
Value interface{} |
|
Doc *interface{} |
|
} |
|
|
|
// A ViewError is a node-specific error indicating a partial failure |
|
// within a view result. |
|
type ViewError struct { |
|
From string |
|
Reason string |
|
} |
|
|
|
func (ve ViewError) Error() string { |
|
return "Node: " + ve.From + ", reason: " + ve.Reason |
|
} |
|
|
|
// ViewResult holds the entire result set from a view request, |
|
// including the rows and the errors. |
|
type ViewResult struct { |
|
TotalRows int `json:"total_rows"` |
|
Rows []ViewRow |
|
Errors []ViewError |
|
} |
|
|
|
func (b *Bucket) randomBaseURL() (*url.URL, error) { |
|
nodes := b.HealthyNodes() |
|
if len(nodes) == 0 { |
|
return nil, errors.New("no available couch rest URLs") |
|
} |
|
nodeNo := rand.Intn(len(nodes)) |
|
node := nodes[nodeNo] |
|
|
|
b.RLock() |
|
name := b.Name |
|
pool := b.pool |
|
b.RUnlock() |
|
|
|
u, err := ParseURL(node.CouchAPIBase) |
|
if err != nil { |
|
return nil, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v", |
|
name, nodeNo, node.CouchAPIBase, err) |
|
} else if pool != nil { |
|
u.User = pool.client.BaseURL.User |
|
} |
|
return u, err |
|
} |
|
|
|
const START_NODE_ID = -1 |
|
|
|
func (b *Bucket) randomNextURL(lastNode int) (*url.URL, int, error) { |
|
nodes := b.HealthyNodes() |
|
if len(nodes) == 0 { |
|
return nil, -1, errors.New("no available couch rest URLs") |
|
} |
|
|
|
var nodeNo int |
|
if lastNode == START_NODE_ID || lastNode >= len(nodes) { |
|
// randomly select a node if the value of lastNode is invalid |
|
nodeNo = rand.Intn(len(nodes)) |
|
} else { |
|
// wrap around the node list |
|
nodeNo = (lastNode + 1) % len(nodes) |
|
} |
|
|
|
b.RLock() |
|
name := b.Name |
|
pool := b.pool |
|
b.RUnlock() |
|
|
|
node := nodes[nodeNo] |
|
u, err := ParseURL(node.CouchAPIBase) |
|
if err != nil { |
|
return nil, -1, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v", |
|
name, nodeNo, node.CouchAPIBase, err) |
|
} else if pool != nil { |
|
u.User = pool.client.BaseURL.User |
|
} |
|
return u, nodeNo, err |
|
} |
|
|
|
// DocID is the document ID type for the startkey_docid parameter in |
|
// views. |
|
type DocID string |
|
|
|
func qParam(k, v string) string { |
|
format := `"%s"` |
|
switch k { |
|
case "startkey_docid", "endkey_docid", "stale": |
|
format = "%s" |
|
} |
|
return fmt.Sprintf(format, v) |
|
} |
|
|
|
// ViewURL constructs a URL for a view with the given ddoc, view name, |
|
// and parameters. |
|
func (b *Bucket) ViewURL(ddoc, name string, |
|
params map[string]interface{}) (string, error) { |
|
u, err := b.randomBaseURL() |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
values := url.Values{} |
|
for k, v := range params { |
|
switch t := v.(type) { |
|
case DocID: |
|
values[k] = []string{string(t)} |
|
case string: |
|
values[k] = []string{qParam(k, t)} |
|
case int: |
|
values[k] = []string{fmt.Sprintf(`%d`, t)} |
|
case bool: |
|
values[k] = []string{fmt.Sprintf(`%v`, t)} |
|
default: |
|
b, err := json.Marshal(v) |
|
if err != nil { |
|
return "", fmt.Errorf("unsupported value-type %T in Query, "+ |
|
"json encoder said %v", t, err) |
|
} |
|
values[k] = []string{fmt.Sprintf(`%v`, string(b))} |
|
} |
|
} |
|
|
|
if ddoc == "" && name == "_all_docs" { |
|
u.Path = fmt.Sprintf("/%s/_all_docs", b.GetName()) |
|
} else { |
|
u.Path = fmt.Sprintf("/%s/_design/%s/_view/%s", b.GetName(), ddoc, name) |
|
} |
|
u.RawQuery = values.Encode() |
|
|
|
return u.String(), nil |
|
} |
|
|
|
// ViewCallback is called for each view invocation. |
|
var ViewCallback func(ddoc, name string, start time.Time, err error) |
|
|
|
// ViewCustom performs a view request that can map row values to a |
|
// custom type. |
|
// |
|
// See the source to View for an example usage. |
|
func (b *Bucket) ViewCustom(ddoc, name string, params map[string]interface{}, |
|
vres interface{}) (err error) { |
|
if SlowServerCallWarningThreshold > 0 { |
|
defer slowLog(time.Now(), "call to ViewCustom(%q, %q)", ddoc, name) |
|
} |
|
|
|
if ViewCallback != nil { |
|
defer func(t time.Time) { ViewCallback(ddoc, name, t, err) }(time.Now()) |
|
} |
|
|
|
u, err := b.ViewURL(ddoc, name, params) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
req, err := http.NewRequest("GET", u, nil) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
ah := b.authHandler(false /* bucket not yet locked */) |
|
maybeAddAuth(req, ah) |
|
|
|
res, err := doHTTPRequest(req) |
|
if err != nil { |
|
return fmt.Errorf("error starting view req at %v: %v", u, err) |
|
} |
|
defer res.Body.Close() |
|
|
|
if res.StatusCode != 200 { |
|
bod := make([]byte, 512) |
|
l, _ := res.Body.Read(bod) |
|
return fmt.Errorf("error executing view req at %v: %v - %s", |
|
u, res.Status, bod[:l]) |
|
} |
|
|
|
body, err := ioutil.ReadAll(res.Body) |
|
if err := json.Unmarshal(body, vres); err != nil { |
|
return nil |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// View executes a view. |
|
// |
|
// The ddoc parameter is just the bare name of your design doc without |
|
// the "_design/" prefix. |
|
// |
|
// Parameters are string keys with values that correspond to couchbase |
|
// view parameters. Primitive should work fairly naturally (booleans, |
|
// ints, strings, etc...) and other values will attempt to be JSON |
|
// marshaled (useful for array indexing on on view keys, for example). |
|
// |
|
// Example: |
|
// |
|
// res, err := couchbase.View("myddoc", "myview", map[string]interface{}{ |
|
// "group_level": 2, |
|
// "startkey_docid": []interface{}{"thing"}, |
|
// "endkey_docid": []interface{}{"thing", map[string]string{}}, |
|
// "stale": false, |
|
// }) |
|
func (b *Bucket) View(ddoc, name string, params map[string]interface{}) (ViewResult, error) { |
|
vres := ViewResult{} |
|
|
|
if err := b.ViewCustom(ddoc, name, params, &vres); err != nil { |
|
//error in accessing views. Retry once after a bucket refresh |
|
b.Refresh() |
|
return vres, b.ViewCustom(ddoc, name, params, &vres) |
|
} else { |
|
return vres, nil |
|
} |
|
}
|
|
|