Skip to content

Commit 4de4c9f

Browse files
committed
SQLAlchemy DQL: Use CrateDB's native ILIKE operator
Instead of using SA's generic implementation `lower() LIKE lower()`, use CrateDB's native one.
1 parent 3376b5c commit 4de4c9f

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Unreleased
88
- SQLAlchemy: Rename leftover occurrences of ``Object``. The new symbol to represent
99
CrateDB's ``OBJECT`` column type is now ``ObjectType``.
1010

11+
- SQLAlchemy DQL: Use CrateDB's native ``ILIKE`` operator instead of using SA's
12+
generic implementation ``lower() LIKE lower()``. Thanks, @hlcianfagna.
13+
1114

1215
2023/07/06 0.32.0
1316
=================

src/crate/client/sqlalchemy/compiler.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,40 @@ def visit_any(self, element, **kw):
244244
self.process(element.right, **kw)
245245
)
246246

247+
def visit_ilike_case_insensitive_operand(self, element, **kw):
248+
"""
249+
Use native `ILIKE` operator, like PostgreSQL's `PGCompiler`.
250+
"""
251+
return element.element._compiler_dispatch(self, **kw)
252+
253+
def visit_ilike_op_binary(self, binary, operator, **kw):
254+
"""
255+
Use native `ILIKE` operator, like PostgreSQL's `PGCompiler`.
256+
257+
Do not implement the `ESCAPE` functionality, because it is not
258+
supported by CrateDB.
259+
"""
260+
if binary.modifiers.get("escape", None) is not None:
261+
raise NotImplementedError("Unsupported feature: ESCAPE is not supported")
262+
return "%s ILIKE %s" % (
263+
self.process(binary.left, **kw),
264+
self.process(binary.right, **kw),
265+
)
266+
267+
def visit_not_ilike_op_binary(self, binary, operator, **kw):
268+
"""
269+
Use native `ILIKE` operator, like PostgreSQL's `PGCompiler`.
270+
271+
Do not implement the `ESCAPE` functionality, because it is not
272+
supported by CrateDB.
273+
"""
274+
if binary.modifiers.get("escape", None) is not None:
275+
raise NotImplementedError("Unsupported feature: ESCAPE is not supported")
276+
return "%s NOT ILIKE %s" % (
277+
self.process(binary.left, **kw),
278+
self.process(binary.right, **kw),
279+
)
280+
247281
def limit_clause(self, select, **kw):
248282
"""
249283
Generate OFFSET / LIMIT clause, PostgreSQL-compatible.

src/crate/client/sqlalchemy/tests/compiler_test.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# However, if you have executed another commercial license agreement
1919
# with Crate these terms will supersede the license and you may use the
2020
# software solely pursuant to the terms of the relevant commercial agreement.
21-
21+
from textwrap import dedent
2222
from unittest import mock, TestCase, skipIf
2323

2424
from crate.client.sqlalchemy.compiler import crate_before_execute
@@ -71,6 +71,58 @@ def test_bulk_update_on_builtin_type(self):
7171

7272
self.assertFalse(hasattr(clauseelement, '_crate_specific'))
7373

74+
def test_select_with_ilike(self):
75+
"""
76+
Verify the compiler uses CrateDB's native `ILIKE` method.
77+
"""
78+
selectable = self.mytable.select().where(self.mytable.c.name.ilike("%foo%"))
79+
statement = str(selectable.compile(bind=self.crate_engine))
80+
self.assertEqual(statement, dedent("""
81+
SELECT mytable.name, mytable.data
82+
FROM mytable
83+
WHERE mytable.name ILIKE ?
84+
""").strip()) # noqa: W291
85+
86+
def test_select_with_not_ilike(self):
87+
"""
88+
Verify the compiler uses CrateDB's native `ILIKE` method.
89+
"""
90+
selectable = self.mytable.select().where(self.mytable.c.name.notilike("%foo%"))
91+
statement = str(selectable.compile(bind=self.crate_engine))
92+
if SA_VERSION < SA_1_4:
93+
self.assertEqual(statement, dedent("""
94+
SELECT mytable.name, mytable.data
95+
FROM mytable
96+
WHERE lower(mytable.name) NOT LIKE lower(?)
97+
""").strip()) # noqa: W291
98+
else:
99+
self.assertEqual(statement, dedent("""
100+
SELECT mytable.name, mytable.data
101+
FROM mytable
102+
WHERE mytable.name NOT ILIKE ?
103+
""").strip()) # noqa: W291
104+
105+
def test_select_with_ilike_and_escape(self):
106+
"""
107+
Verify the compiler fails when using CrateDB's native `ILIKE` method together with `ESCAPE`.
108+
"""
109+
110+
selectable = self.mytable.select().where(self.mytable.c.name.ilike("%foo%", escape='\\'))
111+
with self.assertRaises(NotImplementedError) as cmex:
112+
selectable.compile(bind=self.crate_engine)
113+
self.assertEqual(str(cmex.exception), "Unsupported feature: ESCAPE is not supported")
114+
115+
@skipIf(SA_VERSION < SA_1_4, "SQLAlchemy 1.3 and earlier do not support native `NOT ILIKE` compilation")
116+
def test_select_with_not_ilike_and_escape(self):
117+
"""
118+
Verify the compiler fails when using CrateDB's native `ILIKE` method together with `ESCAPE`.
119+
"""
120+
121+
selectable = self.mytable.select().where(self.mytable.c.name.notilike("%foo%", escape='\\'))
122+
with self.assertRaises(NotImplementedError) as cmex:
123+
selectable.compile(bind=self.crate_engine)
124+
self.assertEqual(str(cmex.exception), "Unsupported feature: ESCAPE is not supported")
125+
74126
def test_select_with_offset(self):
75127
"""
76128
Verify the `CrateCompiler.limit_clause` method, with offset.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy