Проблема в том, что в Sqlite должен выполняться запрос, который включает фильтр ILIKE
для текста, отличного от ASCII, но сборка по умолчанию Sqlite не поддерживает сопоставление без учета регистра для такого текста .
Одним из возможных способов обхода проблемы является хранение нормализованной по регистру копии текста в другом столбце. Это предполагает, что удвоение объема памяти для текста не является проблемой.
Простой способ сделать это будет выглядеть так:
class Likeable(Base):
__tablename__ = 'likeable'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(32))
lowercase_name = sa.Column(sa.String(32))
Экземпляры могут быть созданы следующим образом:
likeable = Likeable(name='Россия', lowercase_name='россия')
и запрашивается следующим образом
session.query(Likeable).filter(Likeable.lowercase_name.like('р%'))
Это нормально, требуется, чтобы name
и lowercase_name
всегда обновлялись одновременно. Мы можем обойти это, замаскировав lowercase_name
с помощью гибридного свойства и перехватив присвоения name
с помощью прослушивателя атрибутов . Слушатель обнаруживает изменение name
и передает новое значение установщику lowercase_name
.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Session
Base = declarative_base()
class Likeable(Base):
__tablename__ = 'likeable'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(32))
_lowercase_name = sa.Column(sa.String(32))
def __repr__(self):
return f"Likeable(name='{self.name}', lowercase_name='{self.lowercase_name}')"
@hybrid_property
def lowercase_name(self):
return self._lowercase_name
@lowercase_name.setter
def lowercase_name(self, value):
self._lowercase_name = value.lower()
@sa.event.listens_for(Likeable.name, 'set')
def receive_set(target, value, oldvalue, initiator):
target.lowercase_name = value
Запуск этого кода:
engine = sa.create_engine('sqlite:///', echo=True)
Base.metadata.create_all(bind=engine)
session = Session(bind=engine)
likeables = [Likeable(name=n) for n in ['Россия', 'Русский', 'Французский']]
session.add_all(likeables)
session.commit()
session.close()
session = Session(bind=engine)
q = session.query(Likeable).filter(Likeable.lowercase_name.like('р%'))
for r in q:
print(r)
session.close()
Производит следующий вывод:
Likeable(name='Россия', lowercase_name='россия')
Likeable(name='Русский', lowercase_name='русский')
Это демонстрационный код. Для производства вы можете добавить проверки, чтобы гарантировать, что name
и lowercase_name
не могут рассинхронизироваться.