From 0e1d9aaf5927fa99a33c26743bafd69b9c0b4ab9 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Thu, 21 Jun 2018 16:30:35 +0200 Subject: Add props field to couch_db_engine Add the ability to store database properties to the couchdb engine. --- src/couch/src/couch_bt_engine.erl | 71 ++++++++++++++++++++++++++++- src/couch/src/couch_bt_engine_compactor.erl | 8 +++- src/couch/src/couch_bt_engine_header.erl | 3 +- src/couch/src/couch_db.erl | 11 +++++ src/couch/src/couch_db_engine.erl | 47 +++++++++++++++++++ src/couch/src/couch_db_int.hrl | 3 +- src/couch/src/couch_db_updater.erl | 11 ++++- src/couch/src/test_engine_get_set_props.erl | 22 +++++++++ 8 files changed, 169 insertions(+), 7 deletions(-) diff --git a/src/couch/src/couch_bt_engine.erl b/src/couch/src/couch_bt_engine.erl index ee0d6d864..a88face11 100644 --- a/src/couch/src/couch_bt_engine.erl +++ b/src/couch/src/couch_bt_engine.erl @@ -39,12 +39,16 @@ get_purge_seq/1, get_revs_limit/1, get_security/1, + get_props/1, + get_prop/2, + get_prop/3, get_size_info/1, get_update_seq/1, get_uuid/1, set_revs_limit/2, set_security/2, + set_prop/3, open_docs/2, open_local_docs/2, @@ -93,7 +97,8 @@ -export([ set_update_seq/2, update_header/2, - copy_security/2 + copy_security/2, + copy_props/2 ]). @@ -266,6 +271,31 @@ get_security(#st{header = Header} = St) -> end. +get_props(#st{header = Header} = St) -> + case couch_bt_engine_header:get(Header, props_ptr) of + undefined -> + []; + Pointer -> + {ok, Props} = couch_file:pread_term(St#st.fd, Pointer), + Props + end. + + +get_prop(St, Key) -> + Props = get_props(St), + case lists:keyfind(Key, 1, Props) of + false -> {error, no_value}; + {Key, Value} -> {ok, Value} + end. + + +get_prop(St, Key, DefaultValue) -> + case get_prop(St, Key) of + {error, no_value} -> DefaultValue; + Value -> Value + end. + + get_update_seq(#st{header = Header}) -> couch_bt_engine_header:get(Header, update_seq). @@ -296,6 +326,20 @@ set_security(#st{header = Header} = St, NewSecurity) -> {ok, increment_update_seq(NewSt)}. +set_prop(#st{header = Header} = St, Key, Value) -> + OldProps = get_props(St), + NewProps = lists:ukeymerge(1, [{Key, Value}], OldProps), + Options = [{compression, St#st.compression}], + {ok, Ptr, _} = couch_file:append_term(St#st.fd, NewProps, Options), + NewSt = St#st{ + header = couch_bt_engine_header:set(Header, [ + {props_ptr, Ptr} + ]), + needs_commit = true + }, + {ok, increment_update_seq(NewSt)}. + + open_docs(#st{} = St, DocIds) -> Results = couch_btree:lookup(St#st.id_tree, DocIds), lists:map(fun @@ -646,6 +690,16 @@ copy_security(#st{header = Header} = St, SecProps) -> needs_commit = true }}. +copy_props(#st{header = Header} = St, Props) -> + Options = [{compression, St#st.compression}], + {ok, Ptr, _} = couch_file:append_term(St#st.fd, Props, Options), + {ok, St#st{ + header = couch_bt_engine_header:set(Header, [ + {props_ptr, Ptr} + ]), + needs_commit = true + }}. + open_db_file(FilePath, Options) -> case couch_file:open(FilePath, Options) of @@ -682,7 +736,8 @@ init_state(FilePath, Fd, Header0, Options) -> Compression = couch_compress:get_compression_method(), Header1 = couch_bt_engine_header:upgrade(Header0), - Header = set_default_security_object(Fd, Header1, Compression, Options), + Header2 = set_default_security_object(Fd, Header1, Compression, Options), + Header = set_initial_props(Fd, Header2, Compression, Options), IdTreeState = couch_bt_engine_header:id_tree_state(Header), {ok, IdTree} = couch_btree:open(IdTreeState, Fd, [ @@ -763,6 +818,18 @@ set_default_security_object(Fd, Header, Compression, Options) -> end. +set_initial_props(Fd, Header, Compression, Options) -> + case couch_bt_engine_header:get(Header, props_ptr) of + Pointer when is_integer(Pointer) -> + Header; + _ -> + Default = couch_util:get_value(initial_props, Options), + AppendOpts = [{compression, Compression}], + {ok, Ptr, _} = couch_file:append_term(Fd, Default, AppendOpts), + couch_bt_engine_header:set(Header, props_ptr, Ptr) + end. + + delete_compaction_files(FilePath) -> RootDir = config:get("couchdb", "database_dir", "."), DelOpts = [{context, compaction}], diff --git a/src/couch/src/couch_bt_engine_compactor.erl b/src/couch/src/couch_bt_engine_compactor.erl index 4e52064b4..83c9bbc84 100644 --- a/src/couch/src/couch_bt_engine_compactor.erl +++ b/src/couch/src/couch_bt_engine_compactor.erl @@ -186,9 +186,13 @@ copy_compact(DbName, St, NewSt0, Retry) -> SecProps = couch_bt_engine:get_security(St), {ok, NewSt4} = couch_bt_engine:copy_security(NewSt3, SecProps), + % Copy general properties over + Props = couch_bt_engine:get_props(St), + {ok, NewSt5} = couch_bt_engine:copy_props(NewSt4, Props), + FinalUpdateSeq = couch_bt_engine:get_update_seq(St), - {ok, NewSt5} = couch_bt_engine:set_update_seq(NewSt4, FinalUpdateSeq), - commit_compaction_data(NewSt5). + {ok, NewSt6} = couch_bt_engine:set_update_seq(NewSt5, FinalUpdateSeq), + commit_compaction_data(NewSt6). copy_docs(St, #st{} = NewSt, MixedInfos, Retry) -> diff --git a/src/couch/src/couch_bt_engine_header.erl b/src/couch/src/couch_bt_engine_header.erl index 3d24f3189..779bf26cf 100644 --- a/src/couch/src/couch_bt_engine_header.erl +++ b/src/couch/src/couch_bt_engine_header.erl @@ -66,7 +66,8 @@ revs_limit = 1000, uuid, epochs, - compacted_seq + compacted_seq, + props_ptr }). diff --git a/src/couch/src/couch_db.erl b/src/couch/src/couch_db.erl index 65ca54a59..9bb68f878 100644 --- a/src/couch/src/couch_db.erl +++ b/src/couch/src/couch_db.erl @@ -47,6 +47,7 @@ get_pid/1, get_revs_limit/1, get_security/1, + get_props/1, get_update_seq/1, get_user_ctx/1, get_uuid/1, @@ -58,6 +59,7 @@ set_revs_limit/2, set_security/2, + set_prop/3, set_user_ctx/2, ensure_full_commit/1, @@ -578,6 +580,15 @@ set_security(#db{main_pid=Pid}=Db, {NewSecProps}) when is_list(NewSecProps) -> set_security(_, _) -> throw(bad_request). +get_props(#db{props=Props}) -> + Props. + +set_prop(#db{main_pid=Pid}=Db, Key, Value) -> + check_is_admin(Db), + ok = gen_server:call(Pid, {set_prop, Key, Value}, infinity), + {ok, _} = ensure_full_commit(Db), + ok. + set_user_ctx(#db{} = Db, UserCtx) -> {ok, Db#db{user_ctx = UserCtx}}. diff --git a/src/couch/src/couch_db_engine.erl b/src/couch/src/couch_db_engine.erl index 502faa7ee..9b288127a 100644 --- a/src/couch/src/couch_db_engine.erl +++ b/src/couch/src/couch_db_engine.erl @@ -224,6 +224,19 @@ % the last value that was passed to set_security/2. -callback get_security(DbHandle::db_handle()) -> SecProps::any(). +% Get the current properties. +-callback get_props(DbHandle::db_handle()) -> Props::any(). + +% Get the current properties. This should just return +% the last value that was passed to set_prop/2. +-callback get_prop(DbHandle::db_handle(), Prop::atom()) -> + {ok, SecProps::json()} | {error, no_value}. + +% Get the current properties. If the value isn't set it will return the set default value. +% This should just return the last value that was passed to set_prop/2. +-callback get_prop(DbHandle::db_handle(), Prop::atom(), DefaultValue::any()) -> + {ok, SecProps::json()}. + % This information is displayed in the database info poperties. It % should just be a list of {Name::atom(), Size::non_neg_integer()} @@ -265,6 +278,15 @@ {ok, NewDbHandle::db_handle()}. +% This function is only called by couch_db_updater and +% as such is guaranteed to be single threaded calls. The +% database should simply store prop key and value somewhere so +% they can be returned by the corresponding get_prop calls. + +-callback set_prop(DbHandle::db_handle(), PropKey::atom(), PropValue::any()) -> + {ok, NewDbHandle::db_handle()}. + + % This function will be called by many processes concurrently. % It should return a #full_doc_info{} record or not_found for % every provided DocId in the order those DocId's appear in @@ -601,12 +623,16 @@ get_purge_seq/1, get_revs_limit/1, get_security/1, + get_props/1, + get_prop/2, + get_prop/3, get_size_info/1, get_update_seq/1, get_uuid/1, set_revs_limit/2, set_security/2, + set_prop/3, open_docs/2, open_local_docs/2, @@ -757,6 +783,21 @@ get_security(#db{} = Db) -> Engine:get_security(EngineState). +get_props(#db{} = Db) -> + #db{engine = {Engine, EngineState}} = Db, + Engine:get_props(EngineState). + + +get_prop(#db{} = Db, Prop) -> + #db{engine = {Engine, EngineState}} = Db, + Engine:get_prop(EngineState, Prop). + + +get_prop(#db{} = Db, Prop, DefaultValue) -> + #db{engine = {Engine, EngineState}} = Db, + Engine:get_prop(EngineState, Prop, DefaultValue). + + get_size_info(#db{} = Db) -> #db{engine = {Engine, EngineState}} = Db, Engine:get_size_info(EngineState). @@ -783,6 +824,12 @@ set_security(#db{} = Db, SecProps) -> {ok, Db#db{engine = {Engine, NewSt}}}. +set_prop(#db{} = Db, Key, Value) -> + #db{engine = {Engine, EngineState}} = Db, + {ok, NewSt} = Engine:set_prop(EngineState, Key, Value), + {ok, Db#db{engine = {Engine, NewSt}}}. + + open_docs(#db{} = Db, DocIds) -> #db{engine = {Engine, EngineState}} = Db, Engine:open_docs(EngineState, DocIds). diff --git a/src/couch/src/couch_db_int.hrl b/src/couch/src/couch_db_int.hrl index a412b338b..f2a8e17f5 100644 --- a/src/couch/src/couch_db_int.hrl +++ b/src/couch/src/couch_db_int.hrl @@ -35,7 +35,8 @@ waiting_delayed_commit = nil, options = [], - compression + compression, + props = [] }). diff --git a/src/couch/src/couch_db_updater.erl b/src/couch/src/couch_db_updater.erl index acb9ec1c9..d8a769b7f 100644 --- a/src/couch/src/couch_db_updater.erl +++ b/src/couch/src/couch_db_updater.erl @@ -88,6 +88,14 @@ handle_call({set_security, NewSec}, _From, #db{} = Db) -> ok = gen_server:call(couch_server, {db_updated, NewSecDb}, infinity), {reply, ok, NewSecDb, idle_limit()}; +handle_call({set_prop, Key, Value}, _From, #db{} = Db) -> + {ok, NewDb} = couch_db_engine:set_prop(Db, Key, Value), + NewPropDb = commit_data(NewDb#db{ + props = couch_db_engine:get_props(NewDb) + }), + ok = gen_server:call(couch_server, {db_updated, NewPropDb}, infinity), + {reply, ok, NewPropDb, idle_limit()}; + handle_call({set_revs_limit, Limit}, _From, Db) -> {ok, Db2} = couch_db_engine:set_revs_limit(Db, Limit), Db3 = commit_data(Db2), @@ -375,7 +383,8 @@ init_db(DbName, FilePath, EngineState, Options) -> InitDb#db{ committed_update_seq = couch_db_engine:get_update_seq(InitDb), - security = couch_db_engine:get_security(InitDb) + security = couch_db_engine:get_security(InitDb), + props = couch_db_engine:get_props(InitDb) }. diff --git a/src/couch/src/test_engine_get_set_props.erl b/src/couch/src/test_engine_get_set_props.erl index 6d2a44779..b0fbff450 100644 --- a/src/couch/src/test_engine_get_set_props.erl +++ b/src/couch/src/test_engine_get_set_props.erl @@ -50,6 +50,28 @@ cet_set_revs_limit() -> check_prop_set(get_revs_limit, set_revs_limit, 1000, 50). +cet_set_prop() -> + Engine = test_engine_util:get_engine(), + DbPath = test_engine_util:dbpath(), + + {ok, St0} = Engine:init(DbPath, [ + create, + {default_security_object, dso} + ]), + ?assertEqual({error, no_value}, Engine:get_prop(St0, shardkey)), + + ?assertEqual(false, Engine:get_prop(St0, shardkey, false)), + + {ok, St1} = Engine:set_prop(St0, shardkey, true), + ?assertEqual({ok, true}, Engine:get_prop(St1, shardkey)), + + {ok, St2} = Engine:commit_data(St1), + Engine:terminate(normal, St2), + + {ok, St3} = Engine:init(DbPath, []), + ?assertEqual({ok, true}, Engine:get_prop(St3, shardkey)). + + check_prop_set(GetFun, SetFun, Default, Value) -> Engine = test_engine_util:get_engine(), DbPath = test_engine_util:dbpath(), -- cgit v1.2.1