Merge branch 'COUCHDB-3298-improve-couch-btree-chunkify'
diff --git a/src/couch_doc.erl b/src/couch_doc.erl
index af14038..a913eee 100644
--- a/src/couch_doc.erl
+++ b/src/couch_doc.erl
@@ -174,6 +174,14 @@
 validate_docid(<<"_local/">>) ->
     throw({illegal_docid, <<"Illegal document id `_local/`">>});
 validate_docid(Id) when is_binary(Id) ->
+    MaxLen = case config:get("couchdb", "max_document_id_length", "infinity") of
+        "infinity" -> infinity;
+        IntegerVal -> list_to_integer(IntegerVal)
+    end,
+    case MaxLen > 0 andalso byte_size(Id) > MaxLen of
+        true -> throw({illegal_docid, <<"Document id is too long">>});
+        false -> ok
+    end,
     case couch_util:validate_utf8(Id) of
         false -> throw({illegal_docid, <<"Document id must be valid UTF-8">>});
         true -> ok
diff --git a/src/couch_server.erl b/src/couch_server.erl
index 59bffa5..893b957 100644
--- a/src/couch_server.erl
+++ b/src/couch_server.erl
@@ -71,18 +71,17 @@
 
 
 open(DbName, Options0) ->
-    Options = maybe_add_sys_db_callbacks(DbName, Options0),
-    Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
+    Ctx = couch_util:get_value(user_ctx, Options0, #user_ctx{}),
     case ets:lookup(couch_dbs, DbName) of
-    [#db{fd=Fd, fd_monitor=Lock} = Db] when Lock =/= locked ->
+    [#db{fd=Fd, fd_monitor=Lock, options=Options} = Db] when Lock =/= locked ->
         update_lru(DbName, Options),
         {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}};
     _ ->
+        Options = maybe_add_sys_db_callbacks(DbName, Options0),
         Timeout = couch_util:get_value(timeout, Options, infinity),
         Create = couch_util:get_value(create_if_missing, Options, false),
         case gen_server:call(couch_server, {open, DbName, Options}, Timeout) of
         {ok, #db{fd=Fd} = Db} ->
-            update_lru(DbName, Options),
             {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}};
         {not_found, no_db_file} when Create ->
             couch_log:warning("creating missing database: ~s", [DbName]),
diff --git a/src/test_request.erl b/src/test_request.erl
index d757593..8dd0d1a 100644
--- a/src/test_request.erl
+++ b/src/test_request.erl
@@ -15,47 +15,51 @@
 -export([get/1, get/2, get/3]).
 -export([post/2, post/3, post/4]).
 -export([put/2, put/3, put/4]).
--export([delete/1, delete/2]).
+-export([delete/1, delete/2, delete/3]).
 -export([options/1, options/2, options/3]).
 -export([request/3, request/4]).
 
 get(Url) ->
-    request(get, Url, []).
+    get(Url, []).
 
 get(Url, Headers) ->
-    request(get, Url, Headers).
+    get(Url, Headers, []).
+
 get(Url, Headers, Opts) ->
     request(get, Url, Headers, [], Opts).
 
 post(Url, Body) ->
-    request(post, Url, [], Body).
+    post(Url, [], Body).
 
 post(Url, Headers, Body) ->
-    request(post, Url, Headers, Body).
+    post(Url, Headers, Body, []).
 
 post(Url, Headers, Body, Opts) ->
     request(post, Url, Headers, Body, Opts).
 
 put(Url, Body) ->
-    request(put, Url, [], Body).
+    put(Url, [], Body).
 
 put(Url, Headers, Body) ->
-    request(put, Url, Headers, Body).
+    put(Url, Headers, Body, []).
 
 put(Url, Headers, Body, Opts) ->
     request(put, Url, Headers, Body, Opts).
 
 delete(Url) ->
-    request(delete, Url, []).
+    delete(Url, []).
 
 delete(Url, Opts) ->
-    request(delete, Url, [], [], Opts).
+    delete(Url, [], Opts).
+
+delete(Url, Headers, Opts) ->
+    request(delete, Url, Headers, [], Opts).
 
 options(Url) ->
-    request(options, Url, []).
+    options(Url, []).
 
 options(Url, Headers) ->
-    request(options, Url, Headers).
+    options(Url, Headers, []).
 
 options(Url, Headers, Opts) ->
     request(options, Url, Headers, [], Opts).
diff --git a/test/couch_doc_json_tests.erl b/test/couch_doc_json_tests.erl
index ae4d73c..9003d06 100644
--- a/test/couch_doc_json_tests.erl
+++ b/test/couch_doc_json_tests.erl
@@ -18,11 +18,13 @@
 
 setup() ->
     mock(couch_log),
+    mock(config),
     mock(couch_db_plugin),
     ok.
 
 teardown(_) ->
     meck:unload(couch_log),
+    meck:unload(config),
     meck:unload(couch_db_plugin),
     ok.
 
@@ -33,6 +35,11 @@
 mock(couch_log) ->
     ok = meck:new(couch_log, [passthrough]),
     ok = meck:expect(couch_log, debug, fun(_, _) -> ok end),
+    ok;
+mock(config) ->
+    meck:new(config, [passthrough]),
+    meck:expect(config, get, fun(_, _) -> undefined end),
+    meck:expect(config, get, fun(_, _, Default) -> Default end),
     ok.
 
 
diff --git a/test/couch_doc_tests.erl b/test/couch_doc_tests.erl
index fce4ff7..d24cd67 100644
--- a/test/couch_doc_tests.erl
+++ b/test/couch_doc_tests.erl
@@ -29,8 +29,10 @@
     ContentType = "multipart/related;boundary=multipart_related_boundary~~~~~~~~~~~~~~~~~~~~",
     DataFun = fun() -> request(start) end,
 
+    mock_config_max_document_id_length(),
     {ok, #doc{id = <<"doc0">>, atts = [_]}, _Fun, _Parser} =
         couch_doc:doc_from_multi_part_stream(ContentType, DataFun),
+    meck:unload(config),
     ok.
 
 doc_to_multi_part_stream_test() ->
@@ -75,16 +77,19 @@
 validate_docid_test_() ->
     {setup,
         fun() ->
+            mock_config_max_document_id_length(),
             ok = meck:new(couch_db_plugin, [passthrough]),
             meck:expect(couch_db_plugin, validate_docid, fun(_) -> false end)
         end,
         fun(_) ->
+            meck:unload(config),
             meck:unload(couch_db_plugin)
         end,
         [
             ?_assertEqual(ok, couch_doc:validate_docid(<<"idx">>)),
             ?_assertEqual(ok, couch_doc:validate_docid(<<"_design/idx">>)),
             ?_assertEqual(ok, couch_doc:validate_docid(<<"_local/idx">>)),
+            ?_assertEqual(ok, couch_doc:validate_docid(large_id(1024))),
             ?_assertThrow({illegal_docid, _},
                 couch_doc:validate_docid(<<>>)),
             ?_assertThrow({illegal_docid, _},
@@ -96,10 +101,15 @@
             ?_assertThrow({illegal_docid, _},
                 couch_doc:validate_docid(<<"_design/">>)),
             ?_assertThrow({illegal_docid, _},
-                couch_doc:validate_docid(<<"_local/">>))
+                couch_doc:validate_docid(<<"_local/">>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(large_id(1025)))
         ]
     }.
 
+large_id(N) ->
+    << <<"x">> || _ <- lists:seq(1, N) >>.
+
 request(start) ->
     {ok, Doc} = file:read_file(?REQUEST_FIXTURE),
     {Doc, fun() -> request(stop) end};
@@ -116,3 +126,11 @@
 collected() ->
     B = binary:replace(iolist_to_binary(get(data)), <<"\r\n">>, <<0>>, [global]),
     binary:split(B, [<<0>>], [global]).
+
+mock_config_max_document_id_length() ->
+    ok = meck:new(config, [passthrough]),
+    meck:expect(config, get,
+        fun("couchdb", "max_document_id_length", "infinity") -> "1024";
+            (Key, Val, Default) -> meck:passthrough([Key, Val, Default])
+        end
+    ).