1package couchbase 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "math/rand" 9 "net/http" 10 "net/url" 11 "time" 12) 13 14// ViewRow represents a single result from a view. 15// 16// Doc is present only if include_docs was set on the request. 17type ViewRow struct { 18 ID string 19 Key interface{} 20 Value interface{} 21 Doc *interface{} 22} 23 24// A ViewError is a node-specific error indicating a partial failure 25// within a view result. 26type ViewError struct { 27 From string 28 Reason string 29} 30 31func (ve ViewError) Error() string { 32 return "Node: " + ve.From + ", reason: " + ve.Reason 33} 34 35// ViewResult holds the entire result set from a view request, 36// including the rows and the errors. 37type ViewResult struct { 38 TotalRows int `json:"total_rows"` 39 Rows []ViewRow 40 Errors []ViewError 41} 42 43func (b *Bucket) randomBaseURL() (*url.URL, error) { 44 nodes := b.HealthyNodes() 45 if len(nodes) == 0 { 46 return nil, errors.New("no available couch rest URLs") 47 } 48 nodeNo := rand.Intn(len(nodes)) 49 node := nodes[nodeNo] 50 51 b.RLock() 52 name := b.Name 53 pool := b.pool 54 b.RUnlock() 55 56 u, err := ParseURL(node.CouchAPIBase) 57 if err != nil { 58 return nil, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v", 59 name, nodeNo, node.CouchAPIBase, err) 60 } else if pool != nil { 61 u.User = pool.client.BaseURL.User 62 } 63 return u, err 64} 65 66const START_NODE_ID = -1 67 68func (b *Bucket) randomNextURL(lastNode int) (*url.URL, int, error) { 69 nodes := b.HealthyNodes() 70 if len(nodes) == 0 { 71 return nil, -1, errors.New("no available couch rest URLs") 72 } 73 74 var nodeNo int 75 if lastNode == START_NODE_ID || lastNode >= len(nodes) { 76 // randomly select a node if the value of lastNode is invalid 77 nodeNo = rand.Intn(len(nodes)) 78 } else { 79 // wrap around the node list 80 nodeNo = (lastNode + 1) % len(nodes) 81 } 82 83 b.RLock() 84 name := b.Name 85 pool := b.pool 86 b.RUnlock() 87 88 node := nodes[nodeNo] 89 u, err := ParseURL(node.CouchAPIBase) 90 if err != nil { 91 return nil, -1, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v", 92 name, nodeNo, node.CouchAPIBase, err) 93 } else if pool != nil { 94 u.User = pool.client.BaseURL.User 95 } 96 return u, nodeNo, err 97} 98 99// DocID is the document ID type for the startkey_docid parameter in 100// views. 101type DocID string 102 103func qParam(k, v string) string { 104 format := `"%s"` 105 switch k { 106 case "startkey_docid", "endkey_docid", "stale": 107 format = "%s" 108 } 109 return fmt.Sprintf(format, v) 110} 111 112// ViewURL constructs a URL for a view with the given ddoc, view name, 113// and parameters. 114func (b *Bucket) ViewURL(ddoc, name string, 115 params map[string]interface{}) (string, error) { 116 u, err := b.randomBaseURL() 117 if err != nil { 118 return "", err 119 } 120 121 values := url.Values{} 122 for k, v := range params { 123 switch t := v.(type) { 124 case DocID: 125 values[k] = []string{string(t)} 126 case string: 127 values[k] = []string{qParam(k, t)} 128 case int: 129 values[k] = []string{fmt.Sprintf(`%d`, t)} 130 case bool: 131 values[k] = []string{fmt.Sprintf(`%v`, t)} 132 default: 133 b, err := json.Marshal(v) 134 if err != nil { 135 return "", fmt.Errorf("unsupported value-type %T in Query, "+ 136 "json encoder said %v", t, err) 137 } 138 values[k] = []string{fmt.Sprintf(`%v`, string(b))} 139 } 140 } 141 142 if ddoc == "" && name == "_all_docs" { 143 u.Path = fmt.Sprintf("/%s/_all_docs", b.GetName()) 144 } else { 145 u.Path = fmt.Sprintf("/%s/_design/%s/_view/%s", b.GetName(), ddoc, name) 146 } 147 u.RawQuery = values.Encode() 148 149 return u.String(), nil 150} 151 152// ViewCallback is called for each view invocation. 153var ViewCallback func(ddoc, name string, start time.Time, err error) 154 155// ViewCustom performs a view request that can map row values to a 156// custom type. 157// 158// See the source to View for an example usage. 159func (b *Bucket) ViewCustom(ddoc, name string, params map[string]interface{}, 160 vres interface{}) (err error) { 161 if SlowServerCallWarningThreshold > 0 { 162 defer slowLog(time.Now(), "call to ViewCustom(%q, %q)", ddoc, name) 163 } 164 165 if ViewCallback != nil { 166 defer func(t time.Time) { ViewCallback(ddoc, name, t, err) }(time.Now()) 167 } 168 169 u, err := b.ViewURL(ddoc, name, params) 170 if err != nil { 171 return err 172 } 173 174 req, err := http.NewRequest("GET", u, nil) 175 if err != nil { 176 return err 177 } 178 179 ah := b.authHandler(false /* bucket not yet locked */) 180 maybeAddAuth(req, ah) 181 182 res, err := doHTTPRequest(req) 183 if err != nil { 184 return fmt.Errorf("error starting view req at %v: %v", u, err) 185 } 186 defer res.Body.Close() 187 188 if res.StatusCode != 200 { 189 bod := make([]byte, 512) 190 l, _ := res.Body.Read(bod) 191 return fmt.Errorf("error executing view req at %v: %v - %s", 192 u, res.Status, bod[:l]) 193 } 194 195 body, err := ioutil.ReadAll(res.Body) 196 if err := json.Unmarshal(body, vres); err != nil { 197 return nil 198 } 199 200 return nil 201} 202 203// View executes a view. 204// 205// The ddoc parameter is just the bare name of your design doc without 206// the "_design/" prefix. 207// 208// Parameters are string keys with values that correspond to couchbase 209// view parameters. Primitive should work fairly naturally (booleans, 210// ints, strings, etc...) and other values will attempt to be JSON 211// marshaled (useful for array indexing on on view keys, for example). 212// 213// Example: 214// 215// res, err := couchbase.View("myddoc", "myview", map[string]interface{}{ 216// "group_level": 2, 217// "startkey_docid": []interface{}{"thing"}, 218// "endkey_docid": []interface{}{"thing", map[string]string{}}, 219// "stale": false, 220// }) 221func (b *Bucket) View(ddoc, name string, params map[string]interface{}) (ViewResult, error) { 222 vres := ViewResult{} 223 224 if err := b.ViewCustom(ddoc, name, params, &vres); err != nil { 225 //error in accessing views. Retry once after a bucket refresh 226 b.Refresh() 227 return vres, b.ViewCustom(ddoc, name, params, &vres) 228 } else { 229 return vres, nil 230 } 231} 232