well what’s happening here is that the “insert” default on SomeClass.somebool 
is being copied out to SomeHistoryClass.somebool, and the history operation 
uses INSERT.  So the INSERT with “None” as the value hits the default and puts 
in None instead.

Two ways this wouldn’t happen:

1. history meta doesn’t copy column defaults when it copies columns.   

2. history meta does a translation of None to NULL when it does an INSERT.

I think #1 is right, while I want to support INSERT defaults on history tables, 
those would be on columns that were added in addition to the columns that are 
“copied” over from the original.   The columns we copy from the original should 
never need to have defaults since there’s always a value.

here’s a patch:

--- a/examples/versioned_history/history_meta.py
+++ b/examples/versioned_history/history_meta.py
@@ -32,14 +32,19 @@ def _history_mapper(local_mapper):
     polymorphic_on = None
     super_fks = []
 
+    def _col_copy(col):
+        col = col.copy()
+        col.unique = False
+        col.default = col.server_default = None
+        return col
+
     if not super_mapper or local_mapper.local_table is not 
super_mapper.local_table:
         cols = []
         for column in local_mapper.local_table.c:
             if _is_versioning_col(column):
                 continue
 
-            col = column.copy()
-            col.unique = False
+            col = _col_copy(column)
 
             if super_mapper and col_references_table(column, 
super_mapper.local_table):
                 super_fks.append((col.key, 
list(super_history_mapper.local_table.primary_key)[0]))
@@ -80,8 +85,7 @@ def _history_mapper(local_mapper):
         # been added and add them to the history table.
         for column in local_mapper.local_table.c:
             if column.key not in super_history_mapper.local_table.c:
-                col = column.copy()
-                col.unique = False
+                col = _col_copy(column)
                 super_history_mapper.local_table.append_column(col)
         table = None
 





On Feb 24, 2014, at 7:01 AM, 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
> 
> -- 
> 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.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to