Skip to content

Commit a510355

Browse files
committed
Fix local doc rev parsing
Previously, we applied the same "magic" hex decoding to revision ids for local docs as we did for regular docs. The "magic" is to detect the case of binaries with length 32 and assume they are hex-encoded, and then hex-decode them. When encoding them we did the opposite -- checked if they are 16 bytes long, and if, so we hex-encoded them. That's a nice optimization to half the storage needed for revs internally. However, the trick above doesn't really apply to local docs. For local docs revision ids have the format <<"0-$N">>. Where N is a decimal number. It starts at 1 with the first update, then gets bumped on every update. The `0` prefix though never changes, it's always `0`. Since regular (non-local) docs cannot have a revision depth of 0 (it's only the case for local docs) then can detect the case of encoding local doc revision and skip the magic hex encoding and decoding to remove the strange corner case.
1 parent 9ae4b2f commit a510355

3 files changed

Lines changed: 54 additions & 12 deletions

File tree

src/couch/src/couch_doc.erl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,20 @@ to_json_revisions(Options, Start, RevIds0) ->
6868
{<<"_revisions">>,
6969
{[
7070
{<<"start">>, Start},
71-
{<<"ids">>, [revid_to_str(R) || R <- RevIds]}
71+
{<<"ids">>, [revid_to_str(Start, R) || R <- RevIds]}
7272
]}}
7373
]
7474
end.
7575

76-
revid_to_str(RevId) when size(RevId) =:= 16 ->
76+
revid_to_str(Start, RevId) when Start =/= 0, byte_size(RevId) =:= 16 ->
7777
couch_util:to_hex_bin(RevId);
78-
revid_to_str(RevId) when is_binary(RevId) ->
78+
revid_to_str(Start, RevId) when is_integer(Start), Start >= 0, is_binary(RevId) ->
7979
RevId;
80-
revid_to_str(RevId) when is_list(RevId) ->
80+
revid_to_str(Start, RevId) when is_integer(Start), Start >= 0, is_list(RevId) ->
8181
list_to_binary(RevId).
8282

8383
rev_to_str({Pos, RevId}) ->
84-
<<(integer_to_binary(Pos))/binary, $-, (revid_to_str(RevId))/binary>>.
84+
<<(integer_to_binary(Pos))/binary, $-, (revid_to_str(Pos, RevId))/binary>>.
8585

8686
revs_to_strs([]) ->
8787
[];
@@ -188,13 +188,13 @@ from_json_obj({Props}, DbName) ->
188188
from_json_obj(_Other, _) ->
189189
throw({bad_request, "Document must be a JSON object"}).
190190

191-
parse_revid(RevId) when is_binary(RevId), size(RevId) =:= 32 ->
191+
parse_revid(Start, RevId) when Start =/= 0, is_binary(RevId), byte_size(RevId) =:= 32 ->
192192
binary:decode_hex(RevId);
193-
parse_revid(RevId) when is_list(RevId), length(RevId) =:= 32 ->
193+
parse_revid(Start, RevId) when Start =/= 0, is_list(RevId), length(RevId) =:= 32 ->
194194
binary:decode_hex(list_to_binary(RevId));
195-
parse_revid(RevId) when is_binary(RevId) ->
195+
parse_revid(Start, RevId) when is_integer(Start), Start >= 0, is_binary(RevId) ->
196196
RevId;
197-
parse_revid(RevId) when is_list(RevId) ->
197+
parse_revid(Start, RevId) when is_integer(Start), Start >= 0, is_list(RevId) ->
198198
?l2b(RevId).
199199

200200
parse_rev(Rev) when is_binary(Rev) ->
@@ -211,7 +211,7 @@ parse_rev(Rev) when is_list(Rev) ->
211211
{Pos, [$- | RevId]} ->
212212
try
213213
IntPos = list_to_integer(Pos),
214-
{IntPos, parse_revid(RevId)}
214+
{IntPos, parse_revid(IntPos, RevId)}
215215
catch
216216
error:badarg -> throw({bad_request, <<"Invalid rev format">>})
217217
end;
@@ -313,7 +313,7 @@ transfer_fields([{<<"_revisions">>, {Props}} | Rest], Doc, DbName) ->
313313
RevIds2 = lists:map(
314314
fun(RevId) ->
315315
try
316-
parse_revid(RevId)
316+
parse_revid(Start, RevId)
317317
catch
318318
error:function_clause ->
319319
throw({doc_validation, "RevId isn't a string"});

src/couch/test/eunit/couch_db_doc_tests.erl

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,35 @@ add_revisions(Db, DocId, Rev, N) ->
115115
Rev,
116116
lists:seq(1, N)
117117
).
118+
119+
rev_to_str_test() ->
120+
?assertEqual(<<"0-0">>, couch_doc:rev_to_str({0, "0"})),
121+
?assertEqual(<<"0-0">>, couch_doc:rev_to_str({0, <<"0">>})),
122+
?assertEqual(<<"1-a">>, couch_doc:rev_to_str({1, <<"a">>})),
123+
Bytes = <<<<48>> || _ <- lists:seq(1, 16)>>,
124+
?assertEqual(<<"0-0000000000000000">>, couch_doc:rev_to_str({0, Bytes})),
125+
?assertEqual(<<"1-30303030303030303030303030303030">>, couch_doc:rev_to_str({1, Bytes})),
126+
?assertError(badarg, couch_doc:rev_to_str({a, b})),
127+
?assertError(function_clause, couch_doc:rev_to_str({-1, <<"x">>})),
128+
?assertError(function_clause, couch_doc:rev_to_str(foo)).
129+
130+
parse_rev_test() ->
131+
?assertEqual({1, <<"x">>}, couch_doc:parse_rev("1-x")),
132+
?assertEqual({1, <<"x">>}, couch_doc:parse_rev(<<"1-x">>)),
133+
?assertEqual({0, <<"y">>}, couch_doc:parse_rev("0-y")),
134+
?assertEqual({1234567890, <<"abc">>}, couch_doc:parse_rev("1234567890-abc")),
135+
?assertEqual(
136+
{0, <<"00000000000000000000000000000000">>},
137+
couch_doc:parse_rev("0-00000000000000000000000000000000")
138+
),
139+
?assertEqual(
140+
{1, <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>},
141+
couch_doc:parse_rev("1-00000000000000000000000000000000")
142+
),
143+
?assertThrow({bad_request, _}, couch_doc:parse_rev(foo)),
144+
?assertThrow({bad_request, _}, couch_doc:parse_rev("x-y")),
145+
?assertThrow({bad_request, _}, couch_doc:parse_rev("x")),
146+
?assertThrow({bad_request, _}, couch_doc:parse_rev("0")),
147+
?assertThrow({bad_request, _}, couch_doc:parse_rev("1")),
148+
?assertThrow({bad_request, _}, couch_doc:parse_rev("-")),
149+
?assertThrow({bad_request, _}, couch_doc:parse_rev("-0-1")).

src/couch/test/eunit/couch_doc_json_tests.erl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ from_json_success_cases() ->
9393
#doc{revs = {4, [<<"230234">>]}},
9494
"_rev stored in revs."
9595
},
96+
{
97+
{[{<<"_rev">>, <<"0-11111111111111111111111111111111">>}]},
98+
#doc{revs = {0, [<<"11111111111111111111111111111111">>]}},
99+
"_rev with start 0 stored in revs (local docs case)."
100+
},
101+
{
102+
{[{<<"_rev">>, <<"2-11111111111111111111111111111111">>}]},
103+
#doc{revs = {2, [<<17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17>>]}},
104+
"_rev with start 1 and size 32 stored in revs."
105+
},
96106
{
97107
{[{<<"soap">>, 35}]},
98108
#doc{body = {[{<<"soap">>, 35}]}},
@@ -287,7 +297,7 @@ from_json_error_cases() ->
287297
{[
288298
{<<"_revisions">>,
289299
{[
290-
{<<"start">>, 0},
300+
{<<"start">>, 1},
291301
{<<"ids">>, [<<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">>]}
292302
]}}
293303
]},

0 commit comments

Comments
 (0)