...
 
Commits (14)
0.2.3
- Added documentation for public api's
- new namespace option added that namespaces all your keys.
- minor parser fixes and some thrown errors.
0.2.2
- Support for touch command #86
- Fix for chunked responses from the server #84
......
......@@ -126,6 +126,252 @@ Memcached.config.poolSize = 25;
### Public methods
#### memcached.touch(key, lifetime, callback);
Touches the given key.
**Arguments**
`key`: **String** The key
`lifetime`: **Number** After how long should the key expire.
`callback`: **Function**
```js
memcached.touch('key', 10, function (err) {
// stuff
});
```
#### memcached.get(key, callback);
Get the value for the given key.
**Arguments**
`key`: **String**, the key
`callback`: **Function**, the callback.
```js
memcached.get('foo', function (err, data) {
console.log(data);
});
```
#### memcached.gets(key, callback);
Get the value and the CAS id.
**Arguments**
`key`: **String**, the key
`callback`: **Function**, the callback.
```js
memcached.gets('foo', function (err, data) {
console.log(data.foo);
console.log(data.cas);
// Please note that the data is stored under the name of the given key.
});
```
#### memcached.getMulti(keys, callback);
Retrieves a bunch of values from multiple keys.
**Arguments**
`keys`: **Array**, all the keys that needs to be fetched
`callback`: **Function**, the callback.
```js
memcached.getMulti(['foo', 'bar'], function (err, data) {
console.log(data.foo);
console.log(data.bar);
});
```
#### memcached.set(key, lifetime, value, callback);
Stores a new value in Memcached.
**Arguments**
`key`: **String** the name of the key
`lifetime`: **Number**, how long the data needs to be stored
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`callback`: **Function** the callback
```js
memcached.set('foo', 10, 'bar', function (err) {
// stuff
});
```
#### memcached.replace(key, lifetime, value, callback);
Replaces the value in memcached.
**Arguments**
`key`: **String** the name of the key
`lifetime`: **Number**, how long the data needs to be replaced
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`callback`: **Function** the callback
```js
memcached.replaces('foo', 10, 'bar', function (err) {
// stuff
});
```
#### memcached.add(key, lifetime, value, callback);
Add the value, only if it's in memcached already.
**Arguments**
`key`: **String** the name of the key
`lifetime`: **Number**, how long the data needs to be replaced
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`callback`: **Function** the callback
```js
memcached.add('foo', 10, 'bar', function (err) {
// stuff
});
```
#### memcached.cas(key, value, cas, lifetime, callback);
Add the value, only if it matches the given CAS value.
**Arguments**
`key`: **String** the name of the key
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`lifetime`: **Number**, how long the data needs to be replaced
`cas`: **String** the CAS value
`callback`: **Function** the callback
```js
memcached.gets('foo', function (err, data) {
memcached.cas('foo', 'bar', data.cas, 10, function (err) {
// stuff
});
});
```
#### memcached.append(key, value, callback);
Add the given value string to the value of an existing item.
**Arguments**
`key`: **String** the name of the key
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`callback`: **Function** the callback
```js
memcached.append('foo', 'bar', function (err) {
// stuff
});
```
#### memcached.prepend(key, value, callback);
Add the given value string to the value of an existing item.
**Arguments**
`key`: **String** the name of the key
`value`: **Mixed** Either a buffer, JSON, number or string that you want to store.
`callback`: **Function** the callback
```js
memcached.preprend('foo', 'bar', function (err) {
// stuff
});
```
#### memcached.incr(key, amount, callback);
Increment a given key.
**Arguments**
`key`: **String** the name of the key
`amount`: **Number** The incremention
`callback`: **Function** the callback
```js
memcached.incr('foo', 10, function (err) {
// stuff
});
```
#### memcached.decr(key, amount, callback);
Decrement a given kehy.
**Arguments**
`key`: **String** the name of the key
`amount`: **Number** The incremention
`callback`: **Function** the callback
```js
memcached.incr('foo', 10, function (err) {
// stuff
});
```
#### memcached.del(key, callback);
Remove the key from memcached.
**Arguments**
`key`: **String** the name of the key
`callback`: **Function** the callback
```js
memcached.del('foo', function (err) {
// stuff
});
```
#### memcached.version(callback);
Retrieves the version number of your server.
#### memcached.flush(callback);
Flushes the memcached server.
#### memcached.stats(callback);
Retrieves stats from your memcached server.
#### memcached.settings(callback);
Retrieves your `stats settings`.
#### memcached.slabs(callback);
Retrieves `stats slabs` information.
#### memcached.items(callback);
Retrieves `stats items` information.
#### memcached.cachedump(server, slabid, number, callback);
Inspect cache, see examples for a detailed explaination.
#### memcached.end();
Closes all active memcached connections.
### Private methods
The following methods are intended for private usage:
......@@ -154,6 +400,7 @@ memcached.connect( '192.168.0.103:11212', function( err, conn ){
```
---------------------------------------
#### .multi
A small wrapper function that makes it easier to query multiple Memcached
servers. It will return the location for each key or the complete list of
......
......@@ -83,6 +83,7 @@ Client.config = {
, remove: false // remove server if dead if false, we will attempt to reconnect
, redundancy: false // allows you do re-distribute the keys over a x amount of servers
, keyCompression: true // compress keys if they are to large (md5)
, namespace: '' // sentinel to prepend to all memcache keys for namespacing the entries
, debug: false // Output the commands and responses
};
......@@ -179,6 +180,11 @@ Client.config = {
this.connections[server].pull(callback);
};
// Exposes buffer to test-suite
memcached.buffer = function buffer() {
return privates.buffer.apply(this, arguments);
};
// Creates a multi stream, so it's easier to query agains multiple memcached
// servers.
memcached.multi = function memcachedMulti(keys, callback) {
......@@ -506,6 +512,7 @@ Client.config = {
function resultSetIsEmpty(resultSet) {
return !resultSet || (resultSet.length === 1 && !resultSet[0]);
}
// Parses down result sets
privates.resultParsers = {
// combines the stats array, in to an object
......@@ -598,10 +605,9 @@ Client.config = {
});
}
// Fix zero-line endings in the middle
// Fix zero-line endings in the middle
var chunkLength = (chunks.length-1);
if( chunks[chunkLength].length == 0 )
chunks.splice(chunkLength, 1)
if (chunks[chunkLength].length === 0) chunks.splice(chunkLength, 1);
S.responseBuffer = ""; // clear!
this.rawDataReceived(S, S.bufferArray = S.bufferArray.concat(chunks));
......@@ -719,7 +725,7 @@ Client.config = {
// Small wrapper function that only executes errors when we have a callback
privates.errorResponse = function errorResponse(error, callback) {
if (typeof callback === 'function') {
this.makeCallback(callback,error, false);
memcached.makeCallback(callback,error, false);
}
return false;
......@@ -727,28 +733,30 @@ Client.config = {
// This is where the actual Memcached API layer begins:
memcached.touch = function touch(key, lifetime, callback) {
var fullkey = this.namespace + key;
this.command(function touchCommand() {
return {
key: key
key: fullkey
, callback: callback
, lifetime: lifetime
, validate: [['key', String], ['lifetime', Number], ['callback', Function]]
, type: 'touch'
, command: ['touch', key, lifetime].join(' ')
}
})
, command: ['touch', fullkey, lifetime].join(' ')
};
});
};
memcached.get = function get(key, callback) {
if (Array.isArray(key)) return this.getMulti.apply(this, arguments);
var fullkey = this.namespace + key;
this.command(function getCommand(noreply) {
return {
key: key
key: fullkey
, callback: callback
, validate: [['key', String], ['callback', Function]]
, type: 'get'
, command: 'get ' + key
, command: 'get ' + fullkey
};
});
};
......@@ -756,13 +764,14 @@ Client.config = {
// the difference between get and gets is that gets, also returns a cas value
// and gets doesn't support multi-gets at this moment.
memcached.gets = function get(key, callback) {
var fullkey = this.namespace + key;
this.command(function getCommand(noreply) {
return {
key: key
key: fullkey
, callback: callback
, validate: [['key', String], ['callback', Function]]
, type: 'gets'
, command: 'gets ' + key
, command: 'gets ' + fullkey
};
});
};
......@@ -774,6 +783,10 @@ Client.config = {
, errors = []
, calls;
if (memcached.namespace.length) keys = keys.map(function compile(key){
return memcached.namespace + key;
});
// handle multiple responses and cache them untill we receive all.
function handle(err, results) {
if (err) {
......@@ -782,7 +795,15 @@ Client.config = {
// add all responses to the array
(Array.isArray(results) ? results : [results]).forEach(function each(value) {
Utils.merge(responses, value);
if (memcached.namespace.length) {
var ns_key = Object.keys(value)[0]
, newvalue = {};
newvalue[ns_key.replace(memcached.namespace, '')] = value[ns_key];
Utils.merge(responses, newvalue);
} else {
Utils.merge(responses, value);
}
});
if (!--calls){
......@@ -810,6 +831,7 @@ Client.config = {
// do not require a lifetime and a flag, but the memcached server is smart
// enough to ignore those.
privates.setters = function setters(type, validate, key, value, lifetime, callback, cas) {
var fullkey = this.namespace + key;
var flag = 0
, valuetype = typeof value
, length;
......@@ -834,7 +856,7 @@ Client.config = {
this.command(function settersCommand(noreply) {
return {
key: key
key: fullkey
, callback: callback
, lifetime: lifetime
, value: value
......@@ -842,7 +864,7 @@ Client.config = {
, validate: validate
, type: type
, redundancyEnabled: false
, command: [type, key, flag, lifetime, length].join(' ') +
, command: [type, fullkey, flag, lifetime, length].join(' ') +
(cas ? ' ' + cas : '') +
(noreply ? NOREPLY : '') +
LINEBREAK + value
......@@ -990,7 +1012,7 @@ Client.config = {
// multi calls should ALWAYS return an array!
if (!--calls) {
callback(errors && errors.length ? errors.pop() : undefined, responses )
callback(errors && errors.length ? errors.pop() : undefined, responses);
}
}
......
{
"name": "memcached"
, "version": "0.2.2"
, "version": "0.2.3"
, "author": "Arnout Kazemier"
, "description": "A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future."
, "main": "index"
......
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
......@@ -13,23 +14,23 @@ global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(
* Expresso test suite for all `add` related
* memcached commands
*/
describe("Memcached ADD", function() {
describe('Memcached ADD', function () {
/**
* Make sure that adding a key which already exists returns an error.
*/
it("fail to add an already existing key", function(done) {
it('fail to add an already existing key', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.add("test:" + testnr, message, 1000, function(error, answer){
memcached.add('test:' + testnr, message, 1000, function (error, answer) {
++callbacks;
assert.ok(error);
......@@ -37,9 +38,7 @@ describe("Memcached ADD", function() {
memcached.end(); // close connections
assert.equal(callbacks, 2);
done();
});
});
});
});
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, common = require('./common')
, Memcached = require('../');
......@@ -12,32 +13,32 @@ global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(
* Expresso test suite for all `get` related
* memcached commands
*/
describe("Memcached CAS", function() {
describe('Memcached CAS', function () {
/**
* For a proper CAS update in memcached you will need to know the CAS value
* of a given key, this is done by the `gets` command. So we will need to make
* sure that a `cas` key is given.
*/
it("set and gets for cas result", function(done) {
it('set and gets for cas result', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(typeof answer === 'object');
assert.ok(!!answer.cas);
answer["test:" + testnr].should.eql(message);
answer['test:' + testnr].should.eql(message);
memcached.end(); // close connections
assert.equal(callbacks, 2);
......@@ -49,30 +50,30 @@ describe("Memcached CAS", function() {
/**
* Create a successful cas update, so we are sure we send a cas request correctly.
*/
it("successful cas update", function(done) {
it('successful cas update', function(done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer.cas);
// generate new message for the cas update
message = common.alphabet(256);
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){
memcached.cas('test:' + testnr, message, answer.cas, 1000, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer);
memcached.get("test:" + testnr, function(error, answer){
memcached.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
......@@ -81,7 +82,7 @@ describe("Memcached CAS", function() {
memcached.end(); // close connections
assert.equal(callbacks, 4);
done();
})
});
});
});
});
......@@ -91,33 +92,33 @@ describe("Memcached CAS", function() {
* Create a unsuccessful cas update, which would indicate that the server has changed
* while we where doing nothing.
*/
it("unsuccessful cas update", function(done) {
it('unsuccessful cas update', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer.cas);
// generate new message
message = common.alphabet(256);
memcached.set("test:" + testnr, message, 1000, function(){
memcached.set('test:' + testnr, message, 1000, function () {
++callbacks;
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){
memcached.cas('test:' + testnr, message, answer.cas, 1000, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!answer);
memcached.get("test:" + testnr, function(error, answer){
memcached.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
......
......@@ -334,6 +334,27 @@ describe("Memcached GET SET", function() {
});
});
/**
* Set maximum amount of data (1MB), should trigger error, not crash.
*/
it("set maximum data and check for correct error handling", function(done) {
var memcached = new Memcached(common.servers.single)
, message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt').toString()
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, new Array(100).join(message), 1000, function(error, ok){
++callbacks;
assert.equal(error, 'Error: The length of the value is greater than 1048576');
ok.should.be.false;
memcached.end(); // close connections
assert.equal(callbacks, 1);
done();
});
});
/**
* Not only small strings, but also large strings should be processed
* without any issues.
......
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
, Memcached = require('../');
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();
/**
* Expresso test suite for all `get` related
* memcached commands
*/
describe('Memcached tests with Namespaces', function () {
/**
* Make sure that the string that we send to the server is correctly
* stored and retrieved. We will be storing random strings to ensure
* that we are not retrieving old data.
*/
it("set with one namespace and verify it can't be read in another", function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
// Load an non-namespaced entry to memcached
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
var memcachedOther = new Memcached(common.servers.single, {
namespace: 'mySegmentedMemcached:'
});
// Try to load that memcache key with the namespace prepended - this should fail
memcachedOther.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
answer.should.be.false;
// OK, now let's put it in with the namespace prepended
memcachedOther.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
// Now when we request it back, it should be there
memcachedOther.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(typeof answer === 'string');
answer.should.eql(message);
memcachedOther.end(); // close connections
assert.equal(callbacks, 4);
done();
});
});
});
});
});
it('set, set, and multiget with custom namespace', function (done) {
var memcached = new Memcached(common.servers.single, {
namespace: 'mySegmentedMemcached:'
})
, callbacks = 0;
// Load two namespaced variables into memcached
memcached.set('test1', 'test1answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.set('test2', 'test2answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.get(['test1', 'test2'], function (error, answer) {
++callbacks;
assert.ok(typeof answer === 'object');
answer.test1.should.eql('test1answer');
answer.test2.should.eql('test2answer');
memcached.end(); // close connections
assert.equal(callbacks, 3);
done();
});
});
});
});
});
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
, Memcached = require('../');
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();
/**
* Expresso test suite for all `parser` related
* memcached commands
*/
describe('Memcached parser', function() {
it('chunked response', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, chunks = []
, chunk = 'VALUE tests::#{key} 2 {length}'
, chunkJSON = JSON.stringify({
lines: []
, message: message
, id: null
})
, testnr = ++global.testnumbers
, callbacks = 0;
// Build up our tests
var S = {
responseBuffer: ''
, bufferArray: []
, metaData: []
, streamID: 0
};
// Build up our chunk data
chunks.push(chunk.replace('{key}', 1).replace('{length}', chunkJSON.length));
chunks.push(chunkJSON);
chunks.push(chunk.replace('{key}', 2).replace('{length}', chunkJSON.length));
// Insert first chunk
memcached.buffer(S, chunks.join('\r\n') +'\r\n');
// We check for bufferArray length otherwise it will crash on 'SyntaxError: Unexpected token V'
assert.equal(S.bufferArray.length, 3);
// Now add the value of the last response key in previous chunk
chunks.unshift(chunkJSON);
// Add it for the second chunk also
chunks.push(chunkJSON);
// Insert second chunk
memcached.buffer(S, chunks.join('\r\n') + '\r\nEND\r\n');
// Check if everything is cleared up nicely.
assert.equal(S.responseBuffer.length, 0);
assert.equal(S.bufferArray.length, 0);
assert.equal(S.metaData.length, 0);
memcached.end();
done();
});
});