├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── deps
└── .gitignore
├── doc
├── edoc-info
├── erlang.png
├── index.html
├── modules-frame.html
├── overview-summary.html
├── packages-frame.html
├── sqerl.html
└── stylesheet.css
├── ebin
└── sqerl.app
├── rebar
├── rebar.config
├── src
├── sqerl.app.src
└── sqerl.erl
└── test
└── sqerl_tests.erl
/.gitignore:
--------------------------------------------------------------------------------
1 | .eunit
2 | *.beam
3 | .rebar/*
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Devin Torres
2 | Copyright (c) 2006 Yariv Sadan
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REBAR = ./rebar
2 |
3 | .PHONY: all compile test clean get-deps build-plt dialyze
4 |
5 | all: compile
6 |
7 | compile:
8 | @$(REBAR) compile
9 |
10 | test: compile
11 | @$(REBAR) eunit skip_deps=true
12 |
13 | clean:
14 | @$(REBAR) clean
15 |
16 | get-deps:
17 | @$(REBAR) get-deps
18 |
19 | build-plt:
20 | @$(REBAR) build-plt
21 |
22 | dialyze: compile
23 | @$(REBAR) dialyze
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Sqerl
2 | =====
3 |
4 |
5 |
6 | Sqerl is a domain specific embedded language for expressing SQL
7 | statements in Erlang as well as a library for generating the literal
8 | equivalents of Sqerl expressions.
9 |
10 | Sqerl lets you describe SQL queries using a combination of Erlang
11 | lists, tuples, atoms and values in a way that resembles the structure
12 | of SQL statements. You can pass this structure to the `sql/1` or
13 | `sql/2` functions, which parse it and return an iolist (a tree of
14 | strings and/or binaries) or a single binary, either of which can be
15 | sent to database engine through a socket (usually via a
16 | database-specific driver).
17 |
18 | Sqerl supports a large subset of the SQL language implemented by some
19 | popular RDBMS's, including most common `INSERT`, `UPDATE`, `DELETE` and
20 | `SELECT` statements. Sqerl can generate complex queries including those
21 | with unions, nested statements and aggregate functions, but it does
22 | not currently attempt to cover every feature and extension of the SQL
23 | language.
24 |
25 | Sqerl's benefits are:
26 |
27 | - Easy dynamic generation of SQL queries from Erlang by combining
28 | native Erlang types rather than string fragments.
29 | - Prevention of most, if not all, SQL injection attacks by assuring
30 | that all string values are properly escaped.
31 | - Efficient generation of iolists as nested lists of binaries.
32 |
33 | *Warning*: Sqerl allows you to write verbatim `WHERE` clauses as well
34 | as verbatim `LIMIT` and other trailing clauses, but using this feature
35 | is highly discouraged because it exposes you to SQL injection attacks.
36 |
37 | For usage examples, look at the file `test/sqerl_tests.erl`.
38 |
39 | Acknowledgements
40 | ================
41 |
42 | Almost entirely based on ErlyWeb's ErlSQL by Yariv Sadan.
--------------------------------------------------------------------------------
/deps/.gitignore:
--------------------------------------------------------------------------------
1 | */
2 |
--------------------------------------------------------------------------------
/doc/edoc-info:
--------------------------------------------------------------------------------
1 | {application,sqerl}.
2 | {packages,[]}.
3 | {modules,[sqerl]}.
4 |
--------------------------------------------------------------------------------
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devinus/sqerl/b33058b78171699ad80f318ac71dea5750be149f/doc/erlang.png
--------------------------------------------------------------------------------
/doc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
sqerl |
Generated by EDoc, Mar 27 2012, 17:37:19.
15 | 16 | 17 | -------------------------------------------------------------------------------- /doc/packages-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |encode/1 | Calls encode(Val, true) . |
sql/1 | Generate an iolist (a tree of strings and/or binaries) 19 | for a literal SQL statement that corresponds to the Sqerl 20 | structure. |
sql/2 | Similar to sql/1, but accepts a boolean parameter 22 | indicating if the return value should be a single binary 23 | rather than an iolist. |
unsafe_sql/1 | Generate an iolist (a tree of strings and/or binaries) 25 | for a literal SQL statement that corresponds to the Sqerl 26 | structure. |
unsafe_sql/2 | Similar to unsafe_sql/1 , but accepts a boolean parameter
28 | indicating if the return value should be a binary or an iolist. |
encode(Val::term()) -> binary()
Calls encode(Val, true)
.
sql(Sqerl::term()) -> iolist()
Generate an iolist (a tree of strings and/or binaries) 42 | for a literal SQL statement that corresponds to the Sqerl 43 | structure. If the structure is invalid, this function would 44 | crash.
45 | 46 | This function does not allow writing literalWHERE
, LIMIT
47 | and other trailing clauses. To write such clauses,
48 | call unsafe_sql/1
or unsafe_sql/2
.
49 |
50 | sql(Sqerl::term(), X2::boolean()) -> binary() | iolist()
Similar to sql/1, but accepts a boolean parameter 54 | indicating if the return value should be a single binary 55 | rather than an iolist.
56 | 57 |unsafe_sql(Sqerl::term()) -> iolist()
throws {error, {unsafe_expression, Expr}}
61 |Generate an iolist (a tree of strings and/or binaries) 62 | for a literal SQL statement that corresponds to the Sqerl 63 | structure. If the structure is invalid, this function 64 | throws an exception.
65 | 66 |This function allows writing literal WHERE
, LIMIT
67 | and other trailing clauses, such as {where, "a=" ++ Val}
,
68 | or "WHERE a=" ++ Str ++ " LIMIT 5"
.
unsafe_sql
, make sure to
72 | quote all your strings using the encode/1
function.
73 |
74 |
75 | unsafe_sql(Sqerl::term(), AsBinary::boolean()) -> binary() | iolist()
throws {error, {unsafe_expression, Expr}}
79 |Similar to unsafe_sql/1
, but accepts a boolean parameter
80 | indicating if the return value should be a binary or an iolist.
81 |
Generated by EDoc, Mar 27 2012, 17:37:19.
86 | 87 | 88 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /ebin/sqerl.app: -------------------------------------------------------------------------------- 1 | {application,sqerl, 2 | [{description,"An SQL generating DSL"}, 3 | {vsn,"0.1.0"}, 4 | {applications,[kernel,stdlib]}, 5 | {modules,[sqerl]}]}. 6 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devinus/sqerl/b33058b78171699ad80f318ac71dea5750be149f/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [warnings_as_errors]}. 2 | {eunit_opts, [verbose]}. 3 | {lib_dirs, ["deps"]}. 4 | {deps, [ 5 | {nicedecimal, "1.0", {git, "https://github.com/talentdeficit/nicedecimal.git"}} 6 | ]}. 7 | -------------------------------------------------------------------------------- /src/sqerl.app.src: -------------------------------------------------------------------------------- 1 | {application, sqerl, [ 2 | {description, "An SQL generating DSL"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {applications, [kernel, stdlib]} 6 | ]}. 7 | -------------------------------------------------------------------------------- /src/sqerl.erl: -------------------------------------------------------------------------------- 1 | -module(sqerl). 2 | -export([sql/1, sql/2, unsafe_sql/1, unsafe_sql/2, encode/1]). 3 | 4 | %% @doc Generate an iolist (a tree of strings and/or binaries) 5 | %% for a literal SQL statement that corresponds to the Sqerl 6 | %% structure. If the structure is invalid, this function would 7 | %% crash. 8 | %% 9 | %% This function does not allow writing literal `WHERE', `LIMIT' 10 | %% and other trailing clauses. To write such clauses, 11 | %% call `unsafe_sql/1' or `unsafe_sql/2'. 12 | -spec sql(Sqerl::term()) -> iolist(). 13 | sql(Sqerl) -> 14 | sql2(Sqerl, true). 15 | 16 | %% @doc Similar to sql/1, but accepts a boolean parameter 17 | %% indicating if the return value should be a single binary 18 | %% rather than an iolist. 19 | -spec sql(Sqerl::term(), boolean()) -> binary() | iolist(). 20 | sql(Sqerl, true) -> 21 | iolist_to_binary(sql(Sqerl)); 22 | sql(Sqerl, false) -> 23 | sql(Sqerl). 24 | 25 | %% @doc Generate an iolist (a tree of strings and/or binaries) 26 | %% for a literal SQL statement that corresponds to the Sqerl 27 | %% structure. If the structure is invalid, this function 28 | %% throws an exception. 29 | %% 30 | %% This function allows writing literal `WHERE', `LIMIT' 31 | %% and other trailing clauses, such as `{where, "a=" ++ Val}', 32 | %% or `"WHERE a=" ++ Str ++ " LIMIT 5"'. 33 | %% 34 | %% Such clauses are unsafe because they expose you to SQL 35 | %% injection attacks. When you use `unsafe_sql', make sure to 36 | %% quote all your strings using the `encode/1' function. 37 | %% 38 | %% @throws {error, {unsafe_expression, Expr}} 39 | -spec unsafe_sql(Sqerl::term()) -> iolist(). 40 | unsafe_sql(Sqerl) -> 41 | sql2(Sqerl, false). 42 | 43 | %% @doc Similar to `unsafe_sql/1', but accepts a boolean parameter 44 | %% indicating if the return value should be a binary or an iolist. 45 | %% 46 | %% @throws {error, {unsafe_expression, Expr}} 47 | -spec unsafe_sql(Sqerl::term(), AsBinary::boolean()) -> binary() | iolist(). 48 | unsafe_sql(Sqerl, true) -> 49 | iolist_to_binary(unsafe_sql(Sqerl)); 50 | unsafe_sql(Sqerl, false) -> 51 | unsafe_sql(Sqerl). 52 | 53 | %% @doc Calls `encode(Val, true)'. 54 | -spec encode(Val::term()) -> binary(). 55 | encode(Val) -> 56 | encode(Val, true). 57 | 58 | %% @doc Encode a value as a string or a binary to be embedded in 59 | %% a SQL statement. 60 | %% 61 | %% This function can encode numbers, atoms, date/time/datetime values, 62 | %% strings and binaries (which it escapes automatically). 63 | -spec encode(Val::term(), AsBinary::boolean()) -> string() | binary(). 64 | encode(Val, false) when Val =:= undefined; Val =:= null -> 65 | "null"; 66 | encode(Val, true) when Val =:= undefined; Val =:= null -> 67 | <<"null">>; 68 | encode(Val, false) when is_binary(Val) -> 69 | binary_to_list(quote(Val)); 70 | encode(Val, true) when is_binary(Val) -> 71 | quote(Val); 72 | encode(Val, true) -> 73 | list_to_binary(encode(Val,false)); 74 | encode(Val, false) when is_atom(Val) -> 75 | quote(atom_to_list(Val)); 76 | encode(Val, false) when is_list(Val) -> 77 | quote(Val); 78 | encode(Val, false) when is_integer(Val) -> 79 | integer_to_list(Val); 80 | encode(Val, false) when is_float(Val) -> 81 | nicedecimal:format(Val); 82 | encode({datetime, Val}, AsBinary) -> 83 | encode(Val, AsBinary); 84 | encode({{Year,Month,Day}, {Hour,Minute,Second}}, false) -> 85 | [Year1,Month1,Day1,Hour1,Minute1,Second1] = 86 | lists:map(fun two_digits/1,[Year, Month, Day, Hour, Minute,Second]), 87 | lists:flatten(io_lib:format("'~s-~s-~s ~s:~s:~s'", 88 | [Year1,Month1,Day1,Hour1,Minute1,Second1])); 89 | encode({date, {Year, Month, Day}}, false) -> 90 | [Year1,Month1,Day1] = 91 | lists:map(fun two_digits/1,[Year, Month, Day]), 92 | lists:flatten(io_lib:format("'~s-~s-~s'",[Year1,Month1,Day1])); 93 | encode({time, {Hour, Minute, Second}}, false) -> 94 | [Hour1,Minute1,Second1] = 95 | lists:map(fun two_digits/1,[Hour, Minute, Second]), 96 | lists:flatten(io_lib:format("'~s:~s:~s'",[Hour1,Minute1,Second1])); 97 | encode(Val, _AsBinary) -> 98 | {error, {unrecognized_value, {Val}}}. 99 | 100 | two_digits(Nums) when is_list(Nums) -> 101 | [two_digits(Num) || Num <- Nums]; 102 | two_digits(Num) -> 103 | [Str] = io_lib:format("~b", [Num]), 104 | case length(Str) of 105 | 1 -> [$0 | Str]; 106 | _ -> Str 107 | end. 108 | 109 | sql2({select, Tables}, Safe)-> 110 | select(Tables, Safe); 111 | sql2({select, Fields, {from, Tables}}, Safe) -> 112 | select(Fields, Tables, Safe); 113 | sql2({select, Fields, {from, Tables}, {where, WhereExpr}}, Safe) -> 114 | select(undefined, Fields, Tables, WhereExpr, undefined, Safe); 115 | sql2({select, Fields, {from, Tables}, {where, WhereExpr}, Extras}, Safe) -> 116 | select(undefined, Fields, Tables, WhereExpr, Extras, Safe); 117 | sql2({select, Fields, {from, Tables}, WhereExpr, Extras}, Safe) -> 118 | select(undefined, Fields, Tables, WhereExpr, Extras, Safe); 119 | sql2({select, Fields, {from, Tables}, Extras}, Safe) -> 120 | select(undefined, Fields, Tables, undefined, Extras, Safe); 121 | sql2({select, Tables, {where, WhereExpr}}, Safe) -> 122 | select(undefined, undefined, Tables, WhereExpr, Safe); 123 | sql2({select, Tables, WhereExpr}, Safe) -> 124 | select(undefined, undefined, Tables, WhereExpr, Safe); 125 | sql2({select, Modifier, Fields, {from, Tables}}, Safe) -> 126 | select(Modifier, Fields, Tables, Safe); 127 | sql2({select, Modifier, Fields, {from, Tables}, {where, WhereExpr}}, Safe) -> 128 | select(Modifier, Fields, Tables, WhereExpr, Safe); 129 | sql2({select, Modifier, Fields, {from, Tables}, Extras}, Safe) -> 130 | select(Modifier, Fields, Tables, undefined, Extras, Safe); 131 | sql2({select, Modifier, Fields, {from, Tables}, {where, WhereExpr}, Extras}, 132 | Safe) -> 133 | select(Modifier, Fields, Tables, WhereExpr, Extras, Safe); 134 | sql2({select, Modifier, Fields, {from, Tables}, WhereExpr, Extras}, Safe) -> 135 | select(Modifier, Fields, Tables, WhereExpr, Extras, Safe); 136 | 137 | sql2({Select1, union, Select2}, Safe) -> 138 | [$(, sql2(Select1, Safe), <<") UNION (">>, sql2(Select2, Safe), $)]; 139 | sql2({Select1, union, Select2, {where, WhereExpr}}, Safe) -> 140 | [sql2({Select1, union, Select2}, Safe), where(WhereExpr, Safe)]; 141 | sql2({Select1, union, Select2, Extras}, Safe) -> 142 | [sql2({Select1, union, Select2}, Safe), extra_clause(Extras, Safe)]; 143 | sql2({Select1, union, Select2, {where, _} = Where, Extras}, Safe) -> 144 | [sql2({Select1, union, Select2, Where}, Safe), extra_clause(Extras, Safe)]; 145 | 146 | sql2({Select1, union_all, Select2}, Safe) -> 147 | [$(, sql2(Select1, Safe), <<") UNION ALL (">>, sql2(Select2, Safe), $)]; 148 | sql2({Select1, union_all, Select2, {where, WhereExpr}}, Safe) -> 149 | [sql2({Select1, union, Select2}, Safe), where(WhereExpr, Safe)]; 150 | sql2({Select1, union_all, Select2, Extras}, Safe) -> 151 | [sql2({Select1, union, Select2}, Safe), extra_clause(Extras, Safe)]; 152 | sql2({Select1, union_all, Select2, {where, _} = Where, Extras}, Safe) -> 153 | [sql2({Select1, union, Select2, Where}, Safe), extra_clause(Extras, Safe)]; 154 | 155 | sql2({insert, Table, Params}, _Safe) -> 156 | insert(Table, Params); 157 | sql2({insert, Table, Params, Returning}, Safe) -> 158 | insert(Table, Params, Returning, Safe); 159 | 160 | sql2({update, Table, Props}, Safe) -> 161 | update(Table, Props, Safe); 162 | sql2({update, Table, Props, {where, Where}}, Safe) -> 163 | update(Table, Props, Where, Safe); 164 | sql2({update, Table, Props, Where}, Safe) -> 165 | update(Table, Props, Where, Safe); 166 | 167 | sql2({delete, {from, Table}}, Safe) -> 168 | delete(Table, Safe); 169 | sql2({delete, Table}, Safe) -> 170 | delete(Table, Safe); 171 | sql2({delete, {from, Table}, {where, Where}}, Safe) -> 172 | delete(Table, undefined, Where, Safe); 173 | sql2({delete, Table, {where, Where}}, Safe) -> 174 | delete(Table, undefined, Where, Safe); 175 | sql2({delete, Table, Where}, Safe) -> 176 | delete(Table, undefined, Where, Safe); 177 | sql2({delete, Table, Using, Where}, Safe) -> 178 | delete(Table, Using, Where, Safe); 179 | sql2({delete, Table, Using, Where, Extras}, Safe) -> 180 | delete(Table, Using, Where, Extras, Safe). 181 | 182 | select(Fields, Safe) -> 183 | select(undefined, Fields, undefined, undefined, undefined, Safe). 184 | 185 | select(Fields, Tables, Safe) -> 186 | select(undefined, Fields, Tables, undefined, undefined, Safe). 187 | 188 | select(Modifier, Fields, Tables, Safe) -> 189 | select(Modifier, Fields, Tables, undefined, undefined, Safe). 190 | 191 | select(Modifier, Fields, Tables, WhereExpr, Safe) -> 192 | select(Modifier, Fields, Tables, WhereExpr, undefined, Safe). 193 | 194 | select(Modifier, Fields, Tables, WhereExpr, Extras, Safe) -> 195 | S1 = <<"SELECT ">>, 196 | S2 = case Modifier of 197 | undefined -> S1; 198 | Modifier -> 199 | Modifier1 = case Modifier of 200 | distinct -> 'DISTINCT'; 201 | 'all' -> 'ALL'; 202 | Other -> Other 203 | end, 204 | [S1, convert(Modifier1), $\s] 205 | end, 206 | 207 | S3 = [S2, make_list(Fields, fun(Val) -> expr2(Val, Safe) end)], 208 | S4 = case Tables of 209 | undefined -> S3; 210 | _Other -> [S3, <<" FROM ">>, 211 | make_list(Tables, fun(Val) -> join(Val, Safe) end)] 212 | end, 213 | 214 | S5 = case where(WhereExpr, Safe) of 215 | undefined -> S4; 216 | WhereClause -> [S4, WhereClause] 217 | end, 218 | 219 | case extra_clause(Extras, Safe) of 220 | undefined -> S5; 221 | Expr -> [S5, Expr] 222 | end. 223 | 224 | join({Table, Join, Table2, JoinExpr}, Safe) -> 225 | [ expr2(Table, Safe), 226 | join(Join), 227 | expr2(Table2, Safe), 228 | <<" ON ">>, 229 | make_list(JoinExpr, fun(Val) -> expr(Val, Safe) end) ]; 230 | join({Table, Joins}, Safe) when is_list(Joins) -> 231 | S1 = lists:map(fun({Join, Table2, JoinExpr}) -> 232 | [ join(Join), 233 | expr2(Table2, Safe), 234 | <<" ON ">>, 235 | make_list(JoinExpr, fun(Val) -> expr(Val, Safe)end) 236 | ] 237 | end, Joins), 238 | [expr2(Table, Safe), S1]; 239 | join(Table, Safe) -> 240 | expr2(Table, Safe). 241 | 242 | join(join) -> 243 | <<" JOIN ">>; 244 | join({left, join}) -> 245 | <<" LEFT JOIN ">>; 246 | join({inner, join}) -> 247 | <<" INNER JOIN ">>; 248 | join({right, join}) -> 249 | <<" RIGHT JOIN ">>; 250 | join({left, outer, join}) -> 251 | <<" LEFT OUTER JOIN ">>; 252 | join({right, outer, join}) -> 253 | <<" RIGHT OUTER JOIN ">>; 254 | join({full, outer, join}) -> 255 | <<" FULL OUTER JOIN ">>; 256 | join({cross, join}) -> 257 | <<" CROSS JOIN ">>. 258 | 259 | where(undefined, _) -> []; 260 | where(Expr, true) when is_list(Expr); is_binary(Expr) -> 261 | throw({error, {unsafe_expression, Expr}}); 262 | where(Expr, false) when is_binary(Expr) -> 263 | Res = case Expr of 264 | <<"WHERE ", _Rest/binary>> = Expr1 -> 265 | Expr1; 266 | <<"where ", Rest/binary>> -> 267 | <<"WHERE ", Rest/binary>>; 268 | Expr1 -> 269 | <<"WHERE ", Expr1/binary>> 270 | end, 271 | [$\s, Res]; 272 | where(Exprs, false) when is_list(Exprs)-> 273 | where(list_to_binary(Exprs), false); 274 | where(Expr, Safe) when is_tuple(Expr) -> 275 | case expr(Expr, Safe) of 276 | undefined -> []; 277 | Other -> [<<" WHERE ">>, Other] 278 | end. 279 | 280 | extra_clause(undefined, _Safe) -> undefined; 281 | extra_clause(Expr, true) when is_binary(Expr) -> 282 | throw({error, {unsafe_expression, Expr}}); 283 | extra_clause(Expr, false) when is_binary(Expr) -> [$\s, Expr]; 284 | extra_clause([Expr], false) when is_binary(Expr) -> [$\s, Expr]; 285 | extra_clause(Exprs, Safe) when is_list(Exprs) -> 286 | case is_tuple(hd(Exprs)) of 287 | true -> 288 | extra_clause2(Exprs, false); 289 | false -> 290 | if not Safe -> 291 | [$\s, list_to_binary(Exprs)]; 292 | true -> 293 | throw({error, {unsafe_expression, Exprs}}) 294 | end 295 | end; 296 | extra_clause(Exprs, true) when is_list(Exprs) -> 297 | extra_clause2(Exprs, true); 298 | extra_clause({limit, Num}, _Safe) -> 299 | [<<" LIMIT ">>, encode(Num)]; 300 | extra_clause({limit, Offset, Num}, _Safe) -> 301 | [<<" LIMIT ">>, encode(Offset), <<", ">> , encode(Num)]; 302 | extra_clause({group_by, ColNames}, _Safe) -> 303 | [<<" GROUP BY ">>, make_list(ColNames, fun convert/1)]; 304 | extra_clause({group_by, ColNames, having, Expr}, Safe) -> 305 | [extra_clause({group_by, ColNames}, Safe), <<" HAVING ">>, 306 | expr(Expr, Safe)]; 307 | extra_clause({order_by, ColNames}, Safe) -> 308 | [<<" ORDER BY ">>, 309 | make_list(ColNames, fun({Name, Modifier}) when Modifier =:= 'asc' -> 310 | [expr(Name, Safe), $\s, convert('ASC')]; 311 | ({Name, Modifier}) when Modifier =:= 'desc' -> 312 | [expr(Name, Safe), $\s, convert('DESC')]; 313 | (Name) -> 314 | expr(Name, Safe) 315 | end)]. 316 | 317 | extra_clause2(Exprs, Safe) -> 318 | Res = [extra_clause(Expr,Safe) || Expr <- Exprs, Expr =/= undefined], 319 | [Res]. 320 | 321 | insert(Table, Params) when is_list(Params) -> 322 | Names = make_list(Params, fun({Name, _}) -> convert(Name) end), 323 | Values = [$(, make_list(Params, fun({_, Val}) -> encode(Val) end), $)], 324 | make_insert_query(Table, Names, Values); 325 | insert(Table, {Fields, Records}) -> 326 | Names = make_list(Fields, fun convert/1), 327 | Values = make_list(Records, fun(Record) -> 328 | Record1 = if is_tuple(Record) -> tuple_to_list(Record); 329 | true -> Record 330 | end, 331 | [$(, make_list(Record1, fun encode/1), $)] 332 | end), 333 | make_insert_query(Table, Names, Values). 334 | 335 | insert(Table, Params, undefined, _Safe) -> 336 | insert(Table, Params); 337 | insert(Table, Params, {returning, Ret}, Safe) -> 338 | insert(Table, Params, Ret, Safe); 339 | insert(Table, Params, Returning, Safe) -> 340 | [insert(Table, Params), <<" RETURNING ">>, expr(Returning, Safe)]. 341 | 342 | make_insert_query(Table, Names, Values) -> 343 | [<<"INSERT INTO ">>, convert(Table), 344 | <<"(">>, Names, <<") VALUES ">>, Values]. 345 | 346 | update(Table, Props, Safe) -> 347 | update(Table, Props, undefined, Safe). 348 | 349 | update(Table, Props, Where, Safe) when not is_list(Props) -> 350 | update(Table, [Props], Where, Safe); 351 | update(Table, Props, Where, Safe) -> 352 | S1 = case Table of 353 | Table when is_tuple(Table) -> 354 | join(Table, Safe); 355 | _Other -> 356 | convert(Table) 357 | end, 358 | S2 = make_list(Props, fun({Field, Val}) -> 359 | [convert(Field), <<" = ">>, expr(Val, Safe)] 360 | end), 361 | [<<"UPDATE ">>, S1, <<" SET ">>, S2, where(Where, Safe)]. 362 | 363 | delete(Table, Safe) -> 364 | delete(Table, undefined, undefined, undefined, Safe). 365 | 366 | delete(Table, Using, WhereExpr, Safe) -> 367 | delete(Table, Using, WhereExpr, undefined, Safe). 368 | 369 | delete(Table, Using, WhereExpr, Extras, Safe) -> 370 | S1 = case Table of 371 | Table when is_tuple(Table) -> 372 | join(Table, Safe); 373 | _Other -> 374 | convert(Table) 375 | end, 376 | S2 = [<<"DELETE FROM ">>, S1], 377 | S3 = if Using =:= undefined -> S2; 378 | true -> [S2, <<" USING ">>, make_list(Using, fun convert/1)] 379 | end, 380 | S4 = case where(WhereExpr, Safe) of 381 | undefined -> S3; 382 | WhereClause -> [S3, WhereClause] 383 | end, 384 | if Extras =:= undefined -> 385 | S4; 386 | true -> 387 | [S4, extra_clause(Extras, Safe)] 388 | end. 389 | 390 | convert(Val) when is_atom(Val)-> 391 | atom_to_binary(Val, utf8). 392 | 393 | make_list(Vals, ConvertFun) when is_list(Vals) -> 394 | string:join([[ConvertFun(Val)] || Val <- Vals],", "); 395 | make_list(Val, ConvertFun) -> 396 | ConvertFun(Val). 397 | 398 | expr(undefined, _Safe) -> <<"NULL">>; 399 | expr({Not, Expr}, Safe) when (Not =:= 'not' orelse Not =:= '!') -> 400 | [<<"NOT ">>, check_expr(Expr, Safe)]; 401 | expr({Table, Field}, _Safe) when is_atom(Table), is_atom(Field) -> 402 | [convert(Table), $., convert(Field)]; 403 | expr({Expr1, as, Alias}, Safe) when is_atom(Alias) -> 404 | [expr2(Expr1, Safe), <<" AS ">>, convert(Alias)]; 405 | expr({call, FuncName, []}, _Safe) -> 406 | [convert(FuncName), <<"()">>]; 407 | expr({call, FuncName, Params}, _Safe) -> 408 | [convert(FuncName), $(, make_list(Params, fun param/1), $)]; 409 | expr({Val, Op, {select, _} = Subquery}, Safe) -> 410 | subquery(Val, Op, Subquery, Safe); 411 | expr({Val, Op, {select, _, _} = Subquery}, Safe) -> 412 | subquery(Val, Op, Subquery, Safe); 413 | expr({Val, Op, {select, _, _, _} = Subquery}, Safe) -> 414 | subquery(Val, Op, Subquery, Safe); 415 | expr({Val, Op, {select, _, _, _, _} = Subquery}, Safe) -> 416 | subquery(Val, Op, Subquery, Safe); 417 | expr({Val, Op, {select, _, _, _, _, _} = Subquery}, Safe) -> 418 | subquery(Val, Op, Subquery, Safe); 419 | expr({Val, Op, {select, _, _, _, _, _, _} = Subquery}, Safe) -> 420 | subquery(Val, Op, Subquery, Safe); 421 | expr({Val, Op, {_, union, _} = Subquery}, Safe) -> 422 | subquery(Val, Op, Subquery, Safe); 423 | expr({Val, Op, {_, union, _, _} = Subquery}, Safe) -> 424 | subquery(Val, Op, Subquery, Safe); 425 | expr({Val, Op, {_, union, _, _, _} = Subquery}, Safe) -> 426 | subquery(Val, Op, Subquery, Safe); 427 | expr({_, in, []}, _Safe) -> <<"0">>; 428 | expr({Val, Op, Values}, Safe) when (Op =:= in orelse 429 | Op =:= any orelse 430 | Op =:= some) andalso is_list(Values) -> 431 | [expr2(Val, Safe), subquery_op(Op), make_list(Values, fun encode/1), $)]; 432 | expr({undefined, Op, Expr2}, Safe) when Op =:= 'and'; Op =:= 'not' -> 433 | expr(Expr2, Safe); 434 | expr({Expr1, Op, undefined}, Safe) when Op =:= 'and'; Op =:= 'not' -> 435 | expr(Expr1, Safe); 436 | expr({Expr1, Op, Expr2}, Safe) -> 437 | {B1, B2} = if (Op =:= 'and' orelse Op =:= 'or') -> 438 | {check_expr(Expr1, Safe), check_expr(Expr2, Safe)}; 439 | true -> 440 | {expr2(Expr1, Safe), expr2(Expr2, Safe)} 441 | end, 442 | [$(, B1, $\s, op(Op), $\s, B2, $)]; 443 | 444 | expr({list, Vals}, _Safe) when is_list(Vals) -> 445 | [$(, make_list(Vals, fun encode/1), $)]; 446 | expr({Op, Exprs}, Safe) when is_list(Exprs) -> 447 | Res = [[expr(Expr,Safe)] || Expr <- Exprs ], 448 | [$(, string:join(Res,[$\s, op(Op), $\s]), $)]; 449 | expr('?', _Safe) -> $?; 450 | expr(null, _Safe) -> <<"NULL">>; 451 | expr(Val, _Safe) when is_atom(Val) -> convert(Val); 452 | expr(Val, _Safe) -> encode(Val). 453 | 454 | check_expr(Expr, Safe) when is_list(Expr); is_binary(Expr) -> 455 | if Safe -> throw({error, {unsafe_expression, Expr}}); 456 | true -> iolist_to_binary([$(, Expr, $)]) 457 | end; 458 | check_expr(Expr, Safe) -> expr(Expr, Safe). 459 | 460 | op(Op) -> convert(op1(Op)). 461 | op1('and') -> 'AND'; 462 | op1('or') -> 'OR'; 463 | op1(like) -> 'LIKE'; 464 | op1(Op) -> Op. 465 | 466 | subquery(Val, Op, Subquery, Safe) -> 467 | [expr2(Val, Safe), subquery_op(Op), sql2(Subquery, Safe), $)]. 468 | 469 | subquery_op(in) -> <<" IN (">>; 470 | subquery_op(any) -> <<" ANY (">>; 471 | subquery_op(some) -> <<" SOME (">>. 472 | 473 | expr2(undefined, _Safe) -> <<"NULL">>; 474 | expr2(Expr, _Safe) when is_atom(Expr) -> convert(Expr); 475 | expr2(Expr, Safe) -> expr(Expr, Safe). 476 | 477 | param({call, FuncName, []}) -> 478 | [convert(FuncName), <<"()">>]; 479 | param({call, FuncName, Params}) -> 480 | [convert(FuncName), $(, make_list(Params, fun param/1), $)]; 481 | param({Key, Value}) when is_atom(Key) -> 482 | [convert(Key), <<" := ">>, encode(Value)]; 483 | param(Key) when is_atom(Key) -> 484 | convert(Key); 485 | param(Value) -> 486 | encode(Value). 487 | 488 | quote(String) when is_list(String) -> 489 | [$' | lists:reverse([$' | quote(String, [])])]; 490 | quote(Bin) when is_binary(Bin) -> 491 | list_to_binary(quote(binary_to_list(Bin))). 492 | 493 | quote([], Acc) -> 494 | Acc; 495 | quote([$\0 | Rest], Acc) -> 496 | quote(Rest, [$0, $\\ | Acc]); 497 | quote([$\n | Rest], Acc) -> 498 | quote(Rest, [$n, $\\ | Acc]); 499 | quote([$\r | Rest], Acc) -> 500 | quote(Rest, [$r, $\\ | Acc]); 501 | quote([$\\ | Rest], Acc) -> 502 | quote(Rest, [$\\ , $\\ | Acc]); 503 | quote([$' | Rest], Acc) -> 504 | quote(Rest, [$', $\\ | Acc]); 505 | quote([$" | Rest], Acc) -> 506 | quote(Rest, [$", $\\ | Acc]); 507 | quote([$\^Z | Rest], Acc) -> 508 | quote(Rest, [$Z, $\\ | Acc]); 509 | quote([C | Rest], Acc) -> 510 | quote(Rest, [C | Acc]). 511 | -------------------------------------------------------------------------------- /test/sqerl_tests.erl: -------------------------------------------------------------------------------- 1 | -module(sqerl_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | -define(_safe_test(Expr), 6 | (fun(Expect, _) -> 7 | {Expect, ?_assertEqual(Expect, sqerl:sql(Expr, true))} 8 | end)). 9 | 10 | -define(_unsafe_test(Expr), 11 | (fun(Expect, _) -> 12 | {Expect, ?_assertEqual(Expect, sqerl:unsafe_sql(Expr, true))} 13 | end)). 14 | 15 | safe_test_() -> 16 | {foreachx, 17 | fun (_) -> ok end, 18 | [ 19 | {<<"INSERT INTO project(foo, baz) VALUES (5, 'bob')">>, 20 | ?_safe_test({insert,project,[{foo,5},{baz,"bob"}]}) 21 | }, 22 | 23 | {<<"INSERT INTO project(foo, bar, baz) VALUES ('a', 'b', 'c'), ('d', 'e', 'f')">>, 24 | ?_safe_test({insert,project,{[foo,bar,baz],[[a,b,c],[d,e,f]]}}) 25 | }, 26 | 27 | {<<"INSERT INTO project(foo, bar, baz) VALUES ('a', 'b', 'c'), ('d', 'e', 'f')">>, 28 | ?_safe_test({insert,project,{[foo,bar,baz],[{a,b,c},{d,e,f}]}}) 29 | }, 30 | 31 | {<<"INSERT INTO Documents(projectid, documentpath) VALUES (42, '/') RETURNING documentid">>, 32 | ?_safe_test({insert,'Documents', 33 | [{projectid,42},{documentpath,"/"}], 34 | {returning,documentid}}) 35 | }, 36 | 37 | {<<"UPDATE project SET foo = 5, bar = 6, baz = 'hello'">>, 38 | ?_safe_test({update,project,[{foo,5},{bar,6},{baz,"hello"}]}) 39 | }, 40 | 41 | {<<"UPDATE project SET foo = 'quo\\'ted', baz = blub WHERE NOT (a = 5)">>, 42 | ?_safe_test({update,project, 43 | [{foo,"quo'ted"},{baz,blub}], 44 | {where,{'not',{a,'=',5}}}}) 45 | }, 46 | 47 | {<<"UPDATE project JOIN client ON (project.client_id = client.id) SET foo = 5">>, 48 | ?_safe_test({update, 49 | {project,join,client,{'project.client_id','=','client.id'}},[{foo,5}]}) 50 | }, 51 | 52 | {<<"UPDATE project INNER JOIN client ON (project.client_id = client.id) SET foo = 5">>, 53 | ?_safe_test({update, 54 | {project,{inner,join},client,{'project.client_id','=','client.id'}},[{foo,5}]}) 55 | }, 56 | 57 | {<<"DELETE FROM project">>, 58 | ?_safe_test({delete,project}) 59 | }, 60 | 61 | {<<"DELETE FROM project WHERE (a = 5)">>, 62 | ?_safe_test({delete,project,{a,'=',5}}) 63 | }, 64 | 65 | {<<"DELETE FROM project JOIN client ON (project.client_id = client.id) WHERE (client.a = 8)">>, 66 | ?_safe_test({delete,{project,join,client, 67 | {'project.client_id','=','client.id'}},{'client.a','=',8}}) 68 | }, 69 | 70 | {<<"DELETE FROM project WHERE (a = 5)">>, 71 | ?_safe_test({delete,{from,project},{where,{a,'=',5}}}) 72 | }, 73 | 74 | {<<"DELETE FROM developer WHERE NOT ((name LIKE '%Paul%') OR (name LIKE '%Gerber%'))">>, 75 | ?_safe_test({delete,developer, 76 | {'not',{{name,like,"%Paul%"}, 77 | 'or', 78 | {name,like,"%Gerber%"}}}}) 79 | }, 80 | 81 | {<<"SELECT 'foo'">>, 82 | ?_safe_test({select,["foo"]}) 83 | }, 84 | 85 | {<<"SELECT 'foo', 'bar'">>, 86 | ?_safe_test({select,["foo","bar"]}) 87 | }, 88 | 89 | {<<"SELECT (1 + 1)">>, 90 | ?_safe_test({select,{1,'+',1}}) 91 | }, 92 | 93 | {<<"SELECT foo AS bar FROM baz AS blub">>, 94 | ?_safe_test({select,{foo,as,bar},{from,{baz,as,blub}}}) 95 | }, 96 | 97 | {<<"SELECT name FROM developer WHERE (country = 'quoted \\' \\\" string')">>, 98 | ?_safe_test({select,name, 99 | {from,developer}, 100 | {where,{country,'=',"quoted ' \" string"}}}) 101 | }, 102 | 103 | {<<"SELECT p.name AS name, p.age AS age, project.* FROM person AS p, project">>, 104 | ?_safe_test({select,[{{p,name},as,name},{{p,age},as,age},{project,'*'}], 105 | {from,[{person,as,p},project]}}) 106 | }, 107 | 108 | {<<"SELECT count(name) FROM developer">>, 109 | ?_safe_test({select,{call,count,[name]},{from,developer}}) 110 | }, 111 | 112 | {<<"SELECT count(name) AS c FROM developer">>, 113 | ?_safe_test({select,{{call,count,[name]},as,c},{from,developer}}) 114 | }, 115 | 116 | {<<"SELECT CONCAT('-- [', GROUP_CONCAT(comment.id), ']') AS comments FROM posts">>, 117 | ?_safe_test({select,{{call,'CONCAT',["-- [",{call,'GROUP_CONCAT',['comment.id']},"]"]},as,comments},{from,posts}}) 118 | }, 119 | 120 | {<<"SELECT last_insert_id()">>, 121 | ?_safe_test({select,{call,last_insert_id,[]}}) 122 | }, 123 | 124 | {<<"(SELECT name FROM person) UNION (SELECT name FROM project)">>, 125 | ?_safe_test({{select,name,{from,person}}, 126 | union, 127 | {select,name,{from,project}}}) 128 | }, 129 | 130 | {<<"SELECT DISTINCT name FROM person LIMIT 5">>, 131 | ?_safe_test({select,distinct,name,{from,person},{limit,5}}) 132 | }, 133 | 134 | {<<"SELECT name, age FROM person ORDER BY name DESC, age">>, 135 | ?_safe_test({select,[name,age],{from,person},{order_by,[{name,desc},age]}}) 136 | }, 137 | 138 | {<<"SELECT count(name), age FROM developer GROUP BY age">>, 139 | ?_safe_test({select,[{call,count,[name]},age], 140 | {from,developer}, 141 | {group_by,age}}) 142 | }, 143 | 144 | {<<"SELECT count(name), age, country FROM developer GROUP BY age, country HAVING (age > 20)">>, 145 | ?_safe_test({select,[{call,count,[name]},age,country], 146 | {from,developer}, 147 | {group_by,[age,country],having,{age,'>',20}}}) 148 | }, 149 | 150 | {<<"SELECT * FROM developer WHERE name IN ('Paul', 'Frank')">>, 151 | ?_safe_test({select,'*', 152 | {from,developer}, 153 | {where,{name,in,["Paul","Frank"]}}}) 154 | }, 155 | 156 | {<<"SELECT name FROM developer WHERE name IN (SELECT DISTINCT name FROM gymnast)">>, 157 | ?_safe_test({select,name, 158 | {from,developer}, 159 | {where,{name,in, 160 | {select,distinct,name,{from,gymnast}}}}}) 161 | }, 162 | 163 | {<<"SELECT name FROM developer WHERE name IN ((SELECT DISTINCT name FROM gymnast) " 164 | "UNION (SELECT name FROM dancer WHERE ((name LIKE 'Mikhail%') OR (country = 'Russia'))) " 165 | "WHERE (name LIKE 'M%') ORDER BY name DESC LIMIT 5, 10)">>, 166 | ?_safe_test({select,name, 167 | {from,developer}, 168 | {where, 169 | {name,in, 170 | {{select,distinct,name,{from,gymnast}}, 171 | union, 172 | {select,name, 173 | {from,dancer}, 174 | {where, 175 | {{name,like,"Mikhail%"}, 176 | 'or', 177 | {country,'=',"Russia"}}}}, 178 | {where,{name,like,"M%"}}, 179 | [{order_by,{name,desc}},{limit,5,10}]}}}}) 180 | }, 181 | 182 | {<<"SELECT * FROM developer WHERE (name = ?)">>, 183 | ?_safe_test({select,'*',{from,developer},{where,{name,'=','?'}}}) 184 | }, 185 | 186 | {<<"SELECT * FROM foo WHERE (a = (1 + 2 + 3))">>, 187 | ?_safe_test({select,'*',{from,foo},{where,{a,'=',{'+',[1,2,3]}}}}) 188 | }, 189 | 190 | {<<"SELECT * FROM foo WHERE ((a + b + c) = (d + e + f))">>, 191 | ?_safe_test({select,'*', 192 | {from,foo}, 193 | {where,{'=',[{'+',[a,b,c]},{'+',[d,e,f]}]}}}) 194 | }, 195 | 196 | {<<"SELECT * FROM foo WHERE ((a = b) AND (c = d) AND (e = f))">>, 197 | ?_safe_test({select,'*', 198 | {from,foo}, 199 | {where,{'and',[{a,'=',b},{c,'=',d},{e,'=',f}]}}}) 200 | }, 201 | 202 | {<<"SELECT (1 + 2 + 3 + 4)">>, 203 | ?_safe_test({select,{'+',[1,2,3,4]}}) 204 | }, 205 | 206 | {<<"SELECT * FROM blah">>, 207 | ?_safe_test({select,'*',{from,blah},undefined,undefined}) 208 | }, 209 | 210 | {<<"SELECT name FROM search_people(age := 18)">>, 211 | ?_safe_test({select,name,{from,{call,search_people,[{age, 18}]}}}) 212 | }, 213 | {<<"SELECT * FROM foo JOIN bar ON (foo.bar_id = bar.id)">>, 214 | ?_safe_test({select,'*',{from,{foo,join,bar,{'foo.bar_id','=','bar.id'}}}}) 215 | }, 216 | {<<"SELECT * FROM foo AS f JOIN bar AS b ON (f.bar_id = b.id)">>, 217 | ?_safe_test({select,'*',{from,{{foo,as,f},join,{bar,as,b},{'f.bar_id','=','b.id'}}}}) 218 | }, 219 | {<<"SELECT * FROM foo JOIN bar ON ((foo.bar_id = bar.id) AND (foo.bar_type = bar.type))">>, 220 | ?_safe_test({select,'*',{from,{foo,join,bar,[ 221 | {'and', [ 222 | {'foo.bar_id','=','bar.id'}, 223 | {'foo.bar_type','=','bar.type'} 224 | ] 225 | }]}}}) 226 | }, 227 | {<<"SELECT * FROM foo LEFT JOIN bar ON (foo.bar_id = bar.id)">>, 228 | ?_safe_test({select,'*',{from,{foo,{left,join},bar,{'foo.bar_id','=','bar.id'}}}}) 229 | }, 230 | {<<"SELECT * FROM foo INNER JOIN bar ON (foo.bar_id = bar.id)">>, 231 | ?_safe_test({select,'*',{from,{foo,{inner,join},bar,{'foo.bar_id','=','bar.id'}}}}) 232 | }, 233 | {<<"SELECT * FROM foo RIGHT JOIN bar ON (foo.bar_id = bar.id)">>, 234 | ?_safe_test({select,'*',{from,{foo,{right,join},bar,{'foo.bar_id','=','bar.id'}}}}) 235 | }, 236 | {<<"SELECT * FROM foo LEFT OUTER JOIN bar ON (foo.bar_id = bar.id)">>, 237 | ?_safe_test({select,'*',{from,{foo,{left,outer,join},bar,{'foo.bar_id','=','bar.id'}}}}) 238 | }, 239 | {<<"SELECT * FROM foo CROSS JOIN bar ON (foo.bar_id = bar.id)">>, 240 | ?_safe_test({select,'*',{from,{foo,{cross,join},bar,{'foo.bar_id','=','bar.id'}}}}) 241 | }, 242 | {<<"SELECT * FROM foo JOIN bar ON (foo.bar_id = bar.id) JOIN baz ON (bar.baz_id = baz.id)">>, 243 | ?_safe_test({select,'*',{from,{foo,[ {join,bar,{'foo.bar_id','=','bar.id'}}, 244 | {join,baz,{'bar.baz_id','=','baz.id'}} ]}}}) 245 | } 246 | ] 247 | }. 248 | 249 | unsafe_test_() -> 250 | {foreachx, 251 | fun (_) -> ok end, 252 | [ 253 | {<<"SELECT * FROM foo WHERE a = b">>, 254 | ?_unsafe_test({select,'*',{from,foo},"WHERE a = b"}) 255 | }, 256 | 257 | {<<"SELECT * FROM foo WHERE a = 'foo'">>, 258 | ?_unsafe_test({select,'*',{from,foo},"WHERE a = 'foo'"}) 259 | }, 260 | 261 | {<<"SELECT * FROM foo WHERE a = 'i'm an evil query'">>, 262 | ?_unsafe_test({select,'*',{from,foo},<<"WHERE a = 'i'm an evil query'">>}) 263 | }, 264 | 265 | {<<"SELECT * FROM foo WHERE a = b">>, 266 | ?_unsafe_test({select,'*',{from,foo},<<"WHERE a = b">>}) 267 | }, 268 | 269 | {<<"SELECT * FROM foo WHERE a = b">>, 270 | ?_unsafe_test({select,'*',{from,foo},{where,"a = b"}}) 271 | }, 272 | 273 | {<<"SELECT * FROM foo WHERE a = b">>, 274 | ?_unsafe_test({select,'*',{from,foo},{where,<<"a = b">>}}) 275 | }, 276 | 277 | {<<"SELECT * FROM foo WHERE a IN (SELECT * FROM bar WHERE a = b)">>, 278 | ?_unsafe_test({select,'*', 279 | {from,foo}, 280 | {where,{a,in, 281 | {select,'*',{from,bar},{where,"a = b"}}}}}) 282 | }, 283 | 284 | {<<"SELECT * FROM foo WHERE a IN (SELECT * FROM bar WHERE a = b)">>, 285 | ?_unsafe_test({select,'*', 286 | {from,foo}, 287 | {where, 288 | {a,in,{select,'*',{from,bar},{where,<<"a = b">>}}}}}) 289 | }, 290 | 291 | {<<"SELECT * FROM foo WHERE a IN (SELECT * FROM bar WHERE a = b)">>, 292 | ?_unsafe_test({select,'*', 293 | {from,foo}, 294 | {where, 295 | {a,in,{select,'*',{from,bar},<<"WHERE a = b">>}}}}) 296 | }, 297 | 298 | {<<"SELECT * FROM foo WHERE a = b LIMIT 5">>, 299 | ?_unsafe_test({select,'*',{from,foo},{where,<<"a = b">>},<<"LIMIT 5">>}) 300 | }, 301 | 302 | {<<"(SELECT * FROM foo WHERE a = b) UNION (SELECT * FROM bar)">>, 303 | ?_unsafe_test({{select,'*',{from,foo},{where,<<"a = b">>}}, 304 | union, 305 | {select,'*',{from,bar}}}) 306 | }, 307 | 308 | {<<"(SELECT * FROM foo) UNION (SELECT * FROM bar WHERE a = b)">>, 309 | ?_unsafe_test({{select,'*',{from,foo}}, 310 | union, 311 | {select,'*',{from,bar},{where,<<"a = b">>}}}) 312 | }, 313 | 314 | {<<"(SELECT * FROM foo) UNION (SELECT * FROM bar) WHERE a = b">>, 315 | ?_unsafe_test({{select,'*',{from,foo}}, 316 | union, 317 | {select,'*',{from,bar}}, 318 | {where,"a = b"}}) 319 | }, 320 | 321 | {<<"SELECT (a OR (foo))">>, 322 | ?_unsafe_test({select,{a,'or',"foo"}}) 323 | }, 324 | 325 | {<<"SELECT ((bar) OR b)">>, 326 | ?_unsafe_test({select,{"bar",'or',b}}) 327 | }, 328 | 329 | {<<"SELECT ((foo) AND (bar))">>, 330 | ?_unsafe_test({select,{"foo",'and',<<"bar">>}}) 331 | }, 332 | 333 | {<<"SELECT NOT (foo = bar)">>, 334 | ?_unsafe_test({select,{'not',"foo = bar"}}) 335 | }, 336 | 337 | {<<"SELECT NOT (foo = bar)">>, 338 | ?_unsafe_test({select,{'!',"foo = bar"}}) 339 | } 340 | ] 341 | }. 342 | --------------------------------------------------------------------------------