Skip to content

Instantly share code, notes, and snippets.

@vjt
Last active December 24, 2019 07:04
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vjt/6158675 to your computer and use it in GitHub Desktop.
Save vjt/6158675 to your computer and use it in GitHub Desktop.
Undeletes a document from a CouchDB database.
#!/usr/bin/env ruby
#
# Undelete a document from a CouchDB database.
#
# Recovers a previously deleted document by looking at the _changes
# feed, putting a new empty document preserving the revisions chain,
# retrieves the revs_info for the document, asks the user which one
# to recover, and puts back the old revision into place.
#
# For this to work, GETting the document must return error: "not_found",
# deleted: true.
#
# Requires Typhoeus and Oj.
#
# Latest version: https://gist.github.com/vjt/6158675
#
# h/t to http://stackoverflow.com/questions/10854883/retrieve-just-deleted-document
#
# - vjt Mon Aug 5 21:22:13 CEST 2013
#
# Made in Ventotene.
#
require 'typhoeus'
require 'oj'
def get(url, params = {})
res = Typhoeus.get(url, :params => params, :headers => {'Accept' => 'application/json'})
Oj.load(res.body)
end
def put(url, body, params = {})
res = Typhoeus.put(url, :body => Oj.dump(body), :params => params, :headers => {'Accept' => 'application/json'})
Oj.load(res.body)
end
def delete(url, params = {})
res = Typhoeus.delete(url, :params => params, :headers => {'Accept' => 'application/json'})
Oj.load(res.body)
end
class UndeleteError < StandardError; end
if $0 == __FILE__
database = ARGV[0]
docid = ARGV[1]
unless database && docid
raise ArgumentError, "Usage: #$0 <Database> <Document ID>"
end
docurl = [database, docid].join('/')
changes = [database, '_changes'].join('/')
puts "Processing document #{docurl}..."
# Verify the document is deleted
res = get(docurl)
unless res == {"error"=>"not_found", "reason"=>"deleted"}
raise UndeleteError, "The specified document is not recoverable. Sorry, you'll have to recover from backup."
end
# Now get the last revision from _changes
res = get(changes)['results'].select {|d| d['id'] == docid && d['deleted'] == true}.last
unless res
raise UndeleteError, "Could not find the last delete change."
end
lastrev = res['changes'].first['rev']
# Put a new empty_version of the document
res = put(docurl, {}, {:rev => lastrev})
unless res['ok'] == true
raise UndeleteError, "Sorry, an unexpected error has occurred: #{res.inspect}"
end
currrev = res['rev']
# Show previous revisions
res = get(docurl, :revs_info => true)
puts "The following revisions have been found:"
puts res['_revs_info'].select {|r| r['status'] == 'available'}.map(&:inspect).tap(&:shift).join("\n")
print "Type the revision number you want to recover: "
rev = $stdin.gets.chomp
puts "OK, fetching revision #{rev}..."
recovered = get(docurl, :rev => rev, :attachments => true)
recovered['_rev'] = currrev
puts "Putting back the document into couchdb..."
res = put docurl, recovered, :rev => currrev
if res['ok'] == true
puts "All done. Check that #{docurl} now contains what you do expect."
else
puts "An error occurred: #{res}"
end
end # if $0 == __FILE__
# EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment