/* vim: ft=c et ts=8 sts=4 sw=4 cino= * * Copyright 2011, 2012 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "couchbase_ext.h" void cb_arithmetic_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_arithmetic_resp_t *resp) { struct cb_context_st *ctx = (struct cb_context_st *)cookie; struct cb_bucket_st *bucket = ctx->bucket; VALUE cas, key, val, exc, res; ID o; ctx->nqueries--; key = STR_NEW((const char*)resp->v.v0.key, resp->v.v0.nkey); cb_strip_key_prefix(bucket, key); cas = resp->v.v0.cas > 0 ? ULL2NUM(resp->v.v0.cas) : Qnil; o = ctx->arith > 0 ? cb_sym_increment : cb_sym_decrement; exc = cb_check_error(error, "failed to perform arithmetic operation", key); if (exc != Qnil) { rb_ivar_set(exc, cb_id_iv_cas, cas); rb_ivar_set(exc, cb_id_iv_operation, o); ctx->exception = exc; } val = ULL2NUM(resp->v.v0.value); if (bucket->async) { /* asynchronous */ if (ctx->proc != Qnil) { res = rb_class_new_instance(0, NULL, cb_cResult); rb_ivar_set(res, cb_id_iv_error, exc); rb_ivar_set(res, cb_id_iv_operation, o); rb_ivar_set(res, cb_id_iv_key, key); rb_ivar_set(res, cb_id_iv_value, val); rb_ivar_set(res, cb_id_iv_cas, cas); cb_proc_call(bucket, ctx->proc, 1, res); } } else { /* synchronous */ if (NIL_P(exc)) { if (ctx->extended) { rb_hash_aset(ctx->rv, key, rb_ary_new3(2, val, cas)); } else { rb_hash_aset(ctx->rv, key, val); } } } if (ctx->nqueries == 0) { ctx->proc = Qnil; if (bucket->async) { cb_context_free(ctx); } } (void)handle; } static inline VALUE cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self) { struct cb_bucket_st *bucket = DATA_PTR(self); struct cb_context_st *ctx; VALUE rv, proc, exc; lcb_error_t err; struct cb_params_st params; if (!cb_bucket_connected_bang(bucket, sign > 0 ? cb_sym_increment : cb_sym_decrement)) { return Qnil; } memset(¶ms, 0, sizeof(struct cb_params_st)); rb_scan_args(argc, argv, "0*&", ¶ms.args, &proc); if (!bucket->async && proc != Qnil) { rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks"); } params.type = cb_cmd_arith; params.bucket = bucket; params.cmd.arith.sign = sign; cb_params_build(¶ms); ctx = cb_context_alloc_common(bucket, proc, params.cmd.arith.num); ctx->extended = params.cmd.arith.extended; err = lcb_arithmetic(bucket->handle, (const void *)ctx, params.cmd.arith.num, params.cmd.arith.ptr); cb_params_destroy(¶ms); exc = cb_check_error(err, "failed to schedule arithmetic request", Qnil); if (exc != Qnil) { cb_context_free(ctx); rb_exc_raise(exc); } bucket->nbytes += params.npayload; if (bucket->async) { cb_maybe_do_loop(bucket); return Qnil; } else { if (ctx->nqueries > 0) { /* we have some operations pending */ lcb_wait(bucket->handle); } exc = ctx->exception; rv = ctx->rv; cb_context_free(ctx); if (exc != Qnil) { rb_exc_raise(exc); } if (params.cmd.store.num > 1) { return rv; /* return as a hash {key => cas, ...} */ } else { VALUE vv = Qnil; rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv); return vv; } return rv; } } /* * Increment the value of an existing numeric key * * @since 1.0.0 * * The increment methods allow you to increase a given stored integer * value. These are the incremental equivalent of the decrement operations * and work on the same basis; updating the value of a key if it can be * parsed to an integer. The update operation occurs on the server and is * provided at the protocol level. This simplifies what would otherwise be a * two-stage get and set operation. * * @note that server treats values as unsigned numbers, therefore if * you try to store negative number and then increment or decrement it * will cause overflow. (see "Integer overflow" example below) * * @overload incr(key, delta = 1, options = {}) * @param key [String, Symbol] Key used to reference the value. * @param delta [Fixnum] Integer (up to 64 bits) value to increment * @param options [Hash] Options for operation. * @option options [true, false] :create (false) If set to +true+, it will * initialize the key with zero value and zero flags (use +:initial+ * option to set another initial value). Note: it won't increment the * missing value. * @option options [Fixnum] :initial (0) Integer (up to 64 bits) value for * missing key initialization. This option imply +:create+ option is * +true+. * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key. * Values larger than 30*24*60*60 seconds (30 days) are interpreted as * absolute times (from the epoch). This option ignored for existent * keys. * @option options [true, false] :extended (false) If set to +true+, the * operation will return tuple +[value, cas]+, otherwise (by default) it * returns just value. * * @yieldparam ret [Result] the result of operation in asynchronous mode * (valid attributes: +error+, +operation+, +key+, +value+, +cas+). * * @return [Fixnum] the actual value of the key. * * @raise [Couchbase::Error::NotFound] if key is missing and +:create+ * option isn't +true+. * * @raise [Couchbase::Error::DeltaBadval] if the key contains non-numeric * value * * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect}) * * @raise [ArgumentError] when passing the block in synchronous mode * * @example Increment key by one * c.incr("foo") * * @example Increment key by 50 * c.incr("foo", 50) * * @example Increment key by one OR initialize with zero * c.incr("foo", :create => true) #=> will return old+1 or 0 * * @example Increment key by one OR initialize with three * c.incr("foo", 50, :initial => 3) #=> will return old+50 or 3 * * @example Increment key and get its CAS value * val, cas = c.incr("foo", :extended => true) * * @example Integer overflow * c.set("foo", -100) * c.get("foo") #=> -100 * c.incr("foo") #=> 18446744073709551517 * # but it might look like working * c.set("foo", -2) * c.get("foo") #=> -2 * c.incr("foo", 2) #=> 0 * # on server: * # // UINT64_MAX is 18446744073709551615 * # uint64_t num = atoll("-2"); * # // num is 18446744073709551614 * # num += 2 * # // num is 0 * * @example Asynchronous invocation * c.run do * c.incr("foo") do |ret| * ret.operation #=> :increment * ret.success? #=> true * ret.key #=> "foo" * ret.value * ret.cas * end * end * */ VALUE cb_bucket_incr(int argc, VALUE *argv, VALUE self) { return cb_bucket_arithmetic(+1, argc, argv, self); } /* * Decrement the value of an existing numeric key * * @since 1.0.0 * * The decrement methods reduce the value of a given key if the * corresponding value can be parsed to an integer value. These operations * are provided at a protocol level to eliminate the need to get, update, * and reset a simple integer value in the database. It supports the use of * an explicit offset value that will be used to reduce the stored value in * the database. * * @note that server values stored and transmitted as unsigned numbers, * therefore if you try to decrement negative or zero key, you will always * get zero. * * @overload decr(key, delta = 1, options = {}) * @param key [String, Symbol] Key used to reference the value. * @param delta [Fixnum] Integer (up to 64 bits) value to decrement * @param options [Hash] Options for operation. * @option options [true, false] :create (false) If set to +true+, it will * initialize the key with zero value and zero flags (use +:initial+ * option to set another initial value). Note: it won't decrement the * missing value. * @option options [Fixnum] :initial (0) Integer (up to 64 bits) value for * missing key initialization. This option imply +:create+ option is * +true+. * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key. * Values larger than 30*24*60*60 seconds (30 days) are interpreted as * absolute times (from the epoch). This option ignored for existent * keys. * @option options [true, false] :extended (false) If set to +true+, the * operation will return tuple +[value, cas]+, otherwise (by default) it * returns just value. * * @yieldparam ret [Result] the result of operation in asynchronous mode * (valid attributes: +error+, +operation+, +key+, +value+, +cas+). * * @return [Fixnum] the actual value of the key. * * @raise [Couchbase::Error::NotFound] if key is missing and +:create+ * option isn't +true+. * * @raise [Couchbase::Error::DeltaBadval] if the key contains non-numeric * value * * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect}) * * @raise [ArgumentError] when passing the block in synchronous mode * * @example Decrement key by one * c.decr("foo") * * @example Decrement key by 50 * c.decr("foo", 50) * * @example Decrement key by one OR initialize with zero * c.decr("foo", :create => true) #=> will return old-1 or 0 * * @example Decrement key by one OR initialize with three * c.decr("foo", 50, :initial => 3) #=> will return old-50 or 3 * * @example Decrement key and get its CAS value * val, cas = c.decr("foo", :extended => true) * * @example Decrementing zero * c.set("foo", 0) * c.decrement("foo", 100500) #=> 0 * * @example Decrementing negative value * c.set("foo", -100) * c.decrement("foo", 100500) #=> 0 * * @example Asynchronous invocation * c.run do * c.decr("foo") do |ret| * ret.operation #=> :decrement * ret.success? #=> true * ret.key #=> "foo" * ret.value * ret.cas * end * end * */ VALUE cb_bucket_decr(int argc, VALUE *argv, VALUE self) { return cb_bucket_arithmetic(-1, argc, argv, self); }