class CouchRest::Database
Attributes
How many documents should be cached before peforming the bulk save operation
Name of the database of we're using.
Name of the database we can use in requests.
Server
object we'll use to communicate with.
Public Class Methods
Create a CouchRest::Database
adapter for the supplied CouchRest::Server
and database name.
Parameters¶ ↑
- server<CouchRest::Server>
-
database host
- name<String>
-
database name
# File lib/couchrest/database.rb, line 26 def initialize(server, name) @name = name @server = server @path = "/#{CGI.escape(name)}" @bulk_save_cache = [] @bulk_save_cache_limit = 500 # must be smaller than the uuid count end
Public Instance Methods
Query the _all_docs
view. Accepts all the same arguments as view.
# File lib/couchrest/database.rb, line 303 def all_docs(params = {}, payload = {}, &block) view("_all_docs", params, payload, &block) end
Save a document to CouchDB in batch mode. See save_doc
's batch
argument.
# File lib/couchrest/database.rb, line 179 def batch_save_doc(doc) save_doc(doc, false, true) end
POST an array of documents to CouchDB. If any of the documents are missing ids, supply one from the uuid cache.
If called with no arguments, bulk saves the cache of documents to be bulk saved.
# File lib/couchrest/database.rb, line 187 def bulk_save(docs = nil, opts = {}) opts = { :use_uuids => true, :all_or_nothing => false }.update(opts) if docs.nil? docs = @bulk_save_cache @bulk_save_cache = [] end if opts[:use_uuids] ids, noids = docs.partition{|d|d['_id']} uuid_count = [noids.length, @server.uuid_batch_count].max noids.each do |doc| nextid = server.next_uuid(uuid_count) rescue nil doc['_id'] = nextid if nextid end end request_body = {:docs => docs} if opts[:all_or_nothing] request_body[:all_or_nothing] = true end results = connection.post "#{path}/_bulk_docs", request_body docs_by_id = Hash[docs.map { |doc| [doc['_id'], doc] }] unless docs.nil? results.each { |r| docs_by_id[r['id']]['_rev'] = r['rev'] if r['ok'] } unless results.nil? results end
Save a document to CouchDB in bulk mode. See save_doc
's bulk
argument.
# File lib/couchrest/database.rb, line 174 def bulk_save_doc(doc) save_doc(doc, true) end
Query CouchDB's special _changes
feed for the latest. All standard CouchDB options can be provided.
Warning: sending :feed => 'continuous' will cause your code to block indefinetly while waiting for changes. You might want to look-up an alternative to this.
# File lib/couchrest/database.rb, line 314 def changes(params = {}, payload = {}, &block) view("_changes", params, payload, &block) end
Compact the database, removing old document revisions and optimizing space use.
# File lib/couchrest/database.rb, line 55 def compact! connection.post "#{path}/_compact" end
# File lib/couchrest/database.rb, line 34 def connection server.connection end
COPY an existing document to a new id. If the destination id currently exists, a rev must be provided. dest
can take one of two forms if overwriting: “id_to_overwrite?rev=revision” or the actual doc hash with a '_rev' key
# File lib/couchrest/database.rb, line 231 def copy_doc(doc, dest) raise ArgumentError, "_id is required for copying" unless doc['_id'] slug = escape_docid(doc['_id']) destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev'] "#{dest['_id']}?rev=#{dest['_rev']}" else dest end connection.copy "#{path}/#{slug}", destination end
Create the database
# File lib/couchrest/database.rb, line 60 def create! bool = server.create_db(path) rescue false bool && true end
DELETE the database itself. This is not undoable and could be rather catastrophic. Use with care!
# File lib/couchrest/database.rb, line 86 def delete! connection.delete path end
DELETE an attachment directly from CouchDB
# File lib/couchrest/database.rb, line 347 def delete_attachment(doc, name, force=false) attach_path = path_for_attachment(doc, name) begin connection.delete(attach_path) rescue Exception => error if force # get over a 409 doc = get(doc['_id']) attach_path = path_for_attachment(doc, name) connection.delete(attach_path) else error end end end
DELETE the document from CouchDB that has the given _id
and _rev
.
If bulk
is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later. Bulk saving happens automatically when bulk_save_cache limit is exceded, or on the next non bulk save.
# File lib/couchrest/database.rb, line 217 def delete_doc(doc, bulk = false) raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev'] if bulk @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], :_deleted => true } return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit return {'ok' => true} # Mimic the non-deferred version end slug = escape_docid(doc['_id']) connection.delete "#{path}/#{slug}?rev=#{doc['_rev']}" end
GET an attachment directly from CouchDB
# File lib/couchrest/database.rb, line 335 def fetch_attachment(doc, name) connection.get path_for_attachment(doc, name), :raw => true end
Query a CouchDB-Lucene search view
# File lib/couchrest/database.rb, line 319 def fti(name, params={}) # -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*' view("_fti/#{name}", params) end
GET the requested document by ID like `get!`, but returns nil if the document does not exist.
# File lib/couchrest/database.rb, line 111 def get(*args) get!(*args) rescue CouchRest::NotFound nil end
GET a document from CouchDB, by id. Returns a Document
, Design
, or raises an exception if the document does not exist.
# File lib/couchrest/database.rb, line 95 def get!(id, params = {}) slug = escape_docid(id) url = CouchRest.paramify_url("#{path}/#{slug}", params) result = connection.get(url) return result unless result.is_a?(Hash) doc = if /^_design/ =~ result["_id"] Design.new(result) else Document.new(result) end doc.database = self doc end
load a set of documents by passing an array of ids
# File lib/couchrest/database.rb, line 326 def get_bulk(ids) all_docs(:keys => ids, :include_docs => true) end
GET the database info from CouchDB
# File lib/couchrest/database.rb, line 50 def info connection.get path end
PUT an attachment directly to CouchDB, expects an IO object, or a string that will be converted to a StringIO in the 'file' parameter.
# File lib/couchrest/database.rb, line 341 def put_attachment(doc, name, file, options = {}) file = StringIO.new(file) if file.is_a?(String) connection.put path_for_attachment(doc, name), file, options end
Delete and re create the database
# File lib/couchrest/database.rb, line 66 def recreate! delete! create! rescue CouchRest::NotFound ensure create! end
Replicates via “pulling” from another database to this database. Makes no attempt to deal with conflicts.
# File lib/couchrest/database.rb, line 75 def replicate_from(other_db, continuous = false, create_target = false, doc_ids = nil) replicate(other_db, continuous, :target => name, :create_target => create_target, :doc_ids => doc_ids) end
Replicates via “pushing” to another database. Makes no attempt to deal with conflicts.
# File lib/couchrest/database.rb, line 80 def replicate_to(other_db, continuous = false, create_target = false, doc_ids = nil) replicate(other_db, continuous, :source => name, :create_target => create_target, :doc_ids => doc_ids) end
Save a document to CouchDB. This will use the _id
field from the document as the id for PUT, or request a new UUID from CouchDB, if no _id
is present on the document. IDs are attached to documents on the client side because POST has the curious property of being automatically retried by proxies in the event of network segmentation and lost responses.
If bulk
is true (false by default) the document is cached for bulk-saving later. Bulk saving happens automatically when bulk_save_cache limit is exceded, or on the next non bulk save.
If batch
is true (false by default) the document is saved in batch mode, “used to achieve higher throughput at the cost of lower guarantees. When […] sent using this option, it is not immediately written to disk. Instead it is stored in memory on a per-user basis for a second or so (or the number of docs in memory reaches a certain point). After the threshold has passed, the docs are committed to disk. Instead of waiting for the doc to be written to disk before responding, CouchDB sends an HTTP 202 Accepted response immediately. batch=ok is not suitable for crucial data, but it ideal for applications like logging which can accept the risk that a small proportion of updates could be lost due to a crash.”
# File lib/couchrest/database.rb, line 138 def save_doc(doc, bulk = false, batch = false) if doc['_attachments'] doc['_attachments'] = encode_attachments(doc['_attachments']) end if bulk @bulk_save_cache << doc bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit return {'ok' => true} # Compatibility with Document#save elsif !bulk && @bulk_save_cache.length > 0 bulk_save end result = if doc['_id'] slug = escape_docid(doc['_id']) begin doc_path = "#{path}/#{slug}" doc_path << "?batch=ok" if batch connection.put doc_path, doc rescue CouchRest::NotFound puts "resource not found when saving even though an id was passed" slug = doc['_id'] = server.next_uuid connection.put "#{path}/#{slug}", doc end else slug = doc['_id'] = @server.next_uuid connection.put "#{path}/#{slug}", doc end if result['ok'] doc['_id'] = result['id'] doc['_rev'] = result['rev'] doc.database = self if doc.respond_to?(:database=) end result end
POST a temporary view function to CouchDB for querying. This is not recommended, as you don't get any performance benefit from CouchDB's materialized views. Can be quite slow on large databases.
# File lib/couchrest/database.rb, line 296 def temp_view(payload, params = {}, &block) view('_temp_view', params, payload, &block) end
String of root
# File lib/couchrest/database.rb, line 45 def to_s uri.to_s end
Updates the given doc by yielding the current state of the doc and trying to update update_limit times. Returns the doc if successfully updated without hitting the limit. If the limit is reached, the last execption will be raised.
# File lib/couchrest/database.rb, line 246 def update_doc(doc_id, params = {}, update_limit = 10) resp = {'ok' => false} last_fail = nil until resp['ok'] or update_limit <= 0 doc = self.get(doc_id, params) yield doc begin resp = self.save_doc doc rescue CouchRest::RequestFailed => e if e.http_code == 409 # Update collision update_limit -= 1 last_fail = e else raise e end end end raise last_fail unless resp['ok'] doc end
A URI object for the exact location of this database
# File lib/couchrest/database.rb, line 39 def uri server.uri + path end
Query a CouchDB view as defined by a _design
document. Accepts paramaters as described in wiki.apache.org/couchdb/HttpViewApi
# File lib/couchrest/database.rb, line 274 def view(name, params = {}, payload = {}, &block) opts = {} params = params.dup payload['keys'] = params.delete(:keys) if params[:keys] # Continuous feeds need to be parsed differently opts[:continuous] = true if params['feed'] == 'continuous' # Try recognising the name, otherwise assume already prepared view_path = name_to_view_path(name) req_path = CouchRest.paramify_url("#{path}/#{view_path}", params) if payload.empty? connection.get req_path, opts, &block else connection.post req_path, payload, opts, &block end end
Private Instance Methods
# File lib/couchrest/database.rb, line 401 def base64(data) Base64.encode64(data).gsub(/\s/,'') end
# File lib/couchrest/database.rb, line 393 def encode_attachments(attachments) attachments.each do |k,v| next if v['stub'] || v['data'].frozen? v['data'] = base64(v['data']).freeze end attachments end
# File lib/couchrest/database.rb, line 389 def escape_docid id /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id) end
Convert a simplified view name into a complete view path. If the name already starts with a “_” no alterations will be made.
# File lib/couchrest/database.rb, line 407 def name_to_view_path(name) name =~ /^([^_].*?)\/(.*)$/ ? "_design/#{$1}/_view/#{$2}" : name end
# File lib/couchrest/database.rb, line 382 def path_for_attachment(doc, name) docid = escape_docid(doc['_id']) name = CGI.escape(name) rev = doc['_rev'] ? "?rev=#{doc['_rev']}" : '' "#{path}/#{docid}/#{name}#{rev}" end
# File lib/couchrest/database.rb, line 365 def replicate(other_db, continuous, options) raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database) raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source)) doc_ids = options.delete(:doc_ids) payload = options if options.has_key?(:target) payload[:source] = other_db.root.to_s else payload[:target] = other_db.root.to_s end payload[:continuous] = continuous payload[:doc_ids] = doc_ids if doc_ids # Use a short lived request here connection.post "_replicate", payload end