commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnue] r7720 - in trunk: gnue-appserver gnue-common/src/datasources gnue


From: reinhard
Subject: [gnue] r7720 - in trunk: gnue-appserver gnue-common/src/datasources gnue-common/src/datasources/drivers/Base gnue-common/src/datasources/drivers/other
Date: Tue, 12 Jul 2005 11:59:27 -0500 (CDT)

Author: reinhard
Date: 2005-07-12 11:59:25 -0500 (Tue, 12 Jul 2005)
New Revision: 7720

Modified:
   trunk/gnue-appserver/BUGS
   trunk/gnue-common/src/datasources/Exceptions.py
   trunk/gnue-common/src/datasources/GDataSource.py
   trunk/gnue-common/src/datasources/drivers/Base/Connection.py
   trunk/gnue-common/src/datasources/drivers/Base/RecordSet.py
   trunk/gnue-common/src/datasources/drivers/Base/ResultSet.py
   trunk/gnue-common/src/datasources/drivers/other/appserver.py
Log:
If an exception happens when writing to the backend, do not change the status
of all records posted in this transaction, so the will be reposted with the
next call of post().


Modified: trunk/gnue-appserver/BUGS
===================================================================
--- trunk/gnue-appserver/BUGS   2005-07-12 14:05:39 UTC (rev 7719)
+++ trunk/gnue-appserver/BUGS   2005-07-12 16:59:25 UTC (rev 7720)
@@ -1,9 +1,5 @@
 Known bugs/issues in this release:
 
-* Time fields are broken in some dbdrivers. Depending on the driver, it's
-  possible that the "Meeting Time" field in the sample doesn't work. (has to be
-  fixed in gnue-common)
-
 * Currently, the RPC interface is a flat one. Extending the interface to be
   object oriented with a session class and a list class should at least be
   considered. (needs fixed dynamic objects in gnue-common's RPC library)
@@ -13,5 +9,7 @@
   This way referenced classes with many records could be handled. (has to be
   implemented in gnue-forms first)
 
-* There is no way to do a fulltext-search (has to be implemented in gnue-common
-  first)
+* When an exception happens in OnValidate, gnue-forms doesn't move to the
+  record causing the exception, basically because OnValidate happens at commit
+  time and not at store time. Not sure what would be the best way to handle
+  this.

Modified: trunk/gnue-common/src/datasources/Exceptions.py
===================================================================
--- trunk/gnue-common/src/datasources/Exceptions.py     2005-07-12 14:05:39 UTC 
(rev 7719)
+++ trunk/gnue-common/src/datasources/Exceptions.py     2005-07-12 16:59:25 UTC 
(rev 7720)
@@ -151,7 +151,21 @@
         u_("Number of fields in 'masterlink' and 'detaillink' attributes does "
            "not match for datasource '%s'") % name)
 
+
 # -----------------------------------------------------------------------------
+# Call of trigger function not available in this datasource
+# -----------------------------------------------------------------------------
+
+class FunctionNotAvailableError (errors.AdminError):
+  """
+  Cannot use "update" and "call" functions on this datasource.
+  """
+  def __init__ (self):
+    errors.AdminError.__init__ (self,
+        u_("Cannot use 'update' and 'call' functions on this datasource"))
+
+
+# -----------------------------------------------------------------------------
 #
 # -----------------------------------------------------------------------------
 

Modified: trunk/gnue-common/src/datasources/GDataSource.py
===================================================================
--- trunk/gnue-common/src/datasources/GDataSource.py    2005-07-12 14:05:39 UTC 
(rev 7719)
+++ trunk/gnue-common/src/datasources/GDataSource.py    2005-07-12 16:59:25 UTC 
(rev 7720)
@@ -159,7 +159,12 @@
   # ---------------------------------------------------------------------------
 
   def __trigger_update (self):
-
+    # The update function is not available for backends that need a rollback
+    # after a post have failed. Reason: we do an uncommmitted post here, and a
+    # rollback in a later post would also roll back this post, and all changes
+    # in this post would get lost.
+    if self._connection._need_rollback_after_exception_:
+      raise Exceptions.FunctionNotAvailableError
     self.postAll ()
     self.requeryAll ()
 
@@ -171,6 +176,12 @@
   # ---------------------------------------------------------------------------
 
   def __trigger_call (self, name, params):
+    # The update function is not available for backends that need a rollback
+    # after a post have failed. Reason: we do an uncommmitted post here, and a
+    # rollback in a later post would also roll back this post, and all changes
+    # in this post would get lost.
+    if self._connection._need_rollback_after_exception_:
+      raise Exceptions.FunctionNotAvailable
     self.postAll ()
     result = self._currentResultSet.current.call (name, params)
     self.requeryAll ()
@@ -620,7 +631,18 @@
     if self.__master:
       self.__master.postAll ()
     else:
-      self._currentResultSet.post ()
+      try:
+        self._currentResultSet.post ()
+      except:
+        if self._connection._need_rollback_after_exception_:
+          # we have to rollback now, all changes to the backend get lost, so we
+          # don't requery
+          self._connection.rollback ()
+        else:
+          # we don't have to rollback, the changes done so far to the backend
+          # are preserved, we requery so we have the the frontend updated
+          self._currentResultSet.requery ()
+        raise
 
 
   # ---------------------------------------------------------------------------

Modified: trunk/gnue-common/src/datasources/drivers/Base/Connection.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/Base/Connection.py        
2005-07-12 14:05:39 UTC (rev 7719)
+++ trunk/gnue-common/src/datasources/drivers/Base/Connection.py        
2005-07-12 16:59:25 UTC (rev 7720)
@@ -47,6 +47,9 @@
     overwritten by descendants if the backend supports rowids.
   @cvar _primarykeyFields_: Field names of the primary key.  Can be overwritten
     by descendants if the backend has a fixed fieldname for the primary key.
+  @cvar _need_rollback_after_exception_: Can be set to False if the backend
+    does not require to rollback after an exception has happened. Most
+    prominent example of this is the appserver backend.
 
   @ivar name: Name of the connection from connections.conf.
   @ivar parameters: Parameters from connections.conf.
@@ -54,11 +57,12 @@
     for this connection.
   """
 
-  _resultSetClass_   = None
-  _behavior_         = None
-  _defaultBehaviour_ = None
-  _rowidField_       = None
-  _primarykeyFields_ = None
+  _resultSetClass_                = None
+  _behavior_                      = None
+  _defaultBehaviour_              = None
+  _rowidField_                    = None
+  _primarykeyFields_              = None
+  _need_rollback_after_exception_ = True
 
 
   # ---------------------------------------------------------------------------

Modified: trunk/gnue-common/src/datasources/drivers/Base/RecordSet.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/Base/RecordSet.py 2005-07-12 
14:05:39 UTC (rev 7719)
+++ trunk/gnue-common/src/datasources/drivers/Base/RecordSet.py 2005-07-12 
16:59:25 UTC (rev 7720)
@@ -156,9 +156,13 @@
       for fieldname in pkFields:
         self.__detailLinkFlags [fieldname] = True
 
-    # Dictionary with field:value pairs of initial data
+    # Dictionary with field:value pairs of initial data. This dictionary always
+    # represents the committed state of the backend.
     self.__initialData = initialData.copy ()     # don't touch the parameter!
 
+    # Set to true if the changes in this record have been posted
+    self.__posted = False
+
     if self.__initialData:
 
       # Existing record:
@@ -449,7 +453,16 @@
         return True
     return False
 
+  # ---------------------------------------------------------------------------
 
+  def _isPosted (self):
+    """
+    Return True if this record has been successfully posted to the backend with
+    L{_post}.
+    """
+    return self.__posted
+
+
   # ---------------------------------------------------------------------------
   # Set clean data from a dictionary
   # ---------------------------------------------------------------------------
@@ -465,9 +478,27 @@
     """
 
     self.__initialData.update (data)
-    self.__fields.update (data)
 
+    if self.__posted:
+      # record has been written to the backend - everything is clean now
+      self.__fields.update (data)
+      self.__status = 'clean'
+      self.__posted = False
+      self.__modifiedFlags = {}
 
+    elif self.__status == 'clean':
+      # record is clean anyway
+      self.__fields.update (data)
+
+    else:
+      # record has unsaved changes because the last _post to this record (or a
+      # preceding record) failed - we have to be cautious not to overwrite
+      # changes the user has done
+      for (fieldname, value) in data:
+        if not self.__modifiedFlags [fieldname]:
+          self.__fields [fieldname] = value
+
+
   # ---------------------------------------------------------------------------
   # Make this RecordSet the current one (notify all details)
   # ---------------------------------------------------------------------------
@@ -510,6 +541,13 @@
     well as for all detail records where this record is the master.
 
     This is called by L{ResultSet.post} for each record with pending changes.
+
+    This function does not change the record's status; the record remains dirty
+    and the list of modified fields remains intact. In case the whole
+    transaction succeeds, the L{_requery} method must be called subsequently,
+    which will set the record status to "clean". However, if an exception in a
+    later operation of the same transaction happens and causes a rollback on
+    the backend, the record's _post method can simply be called again.
     """
 
     # Just to make sure - you never know who calls us...
@@ -553,20 +591,15 @@
           rowid = self.__connection.insert (self.__tablename, modifiedFields)
           if self.__rowidField:
             self.__fields [self.__rowidField] = rowid
-            self.__initialData [self.__rowidField] = rowid
             # Requery all the fields that are important for inserting details.
             # A backend trigger could have e.g. generated a primary key.
             if self.__detailLinkFlags:
               self.__do_requery (self.__detailLinkFlags.keys ())
-
         else:
           self.__connection.update (self.__tablename, self.__wherefields (),
               modifiedFields)
 
-        # The record is now "clean" again
-        self.__status        = 'clean'
-        self.__modifiedFlags = {}
-        self.__initialData   = self.__fields.copy ()
+    self.__posted = True
 
     # Post all detail records
     for (dataSource, resultSet) in (self.__cachedDetailResultSets.items ()):
@@ -588,11 +621,20 @@
     in the last L{ResultSet.post} call.
     """
 
+    if not self.__posted:
+      return
+
     # First, requery ourselves
     if self.__requery:
       if self.__rowidField or self.__primarykeyFields:
         self.__do_requery (self.__boundFields)
 
+    # The record is now "clean" again
+    self.__status = 'clean'
+    self.__posted = False
+    self.__modifiedFlags = {}
+    self.__initialData = self.__fields.copy ()
+
     # Now, requery detail resultsets
     for (dataSource, resultSet) in self.__cachedDetailResultSets.items ():
       dataSource._requeryResultSet (self, resultSet)
@@ -606,7 +648,6 @@
 
     newfields = self.__connection.requery (self.__tablename,
         self.__wherefields (), fields)
-    self.__initialData.update (newfields)
     self.__fields.update (newfields)
 
 

Modified: trunk/gnue-common/src/datasources/drivers/Base/ResultSet.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/Base/ResultSet.py 2005-07-12 
14:05:39 UTC (rev 7719)
+++ trunk/gnue-common/src/datasources/drivers/Base/ResultSet.py 2005-07-12 
16:59:25 UTC (rev 7720)
@@ -118,9 +118,6 @@
     # Generator to yield fieldname/value dictionaries
     self.__generator = None
 
-    # Records that have to be requeried after the post
-    self.__recordsToRequery = []
-
     # Pointer to the current record
     self.current = None
 
@@ -644,6 +641,9 @@
     to post, L{requery} must be called.  If the operation should be committed,
     the L{Connection.commit} method can be called between post and requery.
 
+    This method does not change the status of any record, so in case of an
+    exception, it can be just called again.
+
     @param fkData: fieldname/value dictionary for foreign key fields. Used
       internally for detail resultsets in a master/detail relationship.
     """
@@ -659,32 +659,21 @@
       while self.__currentRecord < len (self.__cachedRecords):
         self.current = self.__cachedRecords [self.__currentRecord]
         if self.current.isPending ():
+
+          # activate all matching detail resultsets so that committ triggers
+          # see the correct details
           self.current._activate ()
 
-          if self.current.isInserted () or self.current.isModified ():
-            self.__recordsToRequery.append (self.current)
-
           # Set the foreign keys for inserted records in case the master 
changed
           # its primary key in a commit trigger
           if self.current.isInserted ():
             for (fieldname, value) in fkData.items ():
               self.current [fieldname] = value
 
-          wasDeleted = ((self.current.isEmpty () or self.current.isDeleted ()) 
\
-              and self.__connection is not None)
-
+          # write changes to the backend
           self.current._post ()
 
-          if wasDeleted:
-            # Adjust the current record if a preceding record or the current
-            # record is deleted
-            self.__removeRecord (self.__currentRecord)
-            if self.__currentRecord <= currentRecord:
-              currentRecord -= 1
-          else:
-            self.__currentRecord += 1
-        else:
-          self.__currentRecord += 1
+        self.__currentRecord += 1
 
     except:
       # If any error happened on writing to the backend, move the UI to the
@@ -693,15 +682,7 @@
       raise
 
     # Restore current record position
-    if currentRecord >= 0:
-      self.__currentRecord = currentRecord
-    else:
-      # Move to record 0 if all preceding records were deleted
-      # (or set to -1 if all records were deleted)
-      if len(self.__cachedRecords):
-        self.__currentRecord = 0
-      else:
-        self.__currentRecord = -1
+    self.__currentRecord = currentRecord
 
 
   # ---------------------------------------------------------------------------
@@ -717,9 +698,19 @@
     called between post and requery.
     """
 
-    for record in self.__recordsToRequery:
-      record._requery ()
-    self.__recordsToRequery = []
+    index = 0
+    while index < len (self.__cachedRecords):
+      record = self.__cachedRecords [index]
+      if record._isPosted ():
+        if ((record.isEmpty () or record.isDeleted ()) \
+            and self.__connection is not None):
+          self.__removeRecord (index)
+        else:
+          record._requery ()
+          index += 1
+      else:
+        index += 1
+
     self.__sync ()
 
 
@@ -771,8 +762,6 @@
       else:
         # Not found in newData - delete it
         self.__removeRecord (index)
-        if index <= self.__currentRecord:
-          self.__currentRecord -= 1
 
     # Add the rest of newData - it has been inserted
     # Convert the multi dimensional dictionary into a list
@@ -787,16 +776,6 @@
       if row:
         record = self.__createRecord (initialData = row)
 
-    # Move to record 0 if all preceding records were deleted
-    # (or set to -1 if all records were deleted)
-    if self.__currentRecord < 0:
-      if len (self.__cachedRecords):
-        self.__currentRecord = 0
-      else:
-        self.__currentRecord = -1
-
-    # We are completely up to date now.
-    self.__recordsToRequery = []
     self.__sync ()
 
 
@@ -821,7 +800,16 @@
     self.__cachedRecords.pop (index)
     self.__recordCount -= 1
 
+    # if a record preceding the cursor position was deleted, move cursor
+    # position along
+    if index <= self.__currentRecord:
+      self.__currentRecord -= 1
 
+    # ... but don't move below 0 unless there is *really* no record left
+    if self.__currentRecord < 0 and self.__recordCount:
+      self.__currentRecord = 0
+
+
   # ---------------------------------------------------------------------------
   # Virtual methods
   # ---------------------------------------------------------------------------

Modified: trunk/gnue-common/src/datasources/drivers/other/appserver.py
===================================================================
--- trunk/gnue-common/src/datasources/drivers/other/appserver.py        
2005-07-12 14:05:39 UTC (rev 7719)
+++ trunk/gnue-common/src/datasources/drivers/other/appserver.py        
2005-07-12 16:59:25 UTC (rev 7720)
@@ -308,9 +308,10 @@
   Connection class for GNUe-AppServer backends.
   """
 
-  _resultSetClass_   = ResultSet
-  _behavior_         = Behavior
-  _primarykeyFields_ = [u'gnue_id']
+  _resultSetClass_                = ResultSet
+  _behavior_                      = Behavior
+  _primarykeyFields_              = [u'gnue_id']
+  _need_rollback_after_exception_ = False
 
 
   # ---------------------------------------------------------------------------





reply via email to

[Prev in Thread] Current Thread [Next in Thread]