It seems that an error is raised when a deleted node is flushed when
post_update flag is set (See attached test case). Working with rev 1855.

After the second flush, the expected sql output should be:

[engine]: UPDATE node SET prev_sibling_id=? WHERE node.id = ?
[engine]: [2, 4]
[engine]: UPDATE node SET next_sibling_id=? WHERE node.id = ?
[engine]: [4, 2]
[engine]: DELETE FROM node WHERE node.id = ?
[engine]: [3]
[engine]: COMMIT

Instead we get:

[engine]: UPDATE node SET prev_sibling_id=? WHERE node.id = ?
[engine]: [2, 4]
[engine]: DELETE FROM node WHERE node.id = ?
[engine]: [3]
[engine]: UPDATE node SET prev_sibling_id=? WHERE node.id = ?
[engine]: [2, 4]
[engine]: UPDATE node SET next_sibling_id=? WHERE node.id = ?
[engine]: [4, 2]
[engine]: UPDATE node SET parent_id=?, next_sibling_id=? WHERE node.id = ?
[engine]: [None, None, 3]
[engine]: ROLLBACK

With the traceback:

  File "tree.py", line 214, in ?
    session.flush()
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/session.py", line 234,
in flush
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
207, in flush
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
377, in execute
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
639, in execute
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
650, in execute
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
615, in _execute_childtasks
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
648, in execute
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
611, in _execute_dependencies
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
508, in execute
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/dependency.py", line
197, in process_dependencies
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
261, in register_object
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/unitofwork.py", line
588, in append_postupdate
  File "build/bdist.linux-i686/egg/sqlalchemy/orm/mapper.py", line 841,
in save_obj
sqlalchemy.exceptions.FlushError: ConcurrencyError - updated rowcount 0
does not match number of objects updated 1

Two things are wrong here.

1) Attempting to update a deleted node, causing the error.
2) Node id:4 is being updated twice, while is not an error as such -
should not be happening (symptom of something else?)

Nick

Michael Bayer wrote:
> I should note that the relationship you are actually trying to set up  
> includes a circular relationship between two individual rows.   by  
> default this is impossible to accomplish with only INSERT statements  
> and not violate foreign key constraints.  to break this circular  
> relation you have to also add a special flag "post_update=True" to  
> one side of the circle, which will add an extra UPDATE statement to  
> the commit.
> 
> n_mapper = mapper(Node, node_table, properties={
>      'parent': relation(
>          Node,
>          lazy=True,
>          primaryjoin=node_table.c.parent_id==node_table.c.id,
>          foreignkey=node_table.c.id,
>          uselist=False
>      ),
>      'children': relation(
>          Node,
>          primaryjoin=node_table.c.id==node_table.c.parent_id,
>          lazy=True,
>          cascade="all"
>      ),
>      'previous_sibling': relation(
>          Node,
>          primaryjoin=node_table.c.prev_sibling_id==node_table.c.id,
>          foreignkey=node_table.c.id,
>          lazy=True,
>          uselist=False
>      ),
>      'next_sibling': relation(
>          Node,
>          primaryjoin=node_table.c.next_sibling_id==node_table.c.id,
>          foreignkey=node_table.c.id,
>          lazy=True,
>          uselist=False,
>          post_update=True,
>      )
> })
> 
[ snip ]

import sqlalchemy.mods.threadlocal
from sqlalchemy import *

engine = create_engine('sqlite:///:memory:', echo=True)
metadata = BoundMetaData(engine)
session = objectstore.session

node_table = Table('node', metadata,
    Column('id', Integer, primary_key=True),
    Column('path', String(50), nullable=False),
    Column('parent_id', Integer, ForeignKey('node.id'), nullable=True),
    Column('prev_sibling_id', Integer, ForeignKey('node.id'), nullable=True),
    Column('next_sibling_id', Integer, ForeignKey('node.id'), nullable=True)
)

class Node(object):
    def __init__(self, path=''):
        self.path = path
    
    def has_child_nodes(self):
        return len(self.children) == 0
    
    def __repr__(self):
        return '<Node(path=%s)>' % self.path
    
    def get_first_child(self):
        if '_first_child' not in self.__dict__:
            self._first_child = get_first_child(self)
        
        return self._first_child
    
    def set_first_child(self, child):
        self._first_child = child
    
    first_child = property(get_first_child, set_first_child)
    
    def get_last_child(self):
        if '_last_child' not in self.__dict__:
            self._last_child = get_last_child(self)
        
        return self._last_child
    
    def set_last_child(self, child):
        self._last_child = child
    
    last_child = property(get_last_child, set_last_child)

n_mapper = mapper(Node, node_table, properties={
    'parent': relation(
        Node,
        lazy=True,
        primaryjoin=node_table.c.parent_id==node_table.c.id,
        foreignkey=node_table.c.id,
        uselist=False
    ),
    'children': relation(
        Node,
        primaryjoin=node_table.c.id==node_table.c.parent_id,
        lazy=True,
        cascade="all"
    ),
    'previous_sibling': relation(
        Node,
        primaryjoin=node_table.c.prev_sibling_id==node_table.c.id,
        foreignkey=node_table.c.id,
        lazy=True,
        uselist=False
    ),
    'next_sibling': relation(
        Node,
        primaryjoin=node_table.c.next_sibling_id==node_table.c.id,
        foreignkey=node_table.c.id,
        lazy=True,
        uselist=False,
        post_update=True
    )
})


def get_last_child(node):
    node = session.query(Node).select(sqlalchemy.and_(
        node_table.c.parent_id==node.c.id,
        node_table.c.next_sibling_id==None
    ))
    
    if len(node) == 0:
        return None
    
    return node[0]

def get_first_child(node):
    node = session.query(Node).select(sqlalchemy.and_(
        node_table.c.parent_id==node.c.id,
        node_table.c.prev_sibling_id==None
    ))
    
    if len(node) == 0:
        return None
    
    return node[0]

def insert_before(parent, child, ref=None):
    if ref is not None:
        assert ref in parent.children, "Not a child"
    
    if ref is None:
        # append to end of list
        
        parent.children.append(child)
        child.parent = parent
        
        if parent.first_child is None:
            parent.first_child = child
        
        if parent.last_child is not None:
            parent.last_child.next_sibling = child
            child.previous_sibling = parent.last_child
            
            parent.last_child = child
            
        else:
            parent.last_child = parent.children[-1:][0]
        
    else:
        ref_index = parent.children.index(ref)
        
        previous_child = None
        next_child = None
        
        if ref_index > 0:
            previous_child = parent.children[ref_index - 1]
        
        try:
            next_child = parent.children[ref_index]
        except IndexError, e:
            next_child = None
        
        parent.children.insert(ref_index, child)
        
        child.parent = parent
        child.previous_sibling = previous_child
        child.next_sibling = next_child
        
        if previous_child is not None:
            previous_child.next_sibling = child
        
        if next_child is not None:
            next_child.previous_sibling = child
        
        if parent.first_child == ref:
            parent.first_child = child

def append_child(parent, child):
    insert_before(parent, child)

def remove_child(parent, child):
    assert child in parent.children, 'Not a child'
    
    idx = parent.children.index(child)
    
    previous_child = None
    next_child = None
    
    if idx > 0:
        previous_child = parent.children[idx - 1]
    
    try:
        next_child = parent.children[idx + 1]
    except IndexError, e:
        next_child = None
    
    parent.children.remove(child)
    
    if previous_child is not None:
        previous_child.next_sibling = next_child
    
    if next_child is not None:
        next_child.previous_sibling = previous_child
    
    if parent.first_child == child:
        parent.first_child = child.next_sibling
    
    if parent.last_child == child:
        parent.last_child = child.previous_sibling
    
    child.parent = None
    child.previous_sibling = None
    child.next_sibling = None
    
    # child is now an orphan - delete it
    
    session.delete(child)
    
    return child

if __name__ == '__main__':
    session = objectstore.session
    node_table.create()
    
    root = Node('root')
    
    about = Node('about')
    cats = Node('cats')
    stories = Node('stories')
    bruce = Node('bruce')
    
    append_child(root, about)
    append_child(root, cats)
    append_child(root, stories)
    append_child(root, bruce)
    
    session.flush()
    
    remove_child(root, cats)
    
    session.flush()
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Sqlalchemy-users mailing list
Sqlalchemy-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sqlalchemy-users

Reply via email to