/
boss_db_controller.erl
187 lines (155 loc) · 7.03 KB
/
boss_db_controller.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
-module(boss_db_controller).
-behaviour(gen_server).
-export([start_link/0, start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {
adapter,
connection,
shards = [],
model_dict = dict:new(),
cache_enable,
cache_ttl,
cache_prefix,
depth = 0}).
start_link() ->
start_link([]).
start_link(Args) ->
gen_server:start_link(?MODULE, Args, []).
init(Options) ->
AdapterName = proplists:get_value(adapter, Options, mock),
Adapter = list_to_atom(lists:concat(["boss_db_adapter_", AdapterName])),
CacheEnable = proplists:get_value(cache_enable, Options, false),
CacheTTL = proplists:get_value(cache_exp_time, Options, 60),
process_flag(trap_exit, true),
{ok, Conn} = Adapter:init(Options),
{Shards, ModelDict} = lists:foldr(fun(ShardOptions, {ShardAcc, ModelDictAcc}) ->
case proplists:get_value(db_shard_models, ShardOptions, []) of
[] ->
{ShardAcc, ModelDictAcc};
Models ->
ShardAdapter = case proplists:get_value(db_adapter, ShardOptions) of
undefined -> Adapter;
ShortName -> list_to_atom(lists:concat(["boss_db_adapter_", ShortName]))
end,
{ok, ShardConn} = ShardAdapter:start(ShardOptions ++ Options),
Index = erlang:length(ShardAcc),
NewDict = lists:foldr(fun(ModelAtom, Dict) ->
dict:store(ModelAtom, Index, Dict)
end, ModelDictAcc, Models),
{[{ShardAdapter, ShardConn}|ShardAcc], NewDict}
end
end, {[], dict:new()}, proplists:get_value(shards, Options, [])),
{ok, #state{adapter = Adapter, connection = Conn, shards = lists:reverse(Shards), model_dict = ModelDict,
cache_enable = CacheEnable, cache_ttl = CacheTTL, cache_prefix = db }}.
handle_call({find, Key}, From, #state{ cache_enable = true, cache_prefix = Prefix } = State) ->
case boss_cache:get(Prefix, Key) of
undefined ->
{reply, Res, _} = handle_call({find, Key}, From, State#state{ cache_enable = false }),
boss_cache:set(Prefix, Key, Res, State#state.cache_ttl),
boss_news:set_watch(Key, lists:concat([Key, ", ", Key, ".*"]), fun boss_db_cache:handle_record_news/3, {Prefix, Key}, State#state.cache_ttl),
{reply, Res, State};
CachedValue ->
boss_news:extend_watch(Key),
{reply, CachedValue, State}
end;
handle_call({find, Key}, _From, #state{ cache_enable = false } = State) ->
{Adapter, Conn} = db_for_key(Key, State),
{reply, Adapter:find(Conn, Key), State};
handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder} = Cmd, From,
#state{ cache_enable = true, cache_prefix = Prefix } = State) ->
Key = {Type, Conditions, Max, Skip, Sort, SortOrder},
case boss_cache:get(Prefix, Key) of
undefined ->
{reply, Res, _} = handle_call(Cmd, From, State#state{ cache_enable = false }),
boss_cache:set(Prefix, Key, Res, State#state.cache_ttl),
boss_news:set_watch(Key, lists:concat([inflector:pluralize(atom_to_list(Type)), ", ", Type, "-*.*"]),
fun boss_db_cache:handle_collection_news/3, {Prefix, Key}, State#state.cache_ttl),
{reply, Res, State};
CachedValue ->
boss_news:extend_watch(Key),
{reply, CachedValue, State}
end;
handle_call({find, Type, Conditions, Max, Skip, Sort, SortOrder}, _From, #state{ cache_enable = false } = State) ->
{Adapter, Conn} = db_for_type(Type, State),
{reply, Adapter:find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder), State};
handle_call({count, Type}, _From, State) ->
{Adapter, Conn} = db_for_type(Type, State),
{reply, Adapter:count(Conn, Type), State};
handle_call({count, Type, Conditions}, _From, State) ->
{Adapter, Conn} = db_for_type(Type, State),
{reply, Adapter:count(Conn, Type, Conditions), State};
handle_call({counter, Counter}, _From, State) ->
{Adapter, Conn} = db_for_counter(Counter, State),
{reply, Adapter:counter(Conn, Counter), State};
handle_call({incr, Key}, _From, State) ->
{Adapter, Conn} = db_for_counter(Key, State),
{reply, Adapter:incr(Conn, Key), State};
handle_call({incr, Key, Count}, _From, State) ->
{Adapter, Conn} = db_for_counter(Key, State),
{reply, Adapter:incr(Conn, Key, Count), State};
handle_call({delete, Id}, _From, State) ->
{Adapter, Conn} = db_for_key(Id, State),
{reply, Adapter:delete(Conn, Id), State};
handle_call({save_record, Record}, _From, State) ->
{Adapter, Conn} = db_for_record(Record, State),
{reply, Adapter:save_record(Conn, Record), State};
handle_call(push, _From, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
Depth = State#state.depth,
{reply, Adapter:push(Conn, Depth), State#state{depth = Depth + 1}};
handle_call(pop, _From, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
Depth = State#state.depth,
{reply, Adapter:pop(Conn, Depth), State#state{depth = Depth - 1}};
handle_call(depth, _From, State) ->
{reply, State#state.depth, State};
handle_call(dump, _From, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
{reply, Adapter:dump(Conn), State};
handle_call({execute, Commands}, _From, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
{reply, Adapter:execute(Conn, Commands), State};
handle_call({transaction, TransactionFun}, _From, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
{reply, Adapter:transaction(Conn, TransactionFun), State};
handle_call(state, _From, State) ->
{reply, State, State}.
handle_cast(_Request, State) ->
{noreply, State}.
terminate(_Reason, State) ->
Adapter = State#state.adapter,
Conn = State#state.connection,
Adapter:terminate(Conn),
lists:map(fun({A, C}) ->
A:stop(C)
end, State#state.shards).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_info(stop, State) ->
{stop, shutdown, State};
handle_info({'EXIT', _, _}, State) ->
{stop, shutdown, State};
handle_info(_Info, State) ->
{noreply, State}.
db_for_counter(_Counter, State) ->
{State#state.adapter, State#state.connection}.
db_for_record(Record, State) ->
db_for_type(element(1, Record), State).
db_for_key(Key, State) ->
db_for_type(infer_type_from_id(Key), State).
db_for_type(Type, State) ->
case dict:find(Type, State#state.model_dict) of
{ok, Index} ->
lists:nth(Index + 1, State#state.shards);
_ ->
{State#state.adapter, State#state.connection}
end.
infer_type_from_id(Id) when is_binary(Id) ->
infer_type_from_id(binary_to_list(Id));
infer_type_from_id(Id) when is_list(Id) ->
list_to_atom(hd(string:tokens(Id, "-"))).