//-----------------------------------------------------------------------------
// Copyright 2016-2018, Oracle and/or its affiliates. All rights reserved.
//
// Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved.
//
// Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta,
// Canada. All rights reserved.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// cxoError.c
//   Error handling.
//-----------------------------------------------------------------------------

#include "cxoModule.h"

//-----------------------------------------------------------------------------
// forward declarations
//-----------------------------------------------------------------------------
static void cxoError_free(cxoError *error);
static PyObject *cxoError_str(cxoError *error);
static PyObject *cxoError_new(PyTypeObject *type, PyObject *args,
        PyObject *keywordArgs);
static PyObject *cxoError_reduce(cxoError*);


//-----------------------------------------------------------------------------
// declaration of methods
//-----------------------------------------------------------------------------
static PyMethodDef cxoErrorMethods[] = {
    { "__reduce__", (PyCFunction) cxoError_reduce, METH_NOARGS },
    { NULL, NULL }
};


//-----------------------------------------------------------------------------
// declaration of members
//-----------------------------------------------------------------------------
static PyMemberDef cxoErrorMembers[] = {
    { "code", T_LONG, offsetof(cxoError, code), READONLY },
    { "offset", T_UINT, offsetof(cxoError, offset), READONLY },
    { "message", T_OBJECT, offsetof(cxoError, message), READONLY },
    { "context", T_OBJECT, offsetof(cxoError, context), READONLY },
    { "isrecoverable", T_BOOL, offsetof(cxoError, isRecoverable), READONLY },
    { NULL }
};


//-----------------------------------------------------------------------------
// declaration of Python type
//-----------------------------------------------------------------------------
PyTypeObject cxoPyTypeError = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "cx_Oracle._Error",                 // tp_name
    sizeof(cxoError),                   // tp_basicsize
    0,                                  // tp_itemsize
    (destructor) cxoError_free,         // tp_dealloc
    0,                                  // tp_print
    0,                                  // tp_getattr
    0,                                  // tp_setattr
    0,                                  // tp_compare
    0,                                  // tp_repr
    0,                                  // tp_as_number
    0,                                  // tp_as_sequence
    0,                                  // tp_as_mapping
    0,                                  // tp_hash
    0,                                  // tp_call
    (reprfunc) cxoError_str,            // tp_str
    0,                                  // tp_getattro
    0,                                  // tp_setattro
    0,                                  // tp_as_buffer
    Py_TPFLAGS_DEFAULT,                 // tp_flags
    0,                                  // tp_doc
    0,                                  // tp_traverse
    0,                                  // tp_clear
    0,                                  // tp_richcompare
    0,                                  // tp_weaklistoffset
    0,                                  // tp_iter
    0,                                  // tp_iternext
    cxoErrorMethods,                    // tp_methods
    cxoErrorMembers,                    // tp_members
    0,                                  // tp_getset
    0,                                  // tp_base
    0,                                  // tp_dict
    0,                                  // tp_descr_get
    0,                                  // tp_descr_set
    0,                                  // tp_dictoffset
    0,                                  // tp_init
    0,                                  // tp_alloc
    cxoError_new,                       // tp_new
    0,                                  // tp_free
    0,                                  // tp_is_gc
    0                                   // tp_bases
};


//-----------------------------------------------------------------------------
// cxoError_free()
//   Deallocate the error.
//-----------------------------------------------------------------------------
static void cxoError_free(cxoError *error)
{
    Py_CLEAR(error->message);
    Py_CLEAR(error->context);
    PyObject_Del(error);
}


//-----------------------------------------------------------------------------
// cxoError_new()
//   Create a new error object. This is intended to only be used by the
// unpickling routine, and not by direct creation!
//-----------------------------------------------------------------------------
static PyObject *cxoError_new(PyTypeObject *type, PyObject *args,
        PyObject *keywordArgs)
{
    PyObject *message, *context;
    int isRecoverable, code;
    cxoError *error;
    unsigned offset;

    isRecoverable = 0;
    if (!PyArg_ParseTuple(args, "OiIO|i", &message, &code, &offset, &context,
            &isRecoverable))
        return NULL;
    error = (cxoError*) type->tp_alloc(type, 0);
    if (!error)
        return NULL;

    error->code = code;
    error->offset = offset;
    error->isRecoverable = (char) isRecoverable;
    Py_INCREF(message);
    error->message = message;
    Py_INCREF(context);
    error->context = context;

    return (PyObject*) error;
}


//-----------------------------------------------------------------------------
// cxoError_newFromInfo()
//   Internal method for creating an error object from the DPI error
// information.
//-----------------------------------------------------------------------------
cxoError *cxoError_newFromInfo(dpiErrorInfo *errorInfo)
{
    cxoError *error;

    // create error object and initialize it
    error = (cxoError*) cxoPyTypeError.tp_alloc(&cxoPyTypeError, 0);
    if (!error)
        return NULL;
    error->code = errorInfo->code;
    error->offset = errorInfo->offset;
    error->isRecoverable = (char) errorInfo->isRecoverable;

    // create message
    error->message = cxoPyString_fromEncodedString(errorInfo->message,
            errorInfo->messageLength, errorInfo->encoding, NULL);
    if (!error->message) {
        Py_DECREF(error);
        return NULL;
    }

    // create context composed of function name and action
#if PY_MAJOR_VERSION >= 3
    error->context = PyUnicode_FromFormat("%s: %s", errorInfo->fnName,
            errorInfo->action);
#else
    error->context = PyString_FromFormat("%s: %s", errorInfo->fnName,
            errorInfo->action);
#endif
    if (!error->context) {
        Py_DECREF(error);
        return NULL;
    }

    return error;
}


//-----------------------------------------------------------------------------
// cxoError_newFromString()
//   Internal method for creating an error object from the DPI error
// information.
//-----------------------------------------------------------------------------
static cxoError *cxoError_newFromString(const char *message)
{
    cxoError *error;

    error = (cxoError*) cxoPyTypeError.tp_alloc(&cxoPyTypeError, 0);
    if (!error)
        return NULL;
    Py_INCREF(Py_None);
    error->context = Py_None;
    error->message = cxoPyString_fromAscii(message);
    if (!error->message) {
        Py_DECREF(error);
        return NULL;
    }

    return error;
}


//-----------------------------------------------------------------------------
// cxoError_raiseAndReturnInt()
//   Internal method for raising an exception from an error generated from DPI.
// Return -1 as a convenience to the caller.
//-----------------------------------------------------------------------------
int cxoError_raiseAndReturnInt(void)
{
    dpiErrorInfo errorInfo;

    dpiContext_getError(cxoDpiContext, &errorInfo);
    return cxoError_raiseFromInfo(&errorInfo);
}


//-----------------------------------------------------------------------------
// cxoError_raiseAndReturnNull()
//   Internal method for raising an exception from an error generated from DPI.
// Return NULL as a convenience to the caller.
//-----------------------------------------------------------------------------
PyObject *cxoError_raiseAndReturnNull(void)
{
    cxoError_raiseAndReturnInt();
    return NULL;
}


//-----------------------------------------------------------------------------
// cxoError_raiseFromInfo()
//   Internal method for raising an exception given an error information
// structure from DPI. Return -1 as a convenience to the caller.
//-----------------------------------------------------------------------------
int cxoError_raiseFromInfo(dpiErrorInfo *errorInfo)
{
    PyObject *exceptionType;
    cxoError *error;

    error = cxoError_newFromInfo(errorInfo);
    if (!error)
        return -1;
    switch (errorInfo->code) {
        case 1:
        case 1400:
        case 2290:
        case 2291:
        case 2292:
            exceptionType = cxoIntegrityErrorException;
            break;
        case 22:
        case 378:
        case 602:
        case 603:
        case 604:
        case 609:
        case 1012:
        case 1013:
        case 1033:
        case 1034:
        case 1041:
        case 1043:
        case 1089:
        case 1090:
        case 1092:
        case 3113:
        case 3114:
        case 3122:
        case 3135:
        case 12153:
        case 12203:
        case 12500:
        case 12571:
        case 27146:
        case 28511:
            exceptionType = cxoOperationalErrorException;
            break;
        default:
            exceptionType = cxoDatabaseErrorException;
            break;
    }
    PyErr_SetObject(exceptionType, (PyObject*) error);
    Py_DECREF(error);
    return -1;
}


//-----------------------------------------------------------------------------
// cxoError_raiseFromString()
//   Internal method for raising an exception given an error information
// structure from DPI. Return -1 as a convenience to the caller.
//-----------------------------------------------------------------------------
PyObject *cxoError_raiseFromString(PyObject *exceptionType,
        const char *message)
{
    cxoError *error;

    error = cxoError_newFromString(message);
    if (!error)
        return NULL;
    PyErr_SetObject(exceptionType, (PyObject*) error);
    Py_DECREF(error);
    return NULL;
}


//-----------------------------------------------------------------------------
// cxoError_reduce()
//   Method provided for pickling/unpickling of Error objects.
//-----------------------------------------------------------------------------
static PyObject *cxoError_reduce(cxoError *error)
{
    return Py_BuildValue("(O(OiIO))", Py_TYPE(error), error->message,
            error->code, error->offset, error->context);
}


//-----------------------------------------------------------------------------
// cxoError_str()
//   Return a string representation of the error variable.
//-----------------------------------------------------------------------------
static PyObject *cxoError_str(cxoError *error)
{
    Py_INCREF(error->message);
    return error->message;
}

