You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2008/08/12 16:48:07 UTC
svn commit: r685171 - in /incubator/couchdb/trunk:
share/www/script/couch_tests.js src/couchdb/couch_httpd.erl
Author: jan
Date: Tue Aug 12 07:48:06 2008
New Revision: 685171
URL: http://svn.apache.org/viewvc?rev=685171&view=rev
Log:
HTTP COPY & MOVE for documents with tests
Modified:
incubator/couchdb/trunk/share/www/script/couch_tests.js
incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
Modified: incubator/couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch_tests.js?rev=685171&r1=685170&r2=685171&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] Tue Aug 12 07:48:06 2008
@@ -115,6 +115,53 @@
// 1 less document should now be in the results.
T(results.total_rows == 2);
T(db.info().doc_count == 5);
+
+ // copy a doc
+ T(db.save({_id:"doc_to_be_copied",v:1}).ok);
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_that_was_copied"}
+ });
+
+ T(xhr.status == 201);
+ T(db.open("doc_that_was_copied").v == 1);
+
+ // move a doc
+
+ // test error condition
+ var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_that_was_moved"}
+ });
+ T(xhr.status == 400); // bad request, MOVE requires source rev.
+
+ var rev = db.open("doc_to_be_copied")._rev;
+ var xhr = CouchDB.request("MOVE", "/test_suite_db/doc_to_be_copied?rev=" + rev, {
+ headers: {"Destination":"doc_that_was_moved"}
+ });
+
+ T(xhr.status == 201);
+ T(db.open("doc_that_was_moved").v == 1);
+ T(db.open("doc_to_be_copied") == null);
+
+ // COPY with existing target
+ T(db.save({_id:"doc_to_be_copied",v:1}).ok);
+ var doc = db.save({_id:"doc_to_be_overwritten",v:1});
+ T(doc.ok);
+
+ // error condition
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_to_be_overwritten"}
+ });
+ T(xhr.status == 412); // conflict
+
+ var rev = db.open("doc_to_be_overwritten")._rev;
+ var xhr = CouchDB.request("COPY", "/test_suite_db/doc_to_be_copied", {
+ headers: {"Destination":"doc_to_be_overwritten?rev=" + rev}
+ });
+ T(xhr.status == 201);
+
+ var newRev = db.open("doc_to_be_overwritten")._rev;
+ T(rev != newRev);
+
},
// Do some edit conflict detection tests
@@ -414,8 +461,8 @@
// This is the reduce phase, we are reducing over emitted values from
// the map functions.
for(var i in values) {
- total = total + values[i]
- sqrTotal = sqrTotal + (values[i] * values[i])
+ total = total + values[i];
+ sqrTotal = sqrTotal + (values[i] * values[i]);
}
count = values.length;
}
Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=685171&r1=685170&r2=685171&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd.erl Tue Aug 12 07:48:06 2008
@@ -54,15 +54,21 @@
% alias HEAD to GET as mochiweb takes care of stripping the body
Method = case Req:get(method) of
'HEAD' -> 'GET';
- Other -> Other
+ Other ->
+ % handling of non standard HTTP verbs. Should be fixe din gen_tcp:recv()
+ case Other of
+ "COPY" -> 'COPY';
+ "MOVE" -> 'MOVE';
+ StandardMethod -> StandardMethod
+ end
end,
% for the path, use the raw path with the query string and fragment
% removed, but URL quoting left intact
{Path, _, _} = mochiweb_util:urlsplit_path(Req:get(raw_path)),
- ?LOG_DEBUG("~s ~s ~p~nHeaders: ~p", [
- atom_to_list(Req:get(method)),
+ ?LOG_DEBUG("~p ~s ~p~nHeaders: ~p", [
+ Method,
Path,
Req:get(version),
mochiweb_headers:to_list(Req:get(headers))
@@ -75,9 +81,10 @@
send_error(Req, Error)
end,
- ?LOG_INFO("~s - - ~p ~B", [
+ ?LOG_INFO("~s - - ~p ~s ~B", [
Req:get(peer),
- atom_to_list(Req:get(method)) ++ " " ++ Path,
+ Method,
+ Path,
Resp:get(code)
]).
@@ -545,24 +552,7 @@
} = parse_doc_query(Req),
case Revs of
[] ->
- case Rev of
- "" -> % open most recent rev
- case couch_db:open_doc(Db, DocId, Options) of
- {ok, #doc{revs=[DocRev|_]}=Doc} ->
- true;
- Error ->
- Doc = DocRev = undefined,
- throw(Error)
- end;
- _ -> % open a specific rev (deletions come back as stubs)
- case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of
- {ok, [{ok, Doc}]} ->
- DocRev = Rev;
- {ok, [Else]} ->
- Doc = DocRev = undefined,
- throw(Else)
- end
- end,
+ {Doc, DocRev} = couch_doc_open(Db, DocId, Rev, Options),
Etag = none_match(Req, DocRev),
AdditionalHeaders = case Doc#doc.meta of
[] -> [{"Etag", Etag}]; % output etag when we have no meta
@@ -625,8 +615,94 @@
{rev, NewRev}
]});
+handle_doc_request(Req, 'COPY', _DbName, Db, SourceDocId) ->
+ SourceRev = case extract_header_rev(Req) of
+ missing_rev -> [];
+ Rev -> Rev
+ end,
+
+ {TargetDocId, TargetRev} = parse_copy_destination_header(Req),
+
+ % open revision Rev or Current
+ {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []),
+
+ % save new doc
+ {ok, NewTargetRev} = couch_db:update_doc(Db, Doc#doc{id=TargetDocId, revs=TargetRev}, []),
+
+ send_json(Req, 201, [{"Etag", "\"" ++ NewTargetRev ++ "\""}], {obj, [
+ {ok, true},
+ {id, TargetDocId},
+ {rev, NewTargetRev}
+ ]});
+
+handle_doc_request(Req, 'MOVE', _DbName, Db, SourceDocId) ->
+ SourceRev = case extract_header_rev(Req) of
+ missing_rev ->
+ throw({
+ bad_request,
+ "MOVE requires a specified rev parameter for the origin resource."}
+ );
+ Rev -> Rev
+ end,
+
+ {TargetDocId, TargetRev} = parse_copy_destination_header(Req),
+
+ % open revision Rev or Current
+ {Doc, _DocRev} = couch_doc_open(Db, SourceDocId, SourceRev, []),
+
+ % save new doc & delete old doc in one operation
+ Docs = [
+ Doc#doc{id=TargetDocId, revs=TargetRev},
+ #doc{id=SourceDocId, revs=[SourceRev], deleted=true}
+ ],
+
+ {ok, ResultRevs} = couch_db:update_docs(Db, Docs, []),
+
+ DocResults = lists:zipwith(
+ fun(FDoc, NewRev) ->
+ {obj, [{"id", FDoc#doc.id}, {"rev", NewRev}]}
+ end,
+ Docs, ResultRevs),
+ send_json(Req, 201, {obj, [
+ {ok, true},
+ {new_revs, list_to_tuple(DocResults)}
+ ]});
+
handle_doc_request(_Req, _Method, _DbName, _Db, _DocId) ->
- throw({method_not_allowed, "DELETE,GET,HEAD,PUT"}).
+ throw({method_not_allowed, "DELETE,GET,HEAD,PUT,COPY,MOVE"}).
+
+% Useful for debugging
+% couch_doc_open(Db, DocId) ->
+% couch_doc_open(Db, DocId, [], []).
+
+couch_doc_open(Db, DocId, Rev, Options) ->
+ case Rev of
+ "" -> % open most recent rev
+ case couch_db:open_doc(Db, DocId, Options) of
+ {ok, #doc{revs=[DocRev|_]}=Doc} ->
+ {Doc, DocRev};
+ Error ->
+ throw(Error)
+ end;
+ _ -> % open a specific rev (deletions come back as stubs)
+ case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of
+ {ok, [{ok, Doc}]} ->
+ {Doc, Rev};
+ {ok, [Else]} ->
+ throw(Else)
+ end
+ end.
+
+parse_copy_destination_header(Req) ->
+ Destination = Req:get_header_value("Destination"),
+ case regexp:match(Destination, "\\?") of
+ nomatch ->
+ {Destination, []};
+ {match, _, _} ->
+ {ok, [DocId, RevQueryOptions]} = regexp:split(Destination, "\\?"),
+ {ok, [_RevQueryKey, Rev]} = regexp:split(RevQueryOptions, "="),
+ {DocId, [Rev]}
+ end.
% Attachment request handlers