Packages

A convenience layer for SQL databases

Current section

Files

Jump to
sql_bridge src sql_bridge.erl
Raw

src/sql_bridge.erl

%% vim: ts=4 sw=4 et
-module(sql_bridge).
-compile(nowarn_export_all).
-compile(export_all).
-include("compat.hrl").
-define(WARNING(QueryText,Msg), error_logger:info_msg("QUERY WARNING: ~p~n~nQuery:~n~p~n~n",[Msg,QueryText])).
-define(ENV(Var, Def), (sql_bridge_utils:get_env(Var, Def))).
-define(ALIAS, ?ENV(module_alias, db)).
-define(STRINGIFY, ?ENV(stringify_binaries, false)).
-define(ADAPTER,?ENV(adapter, sql_bridge_mysql)).
-define(HOST, ?ENV(host, "127.0.0.1")).
-define(PORT, ?ENV(port, undefined)).
-define(USER, ?ENV(user, "root")).
-define(PASS, ?ENV(pass, "")).
-define(LOOKUP, ?ENV(lookup, fun() -> throw({sql_bridge,undefined_lookup_method}) end )).
-define(CONNECTION_ATTEMPTS, ?ENV(connection_attempts, 5)).
-define(DB, sql_bridge_cached_db).
-type sql() :: iolist().
-type db() :: atom().
-type table() :: string() | atom().
-type field() :: string() | atom().
-type value() :: string() | binary() | atom() | integer() | float().
-type insert_id() :: term().
-type affected_rows() :: integer().
-type proplist() :: [{atom(), value()}].
%-type json() :: list().
-type return_type() :: dict | list | proplist | tuple | insert | update.
-type return_value() :: insert_id() | affected_rows()
| [list() | tuple() | t_dict() | proplist()].
-ifdef(has_maps).
-type proplist_or_map() :: tuple() | proplist() | map().
-else.
-type proplist_or_map() :: tuple() | proplist().
-endif.
-export_type([
sql/0,
db/0,
table/0,
field/0,
value/0,
insert_id/0,
affected_rows/0,
proplist/0,
return_type/0,
return_value/0
]).
%% New API aliases
lists(Q) -> q(Q).
lists(Q, P) -> q(Q, P).
list(Q) -> fr(Q).
list(Q, P) -> fr(Q, P).
tuples(Q) -> tq(Q).
tuples(Q, P) -> tq(Q,P).
tuple(Q) -> tfr(Q).
tuple(Q, P) -> tfr(Q, P).
proplists(Q) -> plq(Q).
proplists(Q, P) -> plq(Q, P).
proplist(Q) -> plfr(Q).
proplist(Q, P) -> plfr(Q, P).
maps(Q) -> mq(Q).
maps(Q, P) -> mq(Q, P).
map(Q) -> mfr(Q).
map(Q, P) -> mfr(Q, P).
dicts(Q) -> dq(Q).
dicts(Q, P) -> dq(Q, P).
dict(Q) -> dfr(Q).
dict(Q, P) -> dfr(Q, P).
qupdate(Q) -> qu(Q).
qupdate(Q, P) -> qu(Q, P).
qinsert(Q) -> qi(Q).
qinsert(Q, P) -> qi(Q, P).
update(Table, Obj) -> plu(Table, Obj).
update(Table, KeyField, Obj) -> plu(Table, KeyField, Obj).
insert(Table, Obj) -> pli(Table, Obj).
-spec lookup() -> db().
% @doc Checks the configuration for how we determine the database we're using
% and returns the database.
lookup() ->
case ?LOOKUP of
A when is_atom(A) -> A;
F when is_function(F, 0) -> F();
{M, F} when is_atom(M), is_atom(F) -> M:F()
end.
-spec db() -> db().
% @doc Checks the process dictionary for an active database connection
% associated with this process. If none, then look it up from configuration and
% store it in the process dictionary. Note, this function does not actually
% establish a connection, only returns the name of the database that either is
% associated with the process or that *should* be associated with the process
% based on some criteria (for example, checking host headers)
db() ->
case erlang:get(?DB) of
undefined ->
DB = lookup(),
db(DB);
DB ->
DB
end.
enable_logging() ->
error_logger:info_msg("SQL Bridge Started Logging"),
application:set_env(sql_bridge, logging, true).
disable_logging() ->
application:set_env(sql_bridge, logging, false),
error_logger:info_msg("SQL Bridge Stopped Logging").
log(Time, DB, SQL, Params) ->
Pid = self(),
Output = io_lib:format("===== ~p (~p): ~p us: ~ts || Params: ~p~n",[Pid, DB, Time, SQL, Params]),
file:write_file("sql_bridge.log", Output, [append, delayed_write]).
log_for_time(Secs) ->
spawn(fun() ->
enable_logging(),
timer:sleep(Secs*1000),
disable_logging()
end).
-spec db(db()) -> db().
% @doc Stores the database name in the process dictionary
db(DB) ->
erlang:put(?DB,DB),
DB.
-spec start() -> ok.
% @doc starts the actual database driver, if necessary
start() ->
application:start(sql_bridge),
ok = sql_bridge_alias:build_stringify(?STRINGIFY),
ok = sql_bridge_alias:build(?ALIAS),
ok = ?ADAPTER:start(),
ok.
-spec connect() -> db().
% @doc establishes a connection to the appropriate database.
connect() ->
connect(db()).
-spec connect(db()) -> db().
% @doc establishes a connection to the named database.
connect(DB) when is_atom(DB) ->
ok = ?ADAPTER:connect(DB, ?USER, ?PASS, ?HOST, ?PORT),
DB.
-spec pl(Table :: table(), Data :: proplist_or_map()) -> insert_id().
pl(Table, Data) ->
save(Table, Data).
-spec pl(Table :: table(), KeyField :: field(), Data :: proplist_or_map()) -> insert_id().
pl(Table, KeyField, Data) ->
save(Table, KeyField, Data).
-spec save(Table :: table(), Data :: proplist_or_map()) -> insert_id().
save(Table, Data0) ->
Data = ensure_proplist(Data0),
save_(Table, Data).
-spec save(Table :: table(), KeyField :: field(), Data0 :: proplist_or_map()) -> insert_id().
save(Table, KeyField, Data0) ->
Data = ensure_proplist(Data0),
save_(Table, KeyField, Data).
-spec save_record(Table :: table(), Record :: tuple(), FieldMap :: [atom()]) -> insert_id().
save_record(Table, Record, FieldMap) ->
PL = sql_bridge_utils:record_to_proplist(Record, FieldMap),
save(Table, PL).
-spec save_record(Table :: table(), KeyField :: field(), Record :: tuple(), FieldMap :: [atom()]) -> insert_id().
save_record(Table, KeyField, Record, FieldMap) ->
PL = sql_bridge_utils:record_to_proplist(Record, FieldMap),
save(Table, KeyField, PL).
save_(Table,PropList) when is_atom(Table) ->
save_(atom_to_list(Table),PropList);
save_(Table,PropList) when is_list(Table) ->
KeyField = Table ++ "id",
save_(Table,KeyField,PropList).
save_(Table,KeyField,PropList) when is_atom(Table) ->
save_(atom_to_list(Table),KeyField,PropList);
save_(Table,KeyField,PropList) when is_list(KeyField) ->
save_(Table,list_to_atom(KeyField),PropList);
save_(Table,KeyField,PropList) when is_list(Table) ->
KeyValue = proplists:get_value(KeyField,PropList,0),
case KeyValue of
Zero when Zero == 0;
Zero == "0";
Zero == undefined;
Zero == null;
Zero == "";
Zero == <<>> ->
pli(Table,lists:keydelete(KeyField,1,PropList));
_ ->
plu(Table,KeyField,PropList)
end.
-ifdef(has_maps).
ensure_proplist(Record) when is_atom(element(1, Record)) ->
ensure_proplist(sql_bridge_utils:convert_record(Record));
ensure_proplist(Map) when is_map(Map) ->
maps:to_list(Map);
ensure_proplist(PL) when is_list(PL) ->
PL.
-else.
ensure_proplist(Record) when is_atom(element(1, Record)) ->
ensure_proplist(sql_bridge_utils:convert_record(Record));
ensure_proplist(PL) when is_list(PL) ->
PL.
-endif.
-spec filter_fields(Table :: table(), PropList :: proplist()) -> proplist().
% @doc removes from Proplist any fields that aren't found in the table "Table"
filter_fields(Table,PropList) ->
TableFields = table_fields(Table),
[{K,V} || {K,V} <- PropList,lists:member(sql_bridge_utils:to_atom(K),TableFields)].
-spec trans(Fun :: fun()) -> ok.
trans(Fun) when is_function(Fun) ->
?ADAPTER:with_transaction(db(), Fun).
-spec start_trans() -> ok.
start_trans() ->
?ADAPTER:start_transaction(db()).
-spec commit() -> ok.
commit() ->
?ADAPTER:commit_transaction(db()).
rollback() ->
?ADAPTER:rollback_transaction(db()).
-spec q(Q :: sql()) -> [list()].
%% @doc Run the SQL query `Q` and return a list of lists, with each inner list
%% representing one record in the return set.
q(Q) ->
Db = db(),
db_q(list,Db,Q).
-spec q(Q :: sql(), ParamList :: [value()]) -> [list()].
%% @doc Run the SQL query `Q`, with the the values of `ParamList` safely
%% injected into the query replacing any instances of question marks (`?`),
%% with each inner list representing one record in the return set (same as q/1).
q(Q,ParamList) ->
Db = db(),
db_q(list,Db,Q,ParamList).
-spec dq(Q :: sql()) -> [t_dict()].
%% @doc Same as q/1, but returns a list of dicts.
dq(Q) ->
Db = db(),
db_q(dict,Db,Q).
-spec dq(Q :: sql(), ParamList :: [value()]) -> [t_dict()].
%% @doc Same as q/2, but returns a list of dicts
dq(Q,ParamList) ->
Db = db(),
db_q(dict,Db,Q,ParamList).
-ifdef(has_maps).
-spec mq(Q :: sql()) -> [map()].
%% @doc Same as d1/, but returns a list of maps
mq(Q) ->
Db = db(),
db_q(map, Db, Q).
-spec mq(Q :: sql(), ParamList :: [value()]) -> [map()].
%% @doc Same as d1/, but returns a list of maps
mq(Q, ParamList) ->
Db = db(),
db_q(map, Db, Q, ParamList).
-endif.
-spec tq(Q :: sql()) -> [tuple()].
%% @doc Same as q/1, but returns a list of tuples
tq(Q) ->
Db = db(),
db_q(tuple,Db,Q).
-spec tq(Q :: sql(), ParamList :: [value()]) -> [tuple()].
%% @doc Same as q/2, but returns a list of tuples
tq(Q,ParamList) ->
Db = db(),
db_q(tuple,Db,Q,ParamList).
-spec plq(Q :: sql()) -> [proplist()].
%% @doc Same as q/1, but returns a list of proplists
plq(Q) ->
Db = db(), db_q(proplist,Db,Q).
-spec plq(Q :: sql(), ParamList :: [value()]) -> [proplist()].
%% @doc Same as q/2, but returns a list of proplists
plq(Q,ParamList) ->
Db = db(),
db_q(proplist,Db,Q,ParamList).
-spec pli(Table :: table(), Data :: proplist_or_map()) -> insert_id().
%% @doc Inserts a proplist into the table
pli(Table,PropList) when is_atom(Table) ->
pli(atom_to_list(Table),PropList);
pli(Table,InitPropList0) ->
InitPropList = ensure_proplist(InitPropList0),
PropList = filter_fields(Table,InitPropList),
Fields0 = [wrap_field(F) || {F,_} <- PropList],
Fields = iolist_join(Fields0, ","),
Values = [V || {_,V} <- PropList],
Placeholders = sql_bridge_utils:create_placeholders(length(Values)),
PlaceholderString = iolist_join(Placeholders, ","),
SQL = ["insert into ",Table,"(",Fields,") values(",PlaceholderString,");"],
qi(SQL, Values).
-spec plu(Table :: table(), PropList :: proplist_or_map()) -> affected_rows().
%% @doc Updates a row from the proplist based on the key `Table ++ "id"` in the Table
plu(Table,PropList) when is_atom(Table) ->
plu(atom_to_list(Table),PropList);
plu(Table,PropList0) ->
PropList = ensure_proplist(PropList0),
KeyField = list_to_atom(Table ++ "id"),
plu(Table,KeyField,PropList).
-spec plu(Table :: table(), KeyField :: field(), PropList :: proplist()) -> affected_rows().
%% @doc Update a row from proplist based on the Keyfield `Keyfield` on provided Table
plu(Table,KeyField,InitPropList) when is_atom(Table) ->
plu(atom_to_list(Table),KeyField,InitPropList);
plu(Table,KeyField,InitPropList) ->
PropList = filter_fields(Table,InitPropList),
SetFields = [wrap_field(F) || {F, _} <- PropList, F =/= KeyField],
SetPlaceholders = sql_bridge_utils:create_placeholders(length(SetFields)),
Sets = [ [F,"=",PH] || {F, PH} <- lists:zip(SetFields, SetPlaceholders) ],
Set = iolist_join(Sets,","),
SetValues = [V || {F, V} <- PropList, F =/= KeyField],
KeyValue = proplists:get_value(KeyField,PropList),
KeyPlaceholder = sql_bridge_utils:create_placeholder(length(SetFields)+1),
Params = SetValues ++ [KeyValue],
SQL = ["update ",Table," set ",Set," where ",atom_to_list(KeyField),"=",KeyPlaceholder],
q(SQL, Params),
KeyValue.
-spec db_q(Type :: return_type(), Db :: db(), Q :: sql()) -> insert_id()
| affected_rows()
| [list() | t_dict() | tuple() | proplist()].
%% @doc Query from the specified Database pool (Db) This will connect to the
%% specified Database Pool Type must be atoms: proplist, dict, list, or tuple
%% Type can also be atom 'insert' in which case, it'll return the insert value
db_q(Type,Db,Q) ->
db_q(Type, Db, Q, []).
-spec db_q(Type :: return_type(), Db :: db(),
Q :: sql(), ParamList :: [value()]) -> insert_id()
| affected_rows()
| [list() | t_dict() | tuple() | proplist()].
%% @doc Same as db_q/3, but ParamList is safely inserted into the Query
db_q(Type,Db,Q,ParamList) ->
ParamList2 = sanitize_params(ParamList),
db_q(Type, Db, Q, ParamList2, ?CONNECTION_ATTEMPTS).
db_q(_Type, Db, _Q, _ParamList, _RemainingAttempts=0) ->
error_logger:error_msg("Unable to connect to pool '~p' after ~p attempts", [Db, ?CONNECTION_ATTEMPTS]),
throw({error, unable_to_connect_to_pool, Db});
db_q(Type, Db, Q, ParamList, RemainingAttempts) ->
{Time, Return} = timer:tc(fun() ->
case ?ADAPTER:query(Type, Db, Q, ParamList) of
{ok, Response} ->
Response;
{error, no_pool} ->
connect(Db),
db_q(Type, Db, Q, ParamList, RemainingAttempts-1);
{error, disconnected} ->
error_logger:warning_msg("WARN: Disconnected worker in pool: ~p~n",[Db]),
db_q(Type, Db, Q, ParamList, RemainingAttempts-1);
{error, Other} ->
error_logger:error_msg("Error in SQL Statement or Adapter:~nDB: ~p~nSQL: ~p~nParams: ~p~nError Message: ~p",[Db, Q, ParamList, Other]),
exit(unknown_error)
end
end),
case application:get_env(sql_bridge, logging, false) of
false -> ok;
true -> log(Time, Db, Q, ParamList)
end,
Return.
-spec qi(Q :: sql()) -> insert_id().
%% @doc A special Query function just for inserting.
%% Inserts the record(s) and returns the insert_id
qi(Q) ->
Db = db(),
db_q(insert,Db,Q).
-spec qi(Q :: sql(), ParamList :: [value()]) -> insert_id().
qi(Q,ParamList) ->
Db = db(),
db_q(insert,Db,Q,ParamList).
-spec qu(Q :: sql()) -> affected_rows().
qu(Q) ->
Db = db(),
db_q(update, Db, Q).
-spec qu(Q :: sql(), ParamList :: [value()]) -> affected_rows().
qu(Q, ParamList) ->
Db = db(),
db_q(update, Db, Q, ParamList).
-spec plfr(Q :: sql(), ParamList :: [value()]) -> proplist() | not_found.
%% @doc fr = First Record
plfr(Q,ParamList) ->
case plq(Q,ParamList) of
[] -> not_found;
[First|_] -> First
end.
-spec plfr(Q :: sql()) -> proplist() | not_found.
plfr(Q) ->
plfr(Q,[]).
-ifdef(has_maps).
-spec mfr(Q :: sql(), ParamList :: [value()]) -> map() | not_found.
%% @doc fr = First Record
mfr(Q,ParamList) ->
case mq(Q,ParamList) of
[] -> not_found;
[First|_] -> First
end.
-spec mfr(Q :: sql()) -> map() | not_found.
mfr(Q) ->
mfr(Q,[]).
-endif.
-spec tfr(Q :: sql()) -> tuple() | not_found.
tfr(Q) ->
tfr(Q,[]).
-spec tfr(Q :: sql(), ParamList :: [value()]) -> tuple() | not_found.
tfr(Q,ParamList) ->
case tq(Q,ParamList) of
[] -> not_found;
[First|_] -> First
end.
-spec dfr(Q :: sql()) -> t_dict() | not_found.
dfr(Q) ->
dfr(Q, []).
-spec dfr(Q :: sql(), ParamList :: [value()]) -> t_dict() | not_found.
dfr(Q, ParamList) ->
case dq(Q, ParamList) of
[] -> not_found;
[First|_] -> First
end.
%% fr = First Record
-spec fr(Q :: sql(), ParamList :: [value()]) -> list() | not_found.
fr(Q,ParamList) ->
case q(Q,ParamList) of
[] -> not_found;
[First|_] -> First
end.
-spec fr(Q :: sql()) -> list() | not_found.
fr(Q) ->
fr(Q,[]).
-spec fffr(Q :: sql(), ParamList :: [value()]) -> string() | integer() | not_found.
%% @doc Get First Field of First record
fffr(Q,ParamList) ->
case fr(Q,ParamList) of
not_found -> not_found;
[First|_] -> First
end.
-spec fffr(Q :: sql()) -> string() | integer() | not_found.
fffr(Q) ->
fffr(Q,[]).
%% First Field List
-spec ffl(Q :: sql(), ParamList :: [value()]) -> [string() | integer()].
ffl(Q,ParamList) ->
[First || [First | _ ] <- q(Q,ParamList)].
-spec ffl(Q :: sql()) -> [string() | integer()].
ffl(Q) ->
ffl(Q,[]).
-spec table_fields(Table :: table()) -> [atom()].
%% deprecate this. Use "fields"
table_fields(Table) when is_atom(Table) ->
table_fields(atom_to_list(Table));
table_fields(Table0) ->
{DB, Table} = table_and_db(Table0),
[T1, T2] = sql_bridge_utils:create_placeholders(2),
DBCol = ?ADAPTER:schema_db_column(),
SQL = [<<"select column_name
from information_schema.columns
where ">>,DBCol,<<"=">>,T1,<<" and table_name=">>,T2],
[sql_bridge_utils:to_atom(F) || F <- ffl(SQL, [DB, Table])].
table_and_db(Table) ->
case string:tokens(Table, ".") of
[DB, TableOnly] ->
{DB, TableOnly};
[TableOnly] ->
{db(), TableOnly}
end.
-spec fields(Table :: table()) -> [atom()].
fields(Table) ->
table_fields(Table).
-spec qexists(Q :: sql()) -> boolean().
%% @doc Existance query, just returns true if the query Q returns anything
%% other than an empty set.
%% TODO: Check for "limit" clause and add? Or rely on user.
qexists(Q) ->
qexists(Q,[]).
-spec qexists(Q :: sql(), ParamList :: [value()]) -> boolean().
qexists(Q,ParamList) ->
case q(Q,ParamList) of
[] -> false;
[_] -> true;
[_|_] ->
?WARNING({Q,ParamList},"qexists returned more than one record. Recommend returning one record for performance."),
true
end.
-spec exists(Table :: table(), IDValue :: value()) -> boolean().
%% @doc Returns true if Table has a record representing Key Value IDValue
exists(Table, IDValue) when is_atom(Table) ->
exists(atom_to_list(Table), IDValue);
exists(Table, IDValue) when is_list(Table) ->
exists(Table, Table ++ "id", IDValue).
-spec exists(Table :: table(), KeyField :: field(), IDValue :: value()) -> boolean().
%% @doc Returns true if Table has a record where KeyField = IDValue
exists(Table, KeyField, IDValue) ->
case field(Table, KeyField, KeyField, IDValue) of
not_found -> false;
_ -> true
end.
-spec field(Table :: table(), Field :: field(), IDField :: field(), IDValue :: value()) -> value() | not_found.
%% @doc retrieves the value of Field from Table where the value of IDField ==
%% IDValue (e.g.: Select 'Field' from 'Table' where 'IDField'='IDValue'). If
%% the query returns more than one record, only the first record's value is
%% returned.
field(Table,Field,IDField,IDValue) when is_atom(Table) ->
field(atom_to_list(Table),Field,IDField,IDValue);
field(Table,Field,IDField,IDValue) when is_atom(Field) ->
field(Table,atom_to_list(Field),IDField,IDValue);
field(Table,Field,IDField,IDValue) when is_atom(IDField) ->
field(Table,Field,atom_to_list(IDField),IDValue);
field(Table,Field,IDField,IDValue) ->
Token = sql_bridge_utils:create_placeholder(1),
fffr(["select ",Field," from ",Table," where ",IDField,"= ",Token],[IDValue]).
-spec field(Table :: table(), Field :: field(), Value :: value()) -> value() | not_found.
%% @doc This does the same as above, but uses Table ++ "id" for the idfield
field(Table,Field,IDValue) when is_atom(Table) ->
field(atom_to_list(Table),Field,IDValue);
field(Table,Field,IDValue) ->
field(Table,Field,Table ++ "id",IDValue).
-spec delete(Table :: table(), ID :: value()) -> affected_rows().
%% @doc Deletes rows from Table where the Table ++ "id" = ID
delete(Table,ID) when is_atom(Table) ->
delete(atom_to_list(Table),ID);
delete(Table,ID) when is_list(Table) ->
KeyField = Table ++ "id",
delete(Table,KeyField,ID).
-spec delete(Table :: table(), KeyField :: field(), ID :: value()) -> affected_rows().
%% @doc Deletes from Table where KeyField = ID
delete(Table,KeyField,ID) when is_atom(Table) ->
delete(atom_to_list(Table),KeyField,ID);
delete(Table,KeyField,ID) when is_atom(KeyField) ->
delete(Table,atom_to_list(KeyField),ID);
delete(Table,KeyField,ID) ->
Token = sql_bridge_utils:create_placeholder(1),
qu(["delete from ",Table," where ",KeyField,"=",Token],[ID]).
sanitize(V) when is_list(V) ->
sanitize(unicode:characters_to_binary(V));
sanitize(true) ->
true;
sanitize(false) ->
false;
sanitize(undefined) ->
undefined;
sanitize(A) when is_atom(A) ->
sanitize(atom_to_list(A));
sanitize(V) ->
V.
sanitize_params(L) ->
[sanitize(X) || X <- L].
-spec encode(V :: any()) -> binary().
%% @doc Safely encodes text for insertion into a query. Replaces the atoms
%% 'true' and 'false' with <<"1">> and <<"0">> respectively.
encode(Other) ->
?ADAPTER:encode(sanitize(Other)).
remove_wrapping_quotes(Bin) when is_binary(Bin) ->
binary_part(Bin, 1, byte_size(Bin)-2);
remove_wrapping_quotes(Str) ->
lists:reverse(tl(lists:reverse(tl(Str)))).
-spec encode64(T :: any()) -> string().
%% @doc Encodes an erlang term into a base64 string which can be safely
%% inserted into a text field
encode64("") -> "";
encode64(undefined) -> "";
encode64(Data) ->
binary_to_list(b64fast:encode64(term_to_binary(Data))).
%base64:encode_to_string(term_to_binary(Data)).
-spec decode64(T :: any()) -> string().
%% @doc Decodes a base64 string into the relevant erlang term.
decode64("") -> "";
decode64(undefined) -> "";
decode64(Data) when is_list(Data) ->
decode64(list_to_binary(Data));
decode64(Data) when is_binary(Data) ->
binary_to_term(b64fast:decode64(Data)).
-spec encode_list(List :: [value()]) -> iolist().
%% @doc Takes a list of items and encodes them for SQL then returns a
%% comma-separated list of them.
encode_list(List) ->
NewList = [encode(X) || X<-List],
iolist_join(NewList,",").
-spec dict_to_proplist(SrcDict :: t_dict(), AcceptableFields :: [field()]) -> proplist().
%% @doc Converts a dict to a proplist, filtering out any fields not found in
%% AcceptableFields
dict_to_proplist(SrcDict,AcceptableFields) ->
DictFilterFoldFun = fun(F,Dict) ->
case dict:is_key(F,Dict) of
true -> Dict;
false -> dict:erase(F,Dict)
end
end,
FilteredDict = lists:foldl(DictFilterFoldFun,SrcDict,AcceptableFields),
dict:to_list(FilteredDict).
-spec iolist_join(List :: [iolist()], Delimiter :: iolist()) -> iolist().
%% @doc Joins a list of iolists together by a delimiter
iolist_join([], _) ->
[];
iolist_join([H], _) ->
[H];
iolist_join([H|T], Delimiter) ->
[H,Delimiter|iolist_join(T, Delimiter)].
to_bool(false) -> false;
to_bool(0) -> false;
to_bool(undefined) -> false;
to_bool(_) -> true.
-spec limit_clause(PerPage :: integer(), Page :: integer()) -> iolist().
%% @doc Generates a SQL "LIMIT" clause based on the PerPage Criteria and the
%% Page, calculating the offset according to the provided values. Useful when
%% building something that breaks a list into pages in an interface.
limit_clause(PerPage, Page) when PerPage < 1 ->
limit_clause(1, Page);
limit_clause(PerPage, Page) ->
Offset = offset(PerPage, Page),
[" limit ",integer_to_list(PerPage)," offset ", integer_to_list(Offset)].
-spec offset(PerPage :: integer(), Page :: integer()) -> Offset :: integer().
offset(PerPage, Page) when Page =< 0 ->
offset(PerPage, 1);
offset(PerPage, Page) when PerPage < 1 ->
offset(1, Page);
offset(PerPage, Page) when Page > 0 ->
(Page-1) * PerPage.
%% Exported as a convenience
q_prep(Q, ParamList) ->
sql_bridge_utils:q_prep(Q, ParamList).
wrap_field(V) ->
?ADAPTER:wrap_field(V).