diff --git a/.gitignore b/.gitignore index 1d3febb..fcb4d5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .eunit *.beam +.rebar/* diff --git a/src/sqerl.erl b/src/sqerl.erl index c1d95d6..eaf074d 100644 --- a/src/sqerl.erl +++ b/src/sqerl.erl @@ -204,11 +204,11 @@ select(Modifier, Fields, Tables, WhereExpr, Extras, Safe) -> [S1, convert(Modifier1), $\s] end, - ListFun = fun(Val) -> expr2(Val, Safe) end, - S3 = [S2, make_list(Fields, ListFun)], + S3 = [S2, make_list(Fields, fun(Val) -> expr2(Val, Safe) end)], S4 = case Tables of undefined -> S3; - _Other -> [S3, <<" FROM ">>, make_list(Tables, ListFun)] + _Other -> [S3, <<" FROM ">>, + make_list(Tables, fun(Val) -> join(Val, Safe) end)] end, S5 = case where(WhereExpr, Safe) of @@ -221,6 +221,41 @@ select(Modifier, Fields, Tables, WhereExpr, Extras, Safe) -> Expr -> [S5, Expr] end. +join({Table, Join, Table2, JoinExpr}, Safe) -> + [ expr2(Table, Safe), + join(Join), + expr2(Table2, Safe), + <<" ON ">>, + make_list(JoinExpr, fun(Val) -> expr(Val, Safe) end) ]; +join({Table, Joins}, Safe) when is_list(Joins) -> + S1 = lists:map(fun({Join, Table2, JoinExpr}) -> + [ join(Join), + expr2(Table2, Safe), + <<" ON ">>, + make_list(JoinExpr, fun(Val) -> expr(Val, Safe)end) + ] + end, Joins), + [expr2(Table, Safe), S1]; +join(Table, Safe) -> + expr2(Table, Safe). + +join(join) -> + <<" JOIN ">>; +join({left, join}) -> + <<" LEFT JOIN ">>; +join({inner, join}) -> + <<" INNER JOIN ">>; +join({right, join}) -> + <<" RIGHT JOIN ">>; +join({left, outer, join}) -> + <<" LEFT OUTER JOIN ">>; +join({right, outer, join}) -> + <<" RIGHT OUTER JOIN ">>; +join({full, outer, join}) -> + <<" FULL OUTER JOIN ">>; +join({cross, join}) -> + <<" CROSS JOIN ">>. + where(undefined, _) -> []; where(Expr, true) when is_list(Expr); is_binary(Expr) -> throw({error, {unsafe_expression, Expr}}); @@ -314,11 +349,16 @@ update(Table, Props, Safe) -> update(Table, Props, Where, Safe) when not is_list(Props) -> update(Table, [Props], Where, Safe); update(Table, Props, Where, Safe) -> - S1 = [<<"UPDATE ">>, convert(Table), <<" SET ">>], + S1 = case Table of + Table when is_tuple(Table) -> + join(Table, Safe); + _Other -> + convert(Table) + end, S2 = make_list(Props, fun({Field, Val}) -> [convert(Field), <<" = ">>, expr(Val, Safe)] end), - [S1, S2, where(Where, Safe)]. + [<<"UPDATE ">>, S1, <<" SET ">>, S2, where(Where, Safe)]. delete(Table, Safe) -> delete(Table, undefined, undefined, undefined, Safe). @@ -327,18 +367,24 @@ delete(Table, Using, WhereExpr, Safe) -> delete(Table, Using, WhereExpr, undefined, Safe). delete(Table, Using, WhereExpr, Extras, Safe) -> - S1 = [<<"DELETE FROM ">>, convert(Table)], - S2 = if Using =:= undefined -> S1; - true -> [S1, <<" USING ">>, make_list(Using, fun convert/1)] + S1 = case Table of + Table when is_tuple(Table) -> + join(Table, Safe); + _Other -> + convert(Table) + end, + S2 = [<<"DELETE FROM ">>, S1], + S3 = if Using =:= undefined -> S2; + true -> [S2, <<" USING ">>, make_list(Using, fun convert/1)] end, - S3 = case where(WhereExpr, Safe) of - undefined -> S2; - WhereClause -> [S2, WhereClause] + S4 = case where(WhereExpr, Safe) of + undefined -> S3; + WhereClause -> [S3, WhereClause] end, if Extras =:= undefined -> - S3; + S4; true -> - [S3, extra_clause(Extras, Safe)] + [S4, extra_clause(Extras, Safe)] end. convert(Val) when is_atom(Val)-> @@ -428,6 +474,10 @@ expr2(undefined, _Safe) -> <<"NULL">>; expr2(Expr, _Safe) when is_atom(Expr) -> convert(Expr); expr2(Expr, Safe) -> expr(Expr, Safe). +param({call, FuncName, []}) -> + [convert(FuncName), <<"()">>]; +param({call, FuncName, Params}) -> + [convert(FuncName), $(, make_list(Params, fun param/1), $)]; param({Key, Value}) when is_atom(Key) -> [convert(Key), <<" := ">>, encode(Value)]; param(Key) when is_atom(Key) -> diff --git a/test/sqerl_tests.erl b/test/sqerl_tests.erl index baff481..19832ca 100644 --- a/test/sqerl_tests.erl +++ b/test/sqerl_tests.erl @@ -44,6 +44,16 @@ safe_test_() -> {where,{'not',{a,'=',5}}}}) }, + {<<"UPDATE project JOIN client ON (project.client_id = client.id) SET foo = 5">>, + ?_safe_test({update, + {project,join,client,{'project.client_id','=','client.id'}},[{foo,5}]}) + }, + + {<<"UPDATE project INNER JOIN client ON (project.client_id = client.id) SET foo = 5">>, + ?_safe_test({update, + {project,{inner,join},client,{'project.client_id','=','client.id'}},[{foo,5}]}) + }, + {<<"DELETE FROM project">>, ?_safe_test({delete,project}) }, @@ -52,6 +62,11 @@ safe_test_() -> ?_safe_test({delete,project,{a,'=',5}}) }, + {<<"DELETE FROM project JOIN client ON (project.client_id = client.id) WHERE (client.a = 8)">>, + ?_safe_test({delete,{project,join,client, + {'project.client_id','=','client.id'}},{'client.a','=',8}}) + }, + {<<"DELETE FROM project WHERE (a = 5)">>, ?_safe_test({delete,{from,project},{where,{a,'=',5}}}) }, @@ -94,6 +109,14 @@ safe_test_() -> ?_safe_test({select,{call,count,[name]},{from,developer}}) }, + {<<"SELECT count(name) AS c FROM developer">>, + ?_safe_test({select,{{call,count,[name]},as,c},{from,developer}}) + }, + + {<<"SELECT CONCAT('-- [', GROUP_CONCAT(comment.id), ']') AS comments FROM posts">>, + ?_safe_test({select,{{call,'CONCAT',["-- [",{call,'GROUP_CONCAT',['comment.id']},"]"]},as,comments},{from,posts}}) + }, + {<<"SELECT last_insert_id()">>, ?_safe_test({select,{call,last_insert_id,[]}}) }, @@ -137,8 +160,8 @@ safe_test_() -> {select,distinct,name,{from,gymnast}}}}}) }, - {<<"SELECT name FROM developer WHERE name IN ((SELECT DISTINCT name FROM gymnast)" - "UNION (SELECT name FROM dancer WHERE ((name LIKE 'Mikhail%') OR (country = 'Russia')))" + {<<"SELECT name FROM developer WHERE name IN ((SELECT DISTINCT name FROM gymnast) " + "UNION (SELECT name FROM dancer WHERE ((name LIKE 'Mikhail%') OR (country = 'Russia'))) " "WHERE (name LIKE 'M%') ORDER BY name DESC LIMIT 5, 10)">>, ?_safe_test({select,name, {from,developer}, @@ -186,6 +209,39 @@ safe_test_() -> {<<"SELECT name FROM search_people(age := 18)">>, ?_safe_test({select,name,{from,{call,search_people,[{age, 18}]}}}) + }, + {<<"SELECT * FROM foo JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,join,bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo AS f JOIN bar AS b ON (f.bar_id = b.id)">>, + ?_safe_test({select,'*',{from,{{foo,as,f},join,{bar,as,b},{'f.bar_id','=','b.id'}}}}) + }, + {<<"SELECT * FROM foo JOIN bar ON ((foo.bar_id = bar.id) AND (foo.bar_type = bar.type))">>, + ?_safe_test({select,'*',{from,{foo,join,bar,[ + {'and', [ + {'foo.bar_id','=','bar.id'}, + {'foo.bar_type','=','bar.type'} + ] + }]}}}) + }, + {<<"SELECT * FROM foo LEFT JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,{left,join},bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo INNER JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,{inner,join},bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo RIGHT JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,{right,join},bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo LEFT OUTER JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,{left,outer,join},bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo CROSS JOIN bar ON (foo.bar_id = bar.id)">>, + ?_safe_test({select,'*',{from,{foo,{cross,join},bar,{'foo.bar_id','=','bar.id'}}}}) + }, + {<<"SELECT * FROM foo JOIN bar ON (foo.bar_id = bar.id) JOIN baz ON (bar.baz_id = baz.id)">>, + ?_safe_test({select,'*',{from,{foo,[ {join,bar,{'foo.bar_id','=','bar.id'}}, + {join,baz,{'bar.baz_id','=','baz.id'}} ]}}}) } ] }.