diff options
-rw-r--r-- | share/www/script/test/attachments_multipart.js | 109 | ||||
-rw-r--r-- | src/couchdb/couch_db.erl | 7 | ||||
-rw-r--r-- | src/couchdb/couch_doc.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 19 |
5 files changed, 109 insertions, 30 deletions
diff --git a/share/www/script/test/attachments_multipart.js b/share/www/script/test/attachments_multipart.js index dbe2cff6..2b79e559 100644 --- a/share/www/script/test/attachments_multipart.js +++ b/share/www/script/test/attachments_multipart.js @@ -79,6 +79,7 @@ couchTests.attachments_multipart= function(debug) { // now edit an attachment var doc = db.open("multipart"); + var firstrev = doc._rev; T(doc._attachments["foo.txt"].stub == true); T(doc._attachments["bar.txt"].stub == true); @@ -111,29 +112,29 @@ couchTests.attachments_multipart= function(debug) { xhr = CouchDB.request("GET", "/test_suite_db/multipart/baz.txt"); T(xhr.status == 404); - xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true", - {headers:{"accept": "multipart/related,*/*;"}}); - - var headers = xhr.getAllResponseHeaders(); - - var ctype = xhr.getResponseHeader("Content-Type"); - - var ctypeArgs = ctype.split("; ").slice(1); - var boundary = null; - for(var i=0; i<ctypeArgs.length; i++) { - if (ctypeArgs[i].indexOf("boundary=") == 0) { - boundary = ctypeArgs[i].split("=")[1]; - if (boundary.charAt(0) == '"') { - // stringified boundary, parse as json - // (will maybe not if there are escape quotes) - boundary = JSON.parse(boundary); + // now test receiving multipart docs + + function getBoundary(xhr) { + var ctype = xhr.getResponseHeader("Content-Type"); + + var ctypeArgs = ctype.split("; ").slice(1); + var boundary = null; + for(var i=0; i<ctypeArgs.length; i++) { + if (ctypeArgs[i].indexOf("boundary=") == 0) { + boundary = ctypeArgs[i].split("=")[1]; + if (boundary.charAt(0) == '"') { + // stringified boundary, parse as json + // (will maybe not if there are escape quotes) + boundary = JSON.parse(boundary); + } } } + return boundary; } - T(boundary != null); - - function parseMime(boundary, mimetext) { + function parseMultipart(xhr) { + var boundary = getBoundary(xhr); + var mimetext = xhr.responseText; // strip off leading boundary var leading = "--" + boundary + "\r\n"; var last = "\r\n--" + boundary + "--"; @@ -163,11 +164,17 @@ couchTests.attachments_multipart= function(debug) { return sections; } - // parse out the multipart + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?attachments=true", + {headers:{"accept": "multipart/related,*/*;"}}); T(xhr.status == 200); - var sections = parseMime(boundary, xhr.responseText); + // parse out the multipart + + var sections = parseMultipart(xhr); + + T(sections.length == 3); // The first section is the json doc. Check it's content-type. It contains // the metadata for all the following attachments @@ -176,10 +183,66 @@ couchTests.attachments_multipart= function(debug) { var doc = JSON.parse(sections[0].body); - T(doc._attachments['foo.txt'].follows = true); - T(doc._attachments['bar.txt'].follows = true); + T(doc._attachments['foo.txt'].follows == true); + T(doc._attachments['bar.txt'].follows == true); + + T(sections[1].body == "this is 21 chars long"); + T(sections[2].body == "this is 18 chars l"); + + // now get attachments incrementally (only the attachments changes since + // a certain rev). + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"" + firstrev + "\"]", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 2); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].stub == true); + T(doc._attachments['bar.txt'].follows == true); + + T(sections[1].body == "this is 18 chars l"); + + // try it with a rev that doesn't exist (should get all attachments) + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\"]", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 3); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].follows == true); + T(doc._attachments['bar.txt'].follows == true); T(sections[1].body == "this is 21 chars long"); T(sections[2].body == "this is 18 chars l"); + // try it with a rev that doesn't exist, and one that does + + xhr = CouchDB.request("GET", "/test_suite_db/multipart?atts_since=[\"1-2897589\",\"" + firstrev + "\"]", + {headers:{"accept": "multipart/related,*/*;"}}); + + T(xhr.status == 200); + + var sections = parseMultipart(xhr); + + T(sections.length == 2); + + var doc = JSON.parse(sections[0].body); + + T(doc._attachments['foo.txt'].stub == true); + T(doc._attachments['bar.txt'].follows == true); + + T(sections[1].body == "this is 18 chars l"); + }; diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index aa46a347..7630989a 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -15,7 +15,7 @@ -export([open/2,close/1,create/2,start_compact/1,get_db_info/1,get_design_docs/1]). -export([open_ref_counted/2,is_idle/1,monitor/1,count_changes_since/2]). --export([update_doc/3,update_docs/4,update_docs/2,update_docs/3,delete_doc/3]). +-export([update_doc/3,update_doc/4,update_docs/4,update_docs/2,update_docs/3,delete_doc/3]). -export([get_doc_info/2,open_doc/2,open_doc/3,open_doc_revs/4]). -export([set_revs_limit/2,get_revs_limit/1,register_update_notifier/3]). -export([get_missing_revs/2,name/1,doc_to_tree/1,get_update_seq/1,get_committed_update_seq/1]). @@ -251,7 +251,10 @@ name(#db{name=Name}) -> Name. update_doc(Db, Doc, Options) -> - case update_docs(Db, [Doc], Options) of + update_doc(Db, Doc, Options, interactive_edit). + +update_doc(Db, Doc, Options, UpdateType) -> + case update_docs(Db, [Doc], Options, UpdateType) of {ok, [{ok, NewRev}]} -> {ok, NewRev}; {ok, [Error]} -> diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index 079d9dfa..eb07a9e6 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -322,7 +322,7 @@ merge_stubs(#doc{id=Id,atts=MemBins}=StubsDoc, #doc{atts=DiskBins}) -> {ok, #att{revpos=RevPos}=DiskAtt} -> DiskAtt; _ -> - throw({missing_stub_on_target, + throw({missing_stub, <<"id:", Id/binary, ", name:", Name/binary>>}) end; (Att) -> diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index a0955daa..9c0a3668 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -559,6 +559,8 @@ error_info({error, illegal_database_name}) -> {400, <<"illegal_database_name">>, <<"Only lowercase characters (a-z), " "digits (0-9), and any of the characters _, $, (, ), +, -, and / " "are allowed">>}; +error_info({missing_stub, Reason}) -> + {412, <<"missing_stub">>, Reason}; error_info({Error, Reason}) -> {500, couch_util:to_binary(Error), couch_util:to_binary(Reason)}; error_info(Error) -> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 4138db7d..71fc3f48 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -30,6 +30,7 @@ rev = nil, open_revs = [], show = nil, + update_type = interactive_edit, atts_since = nil }). @@ -780,6 +781,9 @@ db_doc_req(#httpd{method='POST'}=Req, Db, DocId) -> ]}); db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> + #doc_query_args{ + update_type = UpdateType + } = parse_doc_query(Req), couch_doc:validate_docid(DocId), Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)), @@ -789,7 +793,7 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> Doc0 = couch_doc:doc_from_multi_part_stream(ContentType, fun() -> receive_request_data(Req) end), Doc = couch_doc_from_req(Req, DocId, Doc0), - update_doc(Req, Db, DocId, Doc, RespHeaders); + update_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType); _ -> case couch_httpd:qs_value(Req, "batch") of "ok" -> @@ -810,7 +814,7 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> _Normal -> % normal Doc = couch_doc_from_req(Req, DocId, couch_httpd:json_body(Req)), - update_doc(Req, Db, DocId, Doc, RespHeaders) + update_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType) end end; @@ -911,7 +915,10 @@ update_doc_result_to_json(DocId, Error) -> update_doc(Req, Db, DocId, Doc) -> update_doc(Req, Db, DocId, Doc, []). -update_doc(Req, Db, DocId, #doc{deleted=Deleted}=Doc, Headers) -> +update_doc(Req, Db, DocId, Doc, Headers) -> + update_doc(Req, Db, DocId, Doc, Headers, interactive_edit). + +update_doc(Req, Db, DocId, #doc{deleted=Deleted}=Doc, Headers, UpdateType) -> case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of "true" -> Options = [full_commit]; @@ -920,7 +927,7 @@ update_doc(Req, Db, DocId, #doc{deleted=Deleted}=Doc, Headers) -> _ -> Options = [] end, - {ok, NewRev} = couch_db:update_doc(Db, Doc, Options), + {ok, NewRev} = couch_db:update_doc(Db, Doc, Options, UpdateType), NewRevStr = couch_doc:rev_to_str(NewRev), ResponseHeaders = [{"Etag", <<"\"", NewRevStr/binary, "\"">>}] ++ Headers, send_json(Req, if Deleted -> 200; true -> 201 end, @@ -1133,6 +1140,10 @@ parse_doc_query(Req) -> Args#doc_query_args{atts_since = couch_doc:parse_revs(JsonArray)}; {"show", FormatStr} -> Args#doc_query_args{show=parse_doc_format(FormatStr)}; + {"new_edits", "false"} -> + Args#doc_query_args{update_type=replicated_changes}; + {"new_edits", "true"} -> + Args#doc_query_args{update_type=interactive_edit}; _Else -> % unknown key value pair, ignore. Args end |