Mike,
Thanks a lot for the response and also for considering this as a future
feature. I've switched to using `attr.ilike(value)` in the meantime and it
is working great.
On Thursday, August 27, 2020 at 2:42:43 PM UTC-5 Mike Bayer wrote:
>
>
> On Thu, Aug 27, 2020, at 2:27 PM, Jacob Pavlock wrote:
>
> I have two classes, a Track and an Album. In order to have album fields
> easily accessible from a track, I created an association proxy attribute.
>
> ```python
> class Track(MusicItem, Base):
> __tablename__ = "tracks"
>
> _id = Column(Integer, primary_key=True)
> _album_id = Column(Integer, ForeignKey("albums._id"))
> artist = Column(String, nullable=False, default="")
> path = Column(_PathType, nullable=False, unique=True)
> title = Column(String, nullable=False, default="")
>
> _album_obj = relationship("Album", back_populates="tracks")
>
> album = association_proxy('_album_obj', 'title')
> ```
> ```python
> class Album(MusicItem, Base):
> __tablename__ = "albums"
>
> _id = Column(Integer, primary_key=True)
> artist = Column(String, nullable=False, default="")
> title = Column(String, nullable=False, default="")
>
> tracks = relationship("Track", back_populates="_album_obj",
> cascade="all, delete")
> ```
>
> However, I am having issues when trying to do a case-insensitive query for
> an album.
>
> When creating a query filter, the following works fine for normal
> (non-association proxy) attributes of a Track.
> ```python
> attr = getattr(Track, field)
> attr = sqlalchemy.func.lower(attr)
>
>
> So there is a non-smoothness to how this is right now, but it's not very
> straightforward for the association proxy to do this right now and a new
> feature would need to be added for func.lower() to work directly in that
> way, returning a new expression object that would apply the "lower()"
> function inside of itself, since you'll note that a plain LIKE comparison
> produces this SQL:
>
> SELECT tracks._id AS tracks__id, tracks._album_id AS tracks__album_id,
> tracks.artist AS tracks_artist, tracks.title AS tracks_title
> FROM tracks
> WHERE EXISTS (SELECT 1
> FROM albums
> WHERE albums._id = tracks._album_id AND albums.title LIKE ?)
>
> I can't really come up with a way to make it do the right thing for that
> right now since it would have to be some kind of deferred object that can
> take func.lower() into itself somehow and generate the right output later.
> https://github.com/sqlalchemy/sqlalchemy/issues/5541 is added but I have
> nothing for it right now.
>
> You can get case insensitive using ilike():
>
> print(s.query(Track).filter(Track.album.ilike("%title%")).all())
>
> SELECT tracks._id AS tracks__id, tracks._album_id AS tracks__album_id,
> tracks.artist AS tracks_artist, tracks.title AS tracks_title
> FROM tracks
> WHERE EXISTS (SELECT 1
> FROM albums
> WHERE albums._id = tracks._album_id AND lower(albums.title) LIKE lower(?))
>
>
>
>
>
>
>
> ```
> But, when `attr` is an association proxy attribute, the following error
> occurs:
> ```python
> moe/core/query.py:187: in query
> items = session.query(query_cls).filter(*query_filters).all()
> .venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py:3346: in all
> return list(self)
> .venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py:3508: in __iter__
> return self._execute_and_instances(context)
> .venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py:3533: in
> _execute_and_instances
> result = conn.execute(querycontext.statement, self._params)
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py:1011: in
> execute
> return meth(self, multiparams, params)
> .venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py:298: in
> _execute_on_connection
> return connection._execute_clauseelement(self, multiparams, params)
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py:1124: in
> _execute_clauseelement
> ret = self._execute_context(
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py:1316: in
> _execute_context
> self._handle_dbapi_exception(
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py:1510: in
> _handle_dbapi_exception
> util.raise_(
> .venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py:182: in raise_
> raise exception
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py:1276: in
> _execute_context
> self.dialect.do_execute(
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> _ _ _ _ _ _ _ _ _ _
>
> self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object
> at 0x7f54a0692430>
> cursor = <sqlite3.Cursor object at 0x7f54a0638b90>
> statement = 'SELECT tracks._id AS tracks__id, tracks._album_id AS
> tracks__album_id, tracks.artist AS tracks_artist, tracks.path AS
> tracks_path, tracks.title AS tracks_title \nFROM tracks \nWHERE lower(?) =
> ?'
> parameters =
> (ColumnAssociationProxyInstance(AssociationProxy('_album_obj', 'title')),
> 'tmp')
> context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object
> at 0x7f54a05b7fa0>
>
> def do_execute(self, cursor, statement, parameters, context=None):
> > cursor.execute(statement, parameters)
> E sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error
> binding parameter 0 - probably unsupported type.
> E [SQL: SELECT tracks._id AS tracks__id, tracks._album_id AS
> tracks__album_id, tracks.artist AS tracks_artist, tracks.path AS
> tracks_path, tracks.title AS tracks_title
> E FROM tracks
> E WHERE lower(?) = ?]
> E [parameters:
> (ColumnAssociationProxyInstance(AssociationProxy('_album_obj', 'title')),
> 'tmp')]
> E (Background on this error at: http://sqlalche.me/e/13/rvf5)
>
> .venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py:593:
> InterfaceError
> ```
>
> Not sure if helpful, but here's some more simplified code on the query. I
> can expand on this if needed.
> ```python
> query_filters = [attr == query_value]
> items = session.query(Track).filter(*query_filters).all()
> ```
>
> I have also seen this relevant piece in the docs on creating a custom
> comparator, but I could not figure out how to apply this to my code. I
> added it alongside the Track/Album classes, but it did not help anything
> and the same error occurs.
>
> ```
> *class* *MyComparator*(ColumnOperators):
> *def* operate(self, op, other):
> *return* op(func.lower(self), func.lower(other))
> ```
>
> What's the best way to handle case-insensitive queries with association
> proxy attributes?
>
> I am crossposting this from stackoverflow
> <https://stackoverflow.com/questions/63564747/how-to-do-a-case-insensitive-query-for-sqlalchemy-association-proxy-attributes>
>
>
> --
> SQLAlchemy -
> The Python SQL Toolkit and Object Relational Mapper
>
> http://www.sqlalchemy.org/
>
> To post example code, please provide an MCVE: Minimal, Complete, and
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> description.
> ---
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sqlalchemy/da34bbda-deab-496d-9937-7a4d3b938616n%40googlegroups.com
>
> <https://groups.google.com/d/msgid/sqlalchemy/da34bbda-deab-496d-9937-7a4d3b938616n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
>
>
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/e059c741-7bdb-47f8-925a-e1a8a53dcb5cn%40googlegroups.com.