-module(erlquery). -export([parse/1, codegen/1]). -type erlq_module() :: iodata() | undefined. -type erlq_query() :: {iodata(), iodata(), iodata()} | undefined. -type erlq_method() :: {iodata(), iodata()}. -type erlq_methods() :: [erlq_method()]. -type erlq_value() :: erlq_module() | erlq_query() | erlq_methods(). -record(config, {module :: erlq_module(), query :: erlq_query(), methods :: erlq_methods()}). -type config() :: #config{}. -spec parse(iodata()) -> {ok, config()} | {error, any()}. parse(S) -> Clauses = split_clauses(S), reduce_clauses(Clauses, init_config()). -spec init_config() -> config(). init_config() -> #config{methods = []}. -spec split_clauses(iodata()) -> [{integer(), iodata()}]. split_clauses(S) -> Clauses0 = string:split(S, ".\n", all), CLs = add_line_numbers(Clauses0), Clauses1 = lists:filter(fun not_whitespace/1, CLs), Clauses2 = lists:map(fun({L, X}) -> {L, string:trim(X, both)} end, Clauses1), lists:reverse(Clauses2). -spec add_line_numbers([iodata()]) -> [{integer(), iodata()}]. add_line_numbers(Lines) -> add_line_numbers(Lines, 1, []). -spec add_line_numbers([iodata()], integer(), [{integer(), iodata()}]) -> [{integer(), iodata()}]. add_line_numbers([], _, Acc) -> Acc; add_line_numbers([H | T], Counter, Acc) -> N = length(string:split(H, <<"\n">>, all)), add_line_numbers(T, Counter + 1 + N, [{Counter, H} | Acc]). -spec not_whitespace({integer(), iodata()}) -> boolean(). not_whitespace({_, S}) -> Res = re:replace(S, "[\s\n]", "", [global]), B = iolist_to_binary(Res), B =/= <<"">>. -spec reduce_clauses([{integer(), iodata()}], config()) -> {ok, config()} | {error, any()}. reduce_clauses([], Config) -> {ok, Config}; reduce_clauses([H | T], Config) -> case reduce_clause(H, Config) of {error, _Err} = Err -> Err; NewConfig -> reduce_clauses(T, NewConfig) end. -spec reduce_clause({integer(), iodata()}, config()) -> config() | {error, any()}. reduce_clause({Line, S}, Config) -> case clause_type(S) of module -> M = match_module(S), case validate_module(M) of true -> Config#config{module = M}; false -> {error, {invalid_module_clause, {Line, S}}} end; query -> Q = match_query(S), case validate_query(Q) of true -> case Q of {_, _, <<"2">>} -> Config#config{query = Q}; _ -> {error, {invalid_query_arity, {Line, S}}} end; false -> {error, {invalid_query_clause, {Line, S}}} end; method -> Method = match_method(S), case validate_method(Method) of true -> NewMethods = [Method | Config#config.methods], Config#config{methods = NewMethods}; false -> {error, {invalid_method_clause, {Line, S}}} end; _ -> {error, {invalid_clause, {Line, S}}} end. -spec clause_type(iodata()) -> module | query | method | nomatch. clause_type(S) -> case is_module_clause(S) of true -> module; false -> case is_query_clause(S) of true -> query; false -> case is_method_clause(S) of true -> method; false -> nomatch end end end. -spec is_module_clause(iodata()) -> boolean(). is_module_clause(S) -> case string:prefix(S, "-module(") of nomatch -> false; _ -> true end. -spec is_query_clause(iodata()) -> boolean(). is_query_clause(S) -> case string:prefix(S, "-query(") of nomatch -> false; _ -> true end. -spec is_method_clause(iodata()) -> boolean(). is_method_clause(S) -> case re:run(S, "([a-zA-Z0-9]+)\s?+->") of {match, _} -> true; _ -> false end. %% Expecting the .\n suffix to be stripped by split_clauses -spec match_method(iodata()) -> erlq_method() | nomatch. match_method(S) -> case re:run(S, "([a-zA-Z0-9_]+)[\s\n]?+(->)", [{capture, [1, 2]}]) of {match, [{Start0, End0}, {Start1, End1}]} -> Name = string:slice(S, Start0, End0), Query = string:trim( string:slice(S, Start1 + End1), both), case {Name, Query} of {[], _} -> nomatch; {_, []} -> nomatch; _ -> {bcast(Name), bcast(Query)} end; _ -> nomatch end. -spec match_module(iodata()) -> erlq_module() | nomatch. match_module(S) -> case re:run(S, "-module\\(([a-zA-Z0-9_]+)\\)", [{capture, [1]}]) of {match, [{Start, End}]} -> case string:slice(S, Start, End) of [] -> nomatch; Slice -> bcast(Slice) end; _ -> nomatch end. %% Expecting query to look like -query(pgo:query/2). -spec match_query(iodata()) -> erlq_query() | nomatch. match_query(S) -> case re:run(S, "-query\\(([a-zA-Z0-9_]+):([a-zA-Z0-9_]+)/([0-9]+)\\)", [{capture, [1, 2, 3]}]) of {match, [{Start0, End0}, {Start1, End1}, {Start2, End2}]} -> Mod = string:slice(S, Start0, End0), Fun = string:slice(S, Start1, End1), Arity = string:slice(S, Start2, End2), case {Mod, Fun, Arity} of {[], _, _} -> nomatch; {_, [], _} -> nomatch; {_, _, []} -> nomatch; _ -> {bcast(Mod), bcast(Fun), bcast(Arity)} end; _ -> nomatch end. -spec bcast(binary() | iolist()) -> binary(). bcast(<>) -> S; bcast(S = [_ | _]) -> list_to_binary(S). -spec codegen(config()) -> {ok, erl_parse:abstract_form()} | {error, {atom(), erlq_value()}}. codegen(Config) -> case validate_config(Config) of ok -> M = codegen_module_form(Config#config.module), Q = {_, Arity} = codegen_query_form(Config#config.query), E = codegen_export_form(Config#config.methods, Arity), Ms = codegen_method_forms(Q, Config#config.methods), {ok, [M, E] ++ Ms}; E -> E end. -spec codegen_module_form(binary()) -> erl_parse:erl_parse_tree(). codegen_module_form(Module) -> {attribute, 0, module, binary_to_atom(Module)}. -spec codegen_query_form(erlq_query()) -> {erl_parse:erl_parse_tree(), iodata()} | {undefined, iodata()}. codegen_query_form({Module, Method, Arity}) -> {{remote, 0, {atom, 0, binary_to_atom(bcast(Module))}, {atom, 0, binary_to_atom(bcast(Method))}}, Arity}; codegen_query_form(undefined) -> {undefined, <<"0">>}. -spec codegen_method_forms({erl_parse:erl_parse_tree(), iodata()} | undefined, [{iodata(), iodata()}]) -> [erl_parse:erl_parse_tree()]. codegen_method_forms(Query, Methods) -> reduce_method_forms(Query, Methods, []). -spec reduce_method_forms(erl_parse:erl_parse_tree() | undefined, [{iodata(), iodata()}], [erl_parse:erl_parse_tree()]) -> [erl_parse:erl_parse_tree()]. reduce_method_forms(Query, [H | T], Acc) -> Res = codegen_method_form(Query, H), reduce_method_forms(Query, T, Acc ++ Res); reduce_method_forms(_Query, [], Acc) -> Acc. -spec codegen_method_form({erl_parse:erl_parse_tree(), iodata()} | {undefined, iodata()}, {iodata(), iodata()}) -> erl_parse:erl_parse_tree(). codegen_method_form({undefined, _}, {Method, QueryText}) -> AM = binary_to_atom(bcast(Method)), QueryS = lcast(QueryText), [{function, 0, AM, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, AM}, [{nil, 0}]}]}]}, {function, 0, AM, 1, [{clause, 0, [{var, 0, '_Args'}], [], [{bin, 0, [{bin_element, 0, {string, 0, QueryS}, default, default}]}]}]}]; codegen_method_form({Query, QueryArity}, {Method, QueryText}) -> case QueryArity of <<"2">> -> codegen_method_form_2(Query, Method, QueryText); "2" -> codegen_method_form_2(Query, Method, QueryText); <<"3">> -> codegen_method_form_3(Query, Method, QueryText); "3" -> codegen_method_form_3(Query, Method, QueryText) end. -spec codegen_method_form_2(erl_parse:erl_parse_tree(), iodata(), iodata()) -> erl_parse:erl_parse_tree(). codegen_method_form_2(Query, Method, QueryText) -> AM = binary_to_atom(bcast(Method)), QueryS = lcast(QueryText), [{function, 0, AM, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, AM}, [{nil, 0}]}]}]}, %% Optional arg {function, 0, AM, 1, [{clause, 0, [{var, 0, 'Args'}], [], [{call, 0, Query, [{bin, 0, [{bin_element, 0, {string, 0, QueryS}, default, default}]}, {var, 0, 'Args'}]}]}]}]. -spec codegen_method_form_3(erl_parse:erl_parse_tree(), iodata(), iodata()) -> erl_parse:erl_parse_tree(). codegen_method_form_3(Query, Method, QueryText) -> AM = binary_to_atom(bcast(Method)), QueryS = lcast(QueryText), [{function, 0, AM, 1, [{clause, 0, [{var, 0, 'Conn'}], [], [{call, 0, Query, [{var, 0, 'Conn'}, {bin, 0, [{bin_element, 0, {string, 0, QueryS}, default, default}]}]}]}]}, {function, 0, AM, 2, [{clause, 0, [{var, 0, 'Conn'}, {var, 0, 'Args'}], [], [{call, 0, Query, [{var, 0, 'Conn'}, {bin, 0, [{bin_element, 0, {string, 0, QueryS}, default, default}]}, {var, 0, 'Args'}]}]}]}]. -spec lcast(iodata()) -> string(). lcast(<>) -> binary_to_list(S); lcast(S) -> S. -spec codegen_export_form([{binary(), binary()}], iodata()) -> erl_parse:erl_parse_tree(). codegen_export_form(Methods, Arity) -> {attribute, 0, export, reduce_exports_form(Methods, Arity, [])}. -spec reduce_exports_form([{binary(), binary()}], iodata(), [erl_parse:erl_parse_tree()]) -> [erl_parse:erl_parse_tree()]. reduce_exports_form([H | T], Arity, Acc) -> {Res1, Res0} = codegen_export_method_form(H, Arity), reduce_exports_form(T, Arity, Acc ++ [Res1, Res0]); reduce_exports_form([], _Arity, Acc) -> Acc. -spec codegen_export_method_form({iodata(), iodata()}, iodata()) -> {erl_parse:erl_parse_tree(), erl_parse:erl_parse_tree()}. codegen_export_method_form({Name, _QueryText}, Arity) -> case Arity of <<"0">> -> {{binary_to_atom(bcast(Name)), 1}, {binary_to_atom(bcast(Name)), 0}}; "0" -> {{binary_to_atom(bcast(Name)), 1}, {binary_to_atom(bcast(Name)), 0}}; <<"2">> -> {{binary_to_atom(bcast(Name)), 1}, {binary_to_atom(bcast(Name)), 0}}; "2" -> {{binary_to_atom(bcast(Name)), 1}, {binary_to_atom(bcast(Name)), 0}}; <<"3">> -> {{binary_to_atom(bcast(Name)), 2}, {binary_to_atom(bcast(Name)), 1}}; "3" -> {{binary_to_atom(bcast(Name)), 2}, {binary_to_atom(bcast(Name)), 1}} end. -spec validate_config(config()) -> ok | {error, {atom(), any()}}. validate_config(Config) -> case validate_module(Config#config.module) of true -> case validate_query(Config#config.query) of true -> case validate_query_arity(Config#config.query) of true -> case validate_methods(Config#config.methods) of true -> ok; false -> {error, {invalid_methods, Config#config.methods}} end; false -> {error, {invalid_query_arity, Config#config.query}} end; false -> {error, {invalid_query, Config#config.query}} end; false -> {error, {invalid_module, Config#config.module}} end. -spec validate_module(erlq_module() | any()) -> boolean(). validate_module(<<_/binary>>) -> true; validate_module(_) -> false. -spec validate_query(erlq_query()) -> boolean(). validate_query({<<_/binary>>, <<_/binary>>, <<_/binary>>}) -> true; validate_query(undefined) -> true; validate_query(_) -> false. -spec validate_query_arity(erlq_query()) -> boolean(). validate_query_arity(undefined) -> true; validate_query_arity({_, _, <<"2">>}) -> true; validate_query_arity({_, _, <<"3">>}) -> true; validate_query_arity({_, _, "2"}) -> true; validate_query_arity({_, _, "3"}) -> true; validate_query_arity(_) -> false. -spec validate_methods(erlq_methods() | any()) -> boolean(). validate_methods(_ = []) -> true; validate_methods(_ = [H | T]) -> case validate_method(H) of true -> validate_methods(T); false -> false end. -spec validate_method(erlq_method() | any()) -> boolean(). validate_method(_ = {<<_/binary>>, <<_/binary>>}) -> true; validate_method(_) -> false. -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). match_module_test() -> ?assertEqual(<<"foobar">>, match_module("-module(foobar).")), ?assertEqual(<<"foo_bar">>, match_module("-module(foo_bar).")). match_query_test() -> ?assertEqual({<<"pgo">>, <<"query">>, <<"2">>}, match_query("-query(pgo:query/2)")), ?assertEqual({<<"pgo">>, <<"super_query">>, <<"2">>}, match_query("-query(pgo:super_query/2)")), ?assertEqual({<<"p_go">>, <<"super_query">>, <<"2">>}, match_query("-query(p_go:super_query/2)")). match_method_test() -> ?assertEqual({<<"foobar">>, <<"SELECT * FROM foobar">>}, match_method("foobar ->\n SELECT * FROM foobar")), ?assertEqual({<<"foobar">>, <<"SELECT * FROM foobar">>}, match_method("foobar->\n SELECT * FROM foobar")), ?assertEqual({<<"foobar">>, <<"SELECT * FROM foobar\nINNER JOIN accounts ON foobar.id = accounts.id">>}, match_method("foobar ->\n SELECT * FROM foobar\nINNER JOIN accounts ON foobar.id = accounts.id")), ?assertEqual({<<"foo_bar">>, <<"SELECT * FROM foobar">>}, match_method("foo_bar ->\n SELECT * FROM foobar")). is_module_clause_test() -> ?assert(is_module_clause("-module(foobar).")), ?assertNot(is_module_clause("foobar")). is_query_clause_test() -> ?assert(is_query_clause("-query(foobar:goo/3).")), ?assertNot(is_query_clause("foobar")). is_method_clause_test() -> ?assert(is_method_clause("foobar -> jhgjgj")), ?assert(is_method_clause("foobar-> jhgjgj")), ?assertNot(is_method_clause("foobar")), ?assert(is_method_clause("foo->bar")). clause_type_test() -> ?assertEqual(module, clause_type("-module(foobar)")), ?assertEqual(query, clause_type("-query(foobar:goo/2)")), ?assertEqual(method, clause_type("foo -> SELECT * FROM foobar")), ?assertEqual(nomatch, clause_type("foobar")). reduce_clause_test() -> ?assertEqual(#config{module = <<"foo">>}, reduce_clause({0, "-module(foo)"}, #config{})), ?assertEqual(#config{query = {<<"foo">>, <<"bar">>, <<"2">>}}, reduce_clause({0, "-query(foo:bar/2)"}, #config{})), ?assertEqual(#config{methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}, reduce_clause({0, "foobar -> SELECT * FROM foobars"}, #config{methods = []})), ?assertMatch({error, {invalid_clause, {_, _}}}, reduce_clause({0, "stuff"}, #config{})), ?assertMatch({error, {invalid_module_clause, {_, _}}}, reduce_clause({0, "-module()"}, #config{})), ?assertMatch({error, {invalid_module_clause, {_, _}}}, reduce_clause({0, "-module( )"}, #config{})), ?assertMatch({error, {invalid_query_arity, {_, _}}}, reduce_clause({0, "-query(foo:bar/3)"}, #config{})), ?assertMatch({error, {invalid_query_clause, {_, _}}}, reduce_clause({0, "-query(foo: /3)"}, #config{})), ?assertMatch({error, {invalid_query_clause, {_, _}}}, reduce_clause({0, "-query()"}, #config{})), ?assertMatch({error, {invalid_method_clause, {_, _}}}, reduce_clause({0, "foobar ->"}, #config{})), ?assertMatch({error, {invalid_clause, {_, _}}}, reduce_clause({0, " -> foobar"}, #config{})). add_line_numbers_test() -> ?assertEqual([], add_line_numbers([])), ?assertEqual([{1, "foobar"}], add_line_numbers(["foobar"])), ?assertEqual([{5, "foobar"}, {1, "foobar\n\nhello"}], add_line_numbers(["foobar\n\nhello", "foobar"])). not_whitespace_test() -> ?assert(not_whitespace({0, "foobar"})), ?assertNot(not_whitespace({0, <<"">>})), ?assert(not_whitespace({0, <<"foobar sdfsdfsdf">>})). split_clauses_test() -> S = "-module(foobar).\n\n-query(pgo:query/2).\n\nbarfoo->\n SELECT * FROM foobars.\n", ?assertMatch([{_, "-module(foobar)"}, {_, "-query(pgo:query/2)"}, {_, "barfoo->\n SELECT * FROM foobars"}], split_clauses(S)). parse_test() -> ?assertEqual({ok, #config{module = <<"fooq">>, query = {<<"foo">>, <<"bar">>, <<"2">>}, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}}, parse(list_to_binary(["-module(fooq).\n\n", "-query(foo:bar/2).\n\n", "foobar ->\n", " SELECT * FROM foobars"]))). parse_no_query_test() -> ?assertEqual({ok, #config{module = <<"fooq">>, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}}, parse(list_to_binary(["-module(fooq).\n\n", "foobar ->\n", " SELECT * FROM foobars"]))). codegen_export_method_form_test() -> ?assertEqual({{foobar, 1}, {foobar, 0}}, codegen_export_method_form({<<"foobar">>, <<"barfoo">>}, <<"2">>)). codegen_export_form_test() -> ?assertEqual({attribute, 0, export, [{foobar, 1}, {foobar, 0}, {barfoo, 1}, {barfoo, 0}]}, codegen_export_form([{"foobar", "blah"}, {"barfoo", "blahblah"}], <<"2">>)). codegen_query_form_test() -> ?assertEqual({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, <<"2">>}, codegen_query_form({<<"barfoo">>, <<"query">>, <<"2">>})), ?assertEqual({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, "2"}, codegen_query_form({"barfoo", "query", "2"})), ?assertEqual({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, <<"3">>}, codegen_query_form({<<"barfoo">>, <<"query">>, <<"3">>})), ?assertEqual({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, "3"}, codegen_query_form({"barfoo", "query", "3"})), ?assertEqual({undefined, <<"0">>}, codegen_query_form(undefined)). codegen_module_form_test() -> ?assertEqual({attribute, 0, module, hello}, codegen_module_form(<<"hello">>)). codegen_method_form_test() -> ?assertEqual([{function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, 'Args'}], [], [{call, 0, {remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}, {var, 0, 'Args'}]}]}]}], codegen_method_form({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, <<"2">>}, {<<"foobar">>, <<"SELECT * FROM foobars">>})), ?assertEqual([{function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, '_Args'}], [], [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}]}]}], codegen_method_form({undefined, <<"0">>}, {<<"foobar">>, <<"SELECT * FROM foobars">>})), ?assertEqual([{function, 0, foobar, 1, [{clause, 0, [{var, 0, 'Conn'}], [], [{call, 0, {remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, [{var, 0, 'Conn'}, {bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}]}]}]}, {function, 0, foobar, 2, [{clause, 0, [{var, 0, 'Conn'}, {var, 0, 'Args'}], [], [{call, 0, {remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, [{var, 0, 'Conn'}, {bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}, {var, 0, 'Args'}]}]}]}], codegen_method_form({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, <<"3">>}, {<<"foobar">>, <<"SELECT * FROM foobars">>})). codegen_method_forms_test() -> ?assertEqual([{function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, 'Args'}], [], [{call, 0, {remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}, {var, 0, 'Args'}]}]}]}], codegen_method_forms({{remote, 0, {atom, 0, barfoo}, {atom, 0, query}}, <<"2">>}, [{<<"foobar">>, <<"SELECT * FROM foobars">>}])), ?assertEqual([{function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, '_Args'}], [], [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}]}]}], codegen_method_forms({undefined, <<"0">>}, [{<<"foobar">>, <<"SELECT * FROM foobars">>}])). codegen_forms_test() -> ?assertEqual({ok, [{attribute, 0, module, things}, {attribute, 0, export, [{foobar, 1}, {foobar, 0}]}, {function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, 'Args'}], [], [{call, 0, {remote, 0, {atom, 0, bar}, {atom, 0, foo}}, [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}, {var, 0, 'Args'}]}]}]}]}, codegen(#config{module = <<"things">>, query = {<<"bar">>, <<"foo">>, <<"2">>}, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]})). codegen_forms_no_query_test() -> ?assertEqual({ok, [{attribute, 0, module, things}, {attribute, 0, export, [{foobar, 1}, {foobar, 0}]}, {function, 0, foobar, 0, [{clause, 0, [], [], [{call, 0, {atom, 0, foobar}, [{nil, 0}]}]}]}, {function, 0, foobar, 1, [{clause, 0, [{var, 0, '_Args'}], [], [{bin, 0, [{bin_element, 0, {string, 0, "SELECT * FROM foobars"}, default, default}]}]}]}]}, codegen(#config{module = <<"things">>, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]})). compile_forms_test() -> {ok, Forms} = codegen(#config{module = <<"ftest">>, query = {<<"erlquery_mock">>, <<"query">>, <<"2">>}, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}), Res = compile:forms(Forms), ?assertMatch({ok, ftest, _}, Res), {_, _, Bin} = Res, ?assertEqual({module, ftest}, code:load_binary(ftest, "ftest.beam", Bin)), ?assertEqual(ok, ftest:foobar([])), ?assertEqual(ok, ftest:foobar()). compile_forms_query_3_test() -> {ok, Forms} = codegen(#config{module = <<"ftest3">>, query = {<<"erlquery_mock">>, <<"query">>, <<"3">>}, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}), Res = compile:forms(Forms), ?assertMatch({ok, ftest3, _}, Res), {_, _, Bin} = Res, ?assertEqual({module, ftest3}, code:load_binary(ftest3, "ftest3.beam", Bin)), Conn = mock_connection, ?assertEqual(ok, ftest3:foobar(Conn)), ?assertEqual(ok, ftest3:foobar(Conn, [])). compile_forms_no_query_test() -> {ok, Forms} = codegen(#config{module = <<"fqtest">>, methods = [{<<"foobar">>, <<"SELECT * FROM foobars">>}]}), Res = compile:forms(Forms), ?assertMatch({ok, fqtest, _}, Res), {_, _, Bin} = Res, ?assertEqual({module, fqtest}, code:load_binary(fqtest, "fqtest.beam", Bin)), ?assertEqual(<<"SELECT * FROM foobars">>, fqtest:foobar([])), ?assertEqual(<<"SELECT * FROM foobars">>, fqtest:foobar()). validate_module_test() -> ?assert(validate_module(<<"foo">>)), ?assertNot(validate_module("foo")), ?assertNot(validate_module(1)), ?assertNot(validate_module(1.0)), ?assertNot(validate_module(true)), ?assertNot(validate_module([])), ?assertNot(validate_module(#{})). validate_query_test() -> ?assert(validate_query({<<"foo">>, <<"bar">>, <<"boo">>})), ?assertNot(validate_query({"foo", "bar", "boo"})), ?assertNot(validate_query({1, 2, 3})), ?assertNot(validate_query({1.0, 2.3, 4.5})), ?assert(validate_query(undefined)). validate_query_arity_test() -> ?assert(validate_query_arity({<<"foo">>, <<"bar">>, <<"2">>})), ?assert(validate_query_arity({<<"foo">>, <<"bar">>, <<"3">>})), ?assert(validate_query_arity({<<"foo">>, <<"bar">>, "2"})), ?assert(validate_query_arity({<<"foo">>, <<"bar">>, "3"})), ?assert(validate_query_arity(undefined)), ?assertNot(validate_query_arity({<<"foo">>, <<"bar">>, <<"4">>})), ?assertNot(validate_query_arity({<<"foo">>, <<"bar">>, <<"1">>})), ?assertNot(validate_query_arity({<<"foo">>, <<"bar">>, <<"42">>})), ?assertNot(validate_query_arity({<<"foo">>, <<"bar">>, <<"22">>})). validate_method_test() -> ?assert(validate_method({<<"foo">>, <<"bar">>})), ?assertNot(validate_method({"foo", "bar"})), ?assertNot(validate_method({1, 2})), ?assertNot(validate_method(nomatch)). validate_methods_test() -> ?assert(validate_methods([])), ?assert(validate_methods([{<<"foo">>, <<"bar">>}])), ?assert(validate_methods([{<<"foo">>, <<"bar">>}, {<<"foo">>, <<"bar">>}, {<<"foo">>, <<"bar">>}])), ?assertNot(validate_methods([{<<"foo">>, <<"bar">>}, {"foo", "bar"}, {1, 2}])). validate_config_test() -> ?assertEqual(ok, validate_config(#config{module = <<"foobar">>, query = {<<"foob">>, <<"bar">>, <<"2">>}, methods = [{<<"hello">>, <<"yep">>}]})), ?assertEqual(ok, validate_config(#config{module = <<"foobar">>, query = {<<"foo">>, <<"bar">>, <<"3">>}, methods = [{<<"stuff">>, <<"hello">>}]})), ?assertEqual(ok, validate_config(#config{module = <<"foobar">>, methods = [{<<"hello">>, <<"yep">>}]})), ?assertMatch({error, {invalid_module, _}}, validate_config(#config{module = 42, query = {<<"foo">>, <<"bar">>, <<"foobar">>}, methods = [{<<"stuff">>, <<"hello world">>}]})), ?assertMatch({error, {invalid_query, _}}, validate_config(#config{module = <<"foobar">>, query = {1, 2, 3}, methods = [{<<"stuff">>, <<"hello">>}]})), ?assertMatch({error, {invalid_methods, _}}, validate_config(#config{module = <<"foobar">>, query = {<<"foo">>, <<"bar">>, <<"2">>}, methods = [{1, 2}]})). -endif.