Hi. I'm trying to use ext.versioned, and it seems to malfunction when
working against an Oracle Database. Here's a testcase for Oracle that
fails:

from elixir import Entity, Unicode, Integer, using_options, Field,
ManyToOne, metadata, session, setup_all, create_all
from elixir.ext.versioned import acts_as_versioned

from sqlalchemy import Sequence

class Book(Entity):
    using_options(tablename='books', auto_primarykey=False)
    acts_as_versioned()

    book_id = Field(Integer, Sequence('books_seq'), primary_key=True)
    title = Field(Unicode(120))
    category = Field(Unicode(120))
    author = ManyToOne('Author')

class Author(Entity):
    using_options(tablename='authors', auto_primarykey=False)

    id = Field(Integer, Sequence('authors_seq'), primary_key=True)
    name = Field(Unicode(120))

metadata.bind = "oracle://:@pearl/"
metadata.bind.echo = True

setup_all()

metadata.drop_all()
metadata.create_all()

a = Author(name='Itamar Ravid')
b = Book(title='A Test', category='Category', author=a)

session.commit()

b.category = 'A Change'

session.commit()

Here are the relevant error messages:

2008-09-29 15:05:27,205 INFO sqlalchemy.engine.base.Engine.0x..0c
BEGIN
2008-09-29 15:05:27,207 INFO sqlalchemy.engine.base.Engine.0x..0c
SELECT authors_seq.nextval FROM DUAL
2008-09-29 15:05:27,208 INFO sqlalchemy.engine.base.Engine.0x..0c {}
2008-09-29 15:05:27,215 INFO sqlalchemy.engine.base.Engine.0x..0c
INSERT INTO authors (id, name) VALUES (:id, :name)
2008-09-29 15:05:27,216 INFO sqlalchemy.engine.base.Engine.0x..0c
{'name': 'Itamar Ravid', 'id': 1}
2008-09-29 15:05:27,225 INFO sqlalchemy.engine.base.Engine.0x..0c
SELECT books_seq.nextval FROM DUAL
2008-09-29 15:05:27,226 INFO sqlalchemy.engine.base.Engine.0x..0c {}
2008-09-29 15:05:27,233 INFO sqlalchemy.engine.base.Engine.0x..0c
INSERT INTO books (book_id, title, category, author_id, version,
timestamp) VALUES
(:book_id, :title, :category, :author_id, :version, :timest
amp)
2008-09-29 15:05:27,234 INFO sqlalchemy.engine.base.Engine.0x..0c
{'category': 'Category', 'title': 'A Test', 'timestamp':
datetime.datetime(2008, 9, 29, 15, 5, 27, 224406), 'version': 1,
'book_id': 1, 'autho
r_id': 1}
2008-09-29 15:05:27,239 INFO sqlalchemy.engine.base.Engine.0x..0c
COMMIT
2008-09-29 15:05:27,244 INFO sqlalchemy.engine.base.Engine.0x..0c
BEGIN
2008-09-29 15:05:27,246 INFO sqlalchemy.engine.base.Engine.0x..0c
SELECT books.book_id, books.title, books.category, books.author_id,
books.version, books.timestamp
FROM books
WHERE books.book_id = :book_id_1
2008-09-29 15:05:27,248 INFO sqlalchemy.engine.base.Engine.0x..0c
{'book_id_1': 1}
2008-09-29 15:05:27,254 INFO sqlalchemy.engine.base.Engine.0x..0c
SELECT books_seq.nextval FROM DUAL
2008-09-29 15:05:27,255 INFO sqlalchemy.engine.base.Engine.0x..0c {}
2008-09-29 15:05:27,258 INFO sqlalchemy.engine.base.Engine.0x..0c
INSERT INTO books_history (book_id, version) VALUES
(:book_id, :version)
2008-09-29 15:05:27,259 INFO sqlalchemy.engine.base.Engine.0x..0c
{'version': None, 'book_id': 2}
2008-09-29 15:05:27,271 INFO sqlalchemy.engine.base.Engine.0x..0c
ROLLBACK
...
---------------------------------------------------------------------------
DatabaseError                             Traceback (most recent call
last)

/home/iravid/bla.py in <module>()
     34 b.category = 'A Change'
     35
---> 36 session.commit()
     37
     38

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/scoping.pyc in do(self, *args, **kwargs)
     96 def instrument(name):
     97     def do(self, *args, **kwargs):
---> 98         return getattr(self.registry(), name)(*args, **kwargs)
     99     return do
    100 for meth in ('add', 'add_all', 'get', 'load', 'close', 'save',
'commit', 'update', 'save_or_update', 'flush', 'query', 'delete',
'merge', 'clear', 'refresh', 'expire', 'expunge', 'rollback', 'begin',
'begin_nested', 'connection', 'execute', 'scalar', 'get_bind',
'is_modified', '__contains__', '__iter__'):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/session.pyc in commit(self)
    555                 raise exceptions.InvalidRequestError("No
transaction is begun.")
    556
--> 557         self.transaction.commit()
    558         if self.transaction is None and self.transactional:
    559             self.begin()

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/session.pyc in commit(self)
    260         self._assert_is_open()
    261         if not self._prepared:
--> 262             self._prepare_impl()
    263
    264         if self._parent is None or self.nested:


/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/session.pyc in _prepare_impl(self)
    244
    245         if self.autoflush:
--> 246             self.session.flush()
    247
    248         if self._parent is None and self.session.twophase:

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/session.pyc in flush(self, objects)
    787                 if not objects:
    788                     return
--> 789         self.uow.flush(self, objects)
    790
    791     def get(self, class_, ident, **kwargs):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/unitofwork.pyc in flush(self, session,
objects)
    231         flush_context.transaction = session.transaction
    232         try:
--> 233             flush_context.execute()
    234
    235             if session.extension is not None:

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/unitofwork.pyc in execute(self)
    443         if self._should_log_info:
    444             self.logger.info("Task dump:\n" +
self._dump(tasks))
--> 445         UOWExecutor().execute(self, tasks)
    446         if self._should_log_info:
    447             self.logger.info("Execute Complete")

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/unitofwork.pyc in execute(self, trans, tasks,
isdelete)
    928         if isdelete is not True:
    929             for task in tasks:
--> 930                 self.execute_save_steps(trans, task)
    931         if isdelete is not False:
    932             for task in util.reversed(tasks):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/unitofwork.pyc in execute_save_steps(self,
trans, task)
    943
    944     def execute_save_steps(self, trans, task):
--> 945         self.save_objects(trans, task)
    946         self.execute_cyclical_dependencies(trans, task, False)
    947         self.execute_per_element_childtasks(trans, task,
False)

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/unitofwork.pyc in save_objects(self, trans,
task)
    934
    935     def save_objects(self, trans, task):
--> 936         task.mapper._save_obj(task.polymorphic_tosave_objects,
trans)
    937
    938     def delete_objects(self, trans, task):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/mapper.pyc in _save_obj(self, states,
uowtransaction, postupdate, post_update_cols, single)
   1018                 else:
   1019                     if 'before_update' in
mapper.extension.methods:
-> 1020                         mapper.extension.before_update(mapper,
connection, state.obj())
   1021
   1022         for state, connection, has_identity in tups:

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/orm/util.pyc in _do(*args, **kwargs)
    121         def _do(*args, **kwargs):
    122             for elem in self.__elements:
--> 123                 ret = getattr(elem, funcname)(*args, **kwargs)
    124                 if ret is not EXT_CONTINUE:
    125                     return ret

/home/iravid/pythonenv/lib/python2.5/site-packages/Elixir-0.6.1-
py2.5.egg/elixir/ext/versioned.py in before_update(self, mapper,
connection, instance)
    109                 dict_values = dict(old_values.items())
    110                 connection.execute(
--> 111
instance.__class__.__history_table__.insert(), dict_values)
    112                 instance.version = instance.version + 1
    113                 instance.timestamp = datetime.now()

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in execute(self, object,
*multiparams, **params)
    842         for c in type(object).__mro__:
    843             if c in Connection.executors:
--> 844                 return Connection.executors[c](self, object,
multiparams, params)
    845         else:
    846             raise exceptions.InvalidRequestError("Unexecutable
object type: " + str(type(object)))

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in execute_clauseelement(self,
elem, multiparams, params)
    893         else:
    894             keys = None
--> 895         return
self._execute_compiled(elem.compile(dialect=self.dialect,
column_keys=keys, inline=len(params) > 1), distilled_params=params)
    896
    897     def _execute_compiled(self, compiled, multiparams=None,
params=None, distilled_params=None):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in _execute_compiled(self,
compiled, multiparams, params, distilled_params)
    905
    906         context.pre_execution()
--> 907         self.__execute_raw(context)
    908         context.post_execution()
    909         self._autocommit(context)

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in __execute_raw(self, context)
    914             self._cursor_executemany(context.cursor,
context.statement, context.parameters, context=context)
    915         else:
--> 916             self._cursor_execute(context.cursor,
context.statement, context.parameters[0], context=context)
    917
    918     def _execute_ddl(self, ddl, params, multiparams):

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in _cursor_execute(self, cursor,
statement, parameters, context)
    958             self.dialect.do_execute(cursor, statement,
parameters, context=context)
    959         except Exception, e:
--> 960             self._handle_dbapi_exception(e, statement,
parameters, cursor)
    961             raise
    962

/home/iravid/pythonenv/lib/python2.5/site-packages/SQLAlchemy-0.4.7p1-
py2.5.egg/sqlalchemy/engine/base.pyc in _handle_dbapi_exception(self,
e, statement, parameters, cursor)
    940                 if self.__close_with_result:
    941                     self.close()
--> 942             raise exceptions.DBAPIError.instance(statement,
parameters, e, connection_invalidated=is_disconnect)
    943         finally:
    944             del self._reentrant_error

DatabaseError: (DatabaseError) ORA-01400: cannot insert NULL into
("IRAVID"."BOOKS_HISTORY"."VERSION")
 'INSERT INTO books_history (book_id, version) VALUES
(:book_id, :version)' {'version': None, 'book_id': 2}
WARNING: Failure executing file: <bla.py>

If you look closely at the SQL statements being sent to Oracle
preceding the stack trace, you'll notice that for some reason SA is
fetching another book_id from the relevant sequence in order to insert
into books_history, which is clearly wrong behavior.

The following, equivalent testcase, modified for MySQL, succeeds:

from elixir import Entity, Unicode, Integer, using_options, Field,
ManyToOne, metadata, session, setup_all, create_all
from elixir.ext.versioned import acts_as_versioned

class Book(Entity):
    using_options(tablename='books')
    acts_as_versioned()

    title = Field(Unicode(120))
    category = Field(Unicode(120))
    author = ManyToOne('Author')

class Author(Entity):
    using_options(tablename='authors')

    name = Field(Unicode(120))

metadata.bind = "mysql://iravid:@localhost/books"
metadata.bind.echo = True

setup_all()

metadata.drop_all()
metadata.create_all()

a = Author(name='Itamar Ravid')
b = Book(title='A Test', category='Category', author=a)

session.commit()

b.category = 'A Change'

session.commit()

I was wondering if anyone could spot a mistake in my code, before I
open a bug in Elixir's trac.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"SQLElixir" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/sqlelixir?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to