[Top][All Lists]
[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", {})
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- r5738 - trunk/gnue-appserver/src,
reinhard <=