diff --git a/.gitmodules b/.gitmodules index 8272de377..136ba68cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,4 +2,4 @@ path = aerospike-client-c # url = git@github.com:aerospike/aerospike-client-c.git url = https://github.com/aerospike/aerospike-client-c.git - branch = CLIENT-3307 + branch = stage diff --git a/aerospike-client-c b/aerospike-client-c index ee9fce614..ddaf1f0c6 160000 --- a/aerospike-client-c +++ b/aerospike-client-c @@ -1 +1 @@ -Subproject commit ee9fce6140e8f85fbefc6dea7988b23691090d81 +Subproject commit ddaf1f0c6ebfc77db59ba063b7fb2e10022d1190 diff --git a/aerospike-stubs/aerospike.pyi b/aerospike-stubs/aerospike.pyi index 0b71198ee..313e8c4dd 100644 --- a/aerospike-stubs/aerospike.pyi +++ b/aerospike-stubs/aerospike.pyi @@ -295,15 +295,17 @@ QUERY_DURATION_LONG_RELAX_AP: Literal[2] COMMIT_OK: Literal[0] COMMIT_ALREADY_COMMITTED: Literal[1] -COMMIT_VERIFY_FAILED: Literal[2] -COMMIT_MARK_ROLL_FORWARD_ABANDONED: Literal[3] -COMMIT_ROLL_FORWARD_ABANDONED: Literal[4] -COMMIT_CLOSE_ABANDONED: Literal[5] +COMMIT_ALREADY_ABORTED: Literal[2] +COMMIT_VERIFY_FAILED: Literal[3] +COMMIT_MARK_ROLL_FORWARD_ABANDONED: Literal[4] +COMMIT_ROLL_FORWARD_ABANDONED: Literal[5] +COMMIT_CLOSE_ABANDONED: Literal[6] ABORT_OK: Literal[0] ABORT_ALREADY_ABORTED: Literal[1] -ABORT_ROLL_BACK_ABANDONED: Literal[2] -ABORT_CLOSE_ABANDONED: Literal[3] +ABORT_ALREADY_COMMITTED: Literal[2] +ABORT_ROLL_BACK_ABANDONED: Literal[3] +ABORT_CLOSE_ABANDONED: Literal[4] TXN_STATE_OPEN: Literal[0] TXN_STATE_VERIFIED: Literal[1] diff --git a/doc/aerospike.rst b/doc/aerospike.rst index 2e23d8291..ecd05f5d4 100644 --- a/doc/aerospike.rst +++ b/doc/aerospike.rst @@ -1594,6 +1594,10 @@ Transaction Commit Status Transaction has already been committed. +.. data:: COMMIT_ALREADY_ABORTED + + Transaction has already been aborted. + .. data:: COMMIT_VERIFY_FAILED Transaction verify failed. Transaction will be aborted. @@ -1625,6 +1629,10 @@ Transaction Abort Status Transaction has already been aborted. +.. data:: ABORT_ALREADY_COMMITTED + + Transaction has already been committed. + .. data:: ABORT_ROLL_BACK_ABANDONED Client roll back abandoned. Server will eventually abort the transaction. diff --git a/doc/exception.rst b/doc/exception.rst index c81c67724..2081e2f68 100644 --- a/doc/exception.rst +++ b/doc/exception.rst @@ -56,6 +56,18 @@ Base Class ``True`` if it is possible that the command succeeded. See :ref:`indoubt`. + .. py:attribute:: commit_status + + Set to one of :ref:`mrt_commit_status_constants` when :meth:`~aerospike.Client.commit` raises an exception. + + Otherwise, :py:obj:`None` if not set. + + .. py:attribute:: abort_status + + Set to one of :ref:`mrt_abort_status_constants` when :meth:`~aerospike.Client.abort` raises an exception. + + Otherwise, :py:obj:`None` if not set. + In addition to accessing these attributes by their names, \ they can also be checked by calling ``exc.args[i]``, where ``exc`` is the exception object and \ ``i`` is the index of the attribute in the order they appear above. \ diff --git a/src/include/conversions.h b/src/include/conversions.h index 2c97276d2..3c84928a7 100644 --- a/src/include/conversions.h +++ b/src/include/conversions.h @@ -150,6 +150,9 @@ as_status bins_to_pyobject(AerospikeClient *self, as_error *err, bool cnvt_list_to_map); void error_to_pyobject(const as_error *err, PyObject **obj); +void as_error_and_mrt_status_to_pytuple(const as_error *err, PyObject **obj, + PyObject *py_commit_status, + PyObject *py_abort_status); as_status pyobject_to_astype_write(AerospikeClient *self, as_error *err, PyObject *py_value, as_val **val, diff --git a/src/include/exceptions.h b/src/include/exceptions.h index f2778307d..5e9018fe2 100644 --- a/src/include/exceptions.h +++ b/src/include/exceptions.h @@ -20,6 +20,8 @@ PyObject *AerospikeException_New(void); void raise_exception(as_error *err); +void raise_exception_with_mrt_status(as_error *err, PyObject *py_commit_status, + PyObject *py_abort_status); PyObject *raise_exception_old(as_error *err); void remove_exception(as_error *err); void set_aerospike_exc_attrs_using_tuple_of_attrs(PyObject *py_exc, diff --git a/src/main/aerospike.c b/src/main/aerospike.c index 1d4e4caa2..0f54c3c57 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -503,6 +503,7 @@ static struct module_constant_name_to_value module_constants[] = { {"COMMIT_OK", .value.integer = AS_COMMIT_OK}, {"COMMIT_ALREADY_COMMITTED", .value.integer = AS_COMMIT_ALREADY_COMMITTED}, + {"COMMIT_ALREADY_ABORTED", .value.integer = AS_COMMIT_ALREADY_ABORTED}, {"COMMIT_VERIFY_FAILED", .value.integer = AS_COMMIT_VERIFY_FAILED}, {"COMMIT_MARK_ROLL_FORWARD_ABANDONED", .value.integer = AS_COMMIT_MARK_ROLL_FORWARD_ABANDONED}, @@ -512,6 +513,7 @@ static struct module_constant_name_to_value module_constants[] = { {"ABORT_OK", .value.integer = AS_ABORT_OK}, {"ABORT_ALREADY_ABORTED", .value.integer = AS_ABORT_ALREADY_ABORTED}, + {"ABORT_ALREADY_COMMITTED", .value.integer = AS_ABORT_ALREADY_COMMITTED}, {"ABORT_ROLL_BACK_ABANDONED", .value.integer = AS_ABORT_ROLL_BACK_ABANDONED}, {"ABORT_CLOSE_ABANDONED", .value.integer = AS_ABORT_CLOSE_ABANDONED}, diff --git a/src/main/client/mrt.c b/src/main/client/mrt.c index d49bd43c4..5b729be0a 100644 --- a/src/main/client/mrt.c +++ b/src/main/client/mrt.c @@ -21,21 +21,23 @@ PyObject *AerospikeClient_Commit(AerospikeClient *self, PyObject *args, as_error err; as_error_init(&err); - as_commit_status status; + // Set a default value in case the C client does not initialize the status. + as_commit_status status = AS_COMMIT_OK; Py_BEGIN_ALLOW_THREADS aerospike_commit(self->as, &err, py_transaction->txn, &status); Py_END_ALLOW_THREADS - if (err.code != AEROSPIKE_OK) { - raise_exception(&err); + PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status); + if (py_status == NULL) { return NULL; } - PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status); - if (py_status == NULL) { + if (err.code != AEROSPIKE_OK) { + raise_exception_with_mrt_status(&err, py_status, NULL); return NULL; } + return py_status; } @@ -55,20 +57,22 @@ PyObject *AerospikeClient_Abort(AerospikeClient *self, PyObject *args, as_error err; as_error_init(&err); - as_abort_status status; + // Set a default value in case the C client does not initialize the status. + as_abort_status status = AS_ABORT_OK; Py_BEGIN_ALLOW_THREADS aerospike_abort(self->as, &err, py_transaction->txn, &status); Py_END_ALLOW_THREADS - if (err.code != AEROSPIKE_OK) { - raise_exception(&err); + PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status); + if (py_status == NULL) { return NULL; } - PyObject *py_status = PyLong_FromUnsignedLong((unsigned long)status); - if (py_status == NULL) { + if (err.code != AEROSPIKE_OK) { + raise_exception_with_mrt_status(&err, NULL, py_status); return NULL; } + return py_status; } diff --git a/src/main/conversions.c b/src/main/conversions.c index 37f55fbc8..7b5b3b636 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -59,6 +59,8 @@ #define PY_EXCEPTION_FILE 2 #define PY_EXCEPTION_LINE 3 #define AS_PY_EXCEPTION_IN_DOUBT 4 +#define AS_PY_EXCEPTION_COMMIT_STATUS 5 +#define AS_PY_EXCEPTION_ABORT_STATUS 6 #define CTX_KEY "ctx" #define CDT_CTX_ORDER_KEY "order_key" @@ -2389,7 +2391,17 @@ as_status metadata_to_pyobject(as_error *err, const as_record *rec, return err->code; } +// TODO: a better name for this is as_error_to_pytuple void error_to_pyobject(const as_error *err, PyObject **obj) +{ + as_error_and_mrt_status_to_pytuple(err, obj, NULL, NULL); +} + +// Steals reference to MRT statuses, if non-NULL +// If MRT statuses are NULL, that just means they will be None in the Python tuple +void as_error_and_mrt_status_to_pytuple(const as_error *err, PyObject **obj, + PyObject *py_commit_status, + PyObject *py_abort_status) { PyObject *py_file = NULL; if (err->file) { @@ -2414,12 +2426,24 @@ void error_to_pyobject(const as_error *err, PyObject **obj) PyObject *py_in_doubt = err->in_doubt ? Py_True : Py_False; Py_INCREF(py_in_doubt); - PyObject *py_err = PyTuple_New(5); + if (py_commit_status == NULL) { + Py_INCREF(Py_None); + py_commit_status = Py_None; + } + + if (py_abort_status == NULL) { + Py_INCREF(Py_None); + py_abort_status = Py_None; + } + + PyObject *py_err = PyTuple_New(7); PyTuple_SetItem(py_err, PY_EXCEPTION_CODE, py_code); PyTuple_SetItem(py_err, PY_EXCEPTION_MSG, py_message); PyTuple_SetItem(py_err, PY_EXCEPTION_FILE, py_file); PyTuple_SetItem(py_err, PY_EXCEPTION_LINE, py_line); PyTuple_SetItem(py_err, AS_PY_EXCEPTION_IN_DOUBT, py_in_doubt); + PyTuple_SetItem(py_err, AS_PY_EXCEPTION_COMMIT_STATUS, py_commit_status); + PyTuple_SetItem(py_err, AS_PY_EXCEPTION_ABORT_STATUS, py_abort_status); *obj = py_err; } diff --git a/src/main/exception.c b/src/main/exception.c index 738fbfe41..cfda3733b 100644 --- a/src/main/exception.c +++ b/src/main/exception.c @@ -69,8 +69,9 @@ struct exception_def { #define NO_ERROR_CODE 0 // Same order as the tuple of args passed into the exception -const char *const aerospike_err_attrs[] = {"code", "msg", "file", - "line", "in_doubt", NULL}; +const char *const aerospike_err_attrs[] = { + "code", "msg", "file", "line", + "in_doubt", "commit_status", "abort_status", NULL}; const char *const record_err_attrs[] = {"key", "bin", NULL}; const char *const index_err_attrs[] = {"name", NULL}; const char *const udf_err_attrs[] = {"module", "func", NULL}; @@ -395,8 +396,16 @@ void set_aerospike_exc_attrs_using_tuple_of_attrs(PyObject *py_exc, } } -// TODO: idea. Use python dict to map error code to exception void raise_exception(as_error *err) +{ + raise_exception_with_mrt_status(err, NULL, NULL); +} + +// TODO: idea. Use python dict to map error code to exception +// If py_commit_status is NULL, ignore it. Same with py_abort_status +// Steals reference to either status objects +void raise_exception_with_mrt_status(as_error *err, PyObject *py_commit_status, + PyObject *py_abort_status) { PyObject *py_key = NULL, *py_value = NULL; Py_ssize_t pos = 0; @@ -428,8 +437,10 @@ void raise_exception(as_error *err) Py_INCREF(py_value); // Convert C error to Python exception + // Also add commit or abort status to exception, if needed PyObject *py_err = NULL; - error_to_pyobject(err, &py_err); + as_error_and_mrt_status_to_pytuple(err, &py_err, py_commit_status, + py_abort_status); set_aerospike_exc_attrs_using_tuple_of_attrs(py_value, py_err); // Raise exception diff --git a/test/new_tests/test_exceptions.py b/test/new_tests/test_exceptions.py index 1276735e7..6ac68772f 100644 --- a/test/new_tests/test_exceptions.py +++ b/test/new_tests/test_exceptions.py @@ -23,7 +23,9 @@ "msg", "file", "line", - "in_doubt" + "in_doubt", + "commit_status", + "abort_status" ], e.RecordError: [ "key", diff --git a/test/new_tests/test_mrt_functionality.py b/test/new_tests/test_mrt_functionality.py index 4457b4673..9e0dbce0a 100644 --- a/test/new_tests/test_mrt_functionality.py +++ b/test/new_tests/test_mrt_functionality.py @@ -89,8 +89,9 @@ def test_commit_fail(self): } self.as_connection.put(self.keys[0], {self.bin_name: 1}, policy=policy) self.as_connection.abort(mrt) - with pytest.raises(e.TransactionAlreadyAborted): + with pytest.raises(e.TransactionAlreadyAborted) as excinfo: self.as_connection.commit(mrt) + assert excinfo.value.commit_status == aerospike.COMMIT_ALREADY_ABORTED # Test case 10: Issue abort after issung commit. (P1) def test_abort_fail(self): @@ -100,5 +101,6 @@ def test_abort_fail(self): } self.as_connection.put(self.keys[0], {self.bin_name: 1}, policy=policy) self.as_connection.commit(mrt) - with pytest.raises(e.TransactionAlreadyCommitted): + with pytest.raises(e.TransactionAlreadyCommitted) as excinfo: self.as_connection.abort(mrt) + assert excinfo.value.abort_status == aerospike.ABORT_ALREADY_COMMITTED