mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 00:11:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			231 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			231 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
	}
 | 
						|
}
 |