From 7f3ee1ddb93c0fa21d5e3c1f1aa2d0f9bdc5bf41 Mon Sep 17 00:00:00 2001 From: Kubo Takehiro Date: Mon, 29 Apr 2019 20:47:51 +0900 Subject: [PATCH] Straight mapping of OCI transaction functions to ruby. --- ext/oci8/apiwrap.yml | 33 ++++++ ext/oci8/extconf.rb | 4 +- ext/oci8/oci8.c | 140 ++++++++++++++++++++++++- ext/oci8/oci8.h | 4 + ext/oci8/oci8lib.c | 1 + ext/oci8/transaction.c | 233 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 ext/oci8/transaction.c diff --git a/ext/oci8/apiwrap.yml b/ext/oci8/apiwrap.yml index 4f668c5b..131a79b6 100644 --- a/ext/oci8/apiwrap.yml +++ b/ext/oci8/apiwrap.yml @@ -867,6 +867,30 @@ OCITransCommit_nb: - OCIError *errhp - ub4 flags +# round trip: 1 +OCITransDetach_nb: + :version: 800 + :args: + - OCISvcCtx *svchp + - OCIError *errhp + - ub4 flags + +# round trip: 1 +OCITransForget_nb: + :version: 800 + :args: + - OCISvcCtx *svchp + - OCIError *errhp + - ub4 flags + +# round trip: 1 +OCITransPrepare_nb: + :version: 800 + :args: + - OCISvcCtx *svchp + - OCIError *errhp + - ub4 flags + # round trip: 1 OCITransRollback: :version: 800 @@ -883,6 +907,15 @@ OCITransRollback_nb: - OCIError *errhp - ub4 flags +# round trip: 1 +OCITransStart_nb: + :version: 800 + :args: + - OCISvcCtx *svchp + - OCIError *errhp + - uword timeout + - ub4 flags + # round trip: 0 ? OCITypeTypeCode: :version: 800 diff --git a/ext/oci8/extconf.rb b/ext/oci8/extconf.rb index 751e9b3a..47dbcde1 100644 --- a/ext/oci8/extconf.rb +++ b/ext/oci8/extconf.rb @@ -70,7 +70,9 @@ def fmt.%(x) "stmt.o", "bind.o", "metadata.o", "attr.o", "lob.o", "oradate.o", "ocinumber.o", "ocidatetime.o", "object.o", "apiwrap.o", - "encoding.o", "oranumber_util.o", "thread_util.o", "util.o"] + "encoding.o", "oranumber_util.o", "thread_util.o", + "transaction.o", + "util.o"] if RUBY_PLATFORM =~ /mswin32|cygwin|mingw32|bccwin32/ $defs << "-DUSE_WIN32_C" diff --git a/ext/oci8/oci8.c b/ext/oci8/oci8.c index 1700264d..ce4be56e 100644 --- a/ext/oci8/oci8.c +++ b/ext/oci8/oci8.c @@ -45,6 +45,54 @@ static VALUE cEnvironment; static VALUE cProcess; static ID id_at_session_handle; static ID id_at_server_handle; +static ID id_at_trans_handle; + +typedef struct { + const char *name; + ub4 val; +} ub4_flag_def_t; + +static ub4 check_ub4_flags(int argc, VALUE *argv, const ub4_flag_def_t *flag_defs, const char *flag_name) +{ + ub4 flags = 0; + int i; + + for (i = 0; i < argc; i++) { + VALUE val = argv[i]; + const char *name; + size_t len; + int j; + + if (SYMBOL_P(val)) { +#ifdef HAVE_RB_SYM2STR + VALUE symstr = rb_sym2str(val); + name = RSTRING_PTR(symstr); + len = RSTRING_LEN(symstr); +#else + name = rb_id2name(SYM2ID(val)); + len = strlen(symname); +#endif + } else { + SafeStringValue(val); + name = RSTRING_PTR(val); + len = RSTRING_LEN(val); + } + for (j = 0; flag_defs[j].name != NULL; j++) { + if (strncmp(name, flag_defs[j].name, len) != 0) { + continue; + } + if (flag_defs[j].name[len] != '\0') { + continue; + } + flags |= flag_defs[j].val; + break; + } + if (flag_defs[j].name == NULL) { + rb_raise(rb_eArgError, "Unknown %s flag: %.*s", flag_name, (int)len, name); + } + } + return flags; +} static VALUE dummy_env_method_missing(int argc, VALUE *argv, VALUE self) { @@ -644,14 +692,27 @@ static VALUE oci8_svcctx_logoff(VALUE self) } /* - * @overload commit + * @overload commit(*flags) * * Commits the transaction. + * + * @param [] */ -static VALUE oci8_commit(VALUE self) +static VALUE oci8_commit(int argc, VALUE *argv, VALUE self) { + static const ub4_flag_def_t flag_defs[] = { + {"two_phase", OCI_TRANS_TWOPHASE}, + {"write_immediate", OCI_TRANS_WRITEIMMED}, + {"write_batch", OCI_TRANS_WRITEBATCH}, + {"write_wait", OCI_TRANS_WRITEWAIT}, + {"write_no_wait", OCI_TRANS_WRITENOWAIT}, + {NULL, 0}, + }; oci8_svcctx_t *svcctx = oci8_get_svcctx(self); - chker2(OCITransCommit_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, OCI_DEFAULT), &svcctx->base); + ub4 flags = check_ub4_flags(argc, argv, flag_defs, "commit"); + + chker2(OCITransCommit_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, flags), + &svcctx->base); return self; } @@ -975,6 +1036,70 @@ static VALUE oci8_set_client_info(VALUE self, VALUE val) return val; } +static VALUE oci8_get_trans_handle(VALUE self) +{ + return rb_ivar_get(self, id_at_trans_handle); +} + +static VALUE oci8_set_trans_handle(VALUE self, VALUE obj) +{ + oci8_svcctx_t *svcctx = oci8_get_svcctx(self); + oci8_base_t *trans = oci8_check_typeddata(obj, &oci8_trans_data_type, 1); + chker2(OCIAttrSet(svcctx->base.hp.svc, OCI_HTYPE_SVCCTX, trans->hp.ptr, 0, OCI_ATTR_TRANS, oci8_errhp), + &svcctx->base); + rb_ivar_set(self, id_at_trans_handle, obj); + return obj; +} + +static VALUE oci8_trans_start(int argc, VALUE *argv, VALUE self) +{ + static const ub4_flag_def_t flag_defs[] = { + {"new", OCI_TRANS_NEW}, + {"tight", OCI_TRANS_TIGHT}, + {"loose", OCI_TRANS_LOOSE}, + {"resume", OCI_TRANS_RESUME}, + {"readonly", OCI_TRANS_READONLY}, + {"serializable", OCI_TRANS_SERIALIZABLE}, + {NULL, 0}, + }; + oci8_svcctx_t *svcctx = oci8_get_svcctx(self); + uword timeout; + ub4 flags; + + if (argc == 0) { + rb_raise(rb_eArgError, "wrong number of arguments (given 0, expected 1+)"); + } + timeout = NUM2UINT(argv[0]); + flags = check_ub4_flags(argc - 1, argv + 1, flag_defs, "trans_start"); + chker2(OCITransStart_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, timeout, flags), + &svcctx->base); + return self; +} + +static VALUE oci8_trans_detach(VALUE self) +{ + oci8_svcctx_t *svcctx = oci8_get_svcctx(self); + chker2(OCITransDetach_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, OCI_DEFAULT), + &svcctx->base); + return self; +} + +static VALUE oci8_trans_prepare(VALUE self) +{ + oci8_svcctx_t *svcctx = oci8_get_svcctx(self); + sword rv = OCITransPrepare_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, OCI_DEFAULT); + chker2(rv, &svcctx->base); + return rv == OCI_SUCCESS ? Qtrue : Qfalse; +} + +static VALUE oci8_trans_forget(VALUE self) +{ + oci8_svcctx_t *svcctx = oci8_get_svcctx(self); + chker2(OCITransForget_nb(svcctx, svcctx->base.hp.svc, oci8_errhp, OCI_DEFAULT), + &svcctx->base); + return self; +} + void Init_oci8(VALUE *out) { VALUE obj; @@ -990,6 +1115,7 @@ void Init_oci8(VALUE *out) cProcess = oci8_define_class_under(cOCI8, "Process", &oci8_process_data_type, oci8_process_alloc); id_at_session_handle = rb_intern("@session_handle"); id_at_server_handle = rb_intern("@server_handle"); + id_at_trans_handle = rb_intern("@trans_handle"); /* setup a dummy environment handle to lazily initialize the environment handle */ obj = rb_obj_alloc(rb_cObject); @@ -1020,7 +1146,7 @@ void Init_oci8(VALUE *out) rb_define_private_method(cOCI8, "server_attach", oci8_server_attach, 2); rb_define_private_method(cOCI8, "session_begin", oci8_session_begin, 2); rb_define_method(cOCI8, "logoff", oci8_svcctx_logoff, 0); - rb_define_method(cOCI8, "commit", oci8_commit, 0); + rb_define_method(cOCI8, "commit", oci8_commit, -1); rb_define_method(cOCI8, "rollback", oci8_rollback, 0); rb_define_method(cOCI8, "non_blocking?", oci8_non_blocking_p, 0); rb_define_method(cOCI8, "non_blocking=", oci8_set_non_blocking, 1); @@ -1035,6 +1161,12 @@ void Init_oci8(VALUE *out) rb_define_method(cOCI8, "module=", oci8_set_module, 1); rb_define_method(cOCI8, "action=", oci8_set_action, 1); rb_define_method(cOCI8, "client_info=", oci8_set_client_info, 1); + rb_define_method(cOCI8, "trans_handle", oci8_get_trans_handle, 0); + rb_define_method(cOCI8, "trans_handle=", oci8_set_trans_handle, 1); + rb_define_method(cOCI8, "trans_start", oci8_trans_start, -1); + rb_define_method(cOCI8, "trans_detach", oci8_trans_detach, 0); + rb_define_method(cOCI8, "trans_prepare", oci8_trans_prepare, 0); + rb_define_method(cOCI8, "trans_forget", oci8_trans_forget, 0); *out = cOCI8; } diff --git a/ext/oci8/oci8.h b/ext/oci8/oci8.h index 03553eb7..63ba1a0d 100644 --- a/ext/oci8/oci8.h +++ b/ext/oci8/oci8.h @@ -515,6 +515,10 @@ VALUE oci8_make_interval_ds(OCIInterval *s); /* object.c */ void Init_oci_object(VALUE mOCI); +/* transaction.c */ +void Init_oci8_transaction(VALUE cOCI8); +extern const oci8_handle_data_type_t oci8_trans_data_type; + /* attr.c */ VALUE oci8_get_rowid_attr(oci8_base_t *base, ub4 attrtype, OCIStmt *stmtp); diff --git a/ext/oci8/oci8lib.c b/ext/oci8/oci8lib.c index 248df958..3cffc463 100644 --- a/ext/oci8/oci8lib.c +++ b/ext/oci8/oci8lib.c @@ -292,6 +292,7 @@ Init_oci8lib() Init_ora_date(); Init_oci_datetime(); Init_oci_object(cOCI8); + Init_oci8_transaction(cOCI8); #ifdef USE_WIN32_C Init_oci8_win32(cOCI8); diff --git a/ext/oci8/transaction.c b/ext/oci8/transaction.c new file mode 100644 index 00000000..76ed9bcc --- /dev/null +++ b/ext/oci8/transaction.c @@ -0,0 +1,233 @@ +/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ +/* + * transaction.c - part of ruby-oci8 + * implement the methods of OCI8::Transaction + * + * Copyright (C) 2019 Kubo Takehiro + * + */ +#include "oci8.h" +#include "xa.h" + +static VALUE cOCI8Trans; +static VALUE cOCI8Xid; + +#define TO_TRANS(obj) oci8_check_typeddata((obj), &oci8_trans_data_type, 1) + +static size_t xid_memsize(const void *ptr) +{ + return sizeof(XID); +} + +static const rb_data_type_t xid_data_type = { + "OCI8::Xid", + {NULL, RUBY_DEFAULT_FREE, xid_memsize,}, +}; + +static VALUE xid_alloc(VALUE klass) +{ + XID *xid; + return TypedData_Make_Struct(klass, XID, &xid_data_type, xid); +} + +static VALUE xid_initialize(VALUE self, VALUE format_id, VALUE gtrid, VALUE bqual) +{ + XID *xid = RTYPEDDATA_DATA(self); + long fmtid = NUM2LONG(format_id); + long gtrid_length; + long bqual_length; + + /* check gtrid */ + StringValue(gtrid); + gtrid_length = RSTRING_LEN(gtrid); + if (gtrid_length < 1 || MAXGTRIDSIZE < gtrid_length) { + rb_raise(rb_eArgError, "Invalid length of global transaction id: %ld", gtrid_length); + } + /* check bqual */ + StringValue(bqual); + bqual_length = RSTRING_LEN(bqual); + if (bqual_length < 1 || MAXBQUALSIZE < bqual_length) { + rb_raise(rb_eArgError, "Invalid length of branch qualifier: %ld", bqual_length); + } + + xid->formatID = fmtid; + xid->gtrid_length = gtrid_length; + xid->bqual_length = bqual_length; + memcpy(xid->data, RSTRING_PTR(gtrid), gtrid_length); + memcpy(xid->data + gtrid_length, RSTRING_PTR(bqual), bqual_length); + return LONG2NUM(xid->formatID); +} + +static VALUE xid_get_format_id(VALUE self) +{ + XID *xid = RTYPEDDATA_DATA(self); + return LONG2NUM(xid->formatID); +} + +static VALUE xid_get_gtrid(VALUE self) +{ + XID *xid = RTYPEDDATA_DATA(self); + VALUE obj = rb_str_new(xid->data, xid->gtrid_length); + if (OBJ_TAINTED(self)) { + OBJ_TAINT(obj); + } + return obj; +} + +static VALUE xid_get_bqual(VALUE self) +{ + XID *xid = RTYPEDDATA_DATA(self); + VALUE obj = rb_str_new(xid->data + xid->gtrid_length, xid->bqual_length); + if (OBJ_TAINTED(self)) { + OBJ_TAINT(obj); + } + return obj; +} + +static VALUE xid_inspect(VALUE self) +{ + XID *xid = RTYPEDDATA_DATA(self); + char gtrid[128]; + char bqual[128]; + int i; +#define TO_HEX(n) ((n) < 9 ? (n) + '0' : (n) - 10 + 'a') + + for (i = 0; i < xid->gtrid_length; i++) { + char x = xid->data[i]; + gtrid[i * 2 + 0] = TO_HEX((x >> 4) & 0x0F); + gtrid[i * 2 + 1] = TO_HEX((x >> 0) & 0x0F); + } + for (i = 0; i < xid->bqual_length; i++) { + char x = xid->data[xid->gtrid_length + i]; + bqual[i * 2 + 0] = TO_HEX((x >> 4) & 0x0F); + bqual[i * 2 + 1] = TO_HEX((x >> 0) & 0x0F); + } + return rb_sprintf("<%s: %ld, %.*s, %.*s>", + rb_obj_classname(self), xid->formatID, + (int)xid->gtrid_length * 2, gtrid, + (int)xid->bqual_length * 2, bqual); +} + +const oci8_handle_data_type_t oci8_trans_data_type = { + { + "OCI8::TransHandle", + { + NULL, + oci8_handle_cleanup, + oci8_handle_size, + }, + &oci8_handle_data_type.rb_data_type, NULL, +#ifdef RUBY_TYPED_WB_PROTECTED + RUBY_TYPED_WB_PROTECTED, +#endif + }, + NULL, + sizeof(oci8_base_t), +}; + +static VALUE oci8_trans_alloc(VALUE klass) +{ + return oci8_allocate_typeddata(klass, &oci8_trans_data_type); +} + +static VALUE oci8_trans_initialize(VALUE self) +{ + oci8_base_t *trans = TO_TRANS(self); + sword rv = OCIHandleAlloc(oci8_envhp, &trans->hp.ptr, OCI_HTYPE_TRANS, 0, NULL); + if (rv != OCI_SUCCESS) + oci8_env_raise(oci8_envhp, rv); + trans->type = OCI_HTYPE_TRANS; + return self; +} + +static VALUE oci8_trans_get_name(VALUE self) +{ + oci8_base_t *trans = TO_TRANS(self); + char *value; + ub4 size = 0; + chkerr(OCIAttrGet(trans->hp.ptr, trans->type, &value, &size, OCI_ATTR_TRANS_NAME, oci8_errhp)); + if (size == 0) { + return Qnil; + } else { + return rb_str_new(value, size); + } +} + +static VALUE oci8_trans_get_timeout(VALUE self) +{ + oci8_base_t *trans = TO_TRANS(self); + ub4 value; + ub4 size = 0; + chkerr(OCIAttrGet(trans->hp.ptr, trans->type, &value, &size, OCI_ATTR_TRANS_TIMEOUT, oci8_errhp)); + return UINT2NUM(value); +} + +static VALUE oci8_trans_get_xid(VALUE self) +{ + oci8_base_t *trans = TO_TRANS(self); + void **value; + ub4 size = 0; + XID *xid; + VALUE obj; + + chkerr(OCIAttrGet(trans->hp.ptr, trans->type, &value, &size, OCI_ATTR_XID, oci8_errhp)); + obj = TypedData_Make_Struct(cOCI8Xid, XID, &xid_data_type, xid); + memcpy(xid, value, sizeof(XID)); + return obj; +} + +static VALUE oci8_trans_set_name(VALUE self, VALUE name) +{ + oci8_base_t *trans = TO_TRANS(self); + void *val = NULL; + ub4 size = 0; + + if (!NIL_P(name)) { + SafeStringValue(name); + val = RSTRING_PTR(name); + size = RSTRING_LEN(name); + } + chkerr(OCIAttrSet(trans->hp.ptr, trans->type, val, size, OCI_ATTR_TRANS_NAME, oci8_errhp)); + return self; +} + +static VALUE oci8_trans_set_timeout(VALUE self, VALUE val) +{ + oci8_base_t *trans = TO_TRANS(self); + ub4 value = NUM2UINT(val); + chkerr(OCIAttrSet(trans->hp.ptr, trans->type, &value, sizeof(value), OCI_ATTR_TRANS_TIMEOUT, oci8_errhp)); + return self; +} + +static VALUE oci8_trans_set_xid(VALUE self, VALUE val) +{ + oci8_base_t *trans = TO_TRANS(self); + XID *xid; + + if (!RTEST(rb_obj_is_kind_of(val, cOCI8Xid))) { + rb_raise(rb_eTypeError, "expect OCI8::Xid but %s", rb_class2name(CLASS_OF(val))); + } + xid = RTYPEDDATA_DATA(val); + chkerr(OCIAttrSet(trans->hp.ptr, trans->type, xid, sizeof(XID), OCI_ATTR_XID, oci8_errhp)); + return self; +} + +void Init_oci8_transaction(VALUE cOCI8) +{ + cOCI8Xid = rb_define_class_under(cOCI8, "Xid", rb_cObject); + rb_define_alloc_func(cOCI8Xid, xid_alloc); + rb_define_method(cOCI8Xid, "initialize", xid_initialize, 3); + rb_define_method(cOCI8Xid, "format_id", xid_get_format_id, 0); + rb_define_method(cOCI8Xid, "gtrid", xid_get_gtrid, 0); + rb_define_method(cOCI8Xid, "bqual", xid_get_bqual, 0); + rb_define_method(cOCI8Xid, "inspect", xid_inspect, 0); + + cOCI8Trans = oci8_define_class_under(cOCI8, "TransHandle", &oci8_trans_data_type, oci8_trans_alloc); + rb_define_method(cOCI8Trans, "initialize", oci8_trans_initialize, 0); + rb_define_method(cOCI8Trans, "name", oci8_trans_get_name, 0); + rb_define_method(cOCI8Trans, "timeout", oci8_trans_get_timeout, 0); + rb_define_method(cOCI8Trans, "xid", oci8_trans_get_xid, 0); + rb_define_method(cOCI8Trans, "name=", oci8_trans_set_name, 1); + rb_define_method(cOCI8Trans, "timeout=", oci8_trans_set_timeout, 1); + rb_define_method(cOCI8Trans, "xid=", oci8_trans_set_xid, 1); +}