dbrow.py :  » Database » PyTable » pytable-0.8.20a » pytable » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » PyTable 
PyTable » pytable 0.8.20a » pytable » dbrow.py
"""Basic Object-Relational-like Database Row class"""
from basicproperty import propertied,common,basic,weak
from pytable import dbresultset,sqlquery
import traceback

class DBRow( propertied.Propertied ):
  """An individual row in a result-set

  The DBRow uses the resultSet for the majority of its
  operation, basically just storing the per-row values
  here, with the database connection all provided by
  the result-set.

  Normally the DBRow class is *subclassed* to create
  a Row class for a particular table of the database.

  XXX This is a very heavy implementation compared to the
    one in OPAL's systems, we may need to move to a lighter
    implementation eventually, but we will need to retain
    the ability to use read/write operations as we do.

  XXX There are currently a number of issues regarding
    namespace collisions that need to be fixed fairly
    soon, as the database's schema can readily create
    collisions with the row's method names, for instance
    with an "update" field.
  """
  __allow_access_to_unprotected_subobjects__ = 1
  _DBRow__data = basic.BasicProperty(
    '_DBRow__data', """The original data for this row, from the data-base""",
  )
  _DBRow__newValues = common.DictionaryProperty(
    '_DBRow__newValues', """Newly set, but not-yet committed values""",
  )
  _DBRow__wrappedValues = common.DictionaryProperty(
    '_DBRow__wrappedValues', """Retrieved and wrapped versions for data-values""",
  )
  def __getattr__( self, key ):
    """Delegate attribute lookup to our resultSet or schema"""
    for target in ( 'schema', ):
      if key != target:
        source = (
          self.__dict__.get( target ) or 
          getattr(self.__class__, target, None)
        )
        try:
          return getattr( source, key )
        except AttributeError:
          pass
    if key != "_DBRow__data":
      try:
        return self._DBRow__data[ key ]
      except Exception:
        pass
    raise AttributeError( """%s instance does not have %r attribute"""%(
      self.__class__.__name__,
      key,
    ))
  
  def setValue( self, fieldSchema, value ):
    """Set the field's value for this row"""
    if getattr( self,'readOnly', None):
      raise AttributeError( """Attempt to set property %r of a read-only %s object"""%(
        fieldSchema.name, self.__class__.__name__,
      ))
    self._DBRow__newValues[ fieldSchema.name ] = value
    self._DBRow__wrappedValues[ fieldSchema.name ] = value
    return value
  def getValue( self, fieldSchema ):
    """Get the field's value for this row"""
    if self._DBRow__newValues.has_key( fieldSchema.name ):
      value = self._DBRow__newValues.get( fieldSchema.name )
      if value is DELETED:
        raise AttributeError( """%r instance' %r field has been deleted"""%(
          self.__class__.__name__,
          fieldSchema.name,
        ))
      return value
    elif self._DBRow__wrappedValues.has_key( fieldSchema.name ):
      return self._DBRow__wrappedValues.get( fieldSchema.name )
    else:
      if hasattr( self, '_DBRow__data'):
        for possible in (fieldSchema.name, fieldSchema.name.lower(), fieldSchema.name.upper()):
          value = self._DBRow__data.get(possible)
          if value is not None:
            break
        if value is not None:
          if hasattr( fieldSchema, 'baseClass'):
            if hasattr( fieldSchema.baseClass, 'dbLoad' ):
              value = fieldSchema.baseClass.dbLoad( value )
              self._DBRow__wrappedValues[fieldSchema.name] = value
            elif hasattr(fieldSchema.baseClass, 'coerce'):
              value = fieldSchema.baseClass.coerce( value )
              self._DBRow__wrappedValues[fieldSchema.name] = value
          return value
      raise AttributeError( """%r instance has no %r field value"""%(
        self.__class__.__name__,
        fieldSchema.name,
      ))
  def delValue( self, fieldSchema ):
    """Delete field's value for this row"""
    found = 0
    value = None
    if not (fieldSchema.nullOk or hasattr(fieldSchema, 'defaultValue')):
      raise TypeError(
        """%r instance field %s requires a non-null value and has no defaultValue, cannot delete"""%(
          self.__class__.__name__,
          fieldSchema.name,
        ))
    for source in (
      '_DBRow__data',
      '_DBRow__wrappedValues',
      '_DBRow__newValues',
    ):
      dictionary = getattr( self, source, None )
      if dictionary and dictionary.has_key( fieldSchema.name ):
        value = dictionary.get( fieldSchema.name )
        del dictionary[ fieldSchema.name ]
        found =1
      elif source=='_DBRow__data':
        for key in ( fieldSchema.name.lower(), fieldSchema.name.upper()):
          if dictionary.has_key( key ):
            value = dictionary.get( key )
            del dictionary[ key ]
            found =1
            break
    if not found:
      raise AttributeError( """%r instance has no attribute %r to delete"""%(
        self.__class__.__name__,
        fieldSchema.name,
      ))
    self._DBRow__newValues[ fieldSchema.name ] = DELETED
    return value

  def insertQuery( self, cursor, *arguments, **named ):
    """Insert this object as a new row in it's table

    cursor -- cursor/connection to use for update
    arguments, named -- passed to the query, though
      using arguments will almost certainly raise a
      TypeError, as the client argument is normally
      the second positional argument...

    This method will first see if you have an explicitly
    specified "insert" callable in your schema.  If you
    do not, will use a generic update query defined later
    in this module (RowUpdate).  Once it has determined
    which callable to use, will call the callable with:
      action( cursor, client=self, *arguments, **named )
    """
    query = self.schema.actionByName( "insert" ) or RowInsert()
    if named.has_key( 'debug' ):
      try:
        setattr( query, 'debug', named.get('debug'))
      except (ValueError,TypeError):
        pass
    return query( cursor, client=self, *arguments, **named )
  def updateQuery( self, cursor, *arguments, **named ):
    """Flush any changes to this row to the database

    cursor -- cursor/connection to use for update
    arguments, named -- passed to the query, though
      using arguments will almost certainly raise a
      TypeError, as the client argument is normally
      the second positional argument...

    This method will first see if you have an explicitly
    specified "update" callable in your schema.  If you
    do not, will use a generic update query defined later
    in this module (RowUpdate).  Once it has determined
    which callable to use, will call the callable with:
      action( cursor, client=self, *arguments, **named )
    """
    query = self.schema.actionByName( "update" ) or RowUpdate()
    if named.has_key( 'debug' ):
      try:
        setattr( query, 'debug', named.get('debug'))
      except (ValueError,TypeError):
        pass
    return query( cursor, client=self, *arguments, **named )
  def abort( self, *arguments, **named ):
    """Abort any changes to this row

    This simply clears the newValues dictionary for the row
    """
    self._DBRow__newValues.clear()
  def deleteQuery( self, cursor, *arguments, **named ):
    """Delete row from table
    
    cursor -- cursor/connection to use for update
    arguments, named -- passed to the query, though
      using arguments will almost certainly raise a
      TypeError, as the client argument is normally
      the second positional argument...

    This method will first see if you have an explicitly
    specified "delete" callable in your schema.  If you
    do not, will use a generic update query defined later
    in this module (RowUpdate).  Once it has determined
    which callable to use, will call the callable with:
      action( cursor, client=self, *arguments, **named )
    """
    query = self.schema.actionByName( "delete" ) or RowDelete()
    if named.has_key( 'debug' ):
      try:
        setattr( query, 'debug', named.get('debug'))
      except (ValueError,TypeError):
        pass
    return query( cursor, client=self, *arguments, **named )
  def refreshQuery( self, cursor, *arguments, **named ):
    """Refresh row from database
    
    cursor -- cursor/connection to use for refresh
    arguments, named -- passed to the query, though
      using arguments will almost certainly raise a
      TypeError, as the client argument is normally
      the second positional argument...

    This method will first see if you have an explicitly
    specified "refresh" callable in your schema.  If you
    do not, will use a generic refresh query defined later
    in this module (RowRefresh).  Once it has determined
    which callable to use, will call the callable with:
      action( cursor, client=self, *arguments, **named )
    """
    query = self.schema.actionByName( "refresh" ) or RowRefresh()
    if named.has_key( 'debug' ):
      try:
        setattr( query, 'debug', named.get('debug'))
      except (ValueError,TypeError):
        pass
    return query( cursor, client=self, *arguments, **named )
  def currentQuery( self, cursor, *arguments, **named ):
    """Retrieve (separate) row from database matching our keys
    
    cursor -- cursor/connection to use for refresh
    arguments, named -- passed to the query, though
      using arguments will almost certainly raise a
      TypeError, as the client argument is normally
      the second positional argument...
    """
    query = self.schema.actionByName( "current" ) or RowCurrent()
    if named.has_key( 'debug' ):
      try:
        setattr( query, 'debug', named.get('debug'))
      except (ValueError,TypeError):
        pass
    return query( cursor, client=self, *arguments, **named )

  def dirty( self ):
    """Return value indicating whether we have been changed"""
    return len(self._DBRow__newValues)

  def getProperties( cls ):
    """Get (dbproperty) properties for this object"""
    base = super(DBRow,cls).getProperties()
    base = [
      item for item in base
      if not item.name.startswith( '_DBRow__')
    ]
    # reorder in schema order...
    map = dict([(x.name,x) for x in base])
    result = [
      map.pop(schema.name)
      for schema in cls.schema.fields
      if map.has_key( schema.name )
    ]
    result += map.values()
    return result
  getProperties = classmethod( getProperties)
  def _keyToFieldSchema( self, key ):
    """Get a particular field schema by integer or string key"""
    props = self.schema.fields
    if isinstance( key, int ):
      return props[ key ]
    elif isinstance( key, (str,unicode)):
      try:
        return self.schema.lookupName( key )
      except NameError:
        raise KeyError( """Unknown field %s for schema %s"""%(key, self.schema))
    else:
      raise TypeError( """Don't know how to get field Schema for %r"""%(key))
  def __getitem__( self, key ):
    """Provide dictionary/list-like indexing"""
    try:
      prop = self._keyToFieldSchema( key )
    except KeyError:
      if hasattr( self, key ):
        return getattr(self, key)
      raise KeyError( """%s instance has no %r field defined"""%(
        self.__class__.__name__, key,
      ))
    else:
      return self.getValue( prop )
  def get( self, key, default=None ):
    """Retrieve property value by name/index or return default"""
    try:
      prop = self._keyToFieldSchema( key )
    except KeyError:
      pass
    else:
      try:
        value = self.getValue( prop )
      except (AttributeError,KeyError,ValueError,TypeError):
        pass
    return getattr( self, key, default )
  def has_key( self, key ):
    """Retrieve property value by name/index or return default"""
    return hasattr( self, key )
  def __setitem__( self, key, value ):
    """Provide dictionary/list-like assignment"""
    try:
      prop = self._keyToFieldSchema( key )
    except KeyError:
      setattr( self, key, value )
      return value
    else:
      return self.setValue( prop, value )
  def setdefault( self, key, default ):
    """Get current, setting to default if not currently set"""
    try:
      return self[ key ]
    except (KeyError,AttributeError), err:
      pass
    self[key] = default
    return self[key]
  def getConnection( self ):
    """Try to get the database connection we should use

    If for some reason the connection isn't available will
    return None.  Note that this does *not* check that the
    connection is still valid!
    """
    try:
      return self.cursor.connection
    except AttributeError:
      try:
        return self.connection
      except AttributeError:
        try:
          return self.application.getDBConnection()
        except AttributeError:
          pass
    return None
  def foreignSimpleRef( cls, key ):
    """Retrieve table and field schemas for foreign key"""
    if isinstance( key, (str,unicode)):
      field = cls.schema.lookupName( key )
    else:
      field = key
    foreignRef = field.foreign()
    if foreignRef:
      foreignFields = foreignRef.getForeignFields()
      if len(foreignFields) == 1:
        foreignTable = cls.schema.lookupName(
          foreignRef.foreignTable
        )
        foreignField = foreignFields[0]
        return foreignTable, foreignTable.lookupName( foreignField )
    return None, None
  foreignSimpleRef = classmethod( foreignSimpleRef )
  def fromDict( cls,  dictionary ):
    """Create class isntance from dictionary-of-values"""
    return cls( **dict([
      (k,v) for (k,v) in dictionary.iteritems()
      if k != '__create'
    ]) )
  fromDict = classmethod( fromDict )
  def createSubRecords( self, key, value ):
    """Create sub-records for this field-value reference
    
    key -- field or field-name
    value -- dictionary or DBRow instance
    """
    foreignTable, foreignField = self.foreignSimpleRef( key )
    if foreignTable:
      if isinstance( value, dict ):
        value = foreignTable.itemClass.fromDict( value )
      # we know what we are trying to reference
      try:
        value.currentQuery( connection )
      except KeyError, err:
        value.insertQuery( connection )
      else:
        if value.dirty():
          value.updateQuery( connection )
      return value
    return value
  def referenceSubRecord( cls, key, record, fullPrefix=None ):
    """Create SQL referencing string for given sub-record"""
    foreignTable, foreignField = cls.foreignSimpleRef( key )
    if not foreignTable:
      foreignTable = cls.schema
      foreignField = foreignTable.lookupName( key )
    
    if isinstance( record, dict ):
      record = foreignTable.itemClass( **record )
    _, properties = RowAction().getObjectSpec( record )
    
    
    if record.has_key( foreignField.name ):
      otherField = record[ foreignField.name ]
      if not isinstance( otherField, (dict,DBRow,SQLString)):
        return otherField
    
    foreignFieldName = foreignField.name
    foreignTableName = foreignTable.name
    result = {}
    keySetFragments = []
    for subKey,subValue in properties.items():
      subKeyFull = '%s.%s'%(fullPrefix or key,subKey)
      if isinstance( subValue, (dict,DBRow)):
        # recursive sub-record reference...
        # record is our remote table, so we want *its*
        # reference to the sub-record
        subRecordResult = foreignTable.itemClass.referenceSubRecord(
          subKey, subValue, subKeyFull,
        )
        if isinstance( subRecordResult, dict ):
          result.update( subRecordResult )
          subValue = result[ subKey ]
        else:
          subValue = subRecordResult
      result[subKeyFull] = subValue
      subValueRef = asValueReference( subKeyFull, subValue )
      keySetFragments.append( 
        '%(subKey)s = %(subValueRef)s'%locals() 
      )
    keySetFragments = " AND ".join( keySetFragments )
    record = SQLString( ('''(
      SELECT %(foreignFieldName)s FROM 
      %(foreignTableName)s WHERE %(keySetFragments)s
    )'''%locals()).replace( '\t', ' ').replace( '\n',' ' ) )
    result[ key ] = record
    return result
  referenceSubRecord = classmethod( referenceSubRecord )
  def all( cls ):
    """Return the set of all instances"""
    order = getattr( cls.schema, 'DEFAULT_ORDER', '' )
    if order:
      order = 'ORDER BY %s'%( order, )
    return findTable( cls.schema.name ).query( 
      """SELECT * FROM %(table)s %(order)s""",
      connection,
      table = cls.schema.name,
      order = order,
    )
  all = classmethod( all )

class SQLString( object ):
  """Produces SQL string un-altered as string value"""
  def __init__( self, data ):
    self.data = data
  def __str__( self ):
    return self.data
  __repr__ = __str__
  def __nonzero__( self ):
    return bool( self.data )

class _MockValue( object ):
  def __init__( self, name ):
    self.name = name
  def __str__( self ):
    return self.name
  __repr__ = __str__
  def __nonzero__( self ):
    return False

DELETED = _MockValue( 'DEFAULT' )
NULL = _MockValue( 'NULL' )

def asValueReference( key, value ):
  """Return a value-reference for given key:value pair
  
  Expands sub-record (dictionary, SQLstring etceteras)
  dropping any dictionary sub-keys into the properties 
  dictionary
  """
  if isinstance( value, SQLString ):
    return str(value )
  else:
    return '%%(%s)s'%( key, )

class RowAction( sqlquery.SQLQuery ):
  """Base-class for dbrow action queries

  This just provides the getObjectSpec method
  which scans through the unique keys sets looking
  for a set which is completely available from the
  client object.
  """
  CREATE_SUBRECORDS = False
  def getObjectSpec( self, client, originalOnly=0 ):
    """Get uniquely identifying client specifier

    return value is (tableName, keyProperties)
    where keyProperties is a dictionary mapping
    field name to (resolved/wrapped) field value
    """
    from pytable import dbschema
    set = []
    err = None
    for keySet in client.getUniqueKeys():
      set = []
      for field in keySet:
        try:
          fieldValue = self.getFieldValue( client,field,originalOnly )
          if fieldValue is None:
            raise TypeError(  """Can't use a NULL as a key""" )
          set.append( fieldValue )
        except (KeyError,ValueError,TypeError,AttributeError),err:
          del set[:]
          break
      if set:
        break
    if not set:
      raise ValueError( "%r instance doesn't have enough data to fill any of it's key-sets, can't create a unique ID for it: %s"%(
        type(client).__name__,
        err,
      ))
    return (
      client.schema.name,
      dict(set)
    )
  def getFieldValue( self, client, field, originalOnly ):
    """Retrieve field value for the given field on client"""
    notNull = not getattr( client.__class__, field ).nullOk
    if originalOnly and hasattr( client, '_DBRow__data'):
      if notNull:
        value = client._DBRow__data[field]
      else:
        value = client._DBRow__data.get(field)
    else:
      if notNull:
        value = getattr(client, field)
      else:
        value = getattr( client, field, None )
    if type(value).__name__ == 'PgInt8':
      class Wrap:
        def __init__( self, value ):
          self.value = value
        def __str__( self ):
          return "%s::int8"%(self.value,)
        __repr__ = __str__
      value = Wrap(value)
    return str(field), value
    
  def createSubRecords( self, client, set ):
    """Process property-set to replace references"""
    # do recursive insertion of dictionary/row types...
    newSet = {}
    for key,value in set.items():
      if isinstance( value, (dict, DBRow)):
        if self.CREATE_SUBRECORDS:
          value = client.createSubRecords( key, value )
        newValue = client.referenceSubRecord( key, value )
        if newValue is not None:
          value = newValue
      newSet[key] = value
    return newSet
  def keySetProperties( self, properties ):
    """Produce set of fragments and arguments to select given row"""
    keySetFragments = []
    for k in properties.keys():
      if isinstance( properties[k], dict ):
        for subKey,subValue in properties[k].items():
          properties[subKey] = subValue
      keySetFragments.append(
        "%s=%s"%( k, asValueReference( k, properties[k]) ),
      )
    return keySetFragments

class RowInsert( RowAction ):
  """Default query to insert a DBRow object into database

  This default query does a very simple insert using
  all fields of the row's schema for which there is
  an attribute for the row.

  Note:
    Very slow inserts can result from situations where
    you have not defined a unique key!  The RowInsert
    action will attempt to find a unique identifier for
    the row by using the OID returned from the insertion
    to lookup the record.  This can take a few seconds
    on even very small tables!
  """
  CREATE_SUBRECORDS = True
  sql = """INSERT INTO
    %(tableName)s
      %(tableColumns)s
  %(VALUE_TYPE)s
    %(tableColumnSubs)s
  ;"""
  #debug = 1
  def __call__( self, cursor, client, doResolveQuery=None, doFullQuery=0, **named ):
    """Update this single client instance in the database

    client -- the client object (a DBRow) being serviced
    doResolveQuery -- whether to do the database query to
      retrieve missing key-values, None indicates that the
      system should only do the query if it's needed, any
      other false value suppresses it, any true value forces
      it.  The query is considered required if there are no
      unique keys available on the client.
    doFullQuery -- if true, force a complete select of the
      record after insertion (which retrieves database-provided
      default values, for instance).
    """
    # XXX need to catch no-fields-defined case and handle specially!
    properties = {}
    for field in client.schema.fields:
      if hasattr( client, field.name ):
        try:
          value = getattr( client, field.name )
        except AttributeError, err:
          if getattr( client, '_DBRow__newValues', {}).get( field.name ) is DELETED:
            value = DELETED
          else:
            raise
        properties[ str(field.name) ] = value
    if doResolveQuery is None:
      try:
        self.getObjectSpec( client )
        doResolveQuery = 0
      except ValueError:
        # don't have enough information to uniquely identify...
        doResolveQuery = 1
    if doResolveQuery or doFullQuery:
      # general query can be done without OIDs *IFF* we have
      # a full set of unique keys...
      try:
        table,fields = self.getObjectSpec( client )
      except (ValueError,KeyError), err:
        # okay, can we get the values/default values for a key-set?
        # this allows for serial data-types in databases that don't
        # provide for get-last-row semantics...
        for keySet in client.getUniqueKeys():
          set = []
          toAssign = []
          for field in keySet:
            try:
              set.append( 
                self.getFieldValue( client,field,originalOnly=False )
              )
            except (KeyError,ValueError,TypeError,AttributeError),err:
              field = getattr( client.__class__, field )
              try:
                set.append( (str(field.name), field.nextValue(connection)) )
              except (AttributeError,ValueError, KeyError,TypeError ), err:
                del set[:]
                break
              else:
                toAssign.append( str(field.name) )
          if set:
            break
        if set:
          for key,value in set:
            properties[key] = value
          for key in toAssign:
            setattr( client, key, properties[key] )
          assert self.getObjectSpec( client ), client
    if not properties:
      tableColumnSubs = tableColumns = ""
      VALUE_TYPE = 'DEFAULT VALUES'
    else:
      columnNames = properties.keys()
      tableColumns = '(%s)'%( ",".join( columnNames ), )
      properties = self.createSubRecords( client, properties )
      tableColumnSubs = []
      for k in columnNames:
        v = properties[k]
        if isinstance( v, dict ):
          for subKey,subValue in v.items():
            properties[subKey] = subValue
          properties[k] = v[k]
        tableColumnSubs.append( asValueReference( k, properties[k]) )
      tableColumnSubs = '(%s)'%(",".join( tableColumnSubs ) )
      VALUE_TYPE = 'VALUES'
    properties = self.createSubRecords( client, properties)
    return super( RowInsert, self ).__call__(
      cursor,
      tableName = client.schema.name,
      tableColumns = tableColumns,
      tableColumnSubs = tableColumnSubs,
      clientObject = client,
      doResolveQuery = doResolveQuery,
      doFullQuery = doFullQuery,
      VALUE_TYPE = VALUE_TYPE,
      **properties
    )
  def processResults( self, cursor, clientObject, doResolveQuery=0, doFullQuery=0, **named ):
    """Process the result-set to set the key-values on the row

    Moves newValues to wrapped values
    Clears newValues
    if doResolveQuery:
      Updates data from resolution query
      Eliminates wrapped versions of data so updated
    if doFullQuery:
      Updates data from a full re-query
      Eliminates wrapped versions of data so updated.
    """
    clientObject._DBRow__wrappedValues.update( clientObject._DBRow__newValues )
    clientObject._DBRow__newValues.clear()
    if doResolveQuery or doFullQuery:
      # general query can be done without OIDs *IFF* we have
      # a full set of unique keys...
      try:
        table,fields = self.getObjectSpec( clientObject )
      except (ValueError,KeyError), err:
        # have to use OIDs or similar driver-level query...
        try:
          fields = clientObject.getUniqueKeys()[0]
        except (IndexError, KeyError):
          fields = None
        if doFullQuery:
          fields = None
        newCursor = cursor.connection.driver.getInsertedRow(
          cursor,
          tableName = clientObject.schema.name,
          fields=fields,
        )
        data = dict(map( None, [x[0] for x in newCursor.description], newCursor.fetchone()))
        if not hasattr( clientObject, "_DBRow__data"):
          clientObject._DBRow__data = {}
        clientObject._DBRow__data.update( data )
        wrapped = clientObject._DBRow__wrappedValues
        for key,value in data.items():
          try:
            del wrapped[key]
          except KeyError:
            pass
      else:
        clientObject.refreshQuery(cursor.connection)
    return clientObject

class RowCurrent( RowAction ):
  """Query to retrieve the current row-record for an instance"""
  CREATE_SUBRECORDS = False
  sql = """SELECT
    *
  FROM 
    %(tableName)s
  WHERE
    %(keySetFragments)s
  ;"""
  #debug=1
  def __call__( self, cursor, client, **named ):
    """Update this single client instance in the database

    client -- the client object (a DBRow) being serviced
    """
    tableName, properties = self.getObjectSpec( client )
    expanded = self.createSubRecords( client, properties)
    keySetFragments = self.keySetProperties( expanded )
    keySetFragments = " AND ".join(keySetFragments )
    return super( RowCurrent, self ).__call__(
      cursor,
      tableName= tableName,
      keySetFragments = keySetFragments,
      clientObject = client,
      **expanded
    )
  def processResults( self, cursor, clientObject, **named ):
    """Process the results by updating the clientObject"""
    row = cursor.fetchone()
    if row is None:
      raise KeyError( """Object %s no longer appears to be in the database, refresh query returned null result-set"""%(
        clientObject,
      ))
    names = [ item[0] for item in cursor.description]
    data = dict( map(None, names, row))
    return clientObject.__class__(
      _DBRow__data = data,
    )
  
class RowRefresh( RowCurrent ):
  """Query to do a refresh of database values for a dbrow object"""
  CREATE_SUBRECORDS = False
  def processResults( self, cursor, clientObject, **named ):
    """Process the results by updating the clientObject"""
    row = cursor.fetchone()
    if row is None:
      raise KeyError( """Object %s no longer appears to be in the database, refresh query returned null result-set"""%(
        clientObject,
      ))
    newData = dict(map( None, [x[0] for x in cursor.description], row))
    if not hasattr( clientObject, "_DBRow__data"):
      clientObject._DBRow__data = {}
    raw = clientObject._DBRow__data
    wrapped = clientObject._DBRow__wrappedValues
    new = clientObject._DBRow__newValues
    # clear out mock objects that hide previously set values
    for key,value in new.items():
      if isinstance(value, _MockValue ):
        for d in (raw,wrapped,new):
          try:
            del d[key]
          except KeyError:
            pass
    # clear out any wrapped/new values shadowing newly-queried values
    for key,value in newData.items():
      try:
        del wrapped[key]
      except KeyError:
        pass
      try:
        del new[key]
      except KeyError:
        pass
    # update the raw dictionary with the new values
    raw.update( newData )
    return clientObject
  
    
class RowUpdate( RowAction ):
  """Query to do an update/commit for dbrow object

  This default query takes into account the possibility
  that the fields being used for uniquely identifying
  a row are themselves being changed, so goes through some
  contortions to create data sets which are able to
  specify both the original and changed values.
  """
  CREATE_SUBRECORDS = True
  sql = """UPDATE
    %(tableName)s
  SET
    %(columnFragments)s
  WHERE
    %(keySetFragments)s
  ;"""
  debug=0
  def __call__( self, cursor, client, **named ):
    """Update this single client instance in the database

    client -- the client object (a DBRow) being serviced
      must have non-null _DBRow__newValues attribute.
    """
    if not client._DBRow__newValues:
      raise ValueError(
        """Update called on %r which has no new data"""%(client,),
      )
    tableName, properties = self.getObjectSpec( client, originalOnly=1 )
    expanded = self.createSubRecords( client, properties)
    keySetFragments = " AND ".join(self.keySetProperties( expanded ))
    
    def unusedName( d, baseName ):
      count = 0
      name = baseName
      while d.has_key( name ):
        count += 1
        name = "%s%d"%( baseName, count )
      return name
    
    # now get any non-key-set changed values...
    columns = []
    columnFragments = []
    for key,value in client._DBRow__newValues.items():
      setName= unusedName( expanded, str(key) )
      
      if value is DELETED:
        schema = getattr(getattr(client.__class__, key, None), 'schema', None)
        if schema is None:
          value = NULL
        else:
          value = getattr( schema,'defaultValue', NULL)
        if value is NULL:
          if not schema.nullOk:
            continue # XXX issue a warning!
        if isinstance( value, (dict, DBRow )):
          # XXX will be failures if there is a conflicting
          # id reference!
          if self.CREATE_SUBRECORDS:
            value = client.createSubRecords( key, value )
          newExpanded = client.referenceSubRecord( key, value )
          value = newExpanded[ key ]
          del newExpanded[ key ]
          expanded.update(
            newExpanded
          )
          if self.CREATE_SUBRECORDS:
            value = client.createSubRecords( key, value )
          value = client.referenceSubRecord( key, value )
        
        if value is not NULL: # is the default, which is in SQL syntax form...
          columnFragments.append( "%s=%s"%(
            key,
            value,
          ))
        else:
          columnFragments.append( "%s=%%(%s)s"%(
            key, # actual field name...
            setName, # may be different if we're changing an id key
          ))
      else:
        if isinstance( value, (dict, DBRow )):
          # XXX will be failures if there is a conflicting
          # id reference!
          if self.CREATE_SUBRECORDS:
            value = client.createSubRecords( key, value )
          newExpanded = client.referenceSubRecord( key, value )
          if isinstance( newExpanded, dict ):
            value = newExpanded[ key ]
            del newExpanded[ key ]
            expanded.update(
              newExpanded
            )
          else:
            value = newExpanded
          columnFragments.append(
            '%s=%s'%( 
              key, 
              asValueReference( setName, value ) 
            )
          )
        else:
          columnFragments.append( "%s=%%(%s)s"%(
            key, # actual field name...
            setName, # may be different if we're changing an id key
          ))
      
      expanded[setName] = value
    columnFragments = ",".join( columnFragments )
    return super( RowUpdate, self ).__call__(
      cursor, tableName = tableName,
      columnFragments = columnFragments,
      keySetFragments = keySetFragments,
      clientObject = client,
      **expanded
    )
  def processResults( self, cursor, clientObject, **named ):
    """Process the result-set to set the key-values on the row

    Moves newValues to wrapped values
    Clears newValues
    """
    clientObject._DBRow__wrappedValues.update( clientObject._DBRow__newValues )
    clientObject._DBRow__newValues.clear()
    return clientObject

class RowDelete( RowAction ):
  """Delete a row from the table"""
  CREATE_SUBRECORDS = False
  sql = """DELETE FROM %(tableName)s WHERE %(keySetFragments)s;"""
  def __call__( self, cursor, client, **named ):
    """Delete this single client instance in the database"""
    tableName, properties = self.getObjectSpec( client )
    keySetFragments = " AND ".join([
      "%s=%%(%s)s"%(key,key)
      for key in properties.keys()
    ])
    return super( RowDelete, self ).__call__(
      cursor,
      tableName= tableName,
      keySetFragments = keySetFragments,
      clientObject = client,
      **properties
    )
  
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.