You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2021/04/16 21:45:15 UTC

[couchdb] 10/24: Remove most of the functionality from couch_server

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 9e4fc195efd9b1e65ca30744a1e2cf168bf58fbf
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 14 00:46:31 2021 -0400

    Remove most of the functionality from couch_server
    
    Remove all the code related to opening and caching of databases.
    
    However, couch_server did a few other things such as:
    
     * Parse and maintain the CouchDB version
    
     * Return the server "uuid" value
    
     * The gen_server was monitoring config updates and hashing admin passwords
       when they were updated
    
    It was a 50/50 decision to move that functionality out to other modules
    completely or keep it where it is. Since it wasn't just a single thing, and the
    overall PR was getting rather large, opted to pair the exisiting code to the
    minimum, and then later we can do another round of cleanup and find a better
    place for those features.
---
 src/couch/src/couch_server.erl | 872 +----------------------------------------
 1 file changed, 16 insertions(+), 856 deletions(-)

diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl
index 42eab73..8fd074a 100644
--- a/src/couch/src/couch_server.erl
+++ b/src/couch/src/couch_server.erl
@@ -15,43 +15,18 @@
 -behaviour(config_listener).
 -vsn(3).
 
--export([open/2,create/2,delete/2,get_version/0,get_version/1,get_git_sha/0,get_uuid/0]).
--export([all_databases/0, all_databases/2]).
+-export([get_version/0,get_version/1,get_git_sha/0,get_uuid/0]).
 -export([init/1, handle_call/3,sup_start_link/0]).
--export([handle_cast/2,code_change/3,handle_info/2,terminate/2,format_status/2]).
--export([dev_start/0,is_admin/2,has_admins/0,get_stats/0]).
--export([close_lru/0]).
--export([close_db_if_idle/1]).
--export([delete_compaction_files/1]).
--export([exists/1]).
--export([get_engine_extensions/0]).
--export([get_engine_path/2]).
--export([lock/2, unlock/1]).
+-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
+-export([is_admin/2,has_admins/0]).
 
 % config_listener api
 -export([handle_config_change/5, handle_config_terminate/3]).
 
 -include_lib("couch/include/couch_db.hrl").
--include("couch_server_int.hrl").
 
--define(MAX_DBS_OPEN, 500).
 -define(RELISTEN_DELAY, 5000).
 
--record(server,{
-    root_dir = [],
-    engines = [],
-    max_dbs_open=?MAX_DBS_OPEN,
-    dbs_open=0,
-    start_time="",
-    update_lru_on_read=true,
-    lru = couch_lru:new()
-    }).
-
-dev_start() ->
-    couch:stop(),
-    up_to_date = make:all([load, debug_info]),
-    couch:start().
-
 get_version() ->
     ?COUCHDB_VERSION. %% Defined in rebar.config.script
 get_version(short) ->
@@ -70,138 +45,9 @@ get_uuid() ->
         UUID -> ?l2b(UUID)
     end.
 
-get_stats() ->
-    {ok, #server{start_time=Time,dbs_open=Open}} =
-            gen_server:call(couch_server, get_server),
-    [{start_time, ?l2b(Time)}, {dbs_open, Open}].
-
 sup_start_link() ->
     gen_server:start_link({local, couch_server}, couch_server, [], []).
 
-open(DbName, Options) ->
-    try
-        validate_open_or_create(DbName, Options),
-        open_int(DbName, Options)
-    catch throw:{?MODULE, Error} ->
-        Error
-    end.
-
-open_int(DbName, Options0) ->
-    Ctx = couch_util:get_value(user_ctx, Options0, #user_ctx{}),
-    case ets:lookup(couch_dbs, DbName) of
-    [#entry{db = Db0, lock = Lock} = Entry] when Lock =/= locked ->
-        update_lru(DbName, Entry#entry.db_options),
-        {ok, Db1} = couch_db:incref(Db0),
-        couch_db:set_user_ctx(Db1, Ctx);
-    _ ->
-        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, Db0} ->
-            {ok, Db1} = couch_db:incref(Db0),
-            couch_db:set_user_ctx(Db1, Ctx);
-        {not_found, no_db_file} when Create ->
-            couch_log:warning("creating missing database: ~s", [DbName]),
-            couch_server:create(DbName, Options);
-        Error ->
-            Error
-        end
-    end.
-
-update_lru(DbName, Options) ->
-    case config:get_boolean("couchdb", "update_lru_on_read", false) of
-        true ->
-            case lists:member(sys_db, Options) of
-                false -> gen_server:cast(couch_server, {update_lru, DbName});
-                true -> ok
-            end;
-        false ->
-            ok
-    end.
-
-close_lru() ->
-    gen_server:call(couch_server, close_lru).
-
-create(DbName, Options) ->
-    try
-        validate_open_or_create(DbName, Options),
-        create_int(DbName, Options)
-    catch throw:{?MODULE, Error} ->
-        Error
-    end.
-
-create_int(DbName, Options0) ->
-    Options = maybe_add_sys_db_callbacks(DbName, Options0),
-    couch_partition:validate_dbname(DbName, Options),
-    case gen_server:call(couch_server, {create, DbName, Options}, infinity) of
-    {ok, Db0} ->
-        Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
-        {ok, Db1} = couch_db:incref(Db0),
-        couch_db:set_user_ctx(Db1, Ctx);
-    Error ->
-        Error
-    end.
-
-delete(DbName, Options) ->
-    gen_server:call(couch_server, {delete, DbName, Options}, infinity).
-
-
-exists(DbName) ->
-    RootDir = config:get("couchdb", "database_dir", "."),
-    Engines = get_configured_engines(),
-    Possible = get_possible_engines(DbName, RootDir, Engines),
-    Possible /= [].
-
-
-delete_compaction_files(DbName) ->
-    delete_compaction_files(DbName, []).
-
-delete_compaction_files(DbName, DelOpts) when is_list(DbName) ->
-    RootDir = config:get("couchdb", "database_dir", "."),
-    lists:foreach(fun({Ext, Engine}) ->
-        FPath = make_filepath(RootDir, DbName, Ext),
-        couch_db_engine:delete_compaction_files(Engine, RootDir, FPath, DelOpts)
-    end, get_configured_engines()),
-    ok;
-delete_compaction_files(DbName, DelOpts) when is_binary(DbName) ->
-    delete_compaction_files(?b2l(DbName), DelOpts).
-
-maybe_add_sys_db_callbacks(DbName, Options) when is_binary(DbName) ->
-    maybe_add_sys_db_callbacks(?b2l(DbName), Options);
-maybe_add_sys_db_callbacks(DbName, Options) ->
-    DbsDbName = config:get("mem3", "shards_db", "_dbs"),
-    NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
-
-    IsReplicatorDb = path_ends_with(DbName, "_replicator"),
-    UsersDbSuffix = config:get("couchdb", "users_db_suffix", "_users"),
-    IsUsersDb = path_ends_with(DbName, "_users")
-        orelse path_ends_with(DbName, UsersDbSuffix),
-    if
-        DbName == DbsDbName ->
-            [sys_db | Options];
-        DbName == NodesDbName ->
-            [sys_db | Options];
-        IsReplicatorDb ->
-            [{before_doc_update, fun couch_replicator_docs:before_doc_update/3},
-             {after_doc_read, fun couch_replicator_docs:after_doc_read/2},
-             sys_db | Options];
-        IsUsersDb ->
-            [{before_doc_update, fun couch_users_db:before_doc_update/3},
-             {after_doc_read, fun couch_users_db:after_doc_read/2},
-             sys_db | Options];
-        true ->
-            Options
-    end.
-
-path_ends_with(Path, Suffix) when is_binary(Suffix) ->
-    Suffix =:= couch_db:dbname_suffix(Path);
-path_ends_with(Path, Suffix) when is_list(Suffix) ->
-    path_ends_with(Path, ?l2b(Suffix)).
-
-check_dbname(DbName) ->
-    couch_db:validate_dbname(DbName).
-
 is_admin(User, ClearPwd) ->
     case config:get("admins", User) of
     "-hashed-" ++ HashedPwdAndSalt ->
@@ -224,22 +70,9 @@ hash_admin_passwords(Persist) ->
             config:set("admins", User, ?b2l(HashedPassword), Persist)
         end, couch_passwords:get_unhashed_admins()).
 
-close_db_if_idle(DbName) ->
-    case ets:lookup(couch_dbs, DbName) of
-        [#entry{}] ->
-            gen_server:cast(couch_server, {close_db_if_idle, DbName});
-        [] ->
-            ok
-    end.
-
-
 init([]) ->
-    couch_util:set_mqd_off_heap(?MODULE),
-    couch_util:set_process_priority(?MODULE, high),
-
     % Mark being able to receive documents with an _access property as a supported feature
     config:enable_feature('access-ready'),
-
     % Mark if fips is enabled
     case
         erlang:function_exported(crypto, info_fips, 0) andalso
@@ -249,83 +82,28 @@ init([]) ->
         false ->
             ok
     end,
+    ok = config:listen_for_changes(?MODULE, nil),
+    hash_admin_passwords(),
+    {ok, nil}.
 
-    % read config and register for configuration changes
+handle_call(Msg, _From, Srv) ->
+    {stop, {bad_call, Msg}, Srv}.
 
-    % just stop if one of the config settings change. couch_server_sup
-    % will restart us and then we will pick up the new settings.
+handle_cast(Msg, Srv) ->
+    {stop, {bad_cast, Msg}, Srv}.
 
-    RootDir = config:get("couchdb", "database_dir", "."),
-    Engines = get_configured_engines(),
-    MaxDbsOpen = list_to_integer(
-            config:get("couchdb", "max_dbs_open", integer_to_list(?MAX_DBS_OPEN))),
-    UpdateLruOnRead =
-        config:get("couchdb", "update_lru_on_read", "false") =:= "true",
-    ok = config:listen_for_changes(?MODULE, nil),
-    ok = couch_file:init_delete_dir(RootDir),
-    hash_admin_passwords(),
-    ets:new(couch_dbs, [
-        set,
-        protected,
-        named_table,
-        {keypos, #entry.name},
-        {read_concurrency, true}
-    ]),
-    ets:new(couch_dbs_pid_to_name, [set, protected, named_table]),
-    ets:new(couch_dbs_locks, [
-        set,
-        public,
-        named_table,
-        {read_concurrency, true}
-    ]),
-    process_flag(trap_exit, true),
-    {ok, #server{root_dir=RootDir,
-                engines = Engines,
-                max_dbs_open=MaxDbsOpen,
-                update_lru_on_read=UpdateLruOnRead,
-                start_time=couch_util:rfc1123_date()}}.
+handle_info(Msg, Srv) ->
+    {stop, {unknown_message, Msg}, Srv}.
 
-terminate(Reason, Srv) ->
-    couch_log:error("couch_server terminating with ~p, state ~2048p",
-                    [Reason,
-                     Srv#server{lru = redacted}]),
-    ets:foldl(fun(#entry{db = Db}, _) ->
-        % Filter out any entry records for open_async
-        % processes that haven't finished.
-        if Db == undefined -> ok; true ->
-            couch_util:shutdown_sync(couch_db:get_pid(Db))
-        end
-    end, nil, couch_dbs),
-    ok.
+code_change(_OldVsn, Srv, _Extra) ->
+    {ok, Srv}.
 
-format_status(_Opt, [_PDict, Srv]) ->
-    Scrubbed = Srv#server{lru=couch_lru:sizes(Srv#server.lru)},
-    [{data, [{"State", ?record_to_keyval(server, Scrubbed)}]}].
+terminate(_Reason, _Srv) ->
+    ok.
 
-handle_config_change("couchdb", "database_dir", _, _, _) ->
-    exit(whereis(couch_server), config_change),
-    remove_handler;
-handle_config_change("couchdb", "update_lru_on_read", "true", _, _) ->
-    {ok, gen_server:call(couch_server,{set_update_lru_on_read,true})};
-handle_config_change("couchdb", "update_lru_on_read", _, _, _) ->
-    {ok, gen_server:call(couch_server,{set_update_lru_on_read,false})};
-handle_config_change("couchdb", "max_dbs_open", Max, _, _) when is_list(Max) ->
-    {ok, gen_server:call(couch_server,{set_max_dbs_open,list_to_integer(Max)})};
-handle_config_change("couchdb", "max_dbs_open", _, _, _) ->
-    {ok, gen_server:call(couch_server,{set_max_dbs_open,?MAX_DBS_OPEN})};
-handle_config_change("couchdb_engines", _, _, _, _) ->
-    {ok, gen_server:call(couch_server, reload_engines)};
 handle_config_change("admins", _, _, Persist, _) ->
     % spawn here so couch event manager doesn't deadlock
     {ok, spawn(fun() -> hash_admin_passwords(Persist) end)};
-handle_config_change("httpd", "authentication_handlers", _, _, _) ->
-    {ok, couch_httpd:stop()};
-handle_config_change("httpd", "bind_address", _, _, _) ->
-    {ok, couch_httpd:stop()};
-handle_config_change("httpd", "port", _, _, _) ->
-    {ok, couch_httpd:stop()};
-handle_config_change("httpd", "max_connections", _, _, _) ->
-    {ok, couch_httpd:stop()};
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
@@ -333,621 +111,3 @@ handle_config_terminate(_, stop, _) ->
     ok;
 handle_config_terminate(_Server, _Reason, _State) ->
     erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
-
-
-all_databases() ->
-    {ok, DbList} = all_databases(
-        fun(DbName, Acc) -> {ok, [DbName | Acc]} end, []),
-    {ok, lists:usort(DbList)}.
-
-all_databases(Fun, Acc0) ->
-    {ok, #server{root_dir=Root}} = gen_server:call(couch_server, get_server),
-    NormRoot = couch_util:normpath(Root),
-    Extensions = get_engine_extensions(),
-    ExtRegExp = "(" ++ string:join(Extensions, "|") ++ ")",
-    RegExp =
-        "^[a-z0-9\\_\\$()\\+\\-]*" % stock CouchDB name regex
-        "(\\.[0-9]{10,})?"         % optional shard timestamp
-        "\\." ++ ExtRegExp ++ "$", % filename extension
-    FinalAcc = try
-    couch_util:fold_files(Root,
-        RegExp,
-        true,
-            fun(Filename, AccIn) ->
-                NormFilename = couch_util:normpath(Filename),
-                case NormFilename -- NormRoot of
-                [$/ | RelativeFilename] -> ok;
-                RelativeFilename -> ok
-                end,
-                Ext = filename:extension(RelativeFilename),
-                case Fun(?l2b(filename:rootname(RelativeFilename, Ext)), AccIn) of
-                {ok, NewAcc} -> NewAcc;
-                {stop, NewAcc} -> throw({stop, Fun, NewAcc})
-                end
-            end, Acc0)
-    catch throw:{stop, Fun, Acc1} ->
-         Acc1
-    end,
-    {ok, FinalAcc}.
-
-
-make_room(Server, Options) ->
-    case lists:member(sys_db, Options) of
-        false -> maybe_close_lru_db(Server);
-        true -> {ok, Server}
-    end.
-
-maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
-        when NumOpen < MaxOpen ->
-    {ok, Server};
-maybe_close_lru_db(#server{lru=Lru}=Server) ->
-    case couch_lru:close(Lru) of
-        {true, NewLru} ->
-            {ok, db_closed(Server#server{lru = NewLru}, [])};
-        false ->
-            {error, all_dbs_active}
-    end.
-
-open_async(Server, From, DbName, Options) ->
-    NoLRUServer = Server#server{
-        lru = redacted
-    },
-    Parent = self(),
-    T0 = os:timestamp(),
-    Opener = spawn_link(fun() ->
-        Res = open_async_int(NoLRUServer, DbName, Options),
-        IsSuccess = case Res of
-            {ok, _} -> true;
-            _ -> false
-        end,
-        case IsSuccess andalso lists:member(create, Options) of
-            true ->
-                couch_event:notify(DbName, created);
-            false ->
-                ok
-        end,
-        gen_server:call(Parent, {open_result, DbName, Res}, infinity),
-        unlink(Parent),
-        case IsSuccess of
-            true ->
-                % Track latency times for successful opens
-                Diff = timer:now_diff(os:timestamp(), T0) / 1000,
-                couch_stats:update_histogram([couchdb, db_open_time], Diff);
-            false ->
-                % Log unsuccessful open results
-                couch_log:info("open_result error ~p for ~s", [Res, DbName])
-        end
-    end),
-    ReqType = case lists:member(create, Options) of
-        true -> create;
-        false -> open
-    end,
-    true = ets:insert(couch_dbs, #entry{
-        name = DbName,
-        pid = Opener,
-        lock = locked,
-        waiters = [From],
-        req_type = ReqType,
-        db_options = Options
-    }),
-    true = ets:insert(couch_dbs_pid_to_name, {Opener, DbName}),
-    db_opened(Server, Options).
-
-open_async_int(Server, DbName, Options) ->
-    DbNameList = binary_to_list(DbName),
-    case check_dbname(DbNameList) of
-        ok ->
-            case get_engine(Server, DbNameList, Options) of
-                {ok, {Module, FilePath}} ->
-                    couch_db:start_link(Module, DbName, FilePath, Options);
-                Error2 ->
-                    Error2
-            end;
-        Error1 ->
-            Error1
-    end.
-
-handle_call(close_lru, _From, #server{lru=Lru} = Server) ->
-    case couch_lru:close(Lru) of
-        {true, NewLru} ->
-            {reply, ok, db_closed(Server#server{lru = NewLru}, [])};
-        false ->
-            {reply, {error, all_dbs_active}, Server}
-    end;
-handle_call(open_dbs_count, _From, Server) ->
-    {reply, Server#server.dbs_open, Server};
-handle_call({set_update_lru_on_read, UpdateOnRead}, _From, Server) ->
-    {reply, ok, Server#server{update_lru_on_read=UpdateOnRead}};
-handle_call({set_max_dbs_open, Max}, _From, Server) ->
-    {reply, ok, Server#server{max_dbs_open=Max}};
-handle_call(reload_engines, _From, Server) ->
-    {reply, ok, Server#server{engines = get_configured_engines()}};
-handle_call(get_server, _From, Server) ->
-    {reply, {ok, Server}, Server};
-handle_call({open_result, DbName, {ok, Db}}, {Opener, _}, Server) ->
-    true = ets:delete(couch_dbs_pid_to_name, Opener),
-    DbPid = couch_db:get_pid(Db),
-    case ets:lookup(couch_dbs, DbName) of
-        [] ->
-            % db was deleted during async open
-            exit(DbPid, kill),
-            {reply, ok, Server};
-        [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
-            link(DbPid),
-            [gen_server:reply(Waiter, {ok, Db}) || Waiter <- Waiters],
-            % Cancel the creation request if it exists.
-            case ReqType of
-                {create, DbName, _Options, CrFrom} ->
-                    gen_server:reply(CrFrom, file_exists);
-                _ ->
-                    ok
-            end,
-            true = ets:insert(couch_dbs, #entry{
-                name = DbName,
-                db = Db,
-                pid = DbPid,
-                lock = unlocked,
-                db_options = Entry#entry.db_options,
-                start_time = couch_db:get_instance_start_time(Db)
-            }),
-            true = ets:insert(couch_dbs_pid_to_name, {DbPid, DbName}),
-            Lru = case couch_db:is_system_db(Db) of
-                false ->
-                    couch_lru:insert(DbName, Server#server.lru);
-                true ->
-                    Server#server.lru
-            end,
-            {reply, ok, Server#server{lru = Lru}};
-        [#entry{}] ->
-            % A mismatched opener pid means that this open_result message
-            % was in our mailbox but is now stale. Mostly ignore
-            % it except to ensure that the db pid is super dead.
-            exit(couch_db:get_pid(Db), kill),
-            {reply, ok, Server}
-    end;
-handle_call({open_result, DbName, {error, eexist}}, From, Server) ->
-    handle_call({open_result, DbName, file_exists}, From, Server);
-handle_call({open_result, DbName, Error}, {Opener, _}, Server) ->
-    case ets:lookup(couch_dbs, DbName) of
-        [] ->
-            % db was deleted during async open
-            {reply, ok, Server};
-        [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] ->
-            [gen_server:reply(Waiter, Error) || Waiter <- Waiters],
-            true = ets:delete(couch_dbs, DbName),
-            true = ets:delete(couch_dbs_pid_to_name, Opener),
-            NewServer = case ReqType of
-                {create, DbName, Options, CrFrom} ->
-                    open_async(Server, CrFrom, DbName, Options);
-                _ ->
-                    Server
-            end,
-            {reply, ok, db_closed(NewServer, Entry#entry.db_options)};
-        [#entry{}] ->
-            % A mismatched pid means that this open_result message
-            % was in our mailbox and is now stale. Ignore it.
-            {reply, ok, Server}
-    end;
-handle_call({open, DbName, Options}, From, Server) ->
-    case ets:lookup(couch_dbs, DbName) of
-    [] ->
-        case make_room(Server, Options) of
-        {ok, Server2} ->
-            {noreply, open_async(Server2, From, DbName, Options)};
-        CloseError ->
-            {reply, CloseError, Server}
-        end;
-    [#entry{waiters = Waiters} = Entry] when is_list(Waiters) ->
-        true = ets:insert(couch_dbs, Entry#entry{waiters = [From | Waiters]}),
-        NumWaiters = length(Waiters),
-        if NumWaiters =< 10 orelse NumWaiters rem 10 /= 0 -> ok; true ->
-            Fmt = "~b clients waiting to open db ~s",
-            couch_log:info(Fmt, [length(Waiters), DbName])
-        end,
-        {noreply, Server};
-    [#entry{db = Db}] ->
-        {reply, {ok, Db}, Server}
-    end;
-handle_call({create, DbName, Options}, From, Server) ->
-    case ets:lookup(couch_dbs, DbName) of
-    [] ->
-        case make_room(Server, Options) of
-        {ok, Server2} ->
-            CrOptions = [create | Options],
-            {noreply, open_async(Server2, From, DbName, CrOptions)};
-        CloseError ->
-            {reply, CloseError, Server}
-        end;
-    [#entry{req_type = open} = Entry] ->
-        % We're trying to create a database while someone is in
-        % the middle of trying to open it. We allow one creator
-        % to wait while we figure out if it'll succeed.
-        CrOptions = [create | Options],
-        Req = {create, DbName, CrOptions, From},
-        true = ets:insert(couch_dbs, Entry#entry{req_type = Req}),
-        {noreply, Server};
-    [_AlreadyRunningDb] ->
-        {reply, file_exists, Server}
-    end;
-handle_call({delete, DbName, Options}, _From, Server) ->
-    DbNameList = binary_to_list(DbName),
-    case check_dbname(DbNameList) of
-    ok ->
-        Server2 =
-        case ets:lookup(couch_dbs, DbName) of
-        [] -> Server;
-        [#entry{pid = Pid, waiters = Waiters} = Entry] when is_list(Waiters) ->
-            true = ets:delete(couch_dbs, DbName),
-            true = ets:delete(couch_dbs_pid_to_name, Pid),
-            exit(Pid, kill),
-            [gen_server:reply(Waiter, not_found) || Waiter <- Waiters],
-            db_closed(Server, Entry#entry.db_options);
-        [#entry{pid = Pid} = Entry] ->
-            true = ets:delete(couch_dbs, DbName),
-            true = ets:delete(couch_dbs_pid_to_name, Pid),
-            exit(Pid, kill),
-            db_closed(Server, Entry#entry.db_options)
-        end,
-
-        couch_db_plugin:on_delete(DbName, Options),
-
-        DelOpt = [{context, delete} | Options],
-
-        % Make sure and remove all compaction data
-        delete_compaction_files(DbNameList, Options),
-
-        {ok, {Engine, FilePath}} = get_engine(Server, DbNameList),
-        RootDir = Server#server.root_dir,
-        case couch_db_engine:delete(Engine, RootDir, FilePath, DelOpt) of
-        ok ->
-            couch_event:notify(DbName, deleted),
-            {reply, ok, Server2};
-        {error, enoent} ->
-            {reply, not_found, Server2};
-        Else ->
-            {reply, Else, Server2}
-        end;
-    Error ->
-        {reply, Error, Server}
-    end;
-handle_call({db_updated, Db}, _From, Server0) ->
-    DbName = couch_db:name(Db),
-    StartTime = couch_db:get_instance_start_time(Db),
-    Server = try ets:lookup_element(couch_dbs, DbName, #entry.start_time) of
-        StartTime ->
-            true = ets:update_element(couch_dbs, DbName, {#entry.db, Db}),
-            Lru = case couch_db:is_system_db(Db) of
-                false -> couch_lru:update(DbName, Server0#server.lru);
-                true -> Server0#server.lru
-            end,
-            Server0#server{lru = Lru};
-        _ ->
-            Server0
-    catch _:_ ->
-        Server0
-    end,
-    {reply, ok, Server}.
-
-handle_cast({update_lru, DbName}, #server{lru = Lru, update_lru_on_read=true} = Server) ->
-    {noreply, Server#server{lru = couch_lru:update(DbName, Lru)}};
-handle_cast({update_lru, _DbName}, Server) ->
-    {noreply, Server};
-handle_cast({close_db_if_idle, DbName}, Server) ->
-    case ets:update_element(couch_dbs, DbName, {#entry.lock, locked}) of
-    true ->
-        [#entry{db = Db, db_options = DbOpts}] = ets:lookup(couch_dbs, DbName),
-        case couch_db:is_idle(Db) of
-        true ->
-            DbPid = couch_db:get_pid(Db),
-            true = ets:delete(couch_dbs, DbName),
-            true = ets:delete(couch_dbs_pid_to_name, DbPid),
-            exit(DbPid, kill),
-            {noreply, db_closed(Server, DbOpts)};
-        false ->
-            true = ets:update_element(
-                     couch_dbs, DbName, {#entry.lock, unlocked}),
-            {noreply, Server}
-        end;
-    false ->
-        {noreply, Server}
-    end;
-
-handle_cast(Msg, Server) ->
-    {stop, {unknown_cast_message, Msg}, Server}.
-
-code_change(_OldVsn, #server{}=State, _Extra) ->
-    {ok, State}.
-
-handle_info({'EXIT', _Pid, config_change}, Server) ->
-    {stop, config_change, Server};
-handle_info({'EXIT', Pid, Reason}, Server) ->
-    case ets:lookup(couch_dbs_pid_to_name, Pid) of
-    [{Pid, DbName}] ->
-        [#entry{waiters = Waiters} = Entry] = ets:lookup(couch_dbs, DbName),
-        if Reason /= snappy_nif_not_loaded -> ok; true ->
-            Msg = io_lib:format("To open the database `~s`, Apache CouchDB "
-                "must be built with Erlang OTP R13B04 or higher.", [DbName]),
-            couch_log:error(Msg, [])
-        end,
-        % We kill databases on purpose so there's no reason
-        % to log that fact. So we restrict logging to "interesting"
-        % reasons.
-        if Reason == normal orelse Reason == killed -> ok; true ->
-            couch_log:info("db ~s died with reason ~p", [DbName, Reason])
-        end,
-        if not is_list(Waiters) -> ok; true ->
-            [gen_server:reply(Waiter, Reason) || Waiter <- Waiters]
-        end,
-        true = ets:delete(couch_dbs, DbName),
-        true = ets:delete(couch_dbs_pid_to_name, Pid),
-        {noreply, db_closed(Server, Entry#entry.db_options)};
-    [] ->
-        {noreply, Server}
-    end;
-handle_info(restart_config_listener, State) ->
-    ok = config:listen_for_changes(?MODULE, nil),
-    {noreply, State};
-handle_info(Info, Server) ->
-    {stop, {unknown_message, Info}, Server}.
-
-db_opened(Server, Options) ->
-    case lists:member(sys_db, Options) of
-        false -> Server#server{dbs_open=Server#server.dbs_open + 1};
-        true -> Server
-    end.
-
-db_closed(Server, Options) ->
-    case lists:member(sys_db, Options) of
-        false -> Server#server{dbs_open=Server#server.dbs_open - 1};
-        true -> Server
-    end.
-
-validate_open_or_create(DbName, Options) ->
-    case check_dbname(DbName) of
-        ok ->
-            ok;
-        DbNameError ->
-            throw({?MODULE, DbNameError})
-    end,
-
-    case check_engine(Options) of
-        ok ->
-            ok;
-        EngineError ->
-            throw({?MODULE, EngineError})
-    end,
-
-    case ets:lookup(couch_dbs_locks, DbName) of
-        [] ->
-            ok;
-        [{DbName, Reason}] ->
-            throw({?MODULE, {error, {locked, Reason}}})
-    end.
-
-get_configured_engines() ->
-    ConfigEntries = config:get("couchdb_engines"),
-    Engines = lists:flatmap(fun({Extension, ModuleStr}) ->
-        try
-            [{Extension, list_to_atom(ModuleStr)}]
-        catch _T:_R ->
-            []
-        end
-    end, ConfigEntries),
-    case Engines of
-        [] ->
-            [{"couch", couch_bt_engine}];
-        Else ->
-            Else
-    end.
-
-
-get_engine(Server, DbName, Options) ->
-    #server{
-        root_dir = RootDir,
-        engines = Engines
-    } = Server,
-    case couch_util:get_value(engine, Options) of
-        Ext when is_binary(Ext) ->
-            ExtStr = binary_to_list(Ext),
-            case lists:keyfind(ExtStr, 1, Engines) of
-                {ExtStr, Engine} ->
-                    Path = make_filepath(RootDir, DbName, ExtStr),
-                    {ok, {Engine, Path}};
-                false ->
-                    {error, {invalid_engine_extension, Ext}}
-            end;
-        _ ->
-            get_engine(Server, DbName)
-    end.
-
-
-get_engine(Server, DbName) ->
-    #server{
-        root_dir = RootDir,
-        engines = Engines
-    } = Server,
-    Possible = get_possible_engines(DbName, RootDir, Engines),
-    case Possible of
-        [] ->
-            get_default_engine(Server, DbName);
-        [Engine] ->
-            {ok, Engine};
-        _ ->
-            erlang:error(engine_conflict)
-    end.
-
-
-get_possible_engines(DbName, RootDir, Engines) ->
-    lists:foldl(fun({Extension, Engine}, Acc) ->
-        Path = make_filepath(RootDir, DbName, Extension),
-        case couch_db_engine:exists(Engine, Path) of
-            true ->
-                [{Engine, Path} | Acc];
-            false ->
-                Acc
-        end
-    end, [], Engines).
-
-
-get_default_engine(Server, DbName) ->
-    #server{
-        root_dir = RootDir,
-        engines = Engines
-    } = Server,
-    Default = {couch_bt_engine, make_filepath(RootDir, DbName, "couch")},
-    case config:get("couchdb", "default_engine") of
-        Extension when is_list(Extension) ->
-            case lists:keyfind(Extension, 1, Engines) of
-                {Extension, Module} ->
-                    {ok, {Module, make_filepath(RootDir, DbName, Extension)}};
-                false ->
-                    Fmt = "Invalid storage engine extension ~s,"
-                            " configured engine extensions are: ~s",
-                    Exts = [E || {E, _} <- Engines],
-                    Args = [Extension, string:join(Exts, ", ")],
-                    couch_log:error(Fmt, Args),
-                    {ok, Default}
-            end;
-        _ ->
-            {ok, Default}
-    end.
-
-
-make_filepath(RootDir, DbName, Extension) when is_binary(RootDir) ->
-    make_filepath(binary_to_list(RootDir), DbName, Extension);
-make_filepath(RootDir, DbName, Extension) when is_binary(DbName) ->
-    make_filepath(RootDir, binary_to_list(DbName), Extension);
-make_filepath(RootDir, DbName, Extension) when is_binary(Extension) ->
-    make_filepath(RootDir, DbName, binary_to_list(Extension));
-make_filepath(RootDir, DbName, Extension) ->
-    filename:join([RootDir, "./" ++ DbName ++ "." ++ Extension]).
-
-
-get_engine_extensions() ->
-    case config:get("couchdb_engines") of
-        [] ->
-            ["couch"];
-        Entries ->
-            [Ext || {Ext, _Mod} <- Entries]
-    end.
-
-
-check_engine(Options) ->
-    case couch_util:get_value(engine, Options) of
-        Ext when is_binary(Ext) ->
-            ExtStr = binary_to_list(Ext),
-            Extensions = get_engine_extensions(),
-            case lists:member(ExtStr, Extensions) of
-                true ->
-                    ok;
-                false ->
-                    {error, {invalid_engine_extension, Ext}}
-            end;
-        _ ->
-            ok
-    end.
-
-
-get_engine_path(DbName, Engine) when is_binary(DbName), is_atom(Engine) ->
-    RootDir = config:get("couchdb", "database_dir", "."),
-    case lists:keyfind(Engine, 2, get_configured_engines()) of
-        {Ext, Engine} ->
-            {ok, make_filepath(RootDir, DbName, Ext)};
-        false ->
-            {error, {invalid_engine, Engine}}
-    end.
-
-lock(DbName, Reason) when is_binary(DbName), is_binary(Reason) ->
-    case ets:lookup(couch_dbs, DbName) of
-        [] ->
-            true = ets:insert(couch_dbs_locks, {DbName, Reason}),
-            ok;
-        [#entry{}] ->
-            {error, already_opened}
-    end.
-
-unlock(DbName) when is_binary(DbName) ->
-    true = ets:delete(couch_dbs_locks, DbName),
-    ok.
-
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-setup_all() ->
-    ok = meck:new(config, [passthrough]),
-    ok = meck:expect(config, get, fun config_get/3),
-    ok.
-
-teardown_all(_) ->
-    meck:unload().
-
-config_get("couchdb", "users_db_suffix", _) -> "users_db";
-config_get(_, _, _) -> undefined.
-
-maybe_add_sys_db_callbacks_pass_test_() ->
-    {
-        setup,
-        fun setup_all/0,
-        fun teardown_all/1,
-        [
-            fun should_add_sys_db_callbacks/0,
-            fun should_not_add_sys_db_callbacks/0
-        ]
-    }.
-
-should_add_sys_db_callbacks() ->
-    Cases = [
-        "shards/00000000-3fffffff/foo/users_db.1415960794.couch",
-        "shards/00000000-3fffffff/foo/users_db.1415960794",
-        "shards/00000000-3fffffff/foo/users_db",
-        "shards/00000000-3fffffff/users_db.1415960794.couch",
-        "shards/00000000-3fffffff/users_db.1415960794",
-        "shards/00000000-3fffffff/users_db",
-
-        "shards/00000000-3fffffff/_users.1415960794.couch",
-        "shards/00000000-3fffffff/_users.1415960794",
-        "shards/00000000-3fffffff/_users",
-
-        "foo/users_db.couch",
-        "foo/users_db",
-        "users_db.couch",
-        "users_db",
-        "foo/_users.couch",
-        "foo/_users",
-        "_users.couch",
-        "_users",
-
-        "shards/00000000-3fffffff/foo/_replicator.1415960794.couch",
-        "shards/00000000-3fffffff/foo/_replicator.1415960794",
-        "shards/00000000-3fffffff/_replicator",
-        "foo/_replicator.couch",
-        "foo/_replicator",
-        "_replicator.couch",
-        "_replicator"
-    ],
-    lists:foreach(fun(DbName) ->
-        check_case(DbName, true),
-        check_case(?l2b(DbName), true)
-    end, Cases).
-
-should_not_add_sys_db_callbacks() ->
-    Cases = [
-        "shards/00000000-3fffffff/foo/mydb.1415960794.couch",
-        "shards/00000000-3fffffff/foo/mydb.1415960794",
-        "shards/00000000-3fffffff/mydb",
-        "foo/mydb.couch",
-        "foo/mydb",
-        "mydb.couch",
-        "mydb"
-    ],
-    lists:foreach(fun(DbName) ->
-        check_case(DbName, false),
-        check_case(?l2b(DbName), false)
-    end, Cases).
-
-check_case(DbName, IsAdded) ->
-    Options = maybe_add_sys_db_callbacks(DbName, [other_options]),
-    ?assertEqual(IsAdded, lists:member(sys_db, Options)).
-
--endif.