"""Thick wrapper around a pytable cursor with a result-set"""
from __future__ import generators
from basictypes import typeunion
from basicproperty import propertied, common, basic, weak
from pytable import dbschema, viewschema, sqlquery, lazyresultset

class DBResultSet( lazyresultset.LazyResultSet, propertied.Propertied):
	"""A pseudo-sequence with read/write lazy-result-set semantics

	The DBResultSet wraps a pytable cursor which has a
	retrieved result-set to provide access to a controlling
	schema (a table or view schema) and to provide automated
	commit/abort of changes to the generated dbrow objects.

	Via the lazyresultset base-class provides lazy loading
	of the results from the set.
	"""
	schema = basic.BasicProperty(
		'schema', """The controlling schema for this result-set""",
		baseType= typeunion.TypeUnion((
			viewschema.ViewSchema,
			dbschema.TableSchema,
		)),
	)
	cursor = basic.BasicProperty(
		'cursor', 'Pointer to our database cursor (a pytable dbcursor instance)',
	)
	def defaultAutoCommit( property, client ):
		"""Get the default for the auto-commit flag"""
		if hasattr( client, 'schema'):
			if hasattr( client.schema,'autoCommit'):
				# write directly through to the database...
				return client.schema.autoCommit
		return 0
		
	autoCommit = common.BooleanProperty(
		'autoCommit', """Override schema's autoCommit value

	autoCommit cause a database update to be done on every
	property __set__.  TableSchemas normally do *not* define an
	autoCommit attribute, so the result-set's autoCommit
	value is normally derived from the defaultFunction of this
	property (which defaults to false)
	""",
		defaultFunction = defaultAutoCommit,
	)
	del defaultAutoCommit
	cursorDescription = basic.BasicProperty(
		'cursorDescription', """The db-api-style cursor description for data-rows

	This is used to unify result-set fields with the
	controlling schema, as the order of fields may
	not match that within the database.
	""",
		defaultFunction = lambda property,client: client.cursor.description
	)
	_rowCache = common.ListProperty(
		'_rowCache', """Cache of row-objects loaded from the database

	These rows in the cache are the wrapped objects, that
	is, dbrow objects.  Generally there's no need to access
	this property directly.

	Note: this property shadows the lazyresultset's
	attribute to provide documentation.
	""",
		defaultFunction = lambda prop,client: [],
	)
	length = common.IntegerProperty(
		'length', """Length of the table if calculated yet, otherwise -1

	You should use len( self ), not self.length for any
	code you write.  Length is just part of the
	lazyresultset base-class's API.

	Note: this property shadows the lazyresultset's
	attribute to provide documentation.
	""",
		defaultValue = -1,
	)
	def __getattr__( self, key ):
		"""Delegate attribute lookup to our schema if it exists"""
		for target in ( 'schema', ):
			if key != target:
				resultSet = self.__dict__.get( target, None ) or getattr(self.__class__, target, None)
				try:
					return getattr( resultSet, key )
				except AttributeError:
					pass
		raise AttributeError( """%s instance does not have %r attribute"""%(
			self.__class__.__name__,
			key,
		))

	def commit( self, rows = None ):
		"""Commit changes to rows, or all changed rows
		"""
		if rows is None:
			rows = [ row for row in self._rowCache if row.dirty() ]
		else:
			rows = [ row for row in rows if row.dirty() ]
		if not rows:
			return 0
		query = self.schema.actionByName( "update" )
		if query:
			return query(
				viewSchema = self.schema,
				rows = rows,
			)
		else:
			# default update semantics, potentially chaining to multiple tables...
			if hasattr( self.schema, 'tables' ):
				raise NotImplementedError( """Don't support multi-table joins yet for %s"""%self.schema )
			# okay, simple table, not a joined/mapped table...
			try:
				result = SimpleTableUpdate()(
					cursor = self.cursor.connection.cursor(),
					tableName = self.schema.name,
					keySet = self.schema.getUniqueKeys()[0],
					columnSet = [ s.name for s in self.schema.fields],
					rows = rows,
				)
			except Exception, err:
				raise 
			else:
				self.cursor.connection.commit()
				return result
			
		raise NotImplementedError( """Schema %s doesn't provide an "update" action"""%self.schema )
	
	def abort( self, rows = None ):
		"""Abort changes to rows, or all changed rows"""
		if rows is None:
			rows = [ row for row in self._rowCache if row._DBRow__newValues ]
		else:
			rows = [ row for row in rows if row._DBRow__newValues ]
		for row in rows:
			if hasattr( row, 'abort'):
				row.abort()
	def getProperties( self ):
		"""Retrieve the properties for this particular result-set"""
		items = self.schema.properties
		result = []
		for field in self.schema.fields:
			result.append( items.get( field.name ))
		return result
	def wrapRow( self, data, index ):
		"""Wrap a single row in our DBRow class"""
		names = [ item[0] for item in self.cursorDescription]
		data = dict( map(None, names, data))
		return self.schema.itemClass(
			_DBRow__data = data,
			_DBRow__resultSet = self,
			_DBRow__index = index,
		)

class SimpleTableUpdate( sqlquery.SQLMultiQuery ):
	"""Query to do an update in a single table for a set of rows

	Note:
		This really is the "simple" update query.  DBRow instances,
		which are far more commonly used, have their own far more
		involved update/insert/delete queries.
		
		XXX Should likely eliminate this code?  It's only use is
			for doing updates of large numbers of rows
			simultaneously with the same fields for each update.
	"""
	#debug = 1
	sql = """UPDATE
		%(tableName)s
	SET
		%(columnFragment)s
	WHERE
		%(keySetFragment)s
	;"""
	def __call__( self, cursor, tableName, keySet, columnSet, rows, **named ):
		"""Build the sql query up from given values and then execute it

		cursor -- DBCursor or DBConnection object
		tableName -- string tableName into which to update
		keySet -- list of key field names (WHERE clause)
		columnSet -- list of fields to update
		rows -- list of objects/dictionaries used as data-source(s)
		named -- passed to the underlying query
		"""
		keySetFragment = " AND ".join([ "%s=%%(%s)s"%(key,key) for key in keySet ])
		columnFragment = ", ".join([ "%s=%%(%s)s"%(attr,attr) for attr in columnSet if attr not in keySet ])
		if not keySetFragment:
			raise TypeError( """Attempt to update table %r row without a keySet"""%(
				tableName,
			))
		named["keySetFragment"] = keySetFragment
		named["columnFragment"] = columnFragment
		named["tableName"] = tableName
		required = {}
		for item in tuple(keySet)+tuple(columnSet):
			required.setdefault( item, 1 )
		required = required.keys()
		dataSet = []
		for row in rows:
			dataSet.append( dict([
				(attr,getattr(row,attr))
				for attr in required
			]))
		return super(SimpleTableUpdate,self).__call__(
			cursor,
			dataSet = dataSet,
			**named
		)
