From d9c25ec2c8bb09e7bab25a5175f882d5c2006df4 Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Mon, 9 Feb 2015 17:03:13 -0400 Subject: [PATCH] JSCBC-196: Add support for marshalling CAS values to strings/JSON. We also support passing of string CAS values to all operation methods that accept a CAS value, this allows you to RTT CAS values to the client, or use CAS values provided through N1QL. Change-Id: I98d34b93afbd5d8635446e3fe81a8dbdeeeae364 Reviewed-on: http://review.couchbase.org/46609 Reviewed-by: Mark Nunberg Tested-by: Brett Lawson --- lib/bucket.js | 4 +-- lib/mock/bucket.js | 25 ++++++++++++++++--- src/binding.cc | 2 ++ src/cas.cc | 61 ++++++++++++++++++++++++++++++++++++++++++---- src/cas.h | 8 ++++++ test/crud.test.js | 25 ++++++++++++++++++- 6 files changed, 114 insertions(+), 11 deletions(-) diff --git a/lib/bucket.js b/lib/bucket.js index dcd13333..4ddf4532 100644 --- a/lib/bucket.js +++ b/lib/bucket.js @@ -803,8 +803,8 @@ Bucket.prototype._checkExpiryOption = function(options) { */ Bucket.prototype._checkCasOption = function(options) { if (options.cas !== undefined) { - if (typeof options.cas !== 'object') { - throw new TypeError('cas option needs to be a CAS object.'); + if (typeof options.cas !== 'object' && typeof options.cas !== 'string') { + throw new TypeError('cas option needs to be a CAS object or string.'); } } }; diff --git a/lib/mock/bucket.js b/lib/mock/bucket.js index 31a657c0..7e160e59 100644 --- a/lib/mock/bucket.js +++ b/lib/mock/bucket.js @@ -12,10 +12,23 @@ var errs = require('../errors'); // Mock version should match the latest fully supported version of couchnode. var MOCK_VERSION = '2.0.0'; +function MockCouchbaseCas(idx) { + this.x = idx; +} +MockCouchbaseCas.prototype.toString = function() { + return this.x.toString(10); +}; +MockCouchbaseCas.prototype.toJSON = function() { + return this.x.toString(10); +}; +MockCouchbaseCas.prototype.inspect = function() { + return 'MockCouchbaseCas<' + this.x + '>'; +}; + var casCounter = 0; /* istanbul ignore next */ function _createCas() { - return {x:casCounter++}; + return new MockCouchbaseCas(casCounter++); } /* istanbul ignore next */ function _compareCas(a, b) { @@ -24,6 +37,12 @@ function _compareCas(a, b) { } else if (!a && b) { return false; } else { + if (typeof a === 'string') { + a = {x: parseInt(a, 10)}; + } + if (typeof b === 'string') { + b = {x: parseInt(b, 10)}; + } return a.x === b.x; } } @@ -244,8 +263,8 @@ MockBucket.prototype._checkExpiryOption = function(options) { MockBucket.prototype._checkCasOption = function(options) { if (options.cas !== undefined) { - if (typeof options.cas !== 'object') { - throw new TypeError('cas option needs to be a CAS object.'); + if (typeof options.cas !== 'object' && typeof options.cas !== 'string') { + throw new TypeError('cas option needs to be a CAS object or string.'); } } }; diff --git a/src/binding.cc b/src/binding.cc index 2a7142b8..76a689c3 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -32,6 +32,7 @@ extern "C" { NanAssignPersistent(lcbErrorKey, NanNew("lcbError")); Error::Init(); + Cas::Init(); DefaultTranscoder::Init(); CouchbaseImpl::Init(target); } @@ -39,6 +40,7 @@ extern "C" { NODE_MODULE(couchbase_impl, init) } +Persistent Cas::casClass; Persistent Error::errorClass; Persistent Error::codeKey; diff --git a/src/cas.cc b/src/cas.cc index 02e91fdd..f703dbf5 100644 --- a/src/cas.cc +++ b/src/cas.cc @@ -16,15 +16,52 @@ */ #include "couchbase_impl.h" #include +#include using namespace Couchnode; +void Cas::Init() { + NanScope(); + + Local t = NanNew(); + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(NanNew("CouchbaseCas")); + + NODE_SET_PROTOTYPE_METHOD(t, "toString", fnToString); + NODE_SET_PROTOTYPE_METHOD(t, "toJSON", fnToString); + NODE_SET_PROTOTYPE_METHOD(t, "inspect", fnInspect); + + NanAssignPersistent(casClass, t->GetFunction()); +} + +NAN_METHOD(Cas::fnToString) +{ + uint64_t casVal = 0; + char casStr[24] = ""; + NanScope(); + + Cas::GetCas(args.This(), &casVal); + sprintf(casStr, "%llu", casVal); + NanReturnValue(NanNew(casStr)); +} + +NAN_METHOD(Cas::fnInspect) +{ + uint64_t casVal = 0; + char casStr[14+24] = ""; + NanScope(); + + Cas::GetCas(args.This(), &casVal); + sprintf(casStr, "CouchbaseCas<%llu>", casVal); + NanReturnValue(NanNew(casStr)); +} + NAN_WEAK_CALLBACK(casDtor) { uint64_t *value = data.GetParameter(); delete value; } Handle Cas::CreateCas(uint64_t cas) { - Local ret = NanNew(); + Local ret = casClass->NewInstance(); uint64_t *p = new uint64_t(cas); ret->SetIndexedPropertiesToExternalArrayData( p, v8::kExternalUnsignedIntArray, 2); @@ -32,15 +69,29 @@ Handle Cas::CreateCas(uint64_t cas) { return ret; } -bool Cas::GetCas(Handle obj, uint64_t *p) { - Handle realObj = obj.As(); - if (!realObj->IsObject()) { +bool _StrToCas(Handle obj, uint64_t *p) { + if (sscanf(*NanUtf8String(obj->ToString()), "%llu", p) != 1) { return false; } + return true; +} + +bool _ObjToCas(Handle obj, uint64_t *p) { + Handle realObj = obj.As(); if (realObj->GetIndexedPropertiesExternalArrayDataLength() != 2) { return false; } - *p = *(uint64_t*)realObj->GetIndexedPropertiesExternalArrayData(); return true; } + +bool Cas::GetCas(Handle obj, uint64_t *p) { + NanScope(); + if (obj->IsObject()) { + return _ObjToCas(obj, p); + } else if (obj->IsString()) { + return _StrToCas(obj, p); + } else { + return false; + } +} diff --git a/src/cas.h b/src/cas.h index 0bc8332d..19f869d8 100644 --- a/src/cas.h +++ b/src/cas.h @@ -25,8 +25,16 @@ namespace Couchnode class Cas { public: + static void Init(); + static NAN_METHOD(fnToString); + static NAN_METHOD(fnInspect); + static bool GetCas(v8::Handle, uint64_t*); static v8::Handle CreateCas(uint64_t); + +private: + static v8::Persistent casClass; + }; } // namespace Couchnode diff --git a/test/crud.test.js b/test/crud.test.js index c2f5e93c..32652fe3 100644 --- a/test/crud.test.js +++ b/test/crud.test.js @@ -71,7 +71,13 @@ describe('#crud', function () { it('should fail with an invalid cas option', function () { assert.throws(function () { fn(H.key(), 'frank', H.noCallback()); - }, TypeError); + }, Error); + }); + + it('should fail with an invalid cas option object', function () { + assert.throws(function () { + fn(H.key(), {}, H.noCallback()); + }, Error); }); } @@ -822,6 +828,23 @@ describe('#crud', function () { })); })); }); + + it('should work with string cas values', function(done) { + var key = H.key(); + H.b.upsert(key, 'test1', H.okCallback(function(res) { + var strCas = res.cas.toString(); + var jsonCas = JSON.stringify(res.cas); + assert(jsonCas); + assert(strCas); + assert(jsonCas === '"'+strCas+'"'); + H.b.upsert(key, 'test2', {cas:'14'}, function(err) { + assert(err); + H.b.upsert(key, 'test3', {cas:strCas}, H.okCallback(function(res) { + done(); + })); + }); + })); + }); } describe('#RealBucket', allTests.bind(this, harness));