Skip to content

Commit

Permalink
JSCBC-196: Add support for marshalling CAS values to strings/JSON.
Browse files Browse the repository at this point in the history
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 <mark.nunberg@couchbase.com>
Tested-by: Brett Lawson <brett19@gmail.com>
  • Loading branch information
brett19 committed Feb 9, 2015
1 parent 6418209 commit d9c25ec
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
4 changes: 2 additions & 2 deletions lib/bucket.js
Expand Up @@ -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.');
}
}
};
Expand Down
25 changes: 22 additions & 3 deletions lib/mock/bucket.js
Expand Up @@ -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) {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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.');
}
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/binding.cc
Expand Up @@ -32,13 +32,15 @@ extern "C" {
NanAssignPersistent(lcbErrorKey, NanNew<String>("lcbError"));

Error::Init();
Cas::Init();
DefaultTranscoder::Init();
CouchbaseImpl::Init(target);
}

NODE_MODULE(couchbase_impl, init)
}

Persistent<Function> Cas::casClass;
Persistent<Function> Error::errorClass;
Persistent<String> Error::codeKey;

Expand Down
61 changes: 56 additions & 5 deletions src/cas.cc
Expand Up @@ -16,31 +16,82 @@
*/
#include "couchbase_impl.h"
#include <sstream>
#include <stdlib.h>
using namespace Couchnode;

void Cas::Init() {
NanScope();

Local<FunctionTemplate> t = NanNew<FunctionTemplate>();
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(NanNew<String>("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<String>(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<String>(casStr));
}

NAN_WEAK_CALLBACK(casDtor) {
uint64_t *value = data.GetParameter();
delete value;
}

Handle<Value> Cas::CreateCas(uint64_t cas) {
Local<Object> ret = NanNew<Object>();
Local<Object> ret = casClass->NewInstance();
uint64_t *p = new uint64_t(cas);
ret->SetIndexedPropertiesToExternalArrayData(
p, v8::kExternalUnsignedIntArray, 2);
NanMakeWeakPersistent(ret, p, casDtor);
return ret;
}

bool Cas::GetCas(Handle<Value> obj, uint64_t *p) {
Handle<Object> realObj = obj.As<Object>();
if (!realObj->IsObject()) {
bool _StrToCas(Handle<Value> obj, uint64_t *p) {
if (sscanf(*NanUtf8String(obj->ToString()), "%llu", p) != 1) {
return false;
}
return true;
}

bool _ObjToCas(Handle<Value> obj, uint64_t *p) {
Handle<Object> realObj = obj.As<Object>();
if (realObj->GetIndexedPropertiesExternalArrayDataLength() != 2) {
return false;
}

*p = *(uint64_t*)realObj->GetIndexedPropertiesExternalArrayData();
return true;
}

bool Cas::GetCas(Handle<Value> obj, uint64_t *p) {
NanScope();
if (obj->IsObject()) {
return _ObjToCas(obj, p);
} else if (obj->IsString()) {
return _StrToCas(obj, p);
} else {
return false;
}
}
8 changes: 8 additions & 0 deletions src/cas.h
Expand Up @@ -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<v8::Value>, uint64_t*);
static v8::Handle<v8::Value> CreateCas(uint64_t);

private:
static v8::Persistent<v8::Function> casClass;

};

} // namespace Couchnode
Expand Down
25 changes: 24 additions & 1 deletion test/crud.test.js
Expand Up @@ -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);
});
}

Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit d9c25ec

Please sign in to comment.