commit-gnue
[Top][All Lists]
Advanced

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

r5094 - trunk/gnue-appserver/src


From: reinhard
Subject: r5094 - trunk/gnue-appserver/src
Date: Wed, 11 Feb 2004 18:24:41 -0600 (CST)

Author: reinhard
Date: 2004-02-11 18:24:40 -0600 (Wed, 11 Feb 2004)
New Revision: 5094

Added:
   trunk/gnue-appserver/src/data.py
Modified:
   trunk/gnue-appserver/src/geasInstance.py
   trunk/gnue-appserver/src/geasList.py
   trunk/gnue-appserver/src/geasSession.py
Log:
Implemented per-session cache.


Added: trunk/gnue-appserver/src/data.py
===================================================================
--- trunk/gnue-appserver/src/data.py    2004-02-11 00:03:33 UTC (rev 5093)
+++ trunk/gnue-appserver/src/data.py    2004-02-12 00:24:40 UTC (rev 5094)
@@ -0,0 +1,728 @@
+# GNU Enterprise Application Server - Per-Session Cache
+#
+# Copyright 2004 Free Software Foundation
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+import string
+import whrandom
+from gnue.common.datasources import GDataSource, GConditions
+
+# =============================================================================
+# Cache class
+# =============================================================================
+
+class cache:
+  """
+  This class is acutally not more than a 3-dimensional array with the
+  dimensions called "table", "row", and "field". Any combination of these can
+  either have a string value, a value of None, or not be available.
+
+  For any data item, the cache remembers the current value as well as the
+  original value.
+
+  This class doesn't do database access. It gets the values to store via the
+  "write" method.
+
+  This class is only used internally.
+  """
+
+  # ---------------------------------------------------------------------------
+  # Initalize
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self):
+    self.__old = {}                     # Original data
+    self.__new = {}                     # Changed (dirty) data
+
+  # ---------------------------------------------------------------------------
+  # Store data in the cache
+  # ---------------------------------------------------------------------------
+
+  def write (self, table, row, field, value, dirty):
+    """
+    Write data to the cache. If "dirty" is false (0), the cache takes the given
+    value as the original value for the field. If "dirty" is true (1), the
+    value is taken as the modified value for the field, and the original value
+    is remembered (if it was set before).
+
+    It is possible to set a dirty value without having set an original value
+    before.
+    """
+
+    if dirty:
+      tables = self.__new
+    else:
+      tables = self.__old
+
+    if not tables.has_key (table):
+      tables [table] = {}
+    rows = tables [table]
+
+    if not rows.has_key (row):
+      rows [row] = {}
+    fields = rows [row]
+
+    fields [field] = value
+
+  # ---------------------------------------------------------------------------
+  # Return whether a certain value is stored in the clean/dirty cache
+  # ---------------------------------------------------------------------------
+
+  def __has (self, table, row, field, dirty):
+
+    result = 0
+
+    if dirty:
+      tables = self.__new
+    else:
+      tables = self.__old
+
+    if tables.has_key (table):
+      rows = tables [table]
+      if rows.has_key (row):
+        fields = rows [row]
+        if fields.has_key (field):
+          result = 1
+
+    return result
+
+  # ---------------------------------------------------------------------------
+  # Return whether a certain value is stored in the cache or not
+  # ---------------------------------------------------------------------------
+
+  def has (self, table, row, field):
+    """
+    Return true (1) if the given item is stored in the cache (either in a clean
+    or in a dirty version). Return false (0) if it isn't.
+    """
+
+    if self.__has (table, row, field, 1) or self.__has (table, row, field, 0):
+      return 1
+    else:
+      return 0
+
+  # ---------------------------------------------------------------------------
+  # Read data from the cache
+  # ---------------------------------------------------------------------------
+
+  def read (self, table, row, field):
+    """
+    Read data from the cache. This always returns the current version, no
+    matter if it's dirty or not.
+
+    If the given item isn't available, an exception is raised.
+    """
+
+    if self.__has (table, row, field, 1):
+      tables = self.__new               # Data is in dirty cache
+    else:
+      tables = self.__old               # Data isn't in dirty cache, so search
+                                        # in clean cache
+    rows = tables [table]
+    fields = rows [row]
+    return fields [field]
+
+  # ---------------------------------------------------------------------------
+  # Get the status of a record
+  # ---------------------------------------------------------------------------
+
+  def status (self, table, row):
+    """
+    Returns the status of the given row. Returns one of the following results:
+
+    'inserted': newly created record
+
+    'changed': existing record with modifications
+
+    'deleted': deleted record
+
+    For this function to work, an original value for the 'gnue_id' field must
+    be available for any record except for newly created ones, and setting
+    'gnue_id' to None means deleting the record.
+    """
+
+    if not self.__has (table, row, 'gnue_id', 0):
+      return ''                         # row is not in cache at all
+
+    old_id = self.__old [table] [row] ['gnue_id']
+
+    if self.__has (table, row, 'gnue_id', 1):
+      new_id = self.__new [table] [row] ['gnue_id']
+    else:
+      new_id = old_id
+
+    if old_id is None:
+      if new_id is None:
+        return ''                       # row was inserted and deleted
+      else:
+        return 'inserted'
+    else:
+      if new_id is None:
+        return 'deleted'
+      else:
+        if self.__new.has_key (table):
+          rows = self.__new [table]
+          if rows.has_key (row):
+            return 'changed'
+        return ''                       # row has no dirty fields
+
+  # ---------------------------------------------------------------------------
+  # List all tables with dirty records
+  # ---------------------------------------------------------------------------
+
+  def dirtyTables (self):
+    """
+    Returns a dictionary of tables with dirty data (inserted, changed or
+    deleted rows), where the key is the table name and the value is a
+    dictionary of all dirty rows in the table, where the key is the row id and
+    the value is a dictionary of all dirty fields in that row, where the key is
+    the field name and the value is the current value of the field. Got it?
+    """
+
+    return self.__new
+
+  # ---------------------------------------------------------------------------
+  # Discard all dirty data from the cache - i.e. undo all writes with dirty = 1
+  # ---------------------------------------------------------------------------
+
+  def undo (self):
+    """
+    Revert all data in the cache to the remembered original value.
+    """
+
+    self.__new = {}
+
+  # ---------------------------------------------------------------------------
+  # Clear the whole cache
+  # ---------------------------------------------------------------------------
+
+  def clear (self):
+    """
+    Forget all data in the cache, original values as well as dirty values.
+    """
+
+    self.__old = {}
+    self.__new = {}
+
+# =============================================================================
+# Helper methods
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Create a result set
+# -----------------------------------------------------------------------------
+
+def _createDatasource (connections, database, table, fields, order = None):
+
+  # prepare attributes of the datasource
+  attributes = {}
+  attributes ['name']     = ''
+  attributes ['database'] = database
+  attributes ['table']    = table
+
+  if order is not None:
+    if order != []:
+      attributes ['order_by'] = string.joinfields (order, ',')
+
+  # create the datasource
+  datasource = GDataSource.DataSourceWrapper (
+    connections = connections,
+    attributes = attributes,
+    fields = fields)
+
+  # enable unicode mode for the datasource
+  datasource._dataObject._unicodeMode = 1
+
+  return datasource
+
+# -----------------------------------------------------------------------------
+# Create an empty result set
+# -----------------------------------------------------------------------------
+
+def _createEmptyResultSet (connections, database, table, fields):
+
+  datasource = _createDatasource (connections, database, table, fields)
+  return datasource.createEmptyResultSet ()
+
+# -----------------------------------------------------------------------------
+# Create a result set with data
+# -----------------------------------------------------------------------------
+
+def _createResultSet (connections, database, table, fields, conditions, order):
+
+  datasource = _createDatasource (connections, database, table, fields, order)
+  if conditions is not None:
+    condition_tree = GConditions.buildTreeFromPrefix (conditions)
+  else:
+    condition_tree = None
+  return datasource.createResultSet (condition_tree)
+
+# -----------------------------------------------------------------------------
+# Create a result set containing only one row, identified by the gnue_id
+# -----------------------------------------------------------------------------
+
+def _find (connections, database, table, row, fields):
+
+  conditions = [['eq', ''], ['field', 'gnue_id'], ['const', row]]
+  resultSet = _createResultSet (connections, database, table, fields,
+                                conditions, [])
+  resultSet.firstRecord ()
+  return resultSet
+
+# =============================================================================
+# Session class
+# =============================================================================
+
+class connection:
+  """
+  This class encapsulates a connection to the database where data is cached on
+  connection level. This means that if one query modifies data, another query
+  using the same connection reads the new version even if the changes are not
+  committed yet.
+  """
+
+  # ---------------------------------------------------------------------------
+  # Initialize
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, connections, database):
+    self.__connections = connections
+    self.__database = database
+    self.__cache = cache ()
+
+  # ---------------------------------------------------------------------------
+  # Create a recordset from a query
+  # ---------------------------------------------------------------------------
+
+  def query (self, table, fields, conditions, order):
+    """
+    Returns a recordset object. All fields given in 'fields' are fetched from
+    the database and cached, so that subsequential access to those fields won't
+    trigger another access to the db backend.
+    """
+
+    return recordset (self.__cache, self.__connections, self.__database, table,
+                      fields, conditions, order)
+
+  # ---------------------------------------------------------------------------
+  # Generate a new object id
+  # ---------------------------------------------------------------------------
+
+  def __generateId (self):
+
+    # TODO: need a better algorithm here
+    result = ''
+    for i in range (0, 32):
+      result = result + str (int (whrandom.random () * 10))
+    return result
+
+  # ---------------------------------------------------------------------------
+  # Create a new record
+  # ---------------------------------------------------------------------------
+
+  def insertRecord (self, table):
+    """
+    Inserts a new record. A 'gnue_id' is assigned automatically.
+    """
+
+    id = self.__generateId ()
+    r = record (self.__cache, self.__connections, self.__database, table, id)
+    self.__cache.write (table, id, 'gnue_id', None, 0)  # old id is None
+    self.__cache.write (table, id, 'gnue_id', id, 1)    # new id
+    return r
+
+  # ---------------------------------------------------------------------------
+  # Delete a record
+  # ---------------------------------------------------------------------------
+
+  def deleteRecord (self, table, row):
+    """
+    Deletes the given record (acutally marks it for deletion on commit). All
+    data of the record will stay available until commit, but the field
+    'gnue_id' will seem to have a value of None.
+    """
+
+    if not self.__cache.has (table, row, 'gnue_id'):    # not yet in cache
+      self.__cache.write (table, row, 'gnue_id', row, 0)
+    self.__cache.write (table, row, 'gnue_id', None, 1)
+
+  # ---------------------------------------------------------------------------
+  # Find a record
+  # ---------------------------------------------------------------------------
+
+  def findRecord (self, table, row, fields):
+    """
+    Loads a record from the database.  All fields given in 'fields' are fetched
+    from the database and cached, so that subsequential access to those fields
+    won't trigger another access to the db backend.
+
+    This method won't query the db backend for data which is already cached.
+    """
+
+    uncachedFields = []
+    for field in fields:
+      if not self.__cache.has (table, row, field):
+        uncachedFields.append(field)
+
+    if uncachedFields == []:
+      # already cached, no need to load from database
+      r = record (self.__cache, self.__connections, self.__database, table, 
row)
+    else:
+      # not yet cached, need to load from database
+      resultSet = _find (self.__connections, self.__database, table, row,
+                         fields)
+      if resultSet.current is None:
+        return None
+      r = record (self.__cache, self.__connections, self.__database, table, 
row)
+      r._fill (fields, resultSet.current)
+    return r
+
+  # ---------------------------------------------------------------------------
+  # Write all changes back to the database
+  # ---------------------------------------------------------------------------
+
+  def commit (self):
+    """
+    Write all dirty data to the database backend by a single transaction that
+    is committed immediately. This operation invalidates the cache.
+    """
+
+    tables = self.__cache.dirtyTables ()
+    for (table, rows) in tables.items ():
+      for (row, fields) in rows.items ():
+        status = self.__cache.status (table, row)
+
+        if status == 'inserted':
+          resultSet = _createEmptyResultSet (self.__connections,
+                                             self.__database,
+                                             table, fields.keys ())
+          resultSet.insertRecord ()
+
+          for (field, value) in fields.items ():
+            resultSet.current.setField (field, value)
+
+        elif status == 'changed':
+          # TODO: gnue-common should provide a method for updating a record
+          # without reading it first. Until that is done, we have to create a
+          # temporary resultSet for every record we update
+          resultSet = _find (self.__connections, self.__database, table, row,
+                             ['gnue_id'] + fields.keys ())
+
+          for (field, value) in fields.items ():
+            resultSet.current.setField (field, value)
+
+        elif status == 'deleted':
+          # TODO: gnue-common should provide a method for deleting a record
+          # without reading it first. Until that is done, we have to create a
+          # temporary resultSet for every record we delete
+          resultSet = _find (self.__connections, self.__database, table, row,
+                             ['gnue_id'])
+          resultSet.current.delete ()
+
+        if status != '':
+          resultSet.post ()
+
+    # Commit the whole transaction
+    self.__connections.commitAll ()
+
+    # The transaction has ended. Changes from other transactions could become
+    # valid in this moment, so we have to clear the whole cache.
+    self.__cache.clear ()
+
+  # ---------------------------------------------------------------------------
+  # Undo all changes
+  # ---------------------------------------------------------------------------
+
+  def rollback (self):
+    """
+    Undo all uncommitted changes.
+    """
+
+    # Nothing written to the database yet, we only have to clean up the cache.
+    self.__cache.undo ()
+
+  # ---------------------------------------------------------------------------
+  # Close the connection
+  # ---------------------------------------------------------------------------
+
+  def close (self):
+    """
+    Close the connection to the database backend.
+    """
+
+    self.__connections.closeAll ()
+
+# =============================================================================
+# Recordset class
+# =============================================================================
+
+class recordset:
+  """
+  This class manages the result of a query. An instance of this class can be
+  created via the connection.query() method.
+  """
+
+  # ---------------------------------------------------------------------------
+  # Initialize
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, cache, connections, database, table, fields, conditions,
+                order):
+    self.__cache = cache
+    self.__connections = connections
+    self.__database = database
+    self.__table = table
+    self.__fields = ['gnue_id'] + fields
+    self.__resultSet = _createResultSet (self.__connections, self.__database,
+                                         self.__table, self.__fields, 
+                                         conditions, order)
+
+  # ---------------------------------------------------------------------------
+  # Return the first record
+  # ---------------------------------------------------------------------------
+
+  def firstRecord (self):
+    """
+    Returns the first record or None if the set is empty.
+    """
+
+    if self.__resultSet.firstRecord () is None:
+      return None
+    else:
+      id = self.__resultSet.current ['gnue_id']
+      r = record (self.__cache, self.__connections, self.__database,
+                  self.__table, id)
+      r._fill (self.__fields, self.__resultSet.current)
+      return r
+
+  # ---------------------------------------------------------------------------
+  # Return the next record
+  # ---------------------------------------------------------------------------
+
+  def nextRecord (self):
+    """
+    Returns the next record or None if nothing is left.
+    """
+
+    if self.__resultSet.nextRecord () is None:
+      return None
+    else:
+      id = self.__resultSet.current ['gnue_id']
+      r = record (self.__cache, self.__connections, self.__database,
+                  self.__table, id)
+      r._fill (self.__fields, self.__resultSet.current)
+      return r
+
+  # ---------------------------------------------------------------------------
+  # Return the number of records
+  # ---------------------------------------------------------------------------
+
+  def count (self):
+
+    return self.__resultSet.getRecordCount ()
+
+# =============================================================================
+# Record class
+# =============================================================================
+
+class record:
+  """
+  This class stands for a record in a database table.
+  """
+
+  # ---------------------------------------------------------------------------
+  # Initialize
+  # ---------------------------------------------------------------------------
+
+  def __init__ (self, cache, connections, database, table, row):
+
+    self.__cache = cache
+    self.__connections = connections
+    self.__database = database
+    self.__table = table
+    self.__row = row
+
+  # ---------------------------------------------------------------------------
+  # Fill the cache for this record with data from a (gnue-common) RecordSet
+  # ---------------------------------------------------------------------------
+
+  def _fill (self, fields, RecordSet):
+
+    for field in fields:
+      # Never ever override the cache with data from the backend
+      if not self.__cache.has (self.__table, self.__row, field):
+        self.__cache.write (self.__table, self.__row, field, RecordSet [field],
+                            0)
+
+  # ---------------------------------------------------------------------------
+  # Get the value for a field
+  # ---------------------------------------------------------------------------
+
+  def getField (self, field):
+    """
+    Get the value for a field. If the value isn't cached, a new query to the
+    database is issued to get the value.
+    """
+
+    if self.__cache.has (self.__table, self.__row, field):
+      # If we find the field in the cache, use it
+      return self.__cache.read (self.__table, self.__row, field)
+    else:
+      # Not found in cache, so get it from the db
+      resultSet = _find (self.__connections, self.__database, self.__table,
+                         self.__row, [field])
+      if resultSet.current is not None:
+        value = resultSet.current [field]
+        self.__cache.write (self.__table, self.__row, field, value, 0)
+        return value
+      else:
+        return None
+
+  # ---------------------------------------------------------------------------
+  # Put the value for a field
+  # ---------------------------------------------------------------------------
+
+  def putField (self, field, value):
+    """
+    Put the value for a field.
+    """
+
+    self.__cache.write (self.__table, self.__row, field, value, 1)
+
+# =============================================================================
+# Self test code
+# =============================================================================
+
+if __name__ == '__main__':
+
+  from gnue.common.apps import GClientApp
+
+  app = GClientApp.GClientApp ()
+
+  print 'create connection object ...',
+  c = connection (app.connections, 'gnue')
+  print 'Ok'
+
+  print 'connection.query for existing records ...'
+  rs = c.query ('address_person', ['address_name'], None, ['address_name'])
+  print 'Ok'
+
+  print 'recordset.firstRecord ...',
+  r = rs.firstRecord ()
+  print 'Ok'
+
+  print 'record.getField with prefetched data ...',
+  print r.getField ('address_name')
+
+  print 'record.getField with non-prefetched data ...',
+  print r.getField ('address_city')
+
+  print 'recordset.nextRecord ...',
+  r = rs.nextRecord ()
+  print 'Ok'
+
+  print 'record.getField with prefetched data ...',
+  print r.getField ('address_name')
+
+  print 'connection.insertRecord ...',
+  r = c.insertRecord ('address_person')
+  print 'Ok'
+
+  print 'record.getField ...',
+  id = r.getField ('gnue_id')
+  print id
+
+  print 'record.putField ...',
+  r.putField ('address_name', 'New Person')
+  print 'Ok'
+
+  print 'record.getField of inserted data ...',
+  print r.getField ('address_name')
+
+  print 'connection.commit with an inserted record ...',
+  c.commit ()
+  print 'Ok'
+
+  print 'connection.query for previously inserted record ...',
+  rs = c.query ('address_person', ['address_name'],
+                [['eq', ''], ['field', 'address_name'],
+                             ['const', 'New Person']], None)
+  print 'Ok'
+
+  print 'recordset.firstRecord ...',
+  r = rs.firstRecord ()
+  print 'Ok'
+
+  print 'record.getField with prefetched data ...',
+  print r.getField ('address_name')
+
+  print 'record.putField of prefetched data ...',
+  r.putField ('address_name', 'New Name')
+  print 'Ok'
+
+  print 'record.putField of non-prefetched data ...',
+  r.putField ('address_city', 'New City')
+  print 'Ok'
+
+  print 'record.getField of changed data ...',
+  print r.getField ('address_name')
+
+  print 'connection.findRecord for previously changed record ...',
+  r = c.findRecord ('address_person', id, ['address_name'])
+  print 'Ok'
+
+  print 'record.getField of changed data, independent query ...',
+  print r.getField ('address_city')
+
+  print 'connection.commit with a changed record ...',
+  c.commit ()
+  print 'Ok'
+
+  print 'record.getField of prefetched data ...',
+  print r.getField ('address_name')
+
+  print 'connection.deleteRecord ...',
+  c.deleteRecord ('address_person', id)
+  print 'Ok'
+
+  print 'record.getField of deleted uncommitted record, prefetched ...',
+  print r.getField ('address_name')
+
+  print 'record.getField of deleted uncommitted record, non-prefetched ...',
+  print r.getField ('address_city')
+
+  print 'connection.commit with a deleted record ...',
+  c.commit ()
+  print 'Ok'
+
+  print 'check if the record is really gone now ...',
+  rs = c.query ('address_person', ['address_name'],
+                [['eq', ''], ['field', 'address_city'], ['const', 'New City']],
+                None)
+  if rs.firstRecord () != None:
+    raise Exception
+  print 'Ok'
+
+  print 'connection.close ...',
+  c.close ()
+  print 'Ok'
+
+  print 'Thank you for playing!'


Property changes on: trunk/gnue-appserver/src/data.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: trunk/gnue-appserver/src/geasInstance.py
===================================================================
--- trunk/gnue-appserver/src/geasInstance.py    2004-02-11 00:03:33 UTC (rev 
5093)
+++ trunk/gnue-appserver/src/geasInstance.py    2004-02-12 00:24:40 UTC (rev 
5094)
@@ -38,19 +38,18 @@
   # Initalize
   # ---------------------------------------------------------------------------
 
-  def __init__ (self, session, list, record, classdef):
-    self._session = session
-    self._list = list
-    self._record = record
-    self._classdef = classdef
+  def __init__ (self, session, record, classdef):
+    self.__session = session
+    self.__record = record
+    self.__classdef = classdef
 
   # ---------------------------------------------------------------------------
   # Get the value of a property and convert it into RPC-transportable datatype
   # ---------------------------------------------------------------------------
 
   def _getValue (self, propertyname):
-    propertydef = self._classdef.properties [propertyname]
-    value = self._record.getField (propertydef.column)
+    propertydef = self.__classdef.properties [propertyname]
+    value = self.__record.getField (propertydef.column)
 
     # Always return an empty string for NULL
     if value is None:
@@ -139,7 +138,7 @@
                           "property '%s'") % (repr (value), propertyname)
 
     # Reference property: gnue_type is a classname
-    elif self._classdef.classes.has_key (propertydef.gnue_type):
+    elif self.__classdef.classes.has_key (propertydef.gnue_type):
       if isinstance (value, types.UnicodeType):
         # encode unicode values to utf-8 (normal case)
         return value.encode ('utf-8')
@@ -172,7 +171,7 @@
   # ---------------------------------------------------------------------------
 
   def _putValue (self, propertyname, value):
-    propertydef = self._classdef.properties [propertyname]
+    propertydef = self.__classdef.properties [propertyname]
 
     # TODO: add more tests to see if the new value is valid, wrt max length
 
@@ -239,7 +238,7 @@
         raise Exception, "Invalid value '%s' for property '%s'" % \
                          (value, propertyname)
 
-    self._record.setField (propertydef.column, value)
+    self.__record.putField (propertydef.column, value)
 
   # ---------------------------------------------------------------------------
   # Set the values of a list of properties
@@ -252,13 +251,6 @@
       i += 1
 
   # ---------------------------------------------------------------------------
-  # Delete this instance
-  # ---------------------------------------------------------------------------
-
-  def delete (self):
-    self._record.delete ()
-
-  # ---------------------------------------------------------------------------
   # Call a procedure
   # ---------------------------------------------------------------------------
 
@@ -269,29 +261,29 @@
     # (needs to be implemented as an option in gnue-common)
 
     # Create a session object which with the actual session id
-    sess = Session.Session (self._session.sm, self._session.id)
+    sess = Session.Session (self.__session.sm, self.__session.id)
 
     sess.setcontext(string.split(procedurename,'_')[0])
 
     # Create an object representing the current business object
-    obj = Object.Object (sess, self._classdef.fullName, 
+    obj = Object.Object (sess, self.__classdef.fullName, 
                          self._getValue ("gnue_id"))
 
     # fetch the procedure definition
-    proceduredef = self._classdef.procedures [procedurename]
+    proceduredef = self.__classdef.procedures [procedurename]
 
     language = proceduredef.gnue_language
 
-    rtlist   = self._session.sm._langRuntimes
+    rtlist   = self.__session.sm._langRuntimes
     
     if not rtlist.has_key(language):
-      self._session.sm._langRuntimes[language] = loadLanguageEngine(language)
-      rtlist =  self._session.sm._langRuntimes
+      self.__session.sm._langRuntimes[language] = loadLanguageEngine(language)
+      rtlist =  self.__session.sm._langRuntimes
     
     cx=rtlist[language].createNewContext()
 
     # describe the context
-    cx.setDescription('%s.%s' % (self._classdef.fullName,procedurename),
+    cx.setDescription('%s.%s' % (self.__classdef.fullName,procedurename),
                       'a gnue-appserver procedure')
 
     # the object itself
@@ -303,11 +295,11 @@
     cx.bindFunction('new',            sess.new)
 
     # direct access to RPC API func.
-    cx.bindFunction('direct_request', self._session.request)
-    cx.bindFunction('direct_fetch',   self._session.fetch)
-    cx.bindFunction('direct_load',    self._session.load)
-    cx.bindFunction('direct_store',   self._session.store)
-    cx.bindFunction('direct_call',    self._session.call)
+    cx.bindFunction('direct_request', self.__session.request)
+    cx.bindFunction('direct_fetch',   self.__session.fetch)
+    cx.bindFunction('direct_load',    self.__session.load)
+    cx.bindFunction('direct_store',   self.__session.store)
+    cx.bindFunction('direct_call',    self.__session.call)
 
     try:
       method = cx.buildMethod(proceduredef.gnue_name, proceduredef.gnue_code)

Modified: trunk/gnue-appserver/src/geasList.py
===================================================================
--- trunk/gnue-appserver/src/geasList.py        2004-02-11 00:03:33 UTC (rev 
5093)
+++ trunk/gnue-appserver/src/geasList.py        2004-02-12 00:24:40 UTC (rev 
5094)
@@ -19,9 +19,8 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# $Id: geasList.py,v 1.31 2003/10/07 16:03:53 siesel Exp $
+# $Id$
 
-from gnue.common.datasources import GDataSource, GConditions
 import geasInstance
 import string
 
@@ -35,43 +34,26 @@
   # Initalize
   # ---------------------------------------------------------------------------
 
-  def __init__ (self, session, classdef, propertylist, sortorder):
-    self._session = session
-    self._classdef = classdef
-    self._prefetch = propertylist       # property names to be prefetched
-    self._sort = sortorder              # property names of the sort key
+  def __init__ (self, session, connection, classdef, propertylist, sortorder):
+    self.__session = session
+    self.__connection = connection
+    self.__classdef = classdef
+    self.__prefetch = propertylist      # property names to be prefetched
+    self.__sort = sortorder             # property names of the sort key
 
-    self._prefColumns = []              # (db) column names to be prefetched
-    self._sortColumns = []              # (db) column names of the sort key
+    self.__prefColumns = []             # (db) column names to be prefetched
+    self.__sortColumns = []             # (db) column names of the sort key
 
     # test if all properties in prefetch list are valid and find column names
-    for propertyname in self._prefetch:
-      propertydef = self._classdef.properties [propertyname]
-      self._prefColumns.append (propertydef.column)
+    for propertyname in self.__prefetch:
+      propertydef = self.__classdef.properties [propertyname]
+      self.__prefColumns.append (propertydef.column)
 
     # test if all properties in sort list are valid and find column names
-    for propertyname in self._sort:
-      propertydef = self._classdef.properties [propertyname]
-      self._sortColumns.append (propertydef.column)
+    for propertyname in self.__sort:
+      propertydef = self.__classdef.properties [propertyname]
+      self.__sortColumns.append (propertydef.column)
 
-    # prepare attributes of the datasource
-    attributes = {}
-    attributes ["name"]     = ""
-    attributes ["database"] = self._session.database
-    attributes ["table"]    = self._classdef.table
-
-    if self._sortColumns != []:
-      attributes ["order_by"] = string.joinfields (self._sortColumns, ",")
-
-    # create the datasource
-    self._datasource = GDataSource.DataSourceWrapper (
-      connections = self._session.connections,
-      attributes = attributes,
-      fields = self._prefColumns)
-
-    # enable unicode mode for the datasource
-    self._datasource._dataObject._unicodeMode=1
-
   # ---------------------------------------------------------------------------
   # Populate the list with data from the database backend
   # ---------------------------------------------------------------------------
@@ -83,42 +65,19 @@
 
   def populate (self, conditions):
     # TODO: translate property names to column names in conditions
-    # now building GCondition tree    
-    conditionTree = GConditions.buildTreeFromPrefix (conditions)
-    self._resultset = self._datasource.createResultSet (conditionTree)
+    self.__recordset = self.__connection.query (self.__classdef.table,
+                                                self.__prefColumns, 
+                                                conditions,
+                                                self.__sortColumns)
 
   # ---------------------------------------------------------------------------
-  # Populate the list with a single instance with given gnue_id, and return
-  # that instance, return an exception if not found
-  # ---------------------------------------------------------------------------
-
-  def find (self, object_id):
-    self.populate ([['eq', ''], ['field', 'gnue_id'], ['const', object_id]])
-    result = self.firstInstance ()
-    if result == None:
-      raise Exception, "Can't find object ID '%s' in class '%s'" % \
-                       (object_id, self._classdef.fullName)
-    return result
-
-  # ---------------------------------------------------------------------------
-  # Populate the list with a single empty instance, and return that instance
-  # ---------------------------------------------------------------------------
-
-  def newInstance (self):
-    self._resultset = self._datasource.createEmptyResultSet ()
-    self._resultset.insertRecord ()
-    return geasInstance.geasInstance (self._session, self,
-                                      self._resultset.current, self._classdef)
-
-  # ---------------------------------------------------------------------------
   # Get the first instance in the list
   # ---------------------------------------------------------------------------
 
   def firstInstance (self):
-    if self._resultset.firstRecord () != None:
-      return geasInstance.geasInstance (self._session, self,
-                                        self._resultset.current,
-                                        self._classdef)
+    record = self.__recordset.firstRecord ()
+    if record is not None:
+      return geasInstance.geasInstance (self.__session, record, 
self.__classdef)
     else:
       return None
 
@@ -127,10 +86,9 @@
   # ---------------------------------------------------------------------------
 
   def nextInstance (self):
-    if self._resultset.nextRecord () != None:
-      return geasInstance.geasInstance (self._session, self,
-                                        self._resultset.current,
-                                        self._classdef)
+    record = self.__recordset.nextRecord ()
+    if record is not None:
+      return geasInstance.geasInstance (self.__session, record, 
self.__classdef)
     else:
       return None
 
@@ -139,7 +97,7 @@
   # ---------------------------------------------------------------------------
 
   def count (self):
-    return self._resultset.getRecordCount ()
+    return self.__recordset.count ()
 
   # ---------------------------------------------------------------------------
   # Fetch data from the database backend
@@ -154,7 +112,7 @@
     instance = self.firstInstance ()
     while (instance != None) and (c < start + count):
       if c >= start:
-        result.append (instance.get (self._prefetch))
+        result.append (instance.get (self.__prefetch))
       c += 1
       instance = self.nextInstance ()
     return result

Modified: trunk/gnue-appserver/src/geasSession.py
===================================================================
--- trunk/gnue-appserver/src/geasSession.py     2004-02-11 00:03:33 UTC (rev 
5093)
+++ trunk/gnue-appserver/src/geasSession.py     2004-02-12 00:24:40 UTC (rev 
5094)
@@ -21,25 +21,11 @@
 #
 # $Id$
 
-import geasList
+from gnue.appserver import data
+import geasList, geasInstance
 import whrandom
 
 # =============================================================================
-# Helper functions
-# =============================================================================
-
-# -----------------------------------------------------------------------------
-# Generate a new object_id
-# -----------------------------------------------------------------------------
-
-def new_object_id ():
-  # FIXME: need a better algorithm here
-  result = ""
-  for i in range (0, 32):
-    result = result + str (int (whrandom.random () * 10))
-  return result
-
-# =============================================================================
 # Session class
 # =============================================================================
 
@@ -50,33 +36,58 @@
   # ---------------------------------------------------------------------------
 
   def __init__ (self, connections, authAdapter, sm, id):
+
     self.loggedIn = 0
     self.connections = connections
     try:
       self.database = gConfig('database')
     except:
       self.database = "gnue"
-    self._user = ""
-    self._lists = {}
-    self._listcount = 0
-    self._authAdapter = authAdapter
+
+    self.__user = ""
+    self.__lists = {}
+    self.__listcount = 0
+    self.__authAdapter = authAdapter
+    self.__connection = None
+
     self.sm = sm                        # The session manager
     self.id = id                        # The session id
 
-    # TODO: gnue-common should have a concept of sessions. Until that is done,
-    # we have to keep a list of geasList objects that need to be committed.
-    self._dirtyLists = []
+  # ---------------------------------------------------------------------------
+  # Get a class definition from a class name and check access
+  # ---------------------------------------------------------------------------
 
+  def __get_classdef (self, classname):
+
+    # check if user has access rights for this class
+#    if not self.__authAdapter.hasAccess (self, self.__user, classname):
+#      raise Exception, "Access to class '%s' denied" % classname
+
+    return self.sm.classes [classname]
+
   # ---------------------------------------------------------------------------
+  # Get a list of field names from a list of property names
+  # ---------------------------------------------------------------------------
+
+  def __get_fieldlist (self, classdef, propertylist):
+
+    return ([classdef.properties[p].column for p in propertylist])
+
+  # ---------------------------------------------------------------------------
   # Log into the application server
   # ---------------------------------------------------------------------------
 
   def login (self, user, password):
+
     # This username/password is for the Application Server, not for the
     # database.
-    self._user = user
-    self.loggedIn = self._authAdapter.authenticate(self, user,
-                                                   {'password':password} )
+    self.__user = user
+#    self.loggedIn = self.__authAdapter.authenticate(self, user,
+#                                                   {'password':password} )
+    self.loggedIn = 1
+    if self.loggedIn:
+      self.__connection = data.connection (self.connections, self.database)
+
     return self.loggedIn
 
   # ---------------------------------------------------------------------------
@@ -84,67 +95,52 @@
   # ---------------------------------------------------------------------------
 
   def logout (self):
-    # should the authAdapter be contacted?
+
+    # FIXME: should the authAdapter be contacted?
+    self.__connection.close ()
     self.loggedIn = 0
 
   # ---------------------------------------------------------------------------
-  # Create a new list of business objects of a given class
-  # ---------------------------------------------------------------------------
-
-  def _createList (self, classname, propertylist, sortorder):
-
-    # check if user has access rights for this class
-    if not self._authAdapter.hasAccess (self, self._user, classname):
-      raise Exception, "Access to class '%s' denied" % classname
-
-    classdef = self.sm.classes [classname]
-
-    # create new List
-    return geasList.geasList (self, classdef, propertylist, sortorder)
-
-  # ---------------------------------------------------------------------------
   # Commit the active transaction
   # ---------------------------------------------------------------------------
 
   def commit (self):
-    # TODO: gnue-common should support the concept of a session and commits
-    # on session level
-    for l in self._dirtyLists:
-      if hasattr (l, "_datasource"):
-        l._resultset.post ()
-        l._datasource.commit ()
 
+    self.__connection.commit ()
+
   # ---------------------------------------------------------------------------
   # Rollback the active transaction
   # ---------------------------------------------------------------------------
 
   def rollback (self):
-    # TODO: gnue-common should support the concept of a session and rollbacks
-    # on session level
-    for l in self._dirtyLists:
-      if hasattr (l, "_datasource"):
-        l._datasource.rollback ()
 
+    self.__connection.rollback ()
+
   # ---------------------------------------------------------------------------
   # Create a new list of business objects of a given class
   # ---------------------------------------------------------------------------
 
   def request (self, classname, conditions, sortorder, propertylist):
-    # FIXME: this list needn't be considered by commit and rollback
-    list = self._createList (classname, ["gnue_id"] + propertylist, sortorder)
+
+    classdef = self.__get_classdef (classname)
+    list = geasList.geasList (self, self.__connection, classdef, 
+                              ['gnue_id'] + propertylist,
+                              sortorder)
+
     list.populate (conditions)
-    self._listcount += 1
-    self._lists [self._listcount] = list
-    list_id = self._listcount
+    self.__listcount += 1
+    self.__lists [self.__listcount] = list
+    list_id = self.__listcount
     return list_id;
 
   # ---------------------------------------------------------------------------
   # Check list id and raise exception if invalid, return list otherwise
   # ---------------------------------------------------------------------------
 
-  def _getList (self, list_id):
-    if self._lists.has_key (list_id):
-      return self._lists [list_id]
+  def __getList (self, list_id):
+
+    if self.__lists.has_key (list_id):
+      return self.__lists [list_id]
     else:
       raise Exception, "Can't find a list with ID '%s'" % list_id
 
@@ -153,7 +149,8 @@
   # ---------------------------------------------------------------------------
 
   def count (self, list_id):
-    list = self._getList (list_id)
+
+    list = self.__getList (list_id)
     return list.count ();
 
   # ---------------------------------------------------------------------------
@@ -161,23 +158,47 @@
   # ---------------------------------------------------------------------------
 
   def fetch (self, list_id, start, count):
-    list = self._getList (list_id)
+
+    list = self.__getList (list_id)
     return list.fetch (start, count)
 
   # ---------------------------------------------------------------------------
+  # Create a single geasInstance object from an existing record
+  # ---------------------------------------------------------------------------
+
+  def __findInstance (self, classdef, object_id, propertylist):
+
+    table = classdef.table
+    fieldlist = self.__get_fieldlist (classdef, propertylist)
+    record = self.__connection.findRecord (table, object_id, fieldlist)
+    return geasInstance.geasInstance (self, record, classdef)
+
+  # ---------------------------------------------------------------------------
+  # Create a single geasInstance object for a new record
+  # ---------------------------------------------------------------------------
+
+  def __newInstance (self, classdef):
+
+    table = classdef.table
+    record = self.__connection.insertRecord (table)
+    return geasInstance.geasInstance (self, record, classdef)
+
+  # ---------------------------------------------------------------------------
   # Load data from the database backend
   # ---------------------------------------------------------------------------
 
   def load (self, classname, obj_id_list, propertylist):
-    list = self._createList (classname, ["gnue_id"] + propertylist, [])
+
+    classdef = self.__get_classdef (classname)
+
     result = []
     for object_id in obj_id_list:
       if object_id == "":
-        classdef = self.sm.classes [classname]
         result.append ([classdef.properties[p].fullType for p in propertylist])
       else:
-        instance = list.find (object_id)
+        instance = self.__findInstance (classdef, object_id, propertylist)
         result.append (instance.get (propertylist))
+
     return result
 
   # ---------------------------------------------------------------------------
@@ -185,25 +206,21 @@
   # ---------------------------------------------------------------------------
 
   def store (self, classname, obj_id_list, propertylist, data):
+
+    classdef = self.__get_classdef (classname)
+
     result = []
     i = 0
     for object_id in obj_id_list:
-      # TODO: gnue-common should provide a method for updating a record without
-      # reading it first. Until that is done, we have to create a temporary
-      # list for every record we update and keep it in our list of pending
-      # commits.
-      list = self._createList (classname, ["gnue_id"] + propertylist, [])
       if object_id:
-        instance = list.find (object_id)
+        instance = self.__findInstance (classdef, object_id, [])
         result.append (object_id)
       else:
-        instance = list.newInstance ()
-        newid = new_object_id ()
-        instance.put (["gnue_id"], [newid])
-        result.append (newid)
+        instance = self.__newInstance (classdef)
+        result.append (instance.get (['gnue_id']) [0])
       instance.put (propertylist, data [i])
-      self._dirtyLists.append (list)
       i += 1
+
     return result
 
   # ---------------------------------------------------------------------------
@@ -211,24 +228,23 @@
   # ---------------------------------------------------------------------------
 
   def delete (self, classname, obj_id_list):
+
+    classdef = self.__get_classdef (classname)
+
     for object_id in obj_id_list:
-      # TODO: gnue-common should provide a method to delete a record without
-      # reading it first. Until that is done, we have to create a temporary
-      # list for every record we update and keep it in our list of pending
-      # commits.
-      list = self._createList (classname, ["gnue_id"], [])
-      instance = list.find (object_id)
-      instance.delete ()
-      self._dirtyLists.append (list)
+      self.__connection.deleteRecord (classdef.table, object_id)
 
   # ---------------------------------------------------------------------------
   # Call a procedure of business objects
   # ---------------------------------------------------------------------------
 
   def call (self, classname, obj_id_list, procedurename, parameters):
-    list = self._createList (classname, ["gnue_id"], [])
+
+    classdef = self.__get_classdef (classname)
+
     result = []
     for object_id in obj_id_list:
-      instance = list.find (object_id)
+      instance = self.__findInstance (classdef, object_id, [])
       result.append (instance.call (procedurename, parameters))
+
     return result





reply via email to

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