Permalink
Browse files

Revamp the boss_db:find/N API

This change eliminates boss_db:find/4-6 in favor of specifying search
options in a proplist as the third argument to boss_db:find/3. Valid
options are:

  'limit' - max # of records to return
  'offset' - # of records to skip
  'order_by' - attribute to sort on
  'descending' - whether values should go from high to low

The new API is easier to remember and the options have been modeled
after their SQL equivalents. Note that since 'descending' takes a
boolean it can be specified as a bare word, e.g.

  boss_db:find(house, [], [{order_by, price}, descending])

This change also eliminates the need to specify whether values
should be treated as numbers or strings when sorting. Instead
this information is inferred from the model's type hints. But
only the Tyrant adapter allows specifying the sort logic so most
users won't need the type hints anyway.

Finally, a BossRecord's -has options have been modified to be
consistent with the new boss_db:find options. 'sort_by' has
been renamed 'order_by', and 'sort_order' has been replaced
with 'descending'. E.g.

  -has({rooms, many, [{order_by, size}, descending]).
  • Loading branch information...
1 parent 12fcdf7 commit 9943a4d494d065ae873a37d4be0f79e7a9b4c1e2 @evanmiller evanmiller committed Jul 22, 2012
View
61 src/boss_db.erl
@@ -8,9 +8,6 @@
find/1,
find/2,
find/3,
- find/4,
- find/5,
- find/6,
find_first/2,
find_first/3,
find_last/2,
@@ -79,62 +76,44 @@ find(_) ->
%% @doc Query for BossRecords. Returns all BossRecords of type
%% `Type' matching all of the given `Conditions'
find(Type, Conditions) ->
- find(Type, Conditions, all).
-
-%% @spec find(Type::atom(), Conditions, Max::integer() | all ) -> [ BossRecord ]
-%% @doc Query for BossRecords. Returns up to `Max' number of BossRecords of type
-%% `Type' matching all of the given `Conditions'
-find(Type, Conditions, Max) ->
- find(Type, Conditions, Max, 0).
-
-%% @spec find( Type::atom(), Conditions, Max::integer() | all, Skip::integer() ) -> [ BossRecord ]
-%% @doc Query for BossRecords. Returns up to `Max' number of BossRecords of type
-%% `Type' matching all of the given `Conditions', skipping the first `Skip' results.
-find(Type, Conditions, Max, Skip) ->
- find(Type, Conditions, Max, Skip, id).
-
-%% @spec find( Type::atom(), Conditions, Max::integer() | all, Skip::integer(), Sort::atom() ) -> [ BossRecord ]
-%% @doc Query for BossRecords. Returns up to `Max' number of BossRecords of type
-%% `Type' matching all of the given `Conditions', skipping the
-%% first `Skip' results, sorted on the attribute `Sort'.
-find(Type, Conditions, Max, Skip, Sort) ->
- find(Type, Conditions, Max, Skip, Sort, str_ascending).
-
-%% @spec find( Type::atom(), Conditions, Max::integer() | all, Skip::integer(), Sort::atom(), SortOrder ) -> [ BossRecord ]
-%% SortOrder = num_ascending | num_descending | str_ascending | str_descending
-%% @doc Query for BossRecords. Returns up to `Max' number of BossRecords of type
-%% Type matching all of the given `Conditions', skipping the
-%% first `Skip' results, sorted on the attribute `Sort'. `SortOrder' specifies whether
-%% to treat values as strings or as numbers, and whether to sort ascending or
-%% descending. (`SortOrder' = `num_ascending', `num_descending', `str_ascending', or
-%% `str_descending')
-%%
-%% Note that Time attributes are stored internally as numbers, so you should
-%% sort them numerically.
-
-find(Type, Conditions, Max, Skip, Sort, SortOrder) ->
+ find(Type, Conditions, []).
+
+%% @spec find(Type::atom(), Conditions, Options::proplist()) -> [ BossRecord ]
+%% @doc Query for BossRecords. Returns BossRecords of type
+%% `Type' matching all of the given `Conditions'. Options may include
+%% `limit' (maximum number of records to return), `offset' (number of records
+%% to skip), `order_by' (attribute to sort on), and `descending' (whether to
+%% sort the values from highest to lowest)
+find(Type, Conditions, Options) ->
+ Max = proplists:get_value(limit, Options, all),
+ Skip = proplists:get_value(offset, Options, 0),
+ Sort = proplists:get_value(order_by, Options, id),
+ SortOrder = case proplists:get_value(descending, Options) of
+ true -> descending;
+ _ -> ascending
+ end,
db_call({find, Type, normalize_conditions(Conditions), Max, Skip, Sort, SortOrder}).
%% @spec find_first( Type::atom(), Conditions ) -> Record | undefined
%% @doc Query for the first BossRecord of type `Type' matching all of the given `Conditions'
find_first(Type, Conditions) ->
- return_one(find(Type, Conditions, 1)).
+ return_one(find(Type, Conditions, [{limit, 1}])).
%% @spec find_first( Type::atom(), Conditions, Sort::atom() ) -> Record | undefined
%% @doc Query for the first BossRecord of type `Type' matching all of the given `Conditions',
%% sorted on the attribute `Sort'.
find_first(Type, Conditions, Sort) ->
- return_one(find(Type, Conditions, 1, 0, Sort)).
+ return_one(find(Type, Conditions, [{limit, 1}, {order_by, Sort}])).
%% @spec find_last( Type::atom(), Conditions ) -> Record | undefined
%% @doc Query for the last BossRecord of type `Type' matching all of the given `Conditions'
find_last(Type, Conditions) ->
- return_one(find(Type, Conditions, 1, 0, id, str_descending)).
+ return_one(find(Type, Conditions, [{limit, 1}, descending])).
%% @spec find_last( Type::atom(), Conditions, Sort ) -> Record | undefined
%% @doc Query for the last BossRecord of type `Type' matching all of the given `Conditions'
find_last(Type, Conditions, Sort) ->
- return_one(find(Type, Conditions, 1, 0, Sort, str_descending)).
+ return_one(find(Type, Conditions, [{limit, 1}, {order_by, Sort}, descending])).
%% @spec count( Type::atom() ) -> integer()
%% @doc Count the number of BossRecords of type `Type' in the database.
View
10 src/boss_db_mock_controller.erl
@@ -24,7 +24,7 @@ handle_call({find, Type, Conditions, Max, Skip, SortBy, SortOrder}, _From, [{Dic
Records = do_find(Dict, Type, Conditions, Max, Skip, SortBy, SortOrder),
{reply, Records, State};
handle_call({count, Type, Conditions}, _From, [{Dict, _IdCounter}|_] = State) ->
- Records = do_find(Dict, Type, Conditions, all, 0, id, str_ascending),
+ Records = do_find(Dict, Type, Conditions, all, 0, id, ascending),
{reply, length(Records), State};
handle_call({delete, Id}, _From, [{Dict, IdCounter}|OldState]) ->
{reply, ok, [{dict:erase(Id, Dict), IdCounter}|OldState]};
@@ -87,13 +87,9 @@ do_find(Dict, Type, Conditions, Max, Skip, SortBy, SortOrder) ->
AttributeA = sortable_attribute(RecordA, SortBy),
AttributeB = sortable_attribute(RecordB, SortBy),
case SortOrder of
- str_ascending ->
+ ascending ->
AttributeA < AttributeB;
- str_descending ->
- AttributeA > AttributeB;
- num_ascending ->
- AttributeA < AttributeB;
- num_descending ->
+ descending ->
AttributeA > AttributeB
end
end,
View
20 src/boss_db_test_app.erl
@@ -133,28 +133,20 @@ run_tests() ->
end,
[
fun(_) ->
- Res = boss_db:find(boss_db_test_model, [], 1),
+ Res = boss_db:find(boss_db_test_model, [], [{limit, 1}]),
{length(Res) =:= 1, "Max not obeyed"}
end,
fun(_) ->
- Res = boss_db:find(boss_db_test_model, [], all, 1),
+ Res = boss_db:find(boss_db_test_model, [], [{offset, 1}]),
{length(Res) =:= 2, "Skip not obeyed"}
end,
fun([_Id1, _Id2, Id3]) ->
- Res = boss_db:find(boss_db_test_model, [], 1, 0, some_text, str_ascending),
- {(hd(Res)):id() =:= Id3, "Sort str_ascending failed"}
+ Res = boss_db:find(boss_db_test_model, [], [{limit, 1}, {order_by, some_text}, {descending, false}]),
+ {(hd(Res)):id() =:= Id3, "Sort ascending failed"}
end,
fun([Id1, _Id2, _Id3]) ->
- Res = boss_db:find(boss_db_test_model, [], 1, 0, some_text, str_descending),
- {(hd(Res)):id() =:= Id1, "Sort str_descending failed"}
- end,
- fun([Id1, _Id2, _Id3]) ->
- Res = boss_db:find(boss_db_test_model, [], 1, 0, some_integer, num_ascending),
- {(hd(Res)):id() =:= Id1, "Sort num_ascending failed"}
- end,
- fun([_Id1, _Id2, Id3]) ->
- Res = boss_db:find(boss_db_test_model, [], 1, 0, some_integer, num_descending),
- {(hd(Res)):id() =:= Id3, "Sort num_descending failed"}
+ Res = boss_db:find(boss_db_test_model, [], [{limit, 1}, {order_by, some_text}, descending]),
+ {(hd(Res)):id() =:= Id1, "Sort descending failed"}
end,
fun(_) ->
View
34 src/boss_record_compiler.erl
@@ -306,16 +306,16 @@ has_one_forms(HasOne, ModuleName, Opts) ->
erl_syntax:function(erl_syntax:atom(HasOne),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, id, str_ascending)
+ has_many_application_forms(Type, ForeignKey, 1, id, false)
)
])]))
].
has_many_forms(HasMany, ModuleName, many, Opts) ->
has_many_forms(HasMany, ModuleName, all, Opts);
has_many_forms(HasMany, ModuleName, Limit, Opts) ->
- Sort = proplists:get_value(sort_by, Opts, 'id'),
- SortOrder = proplists:get_value(sort_order, Opts, str_ascending),
+ Sort = proplists:get_value(order_by, Opts, 'id'),
+ IsDescending = proplists:get_value(descending, Opts, false),
Singular = inflector:singularize(atom_to_list(HasMany)),
Type = proplists:get_value(module, Opts, Singular),
ForeignKey = proplists:get_value(foreign_key, Opts, atom_to_list(ModuleName) ++ "_id"),
@@ -326,7 +326,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
"set to the `Id' of this `", ModuleName, "'"])])],
erl_syntax:function(erl_syntax:atom(HasMany),
[erl_syntax:clause([], none, [
- has_many_application_forms(Type, ForeignKey, Limit, Sort, SortOrder)
+ has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending)
])])),
erl_syntax:add_precomments([erl_syntax:comment(
[
@@ -336,7 +336,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
erl_syntax:function(erl_syntax:atom("first_"++Singular),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, Sort, SortOrder)
+ has_many_application_forms(Type, ForeignKey, 1, Sort, IsDescending)
)
])])),
erl_syntax:add_precomments([erl_syntax:comment(
@@ -347,7 +347,7 @@ has_many_forms(HasMany, ModuleName, Limit, Opts) ->
erl_syntax:function(erl_syntax:atom("last_"++Singular),
[erl_syntax:clause([], none, [
first_or_undefined_forms(
- has_many_application_forms(Type, ForeignKey, 1, Sort, reverse_sort_order(SortOrder))
+ has_many_application_forms(Type, ForeignKey, 1, Sort, not IsDescending)
)
])]))
].
@@ -358,12 +358,7 @@ first_or_undefined_forms(Forms) ->
[erl_syntax:variable(?PREFIX++"Record")]),
erl_syntax:clause([erl_syntax:underscore()], none, [erl_syntax:atom(undefined)])]).
-reverse_sort_order(str_ascending) -> str_descending;
-reverse_sort_order(str_descending) -> str_ascending;
-reverse_sort_order(num_ascending) -> num_descending;
-reverse_sort_order(num_descending) -> num_ascending.
-
-has_many_application_forms(Type, ForeignKey, Limit, Sort, SortOrder) ->
+has_many_application_forms(Type, ForeignKey, Limit, Sort, IsDescending) ->
erl_syntax:application(
erl_syntax:atom(?DATABASE_MODULE),
erl_syntax:atom(find),
@@ -373,10 +368,17 @@ has_many_application_forms(Type, ForeignKey, Limit, Sort, SortOrder) ->
erl_syntax:atom(ForeignKey),
erl_syntax:variable("Id")])
]),
- erl_syntax:integer(Limit),
- erl_syntax:integer(0),
- erl_syntax:atom(Sort),
- erl_syntax:atom(SortOrder)
+ erl_syntax:list([
+ erl_syntax:tuple([
+ erl_syntax:atom(limit),
+ erl_syntax:integer(Limit)]),
+ erl_syntax:tuple([
+ erl_syntax:atom(order_by),
+ erl_syntax:atom(Sort)]),
+ erl_syntax:tuple([
+ erl_syntax:atom(descending),
+ erl_syntax:atom(IsDescending)])
+ ])
]).
belongs_to_forms(Type, BelongsTo, ModuleName) ->
View
4 src/db_adapters/boss_db_adapter_mnesia.erl
@@ -88,10 +88,6 @@ apply_sort([], _Key, _Order) ->
[];
apply_sort(List, primary, Order) ->
apply_sort(List, id, Order);
-apply_sort(List, Key, str_ascending) ->
- apply_sort(List, Key, ascending);
-apply_sort(List, Key, str_descending) ->
- apply_sort(List, Key, descending);
apply_sort(List, Key, ascending) ->
Fun = fun (A, B) -> apply(A,Key,[]) =< apply(B,Key,[]) end,
lists:sort(Fun, List);
View
7 src/db_adapters/boss_db_adapter_mongodb.erl
@@ -369,11 +369,8 @@ boss_to_mongo_op('not_in') -> '$nin'.
% Sort clauses
-pack_sort_order(str_ascending) -> 1;
-pack_sort_order(num_ascending) -> 1;
-pack_sort_order(str_descending) -> -1;
-pack_sort_order(num_descending) -> -1.
-
+pack_sort_order(ascending) -> 1;
+pack_sort_order(descending) -> -1.
%%
View
10 src/db_adapters/boss_db_adapter_mysql.erl
@@ -215,14 +215,10 @@ keyindex(Key, N, [Tuple|Rest], Index) ->
_ -> keyindex(Key, N, Rest, Index + 1)
end.
-sort_order_sql(num_ascending) ->
- "ASC";
-sort_order_sql(num_descending) ->
+sort_order_sql(descending) ->
"DESC";
-sort_order_sql(str_ascending) ->
- "ASC";
-sort_order_sql(str_descending) ->
- "DESC".
+sort_order_sql(ascending) ->
+ "ASC".
build_insert_query(Record) ->
Type = element(1, Record),
View
10 src/db_adapters/boss_db_adapter_pgsql.erl
@@ -186,14 +186,10 @@ keyindex(Key, N, [Tuple|Rest], Index) ->
_ -> keyindex(Key, N, Rest, Index + 1)
end.
-sort_order_sql(num_ascending) ->
- "ASC";
-sort_order_sql(num_descending) ->
+sort_order_sql(descending) ->
"DESC";
-sort_order_sql(str_ascending) ->
- "ASC";
-sort_order_sql(str_descending) ->
- "DESC".
+sort_order_sql(ascending) ->
+ "ASC".
build_insert_query(Record) ->
Type = element(1, Record),
View
6 src/db_adapters/boss_db_adapter_riak.erl
@@ -67,10 +67,8 @@ find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder) ->
is_atom(Sort) ->
lists:sort(fun (A, B) ->
case SortOrder of
- num_ascending -> A:Sort() =< B:Sort();
- str_ascending -> A:Sort() =< B:Sort();
- num_descending -> A:Sort() > B:Sort();
- str_descending -> A:Sort() > B:Sort()
+ ascending -> A:Sort() =< B:Sort();
+ descending -> A:Sort() > B:Sort()
end
end,
Records);
View
9 src/db_adapters/boss_db_adapter_tyrant.erl
@@ -39,7 +39,14 @@ find(Conn, Type, Conditions, Max, Skip, Sort, SortOrder) when is_atom(Type), is_
is_integer(Skip), is_atom(Sort), is_atom(SortOrder) ->
case boss_record_lib:ensure_loaded(Type) of
true ->
- Query = build_query(Type, Conditions, Max, Skip, Sort, SortOrder),
+ AttributeTypes = boss_record_lib:attribute_types(Type),
+ TypedSortOrder = case {SortOrder, proplists:get_value(Sort, AttributeTypes)} of
+ {ascending, Type} when Type =:= float; Type =:= integer; Type =:= datetime; Type =:= timestamp -> num_ascending;
+ {descending, Type} when Type =:= float; Type =:= integer; Type =:= datetime; Type =:= timestamp -> num_descending;
+ {ascending, _} -> str_ascending;
+ {descending, _} -> str_descending
+ end,
+ Query = build_query(Type, Conditions, Max, Skip, Sort, TypedSortOrder),
ResultRows = principe_table:mget(Conn, principe_table:search(Conn, Query)),
FilteredRows = case {Max, Skip} of
{all, Skip} when Skip > 0 ->

0 comments on commit 9943a4d

Please sign in to comment.