-module(pg_sql). -include("query.hrl"). -export([ 'andalso'/2, 'orelse'/2, '=:='/2, '=/='/2, '>'/2, '>='/2, '<'/2, '=<'/2, 'not'/1, '+'/2, '-'/2, '*'/2, '/'/2 ]). -export([ '~'/2, '~*'/2, like/2, ilike/2 ]). -export([ sum/1, count/1, min/1, max/1, distinct/1 ]). -export([ min/2, max/2, row/1, row/2 ]). -export([ coalesce/1, in/2 ]). %% Array functions -export([ '@>'/2 ]). %% Type function -export([ as/2, set_type/2 ]). %% ============================================================================= %% Sql operations %% ============================================================================= -type value() :: qast:ast_node() | any(). %% = Primitive ================================================================= %% @TODO wrap values in $value before validation and add $value match -spec 'andalso'(V, V) -> V when V :: boolean() | qast:ast_node(). 'andalso'(true, B) -> B; 'andalso'(A, true) -> A; 'andalso'(false, _) -> false; 'andalso'(_, false) -> false; 'andalso'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" and "), B, qast:raw(")")], #{type => boolean}). -spec 'orelse'(V, V) -> V when V :: boolean() | qast:ast_node(). 'orelse'(true, _) -> true; 'orelse'(_, true) -> true; 'orelse'(false, B) -> B; 'orelse'(A, false) -> A; 'orelse'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" or "), B, qast:raw(")")], #{type => boolean}). -spec 'not'(V) -> V when V :: boolean() | qast:ast_node(). 'not'(A) when is_boolean(A) -> not A; 'not'(A) -> qast:exp([qast:raw("not "), A], #{type => boolean}). -spec '=:='(value(), value()) -> qast:ast_node(). '=:='(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" = "), B, qast:raw(")")], #{type => boolean}). -spec '=/='(value(), value()) -> qast:ast_node(). '=/='(A, B) -> 'not'('=:='(A,B)). -spec '>'(value(), value()) -> qast:ast_node(). '>'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" > "), B, qast:raw(")")], #{type => boolean}). -spec '>='(value(), value()) -> qast:ast_node(). '>='(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" >= "), B, qast:raw(")")], #{type => boolean}). -spec '<'(value(), value()) -> qast:ast_node(). '<'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" < "), B, qast:raw(")")], #{type => boolean}). -spec '=<'(value(), value()) -> qast:ast_node(). '=<'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" <= "), B, qast:raw(")")], #{type => boolean}). %% @TODO type opts -spec '+'(value(), value()) -> qast:ast_node(). '+'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" + "), B, qast:raw(")")]). -spec '-'(value(), value()) -> qast:ast_node(). '-'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" - "), B, qast:raw(")")]). -spec '*'(value(), value()) -> qast:ast_node(). '*'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" * "), B, qast:raw(")")]). -spec '/'(value(), value()) -> qast:ast_node(). '/'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" / "), B, qast:raw(")")]). %% = LIKE ====================================================================== -spec '~'(value(), value()) -> qast:ast_node(). '~'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" ~ "), B, qast:raw(")")], #{type => boolean}). -spec '~*'(value(), value()) -> qast:ast_node(). '~*'(A, B) -> qast:exp([qast:raw("("), A, qast:raw(" ~* "), B, qast:raw(")")], #{type => boolean}). like(A, B) -> qast:exp([A, qast:raw(" like "), B]). ilike(A, B) -> qast:exp([A, qast:raw(" ilike "), B]). %% = Aggregators =============================================================== -spec sum(qast:ast_node()) -> qast:ast_node(). sum(Ast) -> qast:exp([qast:raw("sum("), Ast, qast:raw(")")], qast:opts(Ast)). -spec count(qast:ast_node()) -> qast:ast_node(). count(Ast) -> qast:exp([qast:raw("count("), Ast, qast:raw(")")], #{type => integer}). -spec 'min'(value()) -> qast:ast_node(). min(A) -> qast:exp([qast:raw("min("), A, qast:raw(")")], qast:opts(A)). -spec 'max'(value()) -> qast:ast_node(). max(A) -> qast:exp([qast:raw("max("), A, qast:raw(")")], qast:opts(A)). distinct(A) -> qast:exp([qast:raw("distinct ("), A, qast:raw(")")], qast:opts(A)). %% = Math ====================================================================== -spec 'min'(value(), value()) -> qast:ast_node(). min(A, B) -> qast:exp([qast:raw("LEAST("), A, qast:raw(","), B, qast:raw(")")], qast:opts(A)). -spec 'max'(value(), value()) -> qast:ast_node(). max(A, B) -> qast:exp([qast:raw("GREATEST("), A, qast:raw(","), B, qast:raw(")")], qast:opts(A)). %% = Additional operations ===================================================== -spec row(#{atom() => qast:ast_node()}) -> qast:ast_node(). row(Fields) when is_map(Fields) -> row(undefined, Fields). -spec row(Model :: module(), #{atom() => qast:ast_node()}) -> qast:ast_node(). row(Model, Fields) when is_map(Fields) -> FieldsList = maps:to_list(Fields), Type = {record, {model, Model, [{F, qast:opts(Node)} || {F, Node} <- FieldsList]}}, qast:exp([ qast:raw("row("), qast:join([Node || {_F, Node} <- FieldsList], qast:raw(",")), qast:raw(")") ], #{type => Type}). coalesce([H|_]=List) -> qast:exp([ qast:raw("coalesce("), qast:join([Node || Node <- List], qast:raw(",")), qast:raw(")") ], #{type => maps:get(type, qast:opts(H))}). in(A, #query{}=Q) -> qast:exp([A, qast:raw(" in ("), qsql:select(Q), qast:raw(")")]); in(A, B) when is_list(B) -> qast:exp([A, qast:raw(" = ANY("), B, qast:raw(")")]). %% = Array oprterations ======================================================== '@>'(A, B) -> qast:exp([A, qast:raw(" @> "), B], qast:opts(A)). %% = Type functions ============================================================ as(Ast, Type) -> Opts = qast:opts(Ast), qast:exp([ qast:raw("("), Ast, qast:raw(")::"), qast:raw(type_str(Type)) ], Opts#{type => Type}). set_type(Ast, Type) -> Opts = qast:opts(Ast), qast:set_opts(Ast, Opts#{type => Type}). type_str(Atom) when is_atom(Atom) -> atom_to_binary(Atom, latin1); type_str({array, Atom}) when is_atom(Atom) -> iolist_to_binary([type_str(Atom), "[]"]); type_str({Type, Args}) when Type =/= array -> iolist_to_binary([ to_iodata(Type), "(", join([to_iodata(A) || A <- Args], ","), ")" ]). to_iodata(Atom) when is_atom(Atom) -> atom_to_list(Atom); to_iodata(D) when is_list(D); is_binary(D) -> D; to_iodata(Int) when is_integer(Int) -> integer_to_list(Int); to_iodata(Float) when is_float(Float) -> io_lib:format("~p", [Float]). join([], _) -> []; join([H|T],Sep) -> [H|[[Sep,E]||E<-T]].