From f7d13c8efbffd37990bd14ad8261566c886ae4f4 Mon Sep 17 00:00:00 2001 From: merdan Date: Wed, 7 Sep 2022 17:57:03 +0500 Subject: [PATCH] link parse test ready --- controllers/ParseController.go | 2 - .../leesper/couchdb-golang/.gitignore | 24 + .../leesper/couchdb-golang/.travis.yml | 12 + .../github.com/leesper/couchdb-golang/LICENSE | 29 + .../leesper/couchdb-golang/README.md | 832 +++++++++ .../leesper/couchdb-golang/database.go | 1506 +++++++++++++++++ .../leesper/couchdb-golang/design.go | 379 +++++ .../github.com/leesper/couchdb-golang/doc.go | 34 + .../leesper/couchdb-golang/mapping.go | 226 +++ .../leesper/couchdb-golang/resource.go | 273 +++ .../leesper/couchdb-golang/server.go | 341 ++++ 11 files changed, 3656 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/leesper/couchdb-golang/.gitignore create mode 100644 vendor/github.com/leesper/couchdb-golang/.travis.yml create mode 100644 vendor/github.com/leesper/couchdb-golang/LICENSE create mode 100644 vendor/github.com/leesper/couchdb-golang/README.md create mode 100644 vendor/github.com/leesper/couchdb-golang/database.go create mode 100644 vendor/github.com/leesper/couchdb-golang/design.go create mode 100644 vendor/github.com/leesper/couchdb-golang/doc.go create mode 100644 vendor/github.com/leesper/couchdb-golang/mapping.go create mode 100644 vendor/github.com/leesper/couchdb-golang/resource.go create mode 100644 vendor/github.com/leesper/couchdb-golang/server.go diff --git a/controllers/ParseController.go b/controllers/ParseController.go index 5aafcdd..2031915 100644 --- a/controllers/ParseController.go +++ b/controllers/ParseController.go @@ -1,8 +1,6 @@ package controller import ( - "db_service/gorm_models" - "db_service/models" helper "db_service/pkg" "db_service/repositories" "encoding/json" diff --git a/vendor/github.com/leesper/couchdb-golang/.gitignore b/vendor/github.com/leesper/couchdb-golang/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/leesper/couchdb-golang/.travis.yml b/vendor/github.com/leesper/couchdb-golang/.travis.yml new file mode 100644 index 0000000..c07dbe5 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/.travis.yml @@ -0,0 +1,12 @@ +language: go + +services: + - couchdb + +go: + - 1.7.x + +before_script: + - go vet + +gobuild_args: -v -race diff --git a/vendor/github.com/leesper/couchdb-golang/LICENSE b/vendor/github.com/leesper/couchdb-golang/LICENSE new file mode 100644 index 0000000..9a94030 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2016, leesper +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/leesper/couchdb-golang/README.md b/vendor/github.com/leesper/couchdb-golang/README.md new file mode 100644 index 0000000..884230c --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/README.md @@ -0,0 +1,832 @@ +CouchDB-Golang Library v1.2 +=========================================== + +A Golang library for working with CouchDB 2.x + +supported Golang version: +* 1.7.x + +[![Build Status](https://travis-ci.org/leesper/couchdb-golang.svg?branch=master)](https://travis-ci.org/leesper/couchdb-golang) + +* Resource : a simple wrapper for HTTP requests and error handling +* Server : CouchDB server instance +* Database : CouchDB database instance +* ViewResults : a representation of the results produced by design document views +* ViewDefinition : a definition of view stored in a specific design document +* Document : a representation of document object in database + +Inspired by [CouchDB-Python](https://github.com/djc/couchdb-python) + +# Documentation +```go +package couchdb +import "github.com/leesper/couchdb" +``` + +## Constants +```go +const ( + // DefaultBaseURL is the default address of CouchDB server. + DefaultBaseURL = "http://localhost:5984" +) +``` + +## Variables +```go +var ( + // ErrBatchValue for invalid batch parameter of IterView + ErrBatchValue = errors.New("batch must be 1 or more") + // ErrLimitValue for invalid limit parameter of IterView + ErrLimitValue = errors.New("limit must be 1 or more") +) + +var ( + // ErrSetID for setting ID to document which already has one. + ErrSetID = errors.New("id can only be set on new documents") + // ErrNotStruct for not a struct value + ErrNotStruct = errors.New("value not of struct type") + // ErrNotDocumentEmbedded for not a document-embedded value + ErrNotDocumentEmbedded = errors.New("value not Document-embedded") +) + +var ( + + // ErrNotModified for HTTP status code 304 + ErrNotModified = errors.New("status 304 - not modified") + // ErrBadRequest for HTTP status code 400 + ErrBadRequest = errors.New("status 400 - bad request") + // ErrUnauthorized for HTTP status code 401 + ErrUnauthorized = errors.New("status 401 - unauthorized") + // ErrForbidden for HTTP status code 403 + ErrForbidden = errors.New("status 403 - forbidden") + // ErrNotFound for HTTP status code 404 + ErrNotFound = errors.New("status 404 - not found") + // ErrResourceNotAllowed for HTTP status code 405 + ErrResourceNotAllowed = errors.New("status 405 - resource not allowed") + // ErrNotAcceptable for HTTP status code 406 + ErrNotAcceptable = errors.New("status 406 - not acceptable") + // ErrConflict for HTTP status code 409 + ErrConflict = errors.New("status 409 - conflict") + // ErrPreconditionFailed for HTTP status code 412 + ErrPreconditionFailed = errors.New("status 412 - precondition failed") + // ErrBadContentType for HTTP status code 415 + ErrBadContentType = errors.New("status 415 - bad content type") + // ErrRequestRangeNotSatisfiable for HTTP status code 416 + ErrRequestRangeNotSatisfiable = errors.New("status 416 - requested range not satisfiable") + // ErrExpectationFailed for HTTP status code 417 + ErrExpectationFailed = errors.New("status 417 - expectation failed") + // ErrInternalServerError for HTTP status code 500 + ErrInternalServerError = errors.New("status 500 - internal server error") +) +``` + +## func FromJSONCompatibleMap +```go +func FromJSONCompatibleMap(obj interface{}, docMap map[string]interface{}) error +``` +FromJSONCompatibleMap constructs a Document-embedded struct from a JSON-compatible map. + +## func GenerateUUID +```go +func GenerateUUID() string +``` +GenerateUUID returns a random 128-bit UUID + +## func Load +```go +func Load(db *Database, docID string, obj interface{}) error +``` +Load loads the document in specified database. + +## func Store +```go +func Store(db *Database, obj interface{}) error +``` +Store stores the document in specified database. + +## func SyncMany +```go +func SyncMany(db *Database, viewDefns []*ViewDefinition, removeMissing bool, callback func(map[string]interface{})) ([]UpdateResult, error) +``` +SyncMany ensures that the views stored in the database match the views defined by the corresponding view definitions. This function might update more than one design document. This is done using CouchDB's bulk update to ensure atomicity of the opeation. db: the corresponding database. + +viewDefns: a sequence of \*ViewDefinition instances. + +removeMissing: whether to remove views found in a design document that are not found in the list of ViewDefinition instances, default false. + +callback: a callback function invoked when a design document gets updated; it is called before the doc has actually been saved back to the database. + +## func ToJSONCompatibleMap +```go +func ToJSONCompatibleMap(obj interface{}) (map[string]interface{}, error) +``` +ToJSONCompatibleMap converts a Document-embedded struct into a JSON-compatible map, e.g. anything that cannot be jsonified will be ignored silently. + +## type Database +```go +type Database struct { + // contains filtered or unexported fields +} +``` +Database represents a CouchDB database instance. + +### func NewDatabase +```go +func NewDatabase(urlStr string) (*Database, error) +``` +NewDatabase returns a CouchDB database instance. + +### func NewDatabaseWithResource +```go +func NewDatabaseWithResource(res *Resource) (*Database, error) +``` +NewDatabaseWithResource returns a CouchDB database instance with resource obj. + +### func (d \*Database) Available +```go +func (d *Database) Available() error +``` +Available returns error if the database is not good to go. + +### func (d \*Database) Changes +```go +func (d *Database) Changes(options url.Values) (map[string]interface{}, error) +``` +Changes returns a sorted list of changes feed made to documents in the database. + +### func (d \*Database) Cleanup +```go +func (d *Database) Cleanup() error +``` +Cleanup removes all view index files no longer required by CouchDB. + +### func (d \*Database) Commit +```go +func (d *Database) Commit() error +``` +Commit flushes any recent changes to the specified database to disk. If the server is configured to delay commits or previous requests use the special "X-Couch-Full-Commit: false" header to disable immediate commits, this method can be used to ensure that non-commited changes are commited to physical storage. + +### func (d \*Database) Compact +```go +func (d *Database) Compact() error +``` +Compact compacts the database by compressing the disk database file. + +### func (d \*Database) Contains +```go +func (d *Database) Contains(docid string) error +``` +Contains returns true if the database contains a document with the specified ID. + +### func (d \*Database) Copy +```go +func (d *Database) Copy(srcID, destID, destRev string) (string, error) +``` +Copy copies an existing document to a new or existing document. + +### func (d \*Database) Delete +```go +func (d *Database) Delete(docid string) error +``` +Delete deletes the document with the specified ID. + +### func (d \*Database) DeleteAttachment +```go +func (d *Database) DeleteAttachment(doc map[string]interface{}, name string) error +``` +DeleteAttachment deletes the specified attachment. + +### func (d \*Database) DeleteDoc +```go +func (d *Database) DeleteDoc(doc map[string]interface{}) error +``` +DeleteDoc deletes the specified document + +### func (d \*Database) DeleteIndex +```go +func (d *Database) DeleteIndex(ddoc, name string) error +``` +DeleteIndex deletes index in database. + +### func (d \*Database) DocIDs +```go +func (d *Database) DocIDs() ([]string, error) +``` +DocIDs returns the IDs of all documents in database. + +### func (d \*Database) Get +```go +func (d *Database) Get(docid string, options url.Values) (map[string]interface{}, error) +``` +Get returns the document with the specified ID. + +### func (d \*Database) GetAttachment +```go +func (d *Database) GetAttachment(doc map[string]interface{}, name string) ([]byte, error) +``` +GetAttachment returns the file attachment associated with the document. The raw data is returned as a []byte. + +### func (d \*Database) GetAttachmentID +```go +func (d *Database) GetAttachmentID(docid, name string) ([]byte, error) +``` +GetAttachmentID returns the file attachment associated with the document ID. The raw data is returned as []byte. + +### func (d \*Database) GetIndex +```go +func (d *Database) GetIndex() (map[string]*json.RawMessage, error) +``` +GetIndex gets all indexes created in database. + +### func (d \*Database) GetRevsLimit +```go +func (d *Database) GetRevsLimit() (int, error) +``` +GetRevsLimit gets the current revs_limit(revision limit) setting. + +### func (d \*Database) GetSecurity +```go +func (d *Database) GetSecurity() (map[string]interface{}, error) +``` +GetSecurity returns the current security object from the given database. + +### func (d \*Database) Info +```go +func (d *Database) Info() (map[string]interface{}, error) +``` +Info returns the information about the database. + +### func (\*Database) IterView +```go +func (d *Database) IterView(name string, batch int, wrapper func(Row) Row, options map[string]interface{}) (<-chan Row, error) +``` +IterView returns a channel fetching rows in batches which iterates a row at a time(pagination). + +name: the name of the view, for user-defined views use the format "design_docid/viewname", that is, the document ID of the design document and the name of the view, separated by a /. + +wrapper: an optional function for processing the result rows after retrieved. + +options: optional query parameters. + +### func (d \*Database) Len +```go +func (d *Database) Len() (int, error) +``` +Len returns the number of documents stored in it. + +### func (\*Database) List +```go +func (d *Database) List(name, view string, options map[string]interface{}) (http.Header, []byte, error) +``` +List formats a view using a server-side 'list' function. + +name: the name of the list function in the format "designdoc/listname" + +view: the name of the view in the format "designdoc/viewname" + +options: optional query parameters + +### func (d \*Database) Name +```go +func (d *Database) Name() (string, error) +``` +Name returns the name of database. + +### func (d \*Database) Purge +```go +func (d *Database) Purge(docs []map[string]interface{}) (map[string]interface{}, error) +``` +Purge performs complete removing of the given documents. + +### func (d \*Database) PutAttachment +```go +func (d *Database) PutAttachment(doc map[string]interface{}, content []byte, name, mimeType string) error +``` +PutAttachment uploads the supplied []byte as an attachment to the specified document. doc: the document that the attachment belongs to. Must have \_id and \_rev inside. content: the data to be attached to doc. name: name of attachment. mimeType: MIME type of content. + +### func (d \*Database) PutIndex +```go +func (d *Database) PutIndex(indexFields []string, ddoc, name string) (string, string, error) +``` +PutIndex creates a new index in database. indexFields: a JSON array of field names following the sort syntax. ddoc: optional, name of the design document in which the index will be created. By default each index will be created in its own design document. Indexes can be grouped into design documents for efficiency. However a change to one index in a design document will invalidate all other indexes in the same document. name: optional, name of the index. A name generated automatically if not provided. + +### func (d \*Database) Query +```go +func (d *Database) Query(fields []string, selector string, sorts []string, limit, skip, index interface{}) ([]map[string]interface{}, error) +``` +Query returns documents using a conditional selector statement in Golang. + +selector: A filter string declaring which documents to return, formatted as a Golang statement. + +fields: Specifying which fields to be returned, if passing nil the entire is returned, no automatic inclusion of \_id or other metadata fields. + +sorts: How to order the documents returned, formatted as ["desc(fieldName1)", "desc(fieldName2)"] or ["fieldNameA", "fieldNameB"] of which "asc" is used by default, passing nil to disable ordering. + +limit: Maximum number of results returned, passing nil to use default value(25). + +skip: Skip the first 'n' results, where 'n' is the number specified, passing nil for no-skip. + +index: Instruct a query to use a specific index, specified either as "" or ["", ""], passing nil to use primary index(\_all_docs) by default. + +## Inner functions for selector syntax + +*nor(condexprs...)* matches if none of the conditions in condexprs match($nor). + +For example: nor(year == 1990, year == 1989, year == 1997) returns all documents whose year field not in 1989, 1990 and 1997. + +*all(field, array)* matches an array value if it contains all the elements of the argument array($all). + +For example: all(genre, []string{"Comedy", "Short"} returns all documents whose genre field contains "Comedy" and "Short". + +*any(field, condexpr)* matches an array field with at least one element meets the specified condition($elemMatch). + +For example: any(genre, genre == "Short" || genre == "Horror") returns all documents whose genre field contains "Short" or "Horror" or both. + +*exists(field, boolean)* checks whether the field exists or not, regardless of its value($exists). + +For example: exists(director, false) returns all documents who does not have a director field. + +*typeof(field, type)* checks the document field's type, valid types are "null", "boolean", "number", "string", "array", "object"($type). + +For example: typeof(genre, "array") returns all documents whose genre field is of array type. + +*in(field, array)* the field must exist in the array provided($in). + +For example: in(director, []string{"Mike Portnoy", "Vitali Kanevsky"}) returns all documents whose director field is "Mike Portnoy" or "Vitali Kanevsky". + +*nin(field, array)* the document field must not exist in the array provided($nin). + +For example: nin(year, []int{1990, 1992, 1998}) returns all documents whose year field is not in 1990, 1992 or 1998. + +*size(field, int)* matches the length of an array field in a document($size). + +For example: size(genre, 2) returns all documents whose genre field is of length 2. + +*mod(field, divisor, remainder)* matches documents where field % divisor == remainder($mod). + +For example: mod(year, 2, 1) returns all documents whose year field is an odd number. + +*regex(field, regexstr)* a regular expression pattern to match against the document field. + +For example: regex(title, "^A") returns all documents whose title is begin with an "A". + +##Inner functions for sort syntax + +*asc(field)* sorts the field in ascending order, this is the default option while desc(field) sorts the field in descending order. + +### func (d \*Database) QueryJSON +```go +func (d *Database) QueryJSON(query string) ([]map[string]interface{}, error) +``` +QueryJSON returns documents using a declarative JSON querying syntax. + +### func (d \*Database) Revisions +```go +func (d *Database) Revisions(docid string, options url.Values) ([]map[string]interface{}, error) +``` +Revisions returns all available revisions of the given document in reverse order, e.g. latest first. + +### func (d \*Database) Save +```go +func (d *Database) Save(doc map[string]interface{}, options url.Values) (string, string, error) +``` +Save creates a new document or update an existing document. If doc has no \_id the server will generate a random UUID and a new document will be created. Otherwise the doc's \_id will be used to identify the document to create or update. Trying to update an existing document with an incorrect \_rev will cause failure. *NOTE* It is recommended to avoid saving doc without \_id and instead generate document ID on client side. To avoid such problems you can generate a UUID on the client side. GenerateUUID provides a simple, platform-independent implementation. You can also use other third-party packages instead. doc: the document to create or update. + +### func (d \*Database) Set +```go +func (d *Database) Set(docid string, doc map[string]interface{}) error +``` +Set creates or updates a document with the specified ID. + +### func (d \*Database) SetRevsLimit +```go +func (d *Database) SetRevsLimit(limit int) error +``` +SetRevsLimit sets the maximum number of document revisions that will be tracked by CouchDB. + +### func (d \*Database) SetSecurity +```go +func (d *Database) SetSecurity(securityDoc map[string]interface{}) error +``` +SetSecurity sets the security object for the given database. + +### func (\*Database) Show +```go +func (d *Database) Show(name, docID string, params url.Values) (http.Header, []byte, error) +``` +Show calls a server-side 'show' function. + +name: the name of the show function in the format "designdoc/showname" + +docID: optional document ID to pass to the show function + +params: optional query parameters + +### func (d \*Database) String +```go +func (d *Database) String() string +``` + +### func (d \*Database) Update +```go +func (d *Database) Update(docs []map[string]interface{}, options map[string]interface{}) ([]UpdateResult, error) +``` +Update performs a bulk update or creation of the given documents in a single HTTP request. It returns a 3-tuple (id, rev, error) + +### func (\*Database) UpdateDoc +```go +func (d *Database) UpdateDoc(name, docID string, params url.Values) (http.Header, []byte, error) +``` +UpdateDoc calls server-side update handler. + +name: the name of the update handler function in the format "designdoc/updatename". + +docID: optional document ID to pass to the show function + +params: optional query parameters + +### func (\*Database) View +```go +func (d *Database) View(name string, wrapper func(Row) Row, options map[string]interface{}) (*ViewResults, error) +``` +View executes a predefined design document view and returns the results. + +name: the name of the view, for user-defined views use the format "design_docid/viewname", that is, the document ID of the design document and the name of the view, separated by a /. + +wrapper: an optional function for processing the result rows after retrieved. + +options: optional query parameters. + +## type Document +```go +type Document struct { + ID string `json:"_id,omitempty"` // for json only, call SetID/GetID instead + Rev string `json:"_rev,omitempty"` // for json only, call GetRev instead + // contains filtered or unexported fields +} +``` +Document represents a document object in database. + +### func DocumentWithID +```go +func DocumentWithID(id string) Document +``` +DocumentWithID returns a new Document with ID. + +### func (\*Document) GetID +```go +func (d *Document) GetID() string +``` +GetID returns the document ID. + +### func (\*Document) GetRev +```go +func (d *Document) GetRev() string +``` +GetRev returns the document revision. + +### func (\*Document) SetID +```go +func (d *Document) SetID(id string) error +``` +SetID sets ID for new document or return error. + +### func (\*Document) SetRev +```go +func (d *Document) SetRev(rev string) +``` +SetRev sets revision for document. + +## type Resource +```go +type Resource struct { + // contains filtered or unexported fields +} +``` +Resource handles all requests to CouchDB. + +### func NewResource +```go +func NewResource(urlStr string, header http.Header) (*Resource, error) +``` +NewResource returns a newly-created Resource instance. + +### func (r \*Resource) Delete +```go +func (r *Resource) Delete(path string, header http.Header, params url.Values) (http.Header, []byte, error) +``` +Delete is a wrapper around http.Delete. + +### func (r \*Resource) DeleteJSON +```go +func (r *Resource) DeleteJSON(path string, header http.Header, params url.Values) (http.Header, []byte, error) +``` +DeleteJSON issues a DELETE to the specified URL, with data returned as json. + +### func (r \*Resource) Get +```go +func (r *Resource) Get(path string, header http.Header, params url.Values) (http.Header, []byte, error) +``` +Get is a wrapper around http.Get. + +### func (r \*Resource) GetJSON +```go +func (r *Resource) GetJSON(path string, header http.Header, params url.Values) (http.Header, []byte, error) +``` +GetJSON issues a GET to the specified URL, with data returned as json. + +### func (r \*Resource) Head +```go +func (r *Resource) Head(path string, header http.Header, params url.Values) (http.Header, []byte, error) +``` +Head is a wrapper around http.Head. + +### func (r \*Resource) NewResourceWithURL +```go +func (r *Resource) NewResourceWithURL(resStr string) (*Resource, error) +``` +NewResourceWithURL returns newly created \*Resource combined with resource string. + +### func (r \*Resource) Post +```go +func (r *Resource) Post(path string, header http.Header, body []byte, params url.Values) (http.Header, []byte, error) +``` +Post is a wrapper around http.Post. + +### func (r \*Resource) PostJSON +```go +func (r *Resource) PostJSON(path string, header http.Header, body map[string]interface{}, params url.Values) (http.Header, []byte, error) +``` +PostJSON issues a POST to the specified URL, with data returned as json. + +### func (r \*Resource) Put +```go +func (r *Resource) Put(path string, header http.Header, body []byte, params url.Values) (http.Header, []byte, error) +``` +Put is a wrapper around http.Put. + +### func (r \*Resource) PutJSON +```go +func (r *Resource) PutJSON(path string, header http.Header, body map[string]interface{}, params url.Values) (http.Header, []byte, error) +``` +PutJSON issues a PUT to the specified URL, with data returned as json. + +## type Row +```go +type Row struct { + ID string + Key interface{} + Val interface{} + Doc interface{} + Err error +} +``` +Row represents a row returned by database views. + +### func (Row) String +```go +func (r Row) String() string +``` +String returns a string representation for Row + +## type Server +```go +type Server struct { + // contains filtered or unexported fields +} +``` +Server represents a CouchDB server instance. + +### func NewServer +```go +func NewServer(urlStr string) (*Server, error) +``` +NewServer creates a CouchDB server instance in address urlStr. + +### func NewServerNoFullCommit +```go +func NewServerNoFullCommit(urlStr string) (*Server, error) +``` +NewServerNoFullCommit creates a CouchDB server instance in address urlStr with X-Couch-Full-Commit disabled. + +### func (s \*Server) ActiveTasks +```go +func (s *Server) ActiveTasks() ([]interface{}, error) +``` +ActiveTasks lists of running tasks. + +### func (s \*Server) AddUser +```go +func (s *Server) AddUser(name, password string, roles []string) (string, string, error) +``` +AddUser adds regular user in authentication database. Returns id and rev of the registered user. + +### func (s \*Server) Config +```go +func (s *Server) Config(node string) (map[string]map[string]string, error) +``` +Config returns the entire CouchDB server configuration as JSON structure. + +### func (s \*Server) Contains +```go +func (s *Server) Contains(name string) bool +``` +Contains returns true if a db with given name exsited. + +### func (s \*Server) Create +```go +func (s *Server) Create(name string) (*Database, error) +``` +Create returns a database instance with the given name, returns true if created, if database already existed, returns false, \*Database will be nil if failed. + +### func (s \*Server) DBs +```go +func (s *Server) DBs() ([]string, error) +``` +DBs returns a list of all the databases in the CouchDB server instance. + +### func (s \*Server) Delete +```go +func (s *Server) Delete(name string) error +``` +Delete deletes a database with the given name. Return false if failed. + +### func (s \*Server) Get +```go +func (s *Server) Get(name string) (*Database, error) +``` +Get gets a database instance with the given name. Return nil if failed. + +### func (s \*Server) Len +```go +func (s *Server) Len() (int, error) +``` +Len returns the number of dbs in CouchDB server instance. + +### func (s \*Server) Login +```go +func (s *Server) Login(name, password string) (string, error) +``` +Login regular user in CouchDB, returns authentication token. + +### func (s \*Server) Logout +```go +func (s *Server) Logout(token string) error +``` +Logout regular user in CouchDB. + +### func (s \*Server) Membership +```go +func (s *Server) Membership() ([]string, []string, error) +``` +Membership displays the nodes that are part of the cluster as clusterNodes. The field allNodes displays all nodes this node knows about, including the ones that are part of cluster. + +### func (s \*Server) RemoveUser +```go +func (s *Server) RemoveUser(name string) error +``` +RemoveUser removes regular user in authentication database. + +### func (s \*Server) Replicate +```go +func (s *Server) Replicate(source, target string, options map[string]interface{}) (map[string]interface{}, error) +``` +Replicate requests, configure or stop a replication operation. + +### func (s \*Server) Stats +```go +func (s *Server) Stats(node, entry string) (map[string]interface{}, error) +``` +Stats returns a JSON object containing the statistics for the running server. + +### func (s \*Server) String +```go +func (s *Server) String() string +``` + +### func (s \*Server) UUIDs +```go +func (s *Server) UUIDs(count int) ([]string, error) +``` +UUIDs requests one or more Universally Unique Identifiers from the CouchDB instance. The response is a JSON object providing a list of UUIDs. count - Number of UUIDs to return. Default is 1. + +### func (s \*Server) VerifyToken +```go +func (s *Server) VerifyToken(token string) error +``` +VerifyToken returns error if user's token is invalid. + +### func (s \*Server) Version +```go +func (s *Server) Version() (string, error) +``` +Version returns the version info about CouchDB instance. + +## type UpdateResult +```go +type UpdateResult struct { + ID, Rev string + Err error +} +``` +UpdateResult represents result of an update. + +## type ViewDefinition +```go +type ViewDefinition struct { + // contains filtered or unexported fields +} +``` +ViewDefinition is a definition of view stored in a specific design document. + +### func NewViewDefinition +```go +func NewViewDefinition(design, name, mapFun, reduceFun, language string, wrapper func(Row) Row, options map[string]interface{}) (*ViewDefinition, error) +``` +NewViewDefinition returns a newly-created \*ViewDefinition. design: the name of the design document. + +name: the name of the view. + +mapFun: the map function code. + +reduceFun: the reduce function code(optional). + +language: the name of the programming language used, default is javascript. + +wrapper: an optional function for processing the result rows after retrieved. + +options: view specific options. + +### func (\*ViewDefinition) GetDoc +```go +func (vd *ViewDefinition) GetDoc(db *Database) (map[string]interface{}, error) +``` +GetDoc retrieves the design document corresponding to this view definition from the given database. + +### func (\*ViewDefinition) Sync +```go +func (vd *ViewDefinition) Sync(db *Database) ([]UpdateResult, error) +``` +Sync ensures that the view stored in the database matches the view defined by this instance. + +### func (\*ViewDefinition) View +```go +func (vd *ViewDefinition) View(db *Database, options map[string]interface{}) (*ViewResults, error) +``` +View executes the view definition in the given database. + +## type ViewField +```go +type ViewField func() (*ViewDefinition, error) +``` +ViewField represents a view definition value bound to Document. + +### func NewViewField +```go +func NewViewField(design, name, mapFun, reduceFun, language string, wrapper func(Row) Row, options map[string]interface{}) ViewField +``` +NewViewField returns a ViewField function. design: the name of the design document. + +name: the name of the view. + +mapFun: the map function code. + +reduceFun: the reduce function code(optional). + +language: the name of the programming language used, default is javascript. + +wrapper: an optional function for processing the result rows after retrieved. + +options: view specific options. + +## type ViewResults +```go +type ViewResults struct { + // contains filtered or unexported fields +} +``` +ViewResults represents the results produced by design document views. + +### func (\*ViewResults) Offset +```go +func (vr *ViewResults) Offset() (int, error) +``` +Offset returns offset of ViewResults + +### func (\*ViewResults) Rows +```go +func (vr *ViewResults) Rows() ([]Row, error) +``` +Rows returns a slice of rows mapped (and reduced) by the view. + +### func (\*ViewResults) TotalRows +```go +func (vr *ViewResults) TotalRows() (int, error) +``` +TotalRows returns total rows of ViewResults + +### func (\*ViewResults) UpdateSeq +```go +func (vr *ViewResults) UpdateSeq() (int, error) +``` +UpdateSeq returns update sequence of ViewResults diff --git a/vendor/github.com/leesper/couchdb-golang/database.go b/vendor/github.com/leesper/couchdb-golang/database.go new file mode 100644 index 0000000..2e6fff1 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/database.go @@ -0,0 +1,1506 @@ +package couchdb + +import ( + "bytes" + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "math" + "net/http" + "net/url" + "os" + "reflect" + "strconv" + "strings" +) + +const ( + // DefaultBaseURL is the default address of CouchDB server. + DefaultBaseURL = "http://localhost:5984" +) + +var ( + // ErrBatchValue for invalid batch parameter of IterView + ErrBatchValue = errors.New("batch must be 1 or more") + // ErrLimitValue for invalid limit parameter of IterView + ErrLimitValue = errors.New("limit must be 1 or more") +) + +// getDefaultCouchDBURL returns the default CouchDB server url. +func getDefaultCouchDBURL() string { + var couchdbURLEnviron string + for _, couchdbURLEnviron = range os.Environ() { + if strings.HasPrefix(couchdbURLEnviron, "COUCHDB_URL") { + break + } + } + if len(couchdbURLEnviron) == 0 { + couchdbURLEnviron = DefaultBaseURL + } else { + couchdbURLEnviron = strings.Split(couchdbURLEnviron, "=")[1] + } + return couchdbURLEnviron +} + +// Database represents a CouchDB database instance. +type Database struct { + resource *Resource +} + +// NewDatabase returns a CouchDB database instance. +func NewDatabase(urlStr string) (*Database, error) { + var dbURLStr string + if !strings.HasPrefix(urlStr, "http") { + base, err := url.Parse(getDefaultCouchDBURL()) + if err != nil { + return nil, err + } + dbURL, err := base.Parse(urlStr) + if err != nil { + return nil, err + } + dbURLStr = dbURL.String() + } else { + dbURLStr = urlStr + } + + res, err := NewResource(dbURLStr, nil) + if err != nil { + return nil, err + } + + return newDatabase(res) +} + +// NewDatabaseWithResource returns a CouchDB database instance with resource obj. +func NewDatabaseWithResource(res *Resource) (*Database, error) { + return newDatabase(res) +} + +func newDatabase(res *Resource) (*Database, error) { + return &Database{ + resource: res, + }, nil +} + +// Available returns error if the database is not good to go. +func (d *Database) Available() error { + _, _, err := d.resource.Head("", nil, nil) + return err +} + +// Save creates a new document or update an existing document. +// If doc has no _id the server will generate a random UUID and a new document will be created. +// Otherwise the doc's _id will be used to identify the document to create or update. +// Trying to update an existing document with an incorrect _rev will cause failure. +// *NOTE* It is recommended to avoid saving doc without _id and instead generate document ID on client side. +// To avoid such problems you can generate a UUID on the client side. +// GenerateUUID provides a simple, platform-independent implementation. +// You can also use other third-party packages instead. +// doc: the document to create or update. +func (d *Database) Save(doc map[string]interface{}, options url.Values) (string, string, error) { + var id, rev string + + var httpFunc func(string, http.Header, map[string]interface{}, url.Values) (http.Header, []byte, error) + if v, ok := doc["_id"]; ok { + httpFunc = docResource(d.resource, v.(string)).PutJSON + } else { + httpFunc = d.resource.PostJSON + } + + _, data, err := httpFunc("", nil, doc, options) + if err != nil { + return id, rev, err + } + + var jsonMap map[string]interface{} + jsonMap, err = parseData(data) + if err != nil { + return id, rev, err + } + + if v, ok := jsonMap["id"]; ok { + id = v.(string) + doc["_id"] = id + } + + if v, ok := jsonMap["rev"]; ok { + rev = v.(string) + doc["_rev"] = rev + } + + return id, rev, nil +} + +// Get returns the document with the specified ID. +func (d *Database) Get(docid string, options url.Values) (map[string]interface{}, error) { + docRes := docResource(d.resource, docid) + _, data, err := docRes.GetJSON("", nil, options) + if err != nil { + return nil, err + } + var doc map[string]interface{} + doc, err = parseData(data) + if err != nil { + return nil, err + } + return doc, nil +} + +// Delete deletes the document with the specified ID. +func (d *Database) Delete(docid string) error { + docRes := docResource(d.resource, docid) + header, _, err := docRes.Head("", nil, nil) + if err != nil { + return err + } + rev := strings.Trim(header.Get("ETag"), `"`) + return deleteDoc(docRes, rev) +} + +// DeleteDoc deletes the specified document +func (d *Database) DeleteDoc(doc map[string]interface{}) error { + id, ok := doc["_id"] + if !ok || id == nil { + return errors.New("document ID not existed") + } + + rev, ok := doc["_rev"] + if !ok || rev == nil { + return errors.New("document rev not existed") + } + + docRes := docResource(d.resource, id.(string)) + return deleteDoc(docRes, rev.(string)) +} + +func deleteDoc(docRes *Resource, rev string) error { + _, _, err := docRes.DeleteJSON("", nil, url.Values{"rev": []string{rev}}) + return err +} + +// Set creates or updates a document with the specified ID. +func (d *Database) Set(docid string, doc map[string]interface{}) error { + docRes := docResource(d.resource, docid) + _, data, err := docRes.PutJSON("", nil, doc, nil) + if err != nil { + return err + } + + result, err := parseData(data) + if err != nil { + return err + } + + doc["_id"] = result["id"].(string) + doc["_rev"] = result["rev"].(string) + return nil +} + +// Contains returns true if the database contains a document with the specified ID. +func (d *Database) Contains(docid string) error { + docRes := docResource(d.resource, docid) + _, _, err := docRes.Head("", nil, nil) + return err +} + +// UpdateResult represents result of an update. +type UpdateResult struct { + ID, Rev string + Err error +} + +// Update performs a bulk update or creation of the given documents in a single HTTP request. +// It returns a 3-tuple (id, rev, error) +func (d *Database) Update(docs []map[string]interface{}, options map[string]interface{}) ([]UpdateResult, error) { + results := make([]UpdateResult, len(docs)) + body := map[string]interface{}{} + if options != nil { + for k, v := range options { + body[k] = v + } + } + body["docs"] = docs + + _, data, err := d.resource.PostJSON("_bulk_docs", nil, body, nil) + if err != nil { + return nil, err + } + var jsonArr []map[string]interface{} + err = json.Unmarshal(data, &jsonArr) + if err != nil { + return nil, err + } + + for i, v := range jsonArr { + var retErr error + var result UpdateResult + if val, ok := v["error"]; ok { + errMsg := val.(string) + switch errMsg { + case "conflict": + retErr = ErrConflict + case "forbidden": + retErr = ErrForbidden + default: + retErr = ErrInternalServerError + } + result = UpdateResult{ + ID: v["id"].(string), + Rev: "", + Err: retErr, + } + } else { + id, rev := v["id"].(string), v["rev"].(string) + result = UpdateResult{ + ID: id, + Rev: rev, + Err: retErr, + } + doc := docs[i] + doc["_id"] = id + doc["_rev"] = rev + } + results[i] = result + } + return results, nil +} + +// DocIDs returns the IDs of all documents in database. +func (d *Database) DocIDs() ([]string, error) { + docRes := docResource(d.resource, "_all_docs") + _, data, err := docRes.GetJSON("", nil, nil) + if err != nil { + return nil, err + } + var jsonMap map[string]*json.RawMessage + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return nil, err + } + var jsonArr []*json.RawMessage + json.Unmarshal(*jsonMap["rows"], &jsonArr) + ids := make([]string, len(jsonArr)) + for i, v := range jsonArr { + var row map[string]interface{} + err = json.Unmarshal(*v, &row) + if err != nil { + return ids, err + } + ids[i] = row["id"].(string) + } + return ids, nil +} + +// Name returns the name of database. +func (d *Database) Name() (string, error) { + var name string + info, err := d.Info("") + if err != nil { + return name, err + } + return info["db_name"].(string), nil +} + +// Info returns the information about the database or design document +func (d *Database) Info(ddoc string) (map[string]interface{}, error) { + var data []byte + var err error + if ddoc == "" { + _, data, err = d.resource.GetJSON("", nil, url.Values{}) + if err != nil { + return nil, err + } + } else { + _, data, err = d.resource.GetJSON(fmt.Sprintf("_design/%s/_info", ddoc), nil, nil) + if err != nil { + return nil, err + } + } + + var info map[string]interface{} + err = json.Unmarshal(data, &info) + if err != nil { + return nil, err + } + + return info, nil +} + +func (d *Database) String() string { + return fmt.Sprintf("Database %s", d.resource.base) +} + +// Commit flushes any recent changes to the specified database to disk. +// If the server is configured to delay commits or previous requests use the special +// "X-Couch-Full-Commit: false" header to disable immediate commits, this method +// can be used to ensure that non-commited changes are commited to physical storage. +func (d *Database) Commit() error { + _, _, err := d.resource.PostJSON("_ensure_full_commit", nil, nil, nil) + return err +} + +// Compact compacts the database by compressing the disk database file. +func (d *Database) Compact() error { + _, _, err := d.resource.PostJSON("_compact", nil, nil, nil) + return err +} + +// Revisions returns all available revisions of the given document in reverse +// order, e.g. latest first. +func (d *Database) Revisions(docid string, options url.Values) ([]map[string]interface{}, error) { + docRes := docResource(d.resource, docid) + _, data, err := docRes.GetJSON("", nil, url.Values{"revs": []string{"true"}}) + if err != nil { + return nil, err + } + var jsonMap map[string]*json.RawMessage + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return nil, err + } + var revsMap map[string]interface{} + err = json.Unmarshal(*jsonMap["_revisions"], &revsMap) + if err != nil { + return nil, err + } + startRev := int(revsMap["start"].(float64)) + val := reflect.ValueOf(revsMap["ids"]) + if options == nil { + options = url.Values{} + } + docs := make([]map[string]interface{}, val.Len()) + for i := 0; i < val.Len(); i++ { + rev := fmt.Sprintf("%d-%s", startRev-i, val.Index(i).Interface().(string)) + options.Set("rev", rev) + doc, err := d.Get(docid, options) + if err != nil { + return nil, err + } + docs[i] = doc + } + return docs, nil +} + +// GetAttachment returns the file attachment associated with the document. +// The raw data is returned as a []byte. +func (d *Database) GetAttachment(doc map[string]interface{}, name string) ([]byte, error) { + docid, ok := doc["_id"] + if !ok { + return nil, errors.New("doc _id not existed") + } + return d.getAttachment(docid.(string), name) +} + +// GetAttachmentID returns the file attachment associated with the document ID. +// The raw data is returned as []byte. +func (d *Database) GetAttachmentID(docid, name string) ([]byte, error) { + return d.getAttachment(docid, name) +} + +func (d *Database) getAttachment(docid, name string) ([]byte, error) { + docRes := docResource(docResource(d.resource, docid), name) + _, data, err := docRes.Get("", nil, nil) + return data, err +} + +// PutAttachment uploads the supplied []byte as an attachment to the specified document. +// doc: the document that the attachment belongs to. Must have _id and _rev inside. +// content: the data to be attached to doc. +// name: name of attachment. +// mimeType: MIME type of content. +func (d *Database) PutAttachment(doc map[string]interface{}, content []byte, name, mimeType string) error { + if id, ok := doc["_id"]; !ok || id.(string) == "" { + return errors.New("doc _id not existed") + } + if rev, ok := doc["_rev"]; !ok || rev.(string) == "" { + return errors.New("doc _rev not extisted") + } + + id, rev := doc["_id"].(string), doc["_rev"].(string) + + docRes := docResource(docResource(d.resource, id), name) + header := http.Header{} + header.Set("Content-Type", mimeType) + params := url.Values{} + params.Set("rev", rev) + + _, data, err := docRes.Put("", header, content, params) + if err != nil { + return err + } + + result, err := parseData(data) + if err != nil { + return err + } + + doc["_rev"] = result["rev"].(string) + return nil +} + +// DeleteAttachment deletes the specified attachment +func (d *Database) DeleteAttachment(doc map[string]interface{}, name string) error { + if id, ok := doc["_id"]; !ok || id.(string) == "" { + return errors.New("doc _id not existed") + } + if rev, ok := doc["_rev"]; !ok || rev.(string) == "" { + return errors.New("doc _rev not extisted") + } + + id, rev := doc["_id"].(string), doc["_rev"].(string) + + params := url.Values{} + params.Set("rev", rev) + docRes := docResource(docResource(d.resource, id), name) + _, data, err := docRes.DeleteJSON("", nil, params) + if err != nil { + return err + } + + result, err := parseData(data) + if err != nil { + return err + } + doc["_rev"] = result["rev"] + + return nil +} + +// Copy copies an existing document to a new or existing document. +func (d *Database) Copy(srcID, destID, destRev string) (string, error) { + docRes := docResource(d.resource, srcID) + var destination string + if destRev != "" { + destination = fmt.Sprintf("%s?rev=%s", destID, destRev) + } else { + destination = destID + } + header := http.Header{ + "Destination": []string{destination}, + } + _, data, err := request("COPY", docRes.base, header, nil, nil) + var rev string + if err != nil { + return rev, err + } + result, err := parseData(data) + if err != nil { + return rev, err + } + rev = result["rev"].(string) + return rev, nil +} + +// Changes returns a sorted list of changes feed made to documents in the database. +func (d *Database) Changes(options url.Values) (map[string]interface{}, error) { + _, data, err := d.resource.GetJSON("_changes", nil, options) + if err != nil { + return nil, err + } + result, err := parseData(data) + return result, err +} + +// Purge performs complete removing of the given documents. +func (d *Database) Purge(docs []map[string]interface{}) (map[string]interface{}, error) { + revs := map[string][]string{} + for _, doc := range docs { + id, rev := doc["_id"].(string), doc["_rev"].(string) + if _, ok := revs[id]; !ok { + revs[id] = []string{} + } + revs[id] = append(revs[id], rev) + } + + body := map[string]interface{}{} + for k, v := range revs { + body[k] = v + } + _, data, err := d.resource.PostJSON("_purge", nil, body, nil) + if err != nil { + return nil, err + } + + return parseData(data) +} + +func parseData(data []byte) (map[string]interface{}, error) { + result := map[string]interface{}{} + err := json.Unmarshal(data, &result) + if err != nil { + return result, err + } + if _, ok := result["error"]; ok { + reason := result["reason"].(string) + return result, errors.New(reason) + } + return result, nil +} + +func parseRaw(data []byte) (map[string]*json.RawMessage, error) { + result := map[string]*json.RawMessage{} + err := json.Unmarshal(data, &result) + if err != nil { + return result, err + } + if _, ok := result["error"]; ok { + var reason string + json.Unmarshal(*result["reason"], &reason) + return result, errors.New(reason) + } + return result, nil +} + +// GenerateUUID returns a random 128-bit UUID +func GenerateUUID() string { + b := make([]byte, 16) + _, err := rand.Read(b) + if err != nil { + return "" + } + + uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + return uuid +} + +// SetSecurity sets the security object for the given database. +func (d *Database) SetSecurity(securityDoc map[string]interface{}) error { + _, _, err := d.resource.PutJSON("_security", nil, securityDoc, nil) + return err +} + +// GetSecurity returns the current security object from the given database. +func (d *Database) GetSecurity() (map[string]interface{}, error) { + _, data, err := d.resource.GetJSON("_security", nil, nil) + if err != nil { + return nil, err + } + return parseData(data) +} + +// Len returns the number of documents stored in it. +func (d *Database) Len() (int, error) { + info, err := d.Info("") + if err != nil { + return 0, err + } + return int(info["doc_count"].(float64)), nil +} + +// GetRevsLimit gets the current revs_limit(revision limit) setting. +func (d *Database) GetRevsLimit() (int, error) { + _, data, err := d.resource.Get("_revs_limit", nil, nil) + if err != nil { + return 0, err + } + limit, err := strconv.Atoi(strings.Trim(string(data), "\n")) + if err != nil { + return limit, err + } + return limit, nil +} + +// SetRevsLimit sets the maximum number of document revisions that will be +// tracked by CouchDB. +func (d *Database) SetRevsLimit(limit int) error { + _, _, err := d.resource.Put("_revs_limit", nil, []byte(strconv.Itoa(limit)), nil) + return err +} + +// docResource returns a Resource instance for docID +func docResource(res *Resource, docID string) *Resource { + if len(docID) == 0 { + return res + } + + docRes := res + if docID[:1] == "_" { + paths := strings.SplitN(docID, "/", 2) + for _, p := range paths { + docRes, _ = docRes.NewResourceWithURL(p) + } + return docRes + } + + docRes, _ = res.NewResourceWithURL(url.QueryEscape(docID)) + return docRes +} + +// Cleanup removes all view index files no longer required by CouchDB. +func (d *Database) Cleanup() error { + _, _, err := d.resource.PostJSON("_view_cleanup", nil, nil, nil) + return err +} + +// Query returns documents using a conditional selector statement in Golang. +// +// selector: A filter string declaring which documents to return, formatted as a Golang statement. +// +// fields: Specifying which fields to be returned, if passing nil the entire +// is returned, no automatic inclusion of _id or other metadata fields. +// +// sorts: How to order the documents returned, formatted as ["desc(fieldName1)", "desc(fieldName2)"] +// or ["fieldNameA", "fieldNameB"] of which "asc" is used by default, passing nil to disable ordering. +// +// limit: Maximum number of results returned, passing nil to use default value(25). +// +// skip: Skip the first 'n' results, where 'n' is the number specified, passing nil for no-skip. +// +// index: Instruct a query to use a specific index, specified either as "" or +// ["", ""], passing nil to use primary index(_all_docs) by default. +// +// Inner functions for selector syntax +// +// nor(condexprs...) matches if none of the conditions in condexprs match($nor). +// +// For example: nor(year == 1990, year == 1989, year == 1997) returns all documents +// whose year field not in 1989, 1990 and 1997. +// +// all(field, array) matches an array value if it contains all the elements of the argument array($all). +// +// For example: all(genre, []string{"Comedy", "Short"} returns all documents whose +// genre field contains "Comedy" and "Short". +// +// any(field, condexpr) matches an array field with at least one element meets the specified condition($elemMatch). +// +// For example: any(genre, genre == "Short" || genre == "Horror") returns all documents whose +// genre field contains "Short" or "Horror" or both. +// +// exists(field, boolean) checks whether the field exists or not, regardless of its value($exists). +// +// For example: exists(director, false) returns all documents who does not have a director field. +// +// typeof(field, type) checks the document field's type, valid types are +// "null", "boolean", "number", "string", "array", "object"($type). +// +// For example: typeof(genre, "array") returns all documents whose genre field is of array type. +// +// in(field, array) the field must exist in the array provided($in). +// +// For example: in(director, []string{"Mike Portnoy", "Vitali Kanevsky"}) returns all documents +// whose director field is "Mike Portnoy" or "Vitali Kanevsky". +// +// nin(field, array) the document field must not exist in the array provided($nin). +// +// For example: nin(year, []int{1990, 1992, 1998}) returns all documents whose year field is not +// in 1990, 1992 or 1998. +// +// size(field, int) matches the length of an array field in a document($size). +// +// For example: size(genre, 2) returns all documents whose genre field is of length 2. +// +// mod(field, divisor, remainder) matches documents where field % divisor == remainder($mod). +// +// For example: mod(year, 2, 1) returns all documents whose year field is an odd number. +// +// regex(field, regexstr) a regular expression pattern to match against the document field. +// +// For example: regex(title, "^A") returns all documents whose title is begin with an "A". +// +// Inner functions for sort syntax +// +// asc(field) sorts the field in ascending order, this is the default option while +// desc(field) sorts the field in descending order. +func (d *Database) Query(fields []string, selector string, sorts []string, limit, skip, index interface{}) ([]map[string]interface{}, error) { + selectorJSON, err := parseSelectorSyntax(selector) + if err != nil { + return nil, err + } + find := map[string]interface{}{ + "selector": selectorJSON, + } + + if limitVal, ok := limit.(int); ok { + find["limit"] = limitVal + } + + if skipVal, ok := skip.(int); ok { + find["skip"] = skipVal + } + + if sorts != nil { + sortsJSON, err := parseSortSyntax(sorts) + if err != nil { + return nil, err + } + find["sort"] = sortsJSON + } + + if fields != nil { + find["fields"] = fields + } + + if index != nil { + find["use_index"] = index + } + + return d.queryJSON(find) +} + +// QueryJSON returns documents using a declarative JSON querying syntax. +func (d *Database) QueryJSON(query string) ([]map[string]interface{}, error) { + queryMap := map[string]interface{}{} + err := json.Unmarshal([]byte(query), &queryMap) + if err != nil { + return nil, err + } + return d.queryJSON(queryMap) +} + +func (d *Database) queryJSON(queryMap map[string]interface{}) ([]map[string]interface{}, error) { + _, data, err := d.resource.PostJSON("_find", nil, queryMap, nil) + if err != nil { + return nil, err + } + + result, err := parseRaw(data) + if err != nil { + return nil, err + } + + docs := []map[string]interface{}{} + err = json.Unmarshal(*result["docs"], &docs) + if err != nil { + return nil, err + } + return docs, nil +} + +// parseSelectorSyntax returns a map representing the selector JSON struct. +func parseSelectorSyntax(selector string) (interface{}, error) { + // protect selector against query selector injection attacks + if strings.Contains(selector, "$") { + return nil, fmt.Errorf("no $s are allowed in selector: %s", selector) + } + + // parse selector into abstract syntax tree (ast) + expr, err := parser.ParseExpr(selector) + if err != nil { + return nil, err + } + + // recursively processing ast into json object + selectObj, err := parseAST(expr) + if err != nil { + return nil, err + } + + return selectObj, nil +} + +// parseSortSyntax returns a slice of sort JSON struct. +func parseSortSyntax(sorts []string) (interface{}, error) { + if sorts == nil { + return nil, nil + } + + sortObjs := []interface{}{} + for _, sort := range sorts { + sortExpr, err := parser.ParseExpr(sort) + if err != nil { + return nil, err + } + + sortObj, err := parseAST(sortExpr) + if err != nil { + return nil, err + } + sortObjs = append(sortObjs, sortObj) + } + + return sortObjs, nil +} + +// parseAST converts and returns a JSON struct according to +// CouchDB mango query syntax for the abstract syntax tree represented by expr. +func parseAST(expr ast.Expr) (interface{}, error) { + switch expr := expr.(type) { + case *ast.BinaryExpr: + // fmt.Println("BinaryExpr", expr) + return parseBinary(expr.Op, expr.X, expr.Y) + case *ast.UnaryExpr: + // fmt.Println("UnaryExpr", expr) + return parseUnary(expr.Op, expr.X) + case *ast.CallExpr: + // fmt.Println("CallExpr", expr, expr.Fun, expr.Args) + return parseFuncCall(expr.Fun, expr.Args) + case *ast.Ident: + // fmt.Println("Ident", expr) + switch expr.Name { + case "nil": // for nil value such as _id > nil + return nil, nil + case "true": // for boolean value true + return true, nil + case "false": + return false, nil // for boolean value false + default: + return expr.Name, nil + } + case *ast.BasicLit: + // fmt.Println("BasicLit", expr) + switch expr.Kind { + case token.INT: + intVal, err := strconv.Atoi(expr.Value) + if err != nil { + return nil, err + } + return intVal, nil + case token.FLOAT: + floatVal, err := strconv.ParseFloat(expr.Value, 64) + if err != nil { + return nil, err + } + return floatVal, nil + case token.STRING: + return strings.Trim(expr.Value, `"`), nil + default: + return nil, fmt.Errorf("token type %s not supported", expr.Kind.String()) + } + case *ast.SelectorExpr: + // fmt.Println("SelectorExpr", expr.X, expr.Sel) + xExpr, err := parseAST(expr.X) + if err != nil { + return nil, err + } + return fmt.Sprintf("%s.%s", xExpr, expr.Sel.Name), nil + case *ast.ParenExpr: + pExpr, err := parseAST(expr.X) + if err != nil { + return nil, err + } + return pExpr, nil + case *ast.CompositeLit: + if _, ok := expr.Type.(*ast.ArrayType); !ok { + return nil, fmt.Errorf("not an ArrayType for a composite literal %v", expr.Type) + } + elements := make([]interface{}, len(expr.Elts)) + for idx, elt := range expr.Elts { + e, err := parseAST(elt) + if err != nil { + return nil, err + } + elements[idx] = e + } + return elements, nil + default: + return nil, fmt.Errorf("expressions other than unary, binary and function call are not allowed %v", expr) + } +} + +// parseBinary parses and returns a JSON struct according to +// CouchDB mango query syntax for the supported binary operators. +func parseBinary(operator token.Token, leftOperand, rightOperand ast.Expr) (interface{}, error) { + left, err := parseAST(leftOperand) + if err != nil { + return nil, err + } + right, err := parseAST(rightOperand) + if err != nil { + return nil, err + } + + // <, <=, ==, !=, >=, >, &&, || + switch operator { + case token.LSS: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$lt": right}, + }, nil + case token.LEQ: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$lte": right}, + }, nil + case token.EQL: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$eq": right}, + }, nil + case token.NEQ: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$ne": right}, + }, nil + case token.GEQ: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$gte": right}, + }, nil + case token.GTR: + return map[string]interface{}{ + left.(string): map[string]interface{}{"$gt": right}, + }, nil + case token.LAND: + return map[string]interface{}{ + "$and": []interface{}{left, right}, + }, nil + case token.LOR: + return map[string]interface{}{ + "$or": []interface{}{left, right}, + }, nil + } + return nil, fmt.Errorf("binary operator %s not supported", operator) +} + +// parseUnary parses and returns a JSON struct according to +// CouchDB mango query syntax for supported unary operators. +func parseUnary(operator token.Token, operandExpr ast.Expr) (interface{}, error) { + operand, err := parseAST(operandExpr) + if err != nil { + return nil, err + } + + switch operator { + case token.NOT: + return map[string]interface{}{ + "$not": operand, + }, nil + } + return nil, fmt.Errorf("unary operator %s not supported", operator) +} + +// parseFuncCall parses and returns a JSON struct according to +// CouchDB mango query syntax for supported meta functions. +func parseFuncCall(funcExpr ast.Expr, args []ast.Expr) (interface{}, error) { + funcIdent := funcExpr.(*ast.Ident) + functionName := funcIdent.Name + switch functionName { + case "nor": + if len(args) < 1 { + return nil, fmt.Errorf("function nor(exprs...) need at least 1 arguments, not %d", len(args)) + } + + selectors := make([]interface{}, len(args)) + for idx, arg := range args { + selector, err := parseAST(arg) + if err != nil { + return nil, err + } + selectors[idx] = selector + } + + return map[string]interface{}{ + "$nor": selectors, + }, nil + case "all": + if len(args) != 2 { + return nil, fmt.Errorf("function all(field, array) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + arrayExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$all": arrayExpr}, + }, nil + case "any": + if len(args) != 2 { + return nil, fmt.Errorf("function any(field, condition) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + anyExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + anyExpr, err = removeFieldKey(fieldExpr.(string), anyExpr) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$elemMatch": anyExpr}, + }, nil + case "exists": + if len(args) != 2 { + return nil, fmt.Errorf("function exists(field, boolean) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + boolExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$exists": boolExpr}, + }, nil + case "typeof": + if len(args) != 2 { + return nil, fmt.Errorf("function typeof(field, type) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + typeStr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$type": typeStr}, + }, nil + case "in": + if len(args) != 2 { + return nil, fmt.Errorf("function in(field, array) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + arrExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$in": arrExpr}, + }, nil + case "nin": + if len(args) != 2 { + return nil, fmt.Errorf("function nin(field, array) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + arrExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$nin": arrExpr}, + }, nil + case "size": + if len(args) != 2 { + return nil, fmt.Errorf("function size(field, int) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + intExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$size": intExpr}, + }, nil + case "mod": + if len(args) != 3 { + return nil, fmt.Errorf("function mod(field, divisor, remainder) need 3 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + divisorExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + divisor, ok := divisorExpr.(int) + if !ok { + return nil, fmt.Errorf("invalid divisor %s", divisorExpr) + } + + remainderExpr, err := parseAST(args[2]) + if err != nil { + return nil, err + } + remainder, ok := remainderExpr.(int) + if !ok { + return nil, fmt.Errorf("invalid remainder %s", remainderExpr) + } + + expr, err := parser.ParseExpr(fmt.Sprintf("%#v", []int{divisor, remainder})) + if err != nil { + return nil, err + } + arrExpr, err := parseAST(expr) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$mod": arrExpr}, + }, nil + case "regex": + if len(args) != 2 { + return nil, fmt.Errorf("function regex(field, regexstr) need 2 arguments, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + regexExpr, err := parseAST(args[1]) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + fieldExpr.(string): map[string]interface{}{"$regex": regexExpr}, + }, nil + case "asc": // for sort syntax + if len(args) != 1 { + return nil, fmt.Errorf("function asc(field) need 1 argument, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + return map[string]interface{}{ + fieldExpr.(string): "asc", + }, nil + case "desc": // for sort syntax + if len(args) != 1 { + return nil, fmt.Errorf("function desc(field) need 1 argument, not %d", len(args)) + } + + fieldExpr, err := parseAST(args[0]) + if err != nil { + return nil, err + } + if _, ok := fieldExpr.(string); !ok { + return nil, fmt.Errorf("invalid field expression type %s", fieldExpr) + } + + return map[string]interface{}{ + fieldExpr.(string): "desc", + }, nil + } + return nil, fmt.Errorf("function %s() not supported", functionName) +} + +// removeFieldKey removes the key which equals to fieldName, +// moves its value one level up in the map. +func removeFieldKey(fieldName string, exprMap interface{}) (interface{}, error) { + mapValue := reflect.ValueOf(exprMap) + if mapValue.Kind() != reflect.Map { + return nil, errors.New("not a map type") + } + mapKeys := mapValue.MapKeys() + for _, mapKey := range mapKeys { + // exprMap is a interface type contains map[string]interface{} + // so MapIndex returns a value whose Kind is Interface, so we + // have to call its Interface() methods then pass to ValueOf() + // to get the underline map type. + value := reflect.ValueOf(mapValue.MapIndex(mapKey).Interface()) + if value.Kind() == reflect.Slice { + for idx := 0; idx < value.Len(); idx++ { + elemVal := value.Index(idx) + processed, err := removeFieldKey(fieldName, elemVal.Interface()) + if err != nil { + return nil, err + } + elemVal.Set(reflect.ValueOf(processed)) + } + mapValue.SetMapIndex(mapKey, value) + } else if value.Kind() == reflect.Map { + if mapKey.Interface().(string) == fieldName { // found + if value.Len() != 1 { + return nil, fmt.Errorf("field map length %d, not 1", value.Len()) + } + // setting to empty value deletes the key + mapValue.SetMapIndex(mapKey, reflect.Value{}) + keys := value.MapKeys() + // moves the value one level up + for _, key := range keys { + val := value.MapIndex(key) + mapValue.SetMapIndex(key, val) + } + } else { + processed, err := removeFieldKey(fieldName, value.Interface()) + if err != nil { + return nil, err + } + mapValue.SetMapIndex(mapKey, reflect.ValueOf(processed)) + } + } + } + return mapValue.Interface(), nil +} + +// beautifulJSONString returns a beautified string representing the JSON struct. +func beautifulJSONString(jsonable interface{}) (string, error) { + b, err := json.Marshal(jsonable) + if err != nil { + return "", err + } + var out bytes.Buffer + err = json.Indent(&out, b, "", "\t") + if err != nil { + return "", err + } + return out.String(), nil +} + +// PutIndex creates a new index in database. +// +// indexFields: a JSON array of field names following the sort syntax. +// +// ddoc: optional, name of the design document in which the index will be created. +// By default each index will be created in its own design document. Indexes can be +// grouped into design documents for efficiency. However a change to one index +// in a design document will invalidate all other indexes in the same document. +// +// name: optional, name of the index. A name generated automatically if not provided. +func (d *Database) PutIndex(indexFields []string, ddoc, name string) (string, string, error) { + var design, index string + if len(indexFields) == 0 { + return design, index, errors.New("index fields cannot be empty") + } + + indexObjs, err := parseSortSyntax(indexFields) + if err != nil { + return design, index, err + } + + indexJSON := map[string]interface{}{} + indexJSON["index"] = map[string]interface{}{ + "fields": indexObjs, + } + + if len(ddoc) > 0 { + indexJSON["ddoc"] = ddoc + } + + if len(name) > 0 { + indexJSON["name"] = name + } + + _, data, err := d.resource.PostJSON("_index", nil, indexJSON, nil) + if err != nil { + return design, index, err + } + + result, err := parseData(data) + if err != nil { + return design, index, err + } + design = result["id"].(string) + index = result["name"].(string) + + return design, index, nil +} + +// GetIndex gets all indexes created in database. +func (d *Database) GetIndex() (map[string]*json.RawMessage, error) { + _, data, err := d.resource.GetJSON("_index", nil, nil) + if err != nil { + return nil, err + } + return parseRaw(data) +} + +// DeleteIndex deletes index in database. +func (d *Database) DeleteIndex(ddoc, name string) error { + indexRes := docResource(d.resource, fmt.Sprintf("_index/%s/json/%s", ddoc, name)) + _, _, err := indexRes.DeleteJSON("", nil, nil) + return err +} + +// designPath resturns a make-up design path based on designDoc and designType +// for example designPath("design/foo", "_view") returns "_design/design/_view/foo" +func designPath(designDoc, designType string) string { + if strings.HasPrefix(designDoc, "_") { + return designDoc + } + parts := strings.SplitN(designDoc, "/", 2) + if len(parts) == 1 { + return parts[0] + } + return strings.Join([]string{"_design", parts[0], designType, parts[1]}, "/") +} + +// View executes a predefined design document view and returns the results. +// +// name: the name of the view, for user-defined views use the format "design_docid/viewname", +// that is, the document ID of the design document and the name of the view, separated by a /. +// +// wrapper: an optional function for processing the result rows after retrieved. +// +// options: optional query parameters. +func (d *Database) View(name string, wrapper func(Row) Row, options map[string]interface{}) (*ViewResults, error) { + designDocPath := designPath(name, "_view") + return newViewResults(d.resource, designDocPath, options, wrapper), nil +} + +// IterView returns a channel fetching rows in batches which iterates a row at a time(pagination). +// +// name: the name of the view, for user-defined views use the format "design_docid/viewname", +// that is, the document ID of the design document and the name of the view, separated by a /. +// +// wrapper: an optional function for processing the result rows after retrieved. +// +// options: optional query parameters. +func (d *Database) IterView(name string, batch int, wrapper func(Row) Row, options map[string]interface{}) (<-chan Row, error) { + if batch <= 0 { + return nil, ErrBatchValue + } + + if options == nil { + options = map[string]interface{}{} + } + + _, ok := options["limit"] + var limit int + if ok { + if options["limit"].(int) <= 0 { + return nil, ErrLimitValue + } + limit = options["limit"].(int) + } + + // Row generator + rchan := make(chan Row) + var err error + go func() { + defer close(rchan) + for { + loopLimit := batch + if ok { + loopLimit = min(batch, limit) + } + // get rows in batch with one extra for start of next batch + options["limit"] = loopLimit + 1 + var results *ViewResults + results, err = d.View(name, wrapper, options) + if err != nil { + break + } + var rows []Row + rows, err = results.Rows() + if err != nil { + break + } + + // send all rows to channel except the last extra one + for _, row := range rows[:min(len(rows), loopLimit)] { + rchan <- row + } + + if ok { + limit -= min(len(rows), batch) + } + + if len(rows) <= batch || (ok && limit == 0) { + break + } + options["startkey"] = rows[len(rows)-1].Key + options["startkey_docid"] = rows[len(rows)-1].ID + options["skip"] = 0 + } + }() + return rchan, nil +} + +func min(a, b int) int { + return int(math.Min(float64(a), float64(b))) +} + +// Show calls a server-side 'show' function. +// +// name: the name of the show function in the format "designdoc/showname" +// +// docID: optional document ID to pass to the show function +// +// params: optional query parameters +func (d *Database) Show(name, docID string, params url.Values) (http.Header, []byte, error) { + designDocPath := designPath(name, "_show") + if docID != "" { + designDocPath = fmt.Sprintf("%s/%s", designDocPath, docID) + } + return d.resource.Get(designDocPath, nil, params) +} + +// List formats a view using a server-side 'list' function. +// +// name: the name of the list function in the format "designdoc/listname" +// +// view: the name of the view in the format "designdoc/viewname" +// +// options: optional query parameters +func (d *Database) List(name, view string, options map[string]interface{}) (http.Header, []byte, error) { + designDocPath := designPath(name, "_list") + res := docResource(d.resource, fmt.Sprintf("%s/%s", designDocPath, strings.Split(view, "/")[1])) + return viewLikeResourceRequest(res, options) +} + +// UpdateDoc calls server-side update handler. +// +// name: the name of the update handler function in the format "designdoc/updatename". +// +// docID: optional document ID to pass to the show function +// +// params: optional query parameters +func (d *Database) UpdateDoc(name, docID string, params url.Values) (http.Header, []byte, error) { + designDocPath := designPath(name, "_update") + if docID == "" { + return d.resource.Post(designDocPath, nil, nil, params) + } + + designDocPath = fmt.Sprintf("%s/%s", designDocPath, docID) + return d.resource.Put(designDocPath, nil, nil, params) +} diff --git a/vendor/github.com/leesper/couchdb-golang/design.go b/vendor/github.com/leesper/couchdb-golang/design.go new file mode 100644 index 0000000..56c12be --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/design.go @@ -0,0 +1,379 @@ +package couchdb + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "reflect" + "sort" + "strings" +) + +// Row represents a row returned by database views. +type Row struct { + ID string + Key interface{} + Val interface{} + Doc interface{} + Err error +} + +// String returns a string representation for Row +func (r Row) String() string { + id := fmt.Sprintf("%s=%s", "id", r.ID) + key := fmt.Sprintf("%s=%v", "key", r.Key) + doc := fmt.Sprintf("%s=%v", "doc", r.Doc) + estr := fmt.Sprintf("%s=%v", "err", r.Err) + val := fmt.Sprintf("%s=%v", "val", r.Val) + return fmt.Sprintf("<%s %s>", "Row", strings.Join([]string{id, key, doc, estr, val}, ", ")) +} + +// ViewResults represents the results produced by design document views. +type ViewResults struct { + resource *Resource + designDoc string + options map[string]interface{} + wrapper func(Row) Row + + offset int + totalRows int + updateSeq int + rows []Row + err error +} + +// newViewResults returns a newly-allocated *ViewResults +func newViewResults(r *Resource, ddoc string, opt map[string]interface{}, wr func(Row) Row) *ViewResults { + return &ViewResults{ + resource: r, + designDoc: ddoc, + options: opt, + wrapper: wr, + offset: -1, + totalRows: -1, + updateSeq: -1, + } +} + +// Offset returns offset of ViewResults +func (vr *ViewResults) Offset() (int, error) { + if vr.rows == nil { + vr.rows, vr.err = vr.fetch() + } + return vr.offset, vr.err +} + +// TotalRows returns total rows of ViewResults +func (vr *ViewResults) TotalRows() (int, error) { + if vr.rows == nil { + vr.rows, vr.err = vr.fetch() + } + return vr.totalRows, vr.err +} + +// UpdateSeq returns update sequence of ViewResults +func (vr *ViewResults) UpdateSeq() (int, error) { + if vr.rows == nil { + vr.rows, vr.err = vr.fetch() + } + return vr.updateSeq, vr.err +} + +// Rows returns a slice of rows mapped (and reduced) by the view. +func (vr *ViewResults) Rows() ([]Row, error) { + if vr.rows == nil { + vr.rows, vr.err = vr.fetch() + } + return vr.rows, vr.err +} + +func viewLikeResourceRequest(res *Resource, opts map[string]interface{}) (http.Header, []byte, error) { + params := url.Values{} + body := map[string]interface{}{} + for key, val := range opts { + switch key { + case "keys": // json-array, put in body and send POST request + body[key] = val + case "key", "startkey", "start_key", "endkey", "end_key": + data, err := json.Marshal(val) + if err != nil { + return nil, nil, err + } + params.Add(key, string(data)) + case "conflicts", "descending", "group", "include_docs", "attachments", "att_encoding_info", "inclusive_end", "reduce", "sorted", "update_seq": + if val.(bool) { + params.Add(key, "true") + } else { + params.Add(key, "false") + } + case "endkey_docid", "end_key_doc_id", "stale", "startkey_docid", "start_key_doc_id", "format": // format for _list request + params.Add(key, val.(string)) + case "group_level", "limit", "skip": + params.Add(key, fmt.Sprintf("%d", val)) + default: + switch val := val.(type) { + case bool: + if val { + params.Add(key, "true") + } else { + params.Add(key, "false") + } + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + params.Add(key, fmt.Sprintf("%d", val)) + case float32, float64: + params.Add(key, fmt.Sprintf("%f", val)) + default: + return nil, nil, fmt.Errorf("value %v not supported", val) + } + } + } + + if len(body) > 0 { + return res.PostJSON("", nil, body, params) + } + + return res.GetJSON("", nil, params) +} + +func (vr *ViewResults) fetch() ([]Row, error) { + res := docResource(vr.resource, vr.designDoc) + _, data, err := viewLikeResourceRequest(res, vr.options) + if err != nil { + return nil, err + } + + var jsonMap map[string]*json.RawMessage + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return nil, err + } + + var totalRows float64 + json.Unmarshal(*jsonMap["total_rows"], &totalRows) + vr.totalRows = int(totalRows) + + if offsetRaw, ok := jsonMap["offset"]; ok { + var offset float64 + json.Unmarshal(*offsetRaw, &offset) + vr.offset = int(offset) + } + + if updateSeqRaw, ok := jsonMap["update_seq"]; ok { + var updateSeq float64 + json.Unmarshal(*updateSeqRaw, &updateSeq) + vr.updateSeq = int(updateSeq) + } + + var rowsRaw []*json.RawMessage + json.Unmarshal(*jsonMap["rows"], &rowsRaw) + + rows := make([]Row, len(rowsRaw)) + var rowMap map[string]interface{} + for idx, raw := range rowsRaw { + json.Unmarshal(*raw, &rowMap) + row := Row{} + if id, ok := rowMap["id"]; ok { + row.ID = id.(string) + } + + if key, ok := rowMap["key"]; ok { + row.Key = key + } + + if val, ok := rowMap["value"]; ok { + row.Val = val + } + + if errmsg, ok := rowMap["error"]; ok { + row.Err = errors.New(errmsg.(string)) + } + + if doc, ok := rowMap["doc"]; ok { + row.Doc = doc + } + + if vr.wrapper != nil { + row = vr.wrapper(row) + } + rows[idx] = row + } + return rows, nil +} + +// ViewDefinition is a definition of view stored in a specific design document. +type ViewDefinition struct { + design, name, mapFun, reduceFun, language string + wrapper func(Row) Row + options map[string]interface{} +} + +// NewViewDefinition returns a newly-created *ViewDefinition. +// design: the name of the design document. +// +// name: the name of the view. +// +// mapFun: the map function code. +// +// reduceFun: the reduce function code(optional). +// +// language: the name of the programming language used, default is javascript. +// +// wrapper: an optional function for processing the result rows after retrieved. +// +// options: view specific options. +func NewViewDefinition(design, name, mapFun, reduceFun, language string, wrapper func(Row) Row, options map[string]interface{}) (*ViewDefinition, error) { + if language == "" { + language = "javascript" + } + + if mapFun == "" { + return nil, errors.New("map function empty") + } + + return &ViewDefinition{ + design: design, + name: name, + mapFun: strings.TrimLeft(mapFun, "\n"), + reduceFun: strings.TrimLeft(reduceFun, "\n"), + language: language, + wrapper: wrapper, + options: options, + }, nil +} + +// View executes the view definition in the given database. +func (vd *ViewDefinition) View(db *Database, options map[string]interface{}) (*ViewResults, error) { + opts := deepCopy(options) + for k, v := range vd.options { + opts[k] = v + } + return db.View(fmt.Sprintf("%s/%s", vd.design, vd.name), nil, opts) +} + +// GetDoc retrieves the design document corresponding to this view definition from +// the given database. +func (vd *ViewDefinition) GetDoc(db *Database) (map[string]interface{}, error) { + if db == nil { + return nil, errors.New("database nil") + } + return db.Get(fmt.Sprintf("_design/%s", vd.design), nil) +} + +// Sync ensures that the view stored in the database matches the view defined by this instance. +func (vd *ViewDefinition) Sync(db *Database) ([]UpdateResult, error) { + if db == nil { + return nil, errors.New("database nil") + } + return SyncMany(db, []*ViewDefinition{vd}, false, nil) +} + +// SyncMany ensures that the views stored in the database match the views defined +// by the corresponding view definitions. This function might update more than +// one design document. This is done using CouchDB's bulk update to ensure atomicity of the opeation. +// db: the corresponding database. +// +// viewDefns: a sequence of *ViewDefinition instances. +// +// removeMissing: whether to remove views found in a design document that are not +// found in the list of ViewDefinition instances, default false. +// +// callback: a callback function invoked when a design document gets updated; +// it is called before the doc has actually been saved back to the database. +func SyncMany(db *Database, viewDefns []*ViewDefinition, removeMissing bool, callback func(map[string]interface{})) ([]UpdateResult, error) { + if db == nil { + return nil, errors.New("database nil") + } + + docs := []map[string]interface{}{} + designs := map[string]bool{} + defMap := map[string][]*ViewDefinition{} + + for _, dfn := range viewDefns { + designs[dfn.design] = true + if _, ok := defMap[dfn.design]; !ok { + defMap[dfn.design] = []*ViewDefinition{} + } + defMap[dfn.design] = append(defMap[dfn.design], dfn) + } + + orders := []string{} + for k := range designs { + orders = append(orders, k) + } + sort.Strings(orders) + + for _, design := range orders { + docID := fmt.Sprintf("_design/%s", design) + doc, err := db.Get(docID, nil) + if err != nil { + doc = map[string]interface{}{"_id": docID} + } + origDoc := deepCopy(doc) + languages := map[string]bool{} + + missing := map[string]bool{} + vs, ok := doc["views"] + if ok { + for k := range vs.(map[string]interface{}) { + missing[k] = true + } + } + + for _, dfn := range defMap[design] { + funcs := map[string]interface{}{"map": dfn.mapFun} + if len(dfn.reduceFun) > 0 { + funcs["reduce"] = dfn.reduceFun + } + if dfn.options != nil { + funcs["options"] = dfn.options + } + _, ok = doc["views"] + if ok { + doc["views"].(map[string]interface{})[dfn.name] = funcs + } else { + doc["views"] = map[string]interface{}{dfn.name: funcs} + } + languages[dfn.language] = true + if missing[dfn.name] { + delete(missing, dfn.name) + } + } + + if removeMissing { + for k := range missing { + delete(doc["views"].(map[string]interface{}), k) + } + } else if _, ok := doc["language"]; ok { + languages[doc["language"].(string)] = true + } + + langs := []string{} + for lang := range languages { + langs = append(langs, lang) + } + + if len(langs) > 1 { + return nil, fmt.Errorf("found different language views in one design document %v", langs) + } + doc["language"] = langs[0] + + if !reflect.DeepEqual(doc, origDoc) { + if callback != nil { + callback(doc) + } + docs = append(docs, doc) + } + } + + return db.Update(docs, nil) +} + +func deepCopy(src map[string]interface{}) map[string]interface{} { + dst := map[string]interface{}{} + for k, v := range src { + dst[k] = v + } + return dst +} diff --git a/vendor/github.com/leesper/couchdb-golang/doc.go b/vendor/github.com/leesper/couchdb-golang/doc.go new file mode 100644 index 0000000..c541516 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/doc.go @@ -0,0 +1,34 @@ +// Package couchdb provides components to work with CouchDB 2.x with Go. +// +// Resource is the low-level wrapper functions of HTTP methods +// used for communicating with CouchDB Server. +// +// Server contains all the functions to work with CouchDB server, including some +// basic functions to facilitate the basic user management provided by it. +// +// Database contains all the functions to work with CouchDB database, such as +// documents manipulating and querying. +// +// ViewResults represents the results produced by design document views. When calling +// any of its functions like Offset(), TotalRows(), UpdateSeq() or Rows(), it will +// perform a query on views on server side, and returns results as slice of Row +// +// ViewDefinition is a definition of view stored in a specific design document, +// you can define your own map-reduce functions and Sync with the database. +// +// Document represents a document object in database. All struct that can be mapped +// into CouchDB document must have it embedded. For example: +// +// type User struct { +// Name string `json:"name"` +// Age int `json:"age"` +// Document +// } +// user := User{"Mike", 18} +// anotherUser := User{} +// +// Then you can call Store(db, &user) to store it into CouchDB or Load(db, user.GetID(), &anotherUser) +// to get the data from database. +// +// ViewField represents a view definition value bound to Document. +package couchdb diff --git a/vendor/github.com/leesper/couchdb-golang/mapping.go b/vendor/github.com/leesper/couchdb-golang/mapping.go new file mode 100644 index 0000000..39d46a0 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/mapping.go @@ -0,0 +1,226 @@ +package couchdb + +import ( + "encoding/json" + "errors" + "reflect" +) + +var ( + // ErrSetID for setting ID to document which already has one. + ErrSetID = errors.New("id can only be set on new documents") + // ErrNotStruct for not a struct value + ErrNotStruct = errors.New("value not of struct type") + // ErrNotDocumentEmbedded for not a document-embedded value + ErrNotDocumentEmbedded = errors.New("value not Document-embedded") + zero = reflect.Value{} +) + +// Document represents a document object in database. +type Document struct { + id string + rev string + ID string `json:"_id,omitempty"` // for json only, call SetID/GetID instead + Rev string `json:"_rev,omitempty"` // for json only, call GetRev instead +} + +// DocumentWithID returns a new Document with ID. +func DocumentWithID(id string) Document { + return Document{ + id: id, + } +} + +// SetID sets ID for new document or return error. +func (d *Document) SetID(id string) error { + if d.id != "" { + return ErrSetID + } + d.id = id + return nil +} + +// GetID returns the document ID. +func (d *Document) GetID() string { + return d.id +} + +// SetRev sets revision for document. +func (d *Document) SetRev(rev string) { + d.rev = rev +} + +// GetRev returns the document revision. +func (d *Document) GetRev() string { + return d.rev +} + +// Store stores the document in specified database. +// obj: a Document-embedded struct value, its id and rev will be updated after stored, +// so caller must pass a pointer value. +func Store(db *Database, obj interface{}) error { + ptrValue := reflect.ValueOf(obj) + if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct { + return ErrNotStruct + } + + if ptrValue.Elem().FieldByName("Document") == zero { + return ErrNotDocumentEmbedded + } + + jsonIDField := ptrValue.Elem().FieldByName("ID") + getIDMethod := ptrValue.MethodByName("GetID") + + idStr := getIDMethod.Call([]reflect.Value{})[0].Interface().(string) + if idStr != "" { + jsonIDField.SetString(idStr) + } + + jsonRevField := ptrValue.Elem().FieldByName("Rev") + getRevMethod := ptrValue.MethodByName("GetRev") + revStr := getRevMethod.Call([]reflect.Value{})[0].Interface().(string) + if revStr != "" { + jsonRevField.SetString(revStr) + } + + doc, err := ToJSONCompatibleMap(ptrValue.Elem().Interface()) + if err != nil { + return err + } + + id, rev, err := db.Save(doc, nil) + if err != nil { + return err + } + + setIDMethod := ptrValue.MethodByName("SetID") + setRevMethod := ptrValue.MethodByName("SetRev") + + if idStr == "" { + setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)}) + } + + setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)}) + jsonRevField.SetString(rev) + + return nil +} + +// Load loads the document in specified database. +func Load(db *Database, docID string, obj interface{}) error { + ptrValue := reflect.ValueOf(obj) + if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct { + return ErrNotStruct + } + + if ptrValue.Elem().FieldByName("Document") == zero { + return ErrNotDocumentEmbedded + } + + doc, err := db.Get(docID, nil) + if err != nil { + return err + } + + err = FromJSONCompatibleMap(obj, doc) + if err != nil { + return err + } + + if id, ok := doc["_id"]; ok { + setIDMethod := ptrValue.MethodByName("SetID") + setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)}) + } + + if rev, ok := doc["_rev"]; ok { + setRevMethod := ptrValue.MethodByName("SetRev") + setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)}) + } + + return nil +} + +// FromJSONCompatibleMap constructs a Document-embedded struct from a JSON-compatible map. +func FromJSONCompatibleMap(obj interface{}, docMap map[string]interface{}) error { + ptrValue := reflect.ValueOf(obj) + if ptrValue.Kind() != reflect.Ptr || ptrValue.Elem().Kind() != reflect.Struct { + return ErrNotStruct + } + + if ptrValue.Elem().FieldByName("Document") == zero { + return ErrNotDocumentEmbedded + } + + data, err := json.Marshal(docMap) + if err != nil { + return err + } + + err = json.Unmarshal(data, obj) + if err != nil { + return err + } + + if id, ok := docMap["_id"]; ok { + setIDMethod := ptrValue.MethodByName("SetID") + setIDMethod.Call([]reflect.Value{reflect.ValueOf(id)}) + } + + if rev, ok := docMap["_rev"]; ok { + setRevMethod := ptrValue.MethodByName("SetRev") + setRevMethod.Call([]reflect.Value{reflect.ValueOf(rev)}) + } + + return nil +} + +// ToJSONCompatibleMap converts a Document-embedded struct into a JSON-compatible map, +// e.g. anything that cannot be jsonified will be ignored silently. +func ToJSONCompatibleMap(obj interface{}) (map[string]interface{}, error) { + structValue := reflect.ValueOf(obj) + if structValue.Kind() != reflect.Struct { + return nil, ErrNotStruct + } + + zero := reflect.Value{} + if structValue.FieldByName("Document") == zero { + return nil, ErrNotDocumentEmbedded + } + + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + doc := map[string]interface{}{} + err = json.Unmarshal(data, &doc) + if err != nil { + return nil, err + } + + return doc, nil +} + +// ViewField represents a view definition value bound to Document. +type ViewField func() (*ViewDefinition, error) + +// NewViewField returns a ViewField function. +// design: the name of the design document. +// +// name: the name of the view. +// +// mapFun: the map function code. +// +// reduceFun: the reduce function code(optional). +// +// language: the name of the programming language used, default is javascript. +// +// wrapper: an optional function for processing the result rows after retrieved. +// +// options: view specific options. +func NewViewField(design, name, mapFun, reduceFun, language string, wrapper func(Row) Row, options map[string]interface{}) ViewField { + f := func() (*ViewDefinition, error) { + return NewViewDefinition(design, name, mapFun, reduceFun, language, wrapper, options) + } + return ViewField(f) +} diff --git a/vendor/github.com/leesper/couchdb-golang/resource.go b/vendor/github.com/leesper/couchdb-golang/resource.go new file mode 100644 index 0000000..ea89f36 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/resource.go @@ -0,0 +1,273 @@ +package couchdb + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + "strings" +) + +var ( + httpClient *http.Client + + // ErrNotModified for HTTP status code 304 + ErrNotModified = errors.New("status 304 - not modified") + // ErrBadRequest for HTTP status code 400 + ErrBadRequest = errors.New("status 400 - bad request") + // ErrUnauthorized for HTTP status code 401 + ErrUnauthorized = errors.New("status 401 - unauthorized") + // ErrForbidden for HTTP status code 403 + ErrForbidden = errors.New("status 403 - forbidden") + // ErrNotFound for HTTP status code 404 + ErrNotFound = errors.New("status 404 - not found") + // ErrResourceNotAllowed for HTTP status code 405 + ErrResourceNotAllowed = errors.New("status 405 - resource not allowed") + // ErrNotAcceptable for HTTP status code 406 + ErrNotAcceptable = errors.New("status 406 - not acceptable") + // ErrConflict for HTTP status code 409 + ErrConflict = errors.New("status 409 - conflict") + // ErrPreconditionFailed for HTTP status code 412 + ErrPreconditionFailed = errors.New("status 412 - precondition failed") + // ErrBadContentType for HTTP status code 415 + ErrBadContentType = errors.New("status 415 - bad content type") + // ErrRequestRangeNotSatisfiable for HTTP status code 416 + ErrRequestRangeNotSatisfiable = errors.New("status 416 - requested range not satisfiable") + // ErrExpectationFailed for HTTP status code 417 + ErrExpectationFailed = errors.New("status 417 - expectation failed") + // ErrInternalServerError for HTTP status code 500 + ErrInternalServerError = errors.New("status 500 - internal server error") + + statusErrMap = map[int]error{ + 304: ErrNotModified, + 400: ErrBadRequest, + 401: ErrUnauthorized, + 403: ErrForbidden, + 404: ErrNotFound, + 405: ErrResourceNotAllowed, + 406: ErrNotAcceptable, + 409: ErrConflict, + 412: ErrPreconditionFailed, + 415: ErrBadContentType, + 416: ErrRequestRangeNotSatisfiable, + 417: ErrExpectationFailed, + 500: ErrInternalServerError, + } +) + +func init() { + httpClient = http.DefaultClient +} + +// Resource handles all requests to CouchDB +type Resource struct { + header http.Header + base *url.URL +} + +// NewResource returns a newly-created Resource instance +func NewResource(urlStr string, header http.Header) (*Resource, error) { + u, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + if strings.HasPrefix(urlStr, "https") { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + httpClient = &http.Client{ + Transport: tr, + } + } + + h := http.Header{} + if header != nil { + h = header + } + + return &Resource{ + header: h, + base: u, + }, nil +} + +func combine(base *url.URL, resPath string) (*url.URL, error) { + if resPath == "" { + return base, nil + } + u, err := base.Parse(path.Join(base.Path, resPath)) + return u, err +} + +// NewResourceWithURL returns newly created *Resource combined with resource string. +func (r *Resource) NewResourceWithURL(resStr string) (*Resource, error) { + u, err := combine(r.base, resStr) + if err != nil { + return nil, err + } + + return &Resource{ + header: r.header, + base: u, + }, nil +} + +// Head is a wrapper around http.Head +func (r *Resource) Head(path string, header http.Header, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodHead, u, header, nil, params) +} + +// Get is a wrapper around http.Get +func (r *Resource) Get(path string, header http.Header, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodGet, u, header, nil, params) +} + +// Post is a wrapper around http.Post +func (r *Resource) Post(path string, header http.Header, body []byte, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodPost, u, header, bytes.NewReader(body), params) +} + +// Delete is a wrapper around http.Delete +func (r *Resource) Delete(path string, header http.Header, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodDelete, u, header, nil, params) +} + +// Put is a wrapper around http.Put +func (r *Resource) Put(path string, header http.Header, body []byte, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodPut, u, header, bytes.NewReader(body), params) +} + +// GetJSON issues a GET to the specified URL, with data returned as json +func (r *Resource) GetJSON(path string, header http.Header, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + return request(http.MethodGet, u, header, nil, params) +} + +// PostJSON issues a POST to the specified URL, with data returned as json +func (r *Resource) PostJSON(path string, header http.Header, body map[string]interface{}, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, nil, err + } + + return request(http.MethodPost, u, header, bytes.NewReader(jsonBody), params) +} + +// DeleteJSON issues a DELETE to the specified URL, with data returned as json +func (r *Resource) DeleteJSON(path string, header http.Header, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + + return request(http.MethodDelete, u, header, nil, params) +} + +// PutJSON issues a PUT to the specified URL, with data returned as json +func (r *Resource) PutJSON(path string, header http.Header, body map[string]interface{}, params url.Values) (http.Header, []byte, error) { + u, err := combine(r.base, path) + if err != nil { + return nil, nil, err + } + + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, nil, err + } + + return request(http.MethodPut, u, header, bytes.NewReader(jsonBody), params) +} + +func checkHTTPStatusError(status int) error { + err, ok := statusErrMap[status] + if !ok { + return nil + } + return err +} + +// helper function to make real request +func request(method string, u *url.URL, header http.Header, body io.Reader, params url.Values) (http.Header, []byte, error) { + method = strings.ToUpper(method) + + u.RawQuery = params.Encode() + var username, password string + if u.User != nil { + username = u.User.Username() + password, _ = u.User.Password() + } + req, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, nil, err + } + + if len(username) > 0 && len(password) > 0 { + req.SetBasicAuth(username, password) + } + + // Accept and Content-type are highly recommended for CouchDB + setDefault(&req.Header, "Accept", "application/json") + setDefault(&req.Header, "Content-Type", "application/json") + updateHeader(&req.Header, &header) + rsp, err := httpClient.Do(req) + if err != nil { + return nil, nil, err + } + defer rsp.Body.Close() + data, err := ioutil.ReadAll(rsp.Body) + if err != nil { + return nil, nil, err + } + + return rsp.Header, data, checkHTTPStatusError(rsp.StatusCode) +} + +// setDefault sets the default value if key not existe in header +func setDefault(header *http.Header, key, value string) { + if header.Get(key) == "" { + header.Set(key, value) + } +} + +// updateHeader updates existing header with new values +func updateHeader(header *http.Header, extra *http.Header) { + if header != nil && extra != nil { + for k := range *extra { + header.Set(k, extra.Get(k)) + } + } +} diff --git a/vendor/github.com/leesper/couchdb-golang/server.go b/vendor/github.com/leesper/couchdb-golang/server.go new file mode 100644 index 0000000..61c1968 --- /dev/null +++ b/vendor/github.com/leesper/couchdb-golang/server.go @@ -0,0 +1,341 @@ +package couchdb + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +// Server represents a CouchDB server instance. +type Server struct { + resource *Resource +} + +// NewServer creates a CouchDB server instance in address urlStr. +func NewServer(urlStr string) (*Server, error) { + return newServer(urlStr, true) +} + +// NewServerNoFullCommit creates a CouchDB server instance in address urlStr +// with X-Couch-Full-Commit disabled. +func NewServerNoFullCommit(urlStr string) (*Server, error) { + return newServer(urlStr, false) +} + +func newServer(urlStr string, fullCommit bool) (*Server, error) { + res, err := NewResource(urlStr, nil) + if err != nil { + return nil, err + } + + s := &Server{ + resource: res, + } + + if !fullCommit { + s.resource.header.Set("X-Couch-Full-Commit", "false") + } + return s, nil +} + +// Config returns the entire CouchDB server configuration as JSON structure. +func (s *Server) Config(node string) (map[string]map[string]string, error) { + _, data, err := s.resource.GetJSON(fmt.Sprintf("_node/%s/_config", node), nil, nil) + if err != nil { + return nil, err + } + var config map[string]map[string]string + err = json.Unmarshal(data, &config) + if err != nil { + return nil, err + } + return config, nil +} + +// Version returns the version info about CouchDB instance. +func (s *Server) Version() (string, error) { + var jsonMap map[string]interface{} + + _, data, err := s.resource.GetJSON("", nil, nil) + if err != nil { + return "", err + } + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return "", err + } + + return jsonMap["version"].(string), nil +} + +func (s *Server) String() string { + return fmt.Sprintf("Server %s", s.resource.base) +} + +// ActiveTasks lists of running tasks. +func (s *Server) ActiveTasks() ([]interface{}, error) { + var tasks []interface{} + _, data, err := s.resource.GetJSON("_active_tasks", nil, nil) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &tasks) + if err != nil { + return nil, err + } + return tasks, nil +} + +// DBs returns a list of all the databases in the CouchDB server instance. +func (s *Server) DBs() ([]string, error) { + var dbs []string + _, data, err := s.resource.GetJSON("_all_dbs", nil, nil) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &dbs) + if err != nil { + return nil, err + } + return dbs, nil +} + +// Stats returns a JSON object containing the statistics for the running server. +func (s *Server) Stats(node, entry string) (map[string]interface{}, error) { + var stats map[string]interface{} + _, data, err := s.resource.GetJSON(fmt.Sprintf("_node/%s/_stats/%s", node, entry), nil, url.Values{}) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &stats) + if err != nil { + return nil, err + } + return stats, nil +} + +// Len returns the number of dbs in CouchDB server instance. +func (s *Server) Len() (int, error) { + dbs, err := s.DBs() + if err != nil { + return -1, err + } + return len(dbs), nil +} + +// Create returns a database instance with the given name, returns true if created, +// if database already existed, returns false, *Database will be nil if failed. +func (s *Server) Create(name string) (*Database, error) { + _, _, err := s.resource.PutJSON(name, nil, nil, nil) + + // ErrPreconditionFailed means database with the given name already existed + if err != nil && err != ErrPreconditionFailed { + return nil, err + } + + db, getErr := s.Get(name) + if getErr != nil { + return nil, getErr + } + return db, err +} + +// Delete deletes a database with the given name. Return false if failed. +func (s *Server) Delete(name string) error { + _, _, err := s.resource.DeleteJSON(name, nil, nil) + return err +} + +// Get gets a database instance with the given name. Return nil if failed. +func (s *Server) Get(name string) (*Database, error) { + res, err := s.resource.NewResourceWithURL(name) + if err != nil { + return nil, err + } + + db, err := NewDatabaseWithResource(res) + if err != nil { + return nil, err + } + + _, _, err = db.resource.Head("", nil, nil) + if err != nil { + return nil, err + } + return db, nil +} + +// Contains returns true if a db with given name exsited. +func (s *Server) Contains(name string) bool { + _, _, err := s.resource.Head(name, nil, nil) + return err == nil +} + +// Membership displays the nodes that are part of the cluster as clusterNodes. +// The field allNodes displays all nodes this node knows about, including the +// ones that are part of cluster. +func (s *Server) Membership() ([]string, []string, error) { + var jsonMap map[string]*json.RawMessage + + _, data, err := s.resource.GetJSON("_membership", nil, nil) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return nil, nil, err + } + + var allNodes []string + var clusterNodes []string + + err = json.Unmarshal(*jsonMap["all_nodes"], &allNodes) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(*jsonMap["cluster_nodes"], &clusterNodes) + if err != nil { + return nil, nil, err + } + + return allNodes, clusterNodes, nil +} + +// Replicate requests, configure or stop a replication operation. +func (s *Server) Replicate(source, target string, options map[string]interface{}) (map[string]interface{}, error) { + var result map[string]interface{} + + body := map[string]interface{}{ + "source": source, + "target": target, + } + + if options != nil { + for k, v := range options { + body[k] = v + } + } + + _, data, err := s.resource.PostJSON("_replicate", nil, body, nil) + if err != nil { + return nil, err + } + json.Unmarshal(data, &result) + + return result, nil +} + +// UUIDs requests one or more Universally Unique Identifiers from the CouchDB instance. +// The response is a JSON object providing a list of UUIDs. +// count - Number of UUIDs to return. Default is 1. +func (s *Server) UUIDs(count int) ([]string, error) { + if count <= 0 { + count = 1 + } + + values := url.Values{} + values.Set("count", strconv.Itoa(count)) + + _, data, err := s.resource.GetJSON("_uuids", nil, values) + if err != nil { + return nil, err + } + + var jsonMap map[string]*json.RawMessage + err = json.Unmarshal(data, &jsonMap) + if err != nil { + return nil, err + } + + var uuids []string + err = json.Unmarshal(*jsonMap["uuids"], &uuids) + if err != nil { + return nil, err + } + + return uuids, nil +} + +// newResource returns an url string representing a resource under server. +func (s *Server) newResource(resource string) string { + resourceURL, err := s.resource.base.Parse(resource) + if err != nil { + return "" + } + return resourceURL.String() +} + +// AddUser adds regular user in authentication database. +// Returns id and rev of the registered user. +func (s *Server) AddUser(name, password string, roles []string) (string, string, error) { + var id, rev string + db, err := s.Get("_users") + if err != nil { + return "", "", err + } + + if roles == nil { + roles = []string{} + } + + userDoc := map[string]interface{}{ + "_id": "org.couchdb.user:" + name, + "name": name, + "password": password, + "roles": roles, + "type": "user", + } + + id, rev, err = db.Save(userDoc, nil) + if err != nil { + return id, rev, err + } + return id, rev, nil +} + +// Login regular user in CouchDB, returns authentication token. +func (s *Server) Login(name, password string) (string, error) { + body := map[string]interface{}{ + "name": name, + "password": password, + } + header, _, err := s.resource.PostJSON("_session", nil, body, nil) + if err != nil { + return "", err + } + + tokenPart := strings.Split(header.Get("Set-Cookie"), ";")[0] + token := strings.Split(tokenPart, "=")[1] + return token, err +} + +// VerifyToken returns error if user's token is invalid. +func (s *Server) VerifyToken(token string) error { + header := http.Header{} + header.Set("Cookie", strings.Join([]string{"AuthSession", token}, "=")) + _, _, err := s.resource.GetJSON("_session", header, nil) + return err +} + +// Logout regular user in CouchDB +func (s *Server) Logout(token string) error { + header := http.Header{} + header.Set("Cookie", strings.Join([]string{"AuthSession", token}, "=")) + _, _, err := s.resource.DeleteJSON("_session", header, nil) + return err +} + +// RemoveUser removes regular user in authentication database. +func (s *Server) RemoveUser(name string) error { + db, err := s.Get("_users") + if err != nil { + return err + } + docID := "org.couchdb.user:" + name + return db.Delete(docID) +}