On 9/15/15 4:10 PM, Mike Bayer wrote:


On 9/15/15 2:47 PM, Katie Wurman wrote:


class Person(Base):
    __tablename__ = 'person'
    id = Column('id_string', CastToIntegerType, primary_key=True)

pets = relationship('Pets', primaryjoin='foreign(Pets.person_id)==Person.id')

class Pets(Base):
    __tablename__ = 'pets'
     id = Column('id', Integer, primary_key=True)
person_id = Column('person_id', Integer, ForeignKey('Person.id'), primary_key=True)


In this case, when I query for person.pets the join condition between Person and Pets becomes: public.pets.person_id = CAST(%(param_1)s AS VARCHAR)
pets.person_id is persisted as an Integer, so I would think a comparison of pets.person_id to an integer value should be using Integer as the type? Let's check...oh well, it does. It's because that particular comparison is generated on a column-by-column basis. That's now bug https://bitbucket.org/zzzeek/sqlalchemy/issues/3530/bind-param-replacement-in-join_condition.

OK this one is throwing me off, but actually if you really intend for this mapping to work in every way right now, you have to use CAST, else the relationship will not work for eager loading. An eager load renders both columns directly so you'd need CAST in the join condition.

That is, to run both of these queries:


p1 = s.query(Person).first()


p1 = s.query(Person).options(joinedload(Person.pets)).first()

the mapping has to look like this:

    pets = relationship('Pets',
        primaryjoin="foreign(Pets.person_id) == cast(Person.id, Integer)")



the eager join generates:

FROM person LEFT OUTER JOIN pets AS pets_1 ON pets_1.person_id = CAST(person.id_string AS INTEGER)

the lazyload generates:

WHERE pets.person_id = CAST(CAST(%(param_1)s AS VARCHAR) AS INTEGER)


some kind of smartness could perhaps figure out that this is a double CAST. But really, in other cases it might not be clear that "id" and "id_string" here both deal with the same type on the Python side.

I'd probably stick with this approach for now. I have doubts about both issues I've created.








So I'd stick a type_coerce in there and be done with it, which won't generate the CAST either, if that was the problem....except....that doesn't seem to be working either because of the way type_coerce doesn't create a permanent construct, so that's bug https://bitbucket.org/zzzeek/sqlalchemy/issues/3531/bind-param-replacement-in-join_condition.

Workaround now is at the bottom of 3531 which looks like:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql import ColumnElement
from sqlalchemy.sql.elements import _clone


class LateTypeCoerce(ColumnElement):
    def __init__(self, element, type_):
        self.element = element
        self.type = type_
        self._proxies = [element]

    def self_group(self, against=None):
return LateTypeCoerce(self.element.self_group(against=against), self.type)

    def get_children(self, **kwargs):
        return self.element,

    def _copy_internals(self, clone=_clone, **kw):
        self.element = clone(self.element, **kw)

    @property
    def _from_objects(self):
        return self.element._from_objects

    def _make_proxy(self, selectable, **kw):
        return self.element._make_proxy(selectable, **kw)


then in mapping:

class Person(Base):
    __tablename__ = 'person'
    id = Column('id_string', CastToIntegerType, primary_key=True)

    pets = relationship('Pets',
primaryjoin=lambda: foreign(Pets.person_id) == LateTypeCoerce(Person.id, Integer))


















I can amend this by explicitly casting Person.id to Integer in the primaryjoin expression, but that's a little redundant. Is there a cleaner way to do this?

Thanks!

On Tuesday, September 15, 2015 at 10:49:51 AM UTC-7, Michael Bayer wrote:



    On 9/15/15 1:19 PM, Katie Wurman wrote:
    Hi,

    I'm having trouble implementing a model whose 'id' column needs
    to be cast to Integer type. Below is the implementation I've got
    so far:

    class CastToIntegerType(types.TypeDecorator):
        '''
        Converts stored String values to Integer via CAST operation
        '''
        impl = types.Numeric
        def column_expression(self, col):
            return func.cast(col, Integer)

    class Person(Base):
         __tablename__ = "person"
        id = Column('id_string', CastToIntegerType, primary_key=True)


    Then when I run the query

    >> person = Person.query.get(12345)

    I see the following error:

    sqlalchemy.exc.ProgrammingError: (ProgrammingError) operator
    does not exist: text = integer
    LINE 3: WHERE public.person.id_string = 12345
                                             ^
    HINT:  No operator matches the given name and argument type(s).
    You might need to add explicit type casts.

    Any ideas what I might be doing wrong?
    OK, this is close, column_expression applies to how the column
    list in the SELECT renders, e.g. "SELECT CAST(mycolumn, INT) as
    mycolumn", for a SQL expression that wraps around a value you're
    sending into the WHERE in a comparison expression, you'd use
    bind_expression for that:
    
http://docs.sqlalchemy.org/en/rel_1_0/core/type_api.html#sqlalchemy.types.TypeEngine.bind_expression
    
<http://docs.sqlalchemy.org/en/rel_1_0/core/type_api.html#sqlalchemy.types.TypeEngine.bind_expression>,
    and in this case you'd be CASTing to String so that the SQL
    renders as "WHERE id_string = CAST(12345, VARCHAR)".







    Thanks!
-- 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] <javascript:>.
    To post to this group, send email to [email protected].
    Visit this group at http://groups.google.com/group/sqlalchemy
    <http://groups.google.com/group/sqlalchemy>.
    For more options, visit https://groups.google.com/d/optout
    <https://groups.google.com/d/optout>.

--
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] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
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] <mailto:[email protected]>. To post to this group, send email to [email protected] <mailto:[email protected]>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
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 post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to