Remove [NOT] BETWEEN operator

[NOT] BETWEEN operator is not supported in AMQP SQL
This commit is contained in:
David Ansari 2025-07-03 14:18:25 +02:00
parent 9f7668f6c2
commit f5ae413659
7 changed files with 1513 additions and 1963 deletions

View File

@ -158,12 +158,6 @@ eval0({unary_minus, Expr}, Msg) ->
end;
%% Special operators
eval0({'between', Expr, From, To}, Msg) ->
Value = eval0(Expr, Msg),
FromVal = eval0(From, Msg),
ToVal = eval0(To, Msg),
between(Value, FromVal, ToVal);
eval0({'in', Expr, ValueList}, Msg) ->
Value = eval0(Expr, Msg),
is_in(Value, ValueList);
@ -218,21 +212,6 @@ arithmetic('/', Left, Right) when is_number(Left) andalso is_number(Right) andal
arithmetic(_, _, _) ->
undefined.
between(Value, From, To)
when Value =:= undefined orelse
From =:= undefined orelse
To =:= undefined ->
undefined;
between(Value, From, To)
when is_number(Value) andalso
is_number(From) andalso
is_number(To) ->
From =< Value andalso Value =< To;
between(_, _, _) ->
%% BETWEEN requires arithmetic expressions
%% "a string cannot be used in an arithmetic expression"
false.
is_in(undefined, _) ->
%% "If identifier of an IN or NOT IN operation is NULL,
%% the value of the operation is unknown."

View File

@ -76,9 +76,6 @@ has_binary_identifier_test() ->
true = has_binary_identifier("custom_metric * 10 < 100"),
true = has_binary_identifier("properties.creation-time >= 12345 OR user_data = 'test'"),
false = has_binary_identifier("properties.group-sequence BETWEEN 1 AND 10"),
true = has_binary_identifier("user_score BETWEEN 1 AND 10"),
false = has_binary_identifier("properties.group-id LIKE 'group_%' ESCAPE '!'"),
true = has_binary_identifier("user_tag LIKE 'group_%' ESCAPE '!'"),
@ -87,10 +84,10 @@ has_binary_identifier_test() ->
false = has_binary_identifier(
"(properties.group-sequence + 1) * 2 <= 100 AND " ++
"(properties.group-id LIKE 'prod_%' OR header.priority BETWEEN 5 AND 10)"),
"(properties.group-id LIKE 'prod_%' OR h.priority > 5)"),
true = has_binary_identifier(
"(properties.group-sequence + 1) * 2 <= 100 AND " ++
"(user_value LIKE 'prod_%' OR properties.absolute-expiry-time BETWEEN 5 AND 10)"),
"(user_value LIKE 'prod_%' OR p.absolute-expiry-time < 10)"),
ok.
has_binary_identifier(Selector) ->

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,6 @@ Rules.
[nN][oO][tT] : {token, {'NOT', TokenLine}}.
% Special operators (case insensitive)
[bB][eE][tT][wW][eE][eE][nN] : {token, {'BETWEEN', TokenLine}}.
[lL][iI][kK][eE] : {token, {'LIKE', TokenLine}}.
[iI][nN] : {token, {'IN', TokenLine}}.
[iI][sS] : {token, {'IS', TokenLine}}.

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,6 @@ Nonterminals
identifier_expr
string_list
string_item
between_expr
in_expr
like_expr
is_null_expr.
@ -27,7 +26,7 @@ Terminals
'=' '<>' '>' '<' '>=' '<='
'+' '-' '*' '/'
'AND' 'OR' 'NOT'
'BETWEEN' 'LIKE' 'IN' 'IS' 'NULL' 'ESCAPE'
'LIKE' 'IN' 'IS' 'NULL' 'ESCAPE'
'(' ')' ','.
Rootsymbol selector.
@ -59,16 +58,11 @@ comparison_expr -> additive_expr '>' additive_expr : {'>', '$1', '$3'}.
comparison_expr -> additive_expr '<' additive_expr : {'<', '$1', '$3'}.
comparison_expr -> additive_expr '>=' additive_expr : {'>=', '$1', '$3'}.
comparison_expr -> additive_expr '<=' additive_expr : {'<=', '$1', '$3'}.
comparison_expr -> between_expr : '$1'.
comparison_expr -> like_expr : '$1'.
comparison_expr -> in_expr : '$1'.
comparison_expr -> is_null_expr : '$1'.
comparison_expr -> additive_expr : '$1'.
%% BETWEEN expression
between_expr -> additive_expr 'BETWEEN' additive_expr 'AND' additive_expr : {'between', '$1', '$3', '$5'}.
between_expr -> additive_expr 'NOT' 'BETWEEN' additive_expr 'AND' additive_expr : {'not', {'between', '$1', '$4', '$6'}}.
%% LIKE expression
like_expr -> additive_expr 'LIKE' string :
{'like', '$1', process_like_pattern('$3'), no_escape}.

View File

@ -30,7 +30,6 @@ groups() ->
string_comparison,
like_operator,
in_operator,
between_operator,
null_handling,
literals,
scientific_notation,
@ -348,43 +347,6 @@ in_operator(_Config) ->
false = match("missing NOT IN ('UK', 'US')", app_props()),
false = match("absent NOT IN ('UK', 'US')", app_props()).
between_operator(_Config) ->
%% Basic BETWEEN operations
true = match("weight BETWEEN 3 AND 7", app_props()),
true = match("weight BETWEEN 5 AND 7", app_props()),
true = match("weight BETWEEN 3 AND 5", app_props()),
false = match("weight BETWEEN 6 AND 10", app_props()),
true = match("price BETWEEN 10 AND 11", app_props()),
true = match("price BETWEEN 10 AND 10.5", app_props()),
false = match("price BETWEEN -1 AND 10", app_props()),
false = match("score BETWEEN tiny_value AND quantity", app_props()),
true = match("score BETWEEN -tiny_value AND quantity", app_props()),
%% NOT BETWEEN
true = match("weight NOT BETWEEN 6 AND 10", app_props()),
false = match("weight NOT BETWEEN 3 AND 7", app_props()),
false = match("weight NOT BETWEEN 3 AND 5", app_props()),
true = match("score NOT BETWEEN tiny_value AND quantity", app_props()),
false = match("score NOT BETWEEN -tiny_value AND quantity", app_props()),
%% Combined with other operators
true = match("weight BETWEEN 4 AND 6 AND country = 'UK'", app_props()),
true = match("(price BETWEEN 20 AND 30) OR (weight BETWEEN 5 AND 6)", app_props()),
%% "a string cannot be used in an arithmetic expression"
false = match("weight BETWEEN 1 AND 'Z'", app_props()),
false = match("country BETWEEN 'A' AND 'Z'", app_props()),
%% "Comparison or arithmetic with an unknown value always yields an unknown value."
false = match("weight BETWEEN absent AND 10", app_props()),
false = match("weight BETWEEN 2 AND absent", app_props()),
false = match("weight BETWEEN absent AND absent", app_props()),
false = match("absent BETWEEN 2 AND 10", app_props()),
false = match("weight NOT BETWEEN absent AND 10", app_props()),
false = match("weight NOT BETWEEN 2 AND absent", app_props()),
false = match("weight NOT BETWEEN absent AND absent", app_props()),
false = match("absent NOT BETWEEN 2 AND 10", app_props()).
null_handling(_Config) ->
%% IS NULL / IS NOT NULL
true = match("missing IS NULL", app_props()),
@ -474,7 +436,6 @@ scientific_notation(_Config) ->
%% Comparisons with scientific notation
true = match("distance > 1E6", app_props()),
true = match("tiny_value < 1E-3", app_props()),
true = match("distance BETWEEN 1E6 AND 2E6", app_props()),
%% Mixed numeric formats
true = match("distance / 1200 = 1000", app_props()),
@ -551,7 +512,7 @@ type_handling(_Config) ->
complex_expressions(_Config) ->
true = match(
"country = 'UK' AND price > 10.0 AND (weight BETWEEN 4 AND 6) AND description LIKE '%test%'",
"country = 'UK' AND price > 10.0 AND description LIKE '%test%'",
app_props()
),
true = match(
@ -574,7 +535,7 @@ complex_expressions(_Config) ->
true = match(
"((country = 'UK' OR country = 'US') AND (city IN ('London', 'New York', 'Paris'))) OR " ++
"(price * (1 - discount) < 10.0 AND quantity > 50 AND description LIKE '%test%') OR " ++
"(active = TRUE AND premium = FALSE AND (weight BETWEEN 4 AND 10))",
"(active AND NOT premium)",
app_props()
).
@ -590,8 +551,6 @@ case_sensitivity(_Config) ->
true = match("country = 'France' or weight < 6", AppProps),
true = match("NoT country = 'France'", AppProps),
true = match("not country = 'France'", AppProps),
true = match("weight BeTwEeN 3 AnD 7", AppProps),
true = match("weight between 3 AnD 7", AppProps),
true = match("description LiKe '%test%'", AppProps),
true = match("description like '%test%'", AppProps),
true = match("country In ('US', 'UK', 'France')", AppProps),
@ -623,7 +582,7 @@ case_sensitivity(_Config) ->
false = match("WEIGHT = 5", AppPropsCaseSensitiveKeys),
true = match(
"country = 'UK' aNd COUNTRY = 'France' and (weight Between 4 AnD 6) AND Weight = 10",
"country = 'UK' aNd COUNTRY = 'France' and weight < 6 AND Weight = 10",
AppPropsCaseSensitiveKeys
).
@ -742,7 +701,6 @@ properties_section(_Config) ->
true = match("p.correlation-id = 789", Ps, APs),
true = match("500 < p.correlation-id", Ps, APs),
true = match("p.correlation-id BETWEEN 700 AND 800", Ps, APs),
false = match("p.correlation-id < 700", Ps, APs),
true = match("p.content-type = 'text/plain'", Ps, APs),
@ -755,11 +713,9 @@ properties_section(_Config) ->
true = match("p.absolute-expiry-time = 1311999988888", Ps, APs),
true = match("p.absolute-expiry-time > 1311999988000", Ps, APs),
true = match("p.absolute-expiry-time BETWEEN 1311999988000 AND 1311999989000", Ps, APs),
true = match("p.creation-time = 1311704463521", Ps, APs),
true = match("p.creation-time < 1311999988888", Ps, APs),
true = match("p.creation-time NOT BETWEEN 1311999988000 AND 1311999989000", Ps, APs),
true = match("p.group-id = 'some group ID'", Ps, APs),
true = match("p.group-id LIKE 'some%ID'", Ps, APs),
@ -767,7 +723,6 @@ properties_section(_Config) ->
true = match("p.group-sequence = 999", Ps, APs),
true = match("p.group-sequence >= 999", Ps, APs),
true = match("p.group-sequence BETWEEN 900 AND 1000", Ps, APs),
false = match("p.group-sequence > 999", Ps, APs),
true = match("p.reply-to-group-id = 'other group ID'", Ps, APs),