//-----------------------------------------------------------------------------
// Copyright 2016, 2017, 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.
//-----------------------------------------------------------------------------

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

//-----------------------------------------------------------------------------
// structure for the Python type
//-----------------------------------------------------------------------------
typedef struct {
    PyObject_HEAD
    long code;
    unsigned offset;
    PyObject *message;
    PyObject *context;
    char isRecoverable;
} udt_Error;


//-----------------------------------------------------------------------------
// forward declarations
//-----------------------------------------------------------------------------
static void Error_Free(udt_Error*);
static PyObject *Error_Str(udt_Error*);
static PyObject *Error_New(PyTypeObject*, PyObject*, PyObject*);
static PyObject *Error_Reduce(udt_Error*);


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


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


//-----------------------------------------------------------------------------
// declaration of Python type
//-----------------------------------------------------------------------------
static PyTypeObject g_ErrorType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "cx_Oracle._Error",                 // tp_name
    sizeof(udt_Error),                  // tp_basicsize
    0,                                  // tp_itemsize
    (destructor) Error_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) Error_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
    g_ErrorMethods,                     // tp_methods
    g_ErrorMembers,                     // 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
    Error_New,                          // tp_new
    0,                                  // tp_free
    0,                                  // tp_is_gc
    0                                   // tp_bases
};


//-----------------------------------------------------------------------------
// Error_Free()
//   Deallocate the error.
//-----------------------------------------------------------------------------
static void Error_Free(udt_Error *self)
{
    Py_CLEAR(self->message);
    Py_CLEAR(self->context);
    PyObject_Del(self);
}


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

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

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

    return (PyObject*) self;
}


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


//-----------------------------------------------------------------------------
// Error_InternalNew()
//   Internal method for creating an error object from the DPI error
// information.
//-----------------------------------------------------------------------------
static udt_Error *Error_InternalNew(dpiErrorInfo *errorInfo)
{
    PyObject *format, *args, *fnName, *action;
    udt_Error *self;

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

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

    // create context composed of function name and action
    fnName = cxString_FromAscii(errorInfo->fnName);
    if (!fnName) {
        Py_DECREF(self);
        return NULL;
    }
    action = cxString_FromAscii(errorInfo->action);
    if (!action) {
        Py_DECREF(fnName);
        Py_DECREF(self);
        return NULL;
    }
    args = PyTuple_Pack(2, fnName, action);
    Py_DECREF(fnName);
    Py_DECREF(action);
    if (!args) {
        Py_DECREF(self);
        return NULL;
    }
    format = cxString_FromAscii("%s: %s");
    if (!format) {
        Py_DECREF(self);
        Py_DECREF(args);
        return NULL;
    }
    self->context = cxString_Format(format, args);
    Py_DECREF(format);
    Py_DECREF(args);
    if (!self->context) {
        Py_DECREF(self);
        return NULL;
    }

    return self;
}


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

    self = Error_InternalNew(errorInfo);
    switch (errorInfo->code) {
        case 1:
        case 1400:
        case 2290:
        case 2291:
        case 2292:
            exceptionType = g_IntegrityErrorException;
            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 = g_OperationalErrorException;
            break;
        default:
            exceptionType = g_DatabaseErrorException;
            break;
    }
    PyErr_SetObject(exceptionType, (PyObject*) self);
    Py_DECREF(self);
    return -1;
}


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

    dpiContext_getError(g_DpiContext, &errorInfo);
    return Error_RaiseFromInfo(&errorInfo);
}


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


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

