From 70a42cab2c540c4eed77ffc09fab8bd966232d33 Mon Sep 17 00:00:00 2001 From: Alexey Romanov Date: Thu, 7 Oct 2010 14:22:29 +0400 Subject: [PATCH] Removed remaining io:format calls when constructing queries --- Emakefile.in | 4 +- include/sqlite3.hrl | 2 + src/sqlite3.erl | 35 +++++++++++++ src/sqlite3_lib.erl | 118 ++++++++++++++++++++++++++++---------------- 4 files changed, 114 insertions(+), 45 deletions(-) create mode 100644 include/sqlite3.hrl diff --git a/Emakefile.in b/Emakefile.in index a75dffe..ce9021e 100644 --- a/Emakefile.in +++ b/Emakefile.in @@ -1,3 +1 @@ -{'src/sqlite3_lib.erl',[debug_info,{outdir,"ebin"}, {d, 'NULL_ATOM', '@NULL_ATOM@'}]}. -{'src/sqlite3.erl',[debug_info,{outdir,"ebin"}]}. -{'src/sqlite3_test.erl',[debug_info,{outdir,"ebin"}]}. +{'src/*', [debug_info, {outdir, "ebin"}, {i, "include"}]}. diff --git a/include/sqlite3.hrl b/include/sqlite3.hrl new file mode 100644 index 0000000..b44e343 --- /dev/null +++ b/include/sqlite3.hrl @@ -0,0 +1,2 @@ +-define(NULL_ATOM, null). +-type(sql_value() :: number() | ?NULL_ATOM | iodata()). diff --git a/src/sqlite3.erl b/src/sqlite3.erl index 6780143..90e5f1e 100644 --- a/src/sqlite3.erl +++ b/src/sqlite3.erl @@ -7,6 +7,7 @@ %%% @end %%%------------------------------------------------------------------- -module(sqlite3). +-include("sqlite3.hrl"). -behaviour(gen_server). @@ -27,6 +28,8 @@ -export([create_function/3]). +-export([value_to_sql/1, value_to_sql_unsafe/1]). + %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -372,6 +375,38 @@ drop_table(Db, Tbl) -> create_function(Db, FunctionName, Function) -> gen_server:call(Db, {create_function, FunctionName, Function}). +%%-------------------------------------------------------------------- +%% @spec value_to_sql_unsafe(Value :: sql_value()) -> iodata() +%% @doc +%% Converts an Erlang term to an SQL string. +%% Currently supports integers, floats, 'null' atom, and iodata +%% (binaries and iolists) which are treated as SQL strings. +%% +%% Note that it opens opportunity for injection if an iolist includes +%% single quotes! Replace all single quotes (') with '' manually, or +%% use value_to_sql/1 if you are not sure if your strings contain +%% single quotes (e.g. can be entered by users). +%% +%% Reexported from sqlite3_lib:value_to_sql/1 for user convenience. +%% @end +%%-------------------------------------------------------------------- +-spec(value_to_sql_unsafe/1::(sql_value()) -> iodata()). +value_to_sql_unsafe(X) -> sqlite3_lib:value_to_sql_unsafe(X). + +%%-------------------------------------------------------------------- +%% @spec value_to_sql(Value :: sql_value()) -> iodata() +%% @doc +%% Converts an Erlang term to an SQL string. +%% Currently supports integers, floats, 'null' atom, and iodata +%% (binaries and iolists) which are treated as SQL strings. +%% +%% All single quotes (') will be replaced with ''. +%% +%% Reexported from sqlite3_lib:value_to_sql/1 for user convenience. +%% @end +%%-------------------------------------------------------------------- +-spec(value_to_sql/1::(sql_value()) -> iolist()). +value_to_sql(X) -> sqlite3_lib:value_to_sql(X). %%==================================================================== %% gen_server callbacks diff --git a/src/sqlite3_lib.erl b/src/sqlite3_lib.erl index 20ef8f0..532662a 100644 --- a/src/sqlite3_lib.erl +++ b/src/sqlite3_lib.erl @@ -7,13 +7,15 @@ %%% @end %%%------------------------------------------------------------------- -module(sqlite3_lib). +-include("sqlite3.hrl"). %% API -export([col_type/1]). +-export([value_to_sql/1, value_to_sql_unsafe/1, escape/1]). -export([write_value_sql/1, write_col_sql/1]). -export([create_table_sql/2, write_sql/2, read_sql/3, delete_sql/3, drop_table/1]). --export ([update_sql/4, update_set_sql/1, replace/1]). --export ([read_sql/4, read_cols_sql/1]). +-export([update_sql/4, update_set_sql/1, replace/1]). +-export([read_sql/4, read_cols_sql/1]). %%==================================================================== %% API @@ -41,21 +43,56 @@ col_type(date) -> col_type("DATE") -> date. +%%-------------------------------------------------------------------- +%% @spec value_to_sql_unsafe(Value :: sql_value()) -> iodata() +%% @doc +%% Converts an Erlang term to an SQL string. +%% Currently supports integers, floats, 'null' atom, and iodata +%% (binaries and iolists) which are treated as SQL strings. +%% +%% Note that it opens opportunity for injection if an iolist includes +%% single quotes! Replace all single quotes (') with '' manually, or +%% use value_to_sql/1 if you are not sure if your strings contain +%% single quotes (e.g. can be entered by users). +%% @end +%%-------------------------------------------------------------------- +-spec(value_to_sql_unsafe/1::(sql_value()) -> iodata()). +value_to_sql_unsafe(X) -> + if + is_integer(X) -> integer_to_list(X); + is_float(X) -> float_to_list(X); + X == ?NULL_ATOM -> "NULL"; + true -> [$', X, $'] %% assumes no $' inside strings! + end. + +%%-------------------------------------------------------------------- +%% @spec value_to_sql(Value :: sql_value()) -> iodata() +%% @doc +%% Converts an Erlang term to an SQL string. +%% Currently supports integers, floats, 'null' atom, and iodata +%% (binaries and iolists) which are treated as SQL strings. +%% +%% All single quotes (') will be replaced with ''. +%% @end +%%-------------------------------------------------------------------- +-spec(value_to_sql/1::(sql_value()) -> iolist()). +value_to_sql(X) -> + if + is_integer(X) -> integer_to_list(X); + is_float(X) -> float_to_list(X); + X == ?NULL_ATOM -> "NULL"; + true -> [$', escape(X), $'] + end. + %%-------------------------------------------------------------------- %% @spec write_value_sql(Value :: [term()]) -> string() %% @doc %% Creates the values portion of the sql stmt. -%% Currently only support integer, double/float and strings. %% @end %%-------------------------------------------------------------------- -spec(write_value_sql/1::(any()) -> string()). write_value_sql(Values) -> - StrValues = lists:map(fun - (X) when is_integer(X) -> integer_to_list(X); - (X) when is_float(X) -> float_to_list(X); - (?NULL_ATOM) -> "null"; - (X) -> io_lib:format("'~s'", [X]) - end, Values), + StrValues = lists:map(fun value_to_sql/1, Values), intersperse(StrValues, ","). %%-------------------------------------------------------------------- @@ -83,26 +120,32 @@ replace([$" | T]) -> replace([H | T]) -> [H | replace(T)]. +%%-------------------------------------------------------------------- +%% @spec escape(IoData :: iodata()) -> iodata() +%% +%% @doc Returns copy of IoData with all ' replaced by '' +%% @end +%%-------------------------------------------------------------------- +-spec(escape/1::(iodata()) -> iodata()). +escape(IoData) -> re:replace(IoData, "'", "''", [global]). + %%-------------------------------------------------------------------- %% @spec update_set_sql ([{Col, Value}]) -> string () %% Col = atom () -%% Value = integer () | float () | string () +%% Value = number () | atom () | string () %% @doc %% Creates update set stmt. -%% Currently only supports integer, double/float and strings. +%% Currently supports integer, double/float and strings. %% For strings \" replaced with '. %% @end %%-------------------------------------------------------------------- -spec (update_set_sql/1::(any ()) -> string ()). update_set_sql (Data) -> Set = lists:map (fun - ({Col, Value}) when is_integer (Value) -> - [atom_to_list (Col), " = ", integer_to_list (Value)]; - ({Col, Value}) when is_float (Value) -> - [atom_to_list (Col), " = ", float_to_list (Value)]; - ({Col, Value}) -> - [atom_to_list (Col), " = ", - io_lib:format ("\"~s\"", [replace (Value)])] + ({Col, Value}) when is_list(Value) -> + [atom_to_list (Col), " = ", $", replace(Value), $"]; + ({Col, Value}) -> + [atom_to_list (Col), " = ", value_to_sql(Value)] end, Data), intersperse(Set, ", "). @@ -128,11 +171,11 @@ read_cols_sql (Columns) -> %%-------------------------------------------------------------------- -spec(create_table_sql/2::(atom(), [{atom(), string()}]) -> string()). create_table_sql(Tbl, [{ColName, Type} | Tl]) -> - CT = io_lib:format("CREATE TABLE ~p ", [Tbl]), - Start = io_lib:format("(~p ~s PRIMARY KEY, ", [ColName, sqlite3_lib:col_type(Type)]), + CT = ["CREATE TABLE ", atom_to_list(Tbl), " "], + Start = ["(", atom_to_list(ColName), " ", sqlite3_lib:col_type(Type), " PRIMARY KEY, "], End = [intersperse( lists:map(fun({Name0, Type0}) -> - io_lib:format("~p ~s", [Name0, sqlite3_lib:col_type(Type0)]) + [atom_to_list(Name0), " ", sqlite3_lib:col_type(Type0)] end, Tl), ", "), ");"], [CT, Start, End]. @@ -148,15 +191,10 @@ create_table_sql(Tbl, [{ColName, Type} | Tl]) -> %% record with matching Value. %% @end %%-------------------------------------------------------------------- --type(sql_value() :: string() | integer() | float()). -spec (update_sql/4::(atom (), atom (), atom (), [{atom (), sql_value ()}]) -> string ()). update_sql (Tbl, Key, Value, Data) -> - io_lib:format("UPDATE ~p SET ~s WHERE ~p = ~p;", - [Tbl, - sqlite3_lib:update_set_sql (Data), - Key, - Value - ]). + ["UPDATE ", atom_to_list(Tbl), " SET ", sqlite3_lib:update_set_sql(Data), + " WHERE ", atom_to_list(Key), " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @spec write_sql(Tbl, Data) -> string() @@ -169,11 +207,8 @@ update_sql (Tbl, Key, Value, Data) -> -spec(write_sql/2::(atom(), [{atom(), sql_value()}]) -> string()). write_sql(Tbl, Data) -> {Cols, Values} = lists:unzip(Data), - io_lib:format("INSERT INTO ~p (~s) values (~s);", - [Tbl, - sqlite3_lib:write_col_sql(Cols), - sqlite3_lib:write_value_sql(Values) - ]). + ["INSERT INTO ", atom_to_list(Tbl), " (", sqlite3_lib:write_col_sql(Cols), + ") values (", sqlite3_lib:write_value_sql(Values), ");"]. %%-------------------------------------------------------------------- %% @spec read_sql(Tbl, Key, Value) -> string() @@ -186,7 +221,8 @@ write_sql(Tbl, Data) -> %%-------------------------------------------------------------------- -spec(read_sql/3::(atom(), atom(), sql_value()) -> string()). read_sql(Tbl, Key, Value) -> - io_lib:format("SELECT * FROM ~p WHERE ~p = ~p;", [Tbl, Key, Value]). + ["SELECT * FROM ", atom_to_list(Tbl), " WHERE ", atom_to_list(Key), + " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @spec read_sql (Tbl, Key, Value, Columns) -> string () @@ -201,12 +237,9 @@ read_sql(Tbl, Key, Value) -> %%-------------------------------------------------------------------- -spec (read_sql/4::(atom (), atom (), sql_value (), [atom ()]) -> string ()). read_sql (Tbl, Key, Value, Columns) -> - io_lib:format("SELECT ~s FROM ~p WHERE ~p = ~p;", - [sqlite3_lib:read_cols_sql (Columns), - Tbl, - Key, - Value - ]). + ["SELECT ", sqlite3_lib:read_cols_sql(Columns), " FROM ", + atom_to_list(Tbl), " WHERE ", atom_to_list(Key), " = ", + value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @spec delete_sql(Tbl, Key, Value) -> string() @@ -219,7 +252,8 @@ read_sql (Tbl, Key, Value, Columns) -> %%-------------------------------------------------------------------- -spec(delete_sql/3::(atom(), atom(), sql_value()) -> string()). delete_sql(Tbl, Key, Value) -> - io_lib:format("DELETE FROM ~p WHERE ~p = ~p;", [Tbl, Key, Value]). + ["DELETE FROM ", atom_to_list(Tbl), " WHERE ", atom_to_list(Key), + " = ", value_to_sql(Value), ";"]. %%-------------------------------------------------------------------- %% @spec drop_table(Tbl) -> string() @@ -229,7 +263,7 @@ delete_sql(Tbl, Key, Value) -> %%-------------------------------------------------------------------- -spec(drop_table/1::(atom()) -> string()). drop_table(Tbl) -> - io_lib:format("DROP TABLE ~p;", [Tbl]). + ["DROP TABLE ", atom_to_list(Tbl), ";"]. %%==================================================================== %% Internal functions