commit-gnue
[Top][All Lists]
Advanced

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

r5738 - trunk/gnue-appserver/src


From: reinhard
Subject: r5738 - trunk/gnue-appserver/src
Date: Sun, 18 Apr 2004 15:31:52 -0500 (CDT)

Author: reinhard
Date: 2004-04-18 15:31:50 -0500 (Sun, 18 Apr 2004)
New Revision: 5738

Modified:
   trunk/gnue-appserver/src/data.py
   trunk/gnue-appserver/src/geasInstance.py
   trunk/gnue-appserver/src/geasList.py
   trunk/gnue-appserver/src/geasSession.py
   trunk/gnue-appserver/src/test.py
Log:
Implemented indirect properties.


Modified: trunk/gnue-appserver/src/data.py
===================================================================
--- trunk/gnue-appserver/src/data.py    2004-04-17 23:19:20 UTC (rev 5737)
+++ trunk/gnue-appserver/src/data.py    2004-04-18 20:31:50 UTC (rev 5738)
@@ -233,13 +233,34 @@
 # Create a datasource object
 # -----------------------------------------------------------------------------
 
-def _createDatasource (connections, database, table, fields, order = None):
+def _createDatasource (connections, database, content, order = None):
 
+  # build table list, field list, and join condition
+  tables = []
+  fields = []
+  conditions = None
+  for (alias, (table, fk_alias, fk_field, fieldlist)) in content.items ():
+    if alias:
+      tables.append (table + ' ' + alias)
+      fields.extend ([alias + '.' + field for field in fieldlist])
+    else:
+      tables.append (table)
+      fields.extend (fieldlist)
+    if fk_alias:
+      c = GConditions.buildTreeFromPrefix (
+            [['eq', ''],
+             ['field', alias + u'.gnue_id'],
+             ['field', fk_alias + u'.' + fk_field]])
+      if conditions:
+        conditions = GConditions.combineConditions (conditions, c)
+      else:
+        conditions = c
+
   # prepare attributes of the datasource
   attributes = {}
   attributes ['name']     = ''
   attributes ['database'] = database
-  attributes ['table']    = table
+  attributes ['table']    = string.join (tables, ', ')
 
   if order is not None:
     if order != []:
@@ -251,6 +272,9 @@
     attributes = attributes,
     fields = fields)
 
+  if conditions:
+    datasource.setCondition (conditions)
+
   # enable unicode mode for the datasource
   datasource._dataObject._unicodeMode = 1
 
@@ -262,16 +286,17 @@
 
 def _createEmptyResultSet (connections, database, table, fields):
 
-  datasource = _createDatasource (connections, database, table, fields)
+  content = {None: (table, None, None, fields)}
+  datasource = _createDatasource (connections, database, content)
   return datasource.createEmptyResultSet ()
 
 # -----------------------------------------------------------------------------
 # Create a result set with data
 # -----------------------------------------------------------------------------
 
-def _createResultSet (connections, database, table, fields, conditions, order):
+def _createResultSet (connections, database, content, conditions, order):
 
-  datasource = _createDatasource (connections, database, table, fields, order)
+  datasource = _createDatasource (connections, database, content, order)
 
   if isinstance (conditions, DictType):
     condition_tree = GConditions.buildConditionFromDict (conditions)
@@ -293,9 +318,9 @@
 
 def _find (connections, database, table, row, fields):
 
+  content = {None: (table, None, None, fields)}
   conditions = [['eq', ''], ['field', u'gnue_id'], ['const', row]]
-  resultSet = _createResultSet (connections, database, table, fields,
-                                conditions, [])
+  resultSet = _createResultSet (connections, database, content, conditions, [])
   resultSet.firstRecord ()
   return resultSet
 
@@ -327,23 +352,59 @@
   # Create a recordset from a query
   # ---------------------------------------------------------------------------
 
-  def query (self, table, fields, conditions, order):
+  def query (self, content, 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.
+    Executes a query and returns a recordset object.
 
-    Table and field names must be unicode strings.
+    @param content: A dictionary of tuples defining the content of the query.
+        The format of the dictionary is {alias: (table, fk_alias, fk_field,
+        fields)}.
+        
+        'alias' is a name for the table that is unique within the query (useful
+        if two different references point to the same table).  The alias may
+        be None if the query references only a single table.
 
-    Field values in conditions must be in native Python type; in case of
-    strings they must be Unicode.
+        'table' is the name of the table.
+        
+        'fk_alias' is the alias of the table containing the foreign key (i.e.
+        the table that references this table), and 'fk_field' is the field name
+        of the foreign key (i.e. of the referencing field).  The primary key is
+        of course always 'gnue_id'. For a single element in the dictionary
+        (namely the main table), 'fk_alias' and 'fk_field' both are None.
+        Note that an inner join will be executet, that means ony results where
+        all references can be resolved (and are non-NULL) will be returned.
+
+        Finally, 'fields' is a list of field names to be included in the query.
+        All these fields will be fetched from the database and cached, so that
+        subsequential access to those fields won't trigger another access to
+        the database backend.
+
+        Alias names, table names, and field names all must be Unicode strings.
+
+    @param conditions: The conditions. May be a GCondition tree, a list in
+        prefix notation, or a dictionary.  Field values in conditions must be
+        in native Python type; in case of strings they must be Unicode.  Field
+        names in conditions must be Unicode.  In case aliases are defined in 
+        the content paramter, field names in conditions must be in the format
+        'alias.field'.
+
+    @param order: A list of Unicode strings telling the field names to order
+        by.  In case aliases are defined in the content parameter, these field
+        names must be in the format 'alias.field'.
     """
-    checktype (table, UnicodeType)
-    checktype (fields, ListType)
-    for fields_element in fields: checktype (fields_element, UnicodeType)
+    checktype (content, DictType)
+    for (content_key, content_value) in content.items ():
+      checktype (content_key, [NoneType, UnicodeType])
+      checktype (content_value, TupleType)
+      (table, fk_alias, fk_field, fields) = content_value
+      checktype (table, UnicodeType)
+      checktype (fk_alias, [NoneType, UnicodeType])
+      checktype (fk_field, [NoneType, UnicodeType])
+      checktype (fields, ListType)
+      for fields_element in fields: checktype (fields_element, UnicodeType)
 
-    return recordset (self.__cache, self.__connections, self.__database, table,
-                      fields, conditions, order)
+    return recordset (self.__cache, self.__connections, self.__database,
+                      content, conditions, order)
 
   # ---------------------------------------------------------------------------
   # Generate a new object id
@@ -429,7 +490,7 @@
       if resultSet.current is None:
         return None
       r = record (self.__cache, self.__connections, self.__database, table, 
row)
-      r._fill (fields, resultSet.current)
+      r._fill (None, fields, resultSet.current)
     return r
 
   # ---------------------------------------------------------------------------
@@ -528,16 +589,19 @@
   # Initialize
   # ---------------------------------------------------------------------------
 
-  def __init__ (self, cache, connections, database, table, fields, conditions,
+  def __init__ (self, cache, connections, database, content, conditions,
                 order):
     self.__cache = cache
     self.__connections = connections
     self.__database = database
-    self.__table = table
-    self.__fields = [u'gnue_id'] + fields
+    self.__content = content
+
+    # make sure gnue_id is selected for all tables
+    for (alias, (table, fk_alias, fk_field, fields)) in self.__content.items():
+      fields.append (u'gnue_id')
+
     self.__resultSet = _createResultSet (self.__connections, self.__database,
-                                         self.__table, self.__fields, 
-                                         conditions, order)
+                                         self.__content, conditions, order)
 
   # ---------------------------------------------------------------------------
   # Return the number of records
@@ -548,22 +612,55 @@
     return self.__resultSet.getRecordCount ()
 
   # ---------------------------------------------------------------------------
+  # Build record objects from current recordSet
+  # ---------------------------------------------------------------------------
+
+  def __buildRecords (self):
+
+    result = None
+    records = {}                        # the record for each alias
+
+    # iterate through all tables invoved in the query
+    for (alias, (table, fk_alias, fk_field, fields)) in self.__content.items():
+
+      # find id of the record
+      if alias:
+        id = self.__resultSet.current [alias + u'.gnue_id']
+      else:
+        id = self.__resultSet.current [u'gnue_id']
+
+      # build the record object
+      r = record (self.__cache, self.__connections, self.__database, table, id)
+      r._fill (alias, fields, self.__resultSet.current)
+      records [alias] = r
+
+      # the table having no join condition is the "main" table
+      if not fk_alias:
+        result = r
+
+    # iterate again to put the foreign keys in the cache
+    for (alias, (table, fk_alias, fk_field, fields)) in self.__content.items():
+      if fk_alias:
+        (records [fk_alias])._cache (fk_field,
+                                     (records [alias]).getField (u'gnue_id'))
+
+    return result
+
+  # ---------------------------------------------------------------------------
   # Return the first record
   # ---------------------------------------------------------------------------
 
   def firstRecord (self):
     """
-    Returns the first record or None if the set is empty.
+    Returns the first record or None if the set is empty.  If the query
+    affected more than one table, the record for the main table (i.e. the table
+    that is the root of all other references) is returned.
     """
 
     if self.__resultSet.firstRecord () is None:
       return None
     else:
-      id = self.__resultSet.current [u'gnue_id']
-      r = record (self.__cache, self.__connections, self.__database,
-                  self.__table, id)
-      r._fill (self.__fields, self.__resultSet.current)
-      return r
+      return self.__buildRecords ()
 
   # ---------------------------------------------------------------------------
   # Return the next record
@@ -571,17 +668,15 @@
 
   def nextRecord (self):
     """
-    Returns the next record or None if nothing is left.
+    Returns the next record or None if nothing is left.  If the query affected
+    more than one table, the record for the main table (i.e. the table that is
+    the root of all other references) is returned.
     """
 
     if self.__resultSet.nextRecord () is None:
       return None
     else:
-      id = self.__resultSet.current [u'gnue_id']
-      r = record (self.__cache, self.__connections, self.__database,
-                  self.__table, id)
-      r._fill (self.__fields, self.__resultSet.current)
-      return r
+      return self.__buildRecords ()
 
 # =============================================================================
 # Record class
@@ -591,7 +686,7 @@
   """
   This class stands for a record in a database table. An instance of this class
   can be created via the recordset.firstRecord() and recordset.nextRecord()
-  methods.
+  methods, or by the connection.findRecord() method.
   """
 
   # ---------------------------------------------------------------------------
@@ -607,16 +702,27 @@
     self.__row = row
 
   # ---------------------------------------------------------------------------
+  # Cache a single value for this record
+  # ---------------------------------------------------------------------------
+
+  def _cache (self, field, value):
+
+    if not self.__cache.has (self.__table, self.__row, field):
+      self.__cache.write (self.__table, self.__row, field, value, 0)
+
+  # ---------------------------------------------------------------------------
   # Fill the cache for this record with data from a (gnue-common) RecordSet
   # ---------------------------------------------------------------------------
 
-  def _fill (self, fields, RecordSet):
+  def _fill (self, alias, 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)
+      if alias:
+        fn = alias + '.' + field
+      else:
+        fn = field
+      self._cache (field, RecordSet [fn])
 
   # ---------------------------------------------------------------------------
   # Get the value for a field
@@ -670,14 +776,17 @@
 
   from gnue.common.apps import GClientApp
 
-  app = GClientApp.GClientApp ()
+  app = GClientApp.GClientApp (application = 'appserver')
 
   print 'create connection object ...',
   c = connection (app.connections, 'gnue')
   print 'Ok'
 
   print 'connection.query for existing records ...'
-  rs = c.query (u'address_person', [u'address_name'], None, [u'address_name'])
+  content = {u't0': (u'address_person', None, None, [u'address_name']),
+             u't1': (u'address_country', u't0', u'address_country',
+                     [u'address_name'])}
+  rs = c.query (content, None, [u't0.address_name'])
   print 'Ok'
 
   print 'recordset.firstRecord ...',
@@ -690,6 +799,16 @@
   print 'record.getField with non-prefetched data ...',
   print repr (r.getField (u'address_city'))
 
+  print 'connection.findRecord for joined table ...',
+  r = c.findRecord (u'address_country', r.getField (u'address_country'), [])
+  print 'Ok'
+
+  print 'record.getField of joined table with prefetched data ...',
+  print repr (r.getField (u'address_name'))
+
+  print 'record.getField of joined table with non-prefetched data ...',
+  print repr (r.getField (u'address_code'))
+
   print 'recordset.nextRecord ...',
   r = rs.nextRecord ()
   print 'Ok'
@@ -717,7 +836,8 @@
   print 'Ok'
 
   print 'connection.query for previously inserted record ...',
-  rs = c.query (u'address_person', [u'address_name'],
+  content = {None: (u'address_person', None, None, [u'address_name'])}
+  rs = c.query (content,
                 [['eq', ''], ['field', u'address_name'],
                              ['const', u'New Person']], None)
   print 'Ok'
@@ -769,7 +889,8 @@
   print 'Ok'
 
   print 'check if the record is really gone now ...',
-  rs = c.query (u'address_person', [u'address_name'],
+  content = {None: (u'address_person', None, None, [u'address_name'])}
+  rs = c.query (content,
                 [['eq', ''], ['field', u'address_city'],
                              ['const', u'New City']],
                 None)

Modified: trunk/gnue-appserver/src/geasInstance.py
===================================================================
--- trunk/gnue-appserver/src/geasInstance.py    2004-04-17 23:19:20 UTC (rev 
5737)
+++ trunk/gnue-appserver/src/geasInstance.py    2004-04-18 20:31:50 UTC (rev 
5738)
@@ -65,8 +65,9 @@
   # Initalize
   # ---------------------------------------------------------------------------
 
-  def __init__ (self, session, record, classdef):
+  def __init__ (self, session, connection, record, classdef):
     self.__session = session
+    self.__connection = connection
     self.__record = record
     self.__classdef = classdef
 
@@ -168,10 +169,22 @@
 
   def __getValue (self, propertyname):
 
-    propertydef = self.__classdef.properties [propertyname]
+    elements = string.split (propertyname, '.')
 
-    value = self.__record.getField (propertydef.column)
+    c = self.__classdef
+    r = self.__record
+    for e in elements [:-1]:
+      p = c.properties [e]
+      if not '_' in p.gnue_type:
+        raise gException, u_("Cannot resolve property name '%(name)s' because "
+                             "'%(part)s' is not a reference property")
+      c = self.__session.sm.classes [p.gnue_type]
+      r = self.__connection.findRecord (c.table, r.getField (p.column), [])
 
+    propertydef = c.properties [elements [-1]]
+
+    value = r.getField (propertydef.column)
+
     return self.__convert (value, propertydef, DbValueError)
 
   # ---------------------------------------------------------------------------

Modified: trunk/gnue-appserver/src/geasList.py
===================================================================
--- trunk/gnue-appserver/src/geasList.py        2004-04-17 23:19:20 UTC (rev 
5737)
+++ trunk/gnue-appserver/src/geasList.py        2004-04-18 20:31:50 UTC (rev 
5738)
@@ -34,9 +34,10 @@
   # Initalize
   # ---------------------------------------------------------------------------
 
-  def __init__ (self, session, classdef, recordset, propertylist):
+  def __init__ (self, session, classdef, connection, recordset, propertylist):
     self.__session = session
     self.__classdef = classdef
+    self.__connection = connection
     self.__recordset = recordset
     self.__prefetch = propertylist      # property names to be prefetched
 
@@ -47,7 +48,8 @@
   def firstInstance (self):
     record = self.__recordset.firstRecord ()
     if record is not None:
-      return geasInstance.geasInstance (self.__session, record, 
self.__classdef)
+      return geasInstance.geasInstance (self.__session, self.__connection,
+                                        record, self.__classdef)
     else:
       return None
 
@@ -58,7 +60,8 @@
   def nextInstance (self):
     record = self.__recordset.nextRecord ()
     if record is not None:
-      return geasInstance.geasInstance (self.__session, record, 
self.__classdef)
+      return geasInstance.geasInstance (self.__session, self.__connection,
+                                        record, self.__classdef)
     else:
       return None
 

Modified: trunk/gnue-appserver/src/geasSession.py
===================================================================
--- trunk/gnue-appserver/src/geasSession.py     2004-04-17 23:19:20 UTC (rev 
5737)
+++ trunk/gnue-appserver/src/geasSession.py     2004-04-18 20:31:50 UTC (rev 
5738)
@@ -21,9 +21,14 @@
 #
 # $Id$
 
+from types import *
+
+import string
+import whrandom
+
 from gnue.appserver import data
+
 import geasList, geasInstance
-import whrandom
 
 # =============================================================================
 # Session class
@@ -75,6 +80,60 @@
     return ([classdef.properties[p].column for p in propertylist])
 
   # ---------------------------------------------------------------------------
+  # Get a field name from a property name, resolving references
+  # ---------------------------------------------------------------------------
+
+  def __getFieldname (self, classdef, propertyname, contentdict, add):
+
+    elements = string.split (propertyname, '.')
+
+    a = u't0'                           # start with main table
+    c = classdef
+    for e in elements [:-1]:
+      p = c.properties [e]
+      if not '_' in p.gnue_type:
+        raise gException, u_("Cannot resolve property name '%(name)s' because "
+                             "'%(part)s' is not a reference property")
+      c = self.sm.classes [p.gnue_type]
+      # do we already have a link?
+      found = False
+      for (alias, (table, fk_alias, fk_field, fields)) in contentdict.items ():
+        if table == c.table and fk_alias == a and fk_field == p.column:
+          found = True
+          a = alias
+          break
+      # have to create new alias (new link)
+      if not found:
+        new_alias = u't' + str (len (contentdict))
+        contentdict [new_alias] = (c.table, a, p.column, [])
+        a = new_alias
+
+    # the real (final) propertydef
+    p = c.properties [elements [-1]]
+
+    # add new field to fieldlist
+    if add:
+      (contentdict [a] [3]).append (p.column)
+
+    # return the column name
+    return a + '.' + p.column
+
+  # ---------------------------------------------------------------------------
+  # Convert property names in conditions to field names
+  # ---------------------------------------------------------------------------
+
+  def __convertCondition (self, classdef, condition, contentdict):
+
+    if isinstance (condition, ListType) and condition:
+      if isinstance (condition [0], StringType) and condition [0] == 'field':
+        if len (condition) == 2 and isinstance (condition [1], StringType):
+          condition [1] = self.__getFieldname (classdef, condition [1],
+                                               contentdict, False)
+      else:
+        for i in condition:
+          self.__convertCondition (classdef, i, contentdict)
+
+  # ---------------------------------------------------------------------------
   # Log into the application server
   # ---------------------------------------------------------------------------
 
@@ -128,14 +187,22 @@
 
     classdef = self.__getClassdef (classname)
 
-    # TODO: translate property names to column names in conditions
-    sortlist = self.__getFieldlist (classdef, sortorder)
-    fieldlist = self.__getFieldlist (classdef, [u'gnue_id'] + propertylist)
+    # Start to build the content dictionary for self.__connection.query()
+    content = {}
+    content [u't0'] = (classdef.table, None, None, [u'gnue_id'])
 
-    recordset = self.__connection.query (classdef.table, fieldlist, conditions,
-                                         sortlist)
+    for p in propertylist:
+      self.__getFieldname (classdef, p, content, True)
 
-    list = geasList.geasList (self, classdef, recordset,
+    self.__convertCondition (classdef, conditions, content)
+
+    sortlist = []
+    for p in sortorder:
+      sortlist.append (self.__getFieldname (classdef, p, content, False))
+
+    recordset = self.__connection.query (content, conditions, sortlist)
+
+    list = geasList.geasList (self, classdef, self.__connection, recordset,
                               [u'gnue_id'] + propertylist)
 
     self.__listcount += 1
@@ -181,7 +248,8 @@
     table = classdef.table
     fieldlist = self.__getFieldlist (classdef, propertylist)
     record = self.__connection.findRecord (table, object_id, fieldlist)
-    return geasInstance.geasInstance (self, record, classdef)
+    return geasInstance.geasInstance (self, self.__connection, record,
+                                      classdef)
 
   # ---------------------------------------------------------------------------
   # Create a single geasInstance object for a new record
@@ -191,7 +259,8 @@
 
     table = classdef.table
     record = self.__connection.insertRecord (table)
-    return geasInstance.geasInstance (self, record, classdef)
+    return geasInstance.geasInstance (self, self.__connection, record,
+                                      classdef)
 
   # ---------------------------------------------------------------------------
   # Load data from the database backend

Modified: trunk/gnue-appserver/src/test.py
===================================================================
--- trunk/gnue-appserver/src/test.py    2004-04-17 23:19:20 UTC (rev 5737)
+++ trunk/gnue-appserver/src/test.py    2004-04-18 20:31:50 UTC (rev 5738)
@@ -63,15 +63,19 @@
 
     print "Creating and populating list object ..."
     list = sm.request (session, "address_person", [], ["address_zip"],
-                       ["address_name", "address_street", "address_city"])
+                       ["address_name", "address_street", "address_city",
+                        "address_country.address_code",
+                        "address_country.address_name"])
 
     print "Retrieving first instance ..."
     rset = sm.fetch (session,list,0,1)
 
     print "These are the values of the first instance:"
-    print "  Name  :", rset[0][1]
-    print "  Street:", rset[0][2]
-    print "  City  :", rset[0][3]
+    print "  Name   :", rset[0][1]
+    print "  Street :", rset[0][2]
+    print "  City   :", rset[0][3]
+    print "  Country:", rset[0][4]
+    print "          ", rset[0][5]
 
     print "Now I call the procedure 'show' for the first instance:"
     sm.call (session, "address_person", [rset[0][0]], "address_show", {})





reply via email to

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