|
@@ -0,0 +1,189 @@
|
|
|
+-module(store_sql).
|
|
|
+-author('Daniil Churikov').
|
|
|
+-compile(export_all).
|
|
|
+-include("sql.hrl").
|
|
|
+-include("metainfo.hrl").
|
|
|
+
|
|
|
+insert(E, Table, S) ->
|
|
|
+ SkipFun = fun(#column{ro = RO}, V) -> RO orelse V == '$skip' end,
|
|
|
+ Q = prepare_insert(skip(SkipFun, Table#table.columns, e2l(E)), Table, S),
|
|
|
+ if Q#query.values /= [] -> {ok, build(Q)};
|
|
|
+ true -> {error, empty_insert} end.
|
|
|
+
|
|
|
+select_pk(E, Table, S) ->
|
|
|
+ SkipFun = fun(#column{key = Key}, _) -> not Key end,
|
|
|
+ Q = prepare_select(skip(SkipFun, Table#table.columns, e2l(E)), Table, S),
|
|
|
+ if Q#query.values /= [] -> {ok, build(Q)};
|
|
|
+ true -> {error, pk_miss} end.
|
|
|
+
|
|
|
+select(E, Table, S) ->
|
|
|
+ SkipFun = fun(_, V) -> V == '$skip' end,
|
|
|
+ {ok, build(prepare_select(skip(SkipFun, Table#table.columns, e2l(E)), Table, S))}.
|
|
|
+
|
|
|
+update_pk(E, Table = #table{columns = MekaoCols}, S) ->
|
|
|
+ SetSkipFun = fun(#column{ro = RO}, V) -> RO orelse V == '$skip' end,
|
|
|
+ WhereSkipFun = fun(#column{key = Key}, _) -> not Key end,
|
|
|
+ Vals = e2l(E),
|
|
|
+ Q = prepare_update(skip(SetSkipFun, MekaoCols, Vals),
|
|
|
+ skip(WhereSkipFun, MekaoCols, Vals),Table, S),
|
|
|
+ if (Q#query.body)#sql_update.set == [] -> {error, empty_update};
|
|
|
+ (Q#query.body)#sql_update.where == [] -> {error, pk_miss};
|
|
|
+ true -> {ok, build(Q)} end.
|
|
|
+
|
|
|
+update_pk_diff(E1, E2, Table = #table{columns = MekaoCols}, S) ->
|
|
|
+ Vals1 = e2l(E1),
|
|
|
+ Vals2 = e2l(E2),
|
|
|
+ DiffVals = map2(
|
|
|
+ fun (V, V) -> '$skip';
|
|
|
+ (_, V2) -> V2 end, Vals1, Vals2),
|
|
|
+ SetSkipFun = fun(#column{ro = RO}, V) -> RO orelse V == '$skip' end,
|
|
|
+ WhereSkipFun = fun(#column{key = Key}, _) -> not Key end,
|
|
|
+ Q = prepare_update(skip(SetSkipFun, MekaoCols, DiffVals),
|
|
|
+ skip(WhereSkipFun, MekaoCols, Vals1),Table, S),
|
|
|
+ if (Q#query.body)#sql_update.set == [] -> {error, empty_update};
|
|
|
+ (Q#query.body)#sql_update.where == [] -> {error, pk_miss};
|
|
|
+ true -> {ok, build(Q)} end.
|
|
|
+
|
|
|
+update(E, Selector, Table = #table{columns = MekaoCols}, S) ->
|
|
|
+ SetSkipFun = fun(#column{ro = RO}, V) -> RO orelse V == '$skip' end,
|
|
|
+ WhereSkipFun = fun(_, V) -> V == '$skip' end,
|
|
|
+ Q = prepare_update(skip(SetSkipFun, MekaoCols, e2l(E)),
|
|
|
+ skip(WhereSkipFun, MekaoCols, e2l(Selector)),Table, S),
|
|
|
+ if (Q#query.body)#sql_update.set == [] -> {error, empty_update};
|
|
|
+ true -> {ok, build(Q)} end.
|
|
|
+
|
|
|
+delete_pk(E, Table, S) ->
|
|
|
+ SkipFun = fun(#column{key = Key}, _) -> not Key end,
|
|
|
+ Q = prepare_delete(skip(SkipFun, Table#table.columns, e2l(E)), Table, S),
|
|
|
+ if Q#query.values /= [] -> {ok, build(Q)};
|
|
|
+ true -> {error, pk_miss} end.
|
|
|
+
|
|
|
+delete(Selector, Table, S) ->
|
|
|
+ SkipFun = fun(_, V) -> V == '$skip' end,
|
|
|
+ Q = prepare_delete(skip(SkipFun, Table#table.columns, e2l(Selector)), Table, S),
|
|
|
+ {ok, build(Q)}.
|
|
|
+
|
|
|
+prepare_insert(E, Table, S) ->
|
|
|
+ {Cols, PHs, Types, Vals} = qdata(1, e2l(E), Table#table.columns, S),
|
|
|
+ Q = #sql_insert{table=Table#table.name,columns=intersperse(Cols, <<", ">>),
|
|
|
+ values=intersperse(PHs, <<", ">>),returning=returning(insert, Table, S)},
|
|
|
+ #query{body=Q,types=Types,values=Vals,next_ph_num=length(PHs) + 1}.
|
|
|
+
|
|
|
+prepare_select(E, Table, S) ->
|
|
|
+ #table{columns = MekaoCols,order_by = OrderBy} = Table,
|
|
|
+ {Where, {_, PHs, Types, Vals}} = where(qdata(1, e2l(E), MekaoCols, S), S),
|
|
|
+ AllCols = intersperse(MekaoCols, <<", ">>, fun(#column{name = Name}) -> Name end),
|
|
|
+ Q = #sql_select{table=Table#table.name,columns=AllCols,where=Where,order_by=order_by(OrderBy)},
|
|
|
+ #query{body=Q,types=Types,values=Vals,next_ph_num = length(PHs) + 1}.
|
|
|
+
|
|
|
+prepare_update(SetE, WhereE, Table = #table{columns = MekaoCols}, S) ->
|
|
|
+ {SetCols, SetPHs, SetTypes, SetVals} = qdata(1, e2l(SetE), MekaoCols, S),
|
|
|
+ SetPHsLen = length(SetPHs),
|
|
|
+ {Where, {_, WherePHs, WhereTypes, WhereVals}} = where(qdata(SetPHsLen + 1, e2l(WhereE), MekaoCols, S), S),
|
|
|
+ WherePHsLen = length(WherePHs),
|
|
|
+ Set = intersperse2(fun (C, PH) -> [C, <<" = ">>, PH] end,<<", ">>, SetCols, SetPHs),
|
|
|
+ Q = #sql_update{table=Table#table.name,set=Set,where=Where,returning=returning(update, Table, S)},
|
|
|
+ #query{body=Q,types=SetTypes++WhereTypes,values=SetVals++WhereVals,next_ph_num = SetPHsLen + WherePHsLen + 1}.
|
|
|
+
|
|
|
+prepare_delete(E, Table, S) ->
|
|
|
+ {Where, {_, PHs, Types, Vals}} = where(qdata(1, e2l(E), Table#table.columns, S), S),
|
|
|
+ Q = #sql_delete{table=Table#table.name,where=Where,returning=returning(delete, Table, S)},
|
|
|
+ #query{body=Q,types=Types,values=Vals,next_ph_num = length(PHs) + 1}.
|
|
|
+
|
|
|
+build(Q = #query{body = Select}) when is_record(Select, sql_select) ->
|
|
|
+ #sql_select{columns=Columns,table=Table,where=Where,order_by=OrderBy} = Select,
|
|
|
+ Q#query{body = [<<"SELECT ">>, Columns, <<" FROM ">>, Table, build_where(Where),build_order_by(OrderBy)]};
|
|
|
+build(Q = #query{body = Insert}) when is_record(Insert, sql_insert) ->
|
|
|
+ #sql_insert{table=Table,columns=Columns,values=Values,returning=Return} = Insert,
|
|
|
+ Q#query{body = [<<"INSERT INTO ">>, Table, <<" (">>, Columns, <<") VALUES (">>,Values, <<")">>, build_return(Return)]};
|
|
|
+build(Q = #query{body = Update}) when is_record(Update, sql_update) ->
|
|
|
+ #sql_update{table=Table,set=Set,where=Where,returning=Return} = Update,
|
|
|
+ Q#query{body = [<<"UPDATE ">>, Table, <<" SET ">>, Set,build_where(Where), build_return(Return)]};
|
|
|
+build(Q = #query{body = Delete}) when is_record(Delete, sql_delete) ->
|
|
|
+ #sql_delete{table=Table,where=Where,returning=Return} = Delete,
|
|
|
+ Q#query{body = [<<"DELETE FROM ">>, Table, build_where(Where),build_return(Return)]}.
|
|
|
+
|
|
|
+e2l(Vals) when is_list(Vals) -> Vals;
|
|
|
+e2l(E) when is_tuple(E) -> [_EntityName | Vals] = tuple_to_list(E), Vals.
|
|
|
+
|
|
|
+skip(SkipFun, Cols, Vals) -> map2(fun(C, V) -> Skip = SkipFun(C, V),if Skip -> '$skip'; true -> V end end, Cols, Vals).
|
|
|
+
|
|
|
+qdata(_, [], [], _) -> {[], [], [], []};
|
|
|
+qdata(Num, ['$skip' | Vals], [_Col | Cols], S) -> qdata(Num, Vals, Cols, S);
|
|
|
+qdata(Num, [Pred | Vals], [Col | Cols], S) ->
|
|
|
+ #column{type = T, name = CName, transform = TrFun} = Col,
|
|
|
+ V = predicate_val(Pred),
|
|
|
+ NewV = if TrFun == undefined -> V;
|
|
|
+ true -> TrFun(V) end,
|
|
|
+ PH = (S#sql_settings.placeholder)(Col, Num, NewV),
|
|
|
+ NewPred = set_predicate_val(Pred, NewV), {ResCols, ResPHs, ResTypes, ResVals} = qdata(Num + 1, Vals, Cols, S),
|
|
|
+ {[CName | ResCols], [PH | ResPHs], [T | ResTypes], [NewPred | ResVals]}.
|
|
|
+
|
|
|
+returning(_QType, _Table, #sql_settings{returning = undefined}) -> [];
|
|
|
+returning(QType, Table, #sql_settings{returning = RetFun}) -> RetFun(QType, Table).
|
|
|
+
|
|
|
+where(QData = {[], [], [], []}, _S) -> {[], QData};
|
|
|
+where({[C], [PH], [T], [V]}, S) ->
|
|
|
+ {W, {NewC, NewPH, NewT, NewV}} = predicate({C, PH, T, V}, S),
|
|
|
+ {[W], {[NewC], [NewPH], [NewT], [NewV]}};
|
|
|
+where({[C | Cs], [PH | PHs], [T | Types], [V | Vals]}, S) ->
|
|
|
+ {W, {NewC, NewPH, NewT, NewV}} = predicate({C, PH, T, V}, S),
|
|
|
+ {Ws, {NewCs, NewPHs, NewTypes, NewVals}} = where({Cs, PHs, Types, Vals}, S),
|
|
|
+ {[W, <<" AND ">> | Ws], {[NewC | NewCs], [NewPH | NewPHs], [NewT | NewTypes], [NewV | NewVals]}}.
|
|
|
+
|
|
|
+%% TODO: add NOT, IN, ANY, ALL, BETWEEN, LIKE handling
|
|
|
+predicate({C, PH, T, {'$predicate', Op, V}}, S) when Op == '='; Op == '<>' ->
|
|
|
+ IsNull = (S#sql_settings.is_null)(V),
|
|
|
+ if not IsNull -> {[C, op_to_bin(Op), PH], {C, PH, T, V}};
|
|
|
+ Op == '=' -> {[C, <<" IS NULL">>], {C, PH, T, V}};
|
|
|
+ Op == '<>' -> {[C, <<" IS NOT NULL">>], {C, PH, T, V}} end;
|
|
|
+predicate({C, PH, T, {'$predicate', OP, V}}, _S) -> {[C, op_to_bin(OP), PH], {C, PH, T, V}};
|
|
|
+predicate({C, PH, T, V}, S) -> predicate({C, PH, T, {'$predicate', '=', V}}, S).
|
|
|
+
|
|
|
+op_to_bin('=') -> <<" = ">>;
|
|
|
+op_to_bin('<>') -> <<" <> ">>;
|
|
|
+op_to_bin('>') -> <<" > ">>;
|
|
|
+op_to_bin('>=') -> <<" >= ">>;
|
|
|
+op_to_bin('<') -> <<" < ">>;
|
|
|
+op_to_bin('<=') -> <<" <= ">>.
|
|
|
+
|
|
|
+order_by([]) -> [];
|
|
|
+order_by([O]) -> [order_by_1(O)];
|
|
|
+order_by([O | OrderBy]) -> [order_by_1(O), <<", ">> | order_by(OrderBy)].
|
|
|
+order_by_1(E) when not is_tuple(E) -> order_by_1({E, {default, default}});
|
|
|
+order_by_1({Pos, Opts}) when is_integer(Pos) -> order_by_1({integer_to_list(Pos - 1), Opts});
|
|
|
+order_by_1({Expr, Opts}) when is_list(Expr); is_binary(Expr) -> [Expr, order_by_opts(Opts)].
|
|
|
+order_by_opts({Ordering, Nulls}) ->
|
|
|
+ O = case Ordering of
|
|
|
+ default -> <<"">>;
|
|
|
+ asc -> <<" ASC">>;
|
|
|
+ desc -> <<" DESC">> end,
|
|
|
+ case Nulls of
|
|
|
+ default -> O;
|
|
|
+ nulls_first -> <<O/binary," NULLS FIRST">>;
|
|
|
+ nulls_last -> <<O/binary, " NULLS LAST">> end.
|
|
|
+
|
|
|
+build_return([]) -> <<>>;
|
|
|
+build_return(Return) -> [<<" ">> | Return].
|
|
|
+build_where([]) -> <<>>;
|
|
|
+build_where(Where) -> [<<" WHERE ">> | Where].
|
|
|
+build_order_by([]) -> <<>>;
|
|
|
+build_order_by(OrderBy) -> [<<" ORDER BY ">>, OrderBy].
|
|
|
+
|
|
|
+predicate_val({'$predicate', _, V}) -> V;
|
|
|
+predicate_val(V) -> V.
|
|
|
+
|
|
|
+set_predicate_val({'$predicate', Op, _}, NewV) -> {'$predicate', Op, NewV};
|
|
|
+set_predicate_val(_, NewV) -> NewV.
|
|
|
+
|
|
|
+map2(_Fun, [], []) -> [];
|
|
|
+map2(Fun, [V1 | L1], [V2 | L2]) -> [Fun(V1, V2) | map2(Fun, L1, L2)].
|
|
|
+
|
|
|
+intersperse(List, Sep) -> intersperse(List, Sep, fun (X) -> X end).
|
|
|
+intersperse([], _, _) -> [];
|
|
|
+intersperse([Item], _, Fun) -> [Fun(Item)];
|
|
|
+intersperse([Item | Items], Sep, Fun) -> [Fun(Item), Sep | intersperse(Items, Sep, Fun)].
|
|
|
+
|
|
|
+intersperse2(_Fun, _Sep, [], []) -> [];
|
|
|
+intersperse2(Fun, _Sep, [I1], [I2]) -> [Fun(I1, I2)];
|
|
|
+intersperse2(Fun, Sep, [I1 | I1s], [I2 | I2s]) -> [Fun(I1, I2), Sep | intersperse2(Fun, Sep, I1s, I2s)].
|