summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/script/test/attachments_multipart.js109
-rw-r--r--src/couchdb/couch_db.erl7
-rw-r--r--src/couchdb/couch_doc.erl2
-rw-r--r--src/couchdb/couch_httpd.erl2
-rw-r--r--src/couchdb/couch_httpd_db.erl19
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