""" A Python Database API Specification v2.0 implementation that provides configuration loading, 
variable substitution, logging, query banding, etc and options to use either ODBC or REST"""

# The MIT License (MIT)
#
# Copyright (c) 2015 by Teradata
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys, string, os.path, logging, time, atexit, datetime, platform, getpass, subprocess, collections
from . import tdodbc, util, api, datatypes
from . import tdrest  # @UnresolvedImport
from .version import __version__  # @UnresolvedImport

# The module logger
logger = logging.getLogger(__name__)

METHOD_REST="rest"
METHOD_ODBC="odbc"

# Implement python version specific setup.
if sys.version_info[0] == 2:
    import ConfigParser as configparser  # @UnresolvedImport #@UnusedImport
else:
    import configparser  # @UnresolvedImport @UnusedImport @Reimport
    
def handleUncaughtException (exc_type, exc_value, exc_traceback):
    """Make sure that uncaught exceptions are logged"""
    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
    sys.__excepthook__(exc_type, exc_value, exc_traceback)
    
def exiting ():
    """Invoked when the python interpreter is exiting."""
    logger.info("UdaExec exiting.")

class UdaExec:
    """Helper class for scripting with Teradata systems"""
    def __init__(self, appName="${appName}", version="${version}",
                 checkpointFile="${checkpointFile}", runNumberFile="${runNumberFile}", runNumber=None,
                 configureLogging="${configureLogging}", logDir="${logDir}", logFile="${logFile}", logConsole="${logConsole}", 
                 logLevel="${logLevel}", logRetention="${logRetention}", systemConfigFile = "/etc/udaexec.ini",
                 userConfigFile="~/udaexec.ini", appConfigFile="udaexec.ini", configFiles=None,
                 configSection="CONFIG", parseCmdLineArgs=True, gitPath="${gitPath}", production="${production}",
                 odbcLibPath="${odbcLibPath}", dataTypeConverter=datatypes.DefaultDataTypeConverter()):
        """ Initializes the UdaExec framework """
        # Load configuration files.
        if configFiles is None:
            configFiles = []
            _appendConfigFiles(configFiles, systemConfigFile, userConfigFile, appConfigFile)
        logMsgs = [(logging.INFO, "Initializing UdaExec...")]
        self.config = UdaExecConfig(configFiles, configSection, parseCmdLineArgs, logMsgs)
        # Verify required configuration parameters are specified.
        self.config['appName'] = self.config.resolve(appName, errorMsg="appName is a required field, it must be passed in as a parameter or specified in a config file.")
        # Initialize runNumbers.
        self._initRunNumber(runNumberFile, runNumber, logMsgs)
        # Configure Logging
        self.configureLogging =  util.booleanValue(self.config.resolve(configureLogging, default="True"))
        if self.configureLogging:
            self._initLogging(self.config.resolve(logDir, default="logs"),
                              self.config.resolve(logFile, default=self.config.resolve("${appName}.${runNumber}.log")),
                              util.booleanValue(self.config.resolve(logConsole, default="True")),
                              getattr(logging, self.config.resolve(logLevel, default="INFO")),
                              int(self.config.resolve(logRetention, default="90")), logMsgs)
        # Log messages that were collected prior to logging being configured.
        for (level,msg) in logMsgs:
            logger.log(level, msg)
        self._initVersion (self.config.resolve(version, default=""), self.config.resolve(gitPath, default=""))
        self._initQueryBands(self.config.resolve(production, default="false"))
        self._initCheckpoint(checkpointFile)
        self.odbcLibPath = self.config.resolve(odbcLibPath, default="")
        self.dataTypeConverter = dataTypeConverter
        logger.info(self)
        logger.debug(self.config)
        # Register exit function. d        
        atexit.register(exiting)
        
    def connect (self, externalDSN=None, dataTypeConverter=None, **kwargs):
        """Creates a database connection"""
        # Construct data source configuration parameters
        args = {}
        if externalDSN is not None:
            externalDSN = self.config.resolve(externalDSN)
            args = self.config.section(externalDSN)
            if args is None:
                raise api.InterfaceError(api.CONFIG_ERROR, "No data source named \"{}\".".format(externalDSN))
        args.update(self.config.resolveDict(kwargs))        
        # Log connection details.
        paramsToLog = dict(args)
        paramsToLog['password'] = 'XXXXXX'
        if externalDSN:
            paramsToLog['externalDSN'] = externalDSN
        logger.info("Creating connection: %s", paramsToLog)
        # Determine connection method.
        method = None
        if 'method' in args: 
            method = args.pop('method')
        util.raiseIfNone('method', method)  
        if 'queryBands' in args:
            queryBands = args.pop('queryBands')
            self.queryBands.update(queryBands);
        if 'autoCommit' not in args:
            args['autoCommit'] = "true"     
        if not dataTypeConverter:
            dataTypeConverter = self.dataTypeConverter
        # Create the connection
        try:
            start = time.time()
            if method.lower() == METHOD_REST:
                conn = UdaExecConnection(self, tdrest.connect(queryBands=self.queryBands, 
                                                              dataTypeConverter=dataTypeConverter, **args))
            elif method.lower() == METHOD_ODBC:
                conn = UdaExecConnection(self, tdodbc.connect(queryBands=self.queryBands, odbcLibPath=self.odbcLibPath, 
                                                              dataTypeConverter=dataTypeConverter, **args))
            else:
                raise api.InterfaceError(api.CONFIG_ERROR, "Connection method \"{}\" not supported".format(method))
            duration = time.time() - start
            logger.info("Connection successful. Duration: %.3f seconds. Details: %s", duration, paramsToLog)
            return conn
        except Exception as e:
            logger.exception("Unable to create connection: %s", paramsToLog)
            raise e          
        
    def checkpoint (self, checkpointName=None):
        """ Sets or clears the current checkpoint."""
        if checkpointName is None:
            logger.info("Clearing checkpoint....")
            self.currentCheckpoint = None
            self.skip = False
            if self.checkpointManager:
                self.checkpointManager.clearCheckpoint()
        else:
            self.currentCheckpoint = checkpointName
            if self.skip:
                if self.resumeFromCheckpoint == self.currentCheckpoint:
                    logger.info("Reached resume checkpoint: \"%s\".  Resuming execution...", checkpointName)
                    self.skip = False 
            else:
                logger.info("Reached checkpoint: \"%s\"",  checkpointName)
                if self.checkpointManager:
                    self.checkpointManager.saveCheckpoint(checkpointName)
                    
    def setCheckpointManager (self, checkpointManager):
        """ Sets a custom Checkpoint Manager. """
        util.raiseIfNone("checkpointManager", checkpointManager)
        logger.info("Setting custom checkpoint manager: %s", checkpointManager)
        self.checkpointManager = checkpointManager
        logger.info("Loading resume checkpoint from checkpoint manager...")
        self.setResumeCheckpoint(checkpointManager.loadCheckpoint())
            
    def setResumeCheckpoint (self, resumeCheckpoint):
        """ Sets the checkpoint that must be hit for executes to not be skipped."""
        self.resumeFromCheckpoint = resumeCheckpoint
        if resumeCheckpoint: 
            logger.info("Resume checkpoint changed to \"%s\".  Skipping all calls to execute until checkpoint is reached.", self.resumeFromCheckpoint)
            self.skip = True
        else:
            self.resumeFromCheckpoint = None
            if self.skip:
                self.skip = False
                logger.info("Resume checkpoint cleared.  Execute calls will no longer be skipped.")
            else:
                logger.info("No resume checkpoint set, continuing execution...")

    def _initLogging (self, logDir, logFile, logConsole, level, logRetention, logMsgs):
        """Initialize UdaExec logging"""
        if not os.path.exists(logDir):
            os.makedirs(logDir)
        self._cleanupLogs (logDir, logRetention, logMsgs)
        self.logDir = os.path.realpath(logDir)
        self.logFile = os.path.join(self.logDir, logFile)
        formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        fh = logging.FileHandler(self.logFile, mode="a", encoding="utf8")
        fh.setFormatter(formatter)
        fh.setLevel(level)
        sh = logging.StreamHandler(sys.stdout)
        sh.setFormatter(formatter)
        sh.setLevel(level)
        root = logging.getLogger()
        root.setLevel(level)
        root.addHandler(fh)
        if logConsole:
            root.addHandler(sh)
        sys.excepthook = handleUncaughtException
    
    def _cleanupLogs (self, logDir, logRetention, logMsgs):
        """Cleanup older log files."""
        logMsgs.append((logging.INFO, "Cleaning up log files older than {} days.".format(logRetention)))
        cutoff = time.time() - (logRetention * 86400)
        count = 0
        for f in os.listdir(logDir):
            f = os.path.join(logDir, f)
            if os.stat(f).st_mtime < cutoff:
                logMsgs.append((logging.DEBUG, "Removing log file: {}".format(f)))
                os.remove(f)
                count += 1
        logMsgs.append((logging.INFO, "Removed {} log files.".format(count)))
        
    def _initRunNumber (self, runNumberFile, runNumber, logMsgs):
        """Initialize the run number unique to this particular execution."""
        if runNumber is not None:
            self.runNumber = runNumber
            logMsgs.append((logging.INFO, "Setting run number to {}.".format(runNumber)))
        else:
            self.runNumber = "1"
            self.runNumberFile = self.config.resolve(runNumberFile, default='.runNumber')
            self.runNumberFile = os.path.abspath(self.runNumberFile)
            if os.path.isfile(self.runNumberFile):
                logMsgs.append((logging.INFO, "Found run number file: \"{}\"".format(self.runNumberFile)))     
                with open (self.runNumberFile, "r") as f:
                    self.runNumber = f.readline()
                if self.runNumber is not None:
                    try:
                        self.runNumber = str(int(self.runNumber) + 1)
                    except:
                        logMsgs.append((logging.WARN, "Unable to increment run number ({}) in {}. Resetting run number to 1.".format(self.runNumber, self.runNumberFile)))
                        self.runNumber = 1   
                else:
                    logMsgs.append((logging.WARN, "No run number found in {}. Resetting run number to 1.".format(self.runNumberFile)))    
            else:
                logMsgs.append((logging.INFO, "No previous run number found as {} does not exist. Initializing run number to 1".format(self.runNumberFile)))    
            with open(self.runNumberFile, 'w') as f:
                f.write(self.runNumber)
            self.runNumber = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + "-" + self.runNumber 
        self.config['runNumber'] = self.runNumber
        
    def _initCheckpoint(self, checkpointFile):
        """Initialize the result checkpoint."""
        self.currentCheckpoint = None
        self.skip = False
        if checkpointFile:
            checkpointFile = self.config.resolve(checkpointFile, default=self.config['appName'] + ".checkpoint")
        if checkpointFile:
            checkpointFile = os.path.abspath(checkpointFile)
            self.checkpointManager = UdaExecCheckpointManagerFileImpl(checkpointFile)
            self.resumeFromCheckpoint = self.checkpointManager.loadCheckpoint()
            if self.resumeFromCheckpoint:
                logger.info("Resuming from checkpoint \"%s\".", self.resumeFromCheckpoint)
                self.skip = True
            else:
                logger.info("No previous checkpoint found, executing from beginning...")
                self.skip = False
        else:
            self.checkpointManager = None
            self.resumeFromCheckpoint = None
            logger.info("Checkpoint file disabled.");
            
    def _initVersion (self, version, gitPath):
        """Initialize the version and GIT revision."""
        if not gitPath:
            gitPath = "git"
            logger.debug("Git path not specified, using system path.")
        self.gitVersion = None
        self.gitRevision = None
        self.gitDirty = None
        try:
            self.gitVersion = subprocess.check_output([gitPath, "--version"], stderr=subprocess.STDOUT).decode("utf-8").strip()
            self.gitRevision = subprocess.check_output([gitPath, "describe", "--tags", "--always", "HEAD"], stderr=subprocess.STDOUT).decode("utf-8").strip()
            self.modifiedFiles = subprocess.check_output([gitPath, "status", "--porcelain"], stderr=subprocess.STDOUT).decode("utf-8").splitlines()
            self.gitDirty = True if self.modifiedFiles else False
        except subprocess.CalledProcessError as e:
            logger.debug("Git information is not available: %s.", e.output.decode("utf-8"))
        except Exception as e:
            logger.debug("Git is not available: %s", e)
        if not version:
            version = self.gitRevision
        if not version:
            raise api.InterfaceError(api.CONFIG_ERROR, "version is a required field, it must be passed in as a parameter, specified in a config file, or pulled from a git repository.")
        self.config['version'] = version
        
            
    def _initQueryBands (self, production):
        """Initialize the Query Band that will be set on future connections."""
        self.queryBands= collections.OrderedDict()
        self.queryBands['ApplicationName'] = self.config['appName']
        self.queryBands['Version'] = self.config['version']
        self.queryBands['JobID'] = self.runNumber
        self.queryBands['ClientUser'] = getpass.getuser()
        self.queryBands['Production'] = util.booleanValue(production)
        if self.configureLogging:
            self.queryBands['udaAppLogFile'] = self.logFile
        if self.gitRevision:
            self.queryBands['gitRevision'] = self.gitRevision
        if self.gitDirty is not None:
            self.queryBands['gitDirty'] = self.gitDirty
        self.queryBands['UtilityName'] = 'PyTd'
        self.queryBands['UtilityVersion'] = __version__
    
    def __str__ (self):
        value = "Execution Details:\n/"
        value += '*' * 80
        value += "\n"
        value += " * Application Name: {}\n".format(self.config['appName'])
        value += " *          Version: {}\n".format(self.config['version'])
        value += " *       Run Number: {}\n".format(self.runNumber)
        value += " *             Host: {}\n".format(platform.node())
        value += " *         Platform: {}\n".format(platform.platform(aliased=True))
        value += " *          OS User: {}\n".format(getpass.getuser())
        value += " *   Python Version: {}\n".format(platform.python_version())
        value += " *  Python Compiler: {}\n".format(platform.python_compiler())
        value += " *     Python Build: {}\n".format(platform.python_build())
        value += " *  UdaExec Version: {}\n".format(__version__)
        value += " *     Program Name: {}\n".format(sys.argv[0])
        value += " *      Working Dir: {}\n".format(os.getcwd())
        if self.gitRevision:
            value += " *      Git Version: {}\n".format(self.gitVersion)
            value += " *     Git Revision: {}\n".format(self.gitRevision)
            value += " *        Git Dirty: {} {}\n".format(self.gitDirty, "" if not self.gitDirty else "[" + ",".join(self.modifiedFiles) + "]")
        if self.configureLogging:
            value += " *          Log Dir: {}\n".format(self.logDir)
            value += " *         Log File: {}\n".format(self.logFile)
        value += " *     Config Files: {}\n".format(self.config.configFiles)
        value += " *      Query Bands: {}\n".format(u";".join(u"{}={}".format(k, v) for k, v in self.queryBands.items()))
        value += '*' * 80
        value += '/'
        return value
    
def _appendConfigFiles (configFiles, *args):
    for arg in args:
        if arg is None:
            continue
        if util.isString(arg):
            configFiles.append(arg)
        else:
            configFiles.extend(arg)
    
class UdaExecCheckpointManager:
    """ Manages the initialization and saving of checkpoints. """
    def loadCheckpoint (self):
        """ Return the checkpoint name that we should resume from. """
        raise NotImplementedError( "loadCheckpoint must be implemented by sub-class" )
    def saveCheckpoint (self, checkpointName):
        """ Save the specified checkpoint """
        raise NotImplementedError( "raiseCheckpoint must be implemented by sub-class" )
    def clearCheckpoint (self):
        """ Remove the checkpoint so that the application starts from beginning next time around. """
        raise NotImplementedError( "clearCheckpoint must be implemented by sub-class" )

class UdaExecCheckpointManagerFileImpl (UdaExecCheckpointManager):
    """ Implementation of the UdaExecCheckpointMananer using a local file."""
    def __init__ (self, file):
        self.file = file 
        
    def loadCheckpoint(self):
        resumeFromCheckpoint = None
        if os.path.isfile(self.file):
            logger.info("Found checkpoint file: \"%s\"", self.file)     
            with open (self.file, "r") as f:
                resumeFromCheckpoint = f.readline()
            if not resumeFromCheckpoint:
                logger.warn("No checkpoint found in %s.", self.file)
        else:
            logger.info("Checkpoint file not found: %s", self.file)
        return resumeFromCheckpoint
            
    def saveCheckpoint(self, checkpointName):
        logger.info("Saving checkpoint \"%s\" to %s.", checkpointName, self.file)
        with open(self.file, 'w') as f:
            f.write(checkpointName)
    
    def clearCheckpoint(self):
        logger.info("Removing checkpoint file %s.", self.file)
        if os.path.isfile(self.file):
            os.remove(self.file)
    
class UdaExecTemplate (string.Template):
    """Template used by UdaExec configuration and token replacement."""
    idpattern = r'[a-z][_a-z0-9\.]*'
                    
class UdaExecConfig:
    """UdaExec configuration loader and resolver."""
    def __init__ (self, configFiles, configSection, parseCmdLineArgs, logMsgs):
        configParser = configparser.ConfigParser()
        configParser.optionxform = str
        configFiles = [os.path.expanduser(f) for f in configFiles]
        self.configFiles=[ str(os.path.abspath(f)) + (": Found" if os.path.isfile(f) else ": Not Found") for f in configFiles]
        logMsgs.append((logging.INFO, "Reading config files: {}".format(self.configFiles)))
        configParser.read(configFiles)
        self.configSection = configSection
        self.sections = {configSection: {}}
        for section in configParser.sections():
            self.sections[section] = dict(configParser.items(section))
        if parseCmdLineArgs:
            for arg in sys.argv:
                if arg.startswith('--') and '=' in arg:
                    (key,val) = arg.split("=", 1)
                    key = key[2:]
                    logMsgs.append((logging.DEBUG, "Configuration value was set via command line: {}={}".format(key, val)))
                    self.sections[configSection][key] = val  
    
    def __iter__ (self):
        return iter(self.sections[self.configSection])
            
    def contains (self, option):
        return option in self.sections[self.configSection]
                                                      
    def resolveDict (self, d, sections=None):
        if sections is None:
            sections = [ self.configSection ]
        for key, value in d.items():
            if util.isString(value):
                d[key] = self._resolve(value, sections, None, None)
        return d
        
    def resolve (self, value, sections=None, default=None, errorMsg=None):
        if value is None:
            if errorMsg is not None:
                raise api.InterfaceError(api.CONFIG_ERROR, errorMsg)
            else:
                util.raiseIfNone("value", value)
        if not util.isString(value):
            return value
        if sections is None:
            sections = [ self.configSection ]
        return self._resolve(value, sections, default, errorMsg)
        
    def _resolve (self, value, sections, default, errorMsg):
        error = None
        for section in sections:
            try:
                s = self.sections[section]
                newValue = UdaExecTemplate(value.replace("$$", "$$$$")).substitute(**s)
                if value != newValue:
                    value = self._resolve(newValue, sections, None, errorMsg)
                else:
                    value = value.replace("$$", "$")
                error = None
            except KeyError as e:
                error = e
        if error is not None:
            if default is not None:
                return default
            if errorMsg is not None:
                raise api.InterfaceError(api.CONFIG_ERROR, errorMsg)
            else:    
                raise api.InterfaceError(api.CONFIG_ERROR, "Unable to resolve variable \"{}\".  Not found: {}".format(value, error))
        return value
        
    def section (self, section):
        try:
            return self.resolveDict(self.sections[section].copy(), (section, self.configSection))
        except KeyError:
            return None
        
    def __getitem__(self, key):
        return self.resolve(self.sections[self.configSection][key])
    
    def __setitem__(self, key, value):
        self.sections[self.configSection][key] = value
        
    def __str__ (self):
        length = 0
        for key in self.sections[self.configSection]:
            keyLength = len(key)
            if keyLength > length:
                length = keyLength     
        value = "Configuration Details:\n/"
        value += '*' * 80
        value += "\n"
        for key in sorted(self.sections[self.configSection]):
            value += " * {}: {}\n".format(key.rjust(length), self.resolve("${" + key + "}") if 'password' not in key.lower() else 'XXXXXX')
        value += '*' * 80
        value += '/'
        return value    
        
class UdaExecConnection:
    """A UdaExec connection wrapper for ODBC or REST connections."""
    def __init__(self, udaexec, conn):
        self.udaexec = udaexec
        self.conn = conn
        self.internalCursor = self.cursor()
        
    def close (self):
        self.internalCursor.close()
        self.conn.close()
            
    def commit (self):
        self.conn.commit()
        
    def rollback (self):
        self.conn.rollback()
    
    def cursor (self):
        return UdaExecCursor(self.udaexec, self.conn.cursor())
        
    def __del__ (self):
        self.close()

    def __enter__ (self):
        return self
    
    def __exit__ (self, exceptionType, exceptionValue, traceback):
        self.close()
        
    def callproc (self, procname, params, **kwargs):
        return self.internalCursor.callproc(procname, params, **kwargs)
        
    def execute (self, query=None, params=None, **kwargs):
        self.internalCursor.execute(query, params, **kwargs)
        return self.internalCursor 
    
    def executemany (self, query, params, **kwargs):
        self.internalCursor.executemany(query, params, **kwargs)
        return self.internalCursor
    
class UdaExecCursor:
    """A UdaExec cursor wrapper for ODBC or REST cursors."""
    def __init__(self, udaexec, cursor):
        self.udaexec = udaexec
        self.cursor = cursor
        self.skip = False
        self.description = None
        self.types = None
        self.rowcount = -1
    
    def callproc (self, procname, params, runAlways=False, continueOnError=False, ignoreErrors=[], **kwargs):
        self.skip = self.udaexec.skip  and not runAlways
        if not self.skip:
            start = time.time()
            try:
                procname = self.udaexec.config.resolve(procname)
                outparams = self.cursor.callproc(procname, params, **kwargs)
                duration = time.time() - start
                logger.info("Procedure Successful. Duration: %.3f seconds, Procedure: %s, Params: %s", duration, procname, params)
                return outparams
            except Exception as e:
                duration = time.time() - start
                if isinstance(e, api.DatabaseError) and e.code in ignoreErrors:
                    logger.error("Procedure Failed! Duration: %.3f seconds, Procedure: %s, Params: %s, Error Ignored: ", duration, procname, params, e)
                else: 
                    logger.exception("Procedure Failed! Duration: %.3f seconds, Procedure: %s, Params: %s", duration, procname, params)
                    if not continueOnError:
                        raise e                    
        else:
            logger.info("Skipping procedure, haven't reached resume checkpoint yet.  Procedure:  %s", procname)
    
    def close (self):
        self.cursor.close()
        
    def execute (self, query=None, params=None, file=None, fileType=None, delimiter=";", **kwargs):
        if file is None:
            util.raiseIfNone("query", query)
        if query is not None:   
            if util.isString(query): 
                self._execute(self.cursor.execute, query, params, **kwargs)
            else:
                for q in query:
                    self._execute(self.cursor.execute, q, params, **kwargs)
        if file is not None:
            self._executeFile(file, params, fileType, delimiter, **kwargs)
        return self
    
    def executemany (self, query, params, **kwargs):
        self._execute(self.cursor.executemany, query, params, **kwargs)
        return self
    
    def _executeFile (self, file, params, fileType, delimiter, runAlways=False, **kwargs):
        self.skip = self.udaexec.skip  and not runAlways
        if not self.skip:
            file = self.udaexec.config.resolve(file)
            if fileType is None:
                script = util.SqlScript(file, delimiter);
            elif fileType == "bteq":
                script = util.BteqScript(file)
            else:
                raise api.InterfaceError("UNKNOWN_FILE_TYPE", "The file type '{}' is not unknown".format(fileType))
            for query in script:
                self._execute(self.cursor.execute, query, params, runAlways, **kwargs)
        else:
            logger.info("Skipping file, haven't reached resume checkpoint yet.  File:  %s", file)
    
    def _execute (self, func, query, params, runAlways=False, continueOnError=False, ignoreErrors=[], **kwargs):
        self.skip = self.udaexec.skip  and not runAlways
        if not self.skip:
            start = time.time()
            paramStr = _getParamsString(params)
            try:
                query = self.udaexec.config.resolve(query)
                func(query, params, **kwargs)
                self.description = self.cursor.description
                self.types = self.cursor.types
                self.rowcount = self.cursor.rowcount
                duration = time.time() - start
                rowsStr = " " if self.cursor.rowcount < 0 else " Rows: %s, " % self.cursor.rowcount
                logger.info("Query Successful. Duration: %.3f seconds,%sQuery: %s%s", duration, rowsStr, query, paramStr)
            except Exception as e:
                self.description = None
                self.types = None
                self.rowcount = -1
                duration = time.time() - start
                if isinstance(e, api.DatabaseError) and e.code in ignoreErrors:
                    logger.error("Query Failed! Duration: %.3f seconds, Query: %s%s, Error Ignored: %s", duration, query, paramStr, e)
                else:
                    logger.exception("Query Failed! Duration: %.3f seconds, Query: %s%s", duration, query, paramStr)
                    if not continueOnError:
                        raise e
        else:
            logger.info("Skipping query, haven't reached resume checkpoint yet.  Query:  %s", query)
            
    def fetchone (self):
        if self.skip:
            return None
        return self.cursor.fetchone()
        
    def fetchmany (self, size=None):
        if self.skip:
            return []
        return self.cursor.fetchmany(size)
        
    def fetchall (self):
        if self.skip:
            return []
        return self.cursor.fetchall()
        
    def nextset (self):
        if self.skip:
            return None
        return self.cursor.nextset()
    
    def setinputsizes (self, sizes):
        self.cursor.setinputsizes(self, sizes)
    
    def setoutputsize (self, size, column=None):
        self.cursor.setoutputsizes(self, size)
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.skip:
            raise StopIteration()
        return self.cursor.__next__()
    
    def next (self):
        return self.__next__()
    
    def __enter__ (self):
        return self
    
    def __exit__ (self, t, value, traceback):
        self.close()
        
def _getParamsString(params):
    paramsStr = ""
    if params:
        if isinstance(params[0], (list, tuple)):
            paramsStr = u", Params: {}".format(u"\n".join(_getParamString(p) for p in params))
        else:
            paramsStr = u", Params: {}".format(_getParamString(params))
    return paramsStr
 
def _getParamString(params):
    paramsStr = []
    for p in params:
        p = repr(p) 
        p = (p[:75] + '...') if len (p) > 80 else p
        paramsStr.append(p)
    return u"[" + u",".join(paramsStr) + u"]"
            
            
    
    
        
        
        
