Packages

A SQL92 predicate expression evaluator in Erlang

Retired package: Release invalid - contains and invalid dependency

Current section

Files

Jump to
evaluator src evaluator.erl
Raw

src/evaluator.erl

-module(evaluator).
%% API exports
-export([evaluate/2]).
%-compile(export_all).
%-include("evaluator.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(nowarn_unused_function).
%%====================================================================
%% API functions
%% @doc Evaluates a SQL92 predicate based on a collection of properties.
evaluate(Predicate, Properties) when is_list(Predicate) ->
case expression_lexer:string(Predicate) of
{ ok, Tokens, _ } ->
case expression_parser:parse(Tokens) of
{ ok, AST } ->
(make_predicate_evaluator(AST))(Properties);
{ error, { _, _, Message }} ->
{ parser_error, Message }
end;
{ error,{ _, _, { illegal, Char }}, Pos} ->
{ unrecognized_token, Pos, Char }
end;
evaluate(Predicate, _) ->
{invalid_predicate_format, Predicate}
.
%%====================================================================
%%====================================================================
%% Internal functions
make_predicate_evaluator({ not_op, Predicate }) -> make_not_evaluator(make_predicate_evaluator(Predicate));
make_predicate_evaluator({ and_op, LeftPredicate, RightPredicate }) ->
make_and_evaluator(
make_predicate_evaluator(LeftPredicate),
make_predicate_evaluator(RightPredicate));
make_predicate_evaluator({ or_op, LeftPredicate, RightPredicate }) ->
make_or_evaluator(
make_predicate_evaluator(LeftPredicate),
make_predicate_evaluator(RightPredicate));
make_predicate_evaluator({ Op = eq, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ Op = neq, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ Op = gt, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ Op = gte, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ Op = lt, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ Op = lte, LeftExpression, RightExpression }) -> make_comparison_evaluator(Op, LeftExpression, RightExpression);
make_predicate_evaluator({ is_null, Property }) -> make_is_null_evaluator(make_expression_evaluator(Property));
make_predicate_evaluator({ is_not_null, Property }) -> make_is_not_null_evaluator(make_expression_evaluator(Property));
make_predicate_evaluator({ in, LeftExpression, ExpressionList }) ->
make_in_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(ExpressionList));
make_predicate_evaluator({ not_in, LeftExpression, ExpressionList }) ->
make_not_in_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(ExpressionList));
make_predicate_evaluator({ like, LeftExpression, PatternExpression }) ->
make_like_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(PatternExpression));
make_predicate_evaluator({ like, LeftExpression, PatternExpression, { escape, EscapeChar} }) ->
make_like_escape_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(PatternExpression),
make_expression_evaluator(EscapeChar));
make_predicate_evaluator({ not_like, LeftExpression, PatternExpression }) ->
make_not_like_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(PatternExpression));
make_predicate_evaluator({ not_like, LeftExpression, PatternExpression, { escape, EscapeChar} }) ->
make_not_like_escape_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(PatternExpression),
make_expression_evaluator(EscapeChar));
make_predicate_evaluator({ exists, Property }) -> make_exist_evaluator(make_expression_evaluator(Property)).
make_comparison_evaluator(Operator, LeftExpression, RightExpression) ->
Associations = dict:from_list([
{ eq, fun make_equal_evaluator/2 },
{ neq, fun make_not_equal_evaluator/2 },
{ gt, fun make_greater_than_evaluator/2 },
{ gte, fun make_greater_than_equal_evaluator/2 },
{ lt, fun make_less_than_evaluator/2 },
{ lte, fun make_less_than_equal_evaluator/2 }
]),
LeftEvaluator = make_expression_evaluator(LeftExpression),
RightEvaluator = make_expression_evaluator(RightExpression),
Func = dict:fetch(Operator, Associations),
Func(LeftEvaluator, RightEvaluator)
.
make_not_evaluator(Evaluator) ->
fun(Properties) ->
not Evaluator(Properties)
end.
make_and_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
false -> LeftValue and RightValue;
_ -> case (LeftValue == false) or (RightValue == false) of
true -> false;
_ -> unknown
end
end
end.
make_or_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
false -> LeftValue or RightValue;
_ -> case (LeftValue == true) or (RightValue == true) of
true -> true;
_ -> unknown
end
end
end.
make_equal_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue == RightValue
end
end.
make_not_equal_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue =/= RightValue
end
end.
make_greater_than_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue > RightValue
end
end.
make_greater_than_equal_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue >= RightValue
end
end.
make_less_than_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue < RightValue
end
end.
make_less_than_equal_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue =< RightValue
end
end.
make_is_null_evaluator(Evaluator) ->
fun(Properties) ->
Value = Evaluator(Properties),
case Value of
unknown -> true;
_ -> Value == null
end
end.
make_is_not_null_evaluator(Evaluator) ->
fun(Properties) ->
Value = Evaluator(Properties),
case Value of
unknown -> false;
_ -> Value /= null
end
end.
make_in_evaluator(LeftEvaluator, EvaluatorList) ->
fun(Properties) ->
Value = LeftEvaluator(Properties),
case any_unknown([Value]) of
true -> unknown;
_ ->
List = lists:foldl(fun(Evaluator, Acc) -> [Evaluator(Properties)] ++ Acc end, [], EvaluatorList),
lists:any(fun(Item) -> Item == Value end, List)
end
end.
make_not_in_evaluator(LeftEvaluator, EvaluatorList) ->
fun(Properties) ->
Value = LeftEvaluator(Properties),
case any_unknown([Value]) of
true -> unknown;
_ ->
List = lists:foldl(fun(Evaluator, Acc) -> [Evaluator(Properties)] ++ Acc end, [], EvaluatorList),
lists:all(fun(Item) -> Item /= Value end, List)
end
end.
make_like_evaluator(ExpressionEvaluator, PatternEvaluator) ->
fun(Properties) ->
is_match(ExpressionEvaluator(Properties), PatternEvaluator(Properties))
end.
make_like_escape_evaluator(ExpressionEvaluator, PatternEvaluator, EscapeCharEvaluator) ->
fun(Properties) ->
is_match(ExpressionEvaluator(Properties), PatternEvaluator(Properties), EscapeCharEvaluator(Properties))
end.
make_not_like_evaluator(ExpressionEvaluator, PatternEvaluator) ->
fun(Properties) ->
not is_match(ExpressionEvaluator(Properties), PatternEvaluator(Properties))
end.
make_not_like_escape_evaluator(ExpressionEvaluator, PatternEvaluator, EscapeCharEvaluator) ->
fun(Properties) ->
not is_match(ExpressionEvaluator(Properties), PatternEvaluator(Properties), EscapeCharEvaluator(Properties))
end.
make_exist_evaluator(ExpressionEvaluator) ->
fun(Properties) ->
case ExpressionEvaluator(Properties) of
unknown -> false;
_ -> true
end
end.
make_expression_evaluator({ integer_constant, String }) -> make_integer_evaluator(String);
make_expression_evaluator({ decimal_constant, String }) -> make_decimal_evaluator(String);
make_expression_evaluator({ boolean_constant, String }) -> make_boolean_evaluator(String);
make_expression_evaluator({ string_constant, String }) -> make_string_evaluator(String);
make_expression_evaluator({ property, String }) -> make_property_evaluator(String);
make_expression_evaluator({ plus, Expression }) -> make_expression_evaluator(Expression);
make_expression_evaluator({ minus, Expression }) -> make_negation_evaluator(make_expression_evaluator(Expression));
make_expression_evaluator({ addition, LeftExpression, RightExpression}) ->
make_addition_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(RightExpression)
);
make_expression_evaluator({ subtraction, LeftExpression, RightExpression}) ->
make_subtraction_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(RightExpression)
);
make_expression_evaluator({ multiplication, LeftExpression, RightExpression}) ->
make_multiplication_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(RightExpression)
);
make_expression_evaluator({ division, LeftExpression, RightExpression}) ->
make_division_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(RightExpression)
);
make_expression_evaluator({ remainder, LeftExpression, RightExpression}) ->
make_remainder_evaluator(
make_expression_evaluator(LeftExpression),
make_expression_evaluator(RightExpression)
);
make_expression_evaluator([Expression|T]) -> [make_expression_evaluator(Expression)] ++ make_expression_evaluator(T);
make_expression_evaluator([]) -> [].
make_integer_evaluator(String) ->
fun(_) ->
{ V, _ } = string:to_integer(String),
V
end.
make_decimal_evaluator(String) ->
fun(_) ->
{ V, _ } = string:to_float(String),
V
end.
make_boolean_evaluator(String) ->
fun(_) ->
string:equal(String, "true", true)
end.
make_string_evaluator(String) ->
fun(_) ->
String
end.
make_property_evaluator(String) ->
fun(Properties) ->
case dict:is_key(String, Properties) of
true -> lists:nth(1, dict:fetch(String, Properties));
_ -> unknown
end
end.
make_negation_evaluator(Evaluator) ->
fun(Properties) ->
Value = Evaluator(Properties),
case any_unknown([Value]) of
true -> unknown;
_ -> -1 * Value
end
end.
make_addition_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue + RightValue
end
end.
make_subtraction_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue - RightValue
end
end.
make_multiplication_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue * RightValue
end
end.
make_division_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue div RightValue
end
end.
make_remainder_evaluator(LeftEvaluator, RightEvaluator) ->
fun(Properties) ->
LeftValue = LeftEvaluator(Properties),
RightValue = RightEvaluator(Properties),
case any_unknown([LeftValue, RightValue]) of
true -> unknown;
_ -> LeftValue rem RightValue
end
end.
is_match(String, Pattern) -> is_match(String, Pattern, []).
is_match(String, Pattern, EscapeChar) ->
Regex = get_like_regex_expression(Pattern, EscapeChar),
case re:run(String, Regex) of
nomatch -> false;
_ -> true
end.
get_like_regex_expression(Pattern, EscapeChar) ->
Regex = get_regex(Pattern, EscapeChar),
{ ok, Caseless_Regex } = re:compile("^" ++ Regex ++ "$", [caseless]),
Caseless_Regex
.
get_regex(Pattern, []) -> apply_wildcards(Pattern);
get_regex(Pattern, Escape) when length(Escape) =< 1 ->
Escape_Char = escape_if_metacharacter(Escape),
Escape_Percent = Escape_Char ++ "%",
Escape_Underscore = Escape_Char ++ "_",
Split = re:split(
Pattern,
"(" ++ Escape_Percent ++ "|" ++ Escape_Underscore ++ ")",
[{ return, list }, group]),
Escaped_Pattern = lists:foldl(
fun(Split_Item, Acc) -> Acc ++ escape(Split_Item) end,
[],
Split),
Escaped_Pattern.
escape_if_metacharacter(Char) ->
Metacharacters = ["\\", "|", "[", "]", "(", ")", "?", "*", "+", "$", "^"],
case lists:any(fun(Item) -> Item == Char end, Metacharacters) of
true -> "\\" ++ Char;
_ -> Char
end.
escape(Split_Item) ->
case Split_Item of
[Part, [_, WildCard]] -> lists:flatten(string:concat([apply_wildcards(Part)], [WildCard]));
[Part] -> apply_wildcards(Part)
end.
apply_wildcards(String) ->
re:replace(
re:replace(String, "%", ".*", [global, { return, list }]),
"_",
".",
[global, { return, list }]).
any_unknown(Values) when is_list(Values) ->
lists:any(fun(Value) -> Value == unknown end, Values);
any_unknown(_) -> ?assert(false).
%$ unit test
get_like_regex_expression_percent_test() -> "H.*o" = get_regex("H%o", []).
get_like_regex_expression_underscore_test() -> "H.llo" = get_regex("H_llo", []).
get_like_escape_regex_expression_percent_test() -> "H%o" = get_regex("Hp%o", "p").
get_like_escape_regex_expression_underscore_test() -> "H_llo" = get_regex("Hp_llo", "p").
get_like_escape_regex_expression_underscore_complex_test() -> "H.l_o" = get_regex("H_lp_o", "p").
get_like_escape_regex_expression_underscore_backslash_test() -> "H.l_o" = get_regex("H_l\\_o", "\\").
get_like_escape_regex_expression_underscore_pipe_test() -> "H.l_o" = get_regex("H_l|_o", "|").
get_like_escape_regex_expression_hyphen_test() -> "H.l_o" = get_regex("H_l-_o", "-").
get_like_escape_regex_expression_bracket_test() -> "H.l_o" = get_regex("H_l[_o", "[").
get_like_escape_regex_expression_parens_test() -> "H.l_o" = get_regex("H_l(_o", "(").
get_like_escape_regex_expression_question_mark_test() -> "H.l_o" = get_regex("H_l?_o", "?").
get_like_escape_regex_expression_asterisk_test() -> "H.l_o" = get_regex("H_l*_o", "*").
get_like_escape_regex_expression_plus_test() -> "H.l_o" = get_regex("H_l+_o", "+").
get_like_escape_regex_expression_dot_test() -> "Hel_o" = get_regex("Hel._o", ".").
get_like_escape_regex_expression_dollar_test() -> "Hel_o" = get_regex("Hel$_o", "$").
get_like_escape_regex_expression_circumflex_test() -> "Hel_o" = get_regex("Hel^_o", "^").
%get_like_escape_regex_expression_clean_useless_escape_test() -> "Hello" = get_regex("?He?ll?o", "?").
any_unknown_test() ->
true = any_unknown([unknown]),
true = any_unknown([3, unknown]),
false = any_unknown([3, 3])
.
%%====================================================================