On Mon, Feb 24, 2014 at 12:01 PM, Simon King <si...@simonking.org.uk> wrote:
> Hi,
>
> I'm using the Versioned class from the examples, and I noticed that
> when I set a Boolean column to None (NULL in the db), the eventual
> history row corresponding to that change gets a 0 rather than a NULL.
>
> In the test script below, I create an instance of a versioned class,
> with a boolean property initially set to False. I set it to None, and
> check that the history table records the old False value, then I set
> it to True and check that the history table records the old None
> value. The final assertion fails because None was actually inserted as
> 0. I've tested using 0.8.2 and 0.9.3, against sqlite and mysql, and
> get the same results.
>
> I've instrumented the create_version function in history_meta.py, and
> as far as I can tell everything looks fine. The boolean column in the
> history table is nullable, the bind processor for the column is
> boolean_to_int, and calling that function with None returns None as
> expected. But the INSERT statement is inserting a 0:
>
> sqlalchemy.engine.base.Engine INSERT INTO sometable_history (id, name,
> somebool, version, changed) VALUES (?, ?, ?, ?, ?)
> sqlalchemy.engine.base.Engine (1, u'sc1modified', 0, 2, '2014-02-24
> 11:56:31.730591')
>
> Here's the test script:
>
> from sqlalchemy.ext.declarative import declarative_base
> import sqlalchemy as sa
> import sqlalchemy.orm as saorm
>
> from history_meta import Versioned, versioned_session
>
> Base = declarative_base()
> Session = saorm.sessionmaker()
> versioned_session(Session)
>
> class SomeClass(Versioned, Base):
>     __tablename__ = 'sometable'
>     id = sa.Column(sa.Integer, primary_key=True)
>     name = sa.Column(sa.String(50))
>     somebool = sa.Column(sa.Boolean, default=False)
>
> def test():
>     engine = sa.create_engine('sqlite://', echo='debug')
>     Base.metadata.create_all(engine)
>     session = Session(bind=engine)
>
>     def someboolhistory():
>         SomeClassHistory = SomeClass.__history_mapper__.class_
>         history = (session.query(SomeClassHistory)
>                    .order_by(SomeClassHistory.version)
>                    .all())
>         return [h.somebool for h in history]
>
>     def checkhistory(expected):
>         actual = someboolhistory()
>         assert actual == expected, ('%s != %s' % (expected, actual))
>
>     sc = SomeClass(name='sc1', somebool=False)
>     session.add(sc)
>     session.commit()
>
>     sc.name = 'sc1modified'
>     sc.somebool = None
>     session.commit()
>
>     checkhistory([False])
>
>     sc.name = 'sc1modified2'
>     sc.somebool = True
>     session.commit()
>
>     checkhistory([False, None])
>
> if __name__ == '__main__':
>     test()
>
>
> Thanks in advance for any help,
>
> Simon

By the way, while investigating this, I came across a potential gotcha
in the ComparableEntity class. It deliberately skips non-None
attributes on the source entity, which means that 2 instances can
compare equal if an attribute is NULL in one case but non-NULL in the
other (exactly what I was looking for in the test above). I guess that
since ComparableEntity is only intended for use in the SA test suite
this is fine, but it caught me out when trying to incorporate the
above script in test_versioning.py.

Cheers,

Simon

-- 
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 sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to