diff --git a/odata_query/sql/base.py b/odata_query/sql/base.py index ec3b119..239421c 100644 --- a/odata_query/sql/base.py +++ b/odata_query/sql/base.py @@ -236,7 +236,7 @@ def _to_pattern(self, arg: ast._Node, prefix: str = "", suffix: str = "") -> str Transform a node into a pattern usable in `LIKE` clauses. :meta private: """ - if isinstance(arg, ast.Identifier): + if isinstance(arg, (ast.Identifier, ast.Call)): res = self.visit(arg) if prefix: res = f"'{prefix}' || " + res diff --git a/tests/integration/django/test_odata_to_django_q.py b/tests/integration/django/test_odata_to_django_q.py index 3dc0d7d..c442748 100644 --- a/tests/integration/django/test_odata_to_django_q.py +++ b/tests/integration/django/test_odata_to_django_q.py @@ -234,6 +234,11 @@ def tz(offset: int) -> dt.tzinfo: "contains(title, 'TEST') eq false", Q(contains_title_test__exact=Value(False)), ), + # GITHUB-47 + ( + "contains(tolower(name), tolower('A'))", + Q(name__lower__contains=fn.Lower(Value("A"))), + ), ], ) def test_odata_filter_to_django_q_pre_v4( @@ -565,6 +570,11 @@ def test_odata_filter_to_django_q_pre_v4( "contains(title, 'TEST') eq false", Q(lu.Exact(lu.Contains(F("title"), Value("TEST")), Value(False))), ), + # GITHUB-47 + ( + "contains(tolower(name), tolower('A'))", + Q(lu.Contains(fn.Lower(F("name")), fn.Lower(Value("A")))), + ), ], ) def test_odata_filter_to_django_q_after_v4( diff --git a/tests/integration/sql/test_odata_to_athena_sql.py b/tests/integration/sql/test_odata_to_athena_sql.py index 13efdea..7ca5788 100644 --- a/tests/integration/sql/test_odata_to_athena_sql.py +++ b/tests/integration/sql/test_odata_to_athena_sql.py @@ -101,6 +101,11 @@ "measurement_class eq 'C' and endswith(data_collector, 'rie')", "\"measurement_class\" = 'C' AND \"data_collector\" LIKE '%rie'", ), + # GITHUB-47 + ( + "contains(tolower(name), tolower('A'))", + "LOWER(\"name\") LIKE '%' || LOWER('A') || '%'", + ), ], ) def test_odata_filter_to_sql(odata_query: str, expected: str, lexer, parser): diff --git a/tests/integration/sql/test_odata_to_sql.py b/tests/integration/sql/test_odata_to_sql.py index 4e472bd..7c1b250 100644 --- a/tests/integration/sql/test_odata_to_sql.py +++ b/tests/integration/sql/test_odata_to_sql.py @@ -103,6 +103,11 @@ "measurement_class eq 'C' and endswith(data_collector, 'rie')", "\"measurement_class\" = 'C' AND \"data_collector\" LIKE '%rie'", ), + # GITHUB-47 + ( + "contains(tolower(name), tolower('A'))", + "LOWER(\"name\") LIKE '%' || LOWER('A') || '%'", + ), ], ) def test_odata_filter_to_sql( diff --git a/tests/integration/sql/test_odata_to_sqlite.py b/tests/integration/sql/test_odata_to_sqlite.py index e65a061..31ceb5b 100644 --- a/tests/integration/sql/test_odata_to_sqlite.py +++ b/tests/integration/sql/test_odata_to_sqlite.py @@ -104,6 +104,11 @@ "measurement_class eq 'C' and endswith(data_collector, 'rie')", "\"measurement_class\" = 'C' AND \"data_collector\" LIKE '%rie'", ), + # GITHUB-47 + ( + "contains(tolower(name), tolower('A'))", + "LOWER(\"name\") LIKE '%' || LOWER('A') || '%'", + ), ], ) def test_odata_filter_to_sql(odata_query: str, expected: str, lexer, parser): diff --git a/tests/integration/sql/test_querying.py b/tests/integration/sql/test_querying.py index ba80c27..3312d50 100644 --- a/tests/integration/sql/test_querying.py +++ b/tests/integration/sql/test_querying.py @@ -55,6 +55,7 @@ def sample_data_sess(db_conn, db_schema): ("blogpost", "year(published_at) eq 2019", 1), # GITHUB-19 ("blogpost", "contains(title, 'Query') eq true", 1), + ("blogpost", "contains(tolower(title), tolower('Query'))", 1), ], ) def test_query_with_odata( diff --git a/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_core.py b/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_core.py index 6c37fcb..e82f2a8 100644 --- a/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_core.py +++ b/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_core.py @@ -261,6 +261,11 @@ def tz(offset: int) -> dt.tzinfo: "contains(title, 'TEST') eq true", BlogPost.c.title.contains("TEST") == True, # noqa:E712 ), + # GITHUB-47 + ( + "contains(tolower(title), tolower('A'))", + functions_ext.lower(BlogPost.c.title).contains(functions_ext.lower("A")), + ), ], ) def test_odata_filter_to_sqlalchemy_query( diff --git a/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_orm.py b/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_orm.py index adb6f00..495d8ca 100644 --- a/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_orm.py +++ b/tests/integration/sqlalchemy/test_odata_to_sqlalchemy_orm.py @@ -197,6 +197,11 @@ def tz(offset: int) -> dt.tzinfo: "contains(title, 'TEST') eq true", BlogPost.title.contains("TEST") == True, # noqa:E712 ), + # GITHUB-47 + ( + "contains(tolower(title), tolower('A'))", + functions_ext.lower(BlogPost.title).contains(functions_ext.lower("A")), + ), ], ) def test_odata_filter_to_sqlalchemy_query(