# -*- coding: utf8 -*-
'''DB MODULE - MS Access
******************************
***  DB MODULE - MS Access
******************************
$RCSfile: dbmod_odbc_access.py,v $
$Revision: 905 $
$Author: markus $
$Date: 2008-04-30 11:57:10 +0200 (Mi, 30. Apr 2008) $

The biocase.wrapper database module to access MS Access databases.
You will need the mxODBC module to connect to a ODBC source.
'''

import os, types
from string								import join
from biocase.wrapper.errorclasses		import *
from biocase.tools.various_functions 	import mergeLoL
from biocase.wrapper.typeconverter		import DBtransformerClass
from biocase.wrapper.sql.operators		import getWhereObjTables
from biocase.wrapper.sql.base			import sqlBase
from biocase.wrapper.dbmod.base_dbmod   import DBBasemodule

import logging
log = logging.getLogger("pywrapper.dbmod")


#############################################
#
# CUSTOMIZATION for Access
#
# OVERLOAD BASE CLASSES AND OTHER METHODS
#
#############################################


# uses inherited ANSI SQL apart from the following.
sqlBase.templates['attribute'] = '[#1].[#2]'
sqlBase.templates['table'] = '[#1]'
sqlBase.templates['join'] = '(#1 LEFT JOIN #2 ON #3)'
sqlBase.templates['select-limit'] = 'SELECT DISTINCT TOP #3 #1 FROM #2'


	
# ---------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------
class DBmodule(DBBasemodule):
	__version__ = '0.92 using mxODBC ?'
	__dbmod__	= u'MS Access DB module'
	maxListLength = 250
	   
	# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
	def __init__(self, psfObj):
		# setting the psfObj reference to self.psfObj
		# call createDBconnection method
		DBBasemodule.__init__(self, psfObj)
		#
		# read DB 8bit encoding
		#
		log.debug("8bit DB Encoding: %s"%(psfObj.encoding))
		self.conn.encoding = psfObj.encoding

	# ------------------   FUNCTION OVERLOAD OF BASE DBMOD MODULE   ------------------
	# --------------------------------------------------------------------------------
	def createDBconnection(self):
		try:
			import mx.ODBC
		except:
			raise DbModuleMissingError('mx_ODBC')
		self.__class__.__version__ = self.__class__.__version__[:-1]+mx.ODBC.__version__
		#
		# connect
		#
		# import OS specific ODBC version:
		# 'posix', 'nt', 'dos', 'mac', 'os2', 'ce', 'java', 'riscos'
		if os.name in ("nt","dos"):
			import mx.ODBC.Windows	as odbc
		elif os.name in ("unix","posix","java"):
			import mx.ODBC.unixODBC as odbc
		else:
			raise ConnectionError(os=os.name)
		loginParas = "DSN=%s;UID=%s;PWD=%s" % (self.psfObj.database, self.psfObj.user ,self.psfObj.password )
		conn = odbc.DriverConnect( loginParas )
		conn.stringformat = odbc.NATIVE_UNICODE_STRINGFORMAT
		return conn 
			
	# --------------------------------------------------------------------------------
	def execSelect(self, selectList, whereObj=None, orderByList=[], limit=None):
		'''execute a select statement. the selectList gives the wanted DB-attribute objects and the optional whereObj the WHERE filter.'''
		resultList = []
		if len(selectList) > self.__class__.maxListLength:
			log.debug("The maximum number of allowed columns in a single SQL select statement is limited to %i. The requested %i columns are split up into several SQL statements." %(self.__class__.maxListLength, len(selectList)))
			if log.isEnabledFor(logging.DEBUG):
				log.debug("The full list of all requested columns is %s"%str(selectList))
		# check if selectList is limited
		if len(selectList) > self.__class__.maxListLength and recID is not None:
			i = 1
			# This causes problems with the ODBC Jet engine - do seperate queries
			# first get the FROM & WHERE clause of all columns to be used for all queries
			(sql, paras) = self.select(selectList, whereObj, limit=limit, orderByList=orderByList)
			fromClause = sql[sql.find(' FROM '):]
			# now get the list of all primary keys of all associated tables:
			PKList = self.psfObj.getPKDBAttrObjListOfDBAttrObjList(dbaoList=selectList, aliasList=getWhereObjTables(whereObj))
			# now do the partial querying of the database
			maxColumns = self.__class__.maxListLength - len(PKList)
			# do logging
			log.debug("Splitting up the SQL including always the primary keys '%s' and the FROM clause '%s' with parameters '%s'"%(str(PKList), fromClause, str(paras)))
			while len(selectList) > maxColumns:
				# get new select part including all PKs
				selectListPart = PKList + selectList[:maxColumns]
				(sql, p) = self.select(selectListPart, whereObj, limit=limit, orderByList=orderByList)
				# replace FROM clause with standard FROM clause
				sql = sql[:sql.find(' FROM ')] + fromClause
				# log
				log.debug("Executing SQL for %i. part of complete select list."%(i))
				# execute SQL part x
				resultListPart = self.execSQL(sql, paras)
				log.debug("part %i contains %i rows" %(i, len(resultListPart)) )
				# sort result according to primary keys to be merged correctly
				resultListPart.sort()
				# merge list of lists without the PKs of the second list (and therefore also never in the first list).
				resultList = mergeLoL(resultList, [row[len(PKList):] for row in resultListPart])
				log.debug("up to now %i merged rows" %(len(resultList)) )
				# remove already queried columns from the selection list
				selectList = selectList[maxColumns:]
				i += 1
			# execute last part < self.__class__.maxListLength 
			selectListPart = PKList + selectList
			(sql, p) = self.select(selectListPart, whereObj)
			sql = sql[:sql.find(' FROM ')] + fromClause
			log.debug("Executing SQL for the last %i. part of complete select list."%(i))
			resultListPart = self.execSQL(sql, paras)
			log.debug("part %i contains %i rows" %(i, len(resultListPart)) )
			resultListPart.sort()
			resultList = mergeLoL(resultList, [row[len(PKList):] for row in resultListPart])
			log.debug("up to now %i merged rows" %(len(resultList)) )
		else:
			# get SQL string
			(sql, paras) = self.select(selectList, whereObj, limit=limit, orderByList=orderByList)
			# execute statement
			resultList = self.execSQL(sql, paras)
		if len(resultList) > 0:
			log.debug("RecordSet with %i column(s) retrieved" %len(resultList[0]))
		else:
			log.debug("RecordSet with no rows retrieved")
		return resultList 


	# --------------------   FUNCTION OVERLOAD OF SELECT MODULE   --------------------
	# --------------------------------------------------------------------------------
	def fromClause(self, tableList):
		'''Returns the SQL string of the FROM clause for a given table(alias) list.
		Makes use of the tablegraph to produce an ordered list of tables
		and their relation - the relObj (table-alias names, whereObj).'''
		if len(tableList) > 1:
			fromClause = ''
			# check if a static table exists
			if len(self.CMFObject.tableTreeObj.staticTables) > 0:
				log.warn("You are trying to use a static table with MS Access. This is not supported. Please enter your static data in Access into a view.")
				raise CMFError(cmf=self.CMFObject.getRootNamespace())
			# add JOINS for non static tables via the tablegraph object
			tableTupleList = self.CMFObject.tableTreeObj.getJoinTableListOfLists(tableList)[0]
			fromClause += self.getTableSyntax(tableTupleList.pop(0)[0])
			for t, relObj in tableTupleList:
				onClause = str(relObj)
				newTable = self.getTableSyntax(t)
				fromClause = self.apply_template('join', [fromClause, newTable, onClause])
			return (fromClause, None)
		else:
			return (self.getTableSyntax(tableList[0]), None)
