# -*- coding: utf8 -*-
'''
DB MODULE - Oracle 8
*******************************
***  DB MODULE - Oracle 8
*******************************
$RCSfile: dbmod_oracle8.py,v $
$Revision: 911 $
$Author: markus $
$Date: 2008-04-30 23:52:39 +0200 (Mi, 30. Apr 2008) $

The biocase.wrapper database module to access Oracle 8 databases.
You will need the cx_Oracle module to use it. 
There are different versions available of cx_Oracle for Oracle 8.
'''

from biocase.wrapper.errorclasses        import *
from biocase.tools.various_functions     import flatten, scalarpNoList
from biocase.wrapper.typeconverter        import DBtransformerClass
from biocase.wrapper.sql.operators        import logicalOperator
from biocase.wrapper.sql.base            import sqlBase
from string                                import join
from biocase.wrapper.dbmod.base_dbmod   import DBBasemodule
from biocase.wrapper.sql.operators         import getWhereObjTables, compressOperator

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



#############################################
#
# CUSTOMIZATION for Oracle 8
#
# OVERLOAD BASE CLASSES AND OTHER METHODS
#
#############################################

# common optimization rule for Oracle 8
# uncomment this line if this optimisation is needed on your system
#
# sqlBase.templates['select'] = 'SELECT /*+ RULE */ #1 FROM #2'


# do a case insensitive LIKE search by applying the function LOWER.
# Please modify this for a different function like UPPER if you want to 
# or outcomment it to make the search case sensitive.
sqlBase.caseAlteringFunctionForLike = 'LOWER'

# escaped characters in SQL
sqlBase.escape = {}
sqlBase.escape["?"] = "\\?"
sqlBase.escape["'"] = "''"

# syntax to create an alias of a table. #1=real table name, #2=alias name
sqlBase.templates['alias'] = '#1 #2'

# a select statement. #1=select part, #2=from(+where+orderby) part, #3=limit/top number
sqlBase.templates['select-limit'] = 'SELECT DISTINCT #1 FROM #2'


#
# enable (+) operator instead of LEFT JOIN for Oracle<9
sqlBase.binaryCOP['ora_left_join'] = "(+) ="

# override standard type-transformation from module dbtypes:
def makeDate(self, x):
    # convert from date-string to Oracle TO_DATE function
    x = unicode(x)
    # replace escape characters
    for old,new in sqlBase.escape.items():
        x = x.replace(old, new)
    dateformat = 'YYYY-MM-DD HH:MM:SS'
    # replace the T for time in the ANSI time format with space so oracle understands it
    x = x.replace('T', ' ')
    dateformat = dateformat[:len(x)]
    value = sqlBase.templates['string'].replace('#1', x)
    return "TO_DATE(%s, '%s')"%(value, dateformat)
DBtransformerClass.makeDate = makeDate



# ---------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------
class DBmodule(DBBasemodule):
    __version__ = '0.90 using cx_Oracle ?'
    __dbmod__    = u'Oracle 8 DB module'
    
    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))
        #
        #
        # FOR TESTING ONLY !!!
        #
        #
        self.trans.integrateParametersInSQLString()
        # replace method again with this special Oracle method!
        DBtransformerClass.makeDate = makeDate
        log.debug("init DB module")

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def createDBconnection(self):
        #class cc:
        #    def __init__(self):
        #        self.cursor = self.t
        #    def t(self):
        #        return "hi"
        #return cc()

        try:
            import cx_Oracle
        except:
            raise DbModuleMissingError('cx_Oracle')
        self.__class__.__version__ = self.__class__.__version__[:-1]+cx_Oracle.version
        try:
            loginParas = "user:%s  password=%s  TNS=%s" %(self.psfObj.user, self.psfObj.password, self.psfObj.database)
            return cx_Oracle.connect(self.psfObj.user, self.psfObj.password, self.psfObj.database)
        except:
            raise ConnectionError()

    # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    def execSQL(self, sql, paras=None):
        self.c = self.conn.cursor()
        # multiple LEFT JOIN hack !
        # replace all occurences of the same table "same_table.XXX (+) =" with "nvl(same_table.XXX, -1) ="
        import re
        leftJoinPat = re.compile(r'\b(\w+)\.(\w+) *\(\+\).+(\1\.(\w+) *\(\+\))', re.IGNORECASE | re.DOTALL)
        allMatches = leftJoinPat.findall(sql)
        for a in allMatches:
            log.debug("Oracle 8 left join Hack: found two left joins on the same table '%s' in SQL = '%s'" %(a[0], sql))
            sql = sql.replace(a[2], "nvl(%s.%s, -1)" %(a[0],a[3]))
            log.debug("Replaced with SQL = '%s'" %(sql))
        # execute SQL statement and return result
        log.debug("start DB query")
        try:
            sql = sql.encode(self.psfObj.encoding)
            log.info("Executing SQL: '%s' " %(sql))
        except:
            log.warn("The current Oracle DB module can't handle unicode requests.")
            self.c.close()
            raise SQLunicodeGenerationError()
        try:
            self.c.execute(sql)
        except:
            raise SQLError(sql)
        tmp = self.c.fetchall()
        log.debug("end DB query")
        log.info("Hits: "+str(len(tmp)))
        if log.isEnabledFor(logging.DEBUG):
            try:
                if type(tmp) == type('a'):
                    rsString = unicode(tmp, self.psfObj.encoding)
                else:
                    rsString = unicode(tmp)
                log.debug("Resulting Recordset:  "+rsString )
            except:
                log.debug("The Recordset Result could not be converted to a string")
        self.c.close()
        return tmp
        
        
    # --------------------   FUNCTION OVERLOAD OF SELECT MODULE   --------------------
    # --------------------------------------------------------------------------------
    def whereClause(self, filterObj=None, limit=0):
        # compress whereobject to reduce SQL complexity
        filterObj = compressOperator(filterObj)
        if filterObj != None:
            (sql_where, paras) = filterObj.getSQLTuple()
        else:
            sql_where = ''
            paras      = []
        # add limit to SQL here!
        if limit > 0:
            sql_where = " ROWNUM <= #3 AND %s" %(sql_where)
        # None is the correct value for non existing where clause
        if sql_where == '':
            sql_where=None
        return (sql_where, paras)

    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:
            TreeJoinLists = self.CMFObject.tableTreeObj.getJoinTableListOfLists(tableList)
            AdditionalWhereObjs = []
            fromClause = None
            for t, relObj in flatten(TreeJoinLists, scalarpNoList):
                if relObj is not None:
                    relObj.type = 'ora_left_join'
                    AdditionalWhereObjs.append(relObj)
                if fromClause is None:
                    fromClause = self.getTableSyntax(t)
                else:
                    fromClause += ', '+ self.getTableSyntax(t)
            addWhereObj = logicalOperator('and', AdditionalWhereObjs)
            return (fromClause, addWhereObj)
        else:
            return (self.getTableSyntax(tableList[0]), None)



