Skip to content

Instantly share code, notes, and snippets.

@Aziodong
Created May 21, 2014 09:01
Show Gist options
  • Save Aziodong/ac214724e036244678a3 to your computer and use it in GitHub Desktop.
Save Aziodong/ac214724e036244678a3 to your computer and use it in GitHub Desktop.
Nodejs学习项目
This gist exceeds the recommended number of files (~10). To access all files, please clone this gist.
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Express4 node_modules" level="project" />
</component>
</module>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{Express4 node_modules}" />
</component>
</project>
<component name="libraryTable">
<library name="Express4 node_modules" type="javaScript">
<properties>
<option name="frameworkName" value="node_modules" />
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules" />
</CLASSES>
<SOURCES />
</library>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" />
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Express4.iml" filepath="$PROJECT_DIR$/.idea/Express4.iml" />
</modules>
</component>
</project>
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
</component>
</project>
var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var connect=require('connect');
var MongoStore=require("connect-mongo-store")(connect);
var mongoStore=new MongoStore('mongodb://localhost:27017/express4');
var log4sini=require('./syssupport/log4sini');
var log4js=log4sini.getLog4s();
var logger4s=log4js.getLogger('normal');
logger4s.setLevel('INFO');
var routes = require('./routes/index');
var app = express();
var midconnect=connect();
// 设置view的路径及模板引擎
app.set('views', path.join(__dirname, 'views'));
//修改ejs模板后缀为html
app.engine('html',require('ejs').__express);
app.set('view engine','html');//app.set('view engine', 'ejs');
//应用给定的中间件
app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser());
//指定静态文件路径前缀
app.use(express.static(path.join(__dirname, 'public')));
//指定MongoDB作为session存储介质
midconnect.use(connect.session({store:mongoStore,secret:'express4test'}));
//指定日志记录为log4js
app.use(log4js.connectLogger(logger4s,{level:log4js.levels.INFO}));
app.use(function(req,res,next){
app.locals.user=connect.session.user;
next();
});
app.use(function(req, res, next){
app.locals.user = connect.session.user;
var err=connect.session.error;
if(connect.session.error) {
delete connect.session.error;
}
app.locals.message = '';
if (err) {
app.locals.message = '<div class="alert alert-error">' + err + '</div>';
}
next();
});
app.use('/', routes);
/// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
/// error handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
//暴露日志api以在route中使用
exports.logger=function(name){
var logger = log4js.getLogger(name);
logger.setLevel('INFO');
return logger;
}
module.exports = app;
#!/usr/bin/env node
var debug = require('debug')('Express4');
var app = require('../app');
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
});
[2014-05-21 16:08:45.267] [INFO] normal - 127.0.0.1 - - "GET /login HTTP/1.1" 200 1312 "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:08:48.010] [INFO] normal - 127.0.0.1 - - "POST /login HTTP/1.1" 302 68 "http://localhost:3000/login" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:08:48.014] [INFO] normal - 127.0.0.1 - - "GET /login HTTP/1.1" 200 1376 "http://localhost:3000/login" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:00:17.479] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 500 1004 "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:00:59.957] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 200 441 "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:01:15.565] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:01:16.479] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:08:34.307] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:08:39.357] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
[2014-05-21 16:08:40.083] [INFO] normal - 127.0.0.1 - - "GET / HTTP/1.1" 304 - "" "Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0"
#!/bin/sh
basedir=`dirname "$0"`
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../supervisor/lib/cli-wrapper.js" "$@"
ret=$?
else
node "$basedir/../supervisor/lib/cli-wrapper.js" "$@"
ret=$?
fi
exit $ret
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\supervisor\lib\cli-wrapper.js" %*
) ELSE (
node "%~dp0\..\supervisor\lib\cli-wrapper.js" %*
)
#!/bin/sh
basedir=`dirname "$0"`
case `uname` in
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../supervisor/lib/cli-wrapper.js" "$@"
ret=$?
else
node "$basedir/../supervisor/lib/cli-wrapper.js" "$@"
ret=$?
fi
exit $ret
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\supervisor\lib\cli-wrapper.js" %*
) ELSE (
node "%~dp0\..\supervisor\lib\cli-wrapper.js" %*
)
node_js:
- "0.10"
language: node_js

1.0.1 / 2014-04-14

  • use type-is module

1.0.1 / 2014-03-20

  • lower default limits to 100kb
var getBody = require('raw-body');
var typeis = require('type-is');
var http = require('http');
var qs = require('qs');
exports = module.exports = bodyParser;
exports.json = json;
exports.urlencoded = urlencoded;
function bodyParser(options){
var _urlencoded = urlencoded(options);
var _json = json(options);
return function bodyParser(req, res, next) {
_json(req, res, function(err){
if (err) return next(err);
_urlencoded(req, res, next);
});
}
}
function json(options){
options = options || {};
var strict = options.strict !== false;
return function jsonParser(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
if (!typeis(req, 'json')) return next();
// flag as parsed
req._body = true;
// parse
getBody(req, {
limit: options.limit || '100kb',
length: req.headers['content-length'],
encoding: 'utf8'
}, function (err, buf) {
if (err) return next(err);
var first = buf.trim()[0];
if (0 == buf.length) {
return next(error(400, 'invalid json, empty body'));
}
if (strict && '{' != first && '[' != first) return next(error(400, 'invalid json'));
try {
req.body = JSON.parse(buf, options.reviver);
} catch (err){
err.body = buf;
err.status = 400;
return next(err);
}
next();
})
};
}
function urlencoded(options){
options = options || {};
return function urlencodedParser(req, res, next) {
if (req._body) return next();
req.body = req.body || {};
if (!typeis(req, 'urlencoded')) return next();
// flag as parsed
req._body = true;
// parse
getBody(req, {
limit: options.limit || '100kb',
length: req.headers['content-length'],
encoding: 'utf8'
}, function (err, buf) {
if (err) return next(err);
try {
req.body = buf.length
? qs.parse(buf)
: {};
} catch (err){
err.body = buf;
return next(err);
}
next();
})
}
}
function error(code, msg) {
var err = new Error(msg || http.STATUS_CODES[code]);
err.status = code;
return err;
}
BIN = ./node_modules/.bin/
test:
@$(BIN)mocha \
--require should \
--reporter spec \
--bail
.PHONY: test
[submodule "support/expresso"]
path = support/expresso
url = git://github.com/visionmedia/expresso.git
[submodule "support/should"]
path = support/should
url = git://github.com/visionmedia/should.js.git
test
.travis.yml
benchmark.js
component.json
examples.js
History.md
Makefile
/**
* Object#toString() ref for stringify().
*/
var toString = Object.prototype.toString;
/**
* Object#hasOwnProperty ref
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Array#indexOf shim.
*/
var indexOf = typeof Array.prototype.indexOf === 'function'
? function(arr, el) { return arr.indexOf(el); }
: function(arr, el) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === el) return i;
}
return -1;
};
/**
* Array.isArray shim.
*/
var isArray = Array.isArray || function(arr) {
return toString.call(arr) == '[object Array]';
};
/**
* Object.keys shim.
*/
var objectKeys = Object.keys || function(obj) {
var ret = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
ret.push(key);
}
}
return ret;
};
/**
* Array#forEach shim.
*/
var forEach = typeof Array.prototype.forEach === 'function'
? function(arr, fn) { return arr.forEach(fn); }
: function(arr, fn) {
for (var i = 0; i < arr.length; i++) fn(arr[i]);
};
/**
* Array#reduce shim.
*/
var reduce = function(arr, fn, initial) {
if (typeof arr.reduce === 'function') return arr.reduce(fn, initial);
var res = initial;
for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]);
return res;
};
/**
* Cache non-integer test regexp.
*/
var isint = /^[0-9]+$/;
function promote(parent, key) {
if (parent[key].length == 0) return parent[key] = {}
var t = {};
for (var i in parent[key]) {
if (hasOwnProperty.call(parent[key], i)) {
t[i] = parent[key][i];
}
}
parent[key] = t;
return t;
}
function parse(parts, parent, key, val) {
var part = parts.shift();
// illegal
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
// end
if (!part) {
if (isArray(parent[key])) {
parent[key].push(val);
} else if ('object' == typeof parent[key]) {
parent[key] = val;
} else if ('undefined' == typeof parent[key]) {
parent[key] = val;
} else {
parent[key] = [parent[key], val];
}
// array
} else {
var obj = parent[key] = parent[key] || [];
if (']' == part) {
if (isArray(obj)) {
if ('' != val) obj.push(val);
} else if ('object' == typeof obj) {
obj[objectKeys(obj).length] = val;
} else {
obj = parent[key] = [parent[key], val];
}
// prop
} else if (~indexOf(part, ']')) {
part = part.substr(0, part.length - 1);
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
// key
} else {
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
parse(parts, obj, part, val);
}
}
}
/**
* Merge parent key/val pair.
*/
function merge(parent, key, val){
if (~indexOf(key, ']')) {
var parts = key.split('[')
, len = parts.length
, last = len - 1;
parse(parts, parent, 'base', val);
// optimize
} else {
if (!isint.test(key) && isArray(parent.base)) {
var t = {};
for (var k in parent.base) t[k] = parent.base[k];
parent.base = t;
}
set(parent.base, key, val);
}
return parent;
}
/**
* Compact sparse arrays.
*/
function compact(obj) {
if ('object' != typeof obj) return obj;
if (isArray(obj)) {
var ret = [];
for (var i in obj) {
if (hasOwnProperty.call(obj, i)) {
ret.push(obj[i]);
}
}
return ret;
}
for (var key in obj) {
obj[key] = compact(obj[key]);
}
return obj;
}
/**
* Parse the given obj.
*/
function parseObject(obj){
var ret = { base: {} };
forEach(objectKeys(obj), function(name){
merge(ret, name, obj[name]);
});
return compact(ret.base);
}
/**
* Parse the given str.
*/
function parseString(str){
var ret = reduce(String(str).split('&'), function(ret, pair){
var eql = indexOf(pair, '=')
, brace = lastBraceInKey(pair)
, key = pair.substr(0, brace || eql)
, val = pair.substr(brace || eql, pair.length)
, val = val.substr(indexOf(val, '=') + 1, val.length);
// ?foo
if ('' == key) key = pair, val = '';
if ('' == key) return ret;
return merge(ret, decode(key), decode(val));
}, { base: {} }).base;
return compact(ret);
}
/**
* Parse the given query `str` or `obj`, returning an object.
*
* @param {String} str | {Object} obj
* @return {Object}
* @api public
*/
exports.parse = function(str){
if (null == str || '' == str) return {};
return 'object' == typeof str
? parseObject(str)
: parseString(str);
};
/**
* Turn the given `obj` into a query string
*
* @param {Object} obj
* @return {String}
* @api public
*/
var stringify = exports.stringify = function(obj, prefix) {
if (isArray(obj)) {
return stringifyArray(obj, prefix);
} else if ('[object Object]' == toString.call(obj)) {
return stringifyObject(obj, prefix);
} else if ('string' == typeof obj) {
return stringifyString(obj, prefix);
} else {
return prefix + '=' + encodeURIComponent(String(obj));
}
};
/**
* Stringify the given `str`.
*
* @param {String} str
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyString(str, prefix) {
if (!prefix) throw new TypeError('stringify expects an object');
return prefix + '=' + encodeURIComponent(str);
}
/**
* Stringify the given `arr`.
*
* @param {Array} arr
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyArray(arr, prefix) {
var ret = [];
if (!prefix) throw new TypeError('stringify expects an object');
for (var i = 0; i < arr.length; i++) {
ret.push(stringify(arr[i], prefix + '[' + i + ']'));
}
return ret.join('&');
}
/**
* Stringify the given `obj`.
*
* @param {Object} obj
* @param {String} prefix
* @return {String}
* @api private
*/
function stringifyObject(obj, prefix) {
var ret = []
, keys = objectKeys(obj)
, key;
for (var i = 0, len = keys.length; i < len; ++i) {
key = keys[i];
if ('' == key) continue;
if (null == obj[key]) {
ret.push(encodeURIComponent(key) + '=');
} else {
ret.push(stringify(obj[key], prefix
? prefix + '[' + encodeURIComponent(key) + ']'
: encodeURIComponent(key)));
}
}
return ret.join('&');
}
/**
* Set `obj`'s `key` to `val` respecting
* the weird and wonderful syntax of a qs,
* where "foo=bar&foo=baz" becomes an array.
*
* @param {Object} obj
* @param {String} key
* @param {String} val
* @api private
*/
function set(obj, key, val) {
var v = obj[key];
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return;
if (undefined === v) {
obj[key] = val;
} else if (isArray(v)) {
v.push(val);
} else {
obj[key] = [v, val];
}
}
/**
* Locate last brace in `str` within the key.
*
* @param {String} str
* @return {Number}
* @api private
*/
function lastBraceInKey(str) {
var len = str.length
, brace
, c;
for (var i = 0; i < len; ++i) {
c = str[i];
if (']' == c) brace = false;
if ('[' == c) brace = true;
if ('=' == c && !brace) return i;
}
}
/**
* Decode `str`.
*
* @param {String} str
* @return {String}
* @api private
*/
function decode(str) {
try {
return decodeURIComponent(str.replace(/\+/g, ' '));
} catch (err) {
return str;
}
}
{
"name": "qs",
"description": "querystring parser",
"version": "0.6.6",
"keywords": [
"query string",
"parser",
"component"
],
"repository": {
"type": "git",
"url": "git://github.com/visionmedia/node-querystring.git"
},
"devDependencies": {
"mocha": "*",
"expect.js": "*"
},
"scripts": {
"test": "make test"
},
"author": {
"name": "TJ Holowaychuk",
"email": "[email protected]",
"url": "http://tjholowaychuk.com"
},
"main": "index",
"engines": {
"node": "*"
},
"readme": "# node-querystring\n\n query string parser for node and the browser supporting nesting, as it was removed from `0.3.x`, so this library provides the previous and commonly desired behaviour (and twice as fast). Used by [express](http://expressjs.com), [connect](http://senchalabs.github.com/connect) and others.\n\n## Installation\n\n $ npm install qs\n\n## Examples\n\n```js\nvar qs = require('qs');\n\nqs.parse('user[name][first]=Tobi&user[email][email protected]');\n// => { user: { name: { first: 'Tobi' }, email: '[email protected]' } }\n\nqs.stringify({ user: { name: 'Tobi', email: '[email protected]' }})\n// => user[name]=Tobi&user[email]=tobi%40learnboost.com\n```\n\n## Testing\n\nInstall dev dependencies:\n\n $ npm install -d\n\nand execute:\n\n $ make test\n\nbrowser:\n\n $ open test/browser/index.html\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2010 TJ Holowaychuk &lt;[email protected]&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
"readmeFilename": "Readme.md",
"bugs": {
"url": "https://github.com/visionmedia/node-querystring/issues"
},
"homepage": "https://github.com/visionmedia/node-querystring",
"_id": "[email protected]",
"_from": "qs@~0.6.6"
}

node-querystring

query string parser for node and the browser supporting nesting, as it was removed from 0.3.x, so this library provides the previous and commonly desired behaviour (and twice as fast). Used by express, connect and others.

Installation

$ npm install qs

Examples

var qs = require('qs');

qs.parse('user[name][first]=Tobi&user[email][email protected]');
// => { user: { name: { first: 'Tobi' }, email: '[email protected]' } }

qs.stringify({ user: { name: 'Tobi', email: '[email protected]' }})
// => user[name]=Tobi&user[email]=tobi%40learnboost.com

Testing

Install dev dependencies:

$ npm install -d

and execute:

$ make test

browser:

$ open test/browser/index.html

License

(The MIT License)

Copyright (c) 2010 TJ Holowaychuk <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

node_js:
- "0.8"
- "0.10"
- "0.11"
language: node_js
var StringDecoder = require('string_decoder').StringDecoder
var bytes = require('bytes')
module.exports = function (stream, options, done) {
if (typeof options === 'function') {
done = options
options = {}
} else if (!options) {
options = {}
} else if (options === true) {
options = {
encoding: 'utf8'
}
}
// convert the limit to an integer
var limit = null
if (typeof options.limit === 'number')
limit = options.limit
if (typeof options.limit === 'string')
limit = bytes(options.limit)
// convert the expected length to an integer
var length = null
if (options.length != null && !isNaN(options.length))
length = parseInt(options.length, 10)
// check the length and limit options.
// note: we intentionally leave the stream paused,
// so users should handle the stream themselves.
if (limit !== null && length !== null && length > limit) {
if (typeof stream.pause === 'function')
stream.pause()
process.nextTick(function () {
var err = makeError('request entity too large', 'entity.too.large')
err.status = err.statusCode = 413
err.length = err.expected = length
err.limit = limit
done(err)
})
return defer
}
var state = stream._readableState
// streams2+: assert the stream encoding is buffer.
if (state && state.encoding != null) {
if (typeof stream.pause === 'function')
stream.pause()
process.nextTick(function () {
var err = makeError('stream encoding should not be set',
'stream.encoding.set')
// developer error
err.status = err.statusCode = 500
done(err)
})
return defer
}
var received = 0
// note: we delegate any invalid encodings to the constructor
var decoder = options.encoding
? new StringDecoder(options.encoding === true ? 'utf8' : options.encoding)
: null
var buffer = decoder
? ''
: []
stream.on('data', onData)
stream.once('end', onEnd)
stream.once('error', onEnd)
stream.once('close', cleanup)
return defer
// yieldable support
function defer(fn) {
done = fn
}
function onData(chunk) {
received += chunk.length
decoder
? buffer += decoder.write(chunk)
: buffer.push(chunk)
if (limit !== null && received > limit) {
if (typeof stream.pause === 'function')
stream.pause()
var err = makeError('request entity too large', 'entity.too.large')
err.status = err.statusCode = 413
err.received = received
err.limit = limit
done(err)
cleanup()
}
}
function onEnd(err) {
if (err) {
if (typeof stream.pause === 'function')
stream.pause()
done(err)
} else if (length !== null && received !== length) {
err = makeError('request size did not match content length',
'request.size.invalid')
err.status = err.statusCode = 400
err.received = received
err.length = err.expected = length
done(err)
} else {
done(null, decoder
? buffer + endStringDecoder(decoder)
: Buffer.concat(buffer)
)
}
cleanup()
}
function cleanup() {
received = buffer = null
stream.removeListener('data', onData)
stream.removeListener('end', onEnd)
stream.removeListener('error', onEnd)
stream.removeListener('close', cleanup)
}
}
// to create serializable errors you must re-set message so
// that it is enumerable and you must re configure the type
// property so that is writable and enumerable
function makeError(message, type) {
var error = new Error()
error.message = message
Object.defineProperty(error, 'type', {
value: type,
enumerable: true,
writable: true,
configurable: true
})
return error
}
// https://github.com/Raynos/body/blob/2512ced39e31776e5a2f7492b907330badac3a40/index.js#L72
// bug fix for missing `StringDecoder.end` in v0.8.x
function endStringDecoder(decoder) {
if (decoder.end) {
return decoder.end()
}
var res = ""
if (decoder.charReceived) {
var cr = decoder.charReceived
var buf = decoder.charBuffer
var enc = decoder.encoding
res += buf.slice(0, cr).toString(enc)
}
return res
}
NODE ?= node
BIN = ./node_modules/.bin/
test:
@${NODE} ${BIN}mocha \
--harmony-generators \
--reporter spec \
--bail \
./test/index.js
clean:
@rm -rf node_modules
.PHONY: test clean
{
"name": "bytes",
"description": "byte size string parser / serializer",
"keywords": ["bytes", "utility"],
"version": "0.2.1",
"scripts": ["index.js"]
}

1.0.0 / 2014-05-05

  • add negative support. fixes #6

0.3.0 / 2014-03-19

  • added terabyte support

0.2.1 / 2013-04-01

  • add .component

0.2.0 / 2012-10-28

  • bytes(200).should.eql('200b')

0.1.0 / 2012-07-04

  • add bytes to string conversion [yields]
/**
* Parse byte `size` string.
*
* @param {String} size
* @return {Number}
* @api public
*/
module.exports = function(size) {
if ('number' == typeof size) return convert(size);
var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb|tb)$/)
, n = parseFloat(parts[1])
, type = parts[2];
var map = {
kb: 1 << 10
, mb: 1 << 20
, gb: 1 << 30
, tb: ((1 << 30) * 1024)
};
return map[type] * n;
};
/**
* convert bytes into string.
*
* @param {Number} b - bytes to convert
* @return {String}
* @api public
*/
function convert (b) {
var tb = ((1 << 30) * 1024), gb = 1 << 30, mb = 1 << 20, kb = 1 << 10, abs = Math.abs(b);
if (abs >= tb) return (Math.round(b / tb * 100) / 100) + 'tb';
if (abs >= gb) return (Math.round(b / gb * 100) / 100) + 'gb';
if (abs >= mb) return (Math.round(b / mb * 100) / 100) + 'mb';
if (abs >= kb) return (Math.round(b / kb * 100) / 100) + 'kb';
return b + 'b';
}
test:
@./node_modules/.bin/mocha \
--reporter spec \
--require should
.PHONY: test
{
"name": "bytes",
"author": {
"name": "TJ Holowaychuk",
"email": "[email protected]",
"url": "http://tjholowaychuk.com"
},
"description": "byte size string parser / serializer",
"repository": {
"type": "git",
"url": "https://github.com/visionmedia/bytes.js.git"
},
"version": "1.0.0",
"main": "index.js",
"dependencies": {},
"devDependencies": {
"mocha": "*",
"should": "*"
},
"component": {
"scripts": {
"bytes/index.js": "index.js"
}
},
"readme": "# node-bytes\n\n Byte string parser / formatter.\n\n## Example:\n\n```js\nbytes('1kb')\n// => 1024\n\nbytes('2mb')\n// => 2097152\n\nbytes('1gb')\n// => 1073741824\n\nbytes(1073741824)\n// => 1gb\n\nbytes(1099511627776)\n// => 1tb\n```\n\n## Installation\n\n```\n$ npm install bytes\n$ component install visionmedia/bytes.js\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2012 TJ Holowaychuk &lt;[email protected]&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"readmeFilename": "Readme.md",
"bugs": {
"url": "https://github.com/visionmedia/bytes.js/issues"
},
"homepage": "https://github.com/visionmedia/bytes.js",
"_id": "[email protected]",
"_from": "bytes@1"
}

node-bytes

Byte string parser / formatter.

Example:

bytes('1kb')
// => 1024

bytes('2mb')
// => 2097152

bytes('1gb')
// => 1073741824

bytes(1073741824)
// => 1gb

bytes(1099511627776)
// => 1tb

Installation

$ npm install bytes
$ component install visionmedia/bytes.js

License

(The MIT License)

Copyright (c) 2012 TJ Holowaychuk <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

{
"name": "raw-body",
"description": "Get and validate the raw body of a readable stream.",
"version": "1.1.5",
"author": {
"name": "Jonathan Ong",
"email": "[email protected]",
"url": "http://jongleberry.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/stream-utils/raw-body.git"
},
"bugs": {
"url": "https://github.com/stream-utils/raw-body/issues"
},
"dependencies": {
"bytes": "1"
},
"devDependencies": {
"readable-stream": "~1.0.17",
"co": "3",
"gnode": "~0.0.4",
"mocha": "^1.14.0",
"through2": "~0.4.1",
"request": "^2.27.0",
"assert-tap": "~0.1.4"
},
"scripts": {
"test": "NODE=gnode make test && node ./test/acceptance.js"
},
"engines": {
"node": ">= 0.8.0"
},
"readme": "# Raw Body [![Build Status](https://travis-ci.org/stream-utils/raw-body.svg?branch=master)](https://travis-ci.org/stream-utils/raw-body)\n\nGets the entire buffer of a stream either as a `Buffer` or a string.\nValidates the stream's length against an expected length and maximum limit.\nIdeal for parsing request bodies.\n\n## API\n\n```js\nvar getRawBody = require('raw-body')\n\napp.use(function (req, res, next) {\n getRawBody(req, {\n length: req.headers['content-length'],\n limit: '1mb',\n encoding: 'utf8'\n }, function (err, string) {\n if (err)\n return next(err)\n\n req.text = string\n next()\n })\n})\n```\n\nor in a Koa generator:\n\n```js\napp.use(function* (next) {\n var string = yield getRawBody(this.req, {\n length: this.length,\n limit: '1mb',\n encoding: 'utf8'\n })\n})\n```\n\n### getRawBody(stream, [options], [callback])\n\nReturns a thunk for yielding with generators.\n\nOptions:\n\n- `length` - The length length of the stream.\n If the contents of the stream do not add up to this length,\n an `400` error code is returned.\n- `limit` - The byte limit of the body.\n If the body ends up being larger than this limit,\n a `413` error code is returned.\n- `encoding` - The requested encoding.\n By default, a `Buffer` instance will be returned.\n Most likely, you want `utf8`.\n You can use any type of encoding supported by [StringDecoder](http://nodejs.org/api/string_decoder.html).\n You can also pass `true` which sets it to the default `utf8`\n\n`callback(err, res)`:\n\n- `err` - the following attributes will be defined if applicable:\n\n - `limit` - the limit in bytes\n - `length` and `expected` - the expected length of the stream\n - `received` - the received bytes\n - `status` and `statusCode` - the corresponding status code for the error\n - `type` - either `entity.too.large`, `request.size.invalid`, or `stream.encoding.set`\n\n- `res` - the result, either as a `String` if an encoding was set or a `Buffer` otherwise.\n\nIf an error occurs, the stream will be paused,\nand you are responsible for correctly disposing the stream.\nFor HTTP requests, no handling is required if you send a response.\nFor streams that use file descriptors, you should `stream.destroy()` or `stream.close()` to prevent leaks.\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Jonathan Ong [email protected]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n",
"readmeFilename": "README.md",
"homepage": "https://github.com/stream-utils/raw-body",
"_id": "[email protected]",
"_from": "raw-body@~1.1.2"
}

Raw Body Build Status

Gets the entire buffer of a stream either as a Buffer or a string. Validates the stream's length against an expected length and maximum limit. Ideal for parsing request bodies.

API

var getRawBody = require('raw-body')

app.use(function (req, res, next) {
  getRawBody(req, {
    length: req.headers['content-length'],
    limit: '1mb',
    encoding: 'utf8'
  }, function (err, string) {
    if (err)
      return next(err)

    req.text = string
    next()
  })
})

or in a Koa generator:

app.use(function* (next) {
  var string = yield getRawBody(this.req, {
    length: this.length,
    limit: '1mb',
    encoding: 'utf8'
  })
})

getRawBody(stream, [options], [callback])

Returns a thunk for yielding with generators.

Options:

  • length - The length length of the stream. If the contents of the stream do not add up to this length, an 400 error code is returned.
  • limit - The byte limit of the body. If the body ends up being larger than this limit, a 413 error code is returned.
  • encoding - The requested encoding. By default, a Buffer instance will be returned. Most likely, you want utf8. You can use any type of encoding supported by StringDecoder. You can also pass true which sets it to the default utf8

callback(err, res):

  • err - the following attributes will be defined if applicable:

    • limit - the limit in bytes
    • length and expected - the expected length of the stream
    • received - the received bytes
    • status and statusCode - the corresponding status code for the error
    • type - either entity.too.large, request.size.invalid, or stream.encoding.set
  • res - the result, either as a String if an encoding was set or a Buffer otherwise.

If an error occurs, the stream will be paused, and you are responsible for correctly disposing the stream. For HTTP requests, no handling is required if you send a response. For streams that use file descriptors, you should stream.destroy() or stream.close() to prevent leaks.

License

The MIT License (MIT)

Copyright (c) 2013 Jonathan Ong [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1.1.0 / 2014-04-12

  • add non-array values support

  • expose internal utilities:

    • .is()
    • .hasBody()
    • .normalize()
    • .match()

1.0.1 / 2014-03-30

  • add multipart as a shorthand
var mime = require('mime');
var slice = [].slice;
module.exports = typeofrequest;
typeofrequest.is = typeis;
typeofrequest.hasBody = hasbody;
typeofrequest.normalize = normalize;
typeofrequest.match = mimeMatch;
/**
* Compare a `value` content-type with `types`.
* Each `type` can be an extension like `html`,
* a special shortcut like `multipart` or `urlencoded`,
* or a mime type.
*
* If no types match, `false` is returned.
* Otherwise, the first `type` that matches is returned.
*
* @param {String} value
* @param {Array} types
* @return String
*/
function typeis(value, types) {
if (!value) return false;
if (types && !Array.isArray(types)) types = slice.call(arguments, 1);
// remove stuff like charsets
var index = value.indexOf(';')
value = ~index ? value.slice(0, index) : value
// no types, return the content type
if (!types || !types.length) return value;
var type;
for (var i = 0; i < types.length; i++)
if (mimeMatch(normalize(type = types[i]), value))
return ~type.indexOf('*') ? value : type;
// no matches
return false;
}
/**
* Check if a request has a request body.
* A request with a body __must__ either have `transfer-encoding`
* or `content-length` headers set.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
*
* @param {Object} request
* @return {Boolean}
* @api public
*/
function hasbody(req) {
var headers = req.headers;
if ('transfer-encoding' in headers) return true;
var length = headers['content-length'];
if (!length) return false;
// no idea when this would happen, but `isNaN(null) === false`
if (isNaN(length)) return false;
return !!parseInt(length, 10);
}
/**
* Check if the incoming request contains the "Content-Type"
* header field, and it contains any of the give mime `type`s.
* If there is no request body, `null` is returned.
* If there is no content type, `false` is returned.
* Otherwise, it returns the first `type` that matches.
*
* Examples:
*
* // With Content-Type: text/html; charset=utf-8
* this.is('html'); // => 'html'
* this.is('text/html'); // => 'text/html'
* this.is('text/*', 'application/json'); // => 'text/html'
*
* // When Content-Type is application/json
* this.is('json', 'urlencoded'); // => 'json'
* this.is('application/json'); // => 'application/json'
* this.is('html', 'application/*'); // => 'application/json'
*
* this.is('html'); // => false
*
* @param {String|Array} types...
* @return {String|false|null}
* @api public
*/
function typeofrequest(req, types) {
if (!hasbody(req)) return null;
if (types && !Array.isArray(types)) types = slice.call(arguments, 1);
return typeis(req.headers['content-type'], types);
}
/**
* Normalize a mime type.
* If it's a shorthand, expand it to a valid mime type.
*
* In general, you probably want:
*
* var type = is(req, ['urlencoded', 'json', 'multipart']);
*
* Then use the appropriate body parsers.
* These three are the most common request body types
* and are thus ensured to work.
*
* @param {String} type
* @api private
*/
function normalize(type) {
switch (type) {
case 'urlencoded': return 'application/x-www-form-urlencoded';
case 'multipart':
type = 'multipart/*';
break;
}
return ~type.indexOf('/') ? type : mime.lookup(type);
}
/**
* Check if `exected` mime type
* matches `actual` mime type with
* wildcard support.
*
* @param {String} expected
* @param {String} actual
* @return {Boolean}
* @api private
*/
function mimeMatch(expected, actual) {
if (expected === actual) return true;
if (!~expected.indexOf('*')) return false;
actual = actual.split('/');
expected = expected.split('/');
if ('*' === expected[0] && expected[1] === actual[1]) return true;
if ('*' === expected[1] && expected[0] === actual[0]) return true;
return false;
}
Copyright (c) 2010 Benjamin Thomas, Robert Kieffer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
var path = require('path');
var fs = require('fs');
function Mime() {
// Map of extension -> mime type
this.types = Object.create(null);
// Map of mime type -> extension
this.extensions = Object.create(null);
}
/**
* Define mimetype -> extension mappings. Each key is a mime-type that maps
* to an array of extensions associated with the type. The first extension is
* used as the default extension for the type.
*
* e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
*
* @param map (Object) type definitions
*/
Mime.prototype.define = function (map) {
for (var type in map) {
var exts = map[type];
for (var i = 0; i < exts.length; i++) {
if (process.env.DEBUG_MIME && this.types[exts]) {
console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' +
this.types[exts] + ' to ' + type);
}
this.types[exts[i]] = type;
}
// Default extension is the first one we encounter
if (!this.extensions[type]) {
this.extensions[type] = exts[0];
}
}
};
/**
* Load an Apache2-style ".types" file
*
* This may be called multiple times (it's expected). Where files declare
* overlapping types/extensions, the last file wins.
*
* @param file (String) path of file to load.
*/
Mime.prototype.load = function(file) {
this._loading = file;
// Read file and split into lines
var map = {},
content = fs.readFileSync(file, 'ascii'),
lines = content.split(/[\r\n]+/);
lines.forEach(function(line) {
// Clean up whitespace/comments, and split into fields
var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
map[fields.shift()] = fields;
});
this.define(map);
this._loading = null;
};
/**
* Lookup a mime type based on extension
*/
Mime.prototype.lookup = function(path, fallback) {
var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase();
return this.types[ext] || fallback || this.default_type;
};
/**
* Return file extension associated with a mime type
*/
Mime.prototype.extension = function(mimeType) {
var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase();
return this.extensions[type];
};
// Default instance
var mime = new Mime();
// Load local copy of
// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
mime.load(path.join(__dirname, 'types/mime.types'));
// Load additional types from node.js community
mime.load(path.join(__dirname, 'types/node.types'));
// Default type
mime.default_type = mime.lookup('bin');
//
// Additional API specific to the default instance
//
mime.Mime = Mime;
/**
* Lookup a charset based on mime type.
*/
mime.charsets = {
lookup: function(mimeType, fallback) {
// Assume text types are utf8
return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
}
};
module.exports = mime;
{
"author": {
"name": "Robert Kieffer",
"email": "[email protected]",
"url": "http://github.com/broofa"
},
"contributors": [
{
"name": "Benjamin Thomas",
"email": "[email protected]",
"url": "http://github.com/bentomas"
}
],
"dependencies": {},
"description": "A comprehensive library for mime-type mapping",
"devDependencies": {},
"keywords": [
"util",
"mime"
],
"main": "mime.js",
"name": "mime",
"repository": {
"url": "https://github.com/broofa/node-mime",
"type": "git"
},
"version": "1.2.11",
"readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.'). E.g.\n\n var mime = require('mime');\n\n mime.lookup('/path/to/file.txt'); // => 'text/plain'\n mime.lookup('file.txt'); // => 'text/plain'\n mime.lookup('.TXT'); // => 'text/plain'\n mime.lookup('htm'); // => 'text/html'\n\n### mime.default_type\nSets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)\n\n### mime.extension(type)\nGet the default extension for `type`\n\n mime.extension('text/html'); // => 'html'\n mime.extension('application/octet-stream'); // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n mime.charsets.lookup('text/plain'); // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project. If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n mime.define({\n 'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n 'application/x-my-type': ['x-mt', 'x-mtt'],\n // etc ...\n });\n\n mime.lookup('x-sft'); // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n mime.load('./my_project.types');\n\nThe .types file format is simple - See the `types` dir for examples.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/broofa/node-mime/issues"
},
"homepage": "https://github.com/broofa/node-mime",
"_id": "[email protected]",
"_from": "mime@~1.2.11"
}

mime

Comprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.

Install

Install with npm:

npm install mime

API - Queries

mime.lookup(path)

Get the mime type associated with a file, if no mime type is found application/octet-stream is returned. Performs a case-insensitive lookup using the extension in path (the substring after the last '/' or '.'). E.g.

var mime = require('mime');

mime.lookup('/path/to/file.txt');         // => 'text/plain'
mime.lookup('file.txt');                  // => 'text/plain'
mime.lookup('.TXT');                      // => 'text/plain'
mime.lookup('htm');                       // => 'text/html'

mime.default_type

Sets the mime type returned when mime.lookup fails to find the extension searched for. (Default is application/octet-stream.)

mime.extension(type)

Get the default extension for type

mime.extension('text/html');                 // => 'html'
mime.extension('application/octet-stream');  // => 'bin'

mime.charsets.lookup()

Map mime-type to charset

mime.charsets.lookup('text/plain');        // => 'UTF-8'

(The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.)

API - Defining Custom Types

The following APIs allow you to add your own type mappings within your project. If you feel a type should be included as part of node-mime, see requesting new types.

mime.define()

Add custom mime/extension mappings

mime.define({
    'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],
    'application/x-my-type': ['x-mt', 'x-mtt'],
    // etc ...
});

mime.lookup('x-sft');                 // => 'text/x-some-format'

The first entry in the extensions array is returned by mime.extension(). E.g.

mime.extension('text/x-some-format'); // => 'x-sf'

mime.load(filepath)

Load mappings from an Apache ".types" format file

mime.load('./my_project.types');

The .types file format is simple - See the types dir for examples.

/**
* Usage: node test.js
*/
var mime = require('./mime');
var assert = require('assert');
var path = require('path');
function eq(a, b) {
console.log('Test: ' + a + ' === ' + b);
assert.strictEqual.apply(null, arguments);
}
console.log(Object.keys(mime.extensions).length + ' types');
console.log(Object.keys(mime.types).length + ' extensions\n');
//
// Test mime lookups
//
eq('text/plain', mime.lookup('text.txt')); // normal file
eq('text/plain', mime.lookup('TEXT.TXT')); // uppercase
eq('text/plain', mime.lookup('dir/text.txt')); // dir + file
eq('text/plain', mime.lookup('.text.txt')); // hidden file
eq('text/plain', mime.lookup('.txt')); // nameless
eq('text/plain', mime.lookup('txt')); // extension-only
eq('text/plain', mime.lookup('/txt')); // extension-less ()
eq('text/plain', mime.lookup('\\txt')); // Windows, extension-less
eq('application/octet-stream', mime.lookup('text.nope')); // unrecognized
eq('fallback', mime.lookup('text.fallback', 'fallback')); // alternate default
//
// Test extensions
//
eq('txt', mime.extension(mime.types.text));
eq('html', mime.extension(mime.types.htm));
eq('bin', mime.extension('application/octet-stream'));
eq('bin', mime.extension('application/octet-stream '));
eq('html', mime.extension(' text/html; charset=UTF-8'));
eq('html', mime.extension('text/html; charset=UTF-8 '));
eq('html', mime.extension('text/html; charset=UTF-8'));
eq('html', mime.extension('text/html ; charset=UTF-8'));
eq('html', mime.extension('text/html;charset=UTF-8'));
eq('html', mime.extension('text/Html;charset=UTF-8'));
eq(undefined, mime.extension('unrecognized'));
//
// Test node.types lookups
//
eq('application/font-woff', mime.lookup('file.woff'));
eq('application/octet-stream', mime.lookup('file.buffer'));
eq('audio/mp4', mime.lookup('file.m4a'));
eq('font/opentype', mime.lookup('file.otf'));
//
// Test charsets
//
eq('UTF-8', mime.charsets.lookup('text/plain'));
eq(undefined, mime.charsets.lookup(mime.types.js));
eq('fallback', mime.charsets.lookup('application/octet-stream', 'fallback'));
//
// Test for overlaps between mime.types and node.types
//
var apacheTypes = new mime.Mime(), nodeTypes = new mime.Mime();
apacheTypes.load(path.join(__dirname, 'types/mime.types'));
nodeTypes.load(path.join(__dirname, 'types/node.types'));
var keys = [].concat(Object.keys(apacheTypes.types))
.concat(Object.keys(nodeTypes.types));
keys.sort();
for (var i = 1; i < keys.length; i++) {
if (keys[i] == keys[i-1]) {
console.warn('Warning: ' +
'node.types defines ' + keys[i] + '->' + nodeTypes.types[keys[i]] +
', mime.types defines ' + keys[i] + '->' + apacheTypes.types[keys[i]]);
}
}
console.log('\nOK');
# This file maps Internet media types to unique file extension(s).
# Although created for httpd, this file is used by many software systems
# and has been placed in the public domain for unlimited redisribution.
#
# The table below contains both registered and (common) unregistered types.
# A type that has no unique extension can be ignored -- they are listed
# here to guide configurations toward known types and to make it easier to
# identify "new" types. File extensions are also commonly used to indicate
# content languages and encodings, so choose them carefully.
#
# Internet media types should be registered as described in RFC 4288.
# The registry is at <http://www.iana.org/assignments/media-types/>.
#
# MIME type (lowercased) Extensions
# ============================================ ==========
# application/1d-interleaved-parityfec
# application/3gpp-ims+xml
# application/activemessage
application/andrew-inset ez
# application/applefile
application/applixware aw
application/atom+xml atom
application/atomcat+xml atomcat
# application/atomicmail
application/atomsvc+xml atomsvc
# application/auth-policy+xml
# application/batch-smtp
# application/beep+xml
# application/calendar+xml
# application/cals-1840
# application/ccmp+xml
application/ccxml+xml ccxml
application/cdmi-capability cdmia
application/cdmi-container cdmic
application/cdmi-domain cdmid
application/cdmi-object cdmio
application/cdmi-queue cdmiq
# application/cea-2018+xml
# application/cellml+xml
# application/cfw
# application/cnrp+xml
# application/commonground
# application/conference-info+xml
# application/cpl+xml
# application/csta+xml
# application/cstadata+xml
application/cu-seeme cu
# application/cybercash
application/davmount+xml davmount
# application/dca-rft
# application/dec-dx
# application/dialog-info+xml
# application/dicom
# application/dns
application/docbook+xml dbk
# application/dskpp+xml
application/dssc+der dssc
application/dssc+xml xdssc
# application/dvcs
application/ecmascript ecma
# application/edi-consent
# application/edi-x12
# application/edifact
application/emma+xml emma
# application/epp+xml
application/epub+zip epub
# application/eshop
# application/example
application/exi exi
# application/fastinfoset
# application/fastsoap
# application/fits
application/font-tdpfr pfr
# application/framework-attributes+xml
application/gml+xml gml
application/gpx+xml gpx
application/gxf gxf
# application/h224
# application/held+xml
# application/http
application/hyperstudio stk
# application/ibe-key-request+xml
# application/ibe-pkg-reply+xml
# application/ibe-pp-data
# application/iges
# application/im-iscomposing+xml
# application/index
# application/index.cmd
# application/index.obj
# application/index.response
# application/index.vnd
application/inkml+xml ink inkml
# application/iotp
application/ipfix ipfix
# application/ipp
# application/isup
application/java-archive jar
application/java-serialized-object ser
application/java-vm class
application/javascript js
application/json json
application/jsonml+json jsonml
# application/kpml-request+xml
# application/kpml-response+xml
application/lost+xml lostxml
application/mac-binhex40 hqx
application/mac-compactpro cpt
# application/macwriteii
application/mads+xml mads
application/marc mrc
application/marcxml+xml mrcx
application/mathematica ma nb mb
# application/mathml-content+xml
# application/mathml-presentation+xml
application/mathml+xml mathml
# application/mbms-associated-procedure-description+xml
# application/mbms-deregister+xml
# application/mbms-envelope+xml
# application/mbms-msk+xml
# application/mbms-msk-response+xml
# application/mbms-protection-description+xml
# application/mbms-reception-report+xml
# application/mbms-register+xml
# application/mbms-register-response+xml
# application/mbms-user-service-description+xml
application/mbox mbox
# application/media_control+xml
application/mediaservercontrol+xml mscml
application/metalink+xml metalink
application/metalink4+xml meta4
application/mets+xml mets
# application/mikey
application/mods+xml mods
# application/moss-keys
# application/moss-signature
# application/mosskey-data
# application/mosskey-request
application/mp21 m21 mp21
application/mp4 mp4s
# application/mpeg4-generic
# application/mpeg4-iod
# application/mpeg4-iod-xmt
# application/msc-ivr+xml
# application/msc-mixer+xml
application/msword doc dot
application/mxf mxf
# application/nasdata
# application/news-checkgroups
# application/news-groupinfo
# application/news-transmission
# application/nss
# application/ocsp-request
# application/ocsp-response
application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy
application/oda oda
application/oebps-package+xml opf
application/ogg ogx
application/omdoc+xml omdoc
application/onenote onetoc onetoc2 onetmp onepkg
application/oxps oxps
# application/parityfec
application/patch-ops-error+xml xer
application/pdf pdf
application/pgp-encrypted pgp
# application/pgp-keys
application/pgp-signature asc sig
application/pics-rules prf
# application/pidf+xml
# application/pidf-diff+xml
application/pkcs10 p10
application/pkcs7-mime p7m p7c
application/pkcs7-signature p7s
application/pkcs8 p8
application/pkix-attr-cert ac
application/pkix-cert cer
application/pkix-crl crl
application/pkix-pkipath pkipath
application/pkixcmp pki
application/pls+xml pls
# application/poc-settings+xml
application/postscript ai eps ps
# application/prs.alvestrand.titrax-sheet
application/prs.cww cww
# application/prs.nprend
# application/prs.plucker
# application/prs.rdf-xml-crypt
# application/prs.xsf+xml
application/pskc+xml pskcxml
# application/qsig
application/rdf+xml rdf
application/reginfo+xml rif
application/relax-ng-compact-syntax rnc
# application/remote-printing
application/resource-lists+xml rl
application/resource-lists-diff+xml rld
# application/riscos
# application/rlmi+xml
application/rls-services+xml rs
application/rpki-ghostbusters gbr
application/rpki-manifest mft
application/rpki-roa roa
# application/rpki-updown
application/rsd+xml rsd
application/rss+xml rss
application/rtf rtf
# application/rtx
# application/samlassertion+xml
# application/samlmetadata+xml
application/sbml+xml sbml
application/scvp-cv-request scq
application/scvp-cv-response scs
application/scvp-vp-request spq
application/scvp-vp-response spp
application/sdp sdp
# application/set-payment
application/set-payment-initiation setpay
# application/set-registration
application/set-registration-initiation setreg
# application/sgml
# application/sgml-open-catalog
application/shf+xml shf
# application/sieve
# application/simple-filter+xml
# application/simple-message-summary
# application/simplesymbolcontainer
# application/slate
# application/smil
application/smil+xml smi smil
# application/soap+fastinfoset
# application/soap+xml
application/sparql-query rq
application/sparql-results+xml srx
# application/spirits-event+xml
application/srgs gram
application/srgs+xml grxml
application/sru+xml sru
application/ssdl+xml ssdl
application/ssml+xml ssml
# application/tamp-apex-update
# application/tamp-apex-update-confirm
# application/tamp-community-update
# application/tamp-community-update-confirm
# application/tamp-error
# application/tamp-sequence-adjust
# application/tamp-sequence-adjust-confirm
# application/tamp-status-query
# application/tamp-status-response
# application/tamp-update
# application/tamp-update-confirm
application/tei+xml tei teicorpus
application/thraud+xml tfi
# application/timestamp-query
# application/timestamp-reply
application/timestamped-data tsd
# application/tve-trigger
# application/ulpfec
# application/vcard+xml
# application/vemmi
# application/vividence.scriptfile
# application/vnd.3gpp.bsf+xml
application/vnd.3gpp.pic-bw-large plb
application/vnd.3gpp.pic-bw-small psb
application/vnd.3gpp.pic-bw-var pvb
# application/vnd.3gpp.sms
# application/vnd.3gpp2.bcmcsinfo+xml
# application/vnd.3gpp2.sms
application/vnd.3gpp2.tcap tcap
application/vnd.3m.post-it-notes pwn
application/vnd.accpac.simply.aso aso
application/vnd.accpac.simply.imp imp
application/vnd.acucobol acu
application/vnd.acucorp atc acutc
application/vnd.adobe.air-application-installer-package+zip air
application/vnd.adobe.formscentral.fcdt fcdt
application/vnd.adobe.fxp fxp fxpl
# application/vnd.adobe.partial-upload
application/vnd.adobe.xdp+xml xdp
application/vnd.adobe.xfdf xfdf
# application/vnd.aether.imp
# application/vnd.ah-barcode
application/vnd.ahead.space ahead
application/vnd.airzip.filesecure.azf azf
application/vnd.airzip.filesecure.azs azs
application/vnd.amazon.ebook azw
application/vnd.americandynamics.acc acc
application/vnd.amiga.ami ami
# application/vnd.amundsen.maze+xml
application/vnd.android.package-archive apk
application/vnd.anser-web-certificate-issue-initiation cii
application/vnd.anser-web-funds-transfer-initiation fti
application/vnd.antix.game-component atx
application/vnd.apple.installer+xml mpkg
application/vnd.apple.mpegurl m3u8
# application/vnd.arastra.swi
application/vnd.aristanetworks.swi swi
application/vnd.astraea-software.iota iota
application/vnd.audiograph aep
# application/vnd.autopackage
# application/vnd.avistar+xml
application/vnd.blueice.multipass mpm
# application/vnd.bluetooth.ep.oob
application/vnd.bmi bmi
application/vnd.businessobjects rep
# application/vnd.cab-jscript
# application/vnd.canon-cpdl
# application/vnd.canon-lips
# application/vnd.cendio.thinlinc.clientconf
application/vnd.chemdraw+xml cdxml
application/vnd.chipnuts.karaoke-mmd mmd
application/vnd.cinderella cdy
# application/vnd.cirpack.isdn-ext
application/vnd.claymore cla
application/vnd.cloanto.rp9 rp9
application/vnd.clonk.c4group c4g c4d c4f c4p c4u
application/vnd.cluetrust.cartomobile-config c11amc
application/vnd.cluetrust.cartomobile-config-pkg c11amz
# application/vnd.collection+json
# application/vnd.commerce-battelle
application/vnd.commonspace csp
application/vnd.contact.cmsg cdbcmsg
application/vnd.cosmocaller cmc
application/vnd.crick.clicker clkx
application/vnd.crick.clicker.keyboard clkk
application/vnd.crick.clicker.palette clkp
application/vnd.crick.clicker.template clkt
application/vnd.crick.clicker.wordbank clkw
application/vnd.criticaltools.wbs+xml wbs
application/vnd.ctc-posml pml
# application/vnd.ctct.ws+xml
# application/vnd.cups-pdf
# application/vnd.cups-postscript
application/vnd.cups-ppd ppd
# application/vnd.cups-raster
# application/vnd.cups-raw
# application/vnd.curl
application/vnd.curl.car car
application/vnd.curl.pcurl pcurl
# application/vnd.cybank
application/vnd.dart dart
application/vnd.data-vision.rdz rdz
application/vnd.dece.data uvf uvvf uvd uvvd
application/vnd.dece.ttml+xml uvt uvvt
application/vnd.dece.unspecified uvx uvvx
application/vnd.dece.zip uvz uvvz
application/vnd.denovo.fcselayout-link fe_launch
# application/vnd.dir-bi.plate-dl-nosuffix
application/vnd.dna dna
application/vnd.dolby.mlp mlp
# application/vnd.dolby.mobile.1
# application/vnd.dolby.mobile.2
application/vnd.dpgraph dpg
application/vnd.dreamfactory dfac
application/vnd.ds-keypoint kpxx
application/vnd.dvb.ait ait
# application/vnd.dvb.dvbj
# application/vnd.dvb.esgcontainer
# application/vnd.dvb.ipdcdftnotifaccess
# application/vnd.dvb.ipdcesgaccess
# application/vnd.dvb.ipdcesgaccess2
# application/vnd.dvb.ipdcesgpdd
# application/vnd.dvb.ipdcroaming
# application/vnd.dvb.iptv.alfec-base
# application/vnd.dvb.iptv.alfec-enhancement
# application/vnd.dvb.notif-aggregate-root+xml
# application/vnd.dvb.notif-container+xml
# application/vnd.dvb.notif-generic+xml
# application/vnd.dvb.notif-ia-msglist+xml
# application/vnd.dvb.notif-ia-registration-request+xml
# application/vnd.dvb.notif-ia-registration-response+xml
# application/vnd.dvb.notif-init+xml
# application/vnd.dvb.pfr
application/vnd.dvb.service svc
# application/vnd.dxr
application/vnd.dynageo geo
# application/vnd.easykaraoke.cdgdownload
# application/vnd.ecdis-update
application/vnd.ecowin.chart mag
# application/vnd.ecowin.filerequest
# application/vnd.ecowin.fileupdate
# application/vnd.ecowin.series
# application/vnd.ecowin.seriesrequest
# application/vnd.ecowin.seriesupdate
# application/vnd.emclient.accessrequest+xml
application/vnd.enliven nml
# application/vnd.eprints.data+xml
application/vnd.epson.esf esf
application/vnd.epson.msf msf
application/vnd.epson.quickanime qam
application/vnd.epson.salt slt
application/vnd.epson.ssf ssf
# application/vnd.ericsson.quickcall
application/vnd.eszigno3+xml es3 et3
# application/vnd.etsi.aoc+xml
# application/vnd.etsi.cug+xml
# application/vnd.etsi.iptvcommand+xml
# application/vnd.etsi.iptvdiscovery+xml
# application/vnd.etsi.iptvprofile+xml
# application/vnd.etsi.iptvsad-bc+xml
# application/vnd.etsi.iptvsad-cod+xml
# application/vnd.etsi.iptvsad-npvr+xml
# application/vnd.etsi.iptvservice+xml
# application/vnd.etsi.iptvsync+xml
# application/vnd.etsi.iptvueprofile+xml
# application/vnd.etsi.mcid+xml
# application/vnd.etsi.overload-control-policy-dataset+xml
# application/vnd.etsi.sci+xml
# application/vnd.etsi.simservs+xml
# application/vnd.etsi.tsl+xml
# application/vnd.etsi.tsl.der
# application/vnd.eudora.data
application/vnd.ezpix-album ez2
application/vnd.ezpix-package ez3
# application/vnd.f-secure.mobile
application/vnd.fdf fdf
application/vnd.fdsn.mseed mseed
application/vnd.fdsn.seed seed dataless
# application/vnd.ffsns
# application/vnd.fints
application/vnd.flographit gph
application/vnd.fluxtime.clip ftc
# application/vnd.font-fontforge-sfd
application/vnd.framemaker fm frame maker book
application/vnd.frogans.fnc fnc
application/vnd.frogans.ltf ltf
application/vnd.fsc.weblaunch fsc
application/vnd.fujitsu.oasys oas
application/vnd.fujitsu.oasys2 oa2
application/vnd.fujitsu.oasys3 oa3
application/vnd.fujitsu.oasysgp fg5
application/vnd.fujitsu.oasysprs bh2
# application/vnd.fujixerox.art-ex
# application/vnd.fujixerox.art4
# application/vnd.fujixerox.hbpl
application/vnd.fujixerox.ddd ddd
application/vnd.fujixerox.docuworks xdw
application/vnd.fujixerox.docuworks.binder xbd
# application/vnd.fut-misnet
application/vnd.fuzzysheet fzs
application/vnd.genomatix.tuxedo txd
# application/vnd.geocube+xml
application/vnd.geogebra.file ggb
application/vnd.geogebra.tool ggt
application/vnd.geometry-explorer gex gre
application/vnd.geonext gxt
application/vnd.geoplan g2w
application/vnd.geospace g3w
# application/vnd.globalplatform.card-content-mgt
# application/vnd.globalplatform.card-content-mgt-response
application/vnd.gmx gmx
application/vnd.google-earth.kml+xml kml
application/vnd.google-earth.kmz kmz
application/vnd.grafeq gqf gqs
# application/vnd.gridmp
application/vnd.groove-account gac
application/vnd.groove-help ghf
application/vnd.groove-identity-message gim
application/vnd.groove-injector grv
application/vnd.groove-tool-message gtm
application/vnd.groove-tool-template tpl
application/vnd.groove-vcard vcg
# application/vnd.hal+json
application/vnd.hal+xml hal
application/vnd.handheld-entertainment+xml zmm
application/vnd.hbci hbci
# application/vnd.hcl-bireports
application/vnd.hhe.lesson-player les
application/vnd.hp-hpgl hpgl
application/vnd.hp-hpid hpid
application/vnd.hp-hps hps
application/vnd.hp-jlyt jlt
application/vnd.hp-pcl pcl
application/vnd.hp-pclxl pclxl
# application/vnd.httphone
application/vnd.hydrostatix.sof-data sfd-hdstx
# application/vnd.hzn-3d-crossword
# application/vnd.ibm.afplinedata
# application/vnd.ibm.electronic-media
application/vnd.ibm.minipay mpy
application/vnd.ibm.modcap afp listafp list3820
application/vnd.ibm.rights-management irm
application/vnd.ibm.secure-container sc
application/vnd.iccprofile icc icm
application/vnd.igloader igl
application/vnd.immervision-ivp ivp
application/vnd.immervision-ivu ivu
# application/vnd.informedcontrol.rms+xml
# application/vnd.informix-visionary
# application/vnd.infotech.project
# application/vnd.infotech.project+xml
# application/vnd.innopath.wamp.notification
application/vnd.insors.igm igm
application/vnd.intercon.formnet xpw xpx
application/vnd.intergeo i2g
# application/vnd.intertrust.digibox
# application/vnd.intertrust.nncp
application/vnd.intu.qbo qbo
application/vnd.intu.qfx qfx
# application/vnd.iptc.g2.conceptitem+xml
# application/vnd.iptc.g2.knowledgeitem+xml
# application/vnd.iptc.g2.newsitem+xml
# application/vnd.iptc.g2.newsmessage+xml
# application/vnd.iptc.g2.packageitem+xml
# application/vnd.iptc.g2.planningitem+xml
application/vnd.ipunplugged.rcprofile rcprofile
application/vnd.irepository.package+xml irp
application/vnd.is-xpr xpr
application/vnd.isac.fcs fcs
application/vnd.jam jam
# application/vnd.japannet-directory-service
# application/vnd.japannet-jpnstore-wakeup
# application/vnd.japannet-payment-wakeup
# application/vnd.japannet-registration
# application/vnd.japannet-registration-wakeup
# application/vnd.japannet-setstore-wakeup
# application/vnd.japannet-verification
# application/vnd.japannet-verification-wakeup
application/vnd.jcp.javame.midlet-rms rms
application/vnd.jisp jisp
application/vnd.joost.joda-archive joda
application/vnd.kahootz ktz ktr
application/vnd.kde.karbon karbon
application/vnd.kde.kchart chrt
application/vnd.kde.kformula kfo
application/vnd.kde.kivio flw
application/vnd.kde.kontour kon
application/vnd.kde.kpresenter kpr kpt
application/vnd.kde.kspread ksp
application/vnd.kde.kword kwd kwt
application/vnd.kenameaapp htke
application/vnd.kidspiration kia
application/vnd.kinar kne knp
application/vnd.koan skp skd skt skm
application/vnd.kodak-descriptor sse
application/vnd.las.las+xml lasxml
# application/vnd.liberty-request+xml
application/vnd.llamagraphics.life-balance.desktop lbd
application/vnd.llamagraphics.life-balance.exchange+xml lbe
application/vnd.lotus-1-2-3 123
application/vnd.lotus-approach apr
application/vnd.lotus-freelance pre
application/vnd.lotus-notes nsf
application/vnd.lotus-organizer org
application/vnd.lotus-screencam scm
application/vnd.lotus-wordpro lwp
application/vnd.macports.portpkg portpkg
# application/vnd.marlin.drm.actiontoken+xml
# application/vnd.marlin.drm.conftoken+xml
# application/vnd.marlin.drm.license+xml
# application/vnd.marlin.drm.mdcf
application/vnd.mcd mcd
application/vnd.medcalcdata mc1
application/vnd.mediastation.cdkey cdkey
# application/vnd.meridian-slingshot
application/vnd.mfer mwf
application/vnd.mfmp mfm
application/vnd.micrografx.flo flo
application/vnd.micrografx.igx igx
application/vnd.mif mif
# application/vnd.minisoft-hp3000-save
# application/vnd.mitsubishi.misty-guard.trustweb
application/vnd.mobius.daf daf
application/vnd.mobius.dis dis
application/vnd.mobius.mbk mbk
application/vnd.mobius.mqy mqy
application/vnd.mobius.msl msl
application/vnd.mobius.plc plc
application/vnd.mobius.txf txf
application/vnd.mophun.application mpn
application/vnd.mophun.certificate mpc
# application/vnd.motorola.flexsuite
# application/vnd.motorola.flexsuite.adsi
# application/vnd.motorola.flexsuite.fis
# application/vnd.motorola.flexsuite.gotap
# application/vnd.motorola.flexsuite.kmr
# application/vnd.motorola.flexsuite.ttc
# application/vnd.motorola.flexsuite.wem
# application/vnd.motorola.iprm
application/vnd.mozilla.xul+xml xul
application/vnd.ms-artgalry cil
# application/vnd.ms-asf
application/vnd.ms-cab-compressed cab
# application/vnd.ms-color.iccprofile
application/vnd.ms-excel xls xlm xla xlc xlt xlw
application/vnd.ms-excel.addin.macroenabled.12 xlam
application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb
application/vnd.ms-excel.sheet.macroenabled.12 xlsm
application/vnd.ms-excel.template.macroenabled.12 xltm
application/vnd.ms-fontobject eot
application/vnd.ms-htmlhelp chm
application/vnd.ms-ims ims
application/vnd.ms-lrm lrm
# application/vnd.ms-office.activex+xml
application/vnd.ms-officetheme thmx
# application/vnd.ms-opentype
# application/vnd.ms-package.obfuscated-opentype
application/vnd.ms-pki.seccat cat
application/vnd.ms-pki.stl stl
# application/vnd.ms-playready.initiator+xml
application/vnd.ms-powerpoint ppt pps pot
application/vnd.ms-powerpoint.addin.macroenabled.12 ppam
application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm
application/vnd.ms-powerpoint.slide.macroenabled.12 sldm
application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm
application/vnd.ms-powerpoint.template.macroenabled.12 potm
# application/vnd.ms-printing.printticket+xml
application/vnd.ms-project mpp mpt
# application/vnd.ms-tnef
# application/vnd.ms-wmdrm.lic-chlg-req
# application/vnd.ms-wmdrm.lic-resp
# application/vnd.ms-wmdrm.meter-chlg-req
# application/vnd.ms-wmdrm.meter-resp
application/vnd.ms-word.document.macroenabled.12 docm
application/vnd.ms-word.template.macroenabled.12 dotm
application/vnd.ms-works wps wks wcm wdb
application/vnd.ms-wpl wpl
application/vnd.ms-xpsdocument xps
application/vnd.mseq mseq
# application/vnd.msign
# application/vnd.multiad.creator
# application/vnd.multiad.creator.cif
# application/vnd.music-niff
application/vnd.musician mus
application/vnd.muvee.style msty
application/vnd.mynfc taglet
# application/vnd.ncd.control
# application/vnd.ncd.reference
# application/vnd.nervana
# application/vnd.netfpx
application/vnd.neurolanguage.nlu nlu
application/vnd.nitf ntf nitf
application/vnd.noblenet-directory nnd
application/vnd.noblenet-sealer nns
application/vnd.noblenet-web nnw
# application/vnd.nokia.catalogs
# application/vnd.nokia.conml+wbxml
# application/vnd.nokia.conml+xml
# application/vnd.nokia.isds-radio-presets
# application/vnd.nokia.iptv.config+xml
# application/vnd.nokia.landmark+wbxml
# application/vnd.nokia.landmark+xml
# application/vnd.nokia.landmarkcollection+xml
# application/vnd.nokia.n-gage.ac+xml
application/vnd.nokia.n-gage.data ngdat
application/vnd.nokia.n-gage.symbian.install n-gage
# application/vnd.nokia.ncd
# application/vnd.nokia.pcd+wbxml
# application/vnd.nokia.pcd+xml
application/vnd.nokia.radio-preset rpst
application/vnd.nokia.radio-presets rpss
application/vnd.novadigm.edm edm
application/vnd.novadigm.edx edx
application/vnd.novadigm.ext ext
# application/vnd.ntt-local.file-transfer
# application/vnd.ntt-local.sip-ta_remote
# application/vnd.ntt-local.sip-ta_tcp_stream
application/vnd.oasis.opendocument.chart odc
application/vnd.oasis.opendocument.chart-template otc
application/vnd.oasis.opendocument.database odb
application/vnd.oasis.opendocument.formula odf
application/vnd.oasis.opendocument.formula-template odft
application/vnd.oasis.opendocument.graphics odg
application/vnd.oasis.opendocument.graphics-template otg
application/vnd.oasis.opendocument.image odi
application/vnd.oasis.opendocument.image-template oti
application/vnd.oasis.opendocument.presentation odp
application/vnd.oasis.opendocument.presentation-template otp
application/vnd.oasis.opendocument.spreadsheet ods
application/vnd.oasis.opendocument.spreadsheet-template ots
application/vnd.oasis.opendocument.text odt
application/vnd.oasis.opendocument.text-master odm
application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-web oth
# application/vnd.obn
# application/vnd.oftn.l10n+json
# application/vnd.oipf.contentaccessdownload+xml
# application/vnd.oipf.contentaccessstreaming+xml
# application/vnd.oipf.cspg-hexbinary
# application/vnd.oipf.dae.svg+xml
# application/vnd.oipf.dae.xhtml+xml
# application/vnd.oipf.mippvcontrolmessage+xml
# application/vnd.oipf.pae.gem
# application/vnd.oipf.spdiscovery+xml
# application/vnd.oipf.spdlist+xml
# application/vnd.oipf.ueprofile+xml
# application/vnd.oipf.userprofile+xml
application/vnd.olpc-sugar xo
# application/vnd.oma-scws-config
# application/vnd.oma-scws-http-request
# application/vnd.oma-scws-http-response
# application/vnd.oma.bcast.associated-procedure-parameter+xml
# application/vnd.oma.bcast.drm-trigger+xml
# application/vnd.oma.bcast.imd+xml
# application/vnd.oma.bcast.ltkm
# application/vnd.oma.bcast.notification+xml
# application/vnd.oma.bcast.provisioningtrigger
# application/vnd.oma.bcast.sgboot
# application/vnd.oma.bcast.sgdd+xml
# application/vnd.oma.bcast.sgdu
# application/vnd.oma.bcast.simple-symbol-container
# application/vnd.oma.bcast.smartcard-trigger+xml
# application/vnd.oma.bcast.sprov+xml
# application/vnd.oma.bcast.stkm
# application/vnd.oma.cab-address-book+xml
# application/vnd.oma.cab-feature-handler+xml
# application/vnd.oma.cab-pcc+xml
# application/vnd.oma.cab-user-prefs+xml
# application/vnd.oma.dcd
# application/vnd.oma.dcdc
application/vnd.oma.dd2+xml dd2
# application/vnd.oma.drm.risd+xml
# application/vnd.oma.group-usage-list+xml
# application/vnd.oma.pal+xml
# application/vnd.oma.poc.detailed-progress-report+xml
# application/vnd.oma.poc.final-report+xml
# application/vnd.oma.poc.groups+xml
# application/vnd.oma.poc.invocation-descriptor+xml
# application/vnd.oma.poc.optimized-progress-report+xml
# application/vnd.oma.push
# application/vnd.oma.scidm.messages+xml
# application/vnd.oma.xcap-directory+xml
# application/vnd.omads-email+xml
# application/vnd.omads-file+xml
# application/vnd.omads-folder+xml
# application/vnd.omaloc-supl-init
application/vnd.openofficeorg.extension oxt
# application/vnd.openxmlformats-officedocument.custom-properties+xml
# application/vnd.openxmlformats-officedocument.customxmlproperties+xml
# application/vnd.openxmlformats-officedocument.drawing+xml
# application/vnd.openxmlformats-officedocument.drawingml.chart+xml
# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml
# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml
# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml
# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml
# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml
# application/vnd.openxmlformats-officedocument.extended-properties+xml
# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml
# application/vnd.openxmlformats-officedocument.presentationml.comments+xml
# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml
# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml
# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml
# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml
application/vnd.openxmlformats-officedocument.presentationml.slide sldx
# application/vnd.openxmlformats-officedocument.presentationml.slide+xml
# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml
# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml
application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml
# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml
# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml
# application/vnd.openxmlformats-officedocument.presentationml.tags+xml
application/vnd.openxmlformats-officedocument.presentationml.template potx
# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml
# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml
application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml
# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml
# application/vnd.openxmlformats-officedocument.theme+xml
# application/vnd.openxmlformats-officedocument.themeoverride+xml
# application/vnd.openxmlformats-officedocument.vmldrawing
# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml
application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml
# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml
# application/vnd.openxmlformats-package.core-properties+xml
# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml
# application/vnd.openxmlformats-package.relationships+xml
# application/vnd.quobject-quoxdocument
# application/vnd.osa.netdeploy
application/vnd.osgeo.mapguide.package mgp
# application/vnd.osgi.bundle
application/vnd.osgi.dp dp
application/vnd.osgi.subsystem esa
# application/vnd.otps.ct-kip+xml
application/vnd.palm pdb pqa oprc
# application/vnd.paos.xml
application/vnd.pawaafile paw
application/vnd.pg.format str
application/vnd.pg.osasli ei6
# application/vnd.piaccess.application-licence
application/vnd.picsel efif
application/vnd.pmi.widget wg
# application/vnd.poc.group-advertisement+xml
application/vnd.pocketlearn plf
application/vnd.powerbuilder6 pbd
# application/vnd.powerbuilder6-s
# application/vnd.powerbuilder7
# application/vnd.powerbuilder7-s
# application/vnd.powerbuilder75
# application/vnd.powerbuilder75-s
# application/vnd.preminet
application/vnd.previewsystems.box box
application/vnd.proteus.magazine mgz
application/vnd.publishare-delta-tree qps
application/vnd.pvi.ptid1 ptid
# application/vnd.pwg-multiplexed
# application/vnd.pwg-xhtml-print+xml
# application/vnd.qualcomm.brew-app-res
application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb
# application/vnd.radisys.moml+xml
# application/vnd.radisys.msml+xml
# application/vnd.radisys.msml-audit+xml
# application/vnd.radisys.msml-audit-conf+xml
# application/vnd.radisys.msml-audit-conn+xml
# application/vnd.radisys.msml-audit-dialog+xml
# application/vnd.radisys.msml-audit-stream+xml
# application/vnd.radisys.msml-conf+xml
# application/vnd.radisys.msml-dialog+xml
# application/vnd.radisys.msml-dialog-base+xml
# application/vnd.radisys.msml-dialog-fax-detect+xml
# application/vnd.radisys.msml-dialog-fax-sendrecv+xml
# application/vnd.radisys.msml-dialog-group+xml
# application/vnd.radisys.msml-dialog-speech+xml
# application/vnd.radisys.msml-dialog-transform+xml
# application/vnd.rainstor.data
# application/vnd.rapid
application/vnd.realvnc.bed bed
application/vnd.recordare.musicxml mxl
application/vnd.recordare.musicxml+xml musicxml
# application/vnd.renlearn.rlprint
application/vnd.rig.cryptonote cryptonote
application/vnd.rim.cod cod
application/vnd.rn-realmedia rm
application/vnd.rn-realmedia-vbr rmvb
application/vnd.route66.link66+xml link66
# application/vnd.rs-274x
# application/vnd.ruckus.download
# application/vnd.s3sms
application/vnd.sailingtracker.track st
# application/vnd.sbm.cid
# application/vnd.sbm.mid2
# application/vnd.scribus
# application/vnd.sealed.3df
# application/vnd.sealed.csf
# application/vnd.sealed.doc
# application/vnd.sealed.eml
# application/vnd.sealed.mht
# application/vnd.sealed.net
# application/vnd.sealed.ppt
# application/vnd.sealed.tiff
# application/vnd.sealed.xls
# application/vnd.sealedmedia.softseal.html
# application/vnd.sealedmedia.softseal.pdf
application/vnd.seemail see
application/vnd.sema sema
application/vnd.semd semd
application/vnd.semf semf
application/vnd.shana.informed.formdata ifm
application/vnd.shana.informed.formtemplate itp
application/vnd.shana.informed.interchange iif
application/vnd.shana.informed.package ipk
application/vnd.simtech-mindmapper twd twds
application/vnd.smaf mmf
# application/vnd.smart.notebook
application/vnd.smart.teacher teacher
# application/vnd.software602.filler.form+xml
# application/vnd.software602.filler.form-xml-zip
application/vnd.solent.sdkm+xml sdkm sdkd
application/vnd.spotfire.dxp dxp
application/vnd.spotfire.sfs sfs
# application/vnd.sss-cod
# application/vnd.sss-dtf
# application/vnd.sss-ntf
application/vnd.stardivision.calc sdc
application/vnd.stardivision.draw sda
application/vnd.stardivision.impress sdd
application/vnd.stardivision.math smf
application/vnd.stardivision.writer sdw vor
application/vnd.stardivision.writer-global sgl
application/vnd.stepmania.package smzip
application/vnd.stepmania.stepchart sm
# application/vnd.street-stream
application/vnd.sun.xml.calc sxc
application/vnd.sun.xml.calc.template stc
application/vnd.sun.xml.draw sxd
application/vnd.sun.xml.draw.template std
application/vnd.sun.xml.impress sxi
application/vnd.sun.xml.impress.template sti
application/vnd.sun.xml.math sxm
application/vnd.sun.xml.writer sxw
application/vnd.sun.xml.writer.global sxg
application/vnd.sun.xml.writer.template stw
# application/vnd.sun.wadl+xml
application/vnd.sus-calendar sus susp
application/vnd.svd svd
# application/vnd.swiftview-ics
application/vnd.symbian.install sis sisx
application/vnd.syncml+xml xsm
application/vnd.syncml.dm+wbxml bdm
application/vnd.syncml.dm+xml xdm
# application/vnd.syncml.dm.notification
# application/vnd.syncml.ds.notification
application/vnd.tao.intent-module-archive tao
application/vnd.tcpdump.pcap pcap cap dmp
application/vnd.tmobile-livetv tmo
application/vnd.trid.tpt tpt
application/vnd.triscape.mxs mxs
application/vnd.trueapp tra
# application/vnd.truedoc
# application/vnd.ubisoft.webplayer
application/vnd.ufdl ufd ufdl
application/vnd.uiq.theme utz
application/vnd.umajin umj
application/vnd.unity unityweb
application/vnd.uoml+xml uoml
# application/vnd.uplanet.alert
# application/vnd.uplanet.alert-wbxml
# application/vnd.uplanet.bearer-choice
# application/vnd.uplanet.bearer-choice-wbxml
# application/vnd.uplanet.cacheop
# application/vnd.uplanet.cacheop-wbxml
# application/vnd.uplanet.channel
# application/vnd.uplanet.channel-wbxml
# application/vnd.uplanet.list
# application/vnd.uplanet.list-wbxml
# application/vnd.uplanet.listcmd
# application/vnd.uplanet.listcmd-wbxml
# application/vnd.uplanet.signal
application/vnd.vcx vcx
# application/vnd.vd-study
# application/vnd.vectorworks
# application/vnd.verimatrix.vcas
# application/vnd.vidsoft.vidconference
application/vnd.visio vsd vst vss vsw
application/vnd.visionary vis
# application/vnd.vividence.scriptfile
application/vnd.vsf vsf
# application/vnd.wap.sic
# application/vnd.wap.slc
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/vnd.webturbo wtb
# application/vnd.wfa.wsc
# application/vnd.wmc
# application/vnd.wmf.bootstrap
# application/vnd.wolfram.mathematica
# application/vnd.wolfram.mathematica.package
application/vnd.wolfram.player nbp
application/vnd.wordperfect wpd
application/vnd.wqd wqd
# application/vnd.wrq-hp3000-labelled
application/vnd.wt.stf stf
# application/vnd.wv.csp+wbxml
# application/vnd.wv.csp+xml
# application/vnd.wv.ssp+xml
application/vnd.xara xar
application/vnd.xfdl xfdl
# application/vnd.xfdl.webform
# application/vnd.xmi+xml
# application/vnd.xmpie.cpkg
# application/vnd.xmpie.dpkg
# application/vnd.xmpie.plan
# application/vnd.xmpie.ppkg
# application/vnd.xmpie.xlim
application/vnd.yamaha.hv-dic hvd
application/vnd.yamaha.hv-script hvs
application/vnd.yamaha.hv-voice hvp
application/vnd.yamaha.openscoreformat osf
application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg
# application/vnd.yamaha.remote-setup
application/vnd.yamaha.smaf-audio saf
application/vnd.yamaha.smaf-phrase spf
# application/vnd.yamaha.through-ngn
# application/vnd.yamaha.tunnel-udpencap
application/vnd.yellowriver-custom-menu cmp
application/vnd.zul zir zirz
application/vnd.zzazz.deck+xml zaz
application/voicexml+xml vxml
# application/vq-rtcpxr
# application/watcherinfo+xml
# application/whoispp-query
# application/whoispp-response
application/widget wgt
application/winhlp hlp
# application/wita
# application/wordperfect5.1
application/wsdl+xml wsdl
application/wspolicy+xml wspolicy
application/x-7z-compressed 7z
application/x-abiword abw
application/x-ace-compressed ace
# application/x-amf
application/x-apple-diskimage dmg
application/x-authorware-bin aab x32 u32 vox
application/x-authorware-map aam
application/x-authorware-seg aas
application/x-bcpio bcpio
application/x-bittorrent torrent
application/x-blorb blb blorb
application/x-bzip bz
application/x-bzip2 bz2 boz
application/x-cbr cbr cba cbt cbz cb7
application/x-cdlink vcd
application/x-cfs-compressed cfs
application/x-chat chat
application/x-chess-pgn pgn
application/x-conference nsc
# application/x-compress
application/x-cpio cpio
application/x-csh csh
application/x-debian-package deb udeb
application/x-dgc-compressed dgc
application/x-director dir dcr dxr cst cct cxt w3d fgd swa
application/x-doom wad
application/x-dtbncx+xml ncx
application/x-dtbook+xml dtb
application/x-dtbresource+xml res
application/x-dvi dvi
application/x-envoy evy
application/x-eva eva
application/x-font-bdf bdf
# application/x-font-dos
# application/x-font-framemaker
application/x-font-ghostscript gsf
# application/x-font-libgrx
application/x-font-linux-psf psf
application/x-font-otf otf
application/x-font-pcf pcf
application/x-font-snf snf
# application/x-font-speedo
# application/x-font-sunos-news
application/x-font-ttf ttf ttc
application/x-font-type1 pfa pfb pfm afm
application/font-woff woff
# application/x-font-vfont
application/x-freearc arc
application/x-futuresplash spl
application/x-gca-compressed gca
application/x-glulx ulx
application/x-gnumeric gnumeric
application/x-gramps-xml gramps
application/x-gtar gtar
# application/x-gzip
application/x-hdf hdf
application/x-install-instructions install
application/x-iso9660-image iso
application/x-java-jnlp-file jnlp
application/x-latex latex
application/x-lzh-compressed lzh lha
application/x-mie mie
application/x-mobipocket-ebook prc mobi
application/x-ms-application application
application/x-ms-shortcut lnk
application/x-ms-wmd wmd
application/x-ms-wmz wmz
application/x-ms-xbap xbap
application/x-msaccess mdb
application/x-msbinder obd
application/x-mscardfile crd
application/x-msclip clp
application/x-msdownload exe dll com bat msi
application/x-msmediaview mvb m13 m14
application/x-msmetafile wmf wmz emf emz
application/x-msmoney mny
application/x-mspublisher pub
application/x-msschedule scd
application/x-msterminal trm
application/x-mswrite wri
application/x-netcdf nc cdf
application/x-nzb nzb
application/x-pkcs12 p12 pfx
application/x-pkcs7-certificates p7b spc
application/x-pkcs7-certreqresp p7r
application/x-rar-compressed rar
application/x-research-info-systems ris
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-silverlight-app xap
application/x-sql sql
application/x-stuffit sit
application/x-stuffitx sitx
application/x-subrip srt
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-t3vm-image t3
application/x-tads gam
application/x-tar tar
application/x-tcl tcl
application/x-tex tex
application/x-tex-tfm tfm
application/x-texinfo texinfo texi
application/x-tgif obj
application/x-ustar ustar
application/x-wais-source src
application/x-x509-ca-cert der crt
application/x-xfig fig
application/x-xliff+xml xlf
application/x-xpinstall xpi
application/x-xz xz
application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8
# application/x400-bp
application/xaml+xml xaml
# application/xcap-att+xml
# application/xcap-caps+xml
application/xcap-diff+xml xdf
# application/xcap-el+xml
# application/xcap-error+xml
# application/xcap-ns+xml
# application/xcon-conference-info-diff+xml
# application/xcon-conference-info+xml
application/xenc+xml xenc
application/xhtml+xml xhtml xht
# application/xhtml-voice+xml
application/xml xml xsl
application/xml-dtd dtd
# application/xml-external-parsed-entity
# application/xmpp+xml
application/xop+xml xop
application/xproc+xml xpl
application/xslt+xml xslt
application/xspf+xml xspf
application/xv+xml mxml xhvml xvml xvm
application/yang yang
application/yin+xml yin
application/zip zip
# audio/1d-interleaved-parityfec
# audio/32kadpcm
# audio/3gpp
# audio/3gpp2
# audio/ac3
audio/adpcm adp
# audio/amr
# audio/amr-wb
# audio/amr-wb+
# audio/asc
# audio/atrac-advanced-lossless
# audio/atrac-x
# audio/atrac3
audio/basic au snd
# audio/bv16
# audio/bv32
# audio/clearmode
# audio/cn
# audio/dat12
# audio/dls
# audio/dsr-es201108
# audio/dsr-es202050
# audio/dsr-es202211
# audio/dsr-es202212
# audio/dv
# audio/dvi4
# audio/eac3
# audio/evrc
# audio/evrc-qcp
# audio/evrc0
# audio/evrc1
# audio/evrcb
# audio/evrcb0
# audio/evrcb1
# audio/evrcwb
# audio/evrcwb0
# audio/evrcwb1
# audio/example
# audio/fwdred
# audio/g719
# audio/g722
# audio/g7221
# audio/g723
# audio/g726-16
# audio/g726-24
# audio/g726-32
# audio/g726-40
# audio/g728
# audio/g729
# audio/g7291
# audio/g729d
# audio/g729e
# audio/gsm
# audio/gsm-efr
# audio/gsm-hr-08
# audio/ilbc
# audio/ip-mr_v2.5
# audio/isac
# audio/l16
# audio/l20
# audio/l24
# audio/l8
# audio/lpc
audio/midi mid midi kar rmi
# audio/mobile-xmf
audio/mp4 mp4a
# audio/mp4a-latm
# audio/mpa
# audio/mpa-robust
audio/mpeg mpga mp2 mp2a mp3 m2a m3a
# audio/mpeg4-generic
# audio/musepack
audio/ogg oga ogg spx
# audio/opus
# audio/parityfec
# audio/pcma
# audio/pcma-wb
# audio/pcmu-wb
# audio/pcmu
# audio/prs.sid
# audio/qcelp
# audio/red
# audio/rtp-enc-aescm128
# audio/rtp-midi
# audio/rtx
audio/s3m s3m
audio/silk sil
# audio/smv
# audio/smv0
# audio/smv-qcp
# audio/sp-midi
# audio/speex
# audio/t140c
# audio/t38
# audio/telephone-event
# audio/tone
# audio/uemclip
# audio/ulpfec
# audio/vdvi
# audio/vmr-wb
# audio/vnd.3gpp.iufp
# audio/vnd.4sb
# audio/vnd.audiokoz
# audio/vnd.celp
# audio/vnd.cisco.nse
# audio/vnd.cmles.radio-events
# audio/vnd.cns.anp1
# audio/vnd.cns.inf1
audio/vnd.dece.audio uva uvva
audio/vnd.digital-winds eol
# audio/vnd.dlna.adts
# audio/vnd.dolby.heaac.1
# audio/vnd.dolby.heaac.2
# audio/vnd.dolby.mlp
# audio/vnd.dolby.mps
# audio/vnd.dolby.pl2
# audio/vnd.dolby.pl2x
# audio/vnd.dolby.pl2z
# audio/vnd.dolby.pulse.1
audio/vnd.dra dra
audio/vnd.dts dts
audio/vnd.dts.hd dtshd
# audio/vnd.dvb.file
# audio/vnd.everad.plj
# audio/vnd.hns.audio
audio/vnd.lucent.voice lvp
audio/vnd.ms-playready.media.pya pya
# audio/vnd.nokia.mobile-xmf
# audio/vnd.nortel.vbk
audio/vnd.nuera.ecelp4800 ecelp4800
audio/vnd.nuera.ecelp7470 ecelp7470
audio/vnd.nuera.ecelp9600 ecelp9600
# audio/vnd.octel.sbc
# audio/vnd.qcelp
# audio/vnd.rhetorex.32kadpcm
audio/vnd.rip rip
# audio/vnd.sealedmedia.softseal.mpeg
# audio/vnd.vmx.cvsd
# audio/vorbis
# audio/vorbis-config
audio/webm weba
audio/x-aac aac
audio/x-aiff aif aiff aifc
audio/x-caf caf
audio/x-flac flac
audio/x-matroska mka
audio/x-mpegurl m3u
audio/x-ms-wax wax
audio/x-ms-wma wma
audio/x-pn-realaudio ram ra
audio/x-pn-realaudio-plugin rmp
# audio/x-tta
audio/x-wav wav
audio/xm xm
chemical/x-cdx cdx
chemical/x-cif cif
chemical/x-cmdf cmdf
chemical/x-cml cml
chemical/x-csml csml
# chemical/x-pdb
chemical/x-xyz xyz
image/bmp bmp
image/cgm cgm
# image/example
# image/fits
image/g3fax g3
image/gif gif
image/ief ief
# image/jp2
image/jpeg jpeg jpg jpe
# image/jpm
# image/jpx
image/ktx ktx
# image/naplps
image/png png
image/prs.btif btif
# image/prs.pti
image/sgi sgi
image/svg+xml svg svgz
# image/t38
image/tiff tiff tif
# image/tiff-fx
image/vnd.adobe.photoshop psd
# image/vnd.cns.inf2
image/vnd.dece.graphic uvi uvvi uvg uvvg
image/vnd.dvb.subtitle sub
image/vnd.djvu djvu djv
image/vnd.dwg dwg
image/vnd.dxf dxf
image/vnd.fastbidsheet fbs
image/vnd.fpx fpx
image/vnd.fst fst
image/vnd.fujixerox.edmics-mmr mmr
image/vnd.fujixerox.edmics-rlc rlc
# image/vnd.globalgraphics.pgb
# image/vnd.microsoft.icon
# image/vnd.mix
image/vnd.ms-modi mdi
image/vnd.ms-photo wdp
image/vnd.net-fpx npx
# image/vnd.radiance
# image/vnd.sealed.png
# image/vnd.sealedmedia.softseal.gif
# image/vnd.sealedmedia.softseal.jpg
# image/vnd.svf
image/vnd.wap.wbmp wbmp
image/vnd.xiff xif
image/webp webp
image/x-3ds 3ds
image/x-cmu-raster ras
image/x-cmx cmx
image/x-freehand fh fhc fh4 fh5 fh7
image/x-icon ico
image/x-mrsid-image sid
image/x-pcx pcx
image/x-pict pic pct
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-tga tga
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
# message/cpim
# message/delivery-status
# message/disposition-notification
# message/example
# message/external-body
# message/feedback-report
# message/global
# message/global-delivery-status
# message/global-disposition-notification
# message/global-headers
# message/http
# message/imdn+xml
# message/news
# message/partial
message/rfc822 eml mime
# message/s-http
# message/sip
# message/sipfrag
# message/tracking-status
# message/vnd.si.simp
# model/example
model/iges igs iges
model/mesh msh mesh silo
model/vnd.collada+xml dae
model/vnd.dwf dwf
# model/vnd.flatland.3dml
model/vnd.gdl gdl
# model/vnd.gs-gdl
# model/vnd.gs.gdl
model/vnd.gtw gtw
# model/vnd.moml+xml
model/vnd.mts mts
# model/vnd.parasolid.transmit.binary
# model/vnd.parasolid.transmit.text
model/vnd.vtu vtu
model/vrml wrl vrml
model/x3d+binary x3db x3dbz
model/x3d+vrml x3dv x3dvz
model/x3d+xml x3d x3dz
# multipart/alternative
# multipart/appledouble
# multipart/byteranges
# multipart/digest
# multipart/encrypted
# multipart/example
# multipart/form-data
# multipart/header-set
# multipart/mixed
# multipart/parallel
# multipart/related
# multipart/report
# multipart/signed
# multipart/voice-message
# text/1d-interleaved-parityfec
text/cache-manifest appcache
text/calendar ics ifb
text/css css
text/csv csv
# text/directory
# text/dns
# text/ecmascript
# text/enriched
# text/example
# text/fwdred
text/html html htm
# text/javascript
text/n3 n3
# text/parityfec
text/plain txt text conf def list log in
# text/prs.fallenstein.rst
text/prs.lines.tag dsc
# text/vnd.radisys.msml-basic-layout
# text/red
# text/rfc822-headers
text/richtext rtx
# text/rtf
# text/rtp-enc-aescm128
# text/rtx
text/sgml sgml sgm
# text/t140
text/tab-separated-values tsv
text/troff t tr roff man me ms
text/turtle ttl
# text/ulpfec
text/uri-list uri uris urls
text/vcard vcard
# text/vnd.abc
text/vnd.curl curl
text/vnd.curl.dcurl dcurl
text/vnd.curl.scurl scurl
text/vnd.curl.mcurl mcurl
# text/vnd.dmclientscript
text/vnd.dvb.subtitle sub
# text/vnd.esmertec.theme-descriptor
text/vnd.fly fly
text/vnd.fmi.flexstor flx
text/vnd.graphviz gv
text/vnd.in3d.3dml 3dml
text/vnd.in3d.spot spot
# text/vnd.iptc.newsml
# text/vnd.iptc.nitf
# text/vnd.latex-z
# text/vnd.motorola.reflex
# text/vnd.ms-mediapackage
# text/vnd.net2phone.commcenter.command
# text/vnd.si.uricatalogue
text/vnd.sun.j2me.app-descriptor jad
# text/vnd.trolltech.linguist
# text/vnd.wap.si
# text/vnd.wap.sl
text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/x-asm s asm
text/x-c c cc cxx cpp h hh dic
text/x-fortran f for f77 f90
text/x-java-source java
text/x-opml opml
text/x-pascal p pas
text/x-nfo nfo
text/x-setext etx
text/x-sfv sfv
text/x-uuencode uu
text/x-vcalendar vcs
text/x-vcard vcf
# text/xml
# text/xml-external-parsed-entity
# video/1d-interleaved-parityfec
video/3gpp 3gp
# video/3gpp-tt
video/3gpp2 3g2
# video/bmpeg
# video/bt656
# video/celb
# video/dv
# video/example
video/h261 h261
video/h263 h263
# video/h263-1998
# video/h263-2000
video/h264 h264
# video/h264-rcdo
# video/h264-svc
video/jpeg jpgv
# video/jpeg2000
video/jpm jpm jpgm
video/mj2 mj2 mjp2
# video/mp1s
# video/mp2p
# video/mp2t
video/mp4 mp4 mp4v mpg4
# video/mp4v-es
video/mpeg mpeg mpg mpe m1v m2v
# video/mpeg4-generic
# video/mpv
# video/nv
video/ogg ogv
# video/parityfec
# video/pointer
video/quicktime qt mov
# video/raw
# video/rtp-enc-aescm128
# video/rtx
# video/smpte292m
# video/ulpfec
# video/vc1
# video/vnd.cctv
video/vnd.dece.hd uvh uvvh
video/vnd.dece.mobile uvm uvvm
# video/vnd.dece.mp4
video/vnd.dece.pd uvp uvvp
video/vnd.dece.sd uvs uvvs
video/vnd.dece.video uvv uvvv
# video/vnd.directv.mpeg
# video/vnd.directv.mpeg-tts
# video/vnd.dlna.mpeg-tts
video/vnd.dvb.file dvb
video/vnd.fvt fvt
# video/vnd.hns.video
# video/vnd.iptvforum.1dparityfec-1010
# video/vnd.iptvforum.1dparityfec-2005
# video/vnd.iptvforum.2dparityfec-1010
# video/vnd.iptvforum.2dparityfec-2005
# video/vnd.iptvforum.ttsavc
# video/vnd.iptvforum.ttsmpeg2
# video/vnd.motorola.video
# video/vnd.motorola.videop
video/vnd.mpegurl mxu m4u
video/vnd.ms-playready.media.pyv pyv
# video/vnd.nokia.interleaved-multimedia
# video/vnd.nokia.videovoip
# video/vnd.objectvideo
# video/vnd.sealed.mpeg1
# video/vnd.sealed.mpeg4
# video/vnd.sealed.swf
# video/vnd.sealedmedia.softseal.mov
video/vnd.uvvu.mp4 uvu uvvu
video/vnd.vivo viv
video/webm webm
video/x-f4v f4v
video/x-fli fli
video/x-flv flv
video/x-m4v m4v
video/x-matroska mkv mk3d mks
video/x-mng mng
video/x-ms-asf asf asx
video/x-ms-vob vob
video/x-ms-wm wm
video/x-ms-wmv wmv
video/x-ms-wmx wmx
video/x-ms-wvx wvx
video/x-msvideo avi
video/x-sgi-movie movie
video/x-smv smv
x-conference/x-cooltalk ice
# What: WebVTT
# Why: To allow formats intended for marking up external text track resources.
# http://dev.w3.org/html5/webvtt/
# Added by: niftylettuce
text/vtt vtt
# What: Google Chrome Extension
# Why: To allow apps to (work) be served with the right content type header.
# http://codereview.chromium.org/2830017
# Added by: niftylettuce
application/x-chrome-extension crx
# What: HTC support
# Why: To properly render .htc files such as CSS3PIE
# Added by: niftylettuce
text/x-component htc
# What: HTML5 application cache manifes ('.manifest' extension)
# Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps
# per https://developer.mozilla.org/en/offline_resources_in_firefox
# Added by: louisremi
text/cache-manifest manifest
# What: node binary buffer format
# Why: semi-standard extension w/in the node community
# Added by: tootallnate
application/octet-stream buffer
# What: The "protected" MP-4 formats used by iTunes.
# Why: Required for streaming music to browsers (?)
# Added by: broofa
application/mp4 m4p
audio/mp4 m4a
# What: Video format, Part of RFC1890
# Why: See https://github.com/bentomas/node-mime/pull/6
# Added by: mjrusso
video/MP2T ts
# What: EventSource mime type
# Why: mime type of Server-Sent Events stream
# http://www.w3.org/TR/eventsource/#text-event-stream
# Added by: francois2metz
text/event-stream event-stream
# What: Mozilla App manifest mime type
# Why: https://developer.mozilla.org/en/Apps/Manifest#Serving_manifests
# Added by: ednapiranha
application/x-web-app-manifest+json webapp
# What: Lua file types
# Why: Googling around shows de-facto consensus on these
# Added by: creationix (Issue #45)
text/x-lua lua
application/x-lua-bytecode luac
# What: Markdown files, as per http://daringfireball.net/projects/markdown/syntax
# Why: http://stackoverflow.com/questions/10701983/what-is-the-mime-type-for-markdown
# Added by: avoidwork
text/x-markdown markdown md mkd
# What: ini files
# Why: because they're just text files
# Added by: Matthew Kastor
text/plain ini
# What: DASH Adaptive Streaming manifest
# Why: https://developer.mozilla.org/en-US/docs/DASH_Adaptive_Streaming_for_HTML_5_Video
# Added by: eelcocramer
application/dash+xml mdp
# What: OpenType font files - http://www.microsoft.com/typography/otspec/
# Why: Browsers usually ignore the font MIME types and sniff the content,
# but Chrome, shows a warning if OpenType fonts aren't served with
# the `font/opentype` MIME type: http://i.imgur.com/8c5RN8M.png.
# Added by: alrra
font/opentype otf
{
"name": "type-is",
"description": "Infer the content type if a request",
"version": "1.1.0",
"author": {
"name": "Jonathan Ong",
"email": "[email protected]",
"url": "http://jongleberry.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/expressjs/type-is"
},
"dependencies": {
"mime": "~1.2.11"
},
"devDependencies": {
"mocha": "*",
"should": "*"
},
"scripts": {
"test": "mocha --require should --reporter spec --bail"
},
"readme": "# Type Is [![Build Status](https://travis-ci.org/expressjs/type-is.png)](https://travis-ci.org/expressjs/type-is)\n\nInfer the content type of a request. \nExtracted from [koa](https://github.com/koajs/koa) for general use.\n\nHere's an example body parser:\n\n```js\nvar is = require('type-is');\nvar parse = require('body');\nvar busboy = require('busboy');\n\nfunction bodyParser(req, res, next) {\n var hasRequestBody = 'content-type' in req.headers\n || 'transfer-encoding' in req.headers;\n if (!hasRequestBody) return next();\n \n switch (is(req, ['urlencoded', 'json', 'multipart'])) {\n case 'urlencoded':\n // parse urlencoded body\n break\n case 'json':\n // parse json body\n break\n case 'multipart':\n // parse multipart body\n break\n default:\n // 415 error code\n }\n}\n```\n\n## API\n\n### var type = is(request, types)\n\n```js\nvar is = require('type-is')\n\nhttp.createServer(function (req, res) {\n is(req, ['text/*'])\n})\n```\n\n`request` is the node HTTP request. `types` is an array of types. Each type can be:\n\n- An extension name such as `json`. This name will be returned if matched.\n- A mime type such as `application/json`.\n- A mime type with a wildcard such as `*/json` or `application/*`. The full mime type will be returned if matched\n\n`false` will be returned if no type matches.\n\nExamples:\n\n```js\n// req.headers.content-type = 'application/json'\nis(req, ['json']) // -> 'json'\nis(req, ['html', 'json']) // -> 'json'\nis(req, ['application/*']) // -> 'application/json'\nis(req, ['application/json']) // -> 'application/json'\nis(req, ['html']) // -> false\n```\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Jonathan Ong [email protected]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/expressjs/type-is/issues"
},
"homepage": "https://github.com/expressjs/type-is",
"_id": "[email protected]",
"_from": "type-is@~1.1.0"
}

Type Is Build Status

Infer the content type of a request. Extracted from koa for general use.

Here's an example body parser:

var is = require('type-is');
var parse = require('body');
var busboy = require('busboy');

function bodyParser(req, res, next) {
  var hasRequestBody = 'content-type' in req.headers
    || 'transfer-encoding' in req.headers;
  if (!hasRequestBody) return next();
  
  switch (is(req, ['urlencoded', 'json', 'multipart'])) {
    case 'urlencoded':
      // parse urlencoded body
      break
    case 'json':
      // parse json body
      break
    case 'multipart':
      // parse multipart body
      break
    default:
      // 415 error code
  }
}

API

var type = is(request, types)

var is = require('type-is')

http.createServer(function (req, res) {
  is(req, ['text/*'])
})

request is the node HTTP request. types is an array of types. Each type can be:

  • An extension name such as json. This name will be returned if matched.
  • A mime type such as application/json.
  • A mime type with a wildcard such as */json or application/*. The full mime type will be returned if matched

false will be returned if no type matches.

Examples:

// req.headers.content-type = 'application/json'
is(req, ['json']) // -> 'json'
is(req, ['html', 'json']) // -> 'json'
is(req, ['application/*']) // -> 'application/json'
is(req, ['application/json']) // -> 'application/json'
is(req, ['html']) // -> false

License

The MIT License (MIT)

Copyright (c) 2013 Jonathan Ong [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

{
"name": "body-parser",
"description": "Connect's body parsing middleware",
"version": "1.0.2",
"author": {
"name": "Jonathan Ong",
"email": "[email protected]",
"url": "http://jongleberry.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/expressjs/body-parser"
},
"dependencies": {
"type-is": "~1.1.0",
"raw-body": "~1.1.2",
"qs": "~0.6.6"
},
"devDependencies": {
"connect": "*",
"mocha": "*",
"should": "*",
"supertest": "*"
},
"scripts": {
"test": "make test"
},
"readme": "# Body Parser [![Build Status](https://travis-ci.org/expressjs/body-parser.png)](https://travis-ci.org/expressjs/body-parser)\n\nConnect's body parsing middleware.\n\n## API\n\n```js\nvar bodyParser = require('body-parser');\n\nvar app = connect();\n\napp.use(bodyParser());\n\napp.use(function (req, res, next) {\n console.log(req.body) // populated!\n next();\n})\n```\n\n### bodyParser([options])\n\nReturns middleware that parses both `json` and `urlencoded`. The `options` are passed to both middleware.\n\n### bodyParser.json([options])\n\nReturns middleware that only parses `json`. The options are:\n\n- `strict` <true> - only parse objects and arrays\n- `limit` <1mb> - maximum request body size\n- `reviver` - passed to `JSON.parse()`\n\n### bodyParser.urlencoded([options])\n\nReturns middleware that only parses `urlencoded` with the [qs](https://github.com/visionmedia/node-querystring) module. The options are:\n\n- `limit` <1mb> - maximum request body size\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Jonathan Ong [email protected]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/expressjs/body-parser/issues"
},
"homepage": "https://github.com/expressjs/body-parser",
"_id": "[email protected]",
"_from": "body-parser@~1.0.0"
}

Body Parser Build Status

Connect's body parsing middleware.

API

var bodyParser = require('body-parser');

var app = connect();

app.use(bodyParser());

app.use(function (req, res, next) {
  console.log(req.body) // populated!
  next();
})

bodyParser([options])

Returns middleware that parses both json and urlencoded. The options are passed to both middleware.

bodyParser.json([options])

Returns middleware that only parses json. The options are:

  • strict - only parse objects and arrays
  • limit <1mb> - maximum request body size
  • reviver - passed to JSON.parse()

bodyParser.urlencoded([options])

Returns middleware that only parses urlencoded with the qs module. The options are:

  • limit <1mb> - maximum request body size

License

The MIT License (MIT)

Copyright (c) 2014 Jonathan Ong [email protected]

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

node_modules
components
.DS_Store
npm-debug.log
tmp
*~
*.swp
*.log
*.pid
*.swo

0.1.7

  • Always add _id to documents to ensure replica sets can work correctly

0.1.8

  • Use connects sessionID as _id
var util = require('util'),
MongoClient = require('mongodb').MongoClient;
/**
* Return the `MongoStore` extending `connect`'s session Store.
*
* @param {object} connect
* @return {Function}
* @api public
*/
module.exports = function(connect) {
/**
* Initialize a new `MongoStore`.
*
* @api public
*/
function MongoStore(uri, options) {
var self = this
this.options = options || (options = {})
options.collectionName || (options.collectionName = 'sessions')
// 1 day
options.ttl || (options.ttl = 24 * 60 * 60 * 1000)
// 60 s
options.cleanupInterval || (options.cleanupInterval = 60 * 1000)
options.server || (options.server = {})
options.server.auto_reconnect != null || (options.server.auto_reconnect = true)
this._error = function(err) {
if (err) self.emit('error', err)
}
// It's a Db instance.
if (uri.collection) {
this.db = uri
this._setup()
} else {
MongoClient.connect(uri, options, function(err, db) {
if (err) return self._error(err)
self.db = db
self._setup()
})
}
}
util.inherits(MongoStore, connect.session.Store)
/**
* Attempt to fetch session by the given `id`.
*
* @param {String} id
* @param {Function} callback
* @api public
*/
MongoStore.prototype.get = function(id, callback) {
this.collection.findOne({_id: id}, function(err, doc) {
callback(err, doc ? doc.sess : null)
})
}
/**
* Commit the given `sess` object associated with the given `id`.
*
* @param {String} id
* @param {Session} sess
* @param {Function} [callback]
* @api public
*/
MongoStore.prototype.set = function(id, sess, callback) {
this.collection.update(
{_id: id},
{$set: {
sess: sess,
expires: Date.now() + this.options.ttl
}},
{upsert: true},
callback || this._error
)
}
/**
* Destroy the session associated with the given `id`.
*
* @param {String} id
* @param {Function} [callback]
* @api public
*/
MongoStore.prototype.destroy = function(id, callback) {
this.collection.remove({_id: id}, callback || this._error)
}
/**
* Invoke the given callback `callback` with all active sessions.
*
* @param {Function} callback
* @api public
*/
MongoStore.prototype.all = function(callback) {
this.collection.find().toArray(function(err, docs) {
var sess = []
if (err) return callback(err)
docs.forEach(function(doc) {
sess.push(doc.sess)
})
callback(null, sess)
})
}
/**
* Clear all sessions.
*
* @param {Function} [callback]
* @api public
*/
MongoStore.prototype.clear = function(callback) {
this.collection.remove({}, callback || this._error)
}
/**
* Fetch number of sessions.
*
* @param {Function} callback
* @api public
*/
MongoStore.prototype.length = function(callback) {
this.collection.count({}, callback)
}
/**
* Setup collection, cleanup, error handler.
*/
MongoStore.prototype._setup = function() {
var self = this
this.db
.on('error', this._error)
.createCollection(
this.options.collectionName,
function(err, collection) {
if (err) return self._error(err)
self.collection = collection
setInterval(function() {
collection.remove({expires: {$lt: Date.now()}}, self._error)
}, self.options.cleanupInterval)
self.emit('connect')
}
)
}
return MongoStore
}
The MIT License (MIT)
Copyright (c) 2008-2013 Oleg Slobodskoi
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
test:
./node_modules/.bin/qunit -d connect:./node_modules/connect -c store:./index.js -t ./test.js
language: node_js
node_js:
- 0.6
- 0.8
- 0.10 # development version of 0.8, may be unstable

Contributing to the driver

Bugfixes

  • Before starting to write code, look for existing tickets or create one for your specific issue. That way you avoid working on something that might not be of interest or that has been addressed already in a different branch.
  • Fork the repo or for small documentation changes, navigate to the source on github and click the Edit button.
  • Follow the general coding style of the rest of the project:
    • 2 space tabs
    • no trailing whitespace
    • comma last
    • inline documentation for new methods, class members, etc
    • 0 space between conditionals/functions, and their parenthesis and curly braces
      • if(..) {
      • for(..) {
      • while(..) {
      • function(err) {
  • Write tests and make sure they pass (execute make test from the cmd line to run the test suite).

Documentation

To contribute to the API documentation just make your changes to the inline documentation of the appropriate source code in the master branch and submit a pull request. You might also use the github Edit button.

If you'd like to preview your documentation changes, first commit your changes to your local master branch, then execute make generate_docs. Make sure you have the python documentation framework sphinx installed easy_install sphinx. The docs are generated under `docs/build'. If all looks good, submit a pull request to the master branch with your changes.

/*!
* Module dependencies.
*/
var Collection = require('./collection').Collection,
Cursor = require('./cursor').Cursor,
DbCommand = require('./commands/db_command').DbCommand,
utils = require('./utils');
/**
* Allows the user to access the admin functionality of MongoDB
*
* @class Represents the Admin methods of MongoDB.
* @param {Object} db Current db instance we wish to perform Admin operations on.
* @return {Function} Constructor for Admin type.
*/
function Admin(db) {
if(!(this instanceof Admin)) return new Admin(db);
this.db = db;
};
/**
* Retrieve the server information for the current
* instance of the db client
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from buildInfo or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.buildInfo = function(callback) {
this.serverInfo(callback);
}
/**
* Retrieve the server information for the current
* instance of the db client
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverInfo or null if an error occured.
* @return {null} Returns no result
* @api private
*/
Admin.prototype.serverInfo = function(callback) {
this.db.executeDbAdminCommand({buildinfo:1}, function(err, doc) {
if(err != null) return callback(err, null);
return callback(null, doc.documents[0]);
});
}
/**
* Retrieve this db's server status.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverStatus or null if an error occured.
* @return {null}
* @api public
*/
Admin.prototype.serverStatus = function(callback) {
var self = this;
this.db.executeDbAdminCommand({serverStatus: 1}, function(err, doc) {
if(err == null && doc.documents[0].ok === 1) {
callback(null, doc.documents[0]);
} else {
if(err) return callback(err, false);
return callback(utils.toError(doc.documents[0]), false);
}
});
};
/**
* Retrieve the current profiling Level for MongoDB
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingLevel or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.profilingLevel = function(callback) {
var self = this;
this.db.executeDbAdminCommand({profile:-1}, function(err, doc) {
doc = doc.documents[0];
if(err == null && doc.ok === 1) {
var was = doc.was;
if(was == 0) return callback(null, "off");
if(was == 1) return callback(null, "slow_only");
if(was == 2) return callback(null, "all");
return callback(new Error("Error: illegal profiling level value " + was), null);
} else {
err != null ? callback(err, null) : callback(new Error("Error with profile command"), null);
}
});
};
/**
* Ping the MongoDB server and retrieve results
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ping or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.ping = function(options, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
this.db.executeDbAdminCommand({ping: 1}, callback);
}
/**
* Authenticate against MongoDB
*
* @param {String} username The user name for the authentication.
* @param {String} password The password for the authentication.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authenticate or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.authenticate = function(username, password, callback) {
this.db.authenticate(username, password, {authdb: 'admin'}, function(err, doc) {
return callback(err, doc);
})
}
/**
* Logout current authenticated user
*
* @param {Object} [options] Optional parameters to the command.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.logout = function(callback) {
this.db.logout({authdb: 'admin'}, function(err, doc) {
return callback(err, doc);
})
}
/**
* Add a user to the MongoDB server, if the user exists it will
* overwrite the current password
*
* Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} username The user name for the authentication.
* @param {String} password The password for the authentication.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.addUser = function(username, password, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
options = args.length ? args.shift() : {};
options.dbName = 'admin';
// Add user
this.db.addUser(username, password, options, function(err, doc) {
return callback(err, doc);
})
}
/**
* Remove a user from the MongoDB server
*
* Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} username The user name for the authentication.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.removeUser = function(username, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() : {};
options.dbName = 'admin';
this.db.removeUser(username, options, function(err, doc) {
return callback(err, doc);
})
}
/**
* Set the current profiling level of MongoDB
*
* @param {String} level The new profiling level (off, slow_only, all)
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from setProfilingLevel or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.setProfilingLevel = function(level, callback) {
var self = this;
var command = {};
var profile = 0;
if(level == "off") {
profile = 0;
} else if(level == "slow_only") {
profile = 1;
} else if(level == "all") {
profile = 2;
} else {
return callback(new Error("Error: illegal profiling level value " + level));
}
// Set up the profile number
command['profile'] = profile;
this.db.executeDbAdminCommand(command, function(err, doc) {
doc = doc.documents[0];
if(err == null && doc.ok === 1)
return callback(null, level);
return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null);
});
};
/**
* Retrive the current profiling information for MongoDB
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingInfo or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.profilingInfo = function(callback) {
try {
new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}, {}, {dbName: 'admin'}).toArray(function(err, items) {
return callback(err, items);
});
} catch (err) {
return callback(err, null);
}
};
/**
* Execute a db command against the Admin database
*
* @param {Object} command A command object `{ping:1}`.
* @param {Object} [options] Optional parameters to the command.
* @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.command = function(command, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() : {};
// Execute a command
this.db.executeDbAdminCommand(command, options, function(err, doc) {
// Ensure change before event loop executes
return callback != null ? callback(err, doc) : null;
});
}
/**
* Validate an existing collection
*
* @param {String} collectionName The name of the collection to validate.
* @param {Object} [options] Optional parameters to the command.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from validateCollection or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.validateCollection = function(collectionName, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() : {};
var self = this;
var command = {validate: collectionName};
var keys = Object.keys(options);
// Decorate command with extra options
for(var i = 0; i < keys.length; i++) {
if(options.hasOwnProperty(keys[i])) {
command[keys[i]] = options[keys[i]];
}
}
this.db.executeDbCommand(command, function(err, doc) {
if(err != null) return callback(err, null);
doc = doc.documents[0];
if(doc.ok === 0)
return callback(new Error("Error with validate command"), null);
if(doc.result != null && doc.result.constructor != String)
return callback(new Error("Error with validation data"), null);
if(doc.result != null && doc.result.match(/exception|corrupt/) != null)
return callback(new Error("Error: invalid collection " + collectionName), null);
if(doc.valid != null && !doc.valid)
return callback(new Error("Error: invalid collection " + collectionName), null);
return callback(null, doc);
});
};
/**
* List the available databases
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from listDatabases or null if an error occured.
* @return {null} Returns no result
* @api public
*/
Admin.prototype.listDatabases = function(callback) {
// Execute the listAllDatabases command
this.db.executeDbAdminCommand({listDatabases:1}, {}, function(err, doc) {
if(err != null) return callback(err, null);
return callback(null, doc.documents[0]);
});
}
/**
* Get ReplicaSet status
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from replSetGetStatus or null if an error occured.
* @return {null}
* @api public
*/
Admin.prototype.replSetGetStatus = function(callback) {
var self = this;
this.db.executeDbAdminCommand({replSetGetStatus:1}, function(err, doc) {
if(err == null && doc.documents[0].ok === 1)
return callback(null, doc.documents[0]);
if(err) return callback(err, false);
return callback(utils.toError(doc.documents[0]), false);
});
};
/**
* @ignore
*/
exports.Admin = Admin;
var DbCommand = require('../commands/db_command').DbCommand
, utils = require('../utils');
var authenticate = function(db, username, password, authdb, options, callback) {
var numberOfConnections = 0;
var errorObject = null;
if(options['connection'] != null) {
//if a connection was explicitly passed on options, then we have only one...
numberOfConnections = 1;
} else {
// Get the amount of connections in the pool to ensure we have authenticated all comments
numberOfConnections = db.serverConfig.allRawConnections().length;
options['onAll'] = true;
}
// Execute all four
db._executeQueryCommand(DbCommand.createGetNonceCommand(db), options, function(err, result, connection) {
// Execute on all the connections
if(err == null) {
// Nonce used to make authentication request with md5 hash
var nonce = result.documents[0].nonce;
// Execute command
db._executeQueryCommand(DbCommand.createAuthenticationCommand(db, username, password, nonce, authdb), {connection:connection}, function(err, result) {
// Count down
numberOfConnections = numberOfConnections - 1;
// Ensure we save any error
if(err) {
errorObject = err;
} else if(result
&& Array.isArray(result.documents)
&& result.documents.length > 0
&& (result.documents[0].err != null || result.documents[0].errmsg != null)) {
errorObject = utils.toError(result.documents[0]);
}
// Work around the case where the number of connections are 0
if(numberOfConnections <= 0 && typeof callback == 'function') {
var internalCallback = callback;
callback = null;
if(errorObject == null
&& result && Array.isArray(result.documents) && result.documents.length > 0
&& result.documents[0].ok == 1) { // We authenticated correctly save the credentials
db.serverConfig.auth.add('MONGODB-CR', db.databaseName, username, password, authdb);
// Return callback
internalCallback(errorObject, true);
} else {
internalCallback(errorObject, false);
}
}
});
}
});
}
exports.authenticate = authenticate;
var DbCommand = require('../commands/db_command').DbCommand
, utils = require('../utils')
, format = require('util').format;
// Kerberos class
var Kerberos = null;
var MongoAuthProcess = null;
// Try to grab the Kerberos class
try {
Kerberos = require('kerberos').Kerberos
// Authentication process for Mongo
MongoAuthProcess = require('kerberos').processes.MongoAuthProcess
} catch(err) {}
var authenticate = function(db, username, password, authdb, options, callback) {
var numberOfConnections = 0;
var errorObject = null;
// We don't have the Kerberos library
if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
if(options['connection'] != null) {
//if a connection was explicitly passed on options, then we have only one...
numberOfConnections = 1;
} else {
// Get the amount of connections in the pool to ensure we have authenticated all comments
numberOfConnections = db.serverConfig.allRawConnections().length;
options['onAll'] = true;
}
// Grab all the connections
var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections();
var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
var error = null;
// Authenticate all connections
for(var i = 0; i < numberOfConnections; i++) {
// Start Auth process for a connection
GSSAPIInitialize(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) {
// Adjust number of connections left to connect
numberOfConnections = numberOfConnections - 1;
// If we have an error save it
if(err) error = err;
// We are done
if(numberOfConnections == 0) {
if(err) return callback(error, false);
// We authenticated correctly save the credentials
db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName);
// Return valid callback
return callback(null, true);
}
});
}
}
//
// Initialize step
var GSSAPIInitialize = function(db, username, password, authdb, gssapiServiceName, connection, callback) {
// Create authenticator
var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, gssapiServiceName);
// Perform initialization
mongo_auth_process.init(username, password, function(err, context) {
if(err) return callback(err, false);
// Perform the first step
mongo_auth_process.transition('', function(err, payload) {
if(err) return callback(err, false);
// Call the next db step
MongoDBGSSAPIFirstStep(mongo_auth_process, payload, db, username, password, authdb, connection, callback);
});
});
}
//
// Perform first step against mongodb
var MongoDBGSSAPIFirstStep = function(mongo_auth_process, payload, db, username, password, authdb, connection, callback) {
// Build the sasl start command
var command = {
saslStart: 1
, mechanism: 'GSSAPI'
, payload: payload
, autoAuthorize: 1
};
// Execute first sasl step
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err, false);
// Get the payload
doc = doc.documents[0];
var db_payload = doc.payload;
mongo_auth_process.transition(doc.payload, function(err, payload) {
if(err) return callback(err, false);
// MongoDB API Second Step
MongoDBGSSAPISecondStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback);
});
});
}
//
// Perform first step against mongodb
var MongoDBGSSAPISecondStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) {
// Build Authentication command to send to MongoDB
var command = {
saslContinue: 1
, conversationId: doc.conversationId
, payload: payload
};
// Execute the command
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err, false);
// Get the result document
doc = doc.documents[0];
// Call next transition for kerberos
mongo_auth_process.transition(doc.payload, function(err, payload) {
if(err) return callback(err, false);
// Call the last and third step
MongoDBGSSAPIThirdStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback);
});
});
}
var MongoDBGSSAPIThirdStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) {
// Build final command
var command = {
saslContinue: 1
, conversationId: doc.conversationId
, payload: payload
};
// Let's finish the auth process against mongodb
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err, false);
mongo_auth_process.transition(null, function(err, payload) {
if(err) return callback(err, false);
callback(null, true);
});
});
}
exports.authenticate = authenticate;
var DbCommand = require('../commands/db_command').DbCommand
, utils = require('../utils')
, Binary = require('bson').Binary
, format = require('util').format;
var authenticate = function(db, username, password, options, callback) {
var numberOfConnections = 0;
var errorObject = null;
if(options['connection'] != null) {
//if a connection was explicitly passed on options, then we have only one...
numberOfConnections = 1;
} else {
// Get the amount of connections in the pool to ensure we have authenticated all comments
numberOfConnections = db.serverConfig.allRawConnections().length;
options['onAll'] = true;
}
// Create payload
var payload = new Binary(format("\x00%s\x00%s", username, password));
// Let's start the sasl process
var command = {
saslStart: 1
, mechanism: 'PLAIN'
, payload: payload
, autoAuthorize: 1
};
// Grab all the connections
var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections();
// Authenticate all connections
for(var i = 0; i < numberOfConnections; i++) {
var connection = connections[i];
// Execute first sasl step
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, result) {
// Count down
numberOfConnections = numberOfConnections - 1;
// Ensure we save any error
if(err) {
errorObject = err;
} else if(result.documents[0].err != null || result.documents[0].errmsg != null){
errorObject = utils.toError(result.documents[0]);
}
// Work around the case where the number of connections are 0
if(numberOfConnections <= 0 && typeof callback == 'function') {
var internalCallback = callback;
callback = null;
if(errorObject == null && result.documents[0].ok == 1) {
// We authenticated correctly save the credentials
db.serverConfig.auth.add('PLAIN', db.databaseName, username, password);
// Return callback
internalCallback(errorObject, true);
} else {
internalCallback(errorObject, false);
}
}
});
}
}
exports.authenticate = authenticate;
var DbCommand = require('../commands/db_command').DbCommand
, utils = require('../utils')
, format = require('util').format;
// Kerberos class
var Kerberos = null;
var MongoAuthProcess = null;
// Try to grab the Kerberos class
try {
Kerberos = require('kerberos').Kerberos
// Authentication process for Mongo
MongoAuthProcess = require('kerberos').processes.MongoAuthProcess
} catch(err) {}
var authenticate = function(db, username, password, authdb, options, callback) {
var numberOfConnections = 0;
var errorObject = null;
// We don't have the Kerberos library
if(Kerberos == null) return callback(new Error("Kerberos library is not installed"));
if(options['connection'] != null) {
//if a connection was explicitly passed on options, then we have only one...
numberOfConnections = 1;
} else {
// Get the amount of connections in the pool to ensure we have authenticated all comments
numberOfConnections = db.serverConfig.allRawConnections().length;
options['onAll'] = true;
}
// Set the sspi server name
var gssapiServiceName = options['gssapiServiceName'] || 'mongodb';
// Grab all the connections
var connections = db.serverConfig.allRawConnections();
var error = null;
// Authenticate all connections
for(var i = 0; i < numberOfConnections; i++) {
// Start Auth process for a connection
SSIPAuthenticate(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) {
// Adjust number of connections left to connect
numberOfConnections = numberOfConnections - 1;
// If we have an error save it
if(err) error = err;
// We are done
if(numberOfConnections == 0) {
if(err) return callback(err, false);
// We authenticated correctly save the credentials
db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName);
// Return valid callback
return callback(null, true);
}
});
}
}
var SSIPAuthenticate = function(db, username, password, authdb, service_name, connection, callback) {
// --------------------------------------------------------------
// Async Version
// --------------------------------------------------------------
var command = {
saslStart: 1
, mechanism: 'GSSAPI'
, payload: ''
, autoAuthorize: 1
};
// Create authenticator
var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, service_name);
// Execute first sasl step
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err);
doc = doc.documents[0];
mongo_auth_process.init(username, password, function(err) {
if(err) return callback(err);
mongo_auth_process.transition(doc.payload, function(err, payload) {
if(err) return callback(err);
// Perform the next step against mongod
var command = {
saslContinue: 1
, conversationId: doc.conversationId
, payload: payload
};
// Execute the command
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err);
doc = doc.documents[0];
mongo_auth_process.transition(doc.payload, function(err, payload) {
if(err) return callback(err);
// Perform the next step against mongod
var command = {
saslContinue: 1
, conversationId: doc.conversationId
, payload: payload
};
// Execute the command
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err);
doc = doc.documents[0];
mongo_auth_process.transition(doc.payload, function(err, payload) {
// Perform the next step against mongod
var command = {
saslContinue: 1
, conversationId: doc.conversationId
, payload: payload
};
// Execute the command
db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) {
if(err) return callback(err);
doc = doc.documents[0];
if(doc.done) return callback(null, true);
callback(new Error("Authentication failed"), false);
});
});
});
});
});
});
});
});
}
exports.authenticate = authenticate;
/**
* Module dependencies.
* @ignore
*/
var InsertCommand = require('./commands/insert_command').InsertCommand
, QueryCommand = require('./commands/query_command').QueryCommand
, DeleteCommand = require('./commands/delete_command').DeleteCommand
, UpdateCommand = require('./commands/update_command').UpdateCommand
, DbCommand = require('./commands/db_command').DbCommand
, ObjectID = require('bson').ObjectID
, Code = require('bson').Code
, Cursor = require('./cursor').Cursor
, utils = require('./utils');
/**
* Precompiled regexes
* @ignore
**/
const eErrorMessages = /No matching object found/;
/**
* toString helper.
* @ignore
*/
var toString = Object.prototype.toString;
/**
* Create a new Collection instance (INTERNAL TYPE)
*
* Options
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **slaveOk** {Boolean, default:false}, Allow reads from secondaries.
* - **serializeFunctions** {Boolean, default:false}, serialize functions on the document.
* - **raw** {Boolean, default:false}, perform all operations using raw bson objects.
* - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation.
*
* @class Represents a Collection
* @param {Object} db db instance.
* @param {String} collectionName collection name.
* @param {Object} [pkFactory] alternative primary key factory.
* @param {Object} [options] additional options for the collection.
* @return {Object} a collection instance.
*/
function Collection (db, collectionName, pkFactory, options) {
if(!(this instanceof Collection)) return new Collection(db, collectionName, pkFactory, options);
checkCollectionName(collectionName);
this.db = db;
this.collectionName = collectionName;
this.internalHint = null;
this.opts = options != null && ('object' === typeof options) ? options : {};
this.slaveOk = options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk;
this.serializeFunctions = options == null || options.serializeFunctions == null ? db.serializeFunctions : options.serializeFunctions;
this.raw = options == null || options.raw == null ? db.raw : options.raw;
this.readPreference = options == null || options.readPreference == null ? db.serverConfig.options.readPreference : options.readPreference;
this.readPreference = this.readPreference == null ? 'primary' : this.readPreference;
this.pkFactory = pkFactory == null
? ObjectID
: pkFactory;
var self = this;
}
/**
* Inserts a single document or a an array of documents into MongoDB.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **continueOnError/keepGoing** {Boolean, default:false}, keep inserting documents even if one document has an error, *mongodb 1.9.1 >*.
* - **serializeFunctions** {Boolean, default:false}, serialize functions on the document.
* - **forceServerObjectId** {Boolean, default:false}, let server assign ObjectId instead of the driver
* - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Array|Object} docs
* @param {Object} [options] optional options for insert command
* @param {Function} [callback] optional callback for the function, must be provided when using a writeconcern
* @return {null}
* @api public
*/
Collection.prototype.insert = function insert (docs, options, callback) {
if ('function' === typeof options) callback = options, options = {};
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
var self = this;
insertAll(self, Array.isArray(docs) ? docs : [docs], options, callback);
return this;
};
/**
* @ignore
*/
var checkCollectionName = function checkCollectionName (collectionName) {
if('string' !== typeof collectionName) {
throw Error("collection name must be a String");
}
if(!collectionName || collectionName.indexOf('..') != -1) {
throw Error("collection names cannot be empty");
}
if(collectionName.indexOf('$') != -1 &&
collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) {
throw Error("collection names must not contain '$'");
}
if(collectionName.match(/^\.|\.$/) != null) {
throw Error("collection names must not start or end with '.'");
}
// Validate that we are not passing 0x00 in the colletion name
if(!!~collectionName.indexOf("\x00")) {
throw new Error("collection names cannot contain a null character");
}
};
/**
* Removes documents specified by `selector` from the db.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **single** {Boolean, default:false}, removes the first document found.
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} [selector] optional select, no selector is equivalent to removing all documents.
* @param {Object} [options] additional options during remove.
* @param {Function} [callback] must be provided if you performing a remove with a writeconcern
* @return {null}
* @api public
*/
Collection.prototype.remove = function remove(selector, options, callback) {
if ('function' === typeof selector) {
callback = selector;
selector = options = {};
} else if ('function' === typeof options) {
callback = options;
options = {};
}
// Ensure options
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
// Ensure we have at least an empty selector
selector = selector == null ? {} : selector;
// Set up flags for the command, if we have a single document remove
var flags = 0 | (options.single ? 1 : 0);
// DbName
var dbName = options['dbName'];
// If no dbname defined use the db one
if(dbName == null) {
dbName = this.db.databaseName;
}
// Create a delete command
var deleteCommand = new DeleteCommand(
this.db
, dbName + "." + this.collectionName
, selector
, flags);
var self = this;
var errorOptions = _getWriteConcern(self, options, callback);
// Execute the command, do not add a callback as it's async
if(_hasWriteConcern(errorOptions) && typeof callback == 'function') {
// Insert options
var commandOptions = {read:false};
// If we have safe set set async to false
if(errorOptions == null) commandOptions['async'] = true;
// Set safe option
commandOptions['safe'] = true;
// If we have an error option
if(typeof errorOptions == 'object') {
var keys = Object.keys(errorOptions);
for(var i = 0; i < keys.length; i++) {
commandOptions[keys[i]] = errorOptions[keys[i]];
}
}
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection)
this.db._executeRemoveCommand(deleteCommand, commandOptions, function (err, error) {
error = error && error.documents;
if(!callback) return;
if(err) {
callback(err);
} else if(error[0].err || error[0].errmsg) {
callback(utils.toError(error[0]));
} else {
callback(null, error[0].n);
}
});
} else if(_hasWriteConcern(errorOptions) && callback == null) {
throw new Error("Cannot use a writeConcern without a provided callback");
} else {
var result = this.db._executeRemoveCommand(deleteCommand);
// If no callback just return
if (!callback) return;
// If error return error
if (result instanceof Error) {
return callback(result);
}
// Otherwise just return
return callback();
}
};
/**
* Renames the collection.
*
* Options
* - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists.
*
* @param {String} newName the new name of the collection.
* @param {Object} [options] returns option results.
* @param {Function} callback the callback accepting the result
* @return {null}
* @api public
*/
Collection.prototype.rename = function rename(newName, options, callback) {
var self = this;
if(typeof options == 'function') {
callback = options;
options = {}
}
// Ensure the new name is valid
checkCollectionName(newName);
// Execute the command, return the new renamed collection if successful
self.db._executeQueryCommand(DbCommand.createRenameCollectionCommand(self.db, self.collectionName, newName, options)
, utils.handleSingleCommandResultReturn(true, false, function(err, result) {
if(err) return callback(err, null)
try {
if(options.new_collection)
return callback(null, new Collection(self.db, newName, self.db.pkFactory));
self.collectionName = newName;
callback(null, self);
} catch(err) {
callback(err, null);
}
}));
}
/**
* @ignore
*/
var insertAll = function insertAll (self, docs, options, callback) {
if('function' === typeof options) callback = options, options = {};
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
// Insert options (flags for insert)
var insertFlags = {};
// If we have a mongodb version >= 1.9.1 support keepGoing attribute
if(options['keepGoing'] != null) {
insertFlags['keepGoing'] = options['keepGoing'];
}
// If we have a mongodb version >= 1.9.1 support keepGoing attribute
if(options['continueOnError'] != null) {
insertFlags['continueOnError'] = options['continueOnError'];
}
// DbName
var dbName = options['dbName'];
// If no dbname defined use the db one
if(dbName == null) {
dbName = self.db.databaseName;
}
// Either use override on the function, or go back to default on either the collection
// level or db
if(options['serializeFunctions'] != null) {
insertFlags['serializeFunctions'] = options['serializeFunctions'];
} else {
insertFlags['serializeFunctions'] = self.serializeFunctions;
}
// Get checkKeys value
var checkKeys = typeof options.checkKeys != 'boolean' ? true : options.checkKeys;
// Pass in options
var insertCommand = new InsertCommand(
self.db
, dbName + "." + self.collectionName, checkKeys, insertFlags);
// Add the documents and decorate them with id's if they have none
for(var index = 0, len = docs.length; index < len; ++index) {
var doc = docs[index];
// Add id to each document if it's not already defined
if (!(Buffer.isBuffer(doc))
&& doc['_id'] == null
&& self.db.forceServerObjectId != true
&& options.forceServerObjectId != true) {
doc['_id'] = self.pkFactory.createPk();
}
insertCommand.add(doc);
}
// Collect errorOptions
var errorOptions = _getWriteConcern(self, options, callback);
// Default command options
var commandOptions = {};
// If safe is defined check for error message
if(_hasWriteConcern(errorOptions) && typeof callback == 'function') {
// Insert options
commandOptions['read'] = false;
// If we have safe set set async to false
if(errorOptions == null) commandOptions['async'] = true;
// Set safe option
commandOptions['safe'] = errorOptions;
// If we have an error option
if(typeof errorOptions == 'object') {
var keys = Object.keys(errorOptions);
for(var i = 0; i < keys.length; i++) {
commandOptions[keys[i]] = errorOptions[keys[i]];
}
}
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection)
self.db._executeInsertCommand(insertCommand, commandOptions, function (err, error) {
error = error && error.documents;
if(!callback) return;
if (err) {
callback(err);
} else if(error[0].err || error[0].errmsg) {
callback(utils.toError(error[0]));
} else {
callback(null, docs);
}
});
} else if(_hasWriteConcern(errorOptions) && callback == null) {
throw new Error("Cannot use a writeConcern without a provided callback");
} else {
// Execute the call without a write concern
var result = self.db._executeInsertCommand(insertCommand, commandOptions);
// If no callback just return
if(!callback) return;
// If error return error
if(result instanceof Error) {
return callback(result);
}
// Otherwise just return
return callback(null, docs);
}
};
/**
* Save a document. Simple full document replacement function. Not recommended for efficiency, use atomic
* operators and update instead for more efficient operations.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} [doc] the document to save
* @param {Object} [options] additional options during remove.
* @param {Function} [callback] must be provided if you performing a safe save
* @return {null}
* @api public
*/
Collection.prototype.save = function save(doc, options, callback) {
if('function' === typeof options) callback = options, options = null;
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
// Throw an error if attempting to perform a bulk operation
if(Array.isArray(doc)) throw new Error("doc parameter must be a single document");
// Extract the id, if we have one we need to do a update command
var id = doc['_id'];
var commandOptions = _getWriteConcern(this, options, callback);
if(id) {
commandOptions.upsert = true;
this.update({ _id: id }, doc, commandOptions, callback);
} else {
this.insert(doc, commandOptions, callback && function (err, docs) {
if(err) return callback(err, null);
if(Array.isArray(docs)) {
callback(err, docs[0]);
} else {
callback(err, docs);
}
});
}
};
/**
* Updates documents.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **upsert** {Boolean, default:false}, perform an upsert operation.
* - **multi** {Boolean, default:false}, update all documents matching the selector.
* - **serializeFunctions** {Boolean, default:false}, serialize functions on the document.
* - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} selector the query to select the document/documents to be updated
* @param {Object} document the fields/vals to be updated, or in the case of an upsert operation, inserted.
* @param {Object} [options] additional options during update.
* @param {Function} [callback] must be provided if you performing an update with a writeconcern
* @return {null}
* @api public
*/
Collection.prototype.update = function update(selector, document, options, callback) {
if('function' === typeof options) callback = options, options = null;
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
// DbName
var dbName = options['dbName'];
// If no dbname defined use the db one
if(dbName == null) {
dbName = this.db.databaseName;
}
// If we are not providing a selector or document throw
if(selector == null || typeof selector != 'object') return callback(new Error("selector must be a valid JavaScript object"));
if(document == null || typeof document != 'object') return callback(new Error("document must be a valid JavaScript object"));
// Either use override on the function, or go back to default on either the collection
// level or db
if(options['serializeFunctions'] != null) {
options['serializeFunctions'] = options['serializeFunctions'];
} else {
options['serializeFunctions'] = this.serializeFunctions;
}
// Build the options command
var updateCommand = new UpdateCommand(
this.db
, dbName + "." + this.collectionName
, selector
, document
, options);
var self = this;
// Unpack the error options if any
var errorOptions = _getWriteConcern(this, options, callback);
// If safe is defined check for error message
if(_hasWriteConcern(errorOptions) && typeof callback == 'function') {
// Insert options
var commandOptions = {read:false};
// If we have safe set set async to false
if(errorOptions == null) commandOptions['async'] = true;
// Set safe option
commandOptions['safe'] = errorOptions;
// If we have an error option
if(typeof errorOptions == 'object') {
var keys = Object.keys(errorOptions);
for(var i = 0; i < keys.length; i++) {
commandOptions[keys[i]] = errorOptions[keys[i]];
}
}
// Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection)
this.db._executeUpdateCommand(updateCommand, commandOptions, function (err, error) {
error = error && error.documents;
if(!callback) return;
if(err) {
callback(err);
} else if(error[0].err || error[0].errmsg) {
callback(utils.toError(error[0]));
} else {
// Perform the callback
callback(null, error[0].n, error[0]);
}
});
} else if(_hasWriteConcern(errorOptions) && callback == null) {
throw new Error("Cannot use a writeConcern without a provided callback");
} else {
// Execute update
var result = this.db._executeUpdateCommand(updateCommand);
// If no callback just return
if (!callback) return;
// If error return error
if (result instanceof Error) {
return callback(result);
}
// Otherwise just return
return callback();
}
};
/**
* The distinct command returns returns a list of distinct values for the given key across a collection.
*
* Options
* - **readPreference** {String}, the preferred read preference (Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {String} key key to run distinct against.
* @param {Object} [query] option query to narrow the returned objects.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from distinct or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.distinct = function distinct(key, query, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
query = args.length ? args.shift() || {} : {};
options = args.length ? args.shift() || {} : {};
var mapCommandHash = {
'distinct': this.collectionName
, 'query': query
, 'key': key
};
// Set read preference if we set one
var readPreference = options['readPreference'] ? options['readPreference'] : false;
// Execute the command
this.db._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this.db, mapCommandHash)
, {read:readPreference}
, utils.handleSingleCommandResultReturn(null, null, function(err, result) {
if(err) return callback(err, null);
callback(null, result.values);
}));
};
/**
* Count number of matching documents in the db to a query.
*
* Options
* - **skip** {Number}, The number of documents to skip for the count.
* - **limit** {Number}, The limit of documents to count.
* - **readPreference** {String}, the preferred read preference (Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Object} [query] query to filter by before performing count.
* @param {Object} [options] additional options during count.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the count method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.count = function count (query, options, callback) {
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
query = args.length ? args.shift() || {} : {};
options = args.length ? args.shift() || {} : {};
var skip = options.skip;
var limit = options.limit;
// Final query
var commandObject = {
'count': this.collectionName
, 'query': query
, 'fields': null
};
// Add limit and skip if defined
if(typeof skip == 'number') commandObject.skip = skip;
if(typeof limit == 'number') commandObject.limit = limit;
// Set read preference if we set one
var readPreference = _getReadConcern(this, options);
// Execute the command
this.db._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this.db, commandObject)
, {read: readPreference}
, utils.handleSingleCommandResultReturn(null, null, function(err, result) {
if(err) return callback(err, null);
if(result == null) return callback(new Error("no result returned for count"), null);
callback(null, result.n);
}));
};
/**
* Drop the collection
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the drop method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.drop = function drop(callback) {
this.db.dropCollection(this.collectionName, callback);
};
/**
* Find and update a document.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **remove** {Boolean, default:false}, set to true to remove the object before returning.
* - **upsert** {Boolean, default:false}, perform an upsert operation.
* - **new** {Boolean, default:false}, set to true if you want to return the modified object rather than the original. Ignored for remove.
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} query query object to locate the object to modify
* @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate
* @param {Object} doc - the fields/vals to be updated
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndModify method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.findAndModify = function findAndModify (query, sort, doc, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
sort = args.length ? args.shift() || [] : [];
doc = args.length ? args.shift() : null;
options = args.length ? args.shift() || {} : {};
var self = this;
var queryObject = {
'findandmodify': this.collectionName
, 'query': query
, 'sort': utils.formattedOrderClause(sort)
};
queryObject.new = options.new ? 1 : 0;
queryObject.remove = options.remove ? 1 : 0;
queryObject.upsert = options.upsert ? 1 : 0;
if (options.fields) {
queryObject.fields = options.fields;
}
if (doc && !options.remove) {
queryObject.update = doc;
}
// Either use override on the function, or go back to default on either the collection
// level or db
if(options['serializeFunctions'] != null) {
options['serializeFunctions'] = options['serializeFunctions'];
} else {
options['serializeFunctions'] = this.serializeFunctions;
}
// Only run command and rely on getLastError command
var command = DbCommand.createDbCommand(this.db, queryObject, options)
// Execute command
this.db._executeQueryCommand(command
, {read:false}, utils.handleSingleCommandResultReturn(null, null, function(err, result) {
if(err) return callback(err, null);
return callback(null, result.value, result);
}));
}
/**
* Find and remove a document
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} query query object to locate the object to modify
* @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndRemove method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.findAndRemove = function(query, sort, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
sort = args.length ? args.shift() || [] : [];
options = args.length ? args.shift() || {} : {};
// Add the remove option
options['remove'] = true;
// Execute the callback
this.findAndModify(query, sort, null, options, callback);
}
var testForFields = {
limit: 1, sort: 1, fields:1, skip: 1, hint: 1, explain: 1, snapshot: 1, timeout: 1, tailable: 1, tailableRetryInterval: 1
, numberOfRetries: 1, awaitdata: 1, exhaust: 1, batchSize: 1, returnKey: 1, maxScan: 1, min: 1, max: 1, showDiskLoc: 1
, comment: 1, raw: 1, readPreference: 1, partial: 1, read: 1, dbName: 1
};
/**
* Creates a cursor for a query that can be used to iterate over results from MongoDB
*
* Various argument possibilities
* - callback?
* - selector, callback?,
* - selector, fields, callback?
* - selector, options, callback?
* - selector, fields, options, callback?
* - selector, fields, skip, limit, callback?
* - selector, fields, skip, limit, timeout, callback?
*
* Options
* - **limit** {Number, default:0}, sets the limit of documents returned in the query.
* - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
* - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1}
* - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination).
* - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1}
* - **explain** {Boolean, default:false}, explain the query instead of returning the data.
* - **snapshot** {Boolean, default:false}, snapshot query.
* - **timeout** {Boolean, default:false}, specify if the cursor can timeout.
* - **tailable** {Boolean, default:false}, specify if the cursor is tailable.
* - **tailableRetryInterval** {Number, default:100}, specify the miliseconds between getMores on tailable cursor.
* - **numberOfRetries** {Number, default:5}, specify the number of times to retry the tailable cursor.
* - **awaitdata** {Boolean, default:false} allow the cursor to wait for data, only applicable for tailable cursor.
* - **exhaust** {Boolean, default:false} have the server send all the documents at once as getMore packets, not recommended.
* - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results.
* - **returnKey** {Boolean, default:false}, only return the index key.
* - **maxScan** {Number}, Limit the number of items to scan.
* - **min** {Number}, Set index bounds.
* - **max** {Number}, Set index bounds.
* - **showDiskLoc** {Boolean, default:false}, Show disk location of results.
* - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler.
* - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
* - **numberOfRetries** {Number, default:5}, if using awaidata specifies the number of times to retry on timeout.
* - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system
*
* @param {Object|ObjectID} query query object to locate the object to modify
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the find method or null if an error occured.
* @return {Cursor} returns a cursor to the query
* @api public
*/
Collection.prototype.find = function find () {
var options
, args = Array.prototype.slice.call(arguments, 0)
, has_callback = typeof args[args.length - 1] === 'function'
, has_weird_callback = typeof args[0] === 'function'
, callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null)
, len = args.length
, selector = len >= 1 ? args[0] : {}
, fields = len >= 2 ? args[1] : undefined;
if(len === 1 && has_weird_callback) {
// backwards compat for callback?, options case
selector = {};
options = args[0];
}
if(len === 2 && !Array.isArray(fields)) {
var fieldKeys = Object.getOwnPropertyNames(fields);
var is_option = false;
for(var i = 0; i < fieldKeys.length; i++) {
if(testForFields[fieldKeys[i]] != null) {
is_option = true;
break;
}
}
if(is_option) {
options = fields;
fields = undefined;
} else {
options = {};
}
} else if(len === 2 && Array.isArray(fields) && !Array.isArray(fields[0])) {
var newFields = {};
// Rewrite the array
for(var i = 0; i < fields.length; i++) {
newFields[fields[i]] = 1;
}
// Set the fields
fields = newFields;
}
if(3 === len) {
options = args[2];
}
// Ensure selector is not null
selector = selector == null ? {} : selector;
// Validate correctness off the selector
var object = selector;
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
// Validate correctness of the field selector
var object = fields;
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
// Check special case where we are using an objectId
if(selector instanceof ObjectID || (selector != null && selector._bsontype == 'ObjectID')) {
selector = {_id:selector};
}
// If it's a serialized fields field we need to just let it through
// user be warned it better be good
if(options && options.fields && !(Buffer.isBuffer(options.fields))) {
fields = {};
if(Array.isArray(options.fields)) {
if(!options.fields.length) {
fields['_id'] = 1;
} else {
for (var i = 0, l = options.fields.length; i < l; i++) {
fields[options.fields[i]] = 1;
}
}
} else {
fields = options.fields;
}
}
if (!options) options = {};
options.skip = len > 3 ? args[2] : options.skip ? options.skip : 0;
options.limit = len > 3 ? args[3] : options.limit ? options.limit : 0;
options.raw = options.raw != null && typeof options.raw === 'boolean' ? options.raw : this.raw;
options.hint = options.hint != null ? normalizeHintField(options.hint) : this.internalHint;
options.timeout = len == 5 ? args[4] : typeof options.timeout === 'undefined' ? undefined : options.timeout;
// If we have overridden slaveOk otherwise use the default db setting
options.slaveOk = options.slaveOk != null ? options.slaveOk : this.db.slaveOk;
// Set option
var o = options;
// Support read/readPreference
if(o["read"] != null) o["readPreference"] = o["read"];
// Set the read preference
o.read = o["readPreference"] ? o.readPreference : this.readPreference;
// Adjust slave ok if read preference is secondary or secondary only
if(o.read == "secondary" || o.read == "secondaryOnly") options.slaveOk = true;
// callback for backward compatibility
if(callback) {
// TODO refactor Cursor args
callback(null, new Cursor(this.db, this, selector, fields, o));
} else {
return new Cursor(this.db, this, selector, fields, o);
}
};
/**
* Normalizes a `hint` argument.
*
* @param {String|Object|Array} hint
* @return {Object}
* @api private
*/
var normalizeHintField = function normalizeHintField(hint) {
var finalHint = null;
if(typeof hint == 'string') {
finalHint = hint;
} else if(Array.isArray(hint)) {
finalHint = {};
hint.forEach(function(param) {
finalHint[param] = 1;
});
} else if(hint != null && typeof hint == 'object') {
finalHint = {};
for (var name in hint) {
finalHint[name] = hint[name];
}
}
return finalHint;
};
/**
* Finds a single document based on the query
*
* Various argument possibilities
* - callback?
* - selector, callback?,
* - selector, fields, callback?
* - selector, options, callback?
* - selector, fields, options, callback?
* - selector, fields, skip, limit, callback?
* - selector, fields, skip, limit, timeout, callback?
*
* Options
* - **limit** {Number, default:0}, sets the limit of documents returned in the query.
* - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
* - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1}
* - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination).
* - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1}
* - **explain** {Boolean, default:false}, explain the query instead of returning the data.
* - **snapshot** {Boolean, default:false}, snapshot query.
* - **timeout** {Boolean, default:false}, specify if the cursor can timeout.
* - **tailable** {Boolean, default:false}, specify if the cursor is tailable.
* - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results.
* - **returnKey** {Boolean, default:false}, only return the index key.
* - **maxScan** {Number}, Limit the number of items to scan.
* - **min** {Number}, Set index bounds.
* - **max** {Number}, Set index bounds.
* - **showDiskLoc** {Boolean, default:false}, Show disk location of results.
* - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler.
* - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents.
* - **readPreference** {String}, the preferred read preference (Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
* - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system
*
* @param {Object|ObjectID} query query object to locate the object to modify
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findOne method or null if an error occured.
* @return {Cursor} returns a cursor to the query
* @api public
*/
Collection.prototype.findOne = function findOne () {
var self = this;
var args = Array.prototype.slice.call(arguments, 0);
var callback = args.pop();
var cursor = this.find.apply(this, args).limit(-1).batchSize(1);
// Return the item
cursor.nextObject(function(err, item) {
if(err != null) return callback(utils.toError(err), null);
callback(null, item);
});
};
/**
* Creates an index on the collection.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **unique** {Boolean, default:false}, creates an unique index.
* - **sparse** {Boolean, default:false}, creates a sparse index.
* - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible.
* - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates.
* - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates.
* - **v** {Number}, specify the format version of the indexes.
* - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} fieldOrSpec fieldOrSpec that defines the index.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the createIndex method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.createIndex = function createIndex (fieldOrSpec, options, callback) {
// Clean up call
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
options = typeof callback === 'function' ? options : callback;
options = options == null ? {} : options;
// Collect errorOptions
var errorOptions = _getWriteConcern(this, options, callback);
// Execute create index
this.db.createIndex(this.collectionName, fieldOrSpec, options, callback);
};
/**
* Ensures that an index exists, if it does not it creates it
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **unique** {Boolean, default:false}, creates an unique index.
* - **sparse** {Boolean, default:false}, creates a sparse index.
* - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible.
* - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates.
* - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates.
* - **v** {Number}, specify the format version of the indexes.
* - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {Object} fieldOrSpec fieldOrSpec that defines the index.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the ensureIndex method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.ensureIndex = function ensureIndex (fieldOrSpec, options, callback) {
// Clean up call
if (typeof callback === 'undefined' && typeof options === 'function') {
callback = options;
options = {};
}
if (options == null) {
options = {};
}
// Execute create index
this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback);
};
/**
* Retrieves this collections index info.
*
* Options
* - **full** {Boolean, default:false}, returns the full raw index information.
*
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexInformation method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.indexInformation = function indexInformation (options, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
// Call the index information
this.db.indexInformation(this.collectionName, options, callback);
};
/**
* Drops an index from this collection.
*
* @param {String} name
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndex method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.dropIndex = function dropIndex (name, callback) {
this.db.dropIndex(this.collectionName, name, callback);
};
/**
* Drops all indexes from this collection.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropAllIndexes method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.dropAllIndexes = function dropIndexes (callback) {
this.db.dropIndex(this.collectionName, '*', function (err, result) {
if(err) return callback(err, false);
callback(null, true);
});
}
/**
* Drops all indexes from this collection.
*
* @deprecated
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndexes method or null if an error occured.
* @return {null}
* @api private
*/
Collection.prototype.dropIndexes = Collection.prototype.dropAllIndexes;
/**
* Reindex all indexes on the collection
* Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the reIndex method or null if an error occured.
* @return {null}
* @api public
**/
Collection.prototype.reIndex = function(callback) {
this.db.reIndex(this.collectionName, callback);
}
/**
* Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection.
*
* Options
* - **out** {Object}, sets the output target for the map reduce job. *{inline:1} | {replace:'collectionName'} | {merge:'collectionName'} | {reduce:'collectionName'}*
* - **query** {Object}, query filter object.
* - **sort** {Object}, sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces.
* - **limit** {Number}, number of objects to return from collection.
* - **keeptemp** {Boolean, default:false}, keep temporary data.
* - **finalize** {Function | String}, finalize function.
* - **scope** {Object}, can pass in variables that can be access from map/reduce/finalize.
* - **jsMode** {Boolean, default:false}, it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X.
* - **verbose** {Boolean, default:false}, provide statistics on job execution time.
* - **readPreference** {String, only for inline results}, the preferred read preference (Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Function|String} map the mapping function.
* @param {Function|String} reduce the reduce function.
* @param {Objects} [options] options for the map reduce job.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the mapReduce method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.mapReduce = function mapReduce (map, reduce, options, callback) {
if ('function' === typeof options) callback = options, options = {};
// Out must allways be defined (make sure we don't break weirdly on pre 1.8+ servers)
if(null == options.out) {
throw new Error("the out option parameter must be defined, see mongodb docs for possible values");
}
if ('function' === typeof map) {
map = map.toString();
}
if ('function' === typeof reduce) {
reduce = reduce.toString();
}
if ('function' === typeof options.finalize) {
options.finalize = options.finalize.toString();
}
var mapCommandHash = {
mapreduce: this.collectionName
, map: map
, reduce: reduce
};
// Add any other options passed in
for (var name in options) {
if ('scope' == name) {
mapCommandHash[name] = processScope(options[name]);
} else {
mapCommandHash[name] = options[name];
}
}
// Set read preference if we set one
var readPreference = _getReadConcern(this, options);
// If we have a read preference and inline is not set as output fail hard
if((readPreference != false && readPreference != 'primary')
&& options['out'] && (options['out'].inline != 1 && options['out'] != 'inline')) {
throw new Error("a readPreference can only be provided when performing an inline mapReduce");
}
// self
var self = this;
var cmd = DbCommand.createDbCommand(this.db, mapCommandHash);
this.db._executeQueryCommand(cmd, {read:readPreference}, function (err, result) {
if(err) return callback(err);
if(!result || !result.documents || result.documents.length == 0)
return callback(Error("command failed to return results"), null)
// Check if we have an error
if(1 != result.documents[0].ok || result.documents[0].err || result.documents[0].errmsg) {
return callback(utils.toError(result.documents[0]));
}
// Create statistics value
var stats = {};
if(result.documents[0].timeMillis) stats['processtime'] = result.documents[0].timeMillis;
if(result.documents[0].counts) stats['counts'] = result.documents[0].counts;
if(result.documents[0].timing) stats['timing'] = result.documents[0].timing;
// invoked with inline?
if(result.documents[0].results) {
return callback(null, result.documents[0].results, stats);
}
// The returned collection
var collection = null;
// If we have an object it's a different db
if(result.documents[0].result != null && typeof result.documents[0].result == 'object') {
var doc = result.documents[0].result;
collection = self.db.db(doc.db).collection(doc.collection);
} else {
// Create a collection object that wraps the result collection
collection = self.db.collection(result.documents[0].result)
}
// If we wish for no verbosity
if(options['verbose'] == null || !options['verbose']) {
return callback(err, collection);
}
// Return stats as third set of values
callback(err, collection, stats);
});
};
/**
* Functions that are passed as scope args must
* be converted to Code instances.
* @ignore
*/
function processScope (scope) {
if (!utils.isObject(scope)) {
return scope;
}
var keys = Object.keys(scope);
var i = keys.length;
var key;
while (i--) {
key = keys[i];
if ('function' == typeof scope[key]) {
scope[key] = new Code(String(scope[key]));
}
}
return scope;
}
/**
* Group function helper
* @ignore
*/
var groupFunction = function () {
var c = db[ns].find(condition);
var map = new Map();
var reduce_function = reduce;
while (c.hasNext()) {
var obj = c.next();
var key = {};
for (var i = 0, len = keys.length; i < len; ++i) {
var k = keys[i];
key[k] = obj[k];
}
var aggObj = map.get(key);
if (aggObj == null) {
var newObj = Object.extend({}, key);
aggObj = Object.extend(newObj, initial);
map.put(key, aggObj);
}
reduce_function(obj, aggObj);
}
return { "result": map.values() };
}.toString();
/**
* Run a group command across a collection
*
* Options
* - **readPreference** {String}, the preferred read preference (Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Object|Array|Function|Code} keys an object, array or function expressing the keys to group by.
* @param {Object} condition an optional condition that must be true for a row to be considered.
* @param {Object} initial initial value of the aggregation counter object.
* @param {Function|Code} reduce the reduce function aggregates (reduces) the objects iterated
* @param {Function|Code} finalize an optional function to be run on each item in the result set just before the item is returned.
* @param {Boolean} command specify if you wish to run using the internal group command or using eval, default is true.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the group method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.group = function group(keys, condition, initial, reduce, finalize, command, options, callback) {
var args = Array.prototype.slice.call(arguments, 3);
callback = args.pop();
// Fetch all commands
reduce = args.length ? args.shift() : null;
finalize = args.length ? args.shift() : null;
command = args.length ? args.shift() : null;
options = args.length ? args.shift() || {} : {};
// Make sure we are backward compatible
if(!(typeof finalize == 'function')) {
command = finalize;
finalize = null;
}
if (!Array.isArray(keys) && keys instanceof Object && typeof(keys) !== 'function' && !(keys instanceof Code)) {
keys = Object.keys(keys);
}
if(typeof reduce === 'function') {
reduce = reduce.toString();
}
if(typeof finalize === 'function') {
finalize = finalize.toString();
}
// Set up the command as default
command = command == null ? true : command;
// Execute using the command
if(command) {
var reduceFunction = reduce instanceof Code
? reduce
: new Code(reduce);
var selector = {
group: {
'ns': this.collectionName
, '$reduce': reduceFunction
, 'cond': condition
, 'initial': initial
, 'out': "inline"
}
};
// if finalize is defined
if(finalize != null) selector.group['finalize'] = finalize;
// Set up group selector
if ('function' === typeof keys || keys instanceof Code) {
selector.group.$keyf = keys instanceof Code
? keys
: new Code(keys);
} else {
var hash = {};
keys.forEach(function (key) {
hash[key] = 1;
});
selector.group.key = hash;
}
var cmd = DbCommand.createDbSlaveOkCommand(this.db, selector);
// Set read preference if we set one
var readPreference = _getReadConcern(this, options);
// Execute the command
this.db._executeQueryCommand(cmd
, {read:readPreference}
, utils.handleSingleCommandResultReturn(null, null, function(err, result) {
if(err) return callback(err, null);
callback(null, result.retval);
}));
} else {
// Create execution scope
var scope = reduce != null && reduce instanceof Code
? reduce.scope
: {};
scope.ns = this.collectionName;
scope.keys = keys;
scope.condition = condition;
scope.initial = initial;
// Pass in the function text to execute within mongodb.
var groupfn = groupFunction.replace(/ reduce;/, reduce.toString() + ';');
this.db.eval(new Code(groupfn, scope), function (err, results) {
if (err) return callback(err, null);
callback(null, results.result || results);
});
}
};
/**
* Returns the options of the collection.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the options method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.options = function options(callback) {
this.db.collectionsInfo(this.collectionName, function (err, cursor) {
if (err) return callback(err);
cursor.nextObject(function (err, document) {
callback(err, document && document.options || null);
});
});
};
/**
* Returns if the collection is a capped collection
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the isCapped method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.isCapped = function isCapped(callback) {
this.options(function(err, document) {
if(err != null) {
callback(err);
} else {
callback(null, document && document.capped);
}
});
};
/**
* Checks if one or more indexes exist on the collection
*
* @param {String|Array} indexNames check if one or more indexes exist on the collection.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexExists method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.indexExists = function indexExists(indexes, callback) {
this.indexInformation(function(err, indexInformation) {
// If we have an error return
if(err != null) return callback(err, null);
// Let's check for the index names
if(Array.isArray(indexes)) {
for(var i = 0; i < indexes.length; i++) {
if(indexInformation[indexes[i]] == null) {
return callback(null, false);
}
}
// All keys found return true
return callback(null, true);
} else {
return callback(null, indexInformation[indexes] != null);
}
});
}
/**
* Execute the geoNear command to search for items in the collection
*
* Options
* - **num** {Number}, max number of results to return.
* - **maxDistance** {Number}, include results up to maxDistance from the point.
* - **distanceMultiplier** {Number}, include a value to multiply the distances with allowing for range conversions.
* - **query** {Object}, filter the results by a query.
* - **spherical** {Boolean, default:false}, perform query using a spherical model.
* - **uniqueDocs** {Boolean, default:false}, the closest location in a document to the center of the search region will always be returned MongoDB > 2.X.
* - **includeLocs** {Boolean, default:false}, include the location data fields in the top level of the results MongoDB > 2.X.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order.
* @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order.
* @param {Objects} [options] options for the map reduce job.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoNear method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.geoNear = function geoNear(x, y, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
// Fetch all commands
options = args.length ? args.shift() || {} : {};
// Build command object
var commandObject = {
geoNear:this.collectionName,
near: [x, y]
}
// Decorate object if any with known properties
if(options['num'] != null) commandObject['num'] = options['num'];
if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance'];
if(options['distanceMultiplier'] != null) commandObject['distanceMultiplier'] = options['distanceMultiplier'];
if(options['query'] != null) commandObject['query'] = options['query'];
if(options['spherical'] != null) commandObject['spherical'] = options['spherical'];
if(options['uniqueDocs'] != null) commandObject['uniqueDocs'] = options['uniqueDocs'];
if(options['includeLocs'] != null) commandObject['includeLocs'] = options['includeLocs'];
// Ensure we have the right read preference inheritance
options.readPreference = _getReadConcern(this, options);
// Execute the command
this.db.command(commandObject, options, function (err, res) {
if (err) {
callback(err);
} else if (res.err || res.errmsg) {
callback(utils.toError(res));
} else {
// should we only be returning res.results here? Not sure if the user
// should see the other return information
callback(null, res);
}
});
}
/**
* Execute a geo search using a geo haystack index on a collection.
*
* Options
* - **maxDistance** {Number}, include results up to maxDistance from the point.
* - **search** {Object}, filter the results by a query.
* - **limit** {Number}, max number of results to return.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order.
* @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order.
* @param {Objects} [options] options for the map reduce job.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoHaystackSearch method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.geoHaystackSearch = function geoHaystackSearch(x, y, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
// Fetch all commands
options = args.length ? args.shift() || {} : {};
// Build command object
var commandObject = {
geoSearch:this.collectionName,
near: [x, y]
}
// Decorate object if any with known properties
if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance'];
if(options['query'] != null) commandObject['search'] = options['query'];
if(options['search'] != null) commandObject['search'] = options['search'];
if(options['limit'] != null) commandObject['limit'] = options['limit'];
// Ensure we have the right read preference inheritance
options.readPreference = _getReadConcern(this, options);
// Execute the command
this.db.command(commandObject, options, function (err, res) {
if (err) {
callback(err);
} else if (res.err || res.errmsg) {
callback(utils.toError(res));
} else {
// should we only be returning res.results here? Not sure if the user
// should see the other return information
callback(null, res);
}
});
}
/**
* Retrieve all the indexes on the collection.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexes method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.indexes = function indexes(callback) {
// Return all the index information
this.db.indexInformation(this.collectionName, {full:true}, callback);
}
/**
* Execute an aggregation framework pipeline against the collection, needs MongoDB >= 2.1
*
* Options
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Array} array containing all the aggregation framework commands for the execution.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the aggregate method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.aggregate = function(pipeline, options, callback) {
// * - **explain** {Boolean}, return the query plan for the aggregation pipeline instead of the results. 2.3, 2.4
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
var self = this;
// If we have any of the supported options in the options object
var opts = args[args.length - 1];
options = opts.readPreference || opts.explain ? args.pop() : {}
// Convert operations to an array
if(!Array.isArray(args[0])) {
pipeline = [];
// Push all the operations to the pipeline
for(var i = 0; i < args.length; i++) pipeline.push(args[i]);
}
// Build the command
var command = { aggregate : this.collectionName, pipeline : pipeline};
// Ensure we have the right read preference inheritance
options.readPreference = _getReadConcern(this, options);
// Execute the command
this.db.command(command, options, function(err, result) {
if(err) {
callback(err);
} else if(result['err'] || result['errmsg']) {
callback(utils.toError(result));
} else if(typeof result == 'object' && result['serverPipeline']) {
callback(null, result);
} else {
callback(null, result.result);
}
});
}
/**
* Get all the collection statistics.
*
* Options
* - **scale** {Number}, divide the returned sizes by scale value.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Objects} [options] options for the stats command.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the stats method or null if an error occured.
* @return {null}
* @api public
*/
Collection.prototype.stats = function stats(options, callback) {
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
// Fetch all commands
options = args.length ? args.shift() || {} : {};
// Build command object
var commandObject = {
collStats:this.collectionName,
}
// Check if we have the scale value
if(options['scale'] != null) commandObject['scale'] = options['scale'];
// Ensure we have the right read preference inheritance
options.readPreference = _getReadConcern(this, options);
// Execute the command
this.db.command(commandObject, options, callback);
}
/**
* @ignore
*/
Object.defineProperty(Collection.prototype, "hint", {
enumerable: true
, get: function () {
return this.internalHint;
}
, set: function (v) {
this.internalHint = normalizeHintField(v);
}
});
var _getReadConcern = function(self, options) {
if(options.readPreference) return options.readPreference;
if(self.readPreference) return self.readPreference;
if(self.db.readPreference) return self.readPreference;
return 'primary';
}
/**
* @ignore
*/
var _hasWriteConcern = function(errorOptions) {
return errorOptions == true
|| errorOptions.w > 0
|| errorOptions.w == 'majority'
|| errorOptions.j == true
|| errorOptions.journal == true
|| errorOptions.fsync == true
}
/**
* @ignore
*/
var _setWriteConcernHash = function(options) {
var finalOptions = {};
if(options.w != null) finalOptions.w = options.w;
if(options.journal == true) finalOptions.j = options.journal;
if(options.j == true) finalOptions.j = options.j;
if(options.fsync == true) finalOptions.fsync = options.fsync;
if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout;
return finalOptions;
}
/**
* @ignore
*/
var _getWriteConcern = function(self, options, callback) {
// Final options
var finalOptions = {w:1};
// Local options verification
if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(options);
} else if(typeof options.safe == "boolean") {
finalOptions = {w: (options.safe ? 1 : 0)};
} else if(options.safe != null && typeof options.safe == 'object') {
finalOptions = _setWriteConcernHash(options.safe);
} else if(self.opts.w != null || typeof self.opts.j == 'boolean' || typeof self.opts.journal == 'boolean' || typeof self.opts.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.opts);
} else if(typeof self.opts.safe == "boolean") {
finalOptions = {w: (self.opts.safe ? 1 : 0)};
} else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.db.safe);
} else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.db.options);
} else if(typeof self.db.safe == "boolean") {
finalOptions = {w: (self.db.safe ? 1 : 0)};
}
// Ensure we don't have an invalid combination of write concerns
if(finalOptions.w < 1
&& (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true");
// Return the options
return finalOptions;
}
/**
* Expose.
*/
exports.Collection = Collection;
/**
Base object used for common functionality
**/
var BaseCommand = exports.BaseCommand = function BaseCommand() {
};
var id = 1;
BaseCommand.prototype.getRequestId = function getRequestId() {
if (!this.requestId) this.requestId = id++;
return this.requestId;
};
BaseCommand.prototype.setMongosReadPreference = function setMongosReadPreference(readPreference, tags) {}
BaseCommand.prototype.updateRequestId = function() {
this.requestId = id++;
return this.requestId;
};
// OpCodes
BaseCommand.OP_REPLY = 1;
BaseCommand.OP_MSG = 1000;
BaseCommand.OP_UPDATE = 2001;
BaseCommand.OP_INSERT = 2002;
BaseCommand.OP_GET_BY_OID = 2003;
BaseCommand.OP_QUERY = 2004;
BaseCommand.OP_GET_MORE = 2005;
BaseCommand.OP_DELETE = 2006;
BaseCommand.OP_KILL_CURSORS = 2007;
var QueryCommand = require('./query_command').QueryCommand,
InsertCommand = require('./insert_command').InsertCommand,
inherits = require('util').inherits,
utils = require('../utils'),
crypto = require('crypto');
/**
Db Command
**/
var DbCommand = exports.DbCommand = function(dbInstance, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) {
QueryCommand.call(this);
this.collectionName = collectionName;
this.queryOptions = queryOptions;
this.numberToSkip = numberToSkip;
this.numberToReturn = numberToReturn;
this.query = query;
this.returnFieldSelector = returnFieldSelector;
this.db = dbInstance;
if(this.db && this.db.slaveOk) {
this.queryOptions |= QueryCommand.OPTS_SLAVE;
}
// Make sure we don't get a null exception
options = options == null ? {} : options;
// Let us defined on a command basis if we want functions to be serialized or not
if(options['serializeFunctions'] != null && options['serializeFunctions']) {
this.serializeFunctions = true;
}
};
inherits(DbCommand, QueryCommand);
// Constants
DbCommand.SYSTEM_NAMESPACE_COLLECTION = "system.namespaces";
DbCommand.SYSTEM_INDEX_COLLECTION = "system.indexes";
DbCommand.SYSTEM_PROFILE_COLLECTION = "system.profile";
DbCommand.SYSTEM_USER_COLLECTION = "system.users";
DbCommand.SYSTEM_COMMAND_COLLECTION = "$cmd";
DbCommand.SYSTEM_JS_COLLECTION = "system.js";
// New commands
DbCommand.NcreateIsMasterCommand = function(db, databaseName) {
return new DbCommand(db, databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null);
};
// Provide constructors for different db commands
DbCommand.createIsMasterCommand = function(db) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null);
};
DbCommand.createCollectionInfoCommand = function(db, selector) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_NAMESPACE_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, 0, selector, null);
};
DbCommand.createGetNonceCommand = function(db, options) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getnonce':1}, null);
};
DbCommand.createAuthenticationCommand = function(db, username, password, nonce, authdb) {
// Use node md5 generator
var md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ":mongo:" + password);
var hash_password = md5.digest('hex');
// Final key
md5 = crypto.createHash('md5');
md5.update(nonce + username + hash_password);
var key = md5.digest('hex');
// Creat selector
var selector = {'authenticate':1, 'user':username, 'nonce':nonce, 'key':key};
// Create db command
return new DbCommand(db, authdb + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NONE, 0, -1, selector, null);
};
DbCommand.createLogoutCommand = function(db) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'logout':1}, null);
};
DbCommand.createCreateCollectionCommand = function(db, collectionName, options) {
var selector = {'create':collectionName};
// Modify the options to ensure correct behaviour
for(var name in options) {
if(options[name] != null && options[name].constructor != Function) selector[name] = options[name];
}
// Execute the command
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, selector, null);
};
DbCommand.createDropCollectionCommand = function(db, collectionName) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'drop':collectionName}, null);
};
DbCommand.createRenameCollectionCommand = function(db, fromCollectionName, toCollectionName, options) {
var renameCollection = db.databaseName + "." + fromCollectionName;
var toCollection = db.databaseName + "." + toCollectionName;
var dropTarget = options && options.dropTarget ? options.dropTarget : false;
return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'renameCollection':renameCollection, 'to':toCollection, 'dropTarget':dropTarget}, null);
};
DbCommand.createGetLastErrorCommand = function(options, db) {
if (typeof db === 'undefined') {
db = options;
options = {};
}
// Final command
var command = {'getlasterror':1};
// If we have an options Object let's merge in the fields (fsync/wtimeout/w)
if('object' === typeof options) {
for(var name in options) {
command[name] = options[name]
}
}
// Special case for w == 1, remove the w
if(1 == command.w) {
delete command.w;
}
// Execute command
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null);
};
DbCommand.createGetLastStatusCommand = DbCommand.createGetLastErrorCommand;
DbCommand.createGetPreviousErrorsCommand = function(db) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getpreverror':1}, null);
};
DbCommand.createResetErrorHistoryCommand = function(db) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reseterror':1}, null);
};
DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, options) {
var fieldHash = {};
var indexes = [];
var keys;
// Get all the fields accordingly
if('string' == typeof fieldOrSpec) {
// 'type'
indexes.push(fieldOrSpec + '_' + 1);
fieldHash[fieldOrSpec] = 1;
} else if(utils.isArray(fieldOrSpec)) {
fieldOrSpec.forEach(function(f) {
if('string' == typeof f) {
// [{location:'2d'}, 'type']
indexes.push(f + '_' + 1);
fieldHash[f] = 1;
} else if(utils.isArray(f)) {
// [['location', '2d'],['type', 1]]
indexes.push(f[0] + '_' + (f[1] || 1));
fieldHash[f[0]] = f[1] || 1;
} else if(utils.isObject(f)) {
// [{location:'2d'}, {type:1}]
keys = Object.keys(f);
keys.forEach(function(k) {
indexes.push(k + '_' + f[k]);
fieldHash[k] = f[k];
});
} else {
// undefined (ignore)
}
});
} else if(utils.isObject(fieldOrSpec)) {
// {location:'2d', type:1}
keys = Object.keys(fieldOrSpec);
keys.forEach(function(key) {
indexes.push(key + '_' + fieldOrSpec[key]);
fieldHash[key] = fieldOrSpec[key];
});
}
// Generate the index name
var indexName = typeof options.name == 'string'
? options.name
: indexes.join("_");
var selector = {
'ns': db.databaseName + "." + collectionName,
'key': fieldHash,
'name': indexName
}
// Ensure we have a correct finalUnique
var finalUnique = options == null || 'object' === typeof options
? false
: options;
// Set up options
options = options == null || typeof options == 'boolean'
? {}
: options;
// Add all the options
var keys = Object.keys(options);
for(var i = 0; i < keys.length; i++) {
selector[keys[i]] = options[keys[i]];
}
if(selector['unique'] == null)
selector['unique'] = finalUnique;
var name = db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION;
var cmd = new InsertCommand(db, name, false);
return cmd.add(selector);
};
DbCommand.logoutCommand = function(db, command_hash, options) {
var dbName = options != null && options['authdb'] != null ? options['authdb'] : db.databaseName;
return new DbCommand(db, dbName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null);
}
DbCommand.createDropIndexCommand = function(db, collectionName, indexName) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'deleteIndexes':collectionName, 'index':indexName}, null);
};
DbCommand.createReIndexCommand = function(db, collectionName) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reIndex':collectionName}, null);
};
DbCommand.createDropDatabaseCommand = function(db) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'dropDatabase':1}, null);
};
DbCommand.createDbCommand = function(db, command_hash, options, auth_db) {
var db_name = (auth_db ? auth_db : db.databaseName) + "." + DbCommand.SYSTEM_COMMAND_COLLECTION;
return new DbCommand(db, db_name, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null, options);
};
DbCommand.createAdminDbCommand = function(db, command_hash) {
return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null);
};
DbCommand.createAdminDbCommandSlaveOk = function(db, command_hash) {
return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null);
};
DbCommand.createDbSlaveOkCommand = function(db, command_hash, options) {
return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null, options);
};
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits;
/**
Insert Document Command
**/
var DeleteCommand = exports.DeleteCommand = function(db, collectionName, selector, flags) {
BaseCommand.call(this);
// Validate correctness off the selector
var object = selector;
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("delete raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
this.flags = flags;
this.collectionName = collectionName;
this.selector = selector;
this.db = db;
};
inherits(DeleteCommand, BaseCommand);
DeleteCommand.OP_DELETE = 2006;
/*
struct {
MsgHeader header; // standard message header
int32 ZERO; // 0 - reserved for future use
cstring fullCollectionName; // "dbname.collectionname"
int32 ZERO; // 0 - reserved for future use
mongo.BSON selector; // query object. See below for details.
}
*/
DeleteCommand.prototype.toBinary = function(bsonSettings) {
// Validate that we are not passing 0x00 in the colletion name
if(!!~this.collectionName.indexOf("\x00")) {
throw new Error("namespace cannot contain a null character");
}
// Calculate total length of the document
var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.selector, false, true) + (4 * 4);
// Enforce maximum bson size
if(!bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxBsonSize)
throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes");
if(bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxMessageSizeBytes)
throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes");
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff;
_command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index] = totalLengthOfCommand & 0xff;
// Adjust index
_index = _index + 4;
// Write the request ID
_command[_index + 3] = (this.requestId >> 24) & 0xff;
_command[_index + 2] = (this.requestId >> 16) & 0xff;
_command[_index + 1] = (this.requestId >> 8) & 0xff;
_command[_index] = this.requestId & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index + 3] = (DeleteCommand.OP_DELETE >> 24) & 0xff;
_command[_index + 2] = (DeleteCommand.OP_DELETE >> 16) & 0xff;
_command[_index + 1] = (DeleteCommand.OP_DELETE >> 8) & 0xff;
_command[_index] = DeleteCommand.OP_DELETE & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the collection name to the command
_index = _index + _command.write(this.collectionName, _index, 'utf8') + 1;
_command[_index - 1] = 0;
// Write the flags
_command[_index + 3] = (this.flags >> 24) & 0xff;
_command[_index + 2] = (this.flags >> 16) & 0xff;
_command[_index + 1] = (this.flags >> 8) & 0xff;
_command[_index] = this.flags & 0xff;
// Adjust index
_index = _index + 4;
// Document binary length
var documentLength = 0
// Serialize the selector
// If we are passing a raw buffer, do minimal validation
if(Buffer.isBuffer(this.selector)) {
documentLength = this.selector.length;
// Copy the data into the current buffer
this.selector.copy(_command, _index);
} else {
documentLength = this.db.bson.serializeWithBufferAndIndex(this.selector, this.checkKeys, _command, _index) - _index + 1;
}
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
return _command;
};
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits,
binaryutils = require('../utils');
/**
Get More Document Command
**/
var GetMoreCommand = exports.GetMoreCommand = function(db, collectionName, numberToReturn, cursorId) {
BaseCommand.call(this);
this.collectionName = collectionName;
this.numberToReturn = numberToReturn;
this.cursorId = cursorId;
this.db = db;
};
inherits(GetMoreCommand, BaseCommand);
GetMoreCommand.OP_GET_MORE = 2005;
GetMoreCommand.prototype.toBinary = function() {
// Validate that we are not passing 0x00 in the colletion name
if(!!~this.collectionName.indexOf("\x00")) {
throw new Error("namespace cannot contain a null character");
}
// Calculate total length of the document
var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 8 + (4 * 4);
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index++] = totalLengthOfCommand & 0xff;
_command[_index++] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index++] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index++] = (totalLengthOfCommand >> 24) & 0xff;
// Write the request ID
_command[_index++] = this.requestId & 0xff;
_command[_index++] = (this.requestId >> 8) & 0xff;
_command[_index++] = (this.requestId >> 16) & 0xff;
_command[_index++] = (this.requestId >> 24) & 0xff;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index++] = GetMoreCommand.OP_GET_MORE & 0xff;
_command[_index++] = (GetMoreCommand.OP_GET_MORE >> 8) & 0xff;
_command[_index++] = (GetMoreCommand.OP_GET_MORE >> 16) & 0xff;
_command[_index++] = (GetMoreCommand.OP_GET_MORE >> 24) & 0xff;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the collection name to the command
_index = _index + _command.write(this.collectionName, _index, 'utf8') + 1;
_command[_index - 1] = 0;
// Number of documents to return
_command[_index++] = this.numberToReturn & 0xff;
_command[_index++] = (this.numberToReturn >> 8) & 0xff;
_command[_index++] = (this.numberToReturn >> 16) & 0xff;
_command[_index++] = (this.numberToReturn >> 24) & 0xff;
// Encode the cursor id
var low_bits = this.cursorId.getLowBits();
// Encode low bits
_command[_index++] = low_bits & 0xff;
_command[_index++] = (low_bits >> 8) & 0xff;
_command[_index++] = (low_bits >> 16) & 0xff;
_command[_index++] = (low_bits >> 24) & 0xff;
var high_bits = this.cursorId.getHighBits();
// Encode high bits
_command[_index++] = high_bits & 0xff;
_command[_index++] = (high_bits >> 8) & 0xff;
_command[_index++] = (high_bits >> 16) & 0xff;
_command[_index++] = (high_bits >> 24) & 0xff;
// Return command
return _command;
};
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits;
/**
Insert Document Command
**/
var InsertCommand = exports.InsertCommand = function(db, collectionName, checkKeys, options) {
BaseCommand.call(this);
this.collectionName = collectionName;
this.documents = [];
this.checkKeys = checkKeys == null ? true : checkKeys;
this.db = db;
this.flags = 0;
this.serializeFunctions = false;
// Ensure valid options hash
options = options == null ? {} : options;
// Check if we have keepGoing set -> set flag if it's the case
if(options['keepGoing'] != null && options['keepGoing']) {
// This will finish inserting all non-index violating documents even if it returns an error
this.flags = 1;
}
// Check if we have keepGoing set -> set flag if it's the case
if(options['continueOnError'] != null && options['continueOnError']) {
// This will finish inserting all non-index violating documents even if it returns an error
this.flags = 1;
}
// Let us defined on a command basis if we want functions to be serialized or not
if(options['serializeFunctions'] != null && options['serializeFunctions']) {
this.serializeFunctions = true;
}
};
inherits(InsertCommand, BaseCommand);
// OpCodes
InsertCommand.OP_INSERT = 2002;
InsertCommand.prototype.add = function(document) {
if(Buffer.isBuffer(document)) {
var object_size = document[0] | document[1] << 8 | document[2] << 16 | document[3] << 24;
if(object_size != document.length) {
var error = new Error("insert raw message size does not match message header size [" + document.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
this.documents.push(document);
return this;
};
/*
struct {
MsgHeader header; // standard message header
int32 ZERO; // 0 - reserved for future use
cstring fullCollectionName; // "dbname.collectionname"
BSON[] documents; // one or more documents to insert into the collection
}
*/
InsertCommand.prototype.toBinary = function(bsonSettings) {
// Validate that we are not passing 0x00 in the colletion name
if(!!~this.collectionName.indexOf("\x00")) {
throw new Error("namespace cannot contain a null character");
}
// Calculate total length of the document
var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + (4 * 4);
// var docLength = 0
for(var i = 0; i < this.documents.length; i++) {
if(Buffer.isBuffer(this.documents[i])) {
totalLengthOfCommand += this.documents[i].length;
} else {
// Calculate size of document
totalLengthOfCommand += this.db.bson.calculateObjectSize(this.documents[i], this.serializeFunctions, true);
}
}
// Enforce maximum bson size
if(!bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxBsonSize)
throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes");
if(bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxMessageSizeBytes)
throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes");
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff;
_command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index] = totalLengthOfCommand & 0xff;
// Adjust index
_index = _index + 4;
// Write the request ID
_command[_index + 3] = (this.requestId >> 24) & 0xff;
_command[_index + 2] = (this.requestId >> 16) & 0xff;
_command[_index + 1] = (this.requestId >> 8) & 0xff;
_command[_index] = this.requestId & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index + 3] = (InsertCommand.OP_INSERT >> 24) & 0xff;
_command[_index + 2] = (InsertCommand.OP_INSERT >> 16) & 0xff;
_command[_index + 1] = (InsertCommand.OP_INSERT >> 8) & 0xff;
_command[_index] = InsertCommand.OP_INSERT & 0xff;
// Adjust index
_index = _index + 4;
// Write flags if any
_command[_index + 3] = (this.flags >> 24) & 0xff;
_command[_index + 2] = (this.flags >> 16) & 0xff;
_command[_index + 1] = (this.flags >> 8) & 0xff;
_command[_index] = this.flags & 0xff;
// Adjust index
_index = _index + 4;
// Write the collection name to the command
_index = _index + _command.write(this.collectionName, _index, 'utf8') + 1;
_command[_index - 1] = 0;
// Write all the bson documents to the buffer at the index offset
for(var i = 0; i < this.documents.length; i++) {
// Document binary length
var documentLength = 0
var object = this.documents[i];
// Serialize the selector
// If we are passing a raw buffer, do minimal validation
if(Buffer.isBuffer(object)) {
documentLength = object.length;
// Copy the data into the current buffer
object.copy(_command, _index);
} else {
// Serialize the document straight to the buffer
documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1;
}
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
}
return _command;
};
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits,
binaryutils = require('../utils');
/**
Insert Document Command
**/
var KillCursorCommand = exports.KillCursorCommand = function(db, cursorIds) {
BaseCommand.call(this);
this.cursorIds = cursorIds;
this.db = db;
};
inherits(KillCursorCommand, BaseCommand);
KillCursorCommand.OP_KILL_CURSORS = 2007;
/*
struct {
MsgHeader header; // standard message header
int32 ZERO; // 0 - reserved for future use
int32 numberOfCursorIDs; // number of cursorIDs in message
int64[] cursorIDs; // array of cursorIDs to close
}
*/
KillCursorCommand.prototype.toBinary = function() {
// Calculate total length of the document
var totalLengthOfCommand = 4 + 4 + (4 * 4) + (this.cursorIds.length * 8);
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff;
_command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index] = totalLengthOfCommand & 0xff;
// Adjust index
_index = _index + 4;
// Write the request ID
_command[_index + 3] = (this.requestId >> 24) & 0xff;
_command[_index + 2] = (this.requestId >> 16) & 0xff;
_command[_index + 1] = (this.requestId >> 8) & 0xff;
_command[_index] = this.requestId & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index + 3] = (KillCursorCommand.OP_KILL_CURSORS >> 24) & 0xff;
_command[_index + 2] = (KillCursorCommand.OP_KILL_CURSORS >> 16) & 0xff;
_command[_index + 1] = (KillCursorCommand.OP_KILL_CURSORS >> 8) & 0xff;
_command[_index] = KillCursorCommand.OP_KILL_CURSORS & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Number of cursors to kill
var numberOfCursors = this.cursorIds.length;
_command[_index + 3] = (numberOfCursors >> 24) & 0xff;
_command[_index + 2] = (numberOfCursors >> 16) & 0xff;
_command[_index + 1] = (numberOfCursors >> 8) & 0xff;
_command[_index] = numberOfCursors & 0xff;
// Adjust index
_index = _index + 4;
// Encode all the cursors
for(var i = 0; i < this.cursorIds.length; i++) {
// Encode the cursor id
var low_bits = this.cursorIds[i].getLowBits();
// Encode low bits
_command[_index + 3] = (low_bits >> 24) & 0xff;
_command[_index + 2] = (low_bits >> 16) & 0xff;
_command[_index + 1] = (low_bits >> 8) & 0xff;
_command[_index] = low_bits & 0xff;
// Adjust index
_index = _index + 4;
var high_bits = this.cursorIds[i].getHighBits();
// Encode high bits
_command[_index + 3] = (high_bits >> 24) & 0xff;
_command[_index + 2] = (high_bits >> 16) & 0xff;
_command[_index + 1] = (high_bits >> 8) & 0xff;
_command[_index] = high_bits & 0xff;
// Adjust index
_index = _index + 4;
}
return _command;
};
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits;
/**
Insert Document Command
**/
var QueryCommand = exports.QueryCommand = function(db, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) {
BaseCommand.call(this);
// Validate correctness off the selector
var object = query,
object_size;
if(Buffer.isBuffer(object)) {
object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
object = returnFieldSelector;
if(Buffer.isBuffer(object)) {
object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
// Make sure we don't get a null exception
options = options == null ? {} : options;
// Set up options
this.collectionName = collectionName;
this.queryOptions = queryOptions;
this.numberToSkip = numberToSkip;
this.numberToReturn = numberToReturn;
// Ensure we have no null query
query = query == null ? {} : query;
// Wrap query in the $query parameter so we can add read preferences for mongos
this.query = query;
this.returnFieldSelector = returnFieldSelector;
this.db = db;
// Force the slave ok flag to be set if we are not using primary read preference
if(this.db && this.db.slaveOk) {
this.queryOptions |= QueryCommand.OPTS_SLAVE;
}
// Let us defined on a command basis if we want functions to be serialized or not
if(options['serializeFunctions'] != null && options['serializeFunctions']) {
this.serializeFunctions = true;
}
};
inherits(QueryCommand, BaseCommand);
QueryCommand.OP_QUERY = 2004;
/*
* Adds the read prefrence to the current command
*/
QueryCommand.prototype.setMongosReadPreference = function(readPreference, tags) {
// If we have readPreference set to true set to secondary prefered
if(readPreference == true) {
readPreference = 'secondaryPreferred';
} else if(readPreference == 'false') {
readPreference = 'primary';
}
// Force the slave ok flag to be set if we are not using primary read preference
if(readPreference != false && readPreference != 'primary') {
this.queryOptions |= QueryCommand.OPTS_SLAVE;
}
// Backward compatibility, ensure $query only set on read preference so 1.8.X works
if((readPreference != null || tags != null) && this.query['$query'] == null) {
this.query = {'$query': this.query};
}
// If we have no readPreference set and no tags, check if the slaveOk bit is set
if(readPreference == null && tags == null) {
// If we have a slaveOk bit set the read preference for MongoS
if(this.queryOptions & QueryCommand.OPTS_SLAVE) {
this.query['$readPreference'] = {mode: 'secondary'}
} else {
this.query['$readPreference'] = {mode: 'primary'}
}
}
// Build read preference object
if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') {
this.query['$readPreference'] = readPreference.toObject();
} else if(readPreference != null) {
// Add the read preference
this.query['$readPreference'] = {mode: readPreference};
// If we have tags let's add them
if(tags != null) {
this.query['$readPreference']['tags'] = tags;
}
}
}
/*
struct {
MsgHeader header; // standard message header
int32 opts; // query options. See below for details.
cstring fullCollectionName; // "dbname.collectionname"
int32 numberToSkip; // number of documents to skip when returning results
int32 numberToReturn; // number of documents to return in the first OP_REPLY
BSON query ; // query object. See below for details.
[ BSON returnFieldSelector; ] // OPTIONAL : selector indicating the fields to return. See below for details.
}
*/
QueryCommand.prototype.toBinary = function(bsonSettings) {
// Validate that we are not passing 0x00 in the colletion name
if(!!~this.collectionName.indexOf("\x00")) {
throw new Error("namespace cannot contain a null character");
}
// Total length of the command
var totalLengthOfCommand = 0;
// Calculate total length of the document
if(Buffer.isBuffer(this.query)) {
totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.query.length + (4 * 4);
} else {
totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.db.bson.calculateObjectSize(this.query, this.serializeFunctions, true) + (4 * 4);
}
// Calculate extra fields size
if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) {
if(Object.keys(this.returnFieldSelector).length > 0) {
totalLengthOfCommand += this.db.bson.calculateObjectSize(this.returnFieldSelector, this.serializeFunctions, true);
}
} else if(Buffer.isBuffer(this.returnFieldSelector)) {
totalLengthOfCommand += this.returnFieldSelector.length;
}
// Enforce maximum bson size
if(!bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxBsonSize)
throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes");
if(bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxMessageSizeBytes)
throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes");
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff;
_command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index] = totalLengthOfCommand & 0xff;
// Adjust index
_index = _index + 4;
// Write the request ID
_command[_index + 3] = (this.requestId >> 24) & 0xff;
_command[_index + 2] = (this.requestId >> 16) & 0xff;
_command[_index + 1] = (this.requestId >> 8) & 0xff;
_command[_index] = this.requestId & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index + 3] = (QueryCommand.OP_QUERY >> 24) & 0xff;
_command[_index + 2] = (QueryCommand.OP_QUERY >> 16) & 0xff;
_command[_index + 1] = (QueryCommand.OP_QUERY >> 8) & 0xff;
_command[_index] = QueryCommand.OP_QUERY & 0xff;
// Adjust index
_index = _index + 4;
// Write the query options
_command[_index + 3] = (this.queryOptions >> 24) & 0xff;
_command[_index + 2] = (this.queryOptions >> 16) & 0xff;
_command[_index + 1] = (this.queryOptions >> 8) & 0xff;
_command[_index] = this.queryOptions & 0xff;
// Adjust index
_index = _index + 4;
// Write the collection name to the command
_index = _index + _command.write(this.collectionName, _index, 'utf8') + 1;
_command[_index - 1] = 0;
// Write the number of documents to skip
_command[_index + 3] = (this.numberToSkip >> 24) & 0xff;
_command[_index + 2] = (this.numberToSkip >> 16) & 0xff;
_command[_index + 1] = (this.numberToSkip >> 8) & 0xff;
_command[_index] = this.numberToSkip & 0xff;
// Adjust index
_index = _index + 4;
// Write the number of documents to return
_command[_index + 3] = (this.numberToReturn >> 24) & 0xff;
_command[_index + 2] = (this.numberToReturn >> 16) & 0xff;
_command[_index + 1] = (this.numberToReturn >> 8) & 0xff;
_command[_index] = this.numberToReturn & 0xff;
// Adjust index
_index = _index + 4;
// Document binary length
var documentLength = 0
var object = this.query;
// Serialize the selector
if(Buffer.isBuffer(object)) {
documentLength = object.length;
// Copy the data into the current buffer
object.copy(_command, _index);
} else {
// Serialize the document straight to the buffer
documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1;
}
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
// Push field selector if available
if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) {
if(Object.keys(this.returnFieldSelector).length > 0) {
var documentLength = this.db.bson.serializeWithBufferAndIndex(this.returnFieldSelector, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1;
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
}
} if(this.returnFieldSelector != null && Buffer.isBuffer(this.returnFieldSelector)) {
// Document binary length
var documentLength = 0
var object = this.returnFieldSelector;
// Serialize the selector
documentLength = object.length;
// Copy the data into the current buffer
object.copy(_command, _index);
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
}
// Return finished command
return _command;
};
// Constants
QueryCommand.OPTS_NONE = 0;
QueryCommand.OPTS_TAILABLE_CURSOR = 2;
QueryCommand.OPTS_SLAVE = 4;
QueryCommand.OPTS_OPLOG_REPLY = 8;
QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16;
QueryCommand.OPTS_AWAIT_DATA = 32;
QueryCommand.OPTS_EXHAUST = 64;
QueryCommand.OPTS_PARTIAL = 128;
var BaseCommand = require('./base_command').BaseCommand,
inherits = require('util').inherits;
/**
Update Document Command
**/
var UpdateCommand = exports.UpdateCommand = function(db, collectionName, spec, document, options) {
BaseCommand.call(this);
var object = spec;
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("update spec raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
var object = document;
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) {
var error = new Error("update document raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
error.name = 'MongoError';
throw error;
}
}
this.collectionName = collectionName;
this.spec = spec;
this.document = document;
this.db = db;
this.serializeFunctions = false;
this.checkKeys = typeof options.checkKeys != 'boolean' ? false : options.checkKeys;
// Generate correct flags
var db_upsert = 0;
var db_multi_update = 0;
db_upsert = options != null && options['upsert'] != null ? (options['upsert'] == true ? 1 : 0) : db_upsert;
db_multi_update = options != null && options['multi'] != null ? (options['multi'] == true ? 1 : 0) : db_multi_update;
// Flags
this.flags = parseInt(db_multi_update.toString() + db_upsert.toString(), 2);
// Let us defined on a command basis if we want functions to be serialized or not
if(options['serializeFunctions'] != null && options['serializeFunctions']) {
this.serializeFunctions = true;
}
};
inherits(UpdateCommand, BaseCommand);
UpdateCommand.OP_UPDATE = 2001;
/*
struct {
MsgHeader header; // standard message header
int32 ZERO; // 0 - reserved for future use
cstring fullCollectionName; // "dbname.collectionname"
int32 flags; // bit vector. see below
BSON spec; // the query to select the document
BSON document; // the document data to update with or insert
}
*/
UpdateCommand.prototype.toBinary = function(bsonSettings) {
// Validate that we are not passing 0x00 in the colletion name
if(!!~this.collectionName.indexOf("\x00")) {
throw new Error("namespace cannot contain a null character");
}
// Calculate total length of the document
var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.spec, false, true) +
this.db.bson.calculateObjectSize(this.document, this.serializeFunctions, true) + (4 * 4);
// Enforce maximum bson size
if(!bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxBsonSize)
throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes");
if(bsonSettings.disableDriverBSONSizeCheck
&& totalLengthOfCommand > bsonSettings.maxMessageSizeBytes)
throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes");
// Let's build the single pass buffer command
var _index = 0;
var _command = new Buffer(totalLengthOfCommand);
// Write the header information to the buffer
_command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff;
_command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff;
_command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff;
_command[_index] = totalLengthOfCommand & 0xff;
// Adjust index
_index = _index + 4;
// Write the request ID
_command[_index + 3] = (this.requestId >> 24) & 0xff;
_command[_index + 2] = (this.requestId >> 16) & 0xff;
_command[_index + 1] = (this.requestId >> 8) & 0xff;
_command[_index] = this.requestId & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the op_code for the command
_command[_index + 3] = (UpdateCommand.OP_UPDATE >> 24) & 0xff;
_command[_index + 2] = (UpdateCommand.OP_UPDATE >> 16) & 0xff;
_command[_index + 1] = (UpdateCommand.OP_UPDATE >> 8) & 0xff;
_command[_index] = UpdateCommand.OP_UPDATE & 0xff;
// Adjust index
_index = _index + 4;
// Write zero
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
_command[_index++] = 0;
// Write the collection name to the command
_index = _index + _command.write(this.collectionName, _index, 'utf8') + 1;
_command[_index - 1] = 0;
// Write the update flags
_command[_index + 3] = (this.flags >> 24) & 0xff;
_command[_index + 2] = (this.flags >> 16) & 0xff;
_command[_index + 1] = (this.flags >> 8) & 0xff;
_command[_index] = this.flags & 0xff;
// Adjust index
_index = _index + 4;
// Document binary length
var documentLength = 0
var object = this.spec;
// Serialize the selector
// If we are passing a raw buffer, do minimal validation
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
documentLength = object.length;
// Copy the data into the current buffer
object.copy(_command, _index);
} else {
documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, false) - _index + 1;
}
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
// Document binary length
var documentLength = 0
var object = this.document;
// Serialize the document
// If we are passing a raw buffer, do minimal validation
if(Buffer.isBuffer(object)) {
var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24;
if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]");
documentLength = object.length;
// Copy the data into the current buffer
object.copy(_command, _index);
} else {
documentLength = this.db.bson.serializeWithBufferAndIndex(object, false, _command, _index, this.serializeFunctions) - _index + 1;
}
// Write the length to the document
_command[_index + 3] = (documentLength >> 24) & 0xff;
_command[_index + 2] = (documentLength >> 16) & 0xff;
_command[_index + 1] = (documentLength >> 8) & 0xff;
_command[_index] = documentLength & 0xff;
// Update index in buffer
_index = _index + documentLength;
// Add terminating 0 for the object
_command[_index - 1] = 0;
return _command;
};
// Constants
UpdateCommand.DB_UPSERT = 0;
UpdateCommand.DB_MULTI_UPDATE = 1;
var EventEmitter = require('events').EventEmitter
, inherits = require('util').inherits
, mongodb_cr_authenticate = require('../auth/mongodb_cr.js').authenticate
, mongodb_gssapi_authenticate = require('../auth/mongodb_gssapi.js').authenticate
, mongodb_sspi_authenticate = require('../auth/mongodb_sspi.js').authenticate;
var id = 0;
/**
* Internal class for callback storage
* @ignore
*/
var CallbackStore = function() {
// Make class an event emitter
EventEmitter.call(this);
// Add a info about call variable
this._notReplied = {};
this.id = id++;
}
/**
* @ignore
*/
inherits(CallbackStore, EventEmitter);
CallbackStore.prototype.notRepliedToIds = function() {
return Object.keys(this._notReplied);
}
CallbackStore.prototype.callbackInfo = function(id) {
return this._notReplied[id];
}
/**
* Internal class for holding non-executed commands
* @ignore
*/
var NonExecutedOperationStore = function(config) {
this.config = config;
this.commands = {
read: []
, write_reads: []
, write: []
};
}
NonExecutedOperationStore.prototype.write = function(op) {
this.commands.write.push(op);
}
NonExecutedOperationStore.prototype.read_from_writer = function(op) {
this.commands.write_reads.push(op);
}
NonExecutedOperationStore.prototype.read = function(op) {
this.commands.read.push(op);
}
NonExecutedOperationStore.prototype.execute_queries = function(executeInsertCommand) {
var connection = this.config.checkoutReader();
if(connection == null || connection instanceof Error) return;
// Write out all the queries
while(this.commands.read.length > 0) {
// Get the next command
var command = this.commands.read.shift();
command.options.connection = connection;
// Execute the next command
command.executeQueryCommand(command.db, command.db_command, command.options, command.callback);
}
}
NonExecutedOperationStore.prototype.execute_writes = function() {
var connection = this.config.checkoutWriter();
if(connection == null || connection instanceof Error) return;
// Write out all the queries to the primary
while(this.commands.write_reads.length > 0) {
// Get the next command
var command = this.commands.write_reads.shift();
command.options.connection = connection;
// Execute the next command
command.executeQueryCommand(command.db, command.db_command, command.options, command.callback);
}
// Execute all write operations
while(this.commands.write.length > 0) {
// Get the next command
var command = this.commands.write.shift();
// Set the connection
command.options.connection = connection;
// Execute the next command
command.executeInsertCommand(command.db, command.db_command, command.options, command.callback);
}
}
/**
* Internal class for authentication storage
* @ignore
*/
var AuthStore = function() {
this._auths = [];
}
AuthStore.prototype.add = function(authMechanism, dbName, username, password, authdbName, gssapiServiceName) {
// Check for duplicates
if(!this.contains(dbName)) {
// Base config
var config = {
'username':username
, 'password':password
, 'db': dbName
, 'authMechanism': authMechanism
, 'gssapiServiceName': gssapiServiceName
};
// Add auth source if passed in
if(typeof authdbName == 'string') {
config['authdb'] = authdbName;
}
// Push the config
this._auths.push(config);
}
}
AuthStore.prototype.contains = function(dbName) {
for(var i = 0; i < this._auths.length; i++) {
if(this._auths[i].db == dbName) return true;
}
return false;
}
AuthStore.prototype.remove = function(dbName) {
var newAuths = [];
// Filter out all the login details
for(var i = 0; i < this._auths.length; i++) {
if(this._auths[i].db != dbName) newAuths.push(this._auths[i]);
}
// Set the filtered list
this._auths = newAuths;
}
AuthStore.prototype.get = function(index) {
return this._auths[index];
}
AuthStore.prototype.length = function() {
return this._auths.length;
}
AuthStore.prototype.toArray = function() {
return this._auths.slice(0);
}
/**
* Internal class for storing db references
* @ignore
*/
var DbStore = function() {
this._dbs = [];
}
DbStore.prototype.add = function(db) {
var found = false;
// Only add if it does not exist already
for(var i = 0; i < this._dbs.length; i++) {
if(db.databaseName == this._dbs[i].databaseName) found = true;
}
// Only add if it does not already exist
if(!found) {
this._dbs.push(db);
}
}
DbStore.prototype.reset = function() {
this._dbs = [];
}
DbStore.prototype.fetch = function(databaseName) {
// Only add if it does not exist already
for(var i = 0; i < this._dbs.length; i++) {
if(databaseName == this._dbs[i].databaseName)
return this._dbs[i];
}
return null;
}
DbStore.prototype.emit = function(event, message, object, reset, filterDb, rethrow_if_no_listeners) {
var emitted = false;
// Emit the events
for(var i = 0; i < this._dbs.length; i++) {
if(this._dbs[i].listeners(event).length > 0) {
if(filterDb == null || filterDb.databaseName !== this._dbs[i].databaseName
|| filterDb.tag !== this._dbs[i].tag) {
this._dbs[i].emit(event, message, object == null ? this._dbs[i] : object);
emitted = true;
}
}
}
// Emit error message
if(message
&& event == 'error'
&& !emitted
&& rethrow_if_no_listeners
&& object && object.db) {
process.nextTick(function() {
object.db.emit(event, message, null);
})
}
// Not emitted and we have enabled rethrow, let process.uncaughtException
// deal with the issue
if(!emitted && rethrow_if_no_listeners) {
throw message;
}
}
var Base = function Base() {
EventEmitter.call(this);
// Callback store is part of connection specification
if(Base._callBackStore == null) {
Base._callBackStore = new CallbackStore();
}
// Create a new callback store
this._callBackStore = new CallbackStore();
// All commands not being executed
this._commandsStore = new NonExecutedOperationStore(this);
// Create a new auth store
this.auth = new AuthStore();
// Contains all the dbs attached to this server config
this._dbStore = new DbStore();
}
/**
* @ignore
*/
inherits(Base, EventEmitter);
/**
* @ignore
*/
Base.prototype._apply_auths = function(db, callback) {
_apply_auths_serially(this, db, this.auth.toArray(), callback);
}
var _apply_auths_serially = function(self, db, auths, callback) {
if(auths.length == 0) return callback(null, null);
// Get the first auth
var auth = auths.shift();
var connections = self.allRawConnections();
var connectionsLeft = connections.length;
var options = {};
if(auth.authMechanism == 'GSSAPI') {
// We have the kerberos library, execute auth process
if(process.platform == 'win32') {
mongodb_sspi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback);
} else {
mongodb_gssapi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback);
}
} else if(auth.authMechanism == 'MONGODB-CR') {
mongodb_cr_authenticate(db, auth.username, auth.password, auth.authdb, options, callback);
}
}
/**
* Fire all the errors
* @ignore
*/
Base.prototype.__executeAllCallbacksWithError = function(err) {
// Check all callbacks
var keys = Object.keys(this._callBackStore._notReplied);
// For each key check if it's a callback that needs to be returned
for(var j = 0; j < keys.length; j++) {
var info = this._callBackStore._notReplied[keys[j]];
// Execute callback with error
this._callBackStore.emit(keys[j], err, null);
// Remove the key
delete this._callBackStore._notReplied[keys[j]];
// Force cleanup _events, node.js seems to set it as a null value
if(this._callBackStore._events) {
delete this._callBackStore._events[keys[j]];
}
}
}
/**
* Fire all the errors
* @ignore
*/
Base.prototype.__executeAllServerSpecificErrorCallbacks = function(host, port, err) {
// Check all callbacks
var keys = Object.keys(this._callBackStore._notReplied);
// For each key check if it's a callback that needs to be returned
for(var j = 0; j < keys.length; j++) {
var info = this._callBackStore._notReplied[keys[j]];
if(info.connection) {
// Unpack the connection settings
var _host = info.connection.socketOptions.host;
var _port = info.connection.socketOptions.port;
// If the server matches execute the callback with the error
if(_port == port && _host == host) {
this._callBackStore.emit(keys[j], err, null);
// Remove the key
delete this._callBackStore._notReplied[keys[j]];
// Force cleanup _events, node.js seems to set it as a null value
if(this._callBackStore._events) {
delete this._callBackStore._events[keys[j]];
}
}
}
}
}
/**
* Register a handler
* @ignore
* @api private
*/
Base.prototype._registerHandler = function(db_command, raw, connection, exhaust, callback) {
// Check if we have exhausted
if(typeof exhaust == 'function') {
callback = exhaust;
exhaust = false;
}
// Add the callback to the list of handlers
this._callBackStore.once(db_command.getRequestId(), callback);
// Add the information about the reply
this._callBackStore._notReplied[db_command.getRequestId().toString()] = {start: new Date().getTime(), 'raw': raw, connection:connection, exhaust:exhaust};
}
/**
* Re-Register a handler, on the cursor id f.ex
* @ignore
* @api private
*/
Base.prototype._reRegisterHandler = function(newId, object, callback) {
// Add the callback to the list of handlers
this._callBackStore.once(newId, object.callback.listener);
// Add the information about the reply
this._callBackStore._notReplied[newId] = object.info;
}
/**
*
* @ignore
* @api private
*/
Base.prototype._callHandler = function(id, document, err) {
var self = this;
// If there is a callback peform it
if(this._callBackStore.listeners(id).length >= 1) {
// Get info object
var info = this._callBackStore._notReplied[id];
// Delete the current object
delete this._callBackStore._notReplied[id];
// Call the handle directly don't emit
var callback = this._callBackStore.listeners(id)[0].listener;
// Remove the listeners
this._callBackStore.removeAllListeners(id);
// Force key deletion because it nulling it not deleting in 0.10.X
if(this._callBackStore._events) {
delete this._callBackStore._events[id];
}
try {
// Execute the callback if one was provided
if(typeof callback == 'function') callback(err, document, info.connection);
} catch(err) {
self._emitAcrossAllDbInstances(self, null, "error", err, self, true, true);
}
}
}
/**
*
* @ignore
* @api private
*/
Base.prototype._hasHandler = function(id) {
return this._callBackStore.listeners(id).length >= 1;
}
/**
*
* @ignore
* @api private
*/
Base.prototype._removeHandler = function(id) {
// Remove the information
if(this._callBackStore._notReplied[id] != null) delete this._callBackStore._notReplied[id];
// Remove the callback if it's registered
this._callBackStore.removeAllListeners(id);
// Force cleanup _events, node.js seems to set it as a null value
if(this._callBackStore._events) {
delete this._callBackStore._events[id];
}
}
/**
*
* @ignore
* @api private
*/
Base.prototype._findHandler = function(id) {
var info = this._callBackStore._notReplied[id];
// Return the callback
return {info:info, callback:(this._callBackStore.listeners(id).length >= 1) ? this._callBackStore.listeners(id)[0] : null}
}
/**
*
* @ignore
* @api private
*/
Base.prototype._emitAcrossAllDbInstances = function(server, filterDb, event, message, object, resetConnection, rethrow_if_no_listeners) {
if(resetConnection) {
for(var i = 0; i < this._dbStore._dbs.length; i++) {
if(typeof this._dbStore._dbs[i].openCalled != 'undefined')
this._dbStore._dbs[i].openCalled = false;
}
}
// Fire event
this._dbStore.emit(event, message, object, resetConnection, filterDb, rethrow_if_no_listeners);
}
exports.Base = Base;
var utils = require('./connection_utils'),
inherits = require('util').inherits,
net = require('net'),
EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits,
binaryutils = require('../utils'),
tls = require('tls');
var Connection = exports.Connection = function(id, socketOptions) {
// Set up event emitter
EventEmitter.call(this);
// Store all socket options
this.socketOptions = socketOptions ? socketOptions : {host:'localhost', port:27017, domainSocket:false};
// Set keep alive default if not overriden
if(this.socketOptions.keepAlive == null && (process.platform !== "sunos" || process.platform !== "win32")) this.socketOptions.keepAlive = 100;
// Id for the connection
this.id = id;
// State of the connection
this.connected = false;
// Set if this is a domain socket
this.domainSocket = this.socketOptions.domainSocket;
//
// Connection parsing state
//
this.maxBsonSize = socketOptions.maxBsonSize ? socketOptions.maxBsonSize : Connection.DEFAULT_MAX_BSON_SIZE;
this.maxMessageSizeBytes = socketOptions.maxMessageSizeBytes ? socketOptions.maxMessageSizeBytes : Connection.DEFAULT_MAX_MESSAGE_SIZE;
// Contains the current message bytes
this.buffer = null;
// Contains the current message size
this.sizeOfMessage = 0;
// Contains the readIndex for the messaage
this.bytesRead = 0;
// Contains spill over bytes from additional messages
this.stubBuffer = 0;
// Just keeps list of events we allow
this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[], end:[]};
// Just keeps list of events we allow
resetHandlers(this, false);
// Bson object
this.maxBsonSettings = {
disableDriverBSONSizeCheck: this.socketOptions['disableDriverBSONSizeCheck'] || false
, maxBsonSize: this.maxBsonSize
, maxMessageSizeBytes: this.maxMessageSizeBytes
}
}
// Set max bson size
Connection.DEFAULT_MAX_BSON_SIZE = 1024 * 1024 * 4;
// Set default to max bson to avoid overflow or bad guesses
Connection.DEFAULT_MAX_MESSAGE_SIZE = Connection.DEFAULT_MAX_BSON_SIZE;
// Inherit event emitter so we can emit stuff wohoo
inherits(Connection, EventEmitter);
Connection.prototype.start = function() {
var self = this;
// If we have a normal connection
if(this.socketOptions.ssl) {
// Create new connection instance
if(this.domainSocket) {
this.connection = net.createConnection(this.socketOptions.host);
} else {
this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host);
}
if(this.logger != null && this.logger.doDebug){
this.logger.debug("opened connection", this.socketOptions);
}
// Set options on the socket
this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout);
// Work around for 0.4.X
if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay);
// Set keep alive if defined
if(process.version.indexOf("v0.4") == -1) {
if(this.socketOptions.keepAlive > 0) {
this.connection.setKeepAlive(true, this.socketOptions.keepAlive);
} else {
this.connection.setKeepAlive(false);
}
}
// Check if the driver should validate the certificate
var validate_certificates = this.socketOptions.sslValidate == true ? true : false;
// Create options for the tls connection
var tls_options = {
socket: this.connection
, rejectUnauthorized: false
}
// If we wish to validate the certificate we have provided a ca store
if(validate_certificates) {
tls_options.ca = this.socketOptions.sslCA;
}
// If we have a certificate to present
if(this.socketOptions.sslCert) {
tls_options.cert = this.socketOptions.sslCert;
tls_options.key = this.socketOptions.sslKey;
}
// If the driver has been provided a private key password
if(this.socketOptions.sslPass) {
tls_options.passphrase = this.socketOptions.sslPass;
}
// Contains the cleartext stream
var cleartext = null;
// Attempt to establish a TLS connection to the server
try {
cleartext = tls.connect(this.socketOptions.port, this.socketOptions.host, tls_options, function() {
// If we have a ssl certificate validation error return an error
if(cleartext.authorizationError && validate_certificates) {
// Emit an error
return self.emit("error", cleartext.authorizationError, self, {ssl:true});
}
// Connect to the server
connectHandler(self)();
})
} catch(err) {
return self.emit("error", "SSL connection failed", self, {ssl:true});
}
// Save the output stream
this.writeSteam = cleartext;
// Set up data handler for the clear stream
cleartext.on("data", createDataHandler(this));
// Do any handling of end event of the stream
cleartext.on("end", endHandler(this));
cleartext.on("error", errorHandler(this));
// Handle any errors
this.connection.on("error", errorHandler(this));
// Handle timeout
this.connection.on("timeout", timeoutHandler(this));
// Handle drain event
this.connection.on("drain", drainHandler(this));
// Handle the close event
this.connection.on("close", closeHandler(this));
} else {
// Create new connection instance
if(this.domainSocket) {
this.connection = net.createConnection(this.socketOptions.host);
} else {
this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host);
}
if(this.logger != null && this.logger.doDebug){
this.logger.debug("opened connection", this.socketOptions);
}
// Set options on the socket
this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout);
// Work around for 0.4.X
if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay);
// Set keep alive if defined
if(process.version.indexOf("v0.4") == -1) {
if(this.socketOptions.keepAlive > 0) {
this.connection.setKeepAlive(true, this.socketOptions.keepAlive);
} else {
this.connection.setKeepAlive(false);
}
}
// Set up write stream
this.writeSteam = this.connection;
// Add handlers
this.connection.on("error", errorHandler(this));
// Add all handlers to the socket to manage it
this.connection.on("connect", connectHandler(this));
// this.connection.on("end", endHandler(this));
this.connection.on("data", createDataHandler(this));
this.connection.on("timeout", timeoutHandler(this));
this.connection.on("drain", drainHandler(this));
this.connection.on("close", closeHandler(this));
}
}
// Check if the sockets are live
Connection.prototype.isConnected = function() {
return this.connected && !this.connection.destroyed && this.connection.writable && this.connection.readable;
}
// Write the data out to the socket
Connection.prototype.write = function(command, callback) {
try {
// If we have a list off commands to be executed on the same socket
if(Array.isArray(command)) {
for(var i = 0; i < command.length; i++) {
try {
// Pass in the bson validation settings (validate early)
var binaryCommand = command[i].toBinary(this.maxBsonSettings)
if(this.logger != null && this.logger.doDebug)
this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]});
this.writeSteam.write(binaryCommand);
} catch(err) {
return callback(err, null);
}
}
} else {
try {
// Pass in the bson validation settings (validate early)
var binaryCommand = command.toBinary(this.maxBsonSettings)
if(this.logger != null && this.logger.doDebug)
this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]});
this.writeSteam.write(binaryCommand);
} catch(err) {
return callback(err, null)
}
}
} catch (err) {
if(typeof callback === 'function') callback(err);
}
}
// Force the closure of the connection
Connection.prototype.close = function() {
// clear out all the listeners
resetHandlers(this, true);
// Add a dummy error listener to catch any weird last moment errors (and ignore them)
this.connection.on("error", function() {})
// destroy connection
this.connection.destroy();
if(this.logger != null && this.logger.doDebug){
this.logger.debug("closed connection", this.connection);
}
}
// Reset all handlers
var resetHandlers = function(self, clearListeners) {
self.eventHandlers = {error:[], connect:[], close:[], end:[], timeout:[], parseError:[], message:[]};
// If we want to clear all the listeners
if(clearListeners && self.connection != null) {
var keys = Object.keys(self.eventHandlers);
// Remove all listeners
for(var i = 0; i < keys.length; i++) {
self.connection.removeAllListeners(keys[i]);
}
}
}
//
// Handlers
//
// Connect handler
var connectHandler = function(self) {
return function(data) {
// Set connected
self.connected = true;
// Now that we are connected set the socket timeout
self.connection.setTimeout(self.socketOptions.socketTimeoutMS != null ? self.socketOptions.socketTimeoutMS : self.socketOptions.timeout);
// Emit the connect event with no error
self.emit("connect", null, self);
}
}
var createDataHandler = exports.Connection.createDataHandler = function(self) {
// We need to handle the parsing of the data
// and emit the messages when there is a complete one
return function(data) {
// Parse until we are done with the data
while(data.length > 0) {
// If we still have bytes to read on the current message
if(self.bytesRead > 0 && self.sizeOfMessage > 0) {
// Calculate the amount of remaining bytes
var remainingBytesToRead = self.sizeOfMessage - self.bytesRead;
// Check if the current chunk contains the rest of the message
if(remainingBytesToRead > data.length) {
// Copy the new data into the exiting buffer (should have been allocated when we know the message size)
data.copy(self.buffer, self.bytesRead);
// Adjust the number of bytes read so it point to the correct index in the buffer
self.bytesRead = self.bytesRead + data.length;
// Reset state of buffer
data = new Buffer(0);
} else {
// Copy the missing part of the data into our current buffer
data.copy(self.buffer, self.bytesRead, 0, remainingBytesToRead);
// Slice the overflow into a new buffer that we will then re-parse
data = data.slice(remainingBytesToRead);
// Emit current complete message
try {
var emitBuffer = self.buffer;
// Reset state of buffer
self.buffer = null;
self.sizeOfMessage = 0;
self.bytesRead = 0;
self.stubBuffer = null;
// Emit the buffer
self.emit("message", emitBuffer, self);
} catch(err) {
var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{
sizeOfMessage:self.sizeOfMessage,
bytesRead:self.bytesRead,
stubBuffer:self.stubBuffer}};
if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject);
// We got a parse Error fire it off then keep going
self.emit("parseError", errorObject, self);
}
}
} else {
// Stub buffer is kept in case we don't get enough bytes to determine the
// size of the message (< 4 bytes)
if(self.stubBuffer != null && self.stubBuffer.length > 0) {
// If we have enough bytes to determine the message size let's do it
if(self.stubBuffer.length + data.length > 4) {
// Prepad the data
var newData = new Buffer(self.stubBuffer.length + data.length);
self.stubBuffer.copy(newData, 0);
data.copy(newData, self.stubBuffer.length);
// Reassign for parsing
data = newData;
// Reset state of buffer
self.buffer = null;
self.sizeOfMessage = 0;
self.bytesRead = 0;
self.stubBuffer = null;
} else {
// Add the the bytes to the stub buffer
var newStubBuffer = new Buffer(self.stubBuffer.length + data.length);
// Copy existing stub buffer
self.stubBuffer.copy(newStubBuffer, 0);
// Copy missing part of the data
data.copy(newStubBuffer, self.stubBuffer.length);
// Exit parsing loop
data = new Buffer(0);
}
} else {
if(data.length > 4) {
// Retrieve the message size
var sizeOfMessage = binaryutils.decodeUInt32(data, 0);
// If we have a negative sizeOfMessage emit error and return
if(sizeOfMessage < 0 || sizeOfMessage > self.maxBsonSize) {
var errorObject = {err:"socketHandler", trace:'', bin:self.buffer, parseState:{
sizeOfMessage: sizeOfMessage,
bytesRead: self.bytesRead,
stubBuffer: self.stubBuffer}};
if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject);
// We got a parse Error fire it off then keep going
self.emit("parseError", errorObject, self);
return;
}
// Ensure that the size of message is larger than 0 and less than the max allowed
if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage > data.length) {
self.buffer = new Buffer(sizeOfMessage);
// Copy all the data into the buffer
data.copy(self.buffer, 0);
// Update bytes read
self.bytesRead = data.length;
// Update sizeOfMessage
self.sizeOfMessage = sizeOfMessage;
// Ensure stub buffer is null
self.stubBuffer = null;
// Exit parsing loop
data = new Buffer(0);
} else if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage == data.length) {
try {
var emitBuffer = data;
// Reset state of buffer
self.buffer = null;
self.sizeOfMessage = 0;
self.bytesRead = 0;
self.stubBuffer = null;
// Exit parsing loop
data = new Buffer(0);
// Emit the message
self.emit("message", emitBuffer, self);
} catch (err) {
var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{
sizeOfMessage:self.sizeOfMessage,
bytesRead:self.bytesRead,
stubBuffer:self.stubBuffer}};
if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject);
// We got a parse Error fire it off then keep going
self.emit("parseError", errorObject, self);
}
} else if(sizeOfMessage <= 4 || sizeOfMessage > self.maxBsonSize) {
var errorObject = {err:"socketHandler", trace:null, bin:data, parseState:{
sizeOfMessage:sizeOfMessage,
bytesRead:0,
buffer:null,
stubBuffer:null}};
if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject);
// We got a parse Error fire it off then keep going
self.emit("parseError", errorObject, self);
// Clear out the state of the parser
self.buffer = null;
self.sizeOfMessage = 0;
self.bytesRead = 0;
self.stubBuffer = null;
// Exit parsing loop
data = new Buffer(0);
} else {
try {
var emitBuffer = data.slice(0, sizeOfMessage);
// Reset state of buffer
self.buffer = null;
self.sizeOfMessage = 0;
self.bytesRead = 0;
self.stubBuffer = null;
// Copy rest of message
data = data.slice(sizeOfMessage);
// Emit the message
self.emit("message", emitBuffer, self);
} catch (err) {
var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{
sizeOfMessage:sizeOfMessage,
bytesRead:self.bytesRead,
stubBuffer:self.stubBuffer}};
if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject);
// We got a parse Error fire it off then keep going
self.emit("parseError", errorObject, self);
}
}
} else {
// Create a buffer that contains the space for the non-complete message
self.stubBuffer = new Buffer(data.length)
// Copy the data to the stub buffer
data.copy(self.stubBuffer, 0);
// Exit parsing loop
data = new Buffer(0);
}
}
}
}
}
}
var endHandler = function(self) {
return function() {
// Set connected to false
self.connected = false;
// Emit end event
self.emit("end", {err: 'connection received Fin packet from [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self);
}
}
var timeoutHandler = function(self) {
return function() {
// Set connected to false
self.connected = false;
// Emit timeout event
self.emit("timeout", {err: 'connection to [' + self.socketOptions.host + ':' + self.socketOptions.port + '] timed out'}, self);
}
}
var drainHandler = function(self) {
return function() {
}
}
var errorHandler = function(self) {
return function(err) {
self.connection.destroy();
// Set connected to false
self.connected = false;
// Emit error
self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self);
}
}
var closeHandler = function(self) {
return function(hadError) {
// If we have an error during the connection phase
if(hadError && !self.connected) {
// Set disconnected
self.connected = false;
// Emit error
self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self);
} else {
// Set disconnected
self.connected = false;
// Emit close
self.emit("close", {err: 'connection closed to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self);
}
}
}
// Some basic defaults
Connection.DEFAULT_PORT = 27017;
var utils = require('./connection_utils'),
inherits = require('util').inherits,
net = require('net'),
timers = require('timers'),
EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits,
MongoReply = require("../responses/mongo_reply").MongoReply,
Connection = require("./connection").Connection;
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bson, socketOptions) {
if(typeof host !== 'string') {
throw new Error("host must be specified [" + host + "]");
}
// Set up event emitter
EventEmitter.call(this);
// Keep all options for the socket in a specific collection allowing the user to specify the
// Wished upon socket connection parameters
this.socketOptions = typeof socketOptions === 'object' ? socketOptions : {};
this.socketOptions.host = host;
this.socketOptions.port = port;
this.socketOptions.domainSocket = false;
this.bson = bson;
// PoolSize is always + 1 for special reserved "measurment" socket (like ping, stats etc)
this.poolSize = poolSize;
this.minPoolSize = Math.floor(this.poolSize / 2) + 1;
// Check if the host is a socket
if(host.match(/^\//)) {
this.socketOptions.domainSocket = true;
} else if(typeof port === 'string') {
try {
port = parseInt(port, 10);
} catch(err) {
new Error("port must be specified or valid integer[" + port + "]");
}
} else if(typeof port !== 'number') {
throw new Error("port must be specified [" + port + "]");
}
// Set default settings for the socket options
utils.setIntegerParameter(this.socketOptions, 'timeout', 0);
// Delay before writing out the data to the server
utils.setBooleanParameter(this.socketOptions, 'noDelay', true);
// Delay before writing out the data to the server
utils.setIntegerParameter(this.socketOptions, 'keepAlive', 0);
// Set the encoding of the data read, default is binary == null
utils.setStringParameter(this.socketOptions, 'encoding', null);
// Allows you to set a throttling bufferSize if you need to stop overflows
utils.setIntegerParameter(this.socketOptions, 'bufferSize', 0);
// Internal structures
this.openConnections = [];
// Assign connection id's
this.connectionId = 0;
// Current index for selection of pool connection
this.currentConnectionIndex = 0;
// The pool state
this._poolState = 'disconnected';
// timeout control
this._timeout = false;
// Time to wait between connections for the pool
this._timeToWait = 10;
}
inherits(ConnectionPool, EventEmitter);
ConnectionPool.prototype.setMaxBsonSize = function(maxBsonSize) {
if(maxBsonSize == null){
maxBsonSize = Connection.DEFAULT_MAX_BSON_SIZE;
}
for(var i = 0; i < this.openConnections.length; i++) {
this.openConnections[i].maxBsonSize = maxBsonSize;
this.openConnections[i].maxBsonSettings.maxBsonSize = maxBsonSize;
}
}
ConnectionPool.prototype.setMaxMessageSizeBytes = function(maxMessageSizeBytes) {
if(maxMessageSizeBytes == null){
maxMessageSizeBytes = Connection.DEFAULT_MAX_MESSAGE_SIZE;
}
for(var i = 0; i < this.openConnections.length; i++) {
this.openConnections[i].maxMessageSizeBytes = maxMessageSizeBytes;
this.openConnections[i].maxBsonSettings.maxMessageSizeBytes = maxMessageSizeBytes;
}
}
// Start a function
var _connect = function(_self) {
// return new function() {
// Create a new connection instance
var connection = new Connection(_self.connectionId++, _self.socketOptions);
// Set logger on pool
connection.logger = _self.logger;
// Connect handler
connection.on("connect", function(err, connection) {
// Add connection to list of open connections
_self.openConnections.push(connection);
// If the number of open connections is equal to the poolSize signal ready pool
if(_self.openConnections.length === _self.poolSize && _self._poolState !== 'disconnected') {
// Set connected
_self._poolState = 'connected';
// Emit pool ready
_self.emit("poolReady");
} else if(_self.openConnections.length < _self.poolSize) {
// Wait a little bit of time to let the close event happen if the server closes the connection
// so we don't leave hanging connections around
if(typeof _self._timeToWait == 'number') {
setTimeout(function() {
// If we are still connecting (no close events fired in between start another connection)
if(_self._poolState == 'connecting') {
_connect(_self);
}
}, _self._timeToWait);
} else {
processor(function() {
// If we are still connecting (no close events fired in between start another connection)
if(_self._poolState == 'connecting') {
_connect(_self);
}
});
}
}
});
var numberOfErrors = 0
// Error handler
connection.on("error", function(err, connection, error_options) {
numberOfErrors++;
// If we are already disconnected ignore the event
if(_self._poolState != 'disconnected' && _self.listeners("error").length > 0) {
_self.emit("error", err, connection, error_options);
}
// Close the connection
connection.close();
// Set pool as disconnected
_self._poolState = 'disconnected';
// Stop the pool
_self.stop();
});
// Close handler
connection.on("close", function() {
// If we are already disconnected ignore the event
if(_self._poolState !== 'disconnected' && _self.listeners("close").length > 0) {
_self.emit("close");
}
// Set disconnected
_self._poolState = 'disconnected';
// Stop
_self.stop();
});
// Timeout handler
connection.on("timeout", function(err, connection) {
// If we are already disconnected ignore the event
if(_self._poolState !== 'disconnected' && _self.listeners("timeout").length > 0) {
_self.emit("timeout", err);
}
// Close the connection
connection.close();
// Set disconnected
_self._poolState = 'disconnected';
_self.stop();
});
// Parse error, needs a complete shutdown of the pool
connection.on("parseError", function() {
// If we are already disconnected ignore the event
if(_self._poolState !== 'disconnected' && _self.listeners("parseError").length > 0) {
_self.emit("parseError", new Error("parseError occured"));
}
// Set disconnected
_self._poolState = 'disconnected';
_self.stop();
});
connection.on("message", function(message) {
_self.emit("message", message);
});
// Start connection in the next tick
connection.start();
// }();
}
// Start method, will throw error if no listeners are available
// Pass in an instance of the listener that contains the api for
// finding callbacks for a given message etc.
ConnectionPool.prototype.start = function() {
var markerDate = new Date().getTime();
var self = this;
if(this.listeners("poolReady").length == 0) {
throw "pool must have at least one listener ready that responds to the [poolReady] event";
}
// Set pool state to connecting
this._poolState = 'connecting';
this._timeout = false;
_connect(self);
}
// Restart a connection pool (on a close the pool might be in a wrong state)
ConnectionPool.prototype.restart = function() {
// Close all connections
this.stop(false);
// Now restart the pool
this.start();
}
// Stop the connections in the pool
ConnectionPool.prototype.stop = function(removeListeners) {
removeListeners = removeListeners == null ? true : removeListeners;
// Set disconnected
this._poolState = 'disconnected';
// Clear all listeners if specified
if(removeListeners) {
this.removeAllEventListeners();
}
// Close all connections
for(var i = 0; i < this.openConnections.length; i++) {
this.openConnections[i].close();
}
// Clean up
this.openConnections = [];
}
// Check the status of the connection
ConnectionPool.prototype.isConnected = function() {
// return this._poolState === 'connected';
return this.openConnections.length > 0 && this.openConnections[0].isConnected();
}
// Checkout a connection from the pool for usage, or grab a specific pool instance
ConnectionPool.prototype.checkoutConnection = function(id) {
var index = (this.currentConnectionIndex++ % (this.openConnections.length));
var connection = this.openConnections[index];
return connection;
}
ConnectionPool.prototype.getAllConnections = function() {
return this.openConnections;
}
// Remove all non-needed event listeners
ConnectionPool.prototype.removeAllEventListeners = function() {
this.removeAllListeners("close");
this.removeAllListeners("error");
this.removeAllListeners("timeout");
this.removeAllListeners("connect");
this.removeAllListeners("end");
this.removeAllListeners("parseError");
this.removeAllListeners("message");
this.removeAllListeners("poolReady");
}
exports.setIntegerParameter = function(object, field, defaultValue) {
if(object[field] == null) {
object[field] = defaultValue;
} else if(typeof object[field] !== "number" && object[field] !== parseInt(object[field], 10)) {
throw "object field [" + field + "] must be a numeric integer value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]";
}
}
exports.setBooleanParameter = function(object, field, defaultValue) {
if(object[field] == null) {
object[field] = defaultValue;
} else if(typeof object[field] !== "boolean") {
throw "object field [" + field + "] must be a boolean value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]";
}
}
exports.setStringParameter = function(object, field, defaultValue) {
if(object[field] == null) {
object[field] = defaultValue;
} else if(typeof object[field] !== "string") {
throw "object field [" + field + "] must be a string value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]";
}
}
var ReadPreference = require('./read_preference').ReadPreference
, Base = require('./base').Base
, Server = require('./server').Server
, format = require('util').format
, timers = require('timers')
, inherits = require('util').inherits;
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
/**
* Mongos constructor provides a connection to a mongos proxy including failover to additional servers
*
* Options
* - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number))
* - **ha** {Boolean, default:true}, turn on high availability, attempts to reconnect to down proxies
* - **haInterval** {Number, default:2000}, time between each replicaset status check.
*
* @class Represents a Mongos connection with failover to backup proxies
* @param {Array} list of mongos server objects
* @param {Object} [options] additional options for the mongos connection
*/
var Mongos = function Mongos(servers, options) {
// Set up basic
if(!(this instanceof Mongos))
return new Mongos(servers, options);
// Set up event emitter
Base.call(this);
// Throw error on wrong setup
if(servers == null || !Array.isArray(servers) || servers.length == 0)
throw new Error("At least one mongos proxy must be in the array");
// Ensure we have at least an empty options object
this.options = options == null ? {} : options;
// Set default connection pool options
this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {};
// Enabled ha
this.haEnabled = this.options['ha'] == null ? true : this.options['ha'];
this._haInProgress = false;
// How often are we checking for new servers in the replicaset
this.mongosStatusCheckInterval = this.options['haInterval'] == null ? 1000 : this.options['haInterval'];
// Save all the server connections
this.servers = servers;
// Servers we need to attempt reconnect with
this.downServers = {};
// Servers that are up
this.upServers = {};
// Up servers by ping time
this.upServersByUpTime = {};
// Emit open setup
this.emitOpen = this.options.emitOpen || true;
// Just contains the current lowest ping time and server
this.lowestPingTimeServer = null;
this.lowestPingTime = 0;
// Connection timeout
this._connectTimeoutMS = this.socketOptions.connectTimeoutMS
? this.socketOptions.connectTimeoutMS
: 1000;
// Add options to servers
for(var i = 0; i < this.servers.length; i++) {
var server = this.servers[i];
server._callBackStore = this._callBackStore;
server.auto_reconnect = false;
// Default empty socket options object
var socketOptions = {host: server.host, port: server.port};
// If a socket option object exists clone it
if(this.socketOptions != null) {
var keys = Object.keys(this.socketOptions);
for(var k = 0; k < keys.length;k++) socketOptions[keys[i]] = this.socketOptions[keys[i]];
}
// Set socket options
server.socketOptions = socketOptions;
}
}
/**
* @ignore
*/
inherits(Mongos, Base);
/**
* @ignore
*/
Mongos.prototype.isMongos = function() {
return true;
}
/**
* @ignore
*/
Mongos.prototype.connect = function(db, options, callback) {
if('function' === typeof options) callback = options, options = {};
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
var self = this;
// Keep reference to parent
this.db = db;
// Set server state to connecting
this._serverState = 'connecting';
// Number of total servers that need to initialized (known servers)
this._numberOfServersLeftToInitialize = this.servers.length;
// Connect handler
var connectHandler = function(_server) {
return function(err, result) {
self._numberOfServersLeftToInitialize = self._numberOfServersLeftToInitialize - 1;
// Add the server to the list of servers that are up
if(!err) {
self.upServers[format("%s:%s", _server.host, _server.port)] = _server;
}
// We are done connecting
if(self._numberOfServersLeftToInitialize == 0) {
// Start ha function if it exists
if(self.haEnabled) {
// Setup the ha process
if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId);
self._replicasetTimeoutId = setInterval(self.mongosCheckFunction, self.mongosStatusCheckInterval);
}
// Set the mongos to connected
self._serverState = "connected";
// Emit the open event
if(self.emitOpen)
self._emitAcrossAllDbInstances(self, null, "open", null, null, null);
self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null);
// Callback
callback(null, self.db);
}
}
};
// Error handler
var errorOrCloseHandler = function(_server) {
return function(err, result) {
// Execute all the callbacks with errors
self.__executeAllCallbacksWithError(err);
// Check if we have the server
var found = false;
// Get the server name
var server_name = format("%s:%s", _server.host, _server.port);
// Add the downed server
self.downServers[server_name] = _server;
// Remove the current server from the list
delete self.upServers[server_name];
// Emit close across all the attached db instances
if(Object.keys(self.upServers).length == 0) {
self._emitAcrossAllDbInstances(self, null, "close", new Error("mongos disconnected, no valid proxies contactable over tcp"), null, null);
}
}
}
// Mongo function
this.mongosCheckFunction = function() {
// Set as not waiting for check event
self._haInProgress = true;
// Servers down
var numberOfServersLeft = Object.keys(self.downServers).length;
// Check downed servers
if(numberOfServersLeft > 0) {
for(var name in self.downServers) {
// Pop a downed server
var downServer = self.downServers[name];
// Set up the connection options for a Mongos
var options = {
auto_reconnect: false,
returnIsMasterResults: true,
slaveOk: true,
poolSize: downServer.poolSize,
socketOptions: {
connectTimeoutMS: self._connectTimeoutMS,
socketTimeoutMS: self._socketTimeoutMS
}
}
// Create a new server object
var newServer = new Server(downServer.host, downServer.port, options);
// Setup the connection function
var connectFunction = function(_db, _server, _options, _callback) {
return function() {
// Attempt to connect
_server.connect(_db, _options, function(err, result) {
numberOfServersLeft = numberOfServersLeft - 1;
if(err) {
return _callback(err, _server);
} else {
// Set the new server settings
_server._callBackStore = self._callBackStore;
// Add server event handlers
_server.on("close", errorOrCloseHandler(_server));
_server.on("timeout", errorOrCloseHandler(_server));
_server.on("error", errorOrCloseHandler(_server));
// Get a read connection
var _connection = _server.checkoutReader();
// Get the start time
var startTime = new Date().getTime();
// Execute ping command to mark each server with the expected times
self.db.command({ping:1}
, {failFast:true, connection:_connection}, function(err, result) {
// Get the start time
var endTime = new Date().getTime();
// Mark the server with the ping time
_server.runtimeStats['pingMs'] = endTime - startTime;
// Execute any waiting reads
self._commandsStore.execute_writes();
self._commandsStore.execute_queries();
// Callback
return _callback(null, _server);
});
}
});
}
}
// Attempt to connect to the database
connectFunction(self.db, newServer, options, function(err, _server) {
// If we have an error
if(err) {
self.downServers[format("%s:%s", _server.host, _server.port)] = _server;
}
// Connection function
var connectionFunction = function(_auth, _connection, _callback) {
var pending = _auth.length();
for(var j = 0; j < pending; j++) {
// Get the auth object
var _auth = _auth.get(j);
// Unpack the parameter
var username = _auth.username;
var password = _auth.password;
var options = {
authMechanism: _auth.authMechanism
, authSource: _auth.authdb
, connection: _connection
};
// If we have changed the service name
if(_auth.gssapiServiceName)
options.gssapiServiceName = _auth.gssapiServiceName;
// Hold any error
var _error = null;
// Authenticate against the credentials
self.db.authenticate(username, password, options, function(err, result) {
_error = err != null ? err : _error;
// Adjust the pending authentication
pending = pending - 1;
// Finished up
if(pending == 0) _callback(_error ? _error : null, _error ? false : true);
});
}
}
// Run auths against the connections
if(self.auth.length() > 0) {
var connections = _server.allRawConnections();
var pendingAuthConn = connections.length;
// No connections we are done
if(connections.length == 0) {
// Set ha done
if(numberOfServersLeft == 0) {
self._haInProgress = false;
}
}
// Final error object
var finalError = null;
// Go over all the connections
for(var j = 0; j < connections.length; j++) {
// Execute against all the connections
connectionFunction(self.auth, connections[j], function(err, result) {
// Pending authentication
pendingAuthConn = pendingAuthConn - 1 ;
// Save error if any
finalError = err ? err : finalError;
// If we are done let's finish up
if(pendingAuthConn == 0) {
// Set ha done
if(numberOfServersLeft == 0) {
self._haInProgress = false;
}
if(!err) {
add_server(self, _server);
}
// Execute any waiting reads
self._commandsStore.execute_writes();
self._commandsStore.execute_queries();
}
});
}
} else {
if(!err) {
add_server(self, _server);
}
// Set ha done
if(numberOfServersLeft == 0) {
self._haInProgress = false;
// Execute any waiting reads
self._commandsStore.execute_writes();
self._commandsStore.execute_queries();
}
}
})();
}
} else {
self._haInProgress = false;
}
}
// Connect all the server instances
for(var i = 0; i < this.servers.length; i++) {
// Get the connection
var server = this.servers[i];
server.mongosInstance = this;
// Add server event handlers
server.on("close", errorOrCloseHandler(server));
server.on("timeout", errorOrCloseHandler(server));
server.on("error", errorOrCloseHandler(server));
// Configuration
var options = {
slaveOk: true,
poolSize: server.poolSize,
socketOptions: { connectTimeoutMS: self._connectTimeoutMS },
returnIsMasterResults: true
}
// Connect the instance
server.connect(self.db, options, connectHandler(server));
}
}
/**
* @ignore
* Add a server to the list of up servers and sort them by ping time
*/
var add_server = function(self, _server) {
var server_key = format("%s:%s", _server.host, _server.port);
// Push to list of valid server
self.upServers[server_key] = _server;
// Remove the server from the list of downed servers
delete self.downServers[server_key];
// Sort the keys by ping time
var keys = Object.keys(self.upServers);
var _upServersSorted = {};
var _upServers = []
// Get all the servers
for(var name in self.upServers) {
_upServers.push(self.upServers[name]);
}
// Sort all the server
_upServers.sort(function(a, b) {
return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs'];
});
// Rebuild the upServer
for(var i = 0; i < _upServers.length; i++) {
_upServersSorted[format("%s:%s", _upServers[i].host, _upServers[i].port)] = _upServers[i];
}
// Set the up servers
self.upServers = _upServersSorted;
}
/**
* @ignore
* Just return the currently picked active connection
*/
Mongos.prototype.allServerInstances = function() {
return this.servers;
}
/**
* Always ourselves
* @ignore
*/
Mongos.prototype.setReadPreference = function() {}
/**
* @ignore
*/
Mongos.prototype.allRawConnections = function() {
// Neeed to build a complete list of all raw connections, start with master server
var allConnections = [];
// Get all connected connections
for(var name in this.upServers) {
allConnections = allConnections.concat(this.upServers[name].allRawConnections());
}
// Return all the conections
return allConnections;
}
/**
* @ignore
*/
Mongos.prototype.isConnected = function() {
return Object.keys(this.upServers).length > 0;
}
/**
* @ignore
*/
Mongos.prototype.isAutoReconnect = function() {
return true;
}
/**
* @ignore
*/
Mongos.prototype.canWrite = Mongos.prototype.isConnected;
/**
* @ignore
*/
Mongos.prototype.canRead = Mongos.prototype.isConnected;
/**
* @ignore
*/
Mongos.prototype.isDestroyed = function() {
return this._serverState == 'destroyed';
}
/**
* @ignore
*/
Mongos.prototype.checkoutWriter = function() {
// Checkout a writer
var keys = Object.keys(this.upServers);
// console.dir("============================ checkoutWriter :: " + keys.length)
if(keys.length == 0) return null;
// console.log("=============== checkoutWriter :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port)
return this.upServers[keys[0]].checkoutWriter();
}
/**
* @ignore
*/
Mongos.prototype.checkoutReader = function(read) {
// console.log("=============== checkoutReader :: read :: " + read);
// If read is set to null default to primary
read = read || 'primary'
// If we have a read preference object unpack it
if(read != null && typeof read == 'object' && read['_type'] == 'ReadPreference') {
// Validate if the object is using a valid mode
if(!read.isValid()) throw new Error("Illegal readPreference mode specified, " + read.mode);
} else if(!ReadPreference.isValid(read)) {
throw new Error("Illegal readPreference mode specified, " + read);
}
// Checkout a writer
var keys = Object.keys(this.upServers);
if(keys.length == 0) return null;
// console.log("=============== checkoutReader :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port)
// console.dir(this._commandsStore.commands)
return this.upServers[keys[0]].checkoutWriter();
}
/**
* @ignore
*/
Mongos.prototype.close = function(callback) {
var self = this;
// Set server status as disconnected
this._serverState = 'destroyed';
// Number of connections to close
var numberOfConnectionsToClose = self.servers.length;
// If we have a ha process running kill it
if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId);
self._replicasetTimeoutId = null;
// Emit close event
processor(function() {
self._emitAcrossAllDbInstances(self, null, "close", null, null, true)
});
// Close all the up servers
for(var name in this.upServers) {
this.upServers[name].close(function(err, result) {
numberOfConnectionsToClose = numberOfConnectionsToClose - 1;
// Callback if we have one defined
if(numberOfConnectionsToClose == 0 && typeof callback == 'function') {
callback(null);
}
});
}
}
/**
* @ignore
* Return the used state
*/
Mongos.prototype._isUsed = function() {
return this._used;
}
exports.Mongos = Mongos;
/**
* A class representation of the Read Preference.
*
* Read Preferences
* - **ReadPreference.PRIMARY**, Read from primary only. All operations produce an error (throw an exception where applicable) if primary is unavailable. Cannot be combined with tags (This is the default.).
* - **ReadPreference.PRIMARY_PREFERRED**, Read from primary if available, otherwise a secondary.
* - **ReadPreference.SECONDARY**, Read from secondary if available, otherwise error.
* - **ReadPreference.SECONDARY_PREFERRED**, Read from a secondary if available, otherwise read from the primary.
* - **ReadPreference.NEAREST**, All modes read from among the nearest candidates, but unlike other modes, NEAREST will include both the primary and all secondaries in the random selection.
*
* @class Represents a Read Preference.
* @param {String} the read preference type
* @param {Object} tags
* @return {ReadPreference}
*/
var ReadPreference = function(mode, tags) {
if(!(this instanceof ReadPreference))
return new ReadPreference(mode, tags);
this._type = 'ReadPreference';
this.mode = mode;
this.tags = tags;
}
/**
* @ignore
*/
ReadPreference.isValid = function(_mode) {
return (_mode == ReadPreference.PRIMARY || _mode == ReadPreference.PRIMARY_PREFERRED
|| _mode == ReadPreference.SECONDARY || _mode == ReadPreference.SECONDARY_PREFERRED
|| _mode == ReadPreference.NEAREST
|| _mode == true || _mode == false);
}
/**
* @ignore
*/
ReadPreference.prototype.isValid = function(mode) {
var _mode = typeof mode == 'string' ? mode : this.mode;
return ReadPreference.isValid(_mode);
}
/**
* @ignore
*/
ReadPreference.prototype.toObject = function() {
var object = {mode:this.mode};
if(this.tags != null) {
object['tags'] = this.tags;
}
return object;
}
/**
* @ignore
*/
ReadPreference.PRIMARY = 'primary';
ReadPreference.PRIMARY_PREFERRED = 'primaryPreferred';
ReadPreference.SECONDARY = 'secondary';
ReadPreference.SECONDARY_PREFERRED = 'secondaryPreferred';
ReadPreference.NEAREST = 'nearest'
/**
* @ignore
*/
exports.ReadPreference = ReadPreference;
var DbCommand = require('../../commands/db_command').DbCommand
, format = require('util').format;
var HighAvailabilityProcess = function(replset, options) {
this.replset = replset;
this.options = options;
this.server = null;
this.state = HighAvailabilityProcess.INIT;
this.selectedIndex = 0;
}
HighAvailabilityProcess.INIT = 'init';
HighAvailabilityProcess.RUNNING = 'running';
HighAvailabilityProcess.STOPPED = 'stopped';
HighAvailabilityProcess.prototype.start = function() {
var self = this;
if(this.replset._state
&& Object.keys(this.replset._state.addresses).length == 0) {
if(this.server) this.server.close();
this.state = HighAvailabilityProcess.STOPPED;
return;
}
if(this.server) this.server.close();
// Start the running
this._haProcessInProcess = false;
this.state = HighAvailabilityProcess.RUNNING;
// Get all possible reader servers
var candidate_servers = this.replset._state.getAllReadServers();
if(candidate_servers.length == 0) {
return;
}
// Select a candidate server for the connection
var server = candidate_servers[this.selectedIndex % candidate_servers.length];
this.selectedIndex = this.selectedIndex + 1;
// Unpack connection options
var connectTimeoutMS = self.options.connectTimeoutMS || 10000;
var socketTimeoutMS = self.options.socketTimeoutMS || 30000;
// Just ensure we don't have a full cycle dependency
var Db = require('../../db').Db
var Server = require('../server').Server;
// Set up a new server instance
var newServer = new Server(server.host, server.port, {
auto_reconnect: false
, returnIsMasterResults: true
, poolSize: 1
, socketOptions: {
connectTimeoutMS: connectTimeoutMS,
socketTimeoutMS: socketTimeoutMS,
keepAlive: 100
}
, ssl: this.options.ssl
, sslValidate: this.options.sslValidate
, sslCA: this.options.sslCA
, sslCert: this.options.sslCert
, sslKey: this.options.sslKey
, sslPass: this.options.sslPass
});
// Create new dummy db for app
self.db = new Db('local', newServer, {w:1});
// Set up the event listeners
newServer.once("error", _handle(this, newServer));
newServer.once("close", _handle(this, newServer));
newServer.once("timeout", _handle(this, newServer));
newServer.name = format("%s:%s", server.host, server.port);
// Let's attempt a connection over here
newServer.connect(self.db, function(err, result, _server) {
if(self.state == HighAvailabilityProcess.STOPPED) {
_server.close();
}
if(err) {
// Close the server
_server.close();
// Check if we can even do HA (is there anything running)
if(Object.keys(self.replset._state.addresses).length == 0) {
return;
}
// Let's boot the ha timeout settings
setTimeout(function() {
self.start();
}, self.options.haInterval);
} else {
self.server = _server;
// Let's boot the ha timeout settings
setTimeout(_timeoutHandle(self), self.options.haInterval);
}
});
}
HighAvailabilityProcess.prototype.stop = function() {
this.state = HighAvailabilityProcess.STOPPED;
if(this.server) this.server.close();
}
var _timeoutHandle = function(self) {
return function() {
if(self.state == HighAvailabilityProcess.STOPPED) {
// Stop all server instances
for(var name in self.replset._state.addresses) {
self.replset._state.addresses[name].close();
delete self.replset._state.addresses[name];
}
// Finished pinging
return;
}
// If the server is connected
if(self.server.isConnected() && !self._haProcessInProcess) {
// Start HA process
self._haProcessInProcess = true;
// Execute is master command
self.db._executeQueryCommand(DbCommand.createIsMasterCommand(self.db),
{failFast:true, connection: self.server.checkoutReader()}
, function(err, res) {
if(err) {
self.server.close();
return setTimeout(_timeoutHandle(self), self.options.haInterval);
}
// Master document
var master = res.documents[0];
var hosts = master.hosts || [];
var reconnect_servers = [];
var state = self.replset._state;
// We are in recovery mode, let's remove the current server
if(!master.ismaster
&& !master.secondary
&& state.addresses[master.me]) {
self.server.close();
state.addresses[master.me].close();
delete state.secondaries[master.me];
return setTimeout(_timeoutHandle(self), self.options.haInterval);
}
// For all the hosts let's check that we have connections
for(var i = 0; i < hosts.length; i++) {
var host = hosts[i];
// Check if we need to reconnect to a server
if(state.addresses[host] == null) {
reconnect_servers.push(host);
} else if(state.addresses[host] && !state.addresses[host].isConnected()) {
state.addresses[host].close();
delete state.secondaries[host];
reconnect_servers.push(host);
}
if((master.primary && state.master == null)
|| (master.primary && state.master.name != master.primary)) {
// Locate the primary and set it
if(state.addresses[master.primary]) {
if(state.master) state.master.close();
delete state.secondaries[master.primary];
state.master = state.addresses[master.primary];
}
// Set up the changes
if(state.master != null && state.master.isMasterDoc != null) {
state.master.isMasterDoc.ismaster = true;
state.master.isMasterDoc.secondary = false;
} else if(state.master != null) {
state.master.isMasterDoc = master;
state.master.isMasterDoc.ismaster = true;
state.master.isMasterDoc.secondary = false;
}
// Execute any waiting commands (queries or writes)
self.replset._commandsStore.execute_queries();
self.replset._commandsStore.execute_writes();
}
}
// Let's reconnect to any server needed
if(reconnect_servers.length > 0) {
_reconnect_servers(self, reconnect_servers);
} else {
self._haProcessInProcess = false
return setTimeout(_timeoutHandle(self), self.options.haInterval);
}
});
} else if(!self.server.isConnected()) {
setTimeout(function() {
return self.start();
}, self.options.haInterval);
} else {
setTimeout(_timeoutHandle(self), self.options.haInterval);
}
}
}
var _reconnect_servers = function(self, reconnect_servers) {
if(reconnect_servers.length == 0) {
self._haProcessInProcess = false
return setTimeout(_timeoutHandle(self), self.options.haInterval);
}
// Unpack connection options
var connectTimeoutMS = self.options.connectTimeoutMS || 10000;
var socketTimeoutMS = self.options.socketTimeoutMS || 30000;
// Server class
var Db = require('../../db').Db
var Server = require('../server').Server;
// Get the host
var host = reconnect_servers.shift();
// Split it up
var _host = host.split(":")[0];
var _port = parseInt(host.split(":")[1], 10);
// Set up a new server instance
var newServer = new Server(_host, _port, {
auto_reconnect: false
, returnIsMasterResults: true
, poolSize: self.options.poolSize
, socketOptions: {
connectTimeoutMS: connectTimeoutMS,
socketTimeoutMS: socketTimeoutMS
}
, ssl: self.options.ssl
, sslValidate: self.options.sslValidate
, sslCA: self.options.sslCA
, sslCert: self.options.sslCert
, sslKey: self.options.sslKey
, sslPass: self.options.sslPass
});
// Create new dummy db for app
var db = new Db('local', newServer, {w:1});
var state = self.replset._state;
// Set up the event listeners
newServer.once("error", _repl_set_handler("error", self.replset, newServer));
newServer.once("close", _repl_set_handler("close", self.replset, newServer));
newServer.once("timeout", _repl_set_handler("timeout", self.replset, newServer));
// Set shared state
newServer.name = host;
newServer._callBackStore = self.replset._callBackStore;
newServer.replicasetInstance = self.replset;
newServer.enableRecordQueryStats(self.replset.recordQueryStats);
// Let's attempt a connection over here
newServer.connect(db, function(err, result, _server) {
if(self.state == HighAvailabilityProcess.STOPPED) {
_server.close();
}
// If we connected let's check what kind of server we have
if(!err) {
_apply_auths(self, db, _server, function(err, result) {
if(err) {
_server.close();
// Process the next server
return setTimeout(function() {
_reconnect_servers(self, reconnect_servers);
}, self.options.haInterval);
}
var doc = _server.isMasterDoc;
// Fire error on any unknown callbacks for this server
self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err);
if(doc.ismaster) {
if(state.secondaries[doc.me]) {
delete state.secondaries[doc.me];
}
// Override any server in list of addresses
state.addresses[doc.me] = _server;
// Set server as master
state.master = _server;
// Execute any waiting writes
self.replset._commandsStore.execute_writes();
} else if(doc.secondary) {
state.secondaries[doc.me] = _server;
// Override any server in list of addresses
state.addresses[doc.me] = _server;
// Execute any waiting reads
self.replset._commandsStore.execute_queries();
} else {
_server.close();
}
// Set any tags on the instance server
_server.name = doc.me;
_server.tags = doc.tags;
// Process the next server
setTimeout(function() {
_reconnect_servers(self, reconnect_servers);
}, self.options.haInterval);
});
} else {
_server.close();
self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err);
setTimeout(function() {
_reconnect_servers(self, reconnect_servers);
}, self.options.haInterval);
}
});
}
var _apply_auths = function(self, _db, _server, _callback) {
if(self.replset.auth.length() == 0) return _callback(null);
// Apply any authentication needed
if(self.replset.auth.length() > 0) {
var pending = self.replset.auth.length();
var connections = _server.allRawConnections();
var pendingAuthConn = connections.length;
// Connection function
var connectionFunction = function(_auth, _connection, __callback) {
var pending = _auth.length();
for(var j = 0; j < pending; j++) {
// Get the auth object
var _auth = _auth.get(j);
// Unpack the parameter
var username = _auth.username;
var password = _auth.password;
var options = {
authMechanism: _auth.authMechanism
, authSource: _auth.authdb
, connection: _connection
};
// If we have changed the service name
if(_auth.gssapiServiceName)
options.gssapiServiceName = _auth.gssapiServiceName;
// Hold any error
var _error = null;
// Authenticate against the credentials
_db.authenticate(username, password, options, function(err, result) {
_error = err != null ? err : _error;
// Adjust the pending authentication
pending = pending - 1;
// Finished up
if(pending == 0) __callback(_error ? _error : null, _error ? false : true);
});
}
}
// Final error object
var finalError = null;
// Iterate over all the connections
for(var i = 0; i < connections.length; i++) {
connectionFunction(self.replset.auth, connections[i], function(err, result) {
// Pending authentication
pendingAuthConn = pendingAuthConn - 1 ;
// Save error if any
finalError = err ? err : finalError;
// If we are done let's finish up
if(pendingAuthConn == 0) {
_callback(null);
}
});
}
}
}
var _handle = function(self, server) {
return function(err) {
server.close();
}
}
var _repl_set_handler = function(event, self, server) {
var ReplSet = require('./repl_set').ReplSet;
return function(err, doc) {
server.close();
// The event happened to a primary
// Remove it from play
if(self._state.isPrimary(server)) {
self._state.master == null;
self._serverState = ReplSet.REPLSET_READ_ONLY;
} else if(self._state.isSecondary(server)) {
delete self._state.secondaries[server.name];
}
// Unpack variables
var host = server.socketOptions.host;
var port = server.socketOptions.port;
// Fire error on any unknown callbacks
self.__executeAllServerSpecificErrorCallbacks(host, port, err);
}
}
exports.HighAvailabilityProcess = HighAvailabilityProcess;
var PingStrategy = require('./strategies/ping_strategy').PingStrategy
, StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy
, ReadPreference = require('../read_preference').ReadPreference;
var Options = function(options) {
options = options || {};
this._options = options;
this.ha = options.ha || true;
this.haInterval = options.haInterval || 2000;
this.reconnectWait = options.reconnectWait || 1000;
this.retries = options.retries || 30;
this.rs_name = options.rs_name;
this.socketOptions = options.socketOptions || {};
this.readPreference = options.readPreference;
this.readSecondary = options.read_secondary;
this.poolSize = options.poolSize == null ? 5 : options.poolSize;
this.strategy = options.strategy || 'ping';
this.secondaryAcceptableLatencyMS = options.secondaryAcceptableLatencyMS || 15;
this.connectArbiter = options.connectArbiter || false;
this.connectWithNoPrimary = options.connectWithNoPrimary || false;
this.logger = options.logger;
this.ssl = options.ssl || false;
this.sslValidate = options.sslValidate || false;
this.sslCA = options.sslCA;
this.sslCert = options.sslCert;
this.sslKey = options.sslKey;
this.sslPass = options.sslPass;
this.emitOpen = options.emitOpen || true;
}
Options.prototype.init = function() {
if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) {
throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate");
}
// Make sure strategy is one of the two allowed
if(this.strategy != null && (this.strategy != 'ping' && this.strategy != 'statistical' && this.strategy != 'none'))
throw new Error("Only ping or statistical strategies allowed");
if(this.strategy == null) this.strategy = 'ping';
// Set logger if strategy exists
if(this.strategyInstance) this.strategyInstance.logger = this.logger;
// Unpack read Preference
var readPreference = this.readPreference;
// Validate correctness of Read preferences
if(readPreference != null) {
if(readPreference != ReadPreference.PRIMARY && readPreference != ReadPreference.PRIMARY_PREFERRED
&& readPreference != ReadPreference.SECONDARY && readPreference != ReadPreference.SECONDARY_PREFERRED
&& readPreference != ReadPreference.NEAREST && typeof readPreference != 'object' && readPreference['_type'] != 'ReadPreference') {
throw new Error("Illegal readPreference mode specified, " + readPreference);
}
this.readPreference = readPreference;
} else {
this.readPreference = null;
}
// Ensure read_secondary is set correctly
if(this.readSecondary != null)
this.readSecondary = this.readPreference == ReadPreference.PRIMARY
|| this.readPreference == false
|| this.readPreference == null ? false : true;
// Ensure correct slave set
if(this.readSecondary) this.slaveOk = true;
// Set up logger if any set
this.logger = this.logger != null
&& (typeof this.logger.debug == 'function')
&& (typeof this.logger.error == 'function')
&& (typeof this.logger.debug == 'function')
? this.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}};
// Connection timeout
this.connectTimeoutMS = this.socketOptions.connectTimeoutMS
? this.socketOptions.connectTimeoutMS
: 1000;
// Socket connection timeout
this.socketTimeoutMS = this.socketOptions.socketTimeoutMS
? this.socketOptions.socketTimeoutMS
: 30000;
}
Options.prototype.decorateAndClean = function(servers, callBackStore) {
var self = this;
// var de duplicate list
var uniqueServers = {};
// De-duplicate any servers in the seed list
for(var i = 0; i < servers.length; i++) {
var server = servers[i];
// If server does not exist set it
if(uniqueServers[server.host + ":" + server.port] == null) {
uniqueServers[server.host + ":" + server.port] = server;
}
}
// Let's set the deduplicated list of servers
var finalServers = [];
// Add the servers
for(var key in uniqueServers) {
finalServers.push(uniqueServers[key]);
}
finalServers.forEach(function(server) {
// Ensure no server has reconnect on
server.options.auto_reconnect = false;
// Set up ssl options
server.ssl = self.ssl;
server.sslValidate = self.sslValidate;
server.sslCA = self.sslCA;
server.sslCert = self.sslCert;
server.sslKey = self.sslKey;
server.sslPass = self.sslPass;
server.poolSize = self.poolSize;
// Set callback store
server._callBackStore = callBackStore;
});
return finalServers;
}
exports.Options = Options;
var ReadPreference = require('../read_preference').ReadPreference
, DbCommand = require('../../commands/db_command').DbCommand
, inherits = require('util').inherits
, format = require('util').format
, timers = require('timers')
, Server = require('../server').Server
, utils = require('../../utils')
, PingStrategy = require('./strategies/ping_strategy').PingStrategy
, StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy
, Options = require('./options').Options
, ReplSetState = require('./repl_set_state').ReplSetState
, HighAvailabilityProcess = require('./ha').HighAvailabilityProcess
, Base = require('../base').Base;
const STATE_STARTING_PHASE_1 = 0;
const STATE_PRIMARY = 1;
const STATE_SECONDARY = 2;
const STATE_RECOVERING = 3;
const STATE_FATAL_ERROR = 4;
const STATE_STARTING_PHASE_2 = 5;
const STATE_UNKNOWN = 6;
const STATE_ARBITER = 7;
const STATE_DOWN = 8;
const STATE_ROLLBACK = 9;
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../../utils').processor();
/**
* ReplSet constructor provides replicaset functionality
*
* Options
* - **ha** {Boolean, default:true}, turn on high availability.
* - **haInterval** {Number, default:2000}, time between each replicaset status check.
* - **reconnectWait** {Number, default:1000}, time to wait in miliseconds before attempting reconnect.
* - **retries** {Number, default:30}, number of times to attempt a replicaset reconnect.
* - **rs_name** {String}, the name of the replicaset to connect to.
* - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number))
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **strategy** {String, default:'ping'}, selection strategy for reads choose between (ping, statistical and none, default is ping)
* - **secondaryAcceptableLatencyMS** {Number, default:15}, sets the range of servers to pick when using NEAREST (lowest ping ms + the latency fence, ex: range of 1 to (1 + 15) ms)
* - **connectWithNoPrimary** {Boolean, default:false}, sets if the driver should connect even if no primary is available
* - **connectArbiter** {Boolean, default:false}, sets if the driver should connect to arbiters or not.
* - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**.
* - **poolSize** {Number, default:5}, number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
* - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support)
* - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
*
* @class Represents a
Replicaset Configuration
* @param {Array} list of server objects participating in the replicaset.
* @param {Object} [options] additional options for the replicaset connection.
*/
var ReplSet = exports.ReplSet = function(servers, options) {
// Set up basic
if(!(this instanceof ReplSet))
return new ReplSet(servers, options);
// Set up event emitter
Base.call(this);
// Ensure we have a list of servers
if(!Array.isArray(servers)) throw Error("The parameter must be an array of servers and contain at least one server");
// Ensure no Mongos's
for(var i = 0; i < servers.length; i++) {
if(!(servers[i] instanceof Server)) throw new Error("list of servers must be of type Server");
}
// Save the options
this.options = new Options(options);
// Ensure basic validation of options
this.options.init();
// Server state
this._serverState = ReplSet.REPLSET_DISCONNECTED;
// Add high availability process
this._haProcess = new HighAvailabilityProcess(this, this.options);
// Let's iterate over all the provided server objects and decorate them
this.servers = this.options.decorateAndClean(servers, this._callBackStore);
// Throw error if no seed servers
if(this.servers.length == 0) throw new Error("No valid seed servers in the array");
// Let's set up our strategy object for picking secondaries
if(this.options.strategy == 'ping') {
// Create a new instance
this.strategyInstance = new PingStrategy(this, this.options.secondaryAcceptableLatencyMS);
} else if(this.options.strategy == 'statistical') {
// Set strategy as statistical
this.strategyInstance = new StatisticsStrategy(this);
// Add enable query information
this.enableRecordQueryStats(true);
}
this.emitOpen = this.options.emitOpen || true;
// Set up a clean state
this._state = new ReplSetState();
// Current round robin selected server
this._currentServerChoice = 0;
// Ensure up the server callbacks
for(var i = 0; i < this.servers.length; i++) {
this.servers[i]._callBackStore = this._callBackStore;
this.servers[i].name = format("%s:%s", this.servers[i].host, this.servers[i].port)
this.servers[i].replicasetInstance = this;
this.servers[i].options.auto_reconnect = false;
this.servers[i].inheritReplSetOptionsFrom(this);
}
}
/**
* @ignore
*/
inherits(ReplSet, Base);
// Replicaset states
ReplSet.REPLSET_CONNECTING = 'connecting';
ReplSet.REPLSET_DISCONNECTED = 'disconnected';
ReplSet.REPLSET_CONNECTED = 'connected';
ReplSet.REPLSET_RECONNECTING = 'reconnecting';
ReplSet.REPLSET_DESTROYED = 'destroyed';
ReplSet.REPLSET_READ_ONLY = 'readonly';
ReplSet.prototype.isAutoReconnect = function() {
return true;
}
ReplSet.prototype.canWrite = function() {
return this._state.master && this._state.master.isConnected();
}
ReplSet.prototype.canRead = function(read) {
if((read == ReadPreference.PRIMARY
|| read == null || read == false) && (this._state.master == null || !this._state.master.isConnected())) return false;
return Object.keys(this._state.secondaries).length > 0;
}
/**
* @ignore
*/
ReplSet.prototype.enableRecordQueryStats = function(enable) {
// Set the global enable record query stats
this.recordQueryStats = enable;
// Enable all the servers
for(var i = 0; i < this.servers.length; i++) {
this.servers[i].enableRecordQueryStats(enable);
}
}
/**
* @ignore
*/
ReplSet.prototype.setReadPreference = function(preference) {
this.options.readPreference = preference;
}
ReplSet.prototype.connect = function(parent, options, callback) {
if(this._serverState != ReplSet.REPLSET_DISCONNECTED)
return callback(new Error("in process of connection"));
// If no callback throw
if(!(typeof callback == 'function'))
throw new Error("cannot call ReplSet.prototype.connect with no callback function");
var self = this;
// Save db reference
this.options.db = parent;
// Set replicaset as connecting
this._serverState = ReplSet.REPLSET_CONNECTING
// Copy all the servers to our list of seeds
var candidateServers = this.servers.slice(0);
// Pop the first server
var server = candidateServers.pop();
server.name = format("%s:%s", server.host, server.port);
// Set up the options
var opts = {
returnIsMasterResults: true,
eventReceiver: server
}
// Register some event listeners
this.once("fullsetup", function(err, db, replset) {
// Set state to connected
self._serverState = ReplSet.REPLSET_CONNECTED;
// Stop any process running
if(self._haProcess) self._haProcess.stop();
// Start the HA process
self._haProcess.start();
// Emit fullsetup
processor(function() {
if(self.emitOpen)
self._emitAcrossAllDbInstances(self, null, "open", null, null, null);
self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null);
});
// If we have a strategy defined start it
if(self.strategyInstance) {
self.strategyInstance.start();
}
// Finishing up the call
callback(err, db, replset);
});
// Errors
this.once("connectionError", function(err, result) {
callback(err, result);
});
// Attempt to connect to the server
server.connect(this.options.db, opts, _connectHandler(this, candidateServers, server));
}
ReplSet.prototype.close = function(callback) {
var self = this;
// Set as destroyed
this._serverState = ReplSet.REPLSET_DESTROYED;
// Stop the ha
this._haProcess.stop();
// If we have a strategy stop it
if(this.strategyInstance) {
this.strategyInstance.stop();
}
// Kill all servers available
for(var name in this._state.addresses) {
this._state.addresses[name].close();
}
// Clean out the state
this._state = new ReplSetState();
// Emit close event
processor(function() {
self._emitAcrossAllDbInstances(self, null, "close", null, null, true)
});
// Callback
if(typeof callback == 'function')
return callback(null, null);
}
/**
* Creates a new server for the `replset` based on `host`.
*
* @param {String} host - host:port pair (localhost:27017)
* @param {ReplSet} replset - the ReplSet instance
* @return {Server}
* @ignore
*/
var createServer = function(self, host, options) {
// copy existing socket options to new server
var socketOptions = {}
if(options.socketOptions) {
var keys = Object.keys(options.socketOptions);
for(var k = 0; k < keys.length; k++) {
socketOptions[keys[k]] = options.socketOptions[keys[k]];
}
}
var parts = host.split(/:/);
if(1 === parts.length) {
parts[1] = Connection.DEFAULT_PORT;
}
socketOptions.host = parts[0];
socketOptions.port = parseInt(parts[1], 10);
var serverOptions = {
readPreference: options.readPreference,
socketOptions: socketOptions,
poolSize: options.poolSize,
logger: options.logger,
auto_reconnect: false,
ssl: options.ssl,
sslValidate: options.sslValidate,
sslCA: options.sslCA,
sslCert: options.sslCert,
sslKey: options.sslKey,
sslPass: options.sslPass
}
var server = new Server(socketOptions.host, socketOptions.port, serverOptions);
// Set up shared state
server._callBackStore = self._callBackStore;
server.replicasetInstance = self;
server.enableRecordQueryStats(self.recordQueryStats);
// Set up event handlers
server.on("close", _handler("close", self, server));
server.on("error", _handler("error", self, server));
server.on("timeout", _handler("timeout", self, server));
return server;
}
var _handler = function(event, self, server) {
return function(err, doc) {
// The event happened to a primary
// Remove it from play
if(self._state.isPrimary(server)) {
var current_master = self._state.master;
self._state.master = null;
self._serverState = ReplSet.REPLSET_READ_ONLY;
if(current_master != null) {
// Unpack variables
var host = current_master.socketOptions.host;
var port = current_master.socketOptions.port;
// Fire error on any unknown callbacks
self.__executeAllServerSpecificErrorCallbacks(host, port, err);
}
} else if(self._state.isSecondary(server)) {
delete self._state.secondaries[server.name];
}
// If there is no more connections left and the setting is not destroyed
// set to disconnected
if(Object.keys(self._state.addresses).length == 0
&& self._serverState != ReplSet.REPLSET_DESTROYED) {
self._serverState = ReplSet.REPLSET_DISCONNECTED;
// Emit close across all the attached db instances
self._dbStore.emit("close", new Error("replicaset disconnected, no valid servers contactable over tcp"), null, true);
}
// Unpack variables
var host = server.socketOptions.host;
var port = server.socketOptions.port;
// Fire error on any unknown callbacks
self.__executeAllServerSpecificErrorCallbacks(host, port, err);
}
}
var locateNewServers = function(self, state, candidateServers, ismaster) {
// Retrieve the host
var hosts = ismaster.hosts;
// In candidate servers
var inCandidateServers = function(name, candidateServers) {
for(var i = 0; i < candidateServers.length; i++) {
if(candidateServers[i].name == name) return true;
}
return false;
}
// New servers
var newServers = [];
if(Array.isArray(hosts)) {
// Let's go over all the hosts
for(var i = 0; i < hosts.length; i++) {
if(!state.contains(hosts[i])
&& !inCandidateServers(hosts[i], candidateServers)) {
newServers.push(createServer(self, hosts[i], self.options));
}
}
}
// Return list of possible new servers
return newServers;
}
var _connectHandler = function(self, candidateServers, instanceServer) {
return function(err, doc) {
// If we have an error add to the list
if(err) {
self._state.errors[instanceServer.name] = instanceServer;
} else {
delete self._state.errors[instanceServer.name];
}
if(!err) {
var ismaster = doc.documents[0]
// Error the server if
if(!ismaster.ismaster
&& !ismaster.secondary) {
self._state.errors[instanceServer.name] = instanceServer;
}
}
// No error let's analyse the ismaster command
if(!err && self._state.errors[instanceServer.name] == null) {
var ismaster = doc.documents[0]
// If no replicaset name exists set the current one
if(self.options.rs_name == null) {
self.options.rs_name = ismaster.setName;
}
// If we have a member that is not part of the set let's finish up
if(typeof ismaster.setName == 'string' && ismaster.setName != self.options.rs_name) {
return self.emit("connectionError", new Error("Replicaset name " + ismaster.setName + " does not match specified name " + self.options.rs_name));
}
// Add the error handlers
instanceServer.on("close", _handler("close", self, instanceServer));
instanceServer.on("error", _handler("error", self, instanceServer));
instanceServer.on("timeout", _handler("timeout", self, instanceServer));
// Set any tags on the instance server
instanceServer.name = ismaster.me;
instanceServer.tags = ismaster.tags;
// Add the server to the list
self._state.addServer(instanceServer, ismaster);
// Check if we have more servers to add (only check when done with initial set)
if(candidateServers.length == 0) {
// Get additional new servers that are not currently in set
var new_servers = locateNewServers(self, self._state, candidateServers, ismaster);
// Locate any new servers that have not errored out yet
for(var i = 0; i < new_servers.length; i++) {
if(self._state.errors[new_servers[i].name] == null) {
candidateServers.push(new_servers[i])
}
}
}
}
// If the candidate server list is empty and no valid servers
if(candidateServers.length == 0 &&
!self._state.hasValidServers()) {
return self.emit("connectionError", new Error("No valid replicaset instance servers found"));
} else if(candidateServers.length == 0) {
if(!self.options.connectWithNoPrimary && (self._state.master == null || !self._state.master.isConnected())) {
return self.emit("connectionError", new Error("No primary found in set"));
}
return self.emit("fullsetup", null, self.options.db, self);
}
// Let's connect the next server
var nextServer = candidateServers.pop();
// Set up the options
var opts = {
returnIsMasterResults: true,
eventReceiver: nextServer
}
// Attempt to connect to the server
nextServer.connect(self.options.db, opts, _connectHandler(self, candidateServers, nextServer));
}
}
ReplSet.prototype.isDestroyed = function() {
return this._serverState == ReplSet.REPLSET_DESTROYED;
}
ReplSet.prototype.isConnected = function(read) {
var isConnected = false;
if(read == null || read == ReadPreference.PRIMARY || read == false)
isConnected = this._state.master != null && this._state.master.isConnected();
if((read == ReadPreference.PRIMARY_PREFERRED || read == ReadPreference.SECONDARY_PREFERRED || read == ReadPreference.NEAREST)
&& ((this._state.master != null && this._state.master.isConnected())
|| (this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0))) {
isConnected = true;
} else if(read == ReadPreference.SECONDARY) {
isConnected = this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0;
}
// No valid connection return false
return isConnected;
}
ReplSet.prototype.isMongos = function() {
return false;
}
ReplSet.prototype.checkoutWriter = function() {
if(this._state.master) return this._state.master.checkoutWriter();
return new Error("no writer connection available");
}
ReplSet.prototype.processIsMaster = function(_server, _ismaster) {
// Server in recovery mode, remove it from available servers
if(!_ismaster.ismaster && !_ismaster.secondary) {
// Locate the actual server
var server = this._state.addresses[_server.name];
// Close the server, simulating the closing of the connection
// to get right removal semantics
if(server) server.close();
// Execute any callback errors
_handler(null, this, server)(new Error("server is in recovery mode"));
}
}
ReplSet.prototype.allRawConnections = function() {
var connections = [];
for(var name in this._state.addresses) {
connections = connections.concat(this._state.addresses[name].allRawConnections());
}
return connections;
}
/**
* @ignore
*/
ReplSet.prototype.allServerInstances = function() {
var self = this;
// If no state yet return empty
if(!self._state) return [];
// Close all the servers (concatenate entire list of servers first for ease)
var allServers = self._state.master != null ? [self._state.master] : [];
// Secondary keys
var keys = Object.keys(self._state.secondaries);
// Add all secondaries
for(var i = 0; i < keys.length; i++) {
allServers.push(self._state.secondaries[keys[i]]);
}
// Return complete list of all servers
return allServers;
}
/**
* @ignore
*/
ReplSet.prototype.checkoutReader = function(readPreference, tags) {
var connection = null;
// If we have a read preference object unpack it
if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') {
// Validate if the object is using a valid mode
if(!readPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + readPreference.mode);
// Set the tag
tags = readPreference.tags;
readPreference = readPreference.mode;
} else if(typeof readPreference == 'object' && readPreference['_type'] != 'ReadPreference') {
return new Error("read preferences must be either a string or an instance of ReadPreference");
}
// Set up our read Preference, allowing us to override the readPreference
var finalReadPreference = readPreference != null ? readPreference : this.options.readPreference;
// Ensure we unpack a reference
if(finalReadPreference != null && typeof finalReadPreference == 'object' && finalReadPreference['_type'] == 'ReadPreference') {
// Validate if the object is using a valid mode
if(!finalReadPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + finalReadPreference.mode);
// Set the tag
tags = finalReadPreference.tags;
readPreference = finalReadPreference.mode;
}
// Finalize the read preference setup
finalReadPreference = finalReadPreference == true ? ReadPreference.SECONDARY_PREFERRED : finalReadPreference;
finalReadPreference = finalReadPreference == null ? ReadPreference.PRIMARY : finalReadPreference;
// If we are reading from a primary
if(finalReadPreference == 'primary') {
// If we provide a tags set send an error
if(typeof tags == 'object' && tags != null) {
return new Error("PRIMARY cannot be combined with tags");
}
// If we provide a tags set send an error
if(this._state.master == null) {
return new Error("No replica set primary available for query with ReadPreference PRIMARY");
}
// Checkout a writer
return this.checkoutWriter();
}
// If we have specified to read from a secondary server grab a random one and read
// from it, otherwise just pass the primary connection
if((this.options.readSecondary || finalReadPreference == ReadPreference.SECONDARY_PREFERRED || finalReadPreference == ReadPreference.SECONDARY) && Object.keys(this._state.secondaries).length > 0) {
// If we have tags, look for servers matching the specific tag
if(this.strategyInstance != null) {
// Only pick from secondaries
var _secondaries = [];
for(var key in this._state.secondaries) {
_secondaries.push(this._state.secondaries[key]);
}
if(finalReadPreference == ReadPreference.SECONDARY) {
// Check out the nearest from only the secondaries
connection = this.strategyInstance.checkoutConnection(tags, _secondaries);
} else {
connection = this.strategyInstance.checkoutConnection(tags, _secondaries);
// No candidate servers that match the tags, error
if(connection == null || connection instanceof Error) {
// No secondary server avilable, attemp to checkout a primary server
connection = this.checkoutWriter();
// If no connection return an error
if(connection == null || connection instanceof Error) {
return new Error("No replica set members available for query");
}
}
}
} else if(tags != null && typeof tags == 'object') {
// Get connection
connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) {
// No candidate servers that match the tags, error
if(connection == null) {
return new Error("No replica set members available for query");
}
} else {
connection = _roundRobin(this, tags);
}
} else if(finalReadPreference == ReadPreference.PRIMARY_PREFERRED) {
// Check if there is a primary available and return that if possible
connection = this.checkoutWriter();
// If no connection available checkout a secondary
if(connection == null || connection instanceof Error) {
// If we have tags, look for servers matching the specific tag
if(tags != null && typeof tags == 'object') {
// Get connection
connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) {
// No candidate servers that match the tags, error
if(connection == null) {
return new Error("No replica set members available for query");
}
} else {
connection = _roundRobin(this, tags);
}
}
} else if(finalReadPreference == ReadPreference.SECONDARY_PREFERRED) {
// If we have tags, look for servers matching the specific tag
if(this.strategyInstance != null) {
connection = this.strategyInstance.checkoutConnection(tags);
// No candidate servers that match the tags, error
if(connection == null || connection instanceof Error) {
// No secondary server avilable, attemp to checkout a primary server
connection = this.checkoutWriter();
// If no connection return an error
if(connection == null || connection instanceof Error) {
var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference;
return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags));
}
}
} else if(tags != null && typeof tags == 'object') {
// Get connection
connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) {
// No candidate servers that match the tags, error
if(connection == null) {
// No secondary server avilable, attemp to checkout a primary server
connection = this.checkoutWriter();
// If no connection return an error
if(connection == null || connection instanceof Error) {
var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference;
return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags));
}
}
}
} else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance != null) {
connection = this.strategyInstance.checkoutConnection(tags);
} else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance == null) {
return new Error("A strategy for calculating nearness must be enabled such as ping or statistical");
} else if(finalReadPreference == ReadPreference.SECONDARY && Object.keys(this._state.secondaries).length == 0) {
if(tags != null && typeof tags == 'object') {
var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference;
return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags));
} else {
return new Error("No replica set secondary available for query with ReadPreference SECONDARY");
}
} else {
connection = this.checkoutWriter();
}
// Return the connection
return connection;
}
/**
* @ignore
*/
var _pickFromTags = function(self, tags) {
// If we have an array or single tag selection
var tagObjects = Array.isArray(tags) ? tags : [tags];
// Iterate over all tags until we find a candidate server
for(var _i = 0; _i < tagObjects.length; _i++) {
// Grab a tag object
var tagObject = tagObjects[_i];
// Matching keys
var matchingKeys = Object.keys(tagObject);
// Match all the servers that match the provdided tags
var keys = Object.keys(self._state.secondaries);
var candidateServers = [];
for(var i = 0; i < keys.length; i++) {
var server = self._state.secondaries[keys[i]];
// If we have tags match
if(server.tags != null) {
var matching = true;
// Ensure we have all the values
for(var j = 0; j < matchingKeys.length; j++) {
if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
matching = false;
break;
}
}
// If we have a match add it to the list of matching servers
if(matching) {
candidateServers.push(server);
}
}
}
// If we have a candidate server return
if(candidateServers.length > 0) {
if(self.strategyInstance) return self.strategyInstance.checkoutConnection(tags, candidateServers);
// Set instance to return
return candidateServers[Math.floor(Math.random() * candidateServers.length)].checkoutReader();
}
}
// No connection found
return null;
}
/**
* Pick a secondary using round robin
*
* @ignore
*/
function _roundRobin (replset, tags) {
var keys = Object.keys(replset._state.secondaries);
// Update index
replset._currentServerChoice = replset._currentServerChoice + 1;
// Pick a server
var key = keys[replset._currentServerChoice % keys.length];
var conn = null != replset._state.secondaries[key]
? replset._state.secondaries[key].checkoutReader()
: null;
// If connection is null fallback to first available secondary
if(null == conn) {
conn = pickFirstConnectedSecondary(replset, tags);
}
return conn;
}
/**
* @ignore
*/
var pickFirstConnectedSecondary = function pickFirstConnectedSecondary(self, tags) {
var keys = Object.keys(self._state.secondaries);
var connection;
// Find first available reader if any
for(var i = 0; i < keys.length; i++) {
connection = self._state.secondaries[keys[i]].checkoutReader();
if(connection) return connection;
}
// If we still have a null, read from primary if it's not secondary only
if(self._readPreference == ReadPreference.SECONDARY_PREFERRED) {
connection = self._state.master.checkoutReader();
if(connection) return connection;
}
var preferenceName = self._readPreference == ReadPreference.SECONDARY_PREFERRED
? 'secondary'
: self._readPreference;
return new Error("No replica set member available for query with ReadPreference "
+ preferenceName + " and tags " + JSON.stringify(tags));
}
/**
* Get list of secondaries
* @ignore
*/
Object.defineProperty(ReplSet.prototype, "secondaries", {enumerable: true
, get: function() {
return utils.objectToArray(this._state.secondaries);
}
});
/**
* Get list of secondaries
* @ignore
*/
Object.defineProperty(ReplSet.prototype, "arbiters", {enumerable: true
, get: function() {
return utils.objectToArray(this._state.arbiters);
}
});
/**
* Interval state object constructor
*
* @ignore
*/
var ReplSetState = function ReplSetState () {
this.errorMessages = [];
this.secondaries = {};
this.addresses = {};
this.arbiters = {};
this.passives = {};
this.members = [];
this.errors = {};
this.setName = null;
this.master = null;
}
ReplSetState.prototype.hasValidServers = function() {
var validServers = [];
if(this.master && this.master.isConnected()) return true;
if(this.secondaries) {
var keys = Object.keys(this.secondaries)
for(var i = 0; i < keys.length; i++) {
if(this.secondaries[keys[i]].isConnected())
return true;
}
}
return false;
}
ReplSetState.prototype.getAllReadServers = function() {
var candidate_servers = [];
for(var name in this.addresses) {
candidate_servers.push(this.addresses[name]);
}
// Return all possible read candidates
return candidate_servers;
}
ReplSetState.prototype.addServer = function(server, master) {
server.name = master.me;
if(master.ismaster) {
this.master = server;
this.addresses[server.name] = server;
} else if(master.secondary) {
this.secondaries[server.name] = server;
this.addresses[server.name] = server;
} else if(master.arbiters) {
this.arbiters[server.name] = server;
this.addresses[server.name] = server;
}
}
ReplSetState.prototype.contains = function(host) {
return this.addresses[host] != null;
}
ReplSetState.prototype.isPrimary = function(server) {
return this.master && this.master.name == server.name;
}
ReplSetState.prototype.isSecondary = function(server) {
return this.secondaries[server.name] != null;
}
exports.ReplSetState = ReplSetState;
var Server = require("../../server").Server
, format = require('util').format;
// The ping strategy uses pings each server and records the
// elapsed time for the server so it can pick a server based on lowest
// return time for the db command {ping:true}
var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) {
this.replicaset = replicaset;
this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS;
this.state = 'disconnected';
this.pingInterval = 5000;
// Class instance
this.Db = require("../../../db").Db;
// Active db connections
this.dbs = {};
// Current server index
this.index = 0;
// Logger api
this.Logger = null;
}
// Starts any needed code
PingStrategy.prototype.start = function(callback) {
// already running?
if ('connected' == this.state) return;
this.state = 'connected';
// Start ping server
this._pingServer(callback);
}
// Stops and kills any processes running
PingStrategy.prototype.stop = function(callback) {
// Stop the ping process
this.state = 'disconnected';
// Stop all the server instances
for(var key in this.dbs) {
this.dbs[key].close();
}
// optional callback
callback && callback(null, null);
}
PingStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) {
// Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS
// Create a list of candidat servers, containing the primary if available
var candidateServers = [];
var self = this;
// If we have not provided a list of candidate servers use the default setup
if(!Array.isArray(secondaryCandidates)) {
candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : [];
// Add all the secondaries
var keys = Object.keys(this.replicaset._state.secondaries);
for(var i = 0; i < keys.length; i++) {
candidateServers.push(this.replicaset._state.secondaries[keys[i]])
}
} else {
candidateServers = secondaryCandidates;
}
// Final list of eligable server
var finalCandidates = [];
// If we have tags filter by tags
if(tags != null && typeof tags == 'object') {
// If we have an array or single tag selection
var tagObjects = Array.isArray(tags) ? tags : [tags];
// Iterate over all tags until we find a candidate server
for(var _i = 0; _i < tagObjects.length; _i++) {
// Grab a tag object
var tagObject = tagObjects[_i];
// Matching keys
var matchingKeys = Object.keys(tagObject);
// Remove any that are not tagged correctly
for(var i = 0; i < candidateServers.length; i++) {
var server = candidateServers[i];
// If we have tags match
if(server.tags != null) {
var matching = true;
// Ensure we have all the values
for(var j = 0; j < matchingKeys.length; j++) {
if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
matching = false;
break;
}
}
// If we have a match add it to the list of matching servers
if(matching) {
finalCandidates.push(server);
}
}
}
}
} else {
// Final array candidates
var finalCandidates = candidateServers;
}
// Sort by ping time
finalCandidates.sort(function(a, b) {
return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs'];
});
if(0 === finalCandidates.length)
return new Error("No replica set members available for query");
// find lowest server with a ping time
var lowest = finalCandidates.filter(function (server) {
return undefined != server.runtimeStats.pingMs;
})[0];
if(!lowest) {
lowest = finalCandidates[0];
}
// convert to integer
var lowestPing = lowest.runtimeStats.pingMs | 0;
// determine acceptable latency
var acceptable = lowestPing + this.secondaryAcceptableLatencyMS;
// remove any server responding slower than acceptable
var len = finalCandidates.length;
while(len--) {
if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) {
finalCandidates.splice(len, 1);
}
}
if(self.logger && self.logger.debug) {
self.logger.debug("Ping strategy selection order for tags", tags);
finalCandidates.forEach(function(c) {
self.logger.debug(format("%s:%s = %s ms", c.host, c.port, c.runtimeStats['pingMs']), null);
})
}
// If no candidates available return an error
if(finalCandidates.length == 0)
return new Error("No replica set members available for query");
// Ensure no we don't overflow
this.index = this.index % finalCandidates.length
// Pick a random acceptable server
var connection = finalCandidates[this.index].checkoutReader();
// Point to next candidate (round robin style)
this.index = this.index + 1;
if(self.logger && self.logger.debug) {
if(connection)
self.logger.debug("picked server %s:%s", connection.socketOptions.host, connection.socketOptions.port);
}
return connection;
}
PingStrategy.prototype._pingServer = function(callback) {
var self = this;
// Ping server function
var pingFunction = function() {
// Our state changed to disconnected or destroyed return
if(self.state == 'disconnected' || self.state == 'destroyed') return;
// If the replicaset is destroyed return
if(self.replicaset.isDestroyed() || self.replicaset._serverState == 'disconnected') return
// Create a list of all servers we can send the ismaster command to
var allServers = self.replicaset._state.master != null ? [self.replicaset._state.master] : [];
// Secondary keys
var keys = Object.keys(self.replicaset._state.secondaries);
// Add all secondaries
for(var i = 0; i < keys.length; i++) {
allServers.push(self.replicaset._state.secondaries[keys[i]]);
}
// Number of server entries
var numberOfEntries = allServers.length;
// We got keys
for(var i = 0; i < allServers.length; i++) {
// We got a server instance
var server = allServers[i];
// Create a new server object, avoid using internal connections as they might
// be in an illegal state
new function(serverInstance) {
var _db = self.dbs[serverInstance.host + ":" + serverInstance.port];
// If we have a db
if(_db != null) {
// Startup time of the command
var startTime = Date.now();
// Execute ping command in own scope
var _ping = function(__db, __serverInstance) {
// Execute ping on this connection
__db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
if(err) {
delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
__db.close();
return done();
}
if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
__serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
}
__db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) {
if(err) {
delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
__db.close();
return done();
}
// Process the ismaster for the server
if(result && result.documents && self.replicaset.processIsMaster) {
self.replicaset.processIsMaster(__serverInstance, result.documents[0]);
}
// Done with the pinging
done();
});
});
};
// Ping
_ping(_db, serverInstance);
} else {
var connectTimeoutMS = self.replicaset.options.socketOptions
? self.replicaset.options.socketOptions.connectTimeoutMS : 0
// Create a new master connection
var _server = new Server(serverInstance.host, serverInstance.port, {
auto_reconnect: false,
returnIsMasterResults: true,
slaveOk: true,
poolSize: 1,
socketOptions: { connectTimeoutMS: connectTimeoutMS },
ssl: self.replicaset.ssl,
sslValidate: self.replicaset.sslValidate,
sslCA: self.replicaset.sslCA,
sslCert: self.replicaset.sslCert,
sslKey: self.replicaset.sslKey,
sslPass: self.replicaset.sslPass
});
// Create Db instance
var _db = new self.Db('local', _server, { safe: true });
_db.on("close", function() {
delete self.dbs[this.serverConfig.host + ":" + this.serverConfig.port];
})
var _ping = function(__db, __serverInstance) {
if(self.state == 'disconnected') {
self.stop();
return;
}
__db.open(function(err, db) {
if(self.state == 'disconnected' && __db != null) {
return __db.close();
}
if(err) {
delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
__db.close();
return done();
}
// Save instance
self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port] = __db;
// Startup time of the command
var startTime = Date.now();
// Execute ping on this connection
__db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
if(err) {
delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
__db.close();
return done();
}
if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
__serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
}
__db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) {
if(err) {
delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
__db.close();
return done();
}
// Process the ismaster for the server
if(result && result.documents && self.replicaset.processIsMaster) {
self.replicaset.processIsMaster(__serverInstance, result.documents[0]);
}
// Done with the pinging
done();
});
});
});
};
// Ping the server
_ping(_db, serverInstance);
}
function done() {
// Adjust the number of checks
numberOfEntries--;
// If we are done with all results coming back trigger ping again
if(0 === numberOfEntries && 'connected' == self.state) {
setTimeout(pingFunction, self.pingInterval);
}
}
}(server);
}
}
// Start pingFunction
pingFunction();
callback && callback(null);
}
// The Statistics strategy uses the measure of each end-start time for each
// query executed against the db to calculate the mean, variance and standard deviation
// and pick the server which the lowest mean and deviation
var StatisticsStrategy = exports.StatisticsStrategy = function(replicaset) {
this.replicaset = replicaset;
// Logger api
this.Logger = null;
}
// Starts any needed code
StatisticsStrategy.prototype.start = function(callback) {
callback && callback(null, null);
}
StatisticsStrategy.prototype.stop = function(callback) {
callback && callback(null, null);
}
StatisticsStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) {
// Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS
// Create a list of candidat servers, containing the primary if available
var candidateServers = [];
// If we have not provided a list of candidate servers use the default setup
if(!Array.isArray(secondaryCandidates)) {
candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : [];
// Add all the secondaries
var keys = Object.keys(this.replicaset._state.secondaries);
for(var i = 0; i < keys.length; i++) {
candidateServers.push(this.replicaset._state.secondaries[keys[i]])
}
} else {
candidateServers = secondaryCandidates;
}
// Final list of eligable server
var finalCandidates = [];
// If we have tags filter by tags
if(tags != null && typeof tags == 'object') {
// If we have an array or single tag selection
var tagObjects = Array.isArray(tags) ? tags : [tags];
// Iterate over all tags until we find a candidate server
for(var _i = 0; _i < tagObjects.length; _i++) {
// Grab a tag object
var tagObject = tagObjects[_i];
// Matching keys
var matchingKeys = Object.keys(tagObject);
// Remove any that are not tagged correctly
for(var i = 0; i < candidateServers.length; i++) {
var server = candidateServers[i];
// If we have tags match
if(server.tags != null) {
var matching = true;
// Ensure we have all the values
for(var j = 0; j < matchingKeys.length; j++) {
if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
matching = false;
break;
}
}
// If we have a match add it to the list of matching servers
if(matching) {
finalCandidates.push(server);
}
}
}
}
} else {
// Final array candidates
var finalCandidates = candidateServers;
}
// If no candidates available return an error
if(finalCandidates.length == 0) return new Error("No replica set members available for query");
// Pick a random server
return finalCandidates[Math.round(Math.random(1000000) * (finalCandidates.length - 1))].checkoutReader();
}
var Connection = require('./connection').Connection,
ReadPreference = require('./read_preference').ReadPreference,
DbCommand = require('../commands/db_command').DbCommand,
MongoReply = require('../responses/mongo_reply').MongoReply,
ConnectionPool = require('./connection_pool').ConnectionPool,
EventEmitter = require('events').EventEmitter,
Base = require('./base').Base,
format = require('util').format,
utils = require('../utils'),
timers = require('timers'),
inherits = require('util').inherits;
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
/**
* Class representing a single MongoDB Server connection
*
* Options
* - **readPreference** {String, default:null}, set's the read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST)
* - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support)
* - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
* - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
* - **poolSize** {Number, default:5}, number of connections in the connection pool, set to 5 as default for legacy reasons.
* - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number))
* - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**.
* - **auto_reconnect** {Boolean, default:false}, reconnect on error.
* - **disableDriverBSONSizeCheck** {Boolean, default:false}, force the server to error if the BSON message is to big
*
* @class Represents a Server connection.
* @param {String} host the server host
* @param {Number} port the server port
* @param {Object} [options] optional options for insert command
*/
function Server(host, port, options) {
// Set up Server instance
if(!(this instanceof Server)) return new Server(host, port, options);
// Set up event emitter
Base.call(this);
// Ensure correct values
if(port != null && typeof port == 'object') {
options = port;
port = Connection.DEFAULT_PORT;
}
var self = this;
this.host = host;
this.port = port;
this.options = options == null ? {} : options;
this.internalConnection;
this.internalMaster = false;
this.connected = false;
this.poolSize = this.options.poolSize == null ? 5 : this.options.poolSize;
this.disableDriverBSONSizeCheck = this.options.disableDriverBSONSizeCheck != null ? this.options.disableDriverBSONSizeCheck : false;
this._used = false;
this.replicasetInstance = null;
// Emit open setup
this.emitOpen = this.options.emitOpen || true;
// Set ssl as connection method
this.ssl = this.options.ssl == null ? false : this.options.ssl;
// Set ssl validation
this.sslValidate = this.options.sslValidate == null ? false : this.options.sslValidate;
// Set the ssl certificate authority (array of Buffer/String keys)
this.sslCA = Array.isArray(this.options.sslCA) ? this.options.sslCA : null;
// Certificate to present to the server
this.sslCert = this.options.sslCert;
// Certificate private key if in separate file
this.sslKey = this.options.sslKey;
// Password to unlock private key
this.sslPass = this.options.sslPass;
// Set server name
this.name = format("%s:%s", host, port);
// Ensure we are not trying to validate with no list of certificates
if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) {
throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate");
}
// Get the readPreference
var readPreference = this.options['readPreference'];
// If readPreference is an object get the mode string
var validateReadPreference = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference;
// Read preference setting
if(validateReadPreference != null) {
if(validateReadPreference != ReadPreference.PRIMARY && validateReadPreference != ReadPreference.SECONDARY && validateReadPreference != ReadPreference.NEAREST
&& validateReadPreference != ReadPreference.SECONDARY_PREFERRED && validateReadPreference != ReadPreference.PRIMARY_PREFERRED) {
throw new Error("Illegal readPreference mode specified, " + validateReadPreference);
}
// Set read Preference
this._readPreference = readPreference;
} else {
this._readPreference = null;
}
// Contains the isMaster information returned from the server
this.isMasterDoc;
// Set default connection pool options
this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {};
if(this.disableDriverBSONSizeCheck) this.socketOptions.disableDriverBSONSizeCheck = this.disableDriverBSONSizeCheck;
// Set ssl up if it's defined
if(this.ssl) {
this.socketOptions.ssl = true;
// Set ssl validation
this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate;
// Set the ssl certificate authority (array of Buffer/String keys)
this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null;
// Set certificate to present
this.socketOptions.sslCert = this.sslCert;
// Set certificate to present
this.socketOptions.sslKey = this.sslKey;
// Password to unlock private key
this.socketOptions.sslPass = this.sslPass;
}
// Set up logger if any set
this.logger = this.options.logger != null
&& (typeof this.options.logger.debug == 'function')
&& (typeof this.options.logger.error == 'function')
&& (typeof this.options.logger.log == 'function')
? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}};
// Just keeps list of events we allow
this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[]};
// Internal state of server connection
this._serverState = 'disconnected';
// Contains state information about server connection
this._state = {'runtimeStats': {'queryStats':new RunningStats()}};
// Do we record server stats or not
this.recordQueryStats = false;
};
/**
* @ignore
*/
inherits(Server, Base);
//
// Deprecated, USE ReadPreferences class
//
Server.READ_PRIMARY = ReadPreference.PRIMARY;
Server.READ_SECONDARY = ReadPreference.SECONDARY_PREFERRED;
Server.READ_SECONDARY_ONLY = ReadPreference.SECONDARY;
/**
* Always ourselves
* @ignore
*/
Server.prototype.setReadPreference = function(readPreference) {
this._readPreference = readPreference;
}
/**
* @ignore
*/
Server.prototype.isMongos = function() {
return this.isMasterDoc != null && this.isMasterDoc['msg'] == "isdbgrid" ? true : false;
}
/**
* @ignore
*/
Server.prototype._isUsed = function() {
return this._used;
}
/**
* @ignore
*/
Server.prototype.close = function(callback) {
// Set server status as disconnected
this._serverState = 'destroyed';
// Remove all local listeners
this.removeAllListeners();
if(this.connectionPool != null) {
// Remove all the listeners on the pool so it does not fire messages all over the place
this.connectionPool.removeAllEventListeners();
// Close the connection if it's open
this.connectionPool.stop(true);
}
// Emit close event
if(this.db && !this.isSetMember()) {
var self = this;
processor(function() {
self._emitAcrossAllDbInstances(self, null, "close", null, null, true)
})
}
// Peform callback if present
if(typeof callback === 'function') callback(null);
};
Server.prototype.isDestroyed = function() {
return this._serverState == 'destroyed';
}
/**
* @ignore
*/
Server.prototype.isConnected = function() {
return this.connectionPool != null && this.connectionPool.isConnected();
}
/**
* @ignore
*/
Server.prototype.canWrite = Server.prototype.isConnected;
Server.prototype.canRead = Server.prototype.isConnected;
Server.prototype.isAutoReconnect = function() {
if(this.isSetMember()) return false;
return this.options.auto_reconnect != null ? this.options.auto_reconnect : true;
}
/**
* @ignore
*/
Server.prototype.allServerInstances = function() {
return [this];
}
/**
* @ignore
*/
Server.prototype.isSetMember = function() {
return this.replicasetInstance != null || this.mongosInstance != null;
}
/**
* Assigns a replica set to this `server`.
*
* @param {ReplSet} replset
* @ignore
*/
Server.prototype.assignReplicaSet = function (replset) {
this.replicasetInstance = replset;
this.inheritReplSetOptionsFrom(replset);
this.enableRecordQueryStats(replset.recordQueryStats);
}
/**
* Takes needed options from `replset` and overwrites
* our own options.
*
* @param {ReplSet} replset
* @ignore
*/
Server.prototype.inheritReplSetOptionsFrom = function (replset) {
this.socketOptions = {};
this.socketOptions.connectTimeoutMS = replset.options.socketOptions.connectTimeoutMS || 30000;
if(replset.options.ssl) {
// Set ssl on
this.socketOptions.ssl = true;
// Set ssl validation
this.socketOptions.sslValidate = replset.options.sslValidate == null ? false : replset.options.sslValidate;
// Set the ssl certificate authority (array of Buffer/String keys)
this.socketOptions.sslCA = Array.isArray(replset.options.sslCA) ? replset.options.sslCA : null;
// Set certificate to present
this.socketOptions.sslCert = replset.options.sslCert;
// Set certificate to present
this.socketOptions.sslKey = replset.options.sslKey;
// Password to unlock private key
this.socketOptions.sslPass = replset.options.sslPass;
}
// If a socket option object exists clone it
if(utils.isObject(replset.options.socketOptions)) {
var keys = Object.keys(replset.options.socketOptions);
for(var i = 0; i < keys.length; i++)
this.socketOptions[keys[i]] = replset.options.socketOptions[keys[i]];
}
}
/**
* Opens this server connection.
*
* @ignore
*/
Server.prototype.connect = function(dbInstance, options, callback) {
if('function' === typeof options) callback = options, options = {};
if(options == null) options = {};
if(!('function' === typeof callback)) callback = null;
var self = this;
// Save the options
this.options = options;
// Currently needed to work around problems with multiple connections in a pool with ssl
// TODO fix if possible
if(this.ssl == true) {
// Set up socket options for ssl
this.socketOptions.ssl = true;
// Set ssl validation
this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate;
// Set the ssl certificate authority (array of Buffer/String keys)
this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null;
// Set certificate to present
this.socketOptions.sslCert = this.sslCert;
// Set certificate to present
this.socketOptions.sslKey = this.sslKey;
// Password to unlock private key
this.socketOptions.sslPass = this.sslPass;
}
// Let's connect
var server = this;
// Let's us override the main receiver of events
var eventReceiver = options.eventReceiver != null ? options.eventReceiver : this;
// Save reference to dbInstance
this.db = dbInstance; // `db` property matches ReplSet and Mongos
this.dbInstances = [dbInstance];
// Force connection pool if there is one
if(server.connectionPool) server.connectionPool.stop();
// Set server state to connecting
this._serverState = 'connecting';
if(server.connectionPool != null) {
// Remove all the listeners on the pool so it does not fire messages all over the place
this.connectionPool.removeAllEventListeners();
// Close the connection if it's open
this.connectionPool.stop(true);
}
this.connectionPool = new ConnectionPool(this.host, this.port, this.poolSize, dbInstance.bson, this.socketOptions);
var connectionPool = this.connectionPool;
// If ssl is not enabled don't wait between the pool connections
if(this.ssl == null || !this.ssl) connectionPool._timeToWait = null;
// Set logger on pool
connectionPool.logger = this.logger;
connectionPool.bson = dbInstance.bson;
// Set basic parameters passed in
var returnIsMasterResults = options.returnIsMasterResults == null ? false : options.returnIsMasterResults;
// Create a default connect handler, overriden when using replicasets
var connectCallback = function(_server) {
return function(err, reply) {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Assign the server
_server = _server != null ? _server : server;
// If something close down the connection and removed the callback before
// proxy killed connection etc, ignore the erorr as close event was isssued
if(err != null && internalCallback == null) return;
// Internal callback
if(err != null) return internalCallback(err, null, _server);
_server.master = reply.documents[0].ismaster == 1 ? true : false;
_server.connectionPool.setMaxBsonSize(reply.documents[0].maxBsonObjectSize);
_server.connectionPool.setMaxMessageSizeBytes(reply.documents[0].maxMessageSizeBytes);
// Set server state to connEcted
_server._serverState = 'connected';
// Set server as connected
_server.connected = true;
// Save document returned so we can query it
_server.isMasterDoc = reply.documents[0];
if(self.emitOpen) {
_server._emitAcrossAllDbInstances(_server, eventReceiver, "open", null, returnIsMasterResults ? reply : null, null);
self.emitOpen = false;
} else {
_server._emitAcrossAllDbInstances(_server, eventReceiver, "reconnect", null, returnIsMasterResults ? reply : null, null);
}
// If we have it set to returnIsMasterResults
if(returnIsMasterResults) {
internalCallback(null, reply, _server);
} else {
internalCallback(null, dbInstance, _server);
}
}
};
// Let's us override the main connect callback
var connectHandler = options.connectHandler == null ? connectCallback(server) : options.connectHandler;
// Set up on connect method
connectionPool.on("poolReady", function() {
// Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks)
var db_command = DbCommand.NcreateIsMasterCommand(dbInstance, dbInstance.databaseName);
// Check out a reader from the pool
var connection = connectionPool.checkoutConnection();
// Register handler for messages
server._registerHandler(db_command, false, connection, connectHandler);
// Write the command out
connection.write(db_command);
})
// Set up item connection
connectionPool.on("message", function(message) {
// Attempt to parse the message
try {
// Create a new mongo reply
var mongoReply = new MongoReply()
// Parse the header
mongoReply.parseHeader(message, connectionPool.bson)
// If message size is not the same as the buffer size
// something went terribly wrong somewhere
if(mongoReply.messageLength != message.length) {
// Emit the error
if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", new Error("bson length is different from message length"), server);
// Remove all listeners
server.removeAllListeners();
} else {
var startDate = new Date().getTime();
// Callback instance
var callbackInfo = server._findHandler(mongoReply.responseTo.toString());
// The command executed another request, log the handler again under that request id
if(mongoReply.requestId > 0 && mongoReply.cursorId.toString() != "0"
&& callbackInfo && callbackInfo.info && callbackInfo.info.exhaust) {
server._reRegisterHandler(mongoReply.requestId, callbackInfo);
}
// Parse the body
mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) {
if(err != null) {
// If pool connection is already closed
if(server._serverState === 'disconnected') return;
// Set server state to disconnected
server._serverState = 'disconnected';
// Remove all listeners and close the connection pool
server.removeAllListeners();
connectionPool.stop(true);
// If we have a callback return the error
if(typeof callback === 'function') {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Perform callback
internalCallback(new Error("connection closed due to parseError"), null, server);
} else if(server.isSetMember()) {
if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", new Error("connection closed due to parseError"), server);
} else {
if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", new Error("connection closed due to parseError"), server);
}
// If we are a single server connection fire errors correctly
if(!server.isSetMember()) {
// Fire all callback errors
server.__executeAllCallbacksWithError(new Error("connection closed due to parseError"));
// Emit error
server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true);
}
// Short cut
return;
}
// Let's record the stats info if it's enabled
if(server.recordQueryStats == true && server._state['runtimeStats'] != null
&& server._state.runtimeStats['queryStats'] instanceof RunningStats) {
// Add data point to the running statistics object
server._state.runtimeStats.queryStats.push(new Date().getTime() - callbackInfo.info.start);
}
// Dispatch the call
server._callHandler(mongoReply.responseTo, mongoReply, null);
// If we have an error about the server not being master or primary
if((mongoReply.responseFlag & (1 << 1)) != 0
&& mongoReply.documents[0].code
&& mongoReply.documents[0].code == 13436) {
server.close();
}
});
}
} catch (err) {
// Throw error in next tick
processor(function() {
throw err;
})
}
});
// Handle timeout
connectionPool.on("timeout", function(err) {
// If pool connection is already closed
if(server._serverState === 'disconnected'
|| server._serverState === 'destroyed') return;
// Set server state to disconnected
server._serverState = 'disconnected';
// If we have a callback return the error
if(typeof callback === 'function') {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Perform callback
internalCallback(err, null, server);
} else if(server.isSetMember()) {
if(server.listeners("timeout") && server.listeners("timeout").length > 0) server.emit("timeout", err, server);
} else {
if(eventReceiver.listeners("timeout") && eventReceiver.listeners("timeout").length > 0) eventReceiver.emit("timeout", err, server);
}
// If we are a single server connection fire errors correctly
if(!server.isSetMember()) {
// Fire all callback errors
server.__executeAllCallbacksWithError(err);
// Emit error
server._emitAcrossAllDbInstances(server, eventReceiver, "timeout", err, server, true);
}
// If we have autoConnect enabled let's fire up an attempt to reconnect
if(server.isAutoReconnect()
&& !server.isSetMember()
&& (server._serverState != 'destroyed')
&& !server._reconnectInProgreess) {
// Set the number of retries
server._reconnect_retries = server.db.numberOfRetries;
// Attempt reconnect
server._reconnectInProgreess = true;
setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds);
}
});
// Handle errors
connectionPool.on("error", function(message, connection, error_options) {
// If pool connection is already closed
if(server._serverState === 'disconnected'
|| server._serverState === 'destroyed') return;
// Set server state to disconnected
server._serverState = 'disconnected';
// Error message
var error_message = new Error(message && message.err ? message.err : message);
// Error message coming from ssl
if(error_options && error_options.ssl) error_message.ssl = true;
// If we have a callback return the error
if(typeof callback === 'function') {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Perform callback
internalCallback(error_message, null, server);
} else if(server.isSetMember()) {
if(server.listeners("error") && server.listeners("error").length > 0) server.emit("error", error_message, server);
} else {
if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", error_message, server);
}
// If we are a single server connection fire errors correctly
if(!server.isSetMember()) {
// Fire all callback errors
server.__executeAllCallbacksWithError(error_message);
// Emit error
server._emitAcrossAllDbInstances(server, eventReceiver, "error", error_message, server, true);
}
// If we have autoConnect enabled let's fire up an attempt to reconnect
if(server.isAutoReconnect()
&& !server.isSetMember()
&& (server._serverState != 'destroyed')
&& !server._reconnectInProgreess) {
// Set the number of retries
server._reconnect_retries = server.db.numberOfRetries;
// Attempt reconnect
server._reconnectInProgreess = true;
setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds);
}
});
// Handle close events
connectionPool.on("close", function() {
// If pool connection is already closed
if(server._serverState === 'disconnected'
|| server._serverState === 'destroyed') return;
// Set server state to disconnected
server._serverState = 'disconnected';
// If we have a callback return the error
if(typeof callback == 'function') {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Perform callback
internalCallback(new Error("connection closed"), null, server);
} else if(server.isSetMember()) {
if(server.listeners("close") && server.listeners("close").length > 0) server.emit("close", new Error("connection closed"), server);
} else {
if(eventReceiver.listeners("close") && eventReceiver.listeners("close").length > 0) eventReceiver.emit("close", new Error("connection closed"), server);
}
// If we are a single server connection fire errors correctly
if(!server.isSetMember()) {
// Fire all callback errors
server.__executeAllCallbacksWithError(new Error("connection closed"));
// Emit error
server._emitAcrossAllDbInstances(server, eventReceiver, "close", server, null, true);
}
// If we have autoConnect enabled let's fire up an attempt to reconnect
if(server.isAutoReconnect()
&& !server.isSetMember()
&& (server._serverState != 'destroyed')
&& !server._reconnectInProgreess) {
// Set the number of retries
server._reconnect_retries = server.db.numberOfRetries;
// Attempt reconnect
server._reconnectInProgreess = true;
setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds);
}
});
/**
* @ignore
*/
var __attemptReconnect = function(server) {
return function() {
// Attempt reconnect
server.connect(server.db, server.options, function(err, result) {
server._reconnect_retries = server._reconnect_retries - 1;
if(err) {
// Retry
if(server._reconnect_retries == 0 || server._serverState == 'destroyed') {
server._serverState = 'connected';
server._reconnectInProgreess = false
// Fire all callback errors
return server.__executeAllCallbacksWithError(new Error("failed to reconnect to server"));
} else {
return setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds);
}
} else {
// Set as authenticating (isConnected will be false)
server._serverState = 'authenticating';
// Apply any auths, we don't try to catch any errors here
// as there are nowhere to simply propagate them to
self._apply_auths(server.db, function(err, result) {
server._serverState = 'connected';
server._reconnectInProgreess = false;
server._commandsStore.execute_queries();
server._commandsStore.execute_writes();
});
}
});
}
}
// If we have a parser error we are in an unknown state, close everything and emit
// error
connectionPool.on("parseError", function(message) {
// If pool connection is already closed
if(server._serverState === 'disconnected'
|| server._serverState === 'destroyed') return;
// Set server state to disconnected
server._serverState = 'disconnected';
// If we have a callback return the error
if(typeof callback === 'function') {
// ensure no callbacks get called twice
var internalCallback = callback;
callback = null;
// Perform callback
internalCallback(new Error("connection closed due to parseError"), null, server);
} else if(server.isSetMember()) {
if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", new Error("connection closed due to parseError"), server);
} else {
if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", new Error("connection closed due to parseError"), server);
}
// If we are a single server connection fire errors correctly
if(!server.isSetMember()) {
// Fire all callback errors
server.__executeAllCallbacksWithError(new Error("connection closed due to parseError"));
// Emit error
server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true);
}
});
// Boot up connection poole, pass in a locator of callbacks
connectionPool.start();
}
/**
* @ignore
*/
Server.prototype.allRawConnections = function() {
return this.connectionPool.getAllConnections();
}
/**
* Check if a writer can be provided
* @ignore
*/
var canCheckoutWriter = function(self, read) {
// We cannot write to an arbiter or secondary server
if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) {
return new Error("Cannot write to an arbiter");
} if(self.isMasterDoc && self.isMasterDoc['secondary'] == true) {
return new Error("Cannot write to a secondary");
} else if(read == true && self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) {
return new Error("Cannot read from primary when secondary only specified");
} else if(!self.isMasterDoc) {
return new Error("Cannot determine state of server");
}
// Return no error
return null;
}
/**
* @ignore
*/
Server.prototype.checkoutWriter = function(read) {
if(read == true) return this.connectionPool.checkoutConnection();
// Check if are allowed to do a checkout (if we try to use an arbiter f.ex)
var result = canCheckoutWriter(this, read);
// If the result is null check out a writer
if(result == null && this.connectionPool != null) {
return this.connectionPool.checkoutConnection();
} else if(result == null) {
return null;
} else {
return result;
}
}
/**
* Check if a reader can be provided
* @ignore
*/
var canCheckoutReader = function(self) {
// We cannot write to an arbiter or secondary server
if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) {
return new Error("Cannot write to an arbiter");
} else if(self._readPreference != null) {
// If the read preference is Primary and the instance is not a master return an error
if((self._readPreference == ReadPreference.PRIMARY) && self.isMasterDoc && self.isMasterDoc['ismaster'] != true) {
return new Error("Read preference is Server.PRIMARY and server is not master");
} else if(self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) {
return new Error("Cannot read from primary when secondary only specified");
}
} else if(!self.isMasterDoc) {
return new Error("Cannot determine state of server");
}
// Return no error
return null;
}
/**
* @ignore
*/
Server.prototype.checkoutReader = function(read) {
// Check if are allowed to do a checkout (if we try to use an arbiter f.ex)
var result = canCheckoutReader(this);
// If the result is null check out a writer
if(result == null && this.connectionPool != null) {
return this.connectionPool.checkoutConnection();
} else if(result == null) {
return null;
} else {
return result;
}
}
/**
* @ignore
*/
Server.prototype.enableRecordQueryStats = function(enable) {
this.recordQueryStats = enable;
}
/**
* Internal statistics object used for calculating average and standard devitation on
* running queries
* @ignore
*/
var RunningStats = function() {
var self = this;
this.m_n = 0;
this.m_oldM = 0.0;
this.m_oldS = 0.0;
this.m_newM = 0.0;
this.m_newS = 0.0;
// Define getters
Object.defineProperty(this, "numDataValues", { enumerable: true
, get: function () { return this.m_n; }
});
Object.defineProperty(this, "mean", { enumerable: true
, get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; }
});
Object.defineProperty(this, "variance", { enumerable: true
, get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); }
});
Object.defineProperty(this, "standardDeviation", { enumerable: true
, get: function () { return Math.sqrt(this.variance); }
});
Object.defineProperty(this, "sScore", { enumerable: true
, get: function () {
var bottom = this.mean + this.standardDeviation;
if(bottom == 0) return 0;
return ((2 * this.mean * this.standardDeviation)/(bottom));
}
});
}
/**
* @ignore
*/
RunningStats.prototype.push = function(x) {
// Update the number of samples
this.m_n = this.m_n + 1;
// See Knuth TAOCP vol 2, 3rd edition, page 232
if(this.m_n == 1) {
this.m_oldM = this.m_newM = x;
this.m_oldS = 0.0;
} else {
this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n;
this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM);
// set up for next iteration
this.m_oldM = this.m_newM;
this.m_oldS = this.m_newS;
}
}
/**
* @ignore
*/
Object.defineProperty(Server.prototype, "autoReconnect", { enumerable: true
, get: function () {
return this.options['auto_reconnect'] == null ? false : this.options['auto_reconnect'];
}
});
/**
* @ignore
*/
Object.defineProperty(Server.prototype, "connection", { enumerable: true
, get: function () {
return this.internalConnection;
}
, set: function(connection) {
this.internalConnection = connection;
}
});
/**
* @ignore
*/
Object.defineProperty(Server.prototype, "master", { enumerable: true
, get: function () {
return this.internalMaster;
}
, set: function(value) {
this.internalMaster = value;
}
});
/**
* @ignore
*/
Object.defineProperty(Server.prototype, "primary", { enumerable: true
, get: function () {
return this;
}
});
/**
* Getter for query Stats
* @ignore
*/
Object.defineProperty(Server.prototype, "queryStats", { enumerable: true
, get: function () {
return this._state.runtimeStats.queryStats;
}
});
/**
* @ignore
*/
Object.defineProperty(Server.prototype, "runtimeStats", { enumerable: true
, get: function () {
return this._state.runtimeStats;
}
});
/**
* Get Read Preference method
* @ignore
*/
Object.defineProperty(Server.prototype, "readPreference", { enumerable: true
, get: function () {
if(this._readPreference == null && this.readSecondary) {
return Server.READ_SECONDARY;
} else if(this._readPreference == null && !this.readSecondary) {
return Server.READ_PRIMARY;
} else {
return this._readPreference;
}
}
});
/**
* @ignore
*/
exports.Server = Server;
var fs = require('fs'),
ReadPreference = require('./read_preference').ReadPreference;
exports.parse = function(url, options) {
// Ensure we have a default options object if none set
options = options || {};
// Variables
var connection_part = '';
var auth_part = '';
var query_string_part = '';
var dbName = 'admin';
// Must start with mongodb
if(url.indexOf("mongodb://") != 0)
throw Error("URL must be in the format mongodb://user:pass@host:port/dbname");
// If we have a ? mark cut the query elements off
if(url.indexOf("?") != -1) {
query_string_part = url.substr(url.indexOf("?") + 1);
connection_part = url.substring("mongodb://".length, url.indexOf("?"))
} else {
connection_part = url.substring("mongodb://".length);
}
// Check if we have auth params
if(connection_part.indexOf("@") != -1) {
auth_part = connection_part.split("@")[0];
connection_part = connection_part.split("@")[1];
}
// Check if the connection string has a db
if(connection_part.indexOf(".sock") != -1) {
if(connection_part.indexOf(".sock/") != -1) {
dbName = connection_part.split(".sock/")[1];
connection_part = connection_part.split("/", connection_part.indexOf(".sock") + ".sock".length);
}
} else if(connection_part.indexOf("/") != -1) {
dbName = connection_part.split("/")[1];
connection_part = connection_part.split("/")[0];
}
// Result object
var object = {};
// Pick apart the authentication part of the string
var authPart = auth_part || '';
var auth = authPart.split(':', 2);
if(options['uri_decode_auth']){
auth[0] = decodeURIComponent(auth[0]);
if(auth[1]){
auth[1] = decodeURIComponent(auth[1]);
}
}
// Add auth to final object if we have 2 elements
if(auth.length == 2) object.auth = {user: auth[0], password: auth[1]};
// Variables used for temporary storage
var hostPart;
var urlOptions;
var servers;
var serverOptions = {socketOptions: {}};
var dbOptions = {read_preference_tags: []};
var replSetServersOptions = {socketOptions: {}};
// Add server options to final object
object.server_options = serverOptions;
object.db_options = dbOptions;
object.rs_options = replSetServersOptions;
object.mongos_options = {};
// Let's check if we are using a domain socket
if(url.match(/\.sock/)) {
// Split out the socket part
var domainSocket = url.substring(
url.indexOf("mongodb://") + "mongodb://".length
, url.lastIndexOf(".sock") + ".sock".length);
// Clean out any auth stuff if any
if(domainSocket.indexOf("@") != -1) domainSocket = domainSocket.split("@")[1];
servers = [{domain_socket: domainSocket}];
} else {
// Split up the db
hostPart = connection_part;
// Parse all server results
servers = hostPart.split(',').map(function(h) {
var hostPort = h.split(':', 2);
var _host = hostPort[0] || 'localhost';
var _port = hostPort[1] != null ? parseInt(hostPort[1], 10) : 27017;
// Check for localhost?safe=true style case
if(_host.indexOf("?") != -1) _host = _host.split(/\?/)[0];
// Return the mapped object
return {host: _host, port: _port};
});
}
// Get the db name
object.dbName = dbName || 'admin';
// Split up all the options
urlOptions = (query_string_part || '').split(/[&;]/);
// Ugh, we have to figure out which options go to which constructor manually.
urlOptions.forEach(function(opt) {
if(!opt) return;
var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1];
// Options implementations
switch(name) {
case 'slaveOk':
case 'slave_ok':
serverOptions.slave_ok = (value == 'true');
dbOptions.slaveOk = (value == 'true');
break;
case 'maxPoolSize':
case 'poolSize':
serverOptions.poolSize = parseInt(value, 10);
replSetServersOptions.poolSize = parseInt(value, 10);
break;
case 'autoReconnect':
case 'auto_reconnect':
serverOptions.auto_reconnect = (value == 'true');
break;
case 'minPoolSize':
throw new Error("minPoolSize not supported");
case 'maxIdleTimeMS':
throw new Error("maxIdleTimeMS not supported");
case 'waitQueueMultiple':
throw new Error("waitQueueMultiple not supported");
case 'waitQueueTimeoutMS':
throw new Error("waitQueueTimeoutMS not supported");
case 'uuidRepresentation':
throw new Error("uuidRepresentation not supported");
case 'ssl':
if(value == 'prefer') {
serverOptions.ssl = value;
replSetServersOptions.ssl = value;
break;
}
serverOptions.ssl = (value == 'true');
replSetServersOptions.ssl = (value == 'true');
break;
case 'replicaSet':
case 'rs_name':
replSetServersOptions.rs_name = value;
break;
case 'reconnectWait':
replSetServersOptions.reconnectWait = parseInt(value, 10);
break;
case 'retries':
replSetServersOptions.retries = parseInt(value, 10);
break;
case 'readSecondary':
case 'read_secondary':
replSetServersOptions.read_secondary = (value == 'true');
break;
case 'fsync':
dbOptions.fsync = (value == 'true');
break;
case 'journal':
dbOptions.journal = (value == 'true');
break;
case 'safe':
dbOptions.safe = (value == 'true');
break;
case 'nativeParser':
case 'native_parser':
dbOptions.native_parser = (value == 'true');
break;
case 'connectTimeoutMS':
serverOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
replSetServersOptions.socketOptions.connectTimeoutMS = parseInt(value, 10);
break;
case 'socketTimeoutMS':
serverOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
replSetServersOptions.socketOptions.socketTimeoutMS = parseInt(value, 10);
break;
case 'w':
dbOptions.w = parseInt(value, 10);
break;
case 'authSource':
dbOptions.authSource = value;
break;
case 'gssapiServiceName':
dbOptions.gssapiServiceName = value;
break;
case 'authMechanism':
if(value == 'GSSAPI') {
// If no password provided decode only the principal
if(object.auth == null) {
var urlDecodeAuthPart = decodeURIComponent(authPart);
if(urlDecodeAuthPart.indexOf("@") == -1) throw new Error("GSSAPI requires a provided principal");
object.auth = {user: urlDecodeAuthPart, password: null};
} else {
object.auth.user = decodeURIComponent(object.auth.user);
}
}
// Only support GSSAPI or MONGODB-CR for now
if(value != 'GSSAPI'
&& value != 'MONGODB-CR'
&& value != 'PLAIN')
throw new Error("only GSSAPI, PLAIN or MONGODB-CR is supported by authMechanism");
// Authentication mechanism
dbOptions.authMechanism = value;
break;
case 'wtimeoutMS':
dbOptions.wtimeoutMS = parseInt(value, 10);
break;
case 'readPreference':
if(!ReadPreference.isValid(value)) throw new Error("readPreference must be either primary/primaryPreferred/secondary/secondaryPreferred/nearest");
dbOptions.read_preference = value;
break;
case 'readPreferenceTags':
// Contains the tag object
var tagObject = {};
if(value == null || value == '') {
dbOptions.read_preference_tags.push(tagObject);
break;
}
// Split up the tags
var tags = value.split(/\,/);
for(var i = 0; i < tags.length; i++) {
var parts = tags[i].trim().split(/\:/);
tagObject[parts[0]] = parts[1];
}
// Set the preferences tags
dbOptions.read_preference_tags.push(tagObject);
break;
default:
break;
}
});
// No tags: should be null (not [])
if(dbOptions.read_preference_tags.length === 0) {
dbOptions.read_preference_tags = null;
}
// Validate if there are an invalid write concern combinations
if((dbOptions.w == -1 || dbOptions.w == 0) && (
dbOptions.journal == true
|| dbOptions.fsync == true
|| dbOptions.safe == true)) throw new Error("w set to -1 or 0 cannot be combined with safe/w/journal/fsync")
// If no read preference set it to primary
if(!dbOptions.read_preference) dbOptions.read_preference = 'primary';
// Add servers to result
object.servers = servers;
// Returned parsed object
return object;
}
var QueryCommand = require('./commands/query_command').QueryCommand,
GetMoreCommand = require('./commands/get_more_command').GetMoreCommand,
KillCursorCommand = require('./commands/kill_cursor_command').KillCursorCommand,
Long = require('bson').Long,
ReadPreference = require('./connection/read_preference').ReadPreference,
CursorStream = require('./cursorstream'),
timers = require('timers'),
utils = require('./utils');
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('./utils').processor();
/**
* Constructor for a cursor object that handles all the operations on query result
* using find. This cursor object is unidirectional and cannot traverse backwards. Clients should not be creating a cursor directly,
* but use find to acquire a cursor. (INTERNAL TYPE)
*
* Options
* - **skip** {Number} skip number of documents to skip.
* - **limit** {Number}, limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1.
* - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
* - **hint** {Object}, hint force the query to use a specific index.
* - **explain** {Boolean}, explain return the explaination of the query.
* - **snapshot** {Boolean}, snapshot Snapshot mode assures no duplicates are returned.
* - **timeout** {Boolean}, timeout allow the query to timeout.
* - **tailable** {Boolean}, tailable allow the cursor to be tailable.
* - **awaitdata** {Boolean}, awaitdata allow the cursor to wait for data, only applicable for tailable cursor.
* - **batchSize** {Number}, batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database.
* - **raw** {Boolean}, raw return all query documents as raw buffers (default false).
* - **read** {Boolean}, read specify override of read from source (primary/secondary).
* - **returnKey** {Boolean}, returnKey only return the index key.
* - **maxScan** {Number}, maxScan limit the number of items to scan.
* - **min** {Number}, min set index bounds.
* - **max** {Number}, max set index bounds.
* - **showDiskLoc** {Boolean}, showDiskLoc show disk location of results.
* - **comment** {String}, comment you can put a $comment field on a query to make looking in the profiler logs simpler.
* - **numberOfRetries** {Number}, numberOfRetries if using awaidata specifies the number of times to retry on timeout.
* - **dbName** {String}, dbName override the default dbName.
* - **tailableRetryInterval** {Number}, tailableRetryInterval specify the miliseconds between getMores on tailable cursor.
* - **exhaust** {Boolean}, exhaust have the server send all the documents at once as getMore packets.
* - **partial** {Boolean}, partial have the sharded system return a partial result from mongos.
*
* @class Represents a Cursor.
* @param {Db} db the database object to work with.
* @param {Collection} collection the collection to query.
* @param {Object} selector the query selector.
* @param {Object} fields an object containing what fields to include or exclude from objects returned.
* @param {Object} [options] additional options for the collection.
*/
function Cursor(db, collection, selector, fields, options) {
this.db = db;
this.collection = collection;
this.selector = selector;
this.fields = fields;
options = !options ? {} : options;
this.skipValue = options.skip == null ? 0 : options.skip;
this.limitValue = options.limit == null ? 0 : options.limit;
this.sortValue = options.sort;
this.hint = options.hint;
this.explainValue = options.explain;
this.snapshot = options.snapshot;
this.timeout = options.timeout == null ? true : options.timeout;
this.tailable = options.tailable;
this.awaitdata = options.awaitdata;
this.numberOfRetries = options.numberOfRetries == null ? 5 : options.numberOfRetries;
this.currentNumberOfRetries = this.numberOfRetries;
this.batchSizeValue = options.batchSize == null ? 0 : options.batchSize;
this.raw = options.raw == null ? false : options.raw;
this.read = options.read == null ? ReadPreference.PRIMARY : options.read;
this.returnKey = options.returnKey;
this.maxScan = options.maxScan;
this.min = options.min;
this.max = options.max;
this.showDiskLoc = options.showDiskLoc;
this.comment = options.comment;
this.tailableRetryInterval = options.tailableRetryInterval || 100;
this.exhaust = options.exhaust || false;
this.partial = options.partial || false;
this.slaveOk = options.slaveOk || false;
this.totalNumberOfRecords = 0;
this.items = [];
this.cursorId = Long.fromInt(0);
// This name
this.dbName = options.dbName;
// State variables for the cursor
this.state = Cursor.INIT;
// Keep track of the current query run
this.queryRun = false;
this.getMoreTimer = false;
// If we are using a specific db execute against it
if(this.dbName != null) {
this.collectionName = this.dbName + "." + this.collection.collectionName;
} else {
this.collectionName = (this.db.databaseName ? this.db.databaseName + "." : '') + this.collection.collectionName;
}
};
/**
* Resets this cursor to its initial state. All settings like the query string,
* tailable, batchSizeValue, skipValue and limits are preserved.
*
* @return {Cursor} returns itself with rewind applied.
* @api public
*/
Cursor.prototype.rewind = function() {
var self = this;
if (self.state != Cursor.INIT) {
if (self.state != Cursor.CLOSED) {
self.close(function() {});
}
self.numberOfReturned = 0;
self.totalNumberOfRecords = 0;
self.items = [];
self.cursorId = Long.fromInt(0);
self.state = Cursor.INIT;
self.queryRun = false;
}
return self;
};
/**
* Returns an array of documents. The caller is responsible for making sure that there
* is enough memory to store the results. Note that the array only contain partial
* results when this cursor had been previouly accessed. In that case,
* cursor.rewind() can be used to reset the cursor.
*
* @param {Function} callback This will be called after executing this method successfully. The first parameter will contain the Error object if an error occured, or null otherwise. The second parameter will contain an array of BSON deserialized objects as a result of the query.
* @return {null}
* @api public
*/
Cursor.prototype.toArray = function(callback) {
var self = this;
if(!callback) {
throw new Error('callback is mandatory');
}
if(this.tailable) {
callback(new Error("Tailable cursor cannot be converted to array"), null);
} else if(this.state != Cursor.CLOSED) {
// return toArrayExhaust(self, callback);
// If we are using exhaust we can't use the quick fire method
if(self.exhaust) return toArrayExhaust(self, callback);
// Quick fire using trampoline to avoid nextTick
self.nextObject({noReturn: true}, function(err, result) {
if(err) return callback(utils.toError(err), null);
if(self.cursorId.toString() == "0") {
self.state = Cursor.CLOSED;
return callback(null, self.items);
}
// Let's issue getMores until we have no more records waiting
getAllByGetMore(self, function(err, done) {
self.state = Cursor.CLOSED;
if(err) return callback(utils.toError(err), null);
// Let's release the internal list
var items = self.items;
self.items = null;
// Return all the items
callback(null, items);
});
})
} else {
callback(new Error("Cursor is closed"), null);
}
}
var toArrayExhaust = function(self, callback) {
var items = [];
self.each(function(err, item) {
if(err != null) {
return callback(utils.toError(err), null);
}
if(item != null && Array.isArray(items)) {
items.push(item);
} else {
var resultItems = items;
items = null;
self.items = [];
callback(null, resultItems);
}
});
}
var getAllByGetMore = function(self, callback) {
getMore(self, {noReturn: true}, function(err, result) {
if(err) return callback(utils.toError(err));
if(result == null) return callback(null, null);
if(self.cursorId.toString() == "0") return callback(null, null);
getAllByGetMore(self, callback);
})
}
/**
* Iterates over all the documents for this cursor. As with **{cursor.toArray}**,
* not all of the elements will be iterated if this cursor had been previouly accessed.
* In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike
* **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements
* at any given time if batch size is specified. Otherwise, the caller is responsible
* for making sure that the entire result can fit the memory.
*
* @param {Function} callback this will be called for while iterating every document of the query result. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the document.
* @return {null}
* @api public
*/
Cursor.prototype.each = function(callback) {
var self = this;
var fn;
if (!callback) {
throw new Error('callback is mandatory');
}
if(this.state != Cursor.CLOSED) {
// If we are using exhaust we can't use the quick fire method
if(self.exhaust) return eachExhaust(self, callback);
// Quick fire using trampoline to avoid nextTick
if(this.items.length > 0) {
// Trampoline all the entries
while(fn = loop(self, callback)) fn(self, callback);
// Call each again
self.each(callback);
} else {
self.nextObject(function(err, item) {
if(err) {
self.state = Cursor.CLOSED;
return callback(utils.toError(err), item);
}
if(item == null) return callback(null, null);
callback(null, item);
self.each(callback);
})
}
} else {
callback(new Error("Cursor is closed"), null);
}
}
// Special for exhaust command as we don't initiate the actual result sets
// the server just sends them as they arrive meaning we need to get the IO event
// loop happen so we can receive more data from the socket or we return to early
// after the first fetch and loose all the incoming getMore's automatically issued
// from the server.
var eachExhaust = function(self, callback) {
//FIX: stack overflow (on deep callback) (cred: https://github.com/limp/node-mongodb-native/commit/27da7e4b2af02035847f262b29837a94bbbf6ce2)
processor(function(){
// Fetch the next object until there is no more objects
self.nextObject(function(err, item) {
if(err != null) return callback(err, null);
if(item != null) {
callback(null, item);
eachExhaust(self, callback);
} else {
// Close the cursor if done
self.state = Cursor.CLOSED;
callback(err, null);
}
});
});
}
// Trampoline emptying the number of retrieved items
// without incurring a nextTick operation
var loop = function(self, callback) {
// No more items we are done
if(self.items.length == 0) return;
// Get the next document
var doc = self.items.shift();
// Callback
callback(null, doc);
// Loop
return loop;
}
/**
* Determines how many result the query for this cursor will return
*
* @param {Boolean} applySkipLimit if set to true will apply the skip and limits set on the cursor. Defaults to false.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the number of results or null if an error occured.
* @return {null}
* @api public
*/
Cursor.prototype.count = function(applySkipLimit, callback) {
if(typeof applySkipLimit == 'function') {
callback = applySkipLimit;
applySkipLimit = false;
}
var options = {};
if(applySkipLimit) {
if(typeof this.skipValue == 'number') options.skip = this.skipValue;
if(typeof this.limitValue == 'number') options.limit = this.limitValue;
}
// Call count command
this.collection.count(this.selector, options, callback);
};
/**
* Sets the sort parameter of this cursor to the given value.
*
* This method has the following method signatures:
* (keyOrList, callback)
* (keyOrList, direction, callback)
*
* @param {String|Array|Object} keyOrList This can be a string or an array. If passed as a string, the string will be the field to sort. If passed an array, each element will represent a field to be sorted and should be an array that contains the format [string, direction].
* @param {String|Number} direction this determines how the results are sorted. "asc", "ascending" or 1 for asceding order while "desc", "desceding or -1 for descending order. Note that the strings are case insensitive.
* @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution.
* @return {Cursor} an instance of this object.
* @api public
*/
Cursor.prototype.sort = function(keyOrList, direction, callback) {
callback = callback || function(){};
if(typeof direction === "function") { callback = direction; direction = null; }
if(this.tailable) {
callback(new Error("Tailable cursor doesn't support sorting"), null);
} else if(this.queryRun == true || this.state == Cursor.CLOSED) {
callback(new Error("Cursor is closed"), null);
} else {
var order = keyOrList;
if(direction != null) {
order = [[keyOrList, direction]];
}
this.sortValue = order;
callback(null, this);
}
return this;
};
/**
* Sets the limit parameter of this cursor to the given value.
*
* @param {Number} limit the new limit.
* @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution.
* @return {Cursor} an instance of this object.
* @api public
*/
Cursor.prototype.limit = function(limit, callback) {
if(this.tailable) {
if(callback) {
callback(new Error("Tailable cursor doesn't support limit"), null);
} else {
throw new Error("Tailable cursor doesn't support limit");
}
} else if(this.queryRun == true || this.state == Cursor.CLOSED) {
if(callback) {
callback(new Error("Cursor is closed"), null);
} else {
throw new Error("Cursor is closed");
}
} else {
if(limit != null && limit.constructor != Number) {
if(callback) {
callback(new Error("limit requires an integer"), null);
} else {
throw new Error("limit requires an integer");
}
} else {
this.limitValue = limit;
if(callback) return callback(null, this);
}
}
return this;
};
/**
* Sets the read preference for the cursor
*
* @param {String} the read preference for the cursor, one of Server.READ_PRIMARY, Server.READ_SECONDARY, Server.READ_SECONDARY_ONLY
* @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the read preference given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution.
* @return {Cursor} an instance of this object.
* @api public
*/
Cursor.prototype.setReadPreference = function(readPreference, tags, callback) {
if(typeof tags == 'function') callback = tags;
var _mode = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference;
if(this.queryRun == true || this.state == Cursor.CLOSED) {
if(callback == null) throw new Error("Cannot change read preference on executed query or closed cursor");
callback(new Error("Cannot change read preference on executed query or closed cursor"));
} else if(_mode != null && _mode != 'primary'
&& _mode != 'secondaryOnly' && _mode != 'secondary'
&& _mode != 'nearest' && _mode != 'primaryPreferred' && _mode != 'secondaryPreferred') {
if(callback == null) throw new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported");
callback(new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported"));
} else {
this.read = readPreference;
if(callback != null) callback(null, this);
}
return this;
}
/**
* Sets the skip parameter of this cursor to the given value.
*
* @param {Number} skip the new skip value.
* @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the skip value given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution.
* @return {Cursor} an instance of this object.
* @api public
*/
Cursor.prototype.skip = function(skip, callback) {
callback = callback || function(){};
if(this.tailable) {
callback(new Error("Tailable cursor doesn't support skip"), null);
} else if(this.queryRun == true || this.state == Cursor.CLOSED) {
callback(new Error("Cursor is closed"), null);
} else {
if(skip != null && skip.constructor != Number) {
callback(new Error("skip requires an integer"), null);
} else {
this.skipValue = skip;
callback(null, this);
}
}
return this;
};
/**
* Sets the batch size parameter of this cursor to the given value.
*
* @param {Number} batchSize the new batch size.
* @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the batchSize given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution.
* @return {Cursor} an instance of this object.
* @api public
*/
Cursor.prototype.batchSize = function(batchSize, callback) {
if(this.state == Cursor.CLOSED) {
if(callback != null) {
return callback(new Error("Cursor is closed"), null);
} else {
throw new Error("Cursor is closed");
}
} else if(batchSize != null && batchSize.constructor != Number) {
if(callback != null) {
return callback(new Error("batchSize requires an integer"), null);
} else {
throw new Error("batchSize requires an integer");
}
} else {
this.batchSizeValue = batchSize;
if(callback != null) return callback(null, this);
}
return this;
};
/**
* The limit used for the getMore command
*
* @return {Number} The number of records to request per batch.
* @ignore
* @api private
*/
var limitRequest = function(self) {
var requestedLimit = self.limitValue;
var absLimitValue = Math.abs(self.limitValue);
var absBatchValue = Math.abs(self.batchSizeValue);
if(absLimitValue > 0) {
if (absBatchValue > 0) {
requestedLimit = Math.min(absLimitValue, absBatchValue);
}
} else {
requestedLimit = self.batchSizeValue;
}
return requestedLimit;
};
/**
* Generates a QueryCommand object using the parameters of this cursor.
*
* @return {QueryCommand} The command object
* @ignore
* @api private
*/
var generateQueryCommand = function(self) {
// Unpack the options
var queryOptions = QueryCommand.OPTS_NONE;
if(!self.timeout) {
queryOptions |= QueryCommand.OPTS_NO_CURSOR_TIMEOUT;
}
if(self.tailable != null) {
queryOptions |= QueryCommand.OPTS_TAILABLE_CURSOR;
self.skipValue = self.limitValue = 0;
// if awaitdata is set
if(self.awaitdata != null) {
queryOptions |= QueryCommand.OPTS_AWAIT_DATA;
}
}
if(self.exhaust) {
queryOptions |= QueryCommand.OPTS_EXHAUST;
}
// Unpack the read preference to set slave ok correctly
var read = self.read instanceof ReadPreference ? self.read.mode : self.read;
// if(self.read == 'secondary')
if(read == ReadPreference.PRIMARY_PREFERRED
|| read == ReadPreference.SECONDARY
|| read == ReadPreference.SECONDARY_PREFERRED
|| read == ReadPreference.NEAREST) {
queryOptions |= QueryCommand.OPTS_SLAVE;
}
// Override slaveOk from the user
if(self.slaveOk) {
queryOptions |= QueryCommand.OPTS_SLAVE;
}
if(self.partial) {
queryOptions |= QueryCommand.OPTS_PARTIAL;
}
// limitValue of -1 is a special case used by Db#eval
var numberToReturn = self.limitValue == -1 ? -1 : limitRequest(self);
// Check if we need a special selector
if(self.sortValue != null || self.explainValue != null || self.hint != null || self.snapshot != null
|| self.returnKey != null || self.maxScan != null || self.min != null || self.max != null
|| self.showDiskLoc != null || self.comment != null) {
// Build special selector
var specialSelector = {'$query':self.selector};
if(self.sortValue != null) specialSelector['orderby'] = utils.formattedOrderClause(self.sortValue);
if(self.hint != null && self.hint.constructor == Object) specialSelector['$hint'] = self.hint;
if(self.snapshot != null) specialSelector['$snapshot'] = true;
if(self.returnKey != null) specialSelector['$returnKey'] = self.returnKey;
if(self.maxScan != null) specialSelector['$maxScan'] = self.maxScan;
if(self.min != null) specialSelector['$min'] = self.min;
if(self.max != null) specialSelector['$max'] = self.max;
if(self.showDiskLoc != null) specialSelector['$showDiskLoc'] = self.showDiskLoc;
if(self.comment != null) specialSelector['$comment'] = self.comment;
// If we have explain set only return a single document with automatic cursor close
if(self.explainValue != null) {
numberToReturn = (-1)*Math.abs(numberToReturn);
specialSelector['$explain'] = true;
}
// Return the query
return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, specialSelector, self.fields);
} else {
return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, self.selector, self.fields);
}
};
/**
* @return {Object} Returns an object containing the sort value of this cursor with
* the proper formatting that can be used internally in this cursor.
* @ignore
* @api private
*/
Cursor.prototype.formattedOrderClause = function() {
return utils.formattedOrderClause(this.sortValue);
};
/**
* Converts the value of the sort direction into its equivalent numerical value.
*
* @param sortDirection {String|number} Range of acceptable values:
* 'ascending', 'descending', 'asc', 'desc', 1, -1
*
* @return {number} The equivalent numerical value
* @throws Error if the given sortDirection is invalid
* @ignore
* @api private
*/
Cursor.prototype.formatSortValue = function(sortDirection) {
return utils.formatSortValue(sortDirection);
};
/**
* Gets the next document from the cursor.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results.
* @api public
*/
Cursor.prototype.nextObject = function(options, callback) {
var self = this;
if(typeof options == 'function') {
callback = options;
options = {};
}
if(self.state == Cursor.INIT) {
var cmd;
try {
cmd = generateQueryCommand(self);
} catch (err) {
return callback(err, null);
}
var queryOptions = {exhaust: self.exhaust, raw:self.raw, read:self.read, connection:self.connection};
// Execute command
var commandHandler = function(err, result) {
// If on reconnect, the command got given a different connection, switch
// the whole cursor to it.
self.connection = queryOptions.connection;
self.state = Cursor.OPEN;
if(err != null && result == null) return callback(utils.toError(err), null);
if(err == null && (result == null || result.documents == null || !Array.isArray(result.documents))) {
return self.close(function() {callback(new Error("command failed to return results"), null);});
}
if(err == null && result && result.documents[0] && result.documents[0]['$err']) {
return self.close(function() {callback(utils.toError(result.documents[0]['$err']), null);});
}
self.queryRun = true;
self.state = Cursor.OPEN; // Adjust the state of the cursor
self.cursorId = result.cursorId;
self.totalNumberOfRecords = result.numberReturned;
// Add the new documents to the list of items, using forloop to avoid
// new array allocations and copying
for(var i = 0; i < result.documents.length; i++) {
self.items.push(result.documents[i]);
}
// If we have noReturn set just return (not modifying the internal item list)
// used for toArray
if(options.noReturn) {
return callback(null, true);
}
// Ignore callbacks until the cursor is dead for exhausted
if(self.exhaust && result.cursorId.toString() == "0") {
self.nextObject(callback);
} else if(self.exhaust == false || self.exhaust == null) {
self.nextObject(callback);
}
};
// If we have no connection set on this cursor check one out
if(self.connection == null) {
try {
self.connection = self.db.serverConfig.checkoutReader(this.read);
// Add to the query options
queryOptions.connection = self.connection;
} catch(err) {
return callback(utils.toError(err), null);
}
}
// Execute the command
self.db._executeQueryCommand(cmd, queryOptions, commandHandler);
// Set the command handler to null
commandHandler = null;
} else if(self.items.length) {
callback(null, self.items.shift());
} else if(self.cursorId.greaterThan(Long.fromInt(0))) {
getMore(self, callback);
} else {
// Force cursor to stay open
return self.close(function() {callback(null, null);});
}
}
/**
* Gets more results from the database if any.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results.
* @ignore
* @api private
*/
var getMore = function(self, options, callback) {
var limit = 0;
if(typeof options == 'function') {
callback = options;
options = {};
}
if(self.state == Cursor.GET_MORE) return callback(null, null);
// Set get more in progress
self.state = Cursor.GET_MORE;
// Set options
if (!self.tailable && self.limitValue > 0) {
limit = self.limitValue - self.totalNumberOfRecords;
if (limit < 1) {
self.close(function() {callback(null, null);});
return;
}
}
try {
var getMoreCommand = new GetMoreCommand(
self.db
, self.collectionName
, limitRequest(self)
, self.cursorId
);
// Set up options
var command_options = {read: self.read, raw: self.raw, connection:self.connection };
// Execute the command
self.db._executeQueryCommand(getMoreCommand, command_options, function(err, result) {
var cbValue;
// Get more done
self.state = Cursor.OPEN;
if(err != null) {
self.state = Cursor.CLOSED;
return callback(utils.toError(err), null);
}
// Ensure we get a valid result
if(!result || !result.documents) {
self.state = Cursor.CLOSED;
return callback(utils.toError("command failed to return results"), null)
}
// If the QueryFailure flag is set
if((result.responseFlag & (1 << 1)) != 0) {
self.state = Cursor.CLOSED;
return callback(utils.toError("QueryFailure flag set on getmore command"), null);
}
try {
var isDead = 1 === result.responseFlag && result.cursorId.isZero();
self.cursorId = result.cursorId;
self.totalNumberOfRecords += result.numberReturned;
// Determine if there's more documents to fetch
if(result.numberReturned > 0) {
if (self.limitValue > 0) {
var excessResult = self.totalNumberOfRecords - self.limitValue;
if (excessResult > 0) {
result.documents.splice(-1 * excessResult, excessResult);
}
}
// Reset the tries for awaitdata if we are using it
self.currentNumberOfRetries = self.numberOfRetries;
// Get the documents
for(var i = 0; i < result.documents.length; i++) {
self.items.push(result.documents[i]);
}
// Don's shift a document out as we need it for toArray
if(options.noReturn) {
cbValue = true;
} else {
cbValue = self.items.shift();
}
} else if(self.tailable && !isDead && self.awaitdata) {
// Excute the tailable cursor once more, will timeout after ~4 sec if awaitdata used
self.currentNumberOfRetries = self.currentNumberOfRetries - 1;
if(self.currentNumberOfRetries == 0) {
self.close(function() {
callback(new Error("tailable cursor timed out"), null);
});
} else {
getMore(self, callback);
}
} else if(self.tailable && !isDead) {
self.getMoreTimer = setTimeout(function() { getMore(self, callback); }, self.tailableRetryInterval);
} else {
self.close(function() {callback(null, null); });
}
result = null;
} catch(err) {
callback(utils.toError(err), null);
}
if (cbValue != null) callback(null, cbValue);
});
getMoreCommand = null;
} catch(err) {
// Get more done
self.state = Cursor.OPEN;
var handleClose = function() {
callback(utils.toError(err), null);
};
self.close(handleClose);
handleClose = null;
}
}
/**
* Gets a detailed information about how the query is performed on this cursor and how
* long it took the database to process it.
*
* @param {Function} callback this will be called after executing this method. The first parameter will always be null while the second parameter will be an object containing the details.
* @api public
*/
Cursor.prototype.explain = function(callback) {
var limit = (-1)*Math.abs(this.limitValue);
// Create a new cursor and fetch the plan
var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, {
skip: this.skipValue
, limit:limit
, sort: this.sortValue
, hint: this.hint
, explain: true
, snapshot: this.snapshot
, timeout: this.timeout
, tailable: this.tailable
, batchSize: this.batchSizeValue
, slaveOk: this.slaveOk
, raw: this.raw
, read: this.read
, returnKey: this.returnKey
, maxScan: this.maxScan
, min: this.min
, max: this.max
, showDiskLoc: this.showDiskLoc
, comment: this.comment
, awaitdata: this.awaitdata
, numberOfRetries: this.numberOfRetries
, dbName: this.dbName
});
// Fetch the explaination document
cursor.nextObject(function(err, item) {
if(err != null) return callback(utils.toError(err), null);
// close the cursor
cursor.close(function(err, result) {
if(err != null) return callback(utils.toError(err), null);
callback(null, item);
});
});
};
/**
* Returns a Node ReadStream interface for this cursor.
*
* Options
* - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting.
*
* @return {CursorStream} returns a stream object.
* @api public
*/
Cursor.prototype.stream = function stream(options) {
return new CursorStream(this, options);
}
/**
* Close the cursor.
*
* @param {Function} callback this will be called after executing this method. The first parameter will always contain null while the second parameter will contain a reference to this cursor.
* @return {null}
* @api public
*/
Cursor.prototype.close = function(callback) {
var self = this
this.getMoreTimer && clearTimeout(this.getMoreTimer);
// Close the cursor if not needed
if(this.cursorId instanceof Long && this.cursorId.greaterThan(Long.fromInt(0))) {
try {
var command = new KillCursorCommand(this.db, [this.cursorId]);
// Added an empty callback to ensure we don't throw any null exceptions
this.db._executeQueryCommand(command, {read:self.read, raw:self.raw, connection:self.connection});
} catch(err) {}
}
// Null out the connection
self.connection = null;
// Reset cursor id
this.cursorId = Long.fromInt(0);
// Set to closed status
this.state = Cursor.CLOSED;
if(callback) {
callback(null, self);
self.items = [];
}
return this;
};
/**
* Check if the cursor is closed or open.
*
* @return {Boolean} returns the state of the cursor.
* @api public
*/
Cursor.prototype.isClosed = function() {
return this.state == Cursor.CLOSED ? true : false;
};
/**
* Init state
*
* @classconstant INIT
**/
Cursor.INIT = 0;
/**
* Cursor open
*
* @classconstant OPEN
**/
Cursor.OPEN = 1;
/**
* Cursor closed
*
* @classconstant CLOSED
**/
Cursor.CLOSED = 2;
/**
* Cursor performing a get more
*
* @classconstant OPEN
**/
Cursor.GET_MORE = 3;
/**
* @ignore
* @api private
*/
exports.Cursor = Cursor;
var timers = require('timers');
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('./utils').processor();
/**
* Module dependecies.
*/
var Stream = require('stream').Stream;
/**
* CursorStream
*
* Returns a stream interface for the **cursor**.
*
* Options
* - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting.
*
* Events
* - **data** {function(item) {}} the data event triggers when a document is ready.
* - **error** {function(err) {}} the error event triggers if an error happens.
* - **close** {function() {}} the end event triggers when there is no more documents available.
*
* @class Represents a CursorStream.
* @param {Cursor} cursor a cursor object that the stream wraps.
* @return {Stream}
*/
function CursorStream(cursor, options) {
if(!(this instanceof CursorStream)) return new CursorStream(cursor);
options = options ? options : {};
Stream.call(this);
this.readable = true;
this.paused = false;
this._cursor = cursor;
this._destroyed = null;
this.options = options;
// give time to hook up events
var self = this;
process.nextTick(function() {
self._init();
});
}
/**
* Inherit from Stream
* @ignore
* @api private
*/
CursorStream.prototype.__proto__ = Stream.prototype;
/**
* Flag stating whether or not this stream is readable.
*/
CursorStream.prototype.readable;
/**
* Flag stating whether or not this stream is paused.
*/
CursorStream.prototype.paused;
/**
* Initialize the cursor.
* @ignore
* @api private
*/
CursorStream.prototype._init = function () {
if (this._destroyed) return;
this._next();
}
/**
* Pull the next document from the cursor.
* @ignore
* @api private
*/
CursorStream.prototype._next = function () {
if(this.paused || this._destroyed) return;
var self = this;
// Get the next object
processor(function() {
if(self.paused || self._destroyed) return;
self._cursor.nextObject(function (err, doc) {
self._onNextObject(err, doc);
});
});
}
/**
* Handle each document as its returned from the cursor.
* @ignore
* @api private
*/
CursorStream.prototype._onNextObject = function (err, doc) {
if(err) return this.destroy(err);
// when doc is null we hit the end of the cursor
if(!doc && (this._cursor.state == 1 || this._cursor.state == 2)) {
this.emit('end')
return this.destroy();
} else if(doc) {
var data = typeof this.options.transform == 'function' ? this.options.transform(doc) : doc;
this.emit('data', data);
this._next();
}
}
/**
* Pauses the stream.
*
* @api public
*/
CursorStream.prototype.pause = function () {
this.paused = true;
}
/**
* Resumes the stream.
*
* @api public
*/
CursorStream.prototype.resume = function () {
var self = this;
// Don't do anything if we are not paused
if(!this.paused) return;
if(!this._cursor.state == 3) return;
process.nextTick(function() {
self.paused = false;
// Only trigger more fetching if the cursor is open
self._next();
})
}
/**
* Destroys the stream, closing the underlying
* cursor. No more events will be emitted.
*
* @api public
*/
CursorStream.prototype.destroy = function (err) {
if (this._destroyed) return;
this._destroyed = true;
this.readable = false;
this._cursor.close();
if(err) {
this.emit('error', err);
}
this.emit('close');
}
// TODO - maybe implement the raw option to pass binary?
//CursorStream.prototype.setEncoding = function () {
//}
module.exports = exports = CursorStream;
/**
* Module dependencies.
* @ignore
*/
var QueryCommand = require('./commands/query_command').QueryCommand
, DbCommand = require('./commands/db_command').DbCommand
, MongoReply = require('./responses/mongo_reply').MongoReply
, Admin = require('./admin').Admin
, Collection = require('./collection').Collection
, Server = require('./connection/server').Server
, ReplSet = require('./connection/repl_set/repl_set').ReplSet
, ReadPreference = require('./connection/read_preference').ReadPreference
, Mongos = require('./connection/mongos').Mongos
, Cursor = require('./cursor').Cursor
, EventEmitter = require('events').EventEmitter
, inherits = require('util').inherits
, crypto = require('crypto')
, timers = require('timers')
, utils = require('./utils')
, mongodb_cr_authenticate = require('./auth/mongodb_cr.js').authenticate
, mongodb_gssapi_authenticate = require('./auth/mongodb_gssapi.js').authenticate
, mongodb_sspi_authenticate = require('./auth/mongodb_sspi.js').authenticate
, mongodb_plain_authenticate = require('./auth/mongodb_plain.js').authenticate;
var hasKerberos = false;
// Check if we have a the kerberos library
try {
require('kerberos');
hasKerberos = true;
} catch(err) {}
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('./utils').processor();
/**
* Create a new Db instance.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **native_parser** {Boolean, default:false}, use c++ bson parser.
* - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client.
* - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation.
* - **serializeFunctions** {Boolean, default:false}, serialize functions.
* - **raw** {Boolean, default:false}, peform operations using raw bson buffers.
* - **recordQueryStats** {Boolean, default:false}, record query statistics during execution.
* - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries.
* - **numberOfRetries** {Number, default:5}, number of retries off connection.
* - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**.
* - **slaveOk** {Number, default:null}, force setting of SlaveOk flag on queries (only use when explicitly connecting to a secondary server).
* - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @class Represents a Db
* @param {String} databaseName name of the database.
* @param {Object} serverConfig server config object.
* @param {Object} [options] additional options for the collection.
*/
function Db(databaseName, serverConfig, options) {
if(!(this instanceof Db)) return new Db(databaseName, serverConfig, options);
EventEmitter.call(this);
var self = this;
this.databaseName = databaseName;
this.serverConfig = serverConfig;
this.options = options == null ? {} : options;
// State to check against if the user force closed db
this._applicationClosed = false;
// Fetch the override flag if any
var overrideUsedFlag = this.options['override_used_flag'] == null ? false : this.options['override_used_flag'];
// Verify that nobody is using this config
if(!overrideUsedFlag && this.serverConfig != null && typeof this.serverConfig == 'object' && this.serverConfig._isUsed && this.serverConfig._isUsed()) {
throw new Error("A Server or ReplSet instance cannot be shared across multiple Db instances");
} else if(!overrideUsedFlag && typeof this.serverConfig == 'object'){
// Set being used
this.serverConfig._used = true;
}
// Allow slaveOk override
this.slaveOk = this.options["slave_ok"] == null ? false : this.options["slave_ok"];
this.slaveOk = this.options["slaveOk"] == null ? this.slaveOk : this.options["slaveOk"];
// Ensure we have a valid db name
validateDatabaseName(databaseName);
// Contains all the connections for the db
try {
this.native_parser = this.options.native_parser;
// The bson lib
var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : require('bson').BSONPure;
// Fetch the serializer object
var BSON = bsonLib.BSON;
// Create a new instance
this.bson = new BSON([bsonLib.Long, bsonLib.ObjectID, bsonLib.Binary, bsonLib.Code, bsonLib.DBRef, bsonLib.Symbol, bsonLib.Double, bsonLib.Timestamp, bsonLib.MaxKey, bsonLib.MinKey]);
this.bson.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs;
// Backward compatibility to access types
this.bson_deserializer = bsonLib;
this.bson_serializer = bsonLib;
// Add any overrides to the serializer and deserializer
this.bson_deserializer.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs;
} catch (err) {
// If we tried to instantiate the native driver
var msg = "Native bson parser not compiled, please compile "
+ "or avoid using native_parser=true";
throw Error(msg);
}
// Internal state of the server
this._state = 'disconnected';
this.pkFactory = this.options.pk == null ? bsonLib.ObjectID : this.options.pk;
this.forceServerObjectId = this.options.forceServerObjectId != null ? this.options.forceServerObjectId : false;
// Added safe
this.safe = this.options.safe == null ? false : this.options.safe;
// If we have not specified a "safe mode" we just print a warning to the console
if(this.options.safe == null && this.options.w == null && this.options.journal == null && this.options.fsync == null) {
console.log("========================================================================================");
console.log("= Please ensure that you set the default write concern for the database by setting =");
console.log("= one of the options =");
console.log("= =");
console.log("= w: (value of > -1 or the string 'majority'), where < 1 means =");
console.log("= no write acknowlegement =");
console.log("= journal: true/false, wait for flush to journal before acknowlegement =");
console.log("= fsync: true/false, wait for flush to file system before acknowlegement =");
console.log("= =");
console.log("= For backward compatibility safe is still supported and =");
console.log("= allows values of [true | false | {j:true} | {w:n, wtimeout:n} | {fsync:true}] =");
console.log("= the default value is false which means the driver receives does not =");
console.log("= return the information of the success/error of the insert/update/remove =");
console.log("= =");
console.log("= ex: new Db(new Server('localhost', 27017), {safe:false}) =");
console.log("= =");
console.log("= http://www.mongodb.org/display/DOCS/getLastError+Command =");
console.log("= =");
console.log("= The default of no acknowlegement will change in the very near future =");
console.log("= =");
console.log("= This message will disappear when the default safe is set on the driver Db =");
console.log("========================================================================================");
}
// Internal states variables
this.notReplied ={};
this.isInitializing = true;
this.openCalled = false;
// Command queue, keeps a list of incoming commands that need to be executed once the connection is up
this.commands = [];
// Set up logger
this.logger = this.options.logger != null
&& (typeof this.options.logger.debug == 'function')
&& (typeof this.options.logger.error == 'function')
&& (typeof this.options.logger.log == 'function')
? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}};
// Associate the logger with the server config
this.serverConfig.logger = this.logger;
if(this.serverConfig.strategyInstance) this.serverConfig.strategyInstance.logger = this.logger;
this.tag = new Date().getTime();
// Just keeps list of events we allow
this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[]};
// Controls serialization options
this.serializeFunctions = this.options.serializeFunctions != null ? this.options.serializeFunctions : false;
// Raw mode
this.raw = this.options.raw != null ? this.options.raw : false;
// Record query stats
this.recordQueryStats = this.options.recordQueryStats != null ? this.options.recordQueryStats : false;
// If we have server stats let's make sure the driver objects have it enabled
if(this.recordQueryStats == true) {
this.serverConfig.enableRecordQueryStats(true);
}
// Retry information
this.retryMiliSeconds = this.options.retryMiliSeconds != null ? this.options.retryMiliSeconds : 1000;
this.numberOfRetries = this.options.numberOfRetries != null ? this.options.numberOfRetries : 60;
// Set default read preference if any
this.readPreference = this.options.readPreference;
// Set read preference on serverConfig if none is set
// but the db one was
if(this.serverConfig.options.readPreference == null
&& this.readPreference != null) {
this.serverConfig.setReadPreference(this.readPreference);
}
// Ensure we keep a reference to this db
this.serverConfig._dbStore.add(this);
};
/**
* @ignore
*/
function validateDatabaseName(databaseName) {
if(typeof databaseName !== 'string') throw new Error("database name must be a string");
if(databaseName.length === 0) throw new Error("database name cannot be the empty string");
if(databaseName == '$external') return;
var invalidChars = [" ", ".", "$", "/", "\\"];
for(var i = 0; i < invalidChars.length; i++) {
if(databaseName.indexOf(invalidChars[i]) != -1) throw new Error("database names cannot contain the character '" + invalidChars[i] + "'");
}
}
/**
* @ignore
*/
inherits(Db, EventEmitter);
/**
* Initialize the database connection.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the index information or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.open = function(callback) {
var self = this;
// Check that the user has not called this twice
if(this.openCalled) {
// Close db
this.close();
// Throw error
throw new Error("db object already connecting, open cannot be called multiple times");
}
// If we have a specified read preference
if(this.readPreference != null) this.serverConfig.setReadPreference(this.readPreference);
// Set that db has been opened
this.openCalled = true;
// Set the status of the server
self._state = 'connecting';
// Set up connections
if(self.serverConfig instanceof Server || self.serverConfig instanceof ReplSet || self.serverConfig instanceof Mongos) {
// Ensure we have the original options passed in for the server config
var connect_options = {};
for(var name in self.serverConfig.options) {
connect_options[name] = self.serverConfig.options[name]
}
connect_options.firstCall = true;
// Attempt to connect
self.serverConfig.connect(self, connect_options, function(err, result) {
if(err != null) {
// Set that db has been closed
self.openCalled = false;
// Return error from connection
return callback(err, null);
}
// Set the status of the server
self._state = 'connected';
// If we have queued up commands execute a command to trigger replays
if(self.commands.length > 0) _execute_queued_command(self);
// Callback
process.nextTick(function() {
try {
callback(null, self);
} catch(err) {
self.close();
throw err;
}
});
});
} else {
try {
callback(Error("Server parameter must be of type Server, ReplSet or Mongos"), null);
} catch(err) {
self.close();
throw err;
}
}
};
/**
* Create a new Db instance sharing the current socket connections.
*
* @param {String} dbName the name of the database we want to use.
* @return {Db} a db instance using the new database.
* @api public
*/
Db.prototype.db = function(dbName) {
// Copy the options and add out internal override of the not shared flag
var options = {};
for(var key in this.options) {
options[key] = this.options[key];
}
// Add override flag
options['override_used_flag'] = true;
// Check if the db already exists and reuse if it's the case
var db = this.serverConfig._dbStore.fetch(dbName);
// Create a new instance
if(!db) {
db = new Db(dbName, this.serverConfig, options);
}
// Return the db object
return db;
}
/**
* Close the current db connection, including all the child db instances. Emits close event if no callback is provided.
*
* @param {Boolean} [forceClose] connection can never be reused.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.close = function(forceClose, callback) {
var self = this;
// Ensure we force close all connections
this._applicationClosed = false;
if(typeof forceClose == 'function') {
callback = forceClose;
} else if(typeof forceClose == 'boolean') {
this._applicationClosed = forceClose;
}
this.serverConfig.close(function(err, result) {
// You can reuse the db as everything is shut down
self.openCalled = false;
// If we have a callback call it
if(callback) callback(err, result);
});
};
/**
* Access the Admin database
*
* @param {Function} [callback] returns the results.
* @return {Admin} the admin db object.
* @api public
*/
Db.prototype.admin = function(callback) {
if(callback == null) return new Admin(this);
callback(null, new Admin(this));
};
/**
* Returns a cursor to all the collection information.
*
* @param {String} [collectionName] the collection name we wish to retrieve the information from.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the options or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.collectionsInfo = function(collectionName, callback) {
if(callback == null && typeof collectionName == 'function') { callback = collectionName; collectionName = null; }
// Create selector
var selector = {};
// If we are limiting the access to a specific collection name
if(collectionName != null) selector.name = this.databaseName + "." + collectionName;
// Return Cursor
// callback for backward compatibility
if(callback) {
callback(null, new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector));
} else {
return new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector);
}
};
/**
* Get the list of all collection names for the specified db
*
* Options
* - **namesOnly** {String, default:false}, Return only the full collection namespace.
*
* @param {String} [collectionName] the collection name we wish to filter by.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collection names or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.collectionNames = function(collectionName, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
collectionName = args.length ? args.shift() : null;
options = args.length ? args.shift() || {} : {};
// Ensure no breaking behavior
if(collectionName != null && typeof collectionName == 'object') {
options = collectionName;
collectionName = null;
}
// Let's make our own callback to reuse the existing collections info method
self.collectionsInfo(collectionName, function(err, cursor) {
if(err != null) return callback(err, null);
cursor.toArray(function(err, documents) {
if(err != null) return callback(err, null);
// List of result documents that have been filtered
var filtered_documents = documents.filter(function(document) {
return !(document.name.indexOf(self.databaseName) == -1 || document.name.indexOf('$') != -1);
});
// If we are returning only the names
if(options.namesOnly) {
filtered_documents = filtered_documents.map(function(document) { return document.name });
}
// Return filtered items
callback(null, filtered_documents);
});
});
};
/**
* Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you can
* can use it without a callback in the following way. var collection = db.collection('mycollection');
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **serializeFunctions** {Boolean, default:false}, serialize functions on the document.
* - **raw** {Boolean, default:false}, perform all operations using raw bson objects.
* - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation.
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **strict**, (Boolean, default:false) returns an error if the collection does not exist
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} collectionName the collection name we wish to access.
* @param {Object} [options] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collection or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.collection = function(collectionName, options, callback) {
var self = this;
if(typeof options === "function") { callback = options; options = {}; }
// Execute safe
if(options && (options.strict)) {
self.collectionNames(collectionName, function(err, collections) {
if(err != null) return callback(err, null);
if(collections.length == 0) {
return callback(new Error("Collection " + collectionName + " does not exist. Currently in safe mode."), null);
} else {
try {
var collection = new Collection(self, collectionName, self.pkFactory, options);
} catch(err) {
return callback(err, null);
}
return callback(null, collection);
}
});
} else {
try {
var collection = new Collection(self, collectionName, self.pkFactory, options);
} catch(err) {
if(callback == null) {
throw err;
} else {
return callback(err, null);
}
}
// If we have no callback return collection object
return callback == null ? collection : callback(null, collection);
}
};
/**
* Fetch all collections for the current db.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the collections or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.collections = function(callback) {
var self = this;
// Let's get the collection names
self.collectionNames(function(err, documents) {
if(err != null) return callback(err, null);
var collections = [];
documents.forEach(function(document) {
collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''), self.pkFactory));
});
// Return the collection objects
callback(null, collections);
});
};
/**
* Evaluate javascript on the server
*
* Options
* - **nolock** {Boolean, default:false}, Tell MongoDB not to block on the evaulation of the javascript.
*
* @param {Code} code javascript to execute on server.
* @param {Object|Array} [parameters] the parameters for the call.
* @param {Object} [options] the options
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from eval or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.eval = function(code, parameters, options, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
parameters = args.length ? args.shift() : parameters;
options = args.length ? args.shift() || {} : {};
var finalCode = code;
var finalParameters = [];
// If not a code object translate to one
if(!(finalCode instanceof this.bsonLib.Code)) {
finalCode = new this.bsonLib.Code(finalCode);
}
// Ensure the parameters are correct
if(parameters != null && parameters.constructor != Array && typeof parameters !== 'function') {
finalParameters = [parameters];
} else if(parameters != null && parameters.constructor == Array && typeof parameters !== 'function') {
finalParameters = parameters;
}
// Create execution selector
var selector = {'$eval':finalCode, 'args':finalParameters};
// Check if the nolock parameter is passed in
if(options['nolock']) {
selector['nolock'] = options['nolock'];
}
// Set primary read preference
options.readPreference = ReadPreference.PRIMARY;
// Execute the eval
this.collection(DbCommand.SYSTEM_COMMAND_COLLECTION).findOne(selector, options, function(err, result) {
if(err) return callback(err);
if(result && result.ok == 1) {
callback(null, result.retval);
} else if(result) {
callback(new Error("eval failed: " + result.errmsg), null); return;
} else {
callback(err, result);
}
});
};
/**
* Dereference a dbref, against a db
*
* @param {DBRef} dbRef db reference object we wish to resolve.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dereference or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.dereference = function(dbRef, callback) {
var db = this;
// If we have a db reference then let's get the db first
if(dbRef.db != null) db = this.db(dbRef.db);
// Fetch the collection and find the reference
var collection = db.collection(dbRef.namespace);
collection.findOne({'_id':dbRef.oid}, function(err, result) {
callback(err, result);
});
};
/**
* Logout user from server, fire off on all connections and remove all auth info
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.logout = function(options, callback) {
var self = this;
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
// Number of connections we need to logout from
var numberOfConnections = this.serverConfig.allRawConnections().length;
// Let's generate the logout command object
var logoutCommand = DbCommand.logoutCommand(self, {logout:1}, options);
self._executeQueryCommand(logoutCommand, {onAll:true}, function(err, result) {
// Count down
numberOfConnections = numberOfConnections - 1;
// Work around the case where the number of connections are 0
if(numberOfConnections <= 0 && typeof callback == 'function') {
var internalCallback = callback;
callback = null;
// Remove the db from auths
self.serverConfig.auth.remove(self.databaseName);
// Handle error result
utils.handleSingleCommandResultReturn(true, false, internalCallback)(err, result);
}
});
}
/**
* Authenticate a user against the server.
* authMechanism
* Options
* - **authSource** {String}, The database that the credentials are for,
* different from the name of the current DB, for example admin
* - **authMechanism** {String, default:MONGODB-CR}, The authentication mechanism to use, GSSAPI or MONGODB-CR
*
* @param {String} username username.
* @param {String} password password.
* @param {Object} [options] the options
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authentication or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.authenticate = function(username, password, options, callback) {
var self = this;
if(typeof callback === 'undefined') {
callback = options;
options = {};
}
// Set default mechanism
if(!options.authMechanism) {
options.authMechanism = 'MONGODB-CR';
} else if(options.authMechanism != 'GSSAPI'
&& options.authMechanism != 'MONGODB-CR'
&& options.authMechanism != 'PLAIN') {
return callback(new Error("only GSSAPI, PLAIN or MONGODB-CR is supported by authMechanism"));
}
// the default db to authenticate against is 'this'
// if authententicate is called from a retry context, it may be another one, like admin
var authdb = options.authdb ? options.authdb : self.databaseName;
authdb = options.authSource ? options.authSource : authdb;
// Callback
var _callback = function(err, result) {
if(self.listeners("authenticated").length > 9) {
self.emit("authenticated", err, result);
}
// Return to caller
callback(err, result);
}
// If classic auth delegate to auth command
if(options.authMechanism == 'MONGODB-CR') {
mongodb_cr_authenticate(self, username, password, authdb, options, _callback);
} else if(options.authMechanism == 'PLAIN') {
mongodb_plain_authenticate(self, username, password, options, _callback);
} else if(options.authMechanism == 'GSSAPI') {
//
// Kerberos library is not installed, throw and error
if(hasKerberos == false) {
console.log("========================================================================================");
console.log("= Please make sure that you install the Kerberos library to use GSSAPI =");
console.log("= =");
console.log("= npm install -g kerberos =");
console.log("= =");
console.log("= The Kerberos package is not installed by default for simplicities sake =");
console.log("= and needs to be global install =");
console.log("========================================================================================");
throw new Error("Kerberos library not installed");
}
if(process.platform == 'win32') {
mongodb_sspi_authenticate(self, username, password, authdb, options, _callback);
} else {
// We have the kerberos library, execute auth process
mongodb_gssapi_authenticate(self, username, password, authdb, options, _callback);
}
}
};
/**
* Add a user to the database.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} username username.
* @param {String} password password.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.addUser = function(username, password, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
// Get the error options
var errorOptions = _getWriteConcern(this, options, callback);
errorOptions.w = errorOptions.w == null ? 1 : errorOptions.w;
// Use node md5 generator
var md5 = crypto.createHash('md5');
// Generate keys used for authentication
md5.update(username + ":mongo:" + password);
var userPassword = md5.digest('hex');
// Fetch a user collection
var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION);
// Check if we are inserting the first user
collection.count({}, function(err, count) {
// We got an error (f.ex not authorized)
if(err != null) return callback(err, null);
// Check if the user exists and update i
collection.find({user: username}, {dbName: options['dbName']}).toArray(function(err, documents) {
// We got an error (f.ex not authorized)
if(err != null) return callback(err, null);
// Add command keys
var commandOptions = errorOptions;
commandOptions.dbName = options['dbName'];
commandOptions.upsert = true;
// We have a user, let's update the password or upsert if not
collection.update({user: username},{$set: {user: username, pwd: userPassword}}, commandOptions, function(err, results) {
if(count == 0 && err) {
callback(null, [{user:username, pwd:userPassword}]);
} else if(err) {
callback(err, null)
} else {
callback(null, [{user:username, pwd:userPassword}]);
}
});
});
});
};
/**
* Remove a user from a database
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} username username.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.removeUser = function(username, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
// Figure out the safe mode settings
var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe;
// Override with options passed in if applicable
safe = options != null && options['safe'] != null ? options['safe'] : safe;
// Ensure it's at least set to safe
safe = safe == null ? {w: 1} : safe;
// Fetch a user collection
var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION);
collection.findOne({user: username}, {dbName: options['dbName']}, function(err, user) {
if(user != null) {
// Add command keys
var commandOptions = safe;
commandOptions.dbName = options['dbName'];
collection.remove({user: username}, commandOptions, function(err, result) {
callback(err, true);
});
} else {
callback(err, false);
}
});
};
/**
* Creates a collection on a server pre-allocating space, need to create f.ex capped collections.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **serializeFunctions** {Boolean, default:false}, serialize functions on the document.
* - **raw** {Boolean, default:false}, perform all operations using raw bson objects.
* - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation.
* - **capped** {Boolean, default:false}, create a capped collection.
* - **size** {Number}, the size of the capped collection in bytes.
* - **max** {Number}, the maximum number of documents in the capped collection.
* - **autoIndexId** {Boolean, default:true}, create an index on the _id field of the document, True by default on MongoDB 2.2 or higher off for version < 2.2.
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **strict**, (Boolean, default:false) throws an error if collection already exists
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} collectionName the collection name we wish to access.
* @param {Object} [options] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from createCollection or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.createCollection = function(collectionName, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() : null;
var self = this;
// Figure out the safe mode settings
var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe;
// Override with options passed in if applicable
safe = options != null && options['safe'] != null ? options['safe'] : safe;
// Ensure it's at least set to safe
safe = safe == null ? {w: 1} : safe;
// Check if we have the name
this.collectionNames(collectionName, function(err, collections) {
if(err != null) return callback(err, null);
var found = false;
collections.forEach(function(collection) {
if(collection.name == self.databaseName + "." + collectionName) found = true;
});
// If the collection exists either throw an exception (if db in safe mode) or return the existing collection
if(found && options && options.strict) {
return callback(new Error("Collection " + collectionName + " already exists. Currently in safe mode."), null);
} else if(found){
try {
var collection = new Collection(self, collectionName, self.pkFactory, options);
} catch(err) {
return callback(err, null);
}
return callback(null, collection);
}
// Create a new collection and return it
self._executeQueryCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options)
, {read:false, safe:safe}
, utils.handleSingleCommandResultReturn(null, null, function(err, result) {
if(err) return callback(err, null);
// Create collection and return
try {
return callback(null, new Collection(self, collectionName, self.pkFactory, options));
} catch(err) {
return callback(err, null);
}
}));
});
};
var _getReadConcern = function(self, options) {
if(options.readPreference) return options.readPreference;
if(self.readPreference) return self.readPreference;
return 'primary';
}
/**
* Execute a command hash against MongoDB. This lets you acess any commands not available through the api on the server.
*
* @param {Object} selector the command hash to send to the server, ex: {ping:1}.
* @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter.
* @return {null}
* @api public
*/
Db.prototype.command = function(selector, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
// Ignore command preference (I know what I'm doing)
var ignoreCommandFilter = options.ignoreCommandFilter ? options.ignoreCommandFilter : false;
// Set up the options
var cursor = new Cursor(this
, new Collection(this, DbCommand.SYSTEM_COMMAND_COLLECTION), selector, {}, {
limit: -1, timeout: QueryCommand.OPTS_NO_CURSOR_TIMEOUT, dbName: options['dbName']
});
// Set read preference if we set one
var readPreference = options['readPreference'] ? options['readPreference'] : false;
// If we have a connection passed in
cursor.connection = options.connection;
// Ensure only commands who support read Prefrences are exeuted otherwise override and use Primary
if(readPreference != false && ignoreCommandFilter == false) {
if(selector['group'] || selector['aggregate'] || selector['collStats'] || selector['dbStats']
|| selector['count'] || selector['distinct'] || selector['geoNear'] || selector['geoSearch']
|| selector['geoWalk'] || selector['text']
|| (selector['mapreduce'] && (selector.out == 'inline' || selector.out.inline))) {
// Set the read preference
cursor.setReadPreference(readPreference);
} else {
cursor.setReadPreference(ReadPreference.PRIMARY);
}
} else if(readPreference != false) {
// Force setting the command filter
cursor.setReadPreference(readPreference);
}
// Get the next result
cursor.nextObject(function(err, result) {
if(err) return callback(err, null);
if(result == null) return callback(new Error("no result returned from command"), null);
callback(null, result);
});
};
/**
* Drop a collection from the database, removing it permanently. New accesses will create a new collection.
*
* @param {String} collectionName the name of the collection we wish to drop.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropCollection or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.dropCollection = function(collectionName, callback) {
var self = this;
callback || (callback = function(){});
// Drop the collection
this._executeQueryCommand(DbCommand.createDropCollectionCommand(this, collectionName)
, utils.handleSingleCommandResultReturn(true, false, callback)
);
};
/**
* Rename a collection.
*
* Options
* - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists.
*
* @param {String} fromCollection the name of the current collection we wish to rename.
* @param {String} toCollection the new name of the collection.
* @param {Object} [options] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from renameCollection or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.renameCollection = function(fromCollection, toCollection, options, callback) {
var self = this;
if(typeof options == 'function') {
callback = options;
options = {}
}
// Add return new collection
options.new_collection = true;
// Execute using the collection method
this.collection(fromCollection).rename(toCollection, options, callback);
};
/**
* Return last error message for the given connection, note options can be combined.
*
* Options
* - **fsync** {Boolean, default:false}, option forces the database to fsync all files before returning.
* - **j** {Boolean, default:false}, awaits the journal commit before returning, > MongoDB 2.0.
* - **w** {Number}, until a write operation has been replicated to N servers.
* - **wtimeout** {Number}, number of miliseconds to wait before timing out.
*
* Connection Options
* - **connection** {Connection}, fire the getLastError down a specific connection.
*
* @param {Object} [options] returns option results.
* @param {Object} [connectionOptions] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from lastError or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.lastError = function(options, connectionOptions, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
connectionOptions = args.length ? args.shift() || {} : {};
this._executeQueryCommand(DbCommand.createGetLastErrorCommand(options, this), connectionOptions, function(err, error) {
callback(err, error && error.documents);
});
};
/**
* Legacy method calls.
*
* @ignore
* @api private
*/
Db.prototype.error = Db.prototype.lastError;
Db.prototype.lastStatus = Db.prototype.lastError;
/**
* Return all errors up to the last time db reset_error_history was called.
*
* Options
* - **connection** {Connection}, fire the getLastError down a specific connection.
*
* @param {Object} [options] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from previousErrors or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.previousErrors = function(options, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
this._executeQueryCommand(DbCommand.createGetPreviousErrorsCommand(this), options, function(err, error) {
callback(err, error.documents);
});
};
/**
* Runs a command on the database.
* @ignore
* @api private
*/
Db.prototype.executeDbCommand = function(command_hash, options, callback) {
if(callback == null) { callback = options; options = {}; }
this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, command_hash, options), options, function(err, result) {
if(callback) callback(err, result);
});
};
/**
* Runs a command on the database as admin.
* @ignore
* @api private
*/
Db.prototype.executeDbAdminCommand = function(command_hash, options, callback) {
if(typeof options == 'function') {
callback = options;
options = {}
}
if(options.readPreference) {
options.read = options.readPreference;
}
this._executeQueryCommand(DbCommand.createAdminDbCommand(this, command_hash), options, function(err, result) {
if(callback) callback(err, result);
});
};
/**
* Resets the error history of the mongo instance.
*
* Options
* - **connection** {Connection}, fire the getLastError down a specific connection.
*
* @param {Object} [options] returns option results.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from resetErrorHistory or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.resetErrorHistory = function(options, callback) {
// Unpack calls
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
this._executeQueryCommand(DbCommand.createResetErrorHistoryCommand(this), options, function(err, error) {
if(callback) callback(err, error && error.documents);
});
};
/**
* Creates an index on the collection.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **unique** {Boolean, default:false}, creates an unique index.
* - **sparse** {Boolean, default:false}, creates a sparse index.
* - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible.
* - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates.
* - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates.
* - **v** {Number}, specify the format version of the indexes.
* - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
*
* @param {String} collectionName name of the collection to create the index on.
* @param {Object} fieldOrSpec fieldOrSpec that defines the index.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from createIndex or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
options = typeof callback === 'function' ? options : callback;
options = options == null ? {} : options;
// Get the error options
var errorOptions = _getWriteConcern(this, options, callback);
// Create command
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options);
// Default command options
var commandOptions = {};
// If we have error conditions set handle them
if(_hasWriteConcern(errorOptions) && typeof callback == 'function') {
// Insert options
commandOptions['read'] = false;
// If we have safe set set async to false
if(errorOptions == null) commandOptions['async'] = true;
// Set safe option
commandOptions['safe'] = errorOptions;
// If we have an error option
if(typeof errorOptions == 'object') {
var keys = Object.keys(errorOptions);
for(var i = 0; i < keys.length; i++) {
commandOptions[keys[i]] = errorOptions[keys[i]];
}
}
// Execute insert command
this._executeInsertCommand(command, commandOptions, function(err, result) {
if(err != null) return callback(err, null);
result = result && result.documents;
if (result[0].err) {
callback(utils.toError(result[0]));
} else {
callback(null, command.documents[0].name);
}
});
} else if(_hasWriteConcern(errorOptions) && callback == null) {
throw new Error("Cannot use a writeConcern without a provided callback");
} else {
// Execute insert command
var result = this._executeInsertCommand(command, commandOptions);
// If no callback just return
if(!callback) return;
// If error return error
if(result instanceof Error) {
return callback(result);
}
// Otherwise just return
return callback(null, null);
}
};
/**
* Ensures that an index exists, if it does not it creates it
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **unique** {Boolean, default:false}, creates an unique index.
* - **sparse** {Boolean, default:false}, creates a sparse index.
* - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible.
* - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates.
* - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates.
* - **v** {Number}, specify the format version of the indexes.
* - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @param {String} collectionName name of the collection to create the index on.
* @param {Object} fieldOrSpec fieldOrSpec that defines the index.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ensureIndex or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callback) {
var self = this;
if (typeof callback === 'undefined' && typeof options === 'function') {
callback = options;
options = {};
}
if (options == null) {
options = {};
}
// Get the error options
var errorOptions = _getWriteConcern(this, options, callback);
// Make sure we don't try to do a write concern without a callback
if(_hasWriteConcern(errorOptions) && callback == null)
throw new Error("Cannot use a writeConcern without a provided callback");
// Create command
var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options);
var index_name = command.documents[0].name;
// Default command options
var commandOptions = {};
// Check if the index allready exists
this.indexInformation(collectionName, function(err, collectionInfo) {
if(err != null) return callback(err, null);
if(!collectionInfo[index_name]) {
// If we have error conditions set handle them
if(_hasWriteConcern(errorOptions) && typeof callback == 'function') {
// Insert options
commandOptions['read'] = false;
// If we have safe set set async to false
if(errorOptions == null) commandOptions['async'] = true;
// If we have an error option
if(typeof errorOptions == 'object') {
var keys = Object.keys(errorOptions);
for(var i = 0; i < keys.length; i++) {
commandOptions[keys[i]] = errorOptions[keys[i]];
}
}
if(typeof callback === 'function'
&& commandOptions.w < 1 && !commandOptions.fsync && !commandOptions.journal) {
commandOptions.w = 1;
}
self._executeInsertCommand(command, commandOptions, function(err, result) {
// Only callback if we have one specified
if(typeof callback === 'function') {
if(err != null) return callback(err, null);
result = result && result.documents;
if (result[0].err) {
callback(utils.toError(result[0]));
} else {
callback(null, command.documents[0].name);
}
}
});
} else {
// Execute insert command
var result = self._executeInsertCommand(command, commandOptions);
// If no callback just return
if(!callback) return;
// If error return error
if(result instanceof Error) {
return callback(result);
}
// Otherwise just return
return callback(null, index_name);
}
} else {
if(typeof callback === 'function') return callback(null, index_name);
}
});
};
/**
* Returns the information available on allocated cursors.
*
* Options
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
*
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from cursorInfo or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.cursorInfo = function(options, callback) {
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
options = args.length ? args.shift() || {} : {};
this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, {'cursorInfo':1})
, options
, utils.handleSingleCommandResultReturn(null, null, callback));
};
/**
* Drop an index on a collection.
*
* @param {String} collectionName the name of the collection where the command will drop an index.
* @param {String} indexName name of the index to drop.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropIndex or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.dropIndex = function(collectionName, indexName, callback) {
this._executeQueryCommand(DbCommand.createDropIndexCommand(this, collectionName, indexName)
, utils.handleSingleCommandResultReturn(null, null, callback));
};
/**
* Reindex all indexes on the collection
* Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections.
*
* @param {String} collectionName the name of the collection.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from reIndex or null if an error occured.
* @api public
**/
Db.prototype.reIndex = function(collectionName, callback) {
this._executeQueryCommand(DbCommand.createReIndexCommand(this, collectionName)
, utils.handleSingleCommandResultReturn(true, false, callback));
};
/**
* Retrieves this collections index info.
*
* Options
* - **full** {Boolean, default:false}, returns the full raw index information.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {String} collectionName the name of the collection.
* @param {Object} [options] additional options during update.
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from indexInformation or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.indexInformation = function(collectionName, options, callback) {
if(typeof callback === 'undefined') {
if(typeof options === 'undefined') {
callback = collectionName;
collectionName = null;
} else {
callback = options;
}
options = {};
}
// If we specified full information
var full = options['full'] == null ? false : options['full'];
// Build selector for the indexes
var selector = collectionName != null ? {ns: (this.databaseName + "." + collectionName)} : {};
// Set read preference if we set one
var readPreference = options['readPreference'] ? options['readPreference'] : ReadPreference.PRIMARY;
// Iterate through all the fields of the index
this.collection(DbCommand.SYSTEM_INDEX_COLLECTION, function(err, collection) {
// Perform the find for the collection
collection.find(selector).setReadPreference(readPreference).toArray(function(err, indexes) {
if(err != null) return callback(err, null);
// Contains all the information
var info = {};
// if full defined just return all the indexes directly
if(full) return callback(null, indexes);
// Process all the indexes
for(var i = 0; i < indexes.length; i++) {
var index = indexes[i];
// Let's unpack the object
info[index.name] = [];
for(var name in index.key) {
info[index.name].push([name, index.key[name]]);
}
}
// Return all the indexes
callback(null, info);
});
});
};
/**
* Drop a database.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from dropDatabase or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.dropDatabase = function(callback) {
this._executeQueryCommand(DbCommand.createDropDatabaseCommand(this)
, utils.handleSingleCommandResultReturn(true, false, callback));
}
/**
* Get all the db statistics.
*
* Options
* - **scale** {Number}, divide the returned sizes by scale value.
* - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Objects} [options] options for the stats command
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from stats or null if an error occured.
* @return {null}
* @api public
*/
Db.prototype.stats = function stats(options, callback) {
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
// Fetch all commands
options = args.length ? args.shift() || {} : {};
// Build command object
var commandObject = {
dbStats:this.collectionName,
}
// Check if we have the scale value
if(options['scale'] != null) commandObject['scale'] = options['scale'];
// Execute the command
this.command(commandObject, options, callback);
}
/**
* @ignore
*/
var __executeQueryCommand = function(self, db_command, options, callback) {
// Options unpacking
var read = options['read'] != null ? options['read'] : false;
var raw = options['raw'] != null ? options['raw'] : self.raw;
var onAll = options['onAll'] != null ? options['onAll'] : false;
var specifiedConnection = options['connection'] != null ? options['connection'] : null;
// Correct read preference to default primary if set to false, null or primary
if(!(typeof read == 'object') && read._type == 'ReadPreference') {
read = (read == null || read == 'primary' || read == false) ? ReadPreference.PRIMARY : read;
if(!ReadPreference.isValid(read)) return callback(new Error("Illegal readPreference mode specified, " + read));
} else if(typeof read == 'object' && read._type == 'ReadPreference') {
if(!read.isValid()) return callback(new Error("Illegal readPreference mode specified, " + read.mode));
}
// If we have a read preference set and we are a mongos pass the read preference on to the mongos instance,
if(self.serverConfig.isMongos() && read != null && read != false) {
db_command.setMongosReadPreference(read);
}
// If we got a callback object
if(typeof callback === 'function' && !onAll) {
// Override connection if we passed in a specific connection
var connection = specifiedConnection != null ? specifiedConnection : null;
if(connection instanceof Error) return callback(connection, null);
// Fetch either a reader or writer dependent on the specified read option if no connection
// was passed in
if(connection == null) {
connection = self.serverConfig.checkoutReader(read);
}
if(connection == null) {
return callback(new Error("no open connections"));
} else if(connection instanceof Error || connection['message'] != null) {
return callback(connection);
}
// Exhaust Option
var exhaust = options.exhaust || false;
// Register the handler in the data structure
self.serverConfig._registerHandler(db_command, raw, connection, exhaust, callback);
// Ensure the connection is valid
if(!connection.isConnected()) {
if(read == ReadPreference.PRIMARY
|| read == ReadPreference.PRIMARY_PREFERRED
|| (read != null && typeof read == 'object' && read.mode)
|| read == null) {
// Save the command
self.serverConfig._commandsStore.read_from_writer(
{ type: 'query'
, db_command: db_command
, options: options
, callback: callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
} else {
self.serverConfig._commandsStore.read(
{ type: 'query'
, db_command: db_command
, options: options
, callback: callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
}
}
// Write the message out and handle any errors if there are any
connection.write(db_command, function(err) {
if(err != null) {
// Call the handler with an error
if(Array.isArray(db_command))
self.serverConfig._callHandler(db_command[0].getRequestId(), null, err);
else
self.serverConfig._callHandler(db_command.getRequestId(), null, err);
}
});
} else if(typeof callback === 'function' && onAll) {
var connections = self.serverConfig.allRawConnections();
var numberOfEntries = connections.length;
// Go through all the connections
for(var i = 0; i < connections.length; i++) {
// Fetch a connection
var connection = connections[i];
// Ensure we have a valid connection
if(connection == null) {
return callback(new Error("no open connections"));
} else if(connection instanceof Error) {
return callback(connection);
}
// Register the handler in the data structure
self.serverConfig._registerHandler(db_command, raw, connection, callback);
// Write the message out
connection.write(db_command, function(err) {
// Adjust the number of entries we need to process
numberOfEntries = numberOfEntries - 1;
// Remove listener
if(err != null) {
// Clean up listener and return error
self.serverConfig._removeHandler(db_command.getRequestId());
}
// No more entries to process callback with the error
if(numberOfEntries <= 0) {
callback(err);
}
});
// Update the db_command request id
db_command.updateRequestId();
}
} else {
// Fetch either a reader or writer dependent on the specified read option
// var connection = read == null || read == 'primary' || read == false ? self.serverConfig.checkoutWriter(true) : self.serverConfig.checkoutReader(read);
var connection = self.serverConfig.checkoutReader(read);
// Override connection if needed
connection = specifiedConnection != null ? specifiedConnection : connection;
// Ensure we have a valid connection
if(connection == null || connection instanceof Error || connection['message'] != null) return null;
// Write the message out
connection.write(db_command, function(err) {
if(err != null) {
// Emit the error
self.emit("error", err);
}
});
}
}
/**
* Execute db query command (not safe)
* @ignore
* @api private
*/
Db.prototype._executeQueryCommand = function(db_command, options, callback) {
var self = this;
// Unpack the parameters
if (typeof callback === 'undefined') {
callback = options;
options = {};
}
// fast fail option used for HA, no retry
var failFast = options['failFast'] != null
? options['failFast']
: false;
// Check if the user force closed the command
if(this._applicationClosed) {
var err = new Error("db closed by application");
if('function' == typeof callback) {
return callback(err, null);
} else {
throw err;
}
}
if(this.serverConfig.isDestroyed())
return callback(new Error("Connection was destroyed by application"));
// Specific connection
var connection = options.connection;
// Check if the connection is actually live
if(connection
&& (!connection.isConnected || !connection.isConnected())) connection = null;
// Get the configuration
var config = this.serverConfig;
var read = options.read;
if(!connection && !config.canRead(read) && !config.canWrite() && config.isAutoReconnect()) {
if(read == ReadPreference.PRIMARY
|| read == ReadPreference.PRIMARY_PREFERRED
|| (read != null && typeof read == 'object' && read.mode)
|| read == null) {
// Save the command
self.serverConfig._commandsStore.read_from_writer(
{ type: 'query'
, db_command: db_command
, options: options
, callback: callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
} else {
self.serverConfig._commandsStore.read(
{ type: 'query'
, db_command: db_command
, options: options
, callback: callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
}
} else if(!connection && !config.canRead(read) && !config.canWrite() && !config.isAutoReconnect()) {
return callback(new Error("no open connections"), null);
} else {
if(typeof callback == 'function') {
__executeQueryCommand(self, db_command, options, function (err, result, conn) {
callback(err, result, conn);
});
} else {
__executeQueryCommand(self, db_command, options);
}
}
};
/**
* @ignore
*/
var __executeInsertCommand = function(self, db_command, options, callback) {
// Always checkout a writer for this kind of operations
var connection = self.serverConfig.checkoutWriter();
// Get safe mode
var safe = options['safe'] != null ? options['safe'] : false;
var raw = options['raw'] != null ? options['raw'] : self.raw;
var specifiedConnection = options['connection'] != null ? options['connection'] : null;
// Override connection if needed
connection = specifiedConnection != null ? specifiedConnection : connection;
// Ensure we have a valid connection
if(typeof callback === 'function') {
// Ensure we have a valid connection
if(connection == null) {
return callback(new Error("no open connections"));
} else if(connection instanceof Error) {
return callback(connection);
}
var errorOptions = _getWriteConcern(self, options, callback);
if(errorOptions.w > 0 || errorOptions.w == 'majority' || errorOptions.j || errorOptions.journal || errorOptions.fsync) {
// db command is now an array of commands (original command + lastError)
db_command = [db_command, DbCommand.createGetLastErrorCommand(safe, self)];
// Register the handler in the data structure
self.serverConfig._registerHandler(db_command[1], raw, connection, callback);
}
}
// If we have no callback and there is no connection
if(connection == null) return null;
if(connection instanceof Error && typeof callback == 'function') return callback(connection, null);
if(connection instanceof Error) return null;
if(connection == null && typeof callback == 'function') return callback(new Error("no primary server found"), null);
// Ensure we truly are connected
if(!connection.isConnected()) {
return self.serverConfig._commandsStore.write(
{ type:'insert'
, 'db_command':db_command
, 'options':options
, 'callback':callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
}
// Write the message out
connection.write(db_command, function(err) {
// Return the callback if it's not a safe operation and the callback is defined
if(typeof callback === 'function' && (safe == null || safe == false)) {
// Perform the callback
callback(err, null);
} else if(typeof callback === 'function') {
// Call the handler with an error
self.serverConfig._callHandler(db_command[1].getRequestId(), null, err);
} else if(typeof callback == 'function' && safe && safe.w == -1) {
// Call the handler with no error
self.serverConfig._callHandler(db_command[1].getRequestId(), null, null);
} else if(!safe || safe.w == -1) {
self.emit("error", err);
}
});
}
/**
* Execute an insert Command
* @ignore
* @api private
*/
Db.prototype._executeInsertCommand = function(db_command, options, callback) {
var self = this;
// Unpack the parameters
if(callback == null && typeof options === 'function') {
callback = options;
options = {};
}
// Ensure options are not null
options = options == null ? {} : options;
// Check if the user force closed the command
if(this._applicationClosed) {
if(typeof callback == 'function') {
return callback(new Error("db closed by application"), null);
} else {
throw new Error("db closed by application");
}
}
if(this.serverConfig.isDestroyed()) return callback(new Error("Connection was destroyed by application"));
// Specific connection
var connection = options.connection;
// Check if the connection is actually live
if(connection
&& (!connection.isConnected || !connection.isConnected())) connection = null;
// Get config
var config = self.serverConfig;
// Check if we are connected
if(!connection && !config.canWrite() && config.isAutoReconnect()) {
self.serverConfig._commandsStore.write(
{ type:'insert'
, 'db_command':db_command
, 'options':options
, 'callback':callback
, db: self
, executeQueryCommand: __executeQueryCommand
, executeInsertCommand: __executeInsertCommand
}
);
} else if(!connection && !config.canWrite() && !config.isAutoReconnect()) {
return callback(new Error("no open connections"), null);
} else {
__executeInsertCommand(self, db_command, options, callback);
}
}
/**
* Update command is the same
* @ignore
* @api private
*/
Db.prototype._executeUpdateCommand = Db.prototype._executeInsertCommand;
/**
* Remove command is the same
* @ignore
* @api private
*/
Db.prototype._executeRemoveCommand = Db.prototype._executeInsertCommand;
/**
* Wrap a Mongo error document into an Error instance.
* Deprecated. Use utils.toError instead.
*
* @ignore
* @api private
* @deprecated
*/
Db.prototype.wrap = utils.toError;
/**
* Default URL
*
* @classconstant DEFAULT_URL
**/
Db.DEFAULT_URL = 'mongodb://localhost:27017/default';
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Options
* - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication
* - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor**
* - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor**
* - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor**
* - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor**
*
* @param {String} url connection url for MongoDB.
* @param {Object} [options] optional options for insert command
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the db instance or null if an error occured.
* @return {null}
* @api public
*/
Db.connect = function(url, options, callback) {
// Ensure correct mapping of the callback
if(typeof options == 'function') {
callback = options;
options = {};
}
// Ensure same behavior as previous version w:0
if(url.indexOf("safe") == -1
&& url.indexOf("w") == -1
&& url.indexOf("journal") == -1 && url.indexOf("j") == -1
&& url.indexOf("fsync") == -1) options.w = 0;
// Avoid circular require problem
var MongoClient = require('./mongo_client.js').MongoClient;
// Attempt to connect
MongoClient.connect.call(MongoClient, url, options, callback);
}
/**
* State of the db connection
* @ignore
*/
Object.defineProperty(Db.prototype, "state", { enumerable: true
, get: function () {
return this.serverConfig._serverState;
}
});
/**
* @ignore
*/
var _hasWriteConcern = function(errorOptions) {
return errorOptions == true
|| errorOptions.w > 0
|| errorOptions.w == 'majority'
|| errorOptions.j == true
|| errorOptions.journal == true
|| errorOptions.fsync == true
}
/**
* @ignore
*/
var _setWriteConcernHash = function(options) {
var finalOptions = {};
if(options.w != null) finalOptions.w = options.w;
if(options.journal == true) finalOptions.j = options.journal;
if(options.j == true) finalOptions.j = options.j;
if(options.fsync == true) finalOptions.fsync = options.fsync;
if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout;
return finalOptions;
}
/**
* @ignore
*/
var _getWriteConcern = function(self, options, callback) {
// Final options
var finalOptions = {w:1};
// Local options verification
if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(options);
} else if(options.safe != null && typeof options.safe == 'object') {
finalOptions = _setWriteConcernHash(options.safe);
} else if(typeof options.safe == "boolean") {
finalOptions = {w: (options.safe ? 1 : 0)};
} else if(self.options.w != null || typeof self.options.j == 'boolean' || typeof self.options.journal == 'boolean' || typeof self.options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.options);
} else if(self.safe.w != null || typeof self.safe.j == 'boolean' || typeof self.safe.journal == 'boolean' || typeof self.safe.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.safe);
} else if(typeof self.safe == "boolean") {
finalOptions = {w: (self.safe ? 1 : 0)};
}
// Ensure we don't have an invalid combination of write concerns
if(finalOptions.w < 1
&& (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true");
// Return the options
return finalOptions;
}
/**
* Legacy support
*
* @ignore
* @api private
*/
exports.connect = Db.connect;
exports.Db = Db;
/**
* Remove all listeners to the db instance.
* @ignore
* @api private
*/
Db.prototype.removeAllEventListeners = function() {
this.removeAllListeners("close");
this.removeAllListeners("error");
this.removeAllListeners("timeout");
this.removeAllListeners("parseError");
this.removeAllListeners("poolReady");
this.removeAllListeners("message");
}
var Binary = require('bson').Binary,
ObjectID = require('bson').ObjectID;
/**
* Class for representing a single chunk in GridFS.
*
* @class
*
* @param file {GridStore} The {@link GridStore} object holding this chunk.
* @param mongoObject {object} The mongo object representation of this chunk.
*
* @throws Error when the type of data field for {@link mongoObject} is not
* supported. Currently supported types for data field are instances of
* {@link String}, {@link Array}, {@link Binary} and {@link Binary}
* from the bson module
*
* @see Chunk#buildMongoObject
*/
var Chunk = exports.Chunk = function(file, mongoObject, writeConcern) {
if(!(this instanceof Chunk)) return new Chunk(file, mongoObject);
this.file = file;
var self = this;
var mongoObjectFinal = mongoObject == null ? {} : mongoObject;
this.writeConcern = writeConcern || {w:1};
this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id;
this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n;
this.data = new Binary();
if(mongoObjectFinal.data == null) {
} else if(typeof mongoObjectFinal.data == "string") {
var buffer = new Buffer(mongoObjectFinal.data.length);
buffer.write(mongoObjectFinal.data, 'binary', 0);
this.data = new Binary(buffer);
} else if(Array.isArray(mongoObjectFinal.data)) {
var buffer = new Buffer(mongoObjectFinal.data.length);
buffer.write(mongoObjectFinal.data.join(''), 'binary', 0);
this.data = new Binary(buffer);
} else if(mongoObjectFinal.data instanceof Binary || Object.prototype.toString.call(mongoObjectFinal.data) == "[object Binary]") {
this.data = mongoObjectFinal.data;
} else if(Buffer.isBuffer(mongoObjectFinal.data)) {
} else {
throw Error("Illegal chunk format");
}
// Update position
this.internalPosition = 0;
};
/**
* Writes a data to this object and advance the read/write head.
*
* @param data {string} the data to write
* @param callback {function(*, GridStore)} This will be called after executing
* this method. The first parameter will contain null and the second one
* will contain a reference to this object.
*/
Chunk.prototype.write = function(data, callback) {
this.data.write(data, this.internalPosition);
this.internalPosition = this.data.length();
if(callback != null) return callback(null, this);
return this;
};
/**
* Reads data and advances the read/write head.
*
* @param length {number} The length of data to read.
*
* @return {string} The data read if the given length will not exceed the end of
* the chunk. Returns an empty String otherwise.
*/
Chunk.prototype.read = function(length) {
// Default to full read if no index defined
length = length == null || length == 0 ? this.length() : length;
if(this.length() - this.internalPosition + 1 >= length) {
var data = this.data.read(this.internalPosition, length);
this.internalPosition = this.internalPosition + length;
return data;
} else {
return '';
}
};
Chunk.prototype.readSlice = function(length) {
if ((this.length() - this.internalPosition) >= length) {
var data = null;
if (this.data.buffer != null) { //Pure BSON
data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length);
} else { //Native BSON
data = new Buffer(length);
length = this.data.readInto(data, this.internalPosition);
}
this.internalPosition = this.internalPosition + length;
return data;
} else {
return null;
}
};
/**
* Checks if the read/write head is at the end.
*
* @return {boolean} Whether the read/write head has reached the end of this
* chunk.
*/
Chunk.prototype.eof = function() {
return this.internalPosition == this.length() ? true : false;
};
/**
* Reads one character from the data of this chunk and advances the read/write
* head.
*
* @return {string} a single character data read if the the read/write head is
* not at the end of the chunk. Returns an empty String otherwise.
*/
Chunk.prototype.getc = function() {
return this.read(1);
};
/**
* Clears the contents of the data in this chunk and resets the read/write head
* to the initial position.
*/
Chunk.prototype.rewind = function() {
this.internalPosition = 0;
this.data = new Binary();
};
/**
* Saves this chunk to the database. Also overwrites existing entries having the
* same id as this chunk.
*
* @param callback {function(*, GridStore)} This will be called after executing
* this method. The first parameter will contain null and the second one
* will contain a reference to this object.
*/
Chunk.prototype.save = function(callback) {
var self = this;
self.file.chunkCollection(function(err, collection) {
if(err) return callback(err);
collection.remove({'_id':self.objectId}, self.writeConcern, function(err, result) {
if(err) return callback(err);
if(self.data.length() > 0) {
self.buildMongoObject(function(mongoObject) {
var options = {forceServerObjectId:true};
for(var name in self.writeConcern) {
options[name] = self.writeConcern[name];
}
collection.insert(mongoObject, options, function(err, collection) {
callback(err, self);
});
});
} else {
callback(null, self);
}
});
});
};
/**
* Creates a mongoDB object representation of this chunk.
*
* @param callback {function(Object)} This will be called after executing this
* method. The object will be passed to the first parameter and will have
* the structure:
*
* <pre><code>
* {
* '_id' : , // {number} id for this chunk
* 'files_id' : , // {number} foreign key to the file collection
* 'n' : , // {number} chunk number
* 'data' : , // {bson#Binary} the chunk data itself
* }
* </code></pre>
*
* @see <a href="http://www.mongodb.org/display/DOCS/GridFS+Specification#GridFSSpecification-{{chunks}}">MongoDB GridFS Chunk Object Structure</a>
*/
Chunk.prototype.buildMongoObject = function(callback) {
var mongoObject = {
'files_id': this.file.fileId,
'n': this.chunkNumber,
'data': this.data};
// If we are saving using a specific ObjectId
if(this.objectId != null) mongoObject._id = this.objectId;
callback(mongoObject);
};
/**
* @return {number} the length of the data
*/
Chunk.prototype.length = function() {
return this.data.length();
};
/**
* The position of the read/write head
* @name position
* @lends Chunk#
* @field
*/
Object.defineProperty(Chunk.prototype, "position", { enumerable: true
, get: function () {
return this.internalPosition;
}
, set: function(value) {
this.internalPosition = value;
}
});
/**
* The default chunk size
* @constant
*/
Chunk.DEFAULT_CHUNK_SIZE = 1024 * 256;
var GridStore = require('./gridstore').GridStore,
ObjectID = require('bson').ObjectID;
/**
* A class representation of a simple Grid interface.
*
* @class Represents the Grid.
* @param {Db} db A database instance to interact with.
* @param {String} [fsName] optional different root collection for GridFS.
* @return {Grid}
*/
function Grid(db, fsName) {
if(!(this instanceof Grid)) return new Grid(db, fsName);
this.db = db;
this.fsName = fsName == null ? GridStore.DEFAULT_ROOT_COLLECTION : fsName;
}
/**
* Puts binary data to the grid
*
* Options
* - **_id** {Any}, unique id for this file
* - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
* - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**.
* - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**.
* - **metadata** {Object}, arbitrary data the user wants to store.
*
* @param {Buffer} data buffer with Binary Data.
* @param {Object} [options] the options for the files.
* @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object.
* @return {null}
* @api public
*/
Grid.prototype.put = function(data, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
options = args.length ? args.shift() : {};
// If root is not defined add our default one
options['root'] = options['root'] == null ? this.fsName : options['root'];
// Return if we don't have a buffer object as data
if(!(Buffer.isBuffer(data))) return callback(new Error("Data object must be a buffer object"), null);
// Get filename if we are using it
var filename = options['filename'] || null;
// Get id if we are using it
var id = options['_id'] || null;
// Create gridstore
var gridStore = new GridStore(this.db, id, filename, "w", options);
gridStore.open(function(err, gridStore) {
if(err) return callback(err, null);
gridStore.write(data, function(err, result) {
if(err) return callback(err, null);
gridStore.close(function(err, result) {
if(err) return callback(err, null);
callback(null, result);
})
})
})
}
/**
* Get binary data to the grid
*
* @param {Any} id for file.
* @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object.
* @return {null}
* @api public
*/
Grid.prototype.get = function(id, callback) {
// Create gridstore
var gridStore = new GridStore(this.db, id, null, "r", {root:this.fsName});
gridStore.open(function(err, gridStore) {
if(err) return callback(err, null);
// Return the data
gridStore.read(function(err, data) {
return callback(err, data)
});
})
}
/**
* Delete file from grid
*
* @param {Any} id for file.
* @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object.
* @return {null}
* @api public
*/
Grid.prototype.delete = function(id, callback) {
// Create gridstore
GridStore.unlink(this.db, id, {root:this.fsName}, function(err, result) {
if(err) return callback(err, false);
return callback(null, true);
});
}
exports.Grid = Grid;
/**
* @fileOverview GridFS is a tool for MongoDB to store files to the database.
* Because of the restrictions of the object size the database can hold, a
* facility to split a file into several chunks is needed. The {@link GridStore}
* class offers a simplified api to interact with files while managing the
* chunks of split files behind the scenes. More information about GridFS can be
* found <a href="http://www.mongodb.org/display/DOCS/GridFS">here</a>.
*/
var Chunk = require('./chunk').Chunk,
DbCommand = require('../commands/db_command').DbCommand,
ObjectID = require('bson').ObjectID,
Buffer = require('buffer').Buffer,
fs = require('fs'),
timers = require('timers'),
util = require('util'),
inherits = util.inherits,
ReadStream = require('./readstream').ReadStream,
Stream = require('stream');
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
var REFERENCE_BY_FILENAME = 0,
REFERENCE_BY_ID = 1;
/**
* A class representation of a file stored in GridFS.
*
* Modes
* - **"r"** - read only. This is the default mode.
* - **"w"** - write in truncate mode. Existing data will be overwriten.
* - **w+"** - write in edit mode.
*
* Options
* - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
* - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**.
* - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**.
* - **metadata** {Object}, arbitrary data the user wants to store.
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
*
* @class Represents the GridStore.
* @param {Db} db A database instance to interact with.
* @param {Any} [id] optional unique id for this file
* @param {String} [filename] optional filename for this file, no unique constrain on the field
* @param {String} mode set the mode for this file.
* @param {Object} options optional properties to specify.
* @return {GridStore}
*/
var GridStore = function GridStore(db, id, filename, mode, options) {
if(!(this instanceof GridStore)) return new GridStore(db, id, filename, mode, options);
var self = this;
this.db = db;
// Call stream constructor
if(typeof Stream == 'function') {
Stream.call(this);
} else {
// 0.4.X backward compatibility fix
Stream.Stream.call(this);
}
// Handle options
if(typeof options === 'undefined') options = {};
// Handle mode
if(typeof mode === 'undefined') {
mode = filename;
filename = undefined;
} else if(typeof mode == 'object') {
options = mode;
mode = filename;
filename = undefined;
}
if(id instanceof ObjectID) {
this.referenceBy = REFERENCE_BY_ID;
this.fileId = id;
this.filename = filename;
} else if(typeof filename == 'undefined') {
this.referenceBy = REFERENCE_BY_FILENAME;
this.filename = id;
if (mode.indexOf('w') != null) {
this.fileId = new ObjectID();
}
} else {
this.referenceBy = REFERENCE_BY_ID;
this.fileId = id;
this.filename = filename;
}
// Set up the rest
this.mode = mode == null ? "r" : mode;
this.options = options == null ? {w:1} : options;
// If we have no write concerns set w:1 as default
if(this.options.w == null
&& this.options.j == null
&& this.options.fsync == null) this.options.w = 1;
// Set the root if overridden
this.root = this.options['root'] == null ? exports.GridStore.DEFAULT_ROOT_COLLECTION : this.options['root'];
this.position = 0;
this.readPreference = this.options.readPreference || 'primary';
this.writeConcern = _getWriteConcern(this, this.options);
// Set default chunk size
this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize'];
}
/**
* Code for the streaming capabilities of the gridstore object
* Most code from Aaron heckmanns project https://github.com/aheckmann/gridfs-stream
* Modified to work on the gridstore object itself
* @ignore
*/
if(typeof Stream == 'function') {
GridStore.prototype = { __proto__: Stream.prototype }
} else {
// Node 0.4.X compatibility code
GridStore.prototype = { __proto__: Stream.Stream.prototype }
}
// Move pipe to _pipe
GridStore.prototype._pipe = GridStore.prototype.pipe;
/**
* Opens the file from the database and initialize this object. Also creates a
* new one if file does not exist.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain an **{Error}** object and the second parameter will be null if an error occured. Otherwise, the first parameter will be null and the second will contain the reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.open = function(callback) {
if( this.mode != "w" && this.mode != "w+" && this.mode != "r"){
callback(new Error("Illegal mode " + this.mode), null);
return;
}
var self = this;
if((self.mode == "w" || self.mode == "w+") && self.db.serverConfig.primary != null) {
// Get files collection
self.collection(function(err, collection) {
if(err) return callback(err);
// Put index on filename
collection.ensureIndex([['filename', 1]], function(err, index) {
if(err) return callback(err);
// Get chunk collection
self.chunkCollection(function(err, chunkCollection) {
if(err) return callback(err);
// Ensure index on chunk collection
chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) {
if(err) return callback(err);
_open(self, callback);
});
});
});
});
} else {
// Open the gridstore
_open(self, callback);
}
};
/**
* Hidding the _open function
* @ignore
* @api private
*/
var _open = function(self, callback) {
self.collection(function(err, collection) {
if(err!==null) {
callback(new Error("at collection: "+err), null);
return;
}
// Create the query
var query = self.referenceBy == REFERENCE_BY_ID ? {_id:self.fileId} : {filename:self.filename};
query = null == self.fileId && this.filename == null ? null : query;
// Fetch the chunks
if(query != null) {
collection.find(query, {readPreference:self.readPreference}, function(err, cursor) {
if(err) return error(err);
// Fetch the file
cursor.nextObject(function(err, doc) {
if(err) return error(err);
// Check if the collection for the files exists otherwise prepare the new one
if(doc != null) {
self.fileId = doc._id;
self.filename = doc.filename;
self.contentType = doc.contentType;
self.internalChunkSize = doc.chunkSize;
self.uploadDate = doc.uploadDate;
self.aliases = doc.aliases;
self.length = doc.length;
self.metadata = doc.metadata;
self.internalMd5 = doc.md5;
} else if (self.mode != 'r') {
self.fileId = self.fileId == null ? new ObjectID() : self.fileId;
self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE;
self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize;
self.length = 0;
} else {
self.length = 0;
var txtId = self.fileId instanceof ObjectID ? self.fileId.toHexString() : self.fileId;
return error(new Error((self.referenceBy == REFERENCE_BY_ID ? txtId : self.filename) + " does not exist", self));
}
// Process the mode of the object
if(self.mode == "r") {
nthChunk(self, 0, function(err, chunk) {
if(err) return error(err);
self.currentChunk = chunk;
self.position = 0;
callback(null, self);
});
} else if(self.mode == "w") {
// Delete any existing chunks
deleteChunks(self, function(err, result) {
if(err) return error(err);
self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern);
self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type'];
self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size'];
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
self.position = 0;
callback(null, self);
});
} else if(self.mode == "w+") {
nthChunk(self, lastChunkNumber(self), function(err, chunk) {
if(err) return error(err);
// Set the current chunk
self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk;
self.currentChunk.position = self.currentChunk.data.length();
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
self.position = self.length;
callback(null, self);
});
}
});
});
} else {
// Write only mode
self.fileId = null == self.fileId ? new ObjectID() : self.fileId;
self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE;
self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize;
self.length = 0;
self.chunkCollection(function(err, collection2) {
if(err) return error(err);
// No file exists set up write mode
if(self.mode == "w") {
// Delete any existing chunks
deleteChunks(self, function(err, result) {
if(err) return error(err);
self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern);
self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type'];
self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size'];
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
self.position = 0;
callback(null, self);
});
} else if(self.mode == "w+") {
nthChunk(self, lastChunkNumber(self), function(err, chunk) {
if(err) return error(err);
// Set the current chunk
self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk;
self.currentChunk.position = self.currentChunk.data.length();
self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata'];
self.position = self.length;
callback(null, self);
});
}
});
}
});
// only pass error to callback once
function error (err) {
if(error.err) return;
callback(error.err = err);
}
};
/**
* Stores a file from the file system to the GridFS database.
*
* @param {String|Buffer|FileHandle} file the file to store.
* @param {Function} callback this will be called after this method is executed. The first parameter will be null and the the second will contain the reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.writeFile = function (file, callback) {
var self = this;
if (typeof file === 'string') {
fs.open(file, 'r', function (err, fd) {
if(err) return callback(err);
self.writeFile(fd, callback);
});
return;
}
self.open(function (err, self) {
if(err) return callback(err, self);
fs.fstat(file, function (err, stats) {
if(err) return callback(err, self);
var offset = 0;
var index = 0;
var numberOfChunksLeft = Math.min(stats.size / self.chunkSize);
// Write a chunk
var writeChunk = function() {
fs.read(file, self.chunkSize, offset, 'binary', function(err, data, bytesRead) {
if(err) return callback(err, self);
offset = offset + bytesRead;
// Create a new chunk for the data
var chunk = new Chunk(self, {n:index++}, self.writeConcern);
chunk.write(data, function(err, chunk) {
if(err) return callback(err, self);
chunk.save(function(err, result) {
if(err) return callback(err, self);
self.position = self.position + data.length;
// Point to current chunk
self.currentChunk = chunk;
if(offset >= stats.size) {
fs.close(file);
self.close(function(err, result) {
if(err) return callback(err, self);
return callback(null, self);
});
} else {
return processor(writeChunk);
}
});
});
});
}
// Process the first write
processor(writeChunk);
});
});
};
/**
* Writes some data. This method will work properly only if initialized with mode
* "w" or "w+".
*
* @param string {string} The data to write.
* @param close {boolean=false} opt_argument Closes this file after writing if
* true.
* @param callback {function(*, GridStore)} This will be called after executing
* this method. The first parameter will contain null and the second one
* will contain a reference to this object.
*
* @ignore
* @api private
*/
var writeBuffer = function(self, buffer, close, callback) {
if(typeof close === "function") { callback = close; close = null; }
var finalClose = (close == null) ? false : close;
if(self.mode[0] != "w") {
callback(new Error((self.referenceBy == REFERENCE_BY_ID ? self.toHexString() : self.filename) + " not opened for writing"), null);
} else {
if(self.currentChunk.position + buffer.length >= self.chunkSize) {
// Write out the current Chunk and then keep writing until we have less data left than a chunkSize left
// to a new chunk (recursively)
var previousChunkNumber = self.currentChunk.chunkNumber;
var leftOverDataSize = self.chunkSize - self.currentChunk.position;
var firstChunkData = buffer.slice(0, leftOverDataSize);
var leftOverData = buffer.slice(leftOverDataSize);
// A list of chunks to write out
var chunksToWrite = [self.currentChunk.write(firstChunkData)];
// If we have more data left than the chunk size let's keep writing new chunks
while(leftOverData.length >= self.chunkSize) {
// Create a new chunk and write to it
var newChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern);
var firstChunkData = leftOverData.slice(0, self.chunkSize);
leftOverData = leftOverData.slice(self.chunkSize);
// Update chunk number
previousChunkNumber = previousChunkNumber + 1;
// Write data
newChunk.write(firstChunkData);
// Push chunk to save list
chunksToWrite.push(newChunk);
}
// Set current chunk with remaining data
self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern);
// If we have left over data write it
if(leftOverData.length > 0) self.currentChunk.write(leftOverData);
// Update the position for the gridstore
self.position = self.position + buffer.length;
// Total number of chunks to write
var numberOfChunksToWrite = chunksToWrite.length;
// Write out all the chunks and then return
for(var i = 0; i < chunksToWrite.length; i++) {
var chunk = chunksToWrite[i];
chunk.save(function(err, result) {
if(err) return callback(err);
numberOfChunksToWrite = numberOfChunksToWrite - 1;
if(numberOfChunksToWrite <= 0) {
return callback(null, self);
}
})
}
} else {
// Update the position for the gridstore
self.position = self.position + buffer.length;
// We have less data than the chunk size just write it and callback
self.currentChunk.write(buffer);
callback(null, self);
}
}
};
/**
* Creates a mongoDB object representation of this object.
*
* @param callback {function(object)} This will be called after executing this
* method. The object will be passed to the first parameter and will have
* the structure:
*
* <pre><code>
* {
* '_id' : , // {number} id for this file
* 'filename' : , // {string} name for this file
* 'contentType' : , // {string} mime type for this file
* 'length' : , // {number} size of this file?
* 'chunksize' : , // {number} chunk size used by this file
* 'uploadDate' : , // {Date}
* 'aliases' : , // {array of string}
* 'metadata' : , // {string}
* }
* </code></pre>
*
* @ignore
* @api private
*/
var buildMongoObject = function(self, callback) {
// // Keeps the final chunk number
// var chunkNumber = 0;
// var previousChunkSize = 0;
// // Get the correct chunk Number, if we have an empty chunk return the previous chunk number
// if(null != self.currentChunk && self.currentChunk.chunkNumber > 0 && self.currentChunk.position == 0) {
// chunkNumber = self.currentChunk.chunkNumber - 1;
// } else {
// chunkNumber = self.currentChunk.chunkNumber;
// previousChunkSize = self.currentChunk.position;
// }
// // Calcuate the length
// var length = self.currentChunk != null ? (chunkNumber * self.chunkSize + previousChunkSize) : 0;
var mongoObject = {
'_id': self.fileId,
'filename': self.filename,
'contentType': self.contentType,
'length': self.position ? self.position : 0,
'chunkSize': self.chunkSize,
'uploadDate': self.uploadDate,
'aliases': self.aliases,
'metadata': self.metadata
};
var md5Command = {filemd5:self.fileId, root:self.root};
self.db.command(md5Command, function(err, results) {
mongoObject.md5 = results.md5;
callback(mongoObject);
});
};
/**
* Saves this file to the database. This will overwrite the old entry if it
* already exists. This will work properly only if mode was initialized to
* "w" or "w+".
*
* @param {Function} callback this will be called after executing this method. Passes an **{Error}** object to the first parameter and null to the second if an error occured. Otherwise, passes null to the first and a reference to this object to the second.
* @return {null}
* @api public
*/
GridStore.prototype.close = function(callback) {
var self = this;
if(self.mode[0] == "w") {
if(self.currentChunk != null && self.currentChunk.position > 0) {
self.currentChunk.save(function(err, chunk) {
if(err && typeof callback == 'function') return callback(err);
self.collection(function(err, files) {
if(err && typeof callback == 'function') return callback(err);
// Build the mongo object
if(self.uploadDate != null) {
files.remove({'_id':self.fileId}, {safe:true}, function(err, collection) {
if(err && typeof callback == 'function') return callback(err);
buildMongoObject(self, function(mongoObject) {
files.save(mongoObject, self.writeConcern, function(err) {
if(typeof callback == 'function')
callback(err, mongoObject);
});
});
});
} else {
self.uploadDate = new Date();
buildMongoObject(self, function(mongoObject) {
files.save(mongoObject, self.writeConcern, function(err) {
if(typeof callback == 'function')
callback(err, mongoObject);
});
});
}
});
});
} else {
self.collection(function(err, files) {
if(err && typeof callback == 'function') return callback(err);
self.uploadDate = new Date();
buildMongoObject(self, function(mongoObject) {
files.save(mongoObject, self.writeConcern, function(err) {
if(typeof callback == 'function')
callback(err, mongoObject);
});
});
});
}
} else if(self.mode[0] == "r") {
if(typeof callback == 'function')
callback(null, null);
} else {
if(typeof callback == 'function')
callback(new Error("Illegal mode " + self.mode), null);
}
};
/**
* Gets the nth chunk of this file.
*
* @param chunkNumber {number} The nth chunk to retrieve.
* @param callback {function(*, Chunk|object)} This will be called after
* executing this method. null will be passed to the first parameter while
* a new {@link Chunk} instance will be passed to the second parameter if
* the chunk was found or an empty object {} if not.
*
* @ignore
* @api private
*/
var nthChunk = function(self, chunkNumber, callback) {
self.chunkCollection(function(err, collection) {
if(err) return callback(err);
collection.find({'files_id':self.fileId, 'n':chunkNumber}, {readPreference: self.readPreference}, function(err, cursor) {
if(err) return callback(err);
cursor.nextObject(function(err, chunk) {
if(err) return callback(err);
var finalChunk = chunk == null ? {} : chunk;
callback(null, new Chunk(self, finalChunk, self.writeConcern));
});
});
});
};
/**
*
* @ignore
* @api private
*/
GridStore.prototype._nthChunk = function(chunkNumber, callback) {
nthChunk(this, chunkNumber, callback);
}
/**
* @return {Number} The last chunk number of this file.
*
* @ignore
* @api private
*/
var lastChunkNumber = function(self) {
return Math.floor(self.length/self.chunkSize);
};
/**
* Retrieve this file's chunks collection.
*
* @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured.
* @return {null}
* @api public
*/
GridStore.prototype.chunkCollection = function(callback) {
this.db.collection((this.root + ".chunks"), callback);
};
/**
* Deletes all the chunks of this file in the database.
*
* @param callback {function(*, boolean)} This will be called after this method
* executes. Passes null to the first and true to the second argument.
*
* @ignore
* @api private
*/
var deleteChunks = function(self, callback) {
if(self.fileId != null) {
self.chunkCollection(function(err, collection) {
if(err) return callback(err, false);
collection.remove({'files_id':self.fileId}, {safe:true}, function(err, result) {
if(err) return callback(err, false);
callback(null, true);
});
});
} else {
callback(null, true);
}
};
/**
* Deletes all the chunks of this file in the database.
*
* @param {Function} callback this will be called after this method executes. Passes null to the first and true to the second argument.
* @return {null}
* @api public
*/
GridStore.prototype.unlink = function(callback) {
var self = this;
deleteChunks(this, function(err) {
if(err!==null) {
err.message = "at deleteChunks: " + err.message;
return callback(err);
}
self.collection(function(err, collection) {
if(err!==null) {
err.message = "at collection: " + err.message;
return callback(err);
}
collection.remove({'_id':self.fileId}, {safe:true}, function(err) {
callback(err, self);
});
});
});
};
/**
* Retrieves the file collection associated with this object.
*
* @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured.
* @return {null}
* @api public
*/
GridStore.prototype.collection = function(callback) {
this.db.collection(this.root + ".files", callback);
};
/**
* Reads the data of this file.
*
* @param {String} [separator] the character to be recognized as the newline separator.
* @param {Function} callback This will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character.
* @return {null}
* @api public
*/
GridStore.prototype.readlines = function(separator, callback) {
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
separator = args.length ? args.shift() : "\n";
this.read(function(err, data) {
if(err) return callback(err);
var items = data.toString().split(separator);
items = items.length > 0 ? items.splice(0, items.length - 1) : [];
for(var i = 0; i < items.length; i++) {
items[i] = items[i] + separator;
}
callback(null, items);
});
};
/**
* Deletes all the chunks of this file in the database if mode was set to "w" or
* "w+" and resets the read/write head to the initial position.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.rewind = function(callback) {
var self = this;
if(this.currentChunk.chunkNumber != 0) {
if(this.mode[0] == "w") {
deleteChunks(self, function(err, gridStore) {
if(err) return callback(err);
self.currentChunk = new Chunk(self, {'n': 0}, self.writeConcern);
self.position = 0;
callback(null, self);
});
} else {
self.currentChunk(0, function(err, chunk) {
if(err) return callback(err);
self.currentChunk = chunk;
self.currentChunk.rewind();
self.position = 0;
callback(null, self);
});
}
} else {
self.currentChunk.rewind();
self.position = 0;
callback(null, self);
}
};
/**
* Retrieves the contents of this file and advances the read/write head. Works with Buffers only.
*
* There are 3 signatures for this method:
*
* (callback)
* (length, callback)
* (length, buffer, callback)
*
* @param {Number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified.
* @param {String|Buffer} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method.
* @param {Function} callback this will be called after this method is executed. null will be passed to the first parameter and a string containing the contents of the buffer concatenated with the contents read from this file will be passed to the second.
* @return {null}
* @api public
*/
GridStore.prototype.read = function(length, buffer, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 0);
callback = args.pop();
length = args.length ? args.shift() : null;
buffer = args.length ? args.shift() : null;
// The data is a c-terminated string and thus the length - 1
var finalLength = length == null ? self.length - self.position : length;
var finalBuffer = buffer == null ? new Buffer(finalLength) : buffer;
// Add a index to buffer to keep track of writing position or apply current index
finalBuffer._index = buffer != null && buffer._index != null ? buffer._index : 0;
if((self.currentChunk.length() - self.currentChunk.position + finalBuffer._index) >= finalLength) {
var slice = self.currentChunk.readSlice(finalLength - finalBuffer._index);
// Copy content to final buffer
slice.copy(finalBuffer, finalBuffer._index);
// Update internal position
self.position = self.position + finalBuffer.length;
// Check if we don't have a file at all
if(finalLength == 0 && finalBuffer.length == 0) return callback(new Error("File does not exist"), null);
// Else return data
callback(null, finalBuffer);
} else {
var slice = self.currentChunk.readSlice(self.currentChunk.length() - self.currentChunk.position);
// Copy content to final buffer
slice.copy(finalBuffer, finalBuffer._index);
// Update index position
finalBuffer._index += slice.length;
// Load next chunk and read more
nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) {
if(err) return callback(err);
if(chunk.length() > 0) {
self.currentChunk = chunk;
self.read(length, finalBuffer, callback);
} else {
if (finalBuffer._index > 0) {
callback(null, finalBuffer)
} else {
callback(new Error("no chunks found for file, possibly corrupt"), null);
}
}
});
}
}
/**
* Retrieves the position of the read/write head of this file.
*
* @param {Function} callback This gets called after this method terminates. null is passed to the first parameter and the position is passed to the second.
* @return {null}
* @api public
*/
GridStore.prototype.tell = function(callback) {
callback(null, this.position);
};
/**
* Moves the read/write head to a new location.
*
* There are 3 signatures for this method
*
* Seek Location Modes
* - **GridStore.IO_SEEK_SET**, **(default)** set the position from the start of the file.
* - **GridStore.IO_SEEK_CUR**, set the position from the current position in the file.
* - **GridStore.IO_SEEK_END**, set the position from the end of the file.
*
* @param {Number} [position] the position to seek to
* @param {Number} [seekLocation] seek mode. Use one of the Seek Location modes.
* @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.seek = function(position, seekLocation, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
seekLocation = args.length ? args.shift() : null;
var seekLocationFinal = seekLocation == null ? exports.GridStore.IO_SEEK_SET : seekLocation;
var finalPosition = position;
var targetPosition = 0;
// Calculate the position
if(seekLocationFinal == exports.GridStore.IO_SEEK_CUR) {
targetPosition = self.position + finalPosition;
} else if(seekLocationFinal == exports.GridStore.IO_SEEK_END) {
targetPosition = self.length + finalPosition;
} else {
targetPosition = finalPosition;
}
// Get the chunk
var newChunkNumber = Math.floor(targetPosition/self.chunkSize);
if(newChunkNumber != self.currentChunk.chunkNumber) {
var seekChunk = function() {
nthChunk(self, newChunkNumber, function(err, chunk) {
self.currentChunk = chunk;
self.position = targetPosition;
self.currentChunk.position = (self.position % self.chunkSize);
callback(err, self);
});
};
if(self.mode[0] == 'w') {
self.currentChunk.save(function(err) {
if(err) return callback(err);
seekChunk();
});
} else {
seekChunk();
}
} else {
self.position = targetPosition;
self.currentChunk.position = (self.position % self.chunkSize);
callback(null, self);
}
};
/**
* Verify if the file is at EOF.
*
* @return {Boolean} true if the read/write head is at the end of this file.
* @api public
*/
GridStore.prototype.eof = function() {
return this.position == this.length ? true : false;
};
/**
* Retrieves a single character from this file.
*
* @param {Function} callback this gets called after this method is executed. Passes null to the first parameter and the character read to the second or null to the second if the read/write head is at the end of the file.
* @return {null}
* @api public
*/
GridStore.prototype.getc = function(callback) {
var self = this;
if(self.eof()) {
callback(null, null);
} else if(self.currentChunk.eof()) {
nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) {
self.currentChunk = chunk;
self.position = self.position + 1;
callback(err, self.currentChunk.getc());
});
} else {
self.position = self.position + 1;
callback(null, self.currentChunk.getc());
}
};
/**
* Writes a string to the file with a newline character appended at the end if
* the given string does not have one.
*
* @param {String} string the string to write.
* @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.puts = function(string, callback) {
var finalString = string.match(/\n$/) == null ? string + "\n" : string;
this.write(finalString, callback);
};
/**
* Returns read stream based on this GridStore file
*
* Events
* - **data** {function(item) {}} the data event triggers when a document is ready.
* - **end** {function() {}} the end event triggers when there is no more documents available.
* - **close** {function() {}} the close event triggers when the stream is closed.
* - **error** {function(err) {}} the error event triggers if an error happens.
*
* @param {Boolean} autoclose if true current GridStore will be closed when EOF and 'close' event will be fired
* @return {null}
* @api public
*/
GridStore.prototype.stream = function(autoclose) {
return new ReadStream(autoclose, this);
};
/**
* The collection to be used for holding the files and chunks collection.
*
* @classconstant DEFAULT_ROOT_COLLECTION
**/
GridStore.DEFAULT_ROOT_COLLECTION = 'fs';
/**
* Default file mime type
*
* @classconstant DEFAULT_CONTENT_TYPE
**/
GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream';
/**
* Seek mode where the given length is absolute.
*
* @classconstant IO_SEEK_SET
**/
GridStore.IO_SEEK_SET = 0;
/**
* Seek mode where the given length is an offset to the current read/write head.
*
* @classconstant IO_SEEK_CUR
**/
GridStore.IO_SEEK_CUR = 1;
/**
* Seek mode where the given length is an offset to the end of the file.
*
* @classconstant IO_SEEK_END
**/
GridStore.IO_SEEK_END = 2;
/**
* Checks if a file exists in the database.
*
* Options
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
*
* @param {Db} db the database to query.
* @param {String} name the name of the file to look for.
* @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
* @param {Function} callback this will be called after this method executes. Passes null to the first and passes true to the second if the file exists and false otherwise.
* @return {null}
* @api public
*/
GridStore.exist = function(db, fileIdObject, rootCollection, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
rootCollection = args.length ? args.shift() : null;
options = args.length ? args.shift() : {};
// Establish read preference
var readPreference = options.readPreference || 'primary';
// Fetch collection
var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION;
db.collection(rootCollectionFinal + ".files", function(err, collection) {
if(err) return callback(err);
// Build query
var query = (typeof fileIdObject == 'string' || Object.prototype.toString.call(fileIdObject) == '[object RegExp]' )
? {'filename':fileIdObject}
: {'_id':fileIdObject}; // Attempt to locate file
collection.find(query, {readPreference:readPreference}, function(err, cursor) {
if(err) return callback(err);
cursor.nextObject(function(err, item) {
if(err) return callback(err);
callback(null, item == null ? false : true);
});
});
});
};
/**
* Gets the list of files stored in the GridFS.
*
* @param {Db} db the database to query.
* @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**.
* @param {Function} callback this will be called after this method executes. Passes null to the first and passes an array of strings containing the names of the files.
* @return {null}
* @api public
*/
GridStore.list = function(db, rootCollection, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = args.pop();
rootCollection = args.length ? args.shift() : null;
options = args.length ? args.shift() : {};
// Ensure we have correct values
if(rootCollection != null && typeof rootCollection == 'object') {
options = rootCollection;
rootCollection = null;
}
// Establish read preference
var readPreference = options.readPreference || 'primary';
// Check if we are returning by id not filename
var byId = options['id'] != null ? options['id'] : false;
// Fetch item
var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION;
var items = [];
db.collection((rootCollectionFinal + ".files"), function(err, collection) {
if(err) return callback(err);
collection.find({}, {readPreference:readPreference}, function(err, cursor) {
if(err) return callback(err);
cursor.each(function(err, item) {
if(item != null) {
items.push(byId ? item._id : item.filename);
} else {
callback(err, items);
}
});
});
});
};
/**
* Reads the contents of a file.
*
* This method has the following signatures
*
* (db, name, callback)
* (db, name, length, callback)
* (db, name, length, offset, callback)
* (db, name, length, offset, options, callback)
*
* @param {Db} db the database to query.
* @param {String} name the name of the file.
* @param {Number} [length] the size of data to read.
* @param {Number} [offset] the offset from the head of the file of which to start reading from.
* @param {Object} [options] the options for the file.
* @param {Function} callback this will be called after this method executes. A string with an error message will be passed to the first parameter when the length and offset combination exceeds the length of the file while an Error object will be passed if other forms of error occured, otherwise, a string is passed. The second parameter will contain the data read if successful or null if an error occured.
* @return {null}
* @api public
*/
GridStore.read = function(db, name, length, offset, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
length = args.length ? args.shift() : null;
offset = args.length ? args.shift() : null;
options = args.length ? args.shift() : null;
new GridStore(db, name, "r", options).open(function(err, gridStore) {
if(err) return callback(err);
// Make sure we are not reading out of bounds
if(offset && offset >= gridStore.length) return callback("offset larger than size of file", null);
if(length && length > gridStore.length) return callback("length is larger than the size of the file", null);
if(offset && length && (offset + length) > gridStore.length) return callback("offset and length is larger than the size of the file", null);
if(offset != null) {
gridStore.seek(offset, function(err, gridStore) {
if(err) return callback(err);
gridStore.read(length, callback);
});
} else {
gridStore.read(length, callback);
}
});
};
/**
* Reads the data of this file.
*
* @param {Db} db the database to query.
* @param {String} name the name of the file.
* @param {String} [separator] the character to be recognized as the newline separator.
* @param {Object} [options] file options.
* @param {Function} callback this will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character.
* @return {null}
* @api public
*/
GridStore.readlines = function(db, name, separator, options, callback) {
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
separator = args.length ? args.shift() : null;
options = args.length ? args.shift() : null;
var finalSeperator = separator == null ? "\n" : separator;
new GridStore(db, name, "r", options).open(function(err, gridStore) {
if(err) return callback(err);
gridStore.readlines(finalSeperator, callback);
});
};
/**
* Deletes the chunks and metadata information of a file from GridFS.
*
* @param {Db} db the database to interact with.
* @param {String|Array} names the name/names of the files to delete.
* @param {Object} [options] the options for the files.
* @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object.
* @return {null}
* @api public
*/
GridStore.unlink = function(db, names, options, callback) {
var self = this;
var args = Array.prototype.slice.call(arguments, 2);
callback = args.pop();
options = args.length ? args.shift() : null;
if(names.constructor == Array) {
var tc = 0;
for(var i = 0; i < names.length; i++) {
++tc;
self.unlink(db, names[i], options, function(result) {
if(--tc == 0) {
callback(null, self);
}
});
}
} else {
new GridStore(db, names, "w", options).open(function(err, gridStore) {
if(err) return callback(err);
deleteChunks(gridStore, function(err, result) {
if(err) return callback(err);
gridStore.collection(function(err, collection) {
if(err) return callback(err);
collection.remove({'_id':gridStore.fileId}, {safe:true}, function(err, collection) {
callback(err, self);
});
});
});
});
}
};
/**
* Returns the current chunksize of the file.
*
* @field chunkSize
* @type {Number}
* @getter
* @setter
* @property return number of bytes in the current chunkSize.
*/
Object.defineProperty(GridStore.prototype, "chunkSize", { enumerable: true
, get: function () {
return this.internalChunkSize;
}
, set: function(value) {
if(!(this.mode[0] == "w" && this.position == 0 && this.uploadDate == null)) {
this.internalChunkSize = this.internalChunkSize;
} else {
this.internalChunkSize = value;
}
}
});
/**
* The md5 checksum for this file.
*
* @field md5
* @type {Number}
* @getter
* @setter
* @property return this files md5 checksum.
*/
Object.defineProperty(GridStore.prototype, "md5", { enumerable: true
, get: function () {
return this.internalMd5;
}
});
/**
* GridStore Streaming methods
* Handles the correct return of the writeable stream status
* @ignore
*/
Object.defineProperty(GridStore.prototype, "writable", { enumerable: true
, get: function () {
if(this._writeable == null) {
this._writeable = this.mode != null && this.mode.indexOf("w") != -1;
}
// Return the _writeable
return this._writeable;
}
, set: function(value) {
this._writeable = value;
}
});
/**
* Handles the correct return of the readable stream status
* @ignore
*/
Object.defineProperty(GridStore.prototype, "readable", { enumerable: true
, get: function () {
if(this._readable == null) {
this._readable = this.mode != null && this.mode.indexOf("r") != -1;
}
return this._readable;
}
, set: function(value) {
this._readable = value;
}
});
GridStore.prototype.paused;
/**
* Handles the correct setting of encoding for the stream
* @ignore
*/
GridStore.prototype.setEncoding = fs.ReadStream.prototype.setEncoding;
/**
* Handles the end events
* @ignore
*/
GridStore.prototype.end = function end(data) {
var self = this;
// allow queued data to write before closing
if(!this.writable) return;
this.writable = false;
if(data) {
this._q.push(data);
}
this.on('drain', function () {
self.close(function (err) {
if (err) return _error(self, err);
self.emit('close');
});
});
_flush(self);
}
/**
* Handles the normal writes to gridstore
* @ignore
*/
var _writeNormal = function(self, data, close, callback) {
// If we have a buffer write it using the writeBuffer method
if(Buffer.isBuffer(data)) {
return writeBuffer(self, data, close, callback);
} else {
// Wrap the string in a buffer and write
return writeBuffer(self, new Buffer(data, 'binary'), close, callback);
}
}
/**
* Writes some data. This method will work properly only if initialized with mode "w" or "w+".
*
* @param {String|Buffer} data the data to write.
* @param {Boolean} [close] closes this file after writing if set to true.
* @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object.
* @return {null}
* @api public
*/
GridStore.prototype.write = function write(data, close, callback) {
// If it's a normal write delegate the call
if(typeof close == 'function' || typeof callback == 'function') {
return _writeNormal(this, data, close, callback);
}
// Otherwise it's a stream write
var self = this;
if (!this.writable) {
throw new Error('GridWriteStream is not writable');
}
// queue data until we open.
if (!this._opened) {
// Set up a queue to save data until gridstore object is ready
this._q = [];
_openStream(self);
this._q.push(data);
return false;
}
// Push data to queue
this._q.push(data);
_flush(this);
// Return write successful
return true;
}
/**
* Handles the destroy part of a stream
* @ignore
*/
GridStore.prototype.destroy = function destroy() {
// close and do not emit any more events. queued data is not sent.
if(!this.writable) return;
this.readable = false;
if(this.writable) {
this.writable = false;
this._q.length = 0;
this.emit('close');
}
}
/**
* Handles the destroySoon part of a stream
* @ignore
*/
GridStore.prototype.destroySoon = function destroySoon() {
// as soon as write queue is drained, destroy.
// may call destroy immediately if no data is queued.
if(!this._q.length) {
return this.destroy();
}
this._destroying = true;
}
/**
* Handles the pipe part of the stream
* @ignore
*/
GridStore.prototype.pipe = function(destination, options) {
var self = this;
// Open the gridstore
this.open(function(err, result) {
if(err) _errorRead(self, err);
if(!self.readable) return;
// Set up the pipe
self._pipe(destination, options);
// Emit the stream is open
self.emit('open');
// Read from the stream
_read(self);
})
}
/**
* Internal module methods
* @ignore
*/
var _read = function _read(self) {
if (!self.readable || self.paused || self.reading) {
return;
}
self.reading = true;
var stream = self._stream = self.stream();
stream.paused = self.paused;
stream.on('data', function (data) {
if (self._decoder) {
var str = self._decoder.write(data);
if (str.length) self.emit('data', str);
} else {
self.emit('data', data);
}
});
stream.on('end', function (data) {
self.emit('end', data);
});
stream.on('error', function (data) {
_errorRead(self, data);
});
stream.on('close', function (data) {
self.emit('close', data);
});
self.pause = function () {
// native doesn't always pause.
// bypass its pause() method to hack it
self.paused = stream.paused = true;
}
self.resume = function () {
if(!self.paused) return;
self.paused = false;
stream.resume();
self.readable = stream.readable;
}
self.destroy = function () {
self.readable = false;
stream.destroy();
}
}
/**
* pause
* @ignore
*/
GridStore.prototype.pause = function pause () {
// Overridden when the GridStore opens.
this.paused = true;
}
/**
* resume
* @ignore
*/
GridStore.prototype.resume = function resume () {
// Overridden when the GridStore opens.
this.paused = false;
}
/**
* Internal module methods
* @ignore
*/
var _flush = function _flush(self, _force) {
if (!self._opened) return;
if (!_force && self._flushing) return;
self._flushing = true;
// write the entire q to gridfs
if (!self._q.length) {
self._flushing = false;
self.emit('drain');
if(self._destroying) {
self.destroy();
}
return;
}
self.write(self._q.shift(), function (err, store) {
if (err) return _error(self, err);
self.emit('progress', store.position);
_flush(self, true);
});
}
var _openStream = function _openStream (self) {
if(self._opening == true) return;
self._opening = true;
// Open the store
self.open(function (err, gridstore) {
if (err) return _error(self, err);
self._opened = true;
self.emit('open');
_flush(self);
});
}
var _error = function _error(self, err) {
self.destroy();
self.emit('error', err);
}
var _errorRead = function _errorRead (self, err) {
self.readable = false;
self.emit('error', err);
}
/**
* @ignore
*/
var _hasWriteConcern = function(errorOptions) {
return errorOptions == true
|| errorOptions.w > 0
|| errorOptions.w == 'majority'
|| errorOptions.j == true
|| errorOptions.journal == true
|| errorOptions.fsync == true
}
/**
* @ignore
*/
var _setWriteConcernHash = function(options) {
var finalOptions = {};
if(options.w != null) finalOptions.w = options.w;
if(options.journal == true) finalOptions.j = options.journal;
if(options.j == true) finalOptions.j = options.j;
if(options.fsync == true) finalOptions.fsync = options.fsync;
if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout;
return finalOptions;
}
/**
* @ignore
*/
var _getWriteConcern = function(self, options, callback) {
// Final options
var finalOptions = {w:1};
options = options || {};
// Local options verification
if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(options);
} else if(typeof options.safe == "boolean") {
finalOptions = {w: (options.safe ? 1 : 0)};
} else if(options.safe != null && typeof options.safe == 'object') {
finalOptions = _setWriteConcernHash(options.safe);
} else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.db.safe);
} else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') {
finalOptions = _setWriteConcernHash(self.db.options);
} else if(typeof self.db.safe == "boolean") {
finalOptions = {w: (self.db.safe ? 1 : 0)};
}
// Ensure we don't have an invalid combination of write concerns
if(finalOptions.w < 1
&& (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true");
// Return the options
return finalOptions;
}
/**
* @ignore
* @api private
*/
exports.GridStore = GridStore;
var Stream = require('stream').Stream,
timers = require('timers'),
util = require('util');
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
/**
* ReadStream
*
* Returns a stream interface for the **file**.
*
* Events
* - **data** {function(item) {}} the data event triggers when a document is ready.
* - **end** {function() {}} the end event triggers when there is no more documents available.
* - **close** {function() {}} the close event triggers when the stream is closed.
* - **error** {function(err) {}} the error event triggers if an error happens.
*
* @class Represents a GridFS File Stream.
* @param {Boolean} autoclose automatically close file when the stream reaches the end.
* @param {GridStore} cursor a cursor object that the stream wraps.
* @return {ReadStream}
*/
function ReadStream(autoclose, gstore) {
if (!(this instanceof ReadStream)) return new ReadStream(autoclose, gstore);
Stream.call(this);
this.autoclose = !!autoclose;
this.gstore = gstore;
this.finalLength = gstore.length - gstore.position;
this.completedLength = 0;
this.currentChunkNumber = gstore.currentChunk.chunkNumber;
this.paused = false;
this.readable = true;
this.pendingChunk = null;
this.executing = false;
// Calculate the number of chunks
this.numberOfChunks = Math.ceil(gstore.length/gstore.chunkSize);
// This seek start position inside the current chunk
this.seekStartPosition = gstore.position - (this.currentChunkNumber * gstore.chunkSize);
var self = this;
processor(function() {
self._execute();
});
};
/**
* Inherit from Stream
* @ignore
* @api private
*/
ReadStream.prototype.__proto__ = Stream.prototype;
/**
* Flag stating whether or not this stream is readable.
*/
ReadStream.prototype.readable;
/**
* Flag stating whether or not this stream is paused.
*/
ReadStream.prototype.paused;
/**
* @ignore
* @api private
*/
ReadStream.prototype._execute = function() {
if(this.paused === true || this.readable === false) {
return;
}
var gstore = this.gstore;
var self = this;
// Set that we are executing
this.executing = true;
var last = false;
var toRead = 0;
if(gstore.currentChunk.chunkNumber >= (this.numberOfChunks - 1)) {
self.executing = false;
last = true;
}
// Data setup
var data = null;
// Read a slice (with seek set if none)
if(this.seekStartPosition > 0 && (gstore.currentChunk.length() - this.seekStartPosition) > 0) {
data = gstore.currentChunk.readSlice(gstore.currentChunk.length() - this.seekStartPosition);
this.seekStartPosition = 0;
} else {
data = gstore.currentChunk.readSlice(gstore.currentChunk.length());
}
// Return the data
if(data != null && gstore.currentChunk.chunkNumber == self.currentChunkNumber) {
self.currentChunkNumber = self.currentChunkNumber + 1;
self.completedLength += data.length;
self.pendingChunk = null;
self.emit("data", data);
}
if(last === true) {
self.readable = false;
self.emit("end");
if(self.autoclose === true) {
if(gstore.mode[0] == "w") {
gstore.close(function(err, doc) {
if (err) {
self.emit("error", err);
return;
}
self.readable = false;
self.emit("close", doc);
});
} else {
self.readable = false;
self.emit("close");
}
}
} else {
gstore._nthChunk(gstore.currentChunk.chunkNumber + 1, function(err, chunk) {
if(err) {
self.readable = false;
self.emit("error", err);
self.executing = false;
return;
}
self.pendingChunk = chunk;
if(self.paused === true) {
self.executing = false;
return;
}
gstore.currentChunk = self.pendingChunk;
self._execute();
});
}
};
/**
* Pauses this stream, then no farther events will be fired.
*
* @ignore
* @api public
*/
ReadStream.prototype.pause = function() {
if(!this.executing) {
this.paused = true;
}
};
/**
* Destroys the stream, then no farther events will be fired.
*
* @ignore
* @api public
*/
ReadStream.prototype.destroy = function() {
this.readable = false;
// Emit close event
this.emit("close");
};
/**
* Resumes this stream.
*
* @ignore
* @api public
*/
ReadStream.prototype.resume = function() {
if(this.paused === false || !this.readable) {
return;
}
this.paused = false;
var self = this;
processor(function() {
self._execute();
});
};
exports.ReadStream = ReadStream;
try {
exports.BSONPure = require('bson').BSONPure;
exports.BSONNative = require('bson').BSONNative;
} catch(err) {
// do nothing
}
// export the driver version
exports.version = require('../../package').version;
[ 'commands/base_command'
, 'admin'
, 'collection'
, 'connection/read_preference'
, 'connection/connection'
, 'connection/server'
, 'connection/mongos'
, 'connection/repl_set/repl_set'
, 'mongo_client'
, 'cursor'
, 'db'
, 'mongo_client'
, 'gridfs/grid'
, 'gridfs/chunk'
, 'gridfs/gridstore'].forEach(function (path) {
var module = require('./' + path);
for (var i in module) {
exports[i] = module[i];
}
});
// backwards compat
exports.ReplSetServers = exports.ReplSet;
// Add BSON Classes
exports.Binary = require('bson').Binary;
exports.Code = require('bson').Code;
exports.DBRef = require('bson').DBRef;
exports.Double = require('bson').Double;
exports.Long = require('bson').Long;
exports.MinKey = require('bson').MinKey;
exports.MaxKey = require('bson').MaxKey;
exports.ObjectID = require('bson').ObjectID;
exports.Symbol = require('bson').Symbol;
exports.Timestamp = require('bson').Timestamp;
// Add BSON Parser
exports.BSON = require('bson').BSONPure.BSON;
// Get the Db object
var Db = require('./db').Db;
// Set up the connect function
var connect = Db.connect;
var obj = connect;
// Map all values to the exports value
for(var name in exports) {
obj[name] = exports[name];
}
// Add the pure and native backward compatible functions
exports.pure = exports.native = function() {
return obj;
}
// Map all values to the exports value
for(var name in exports) {
connect[name] = exports[name];
}
// Set our exports to be the connect function
module.exports = connect;
var Db = require('./db').Db
, Server = require('./connection/server').Server
, Mongos = require('./connection/mongos').Mongos
, ReplSet = require('./connection/repl_set/repl_set').ReplSet
, ReadPreference = require('./connection/read_preference').ReadPreference
, inherits = require('util').inherits
, EventEmitter = require('events').EventEmitter
, parse = require('./connection/url_parser').parse;
/**
* Create a new MongoClient instance.
*
* Options
* - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write
* - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option)
* - **fsync**, (Boolean, default:false) write waits for fsync before returning
* - **journal**, (Boolean, default:false) write waits for journal sync before returning
* - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST).
* - **native_parser** {Boolean, default:false}, use c++ bson parser.
* - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client.
* - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation.
* - **serializeFunctions** {Boolean, default:false}, serialize functions.
* - **raw** {Boolean, default:false}, peform operations using raw bson buffers.
* - **recordQueryStats** {Boolean, default:false}, record query statistics during execution.
* - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries.
* - **numberOfRetries** {Number, default:5}, number of retries off connection.
*
* Deprecated Options
* - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB.
*
* @class Represents a MongoClient
* @param {Object} serverConfig server config object.
* @param {Object} [options] additional options for the collection.
*/
function MongoClient(serverConfig, options) {
if(serverConfig != null) {
options = options == null ? {} : options;
// If no write concern is set set the default to w:1
if(options != null && !options.journal && !options.w && !options.fsync) {
options.w = 1;
}
// The internal db instance we are wrapping
this._db = new Db('test', serverConfig, options);
}
}
/**
* @ignore
*/
inherits(MongoClient, EventEmitter);
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Options
* - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication
* - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor**
* - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor**
* - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor**
* - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor**
*
* @param {String} url connection url for MongoDB.
* @param {Object} [options] optional options for insert command
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured.
* @return {null}
* @api public
*/
MongoClient.prototype.connect = function(url, options, callback) {
var self = this;
if(typeof options == 'function') {
callback = options;
options = {};
}
MongoClient.connect(url, options, function(err, db) {
if(err) return callback(err, db);
// Store internal db instance reference
self._db = db;
// Emit open and perform callback
self.emit("open", err, db);
callback(err, db);
});
}
/**
* Initialize the database connection.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the connected mongoclient or null if an error occured.
* @return {null}
* @api public
*/
MongoClient.prototype.open = function(callback) {
// Self reference
var self = this;
// Open the db
this._db.open(function(err, db) {
if(err) return callback(err, null);
// Emit open event
self.emit("open", err, db);
// Callback
callback(null, self);
})
}
/**
* Close the current db connection, including all the child db instances. Emits close event if no callback is provided.
*
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the close method or null if an error occured.
* @return {null}
* @api public
*/
MongoClient.prototype.close = function(callback) {
this._db.close(callback);
}
/**
* Create a new Db instance sharing the current socket connections.
*
* @param {String} dbName the name of the database we want to use.
* @return {Db} a db instance using the new database.
* @api public
*/
MongoClient.prototype.db = function(dbName) {
return this._db.db(dbName);
}
/**
* Connect to MongoDB using a url as documented at
*
* docs.mongodb.org/manual/reference/connection-string/
*
* Options
* - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication
* - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor**
* - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor**
* - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor**
* - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor**
*
* @param {String} url connection url for MongoDB.
* @param {Object} [options] optional options for insert command
* @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured.
* @return {null}
* @api public
*/
MongoClient.connect = function(url, options, callback) {
var args = Array.prototype.slice.call(arguments, 1);
callback = typeof args[args.length - 1] == 'function' ? args.pop() : null;
options = args.length ? args.shift() : null;
options = options || {};
// Set default empty server options
var serverOptions = options.server || {};
var mongosOptions = options.mongos || {};
var replSetServersOptions = options.replSet || options.replSetServers || {};
var dbOptions = options.db || {};
// If callback is null throw an exception
if(callback == null)
throw new Error("no callback function provided");
// Parse the string
var object = parse(url, options);
// Merge in any options for db in options object
if(dbOptions) {
for(var name in dbOptions) object.db_options[name] = dbOptions[name];
}
// Added the url to the options
object.db_options.url = url;
// Merge in any options for server in options object
if(serverOptions) {
for(var name in serverOptions) object.server_options[name] = serverOptions[name];
}
// Merge in any replicaset server options
if(replSetServersOptions) {
for(var name in replSetServersOptions) object.rs_options[name] = replSetServersOptions[name];
}
// Merge in any replicaset server options
if(mongosOptions) {
for(var name in mongosOptions) object.mongos_options[name] = mongosOptions[name];
}
// We need to ensure that the list of servers are only either direct members or mongos
// they cannot be a mix of monogs and mongod's
var totalNumberOfServers = object.servers.length;
var totalNumberOfMongosServers = 0;
var totalNumberOfMongodServers = 0;
var serverConfig = null;
var errorServers = {};
// Failure modes
if(object.servers.length == 0) throw new Error("connection string must contain at least one seed host");
// If we have no db setting for the native parser try to set the c++ one first
object.db_options.native_parser = _setNativeParser(object.db_options);
// If no auto_reconnect is set, set it to true as default for single servers
if(typeof object.server_options.auto_reconnect != 'boolean') {
object.server_options.auto_reconnect = true;
}
// If we have more than a server, it could be replicaset or mongos list
// need to verify that it's one or the other and fail if it's a mix
// Connect to all servers and run ismaster
for(var i = 0; i < object.servers.length; i++) {
// Set up socket options
var _server_options = {poolSize:1, socketOptions:{connectTimeoutMS:1000}, auto_reconnect:false};
// Ensure we have ssl setup for the servers
if(object.rs_options.ssl) {
_server_options.ssl = object.rs_options.ssl;
_server_options.sslValidate = object.rs_options.sslValidate;
_server_options.sslCA = object.rs_options.sslCA;
_server_options.sslCert = object.rs_options.sslCert;
_server_options.sslKey = object.rs_options.sslKey;
_server_options.sslPass = object.rs_options.sslPass;
} else if(object.server_options.ssl) {
_server_options.ssl = object.server_options.ssl;
_server_options.sslValidate = object.server_options.sslValidate;
_server_options.sslCA = object.server_options.sslCA;
_server_options.sslCert = object.server_options.sslCert;
_server_options.sslKey = object.server_options.sslKey;
_server_options.sslPass = object.server_options.sslPass;
}
// Set up the Server object
var _server = object.servers[i].domain_socket
? new Server(object.servers[i].domain_socket, _server_options)
: new Server(object.servers[i].host, object.servers[i].port, _server_options);
var connectFunction = function(__server) {
// Attempt connect
new Db(object.dbName, __server, {safe:false, native_parser:false}).open(function(err, db) {
// Update number of servers
totalNumberOfServers = totalNumberOfServers - 1;
// If no error do the correct checks
if(!err) {
// Close the connection
db.close(true);
var isMasterDoc = db.serverConfig.isMasterDoc;
// Check what type of server we have
if(isMasterDoc.setName) totalNumberOfMongodServers++;
if(isMasterDoc.msg && isMasterDoc.msg == "isdbgrid") totalNumberOfMongosServers++;
} else {
errorServers[__server.host + ":" + __server.port] = __server;
}
if(totalNumberOfServers == 0) {
// If we have a mix of mongod and mongos, throw an error
if(totalNumberOfMongosServers > 0 && totalNumberOfMongodServers > 0) {
return process.nextTick(function() {
try {
callback(new Error("cannot combine a list of replicaset seeds and mongos seeds"));
} catch (err) {
if(db) db.close();
throw err
}
})
}
if(totalNumberOfMongodServers == 0 && object.servers.length == 1) {
var obj = object.servers[0];
serverConfig = obj.domain_socket ?
new Server(obj.domain_socket, object.server_options)
: new Server(obj.host, obj.port, object.server_options);
} else if(totalNumberOfMongodServers > 0 || totalNumberOfMongosServers > 0) {
var finalServers = object.servers
.filter(function(serverObj) {
return errorServers[serverObj.host + ":" + serverObj.port] == null;
})
.map(function(serverObj) {
return new Server(serverObj.host, serverObj.port, object.server_options);
});
// Clean out any error servers
errorServers = {};
// Set up the final configuration
if(totalNumberOfMongodServers > 0) {
serverConfig = new ReplSet(finalServers, object.rs_options);
} else {
serverConfig = new Mongos(finalServers, object.mongos_options);
}
}
if(serverConfig == null) {
return process.nextTick(function() {
try {
callback(new Error("Could not locate any valid servers in initial seed list"));
} catch (err) {
if(db) db.close();
throw err
}
});
}
// Ensure no firing off open event before we are ready
serverConfig.emitOpen = false;
// Set up all options etc and connect to the database
_finishConnecting(serverConfig, object, options, callback)
}
});
}
// Wrap the context of the call
connectFunction(_server);
}
}
var _setNativeParser = function(db_options) {
if(typeof db_options.native_parser == 'boolean') return db_options.native_parser;
try {
require('bson').BSONNative.BSON;
return true;
} catch(err) {
return false;
}
}
var _finishConnecting = function(serverConfig, object, options, callback) {
// Safe settings
var safe = {};
// Build the safe parameter if needed
if(object.db_options.journal) safe.j = object.db_options.journal;
if(object.db_options.w) safe.w = object.db_options.w;
if(object.db_options.fsync) safe.fsync = object.db_options.fsync;
if(object.db_options.wtimeoutMS) safe.wtimeout = object.db_options.wtimeoutMS;
// If we have a read Preference set
if(object.db_options.read_preference) {
var readPreference = new ReadPreference(object.db_options.read_preference);
// If we have the tags set up
if(object.db_options.read_preference_tags)
readPreference = new ReadPreference(object.db_options.read_preference, object.db_options.read_preference_tags);
// Add the read preference
object.db_options.readPreference = readPreference;
}
// No safe mode if no keys
if(Object.keys(safe).length == 0) safe = false;
// Add the safe object
object.db_options.safe = safe;
// Set up the db options
var db = new Db(object.dbName, serverConfig, object.db_options);
// Open the db
db.open(function(err, db){
if(err) {
return process.nextTick(function() {
try {
callback(err, null);
} catch (err) {
if(db) db.close();
throw err
}
});
}
if(db.options !== null && !db.options.safe && !db.options.journal
&& !db.options.w && !db.options.fsync && typeof db.options.w != 'number'
&& (db.options.safe == false && object.db_options.url.indexOf("safe=") == -1)) {
db.options.w = 1;
}
if(err == null && object.auth){
// What db to authenticate against
var authentication_db = db;
if(object.db_options && object.db_options.authSource) {
authentication_db = db.db(object.db_options.authSource);
}
// Build options object
var options = {};
if(object.db_options.authMechanism) options.authMechanism = object.db_options.authMechanism;
if(object.db_options.gssapiServiceName) options.gssapiServiceName = object.db_options.gssapiServiceName;
// Authenticate
authentication_db.authenticate(object.auth.user, object.auth.password, options, function(err, success){
if(success){
process.nextTick(function() {
try {
callback(null, db);
} catch (err) {
if(db) db.close();
throw err
}
});
} else {
if(db) db.close();
process.nextTick(function() {
try {
callback(err ? err : new Error('Could not authenticate user ' + auth[0]), null);
} catch (err) {
if(db) db.close();
throw err
}
});
}
});
} else {
process.nextTick(function() {
try {
callback(err, db);
} catch (err) {
if(db) db.close();
throw err
}
})
}
});
}
exports.MongoClient = MongoClient;
var Long = require('bson').Long
, timers = require('timers');
// Set processor, setImmediate if 0.10 otherwise nextTick
var processor = require('../utils').processor();
/**
Reply message from mongo db
**/
var MongoReply = exports.MongoReply = function() {
this.documents = [];
this.index = 0;
};
MongoReply.prototype.parseHeader = function(binary_reply, bson) {
// Unpack the standard header first
this.messageLength = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
// Fetch the request id for this reply
this.requestId = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
// Fetch the id of the request that triggered the response
this.responseTo = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
// Skip op-code field
this.index = this.index + 4 + 4;
// Unpack the reply message
this.responseFlag = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
// Unpack the cursor id (a 64 bit long integer)
var low_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
var high_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
this.cursorId = new Long(low_bits, high_bits);
// Unpack the starting from
this.startingFrom = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
// Unpack the number of objects returned
this.numberReturned = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
this.index = this.index + 4;
}
MongoReply.prototype.parseBody = function(binary_reply, bson, raw, callback) {
raw = raw == null ? false : raw;
try {
// Let's unpack all the bson documents, deserialize them and store them
for(var object_index = 0; object_index < this.numberReturned; object_index++) {
var _options = {promoteLongs: bson.promoteLongs};
// Read the size of the bson object
var bsonObjectSize = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24;
// If we are storing the raw responses to pipe straight through
if(raw) {
// Deserialize the object and add to the documents array
this.documents.push(binary_reply.slice(this.index, this.index + bsonObjectSize));
} else {
// Deserialize the object and add to the documents array
this.documents.push(bson.deserialize(binary_reply.slice(this.index, this.index + bsonObjectSize), _options));
}
// Adjust binary index to point to next block of binary bson data
this.index = this.index + bsonObjectSize;
}
// No error return
callback(null);
} catch(err) {
return callback(err);
}
}
MongoReply.prototype.is_error = function(){
if(this.documents.length == 1) {
return this.documents[0].ok == 1 ? false : true;
}
return false;
};
MongoReply.prototype.error_message = function() {
return this.documents.length == 1 && this.documents[0].ok == 1 ? '' : this.documents[0].errmsg;
};
var timers = require('timers');
/**
* Sort functions, Normalize and prepare sort parameters
*/
var formatSortValue = exports.formatSortValue = function(sortDirection) {
var value = ("" + sortDirection).toLowerCase();
switch (value) {
case 'ascending':
case 'asc':
case '1':
return 1;
case 'descending':
case 'desc':
case '-1':
return -1;
default:
throw new Error("Illegal sort clause, must be of the form "
+ "[['field1', '(ascending|descending)'], "
+ "['field2', '(ascending|descending)']]");
}
};
var formattedOrderClause = exports.formattedOrderClause = function(sortValue) {
var orderBy = {};
if (Array.isArray(sortValue)) {
for(var i = 0; i < sortValue.length; i++) {
if(sortValue[i].constructor == String) {
orderBy[sortValue[i]] = 1;
} else {
orderBy[sortValue[i][0]] = formatSortValue(sortValue[i][1]);
}
}
} else if(Object.prototype.toString.call(sortValue) === '[object Object]') {
orderBy = sortValue;
} else if (sortValue.constructor == String) {
orderBy[sortValue] = 1;
} else {
throw new Error("Illegal sort clause, must be of the form " +
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]");
}
return orderBy;
};
exports.encodeInt = function(value) {
var buffer = new Buffer(4);
buffer[3] = (value >> 24) & 0xff;
buffer[2] = (value >> 16) & 0xff;
buffer[1] = (value >> 8) & 0xff;
buffer[0] = value & 0xff;
return buffer;
}
exports.encodeIntInPlace = function(value, buffer, index) {
buffer[index + 3] = (value >> 24) & 0xff;
buffer[index + 2] = (value >> 16) & 0xff;
buffer[index + 1] = (value >> 8) & 0xff;
buffer[index] = value & 0xff;
}
exports.encodeCString = function(string) {
var buf = new Buffer(string, 'utf8');
return [buf, new Buffer([0])];
}
exports.decodeUInt32 = function(array, index) {
return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24;
}
// Decode the int
exports.decodeUInt8 = function(array, index) {
return array[index];
}
/**
* Context insensitive type checks
*/
var toString = Object.prototype.toString;
exports.isObject = function (arg) {
return '[object Object]' == toString.call(arg)
}
exports.isArray = function (arg) {
return Array.isArray(arg) ||
'object' == typeof arg && '[object Array]' == toString.call(arg)
}
exports.isDate = function (arg) {
return 'object' == typeof arg && '[object Date]' == toString.call(arg)
}
exports.isRegExp = function (arg) {
return 'object' == typeof arg && '[object RegExp]' == toString.call(arg)
}
/**
* Wrap a Mongo error document in an Error instance
* @ignore
* @api private
*/
var toError = function(error) {
if (error instanceof Error) return error;
var msg = error.err || error.errmsg || error;
var e = new Error(msg);
e.name = 'MongoError';
// Get all object keys
var keys = typeof error == 'object'
? Object.keys(error)
: [];
for(var i = 0; i < keys.length; i++) {
e[keys[i]] = error[keys[i]];
}
return e;
}
exports.toError = toError;
/**
* Convert a single level object to an array
* @ignore
* @api private
*/
exports.objectToArray = function(object) {
var list = [];
for(var name in object) {
list.push(object[name])
}
return list;
}
/**
* Handle single command document return
* @ignore
* @api private
*/
exports.handleSingleCommandResultReturn = function(override_value_true, override_value_false, callback) {
return function(err, result, connection) {
if(err) return callback(err, null);
if(!result || !result.documents || result.documents.length == 0)
if(callback) return callback(toError("command failed to return results"), null)
if(result.documents[0].ok == 1) {
if(override_value_true) return callback(null, override_value_true)
if(callback) return callback(null, result.documents[0]);
}
// Return the error from the document
if(callback) return callback(toError(result.documents[0]), override_value_false);
}
}
/**
* Return correct processor
* @ignore
* @api private
*/
exports.processor = function() {
// Set processor, setImmediate if 0.10 otherwise nextTick
process.maxTickDepth = Infinity;
var processor = timers.setImmediate ? timers.setImmediate : process.nextTick;
// processor = process.nextTick;
return processor;
}
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
NODE = node
NPM = npm
NODEUNIT = node_modules/nodeunit/bin/nodeunit
DOX = node_modules/dox/bin/dox
name = all
total: build_native
test_functional:
node test/runner.js -t functional
test_ssl:
node test/runner.js -t ssl
test_replicaset:
node test/runner.js -t replicaset
test_sharded:
node test/runner.js -t sharded
test_auth:
node test/runner.js -t auth
generate_docs:
$(NODE) dev/tools/build-docs.js
make --directory=./docs/sphinx-docs --file=Makefile html
.PHONY: total
language: node_js
node_js:
- 0.6
- 0.8
- 0.9 # development version of 0.8, may be unstable
{
'targets': [
{
'target_name': 'bson',
'sources': [ 'ext/bson.cc' ],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
'conditions': [
['OS=="mac"', {
'xcode_settings': {
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
}
}]
]
}
]
}
var bson = (function(){
var pkgmap = {},
global = {},
nativeRequire = typeof require != 'undefined' && require,
lib, ties, main, async;
function exports(){ return main(); };
exports.main = exports;
exports.module = module;
exports.packages = pkgmap;
exports.pkg = pkg;
exports.require = function require(uri){
return pkgmap.main.index.require(uri);
};
ties = {};
aliases = {};
return exports;
function join() {
return normalize(Array.prototype.join.call(arguments, "/"));
};
function normalize(path) {
var ret = [], parts = path.split('/'), cur, prev;
var i = 0, l = parts.length-1;
for (; i <= l; i++) {
cur = parts[i];
if (cur === "." && prev !== undefined) continue;
if (cur === ".." && ret.length && prev !== ".." && prev !== "." && prev !== undefined) {
ret.pop();
prev = ret.slice(-1)[0];
} else {
if (prev === ".") ret.pop();
ret.push(cur);
prev = cur;
}
}
return ret.join("/");
};
function dirname(path) {
return path && path.substr(0, path.lastIndexOf("/")) || ".";
};
function findModule(workingModule, uri){
var moduleId = join(dirname(workingModule.id), /\.\/$/.test(uri) ? (uri + 'index') : uri ).replace(/\.js$/, ''),
moduleIndexId = join(moduleId, 'index'),
pkg = workingModule.pkg,
module;
var i = pkg.modules.length,
id;
while(i-->0){
id = pkg.modules[i].id;
if(id==moduleId || id == moduleIndexId){
module = pkg.modules[i];
break;
}
}
return module;
}
function newRequire(callingModule){
function require(uri){
var module, pkg;
if(/^\./.test(uri)){
module = findModule(callingModule, uri);
} else if ( ties && ties.hasOwnProperty( uri ) ) {
return ties[uri];
} else if ( aliases && aliases.hasOwnProperty( uri ) ) {
return require(aliases[uri]);
} else {
pkg = pkgmap[uri];
if(!pkg && nativeRequire){
try {
pkg = nativeRequire(uri);
} catch (nativeRequireError) {}
if(pkg) return pkg;
}
if(!pkg){
throw new Error('Cannot find module "'+uri+'" @[module: '+callingModule.id+' package: '+callingModule.pkg.name+']');
}
module = pkg.index;
}
if(!module){
throw new Error('Cannot find module "'+uri+'" @[module: '+callingModule.id+' package: '+callingModule.pkg.name+']');
}
module.parent = callingModule;
return module.call();
};
return require;
}
function module(parent, id, wrapper){
var mod = { pkg: parent, id: id, wrapper: wrapper },
cached = false;
mod.exports = {};
mod.require = newRequire(mod);
mod.call = function(){
if(cached) {
return mod.exports;
}
cached = true;
global.require = mod.require;
mod.wrapper(mod, mod.exports, global, global.require);
return mod.exports;
};
if(parent.mainModuleId == mod.id){
parent.index = mod;
parent.parents.length === 0 && ( main = mod.call );
}
parent.modules.push(mod);
}
function pkg(/* [ parentId ...], wrapper */){
var wrapper = arguments[ arguments.length - 1 ],
parents = Array.prototype.slice.call(arguments, 0, arguments.length - 1),
ctx = wrapper(parents);
pkgmap[ctx.name] = ctx;
arguments.length == 1 && ( pkgmap.main = ctx );
return function(modules){
var id;
for(id in modules){
module(ctx, id, modules[id]);
}
};
}
}(this));
bson.pkg(function(parents){
return {
'name' : 'bson',
'mainModuleId' : 'bson',
'modules' : [],
'parents' : parents
};
})({ 'binary': function(module, exports, global, require, undefined){
/**
* Module dependencies.
*/
if(typeof window === 'undefined') {
var Buffer = require('buffer').Buffer; // TODO just use global Buffer
}
// Binary default subtype
var BSON_BINARY_SUBTYPE_DEFAULT = 0;
/**
* @ignore
* @api private
*/
var writeStringToArray = function(data) {
// Create a buffer
var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(data.length)) : new Array(data.length);
// Write the content to the buffer
for(var i = 0; i < data.length; i++) {
buffer[i] = data.charCodeAt(i);
}
// Write the string to the buffer
return buffer;
}
/**
* Convert Array ot Uint8Array to Binary String
*
* @ignore
* @api private
*/
var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) {
var result = "";
for(var i = startIndex; i < endIndex; i++) {
result = result + String.fromCharCode(byteArray[i]);
}
return result;
};
/**
* A class representation of the BSON Binary type.
*
* Sub types
* - **BSON.BSON_BINARY_SUBTYPE_DEFAULT**, default BSON type.
* - **BSON.BSON_BINARY_SUBTYPE_FUNCTION**, BSON function type.
* - **BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY**, BSON byte array type.
* - **BSON.BSON_BINARY_SUBTYPE_UUID**, BSON uuid type.
* - **BSON.BSON_BINARY_SUBTYPE_MD5**, BSON md5 type.
* - **BSON.BSON_BINARY_SUBTYPE_USER_DEFINED**, BSON user defined type.
*
* @class Represents the Binary BSON type.
* @param {Buffer} buffer a buffer object containing the binary data.
* @param {Number} [subType] the option binary type.
* @return {Grid}
*/
function Binary(buffer, subType) {
if(!(this instanceof Binary)) return new Binary(buffer, subType);
this._bsontype = 'Binary';
if(buffer instanceof Number) {
this.sub_type = buffer;
this.position = 0;
} else {
this.sub_type = subType == null ? BSON_BINARY_SUBTYPE_DEFAULT : subType;
this.position = 0;
}
if(buffer != null && !(buffer instanceof Number)) {
// Only accept Buffer, Uint8Array or Arrays
if(typeof buffer == 'string') {
// Different ways of writing the length of the string for the different types
if(typeof Buffer != 'undefined') {
this.buffer = new Buffer(buffer);
} else if(typeof Uint8Array != 'undefined' || (Object.prototype.toString.call(buffer) == '[object Array]')) {
this.buffer = writeStringToArray(buffer);
} else {
throw new Error("only String, Buffer, Uint8Array or Array accepted");
}
} else {
this.buffer = buffer;
}
this.position = buffer.length;
} else {
if(typeof Buffer != 'undefined') {
this.buffer = new Buffer(Binary.BUFFER_SIZE);
} else if(typeof Uint8Array != 'undefined'){
this.buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE));
} else {
this.buffer = new Array(Binary.BUFFER_SIZE);
}
// Set position to start of buffer
this.position = 0;
}
};
/**
* Updates this binary with byte_value.
*
* @param {Character} byte_value a single byte we wish to write.
* @api public
*/
Binary.prototype.put = function put(byte_value) {
// If it's a string and a has more than one character throw an error
if(byte_value['length'] != null && typeof byte_value != 'number' && byte_value.length != 1) throw new Error("only accepts single character String, Uint8Array or Array");
if(typeof byte_value != 'number' && byte_value < 0 || byte_value > 255) throw new Error("only accepts number in a valid unsigned byte range 0-255");
// Decode the byte value once
var decoded_byte = null;
if(typeof byte_value == 'string') {
decoded_byte = byte_value.charCodeAt(0);
} else if(byte_value['length'] != null) {
decoded_byte = byte_value[0];
} else {
decoded_byte = byte_value;
}
if(this.buffer.length > this.position) {
this.buffer[this.position++] = decoded_byte;
} else {
if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) {
// Create additional overflow buffer
var buffer = new Buffer(Binary.BUFFER_SIZE + this.buffer.length);
// Combine the two buffers together
this.buffer.copy(buffer, 0, 0, this.buffer.length);
this.buffer = buffer;
this.buffer[this.position++] = decoded_byte;
} else {
var buffer = null;
// Create a new buffer (typed or normal array)
if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') {
buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE + this.buffer.length));
} else {
buffer = new Array(Binary.BUFFER_SIZE + this.buffer.length);
}
// We need to copy all the content to the new array
for(var i = 0; i < this.buffer.length; i++) {
buffer[i] = this.buffer[i];
}
// Reassign the buffer
this.buffer = buffer;
// Write the byte
this.buffer[this.position++] = decoded_byte;
}
}
};
/**
* Writes a buffer or string to the binary.
*
* @param {Buffer|String} string a string or buffer to be written to the Binary BSON object.
* @param {Number} offset specify the binary of where to write the content.
* @api public
*/
Binary.prototype.write = function write(string, offset) {
offset = typeof offset == 'number' ? offset : this.position;
// If the buffer is to small let's extend the buffer
if(this.buffer.length < offset + string.length) {
var buffer = null;
// If we are in node.js
if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) {
buffer = new Buffer(this.buffer.length + string.length);
this.buffer.copy(buffer, 0, 0, this.buffer.length);
} else if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') {
// Create a new buffer
buffer = new Uint8Array(new ArrayBuffer(this.buffer.length + string.length))
// Copy the content
for(var i = 0; i < this.position; i++) {
buffer[i] = this.buffer[i];
}
}
// Assign the new buffer
this.buffer = buffer;
}
if(typeof Buffer != 'undefined' && Buffer.isBuffer(string) && Buffer.isBuffer(this.buffer)) {
string.copy(this.buffer, offset, 0, string.length);
this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position;
// offset = string.length
} else if(typeof Buffer != 'undefined' && typeof string == 'string' && Buffer.isBuffer(this.buffer)) {
this.buffer.write(string, 'binary', offset);
this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position;
// offset = string.length;
} else if(Object.prototype.toString.call(string) == '[object Uint8Array]'
|| Object.prototype.toString.call(string) == '[object Array]' && typeof string != 'string') {
for(var i = 0; i < string.length; i++) {
this.buffer[offset++] = string[i];
}
this.position = offset > this.position ? offset : this.position;
} else if(typeof string == 'string') {
for(var i = 0; i < string.length; i++) {
this.buffer[offset++] = string.charCodeAt(i);
}
this.position = offset > this.position ? offset : this.position;
}
};
/**
* Reads **length** bytes starting at **position**.
*
* @param {Number} position read from the given position in the Binary.
* @param {Number} length the number of bytes to read.
* @return {Buffer}
* @api public
*/
Binary.prototype.read = function read(position, length) {
length = length && length > 0
? length
: this.position;
// Let's return the data based on the type we have
if(this.buffer['slice']) {
return this.buffer.slice(position, position + length);
} else {
// Create a buffer to keep the result
var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(length)) : new Array(length);
for(var i = 0; i < length; i++) {
buffer[i] = this.buffer[position++];
}
}
// Return the buffer
return buffer;
};
/**
* Returns the value of this binary as a string.
*
* @return {String}
* @api public
*/
Binary.prototype.value = function value(asRaw) {
asRaw = asRaw == null ? false : asRaw;
// If it's a node.js buffer object
if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) {
return asRaw ? this.buffer.slice(0, this.position) : this.buffer.toString('binary', 0, this.position);
} else {
if(asRaw) {
// we support the slice command use it
if(this.buffer['slice'] != null) {
return this.buffer.slice(0, this.position);
} else {
// Create a new buffer to copy content to
var newBuffer = Object.prototype.toString.call(this.buffer) == '[object Uint8Array]' ? new Uint8Array(new ArrayBuffer(this.position)) : new Array(this.position);
// Copy content
for(var i = 0; i < this.position; i++) {
newBuffer[i] = this.buffer[i];
}
// Return the buffer
return newBuffer;
}
} else {
return convertArraytoUtf8BinaryString(this.buffer, 0, this.position);
}
}
};
/**
* Length.
*
* @return {Number} the length of the binary.
* @api public
*/
Binary.prototype.length = function length() {
return this.position;
};
/**
* @ignore
* @api private
*/
Binary.prototype.toJSON = function() {
return this.buffer != null ? this.buffer.toString('base64') : '';
}
/**
* @ignore
* @api private
*/
Binary.prototype.toString = function(format) {
return this.buffer != null ? this.buffer.slice(0, this.position).toString(format) : '';
}
Binary.BUFFER_SIZE = 256;
/**
* Default BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_DEFAULT = 0;
/**
* Function BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_FUNCTION = 1;
/**
* Byte Array BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_BYTE_ARRAY = 2;
/**
* OLD UUID BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_UUID_OLD = 3;
/**
* UUID BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_UUID = 4;
/**
* MD5 BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_MD5 = 5;
/**
* User BSON type
*
* @classconstant SUBTYPE_DEFAULT
**/
Binary.SUBTYPE_USER_DEFINED = 128;
/**
* Expose.
*/
exports.Binary = Binary;
},
'binary_parser': function(module, exports, global, require, undefined){
/**
* Binary Parser.
* Jonas Raoni Soares Silva
* http://jsfromhell.com/classes/binary-parser [v1.0]
*/
var chr = String.fromCharCode;
var maxBits = [];
for (var i = 0; i < 64; i++) {
maxBits[i] = Math.pow(2, i);
}
function BinaryParser (bigEndian, allowExceptions) {
if(!(this instanceof BinaryParser)) return new BinaryParser(bigEndian, allowExceptions);
this.bigEndian = bigEndian;
this.allowExceptions = allowExceptions;
};
BinaryParser.warn = function warn (msg) {
if (this.allowExceptions) {
throw new Error(msg);
}
return 1;
};
BinaryParser.decodeFloat = function decodeFloat (data, precisionBits, exponentBits) {
var b = new this.Buffer(this.bigEndian, data);
b.checkBuffer(precisionBits + exponentBits + 1);
var bias = maxBits[exponentBits - 1] - 1
, signal = b.readBits(precisionBits + exponentBits, 1)
, exponent = b.readBits(precisionBits, exponentBits)
, significand = 0
, divisor = 2
, curByte = b.buffer.length + (-precisionBits >> 3) - 1;
do {
for (var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 );
} while (precisionBits -= startBit);
return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 );
};
BinaryParser.decodeInt = function decodeInt (data, bits, signed, forceBigEndian) {
var b = new this.Buffer(this.bigEndian || forceBigEndian, data)
, x = b.readBits(0, bits)
, max = maxBits[bits]; //max = Math.pow( 2, bits );
return signed && x >= max / 2
? x - max
: x;
};
BinaryParser.encodeFloat = function encodeFloat (data, precisionBits, exponentBits) {
var bias = maxBits[exponentBits - 1] - 1
, minExp = -bias + 1
, maxExp = bias
, minUnnormExp = minExp - precisionBits
, n = parseFloat(data)
, status = isNaN(n) || n == -Infinity || n == +Infinity ? n : 0
, exp = 0
, len = 2 * bias + 1 + precisionBits + 3
, bin = new Array(len)
, signal = (n = status !== 0 ? 0 : n) < 0
, intPart = Math.floor(n = Math.abs(n))
, floatPart = n - intPart
, lastBit
, rounded
, result
, i
, j;
for (i = len; i; bin[--i] = 0);
for (i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2));
for (i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0 ) && --floatPart);
for (i = -1; ++i < len && !bin[i];);
if (bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]) {
if (!(rounded = bin[lastBit])) {
for (j = lastBit + 2; !rounded && j < len; rounded = bin[j++]);
}
for (j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0));
}
for (i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];);
if ((exp = bias + 1 - i) >= minExp && exp <= maxExp) {
++i;
} else if (exp < minExp) {
exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow");
i = bias + 1 - (exp = minExp - 1);
}
if (intPart || status !== 0) {
this.warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status);
exp = maxExp + 1;
i = bias + 2;
if (status == -Infinity) {
signal = 1;
} else if (isNaN(status)) {
bin[i] = 1;
}
}
for (n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1);
for (n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = []; i; j = (j + 1) % 8) {
n += (1 << j) * result.charAt(--i);
if (j == 7) {
r[r.length] = String.fromCharCode(n);
n = 0;
}
}
r[r.length] = n
? String.fromCharCode(n)
: "";
return (this.bigEndian ? r.reverse() : r).join("");
};
BinaryParser.encodeInt = function encodeInt (data, bits, signed, forceBigEndian) {
var max = maxBits[bits];
if (data >= max || data < -(max / 2)) {
this.warn("encodeInt::overflow");
data = 0;
}
if (data < 0) {
data += max;
}
for (var r = []; data; r[r.length] = String.fromCharCode(data % 256), data = Math.floor(data / 256));
for (bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0");
return ((this.bigEndian || forceBigEndian) ? r.reverse() : r).join("");
};
BinaryParser.toSmall = function( data ){ return this.decodeInt( data, 8, true ); };
BinaryParser.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); };
BinaryParser.toByte = function( data ){ return this.decodeInt( data, 8, false ); };
BinaryParser.fromByte = function( data ){ return this.encodeInt( data, 8, false ); };
BinaryParser.toShort = function( data ){ return this.decodeInt( data, 16, true ); };
BinaryParser.fromShort = function( data ){ return this.encodeInt( data, 16, true ); };
BinaryParser.toWord = function( data ){ return this.decodeInt( data, 16, false ); };
BinaryParser.fromWord = function( data ){ return this.encodeInt( data, 16, false ); };
BinaryParser.toInt = function( data ){ return this.decodeInt( data, 32, true ); };
BinaryParser.fromInt = function( data ){ return this.encodeInt( data, 32, true ); };
BinaryParser.toLong = function( data ){ return this.decodeInt( data, 64, true ); };
BinaryParser.fromLong = function( data ){ return this.encodeInt( data, 64, true ); };
BinaryParser.toDWord = function( data ){ return this.decodeInt( data, 32, false ); };
BinaryParser.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); };
BinaryParser.toQWord = function( data ){ return this.decodeInt( data, 64, true ); };
BinaryParser.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); };
BinaryParser.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); };
BinaryParser.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); };
BinaryParser.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); };
BinaryParser.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); };
// Factor out the encode so it can be shared by add_header and push_int32
BinaryParser.encode_int32 = function encode_int32 (number, asArray) {
var a, b, c, d, unsigned;
unsigned = (number < 0) ? (number + 0x100000000) : number;
a = Math.floor(unsigned / 0xffffff);
unsigned &= 0xffffff;
b = Math.floor(unsigned / 0xffff);
unsigned &= 0xffff;
c = Math.floor(unsigned / 0xff);
unsigned &= 0xff;
d = Math.floor(unsigned);
return asArray ? [chr(a), chr(b), chr(c), chr(d)] : chr(a) + chr(b) + chr(c) + chr(d);
};
BinaryParser.encode_int64 = function encode_int64 (number) {
var a, b, c, d, e, f, g, h, unsigned;
unsigned = (number < 0) ? (number + 0x10000000000000000) : number;
a = Math.floor(unsigned / 0xffffffffffffff);
unsigned &= 0xffffffffffffff;
b = Math.floor(unsigned / 0xffffffffffff);
unsigned &= 0xffffffffffff;
c = Math.floor(unsigned / 0xffffffffff);
unsigned &= 0xffffffffff;
d = Math.floor(unsigned / 0xffffffff);
unsigned &= 0xffffffff;
e = Math.floor(unsigned / 0xffffff);
unsigned &= 0xffffff;
f = Math.floor(unsigned / 0xffff);
unsigned &= 0xffff;
g = Math.floor(unsigned / 0xff);
unsigned &= 0xff;
h = Math.floor(unsigned);
return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h);
};
/**
* UTF8 methods
*/
// Take a raw binary string and return a utf8 string
BinaryParser.decode_utf8 = function decode_utf8 (binaryStr) {
var len = binaryStr.length
, decoded = ''
, i = 0
, c = 0
, c1 = 0
, c2 = 0
, c3;
while (i < len) {
c = binaryStr.charCodeAt(i);
if (c < 128) {
decoded += String.fromCharCode(c);
i++;
} else if ((c > 191) && (c < 224)) {
c2 = binaryStr.charCodeAt(i+1);
decoded += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = binaryStr.charCodeAt(i+1);
c3 = binaryStr.charCodeAt(i+2);
decoded += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return decoded;
};
// Encode a cstring
BinaryParser.encode_cstring = function encode_cstring (s) {
return unescape(encodeURIComponent(s)) + BinaryParser.fromByte(0);
};
// Take a utf8 string and return a binary string
BinaryParser.encode_utf8 = function encode_utf8 (s) {
var a = ""
, c;
for (var n = 0, len = s.length; n < len; n++) {
c = s.charCodeAt(n);
if (c < 128) {
a += String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
a += String.fromCharCode((c>>6) | 192) ;
a += String.fromCharCode((c&63) | 128);
} else {
a += String.fromCharCode((c>>12) | 224);
a += String.fromCharCode(((c>>6) & 63) | 128);
a += String.fromCharCode((c&63) | 128);
}
}
return a;
};
BinaryParser.hprint = function hprint (s) {
var number;
for (var i = 0, len = s.length; i < len; i++) {
if (s.charCodeAt(i) < 32) {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(16)
: s.charCodeAt(i).toString(16);
process.stdout.write(number + " ")
} else {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(16)
: s.charCodeAt(i).toString(16);
process.stdout.write(number + " ")
}
}
process.stdout.write("\n\n");
};
BinaryParser.ilprint = function hprint (s) {
var number;
for (var i = 0, len = s.length; i < len; i++) {
if (s.charCodeAt(i) < 32) {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(10)
: s.charCodeAt(i).toString(10);
require('util').debug(number+' : ');
} else {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(10)
: s.charCodeAt(i).toString(10);
require('util').debug(number+' : '+ s.charAt(i));
}
}
};
BinaryParser.hlprint = function hprint (s) {
var number;
for (var i = 0, len = s.length; i < len; i++) {
if (s.charCodeAt(i) < 32) {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(16)
: s.charCodeAt(i).toString(16);
require('util').debug(number+' : ');
} else {
number = s.charCodeAt(i) <= 15
? "0" + s.charCodeAt(i).toString(16)
: s.charCodeAt(i).toString(16);
require('util').debug(number+' : '+ s.charAt(i));
}
}
};
/**
* BinaryParser buffer constructor.
*/
function BinaryParserBuffer (bigEndian, buffer) {
this.bigEndian = bigEndian || 0;
this.buffer = [];
this.setBuffer(buffer);
};
BinaryParserBuffer.prototype.setBuffer = function setBuffer (data) {
var l, i, b;
if (data) {
i = l = data.length;
b = this.buffer = new Array(l);
for (; i; b[l - i] = data.charCodeAt(--i));
this.bigEndian && b.reverse();
}
};
BinaryParserBuffer.prototype.hasNeededBits = function hasNeededBits (neededBits) {
return this.buffer.length >= -(-neededBits >> 3);
};
BinaryParserBuffer.prototype.checkBuffer = function checkBuffer (neededBits) {
if (!this.hasNeededBits(neededBits)) {
throw new Error("checkBuffer::missing bytes");
}
};
BinaryParserBuffer.prototype.readBits = function readBits (start, length) {
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
function shl (a, b) {
for (; b--; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
return a;
}
if (start < 0 || length <= 0) {
return 0;
}
this.checkBuffer(start + length);
var offsetLeft
, offsetRight = start % 8
, curByte = this.buffer.length - ( start >> 3 ) - 1
, lastByte = this.buffer.length + ( -( start + length ) >> 3 )
, diff = curByte - lastByte
, sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)) + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[lastByte++] & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight : 0);
for(; diff; sum += shl(this.buffer[lastByte++], (diff-- << 3) - offsetRight));
return sum;
};
/**
* Expose.
*/
BinaryParser.Buffer = BinaryParserBuffer;
exports.BinaryParser = BinaryParser;
},
'bson': function(module, exports, global, require, undefined){
var Long = require('./long').Long
, Double = require('./double').Double
, Timestamp = require('./timestamp').Timestamp
, ObjectID = require('./objectid').ObjectID
, Symbol = require('./symbol').Symbol
, Code = require('./code').Code
, MinKey = require('./min_key').MinKey
, MaxKey = require('./max_key').MaxKey
, DBRef = require('./db_ref').DBRef
, Binary = require('./binary').Binary
, BinaryParser = require('./binary_parser').BinaryParser
, writeIEEE754 = require('./float_parser').writeIEEE754
, readIEEE754 = require('./float_parser').readIEEE754
// To ensure that 0.4 of node works correctly
var isDate = function isDate(d) {
return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]';
}
/**
* Create a new BSON instance
*
* @class Represents the BSON Parser
* @return {BSON} instance of BSON Parser.
*/
function BSON () {};
/**
* @ignore
* @api private
*/
// BSON MAX VALUES
BSON.BSON_INT32_MAX = 0x7FFFFFFF;
BSON.BSON_INT32_MIN = -0x80000000;
BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1;
BSON.BSON_INT64_MIN = -Math.pow(2, 63);
// JS MAX PRECISE VALUES
BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double.
BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double.
// Internal long versions
var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double.
var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double.
/**
* Number BSON Type
*
* @classconstant BSON_DATA_NUMBER
**/
BSON.BSON_DATA_NUMBER = 1;
/**
* String BSON Type
*
* @classconstant BSON_DATA_STRING
**/
BSON.BSON_DATA_STRING = 2;
/**
* Object BSON Type
*
* @classconstant BSON_DATA_OBJECT
**/
BSON.BSON_DATA_OBJECT = 3;
/**
* Array BSON Type
*
* @classconstant BSON_DATA_ARRAY
**/
BSON.BSON_DATA_ARRAY = 4;
/**
* Binary BSON Type
*
* @classconstant BSON_DATA_BINARY
**/
BSON.BSON_DATA_BINARY = 5;
/**
* ObjectID BSON Type
*
* @classconstant BSON_DATA_OID
**/
BSON.BSON_DATA_OID = 7;
/**
* Boolean BSON Type
*
* @classconstant BSON_DATA_BOOLEAN
**/
BSON.BSON_DATA_BOOLEAN = 8;
/**
* Date BSON Type
*
* @classconstant BSON_DATA_DATE
**/
BSON.BSON_DATA_DATE = 9;
/**
* null BSON Type
*
* @classconstant BSON_DATA_NULL
**/
BSON.BSON_DATA_NULL = 10;
/**
* RegExp BSON Type
*
* @classconstant BSON_DATA_REGEXP
**/
BSON.BSON_DATA_REGEXP = 11;
/**
* Code BSON Type
*
* @classconstant BSON_DATA_CODE
**/
BSON.BSON_DATA_CODE = 13;
/**
* Symbol BSON Type
*
* @classconstant BSON_DATA_SYMBOL
**/
BSON.BSON_DATA_SYMBOL = 14;
/**
* Code with Scope BSON Type
*
* @classconstant BSON_DATA_CODE_W_SCOPE
**/
BSON.BSON_DATA_CODE_W_SCOPE = 15;
/**
* 32 bit Integer BSON Type
*
* @classconstant BSON_DATA_INT
**/
BSON.BSON_DATA_INT = 16;
/**
* Timestamp BSON Type
*
* @classconstant BSON_DATA_TIMESTAMP
**/
BSON.BSON_DATA_TIMESTAMP = 17;
/**
* Long BSON Type
*
* @classconstant BSON_DATA_LONG
**/
BSON.BSON_DATA_LONG = 18;
/**
* MinKey BSON Type
*
* @classconstant BSON_DATA_MIN_KEY
**/
BSON.BSON_DATA_MIN_KEY = 0xff;
/**
* MaxKey BSON Type
*
* @classconstant BSON_DATA_MAX_KEY
**/
BSON.BSON_DATA_MAX_KEY = 0x7f;
/**
* Binary Default Type
*
* @classconstant BSON_BINARY_SUBTYPE_DEFAULT
**/
BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0;
/**
* Binary Function Type
*
* @classconstant BSON_BINARY_SUBTYPE_FUNCTION
**/
BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1;
/**
* Binary Byte Array Type
*
* @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY
**/
BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2;
/**
* Binary UUID Type
*
* @classconstant BSON_BINARY_SUBTYPE_UUID
**/
BSON.BSON_BINARY_SUBTYPE_UUID = 3;
/**
* Binary MD5 Type
*
* @classconstant BSON_BINARY_SUBTYPE_MD5
**/
BSON.BSON_BINARY_SUBTYPE_MD5 = 4;
/**
* Binary User Defined Type
*
* @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED
**/
BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128;
/**
* Calculate the bson size for a passed in Javascript object.
*
* @param {Object} object the Javascript object to calculate the BSON byte size for.
* @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**.
* @return {Number} returns the number of bytes the BSON object will take up.
* @api public
*/
BSON.calculateObjectSize = function calculateObjectSize(object, serializeFunctions) {
var totalLength = (4 + 1);
if(Array.isArray(object)) {
for(var i = 0; i < object.length; i++) {
totalLength += calculateElement(i.toString(), object[i], serializeFunctions)
}
} else {
// If we have toBSON defined, override the current object
if(object.toBSON) {
object = object.toBSON();
}
// Calculate size
for(var key in object) {
totalLength += calculateElement(key, object[key], serializeFunctions)
}
}
return totalLength;
}
/**
* @ignore
* @api private
*/
function calculateElement(name, value, serializeFunctions) {
var isBuffer = typeof Buffer !== 'undefined';
switch(typeof value) {
case 'string':
return 1 + (!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1 + 4 + (!isBuffer ? numberOfBytes(value) : Buffer.byteLength(value, 'utf8')) + 1;
case 'number':
if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) {
if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { // 32 bit
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (4 + 1);
} else {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1);
}
} else { // 64 bit
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1);
}
case 'undefined':
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1);
case 'boolean':
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 1);
case 'object':
if(value == null || value instanceof MinKey || value instanceof MaxKey || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1);
} else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (12 + 1);
} else if(value instanceof Date || isDate(value)) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1);
} else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 4 + 1) + value.length;
} else if(value instanceof Long || value instanceof Double || value instanceof Timestamp
|| value['_bsontype'] == 'Long' || value['_bsontype'] == 'Double' || value['_bsontype'] == 'Timestamp') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1);
} else if(value instanceof Code || value['_bsontype'] == 'Code') {
// Calculate size depending on the availability of a scope
if(value.scope != null && Object.keys(value.scope).length > 0) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions);
} else {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1;
}
} else if(value instanceof Binary || value['_bsontype'] == 'Binary') {
// Check what kind of subtype we have
if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1 + 4);
} else {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1);
}
} else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + ((!isBuffer ? numberOfBytes(value.value) : Buffer.byteLength(value.value, 'utf8')) + 4 + 1 + 1);
} else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') {
// Set up correct object for serialization
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
// Add db reference if it exists
if(null != value.db) {
ordered_values['$db'] = value.db;
}
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + BSON.calculateObjectSize(ordered_values, serializeFunctions);
} else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1
+ (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1
} else {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + BSON.calculateObjectSize(value, serializeFunctions) + 1;
}
case 'function':
// WTF for 0.4.X where typeof /someregexp/ === 'function'
if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1
+ (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1
} else {
if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions);
} else if(serializeFunctions) {
return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1;
}
}
}
return 0;
}
/**
* Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization.
*
* @param {Object} object the Javascript object to serialize.
* @param {Boolean} checkKeys the serializer will check if keys are valid.
* @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Number} index the index in the buffer where we wish to start serializing into.
* @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**.
* @return {Number} returns the new write index in the Buffer.
* @api public
*/
BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index, serializeFunctions) {
// Default setting false
serializeFunctions = serializeFunctions == null ? false : serializeFunctions;
// Write end information (length of the object)
var size = buffer.length;
// Write the size of the object
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
return serializeObject(object, checkKeys, buffer, index, serializeFunctions) - 1;
}
/**
* @ignore
* @api private
*/
var serializeObject = function(object, checkKeys, buffer, index, serializeFunctions) {
// Process the object
if(Array.isArray(object)) {
for(var i = 0; i < object.length; i++) {
index = packElement(i.toString(), object[i], checkKeys, buffer, index, serializeFunctions);
}
} else {
// If we have toBSON defined, override the current object
if(object.toBSON) {
object = object.toBSON();
}
// Serialize the object
for(var key in object) {
// Check the key and throw error if it's illegal
if (key != '$db' && key != '$ref' && key != '$id') {
// dollars and dots ok
BSON.checkKey(key, !checkKeys);
}
// Pack the element
index = packElement(key, object[key], checkKeys, buffer, index, serializeFunctions);
}
}
// Write zero
buffer[index++] = 0;
return index;
}
var stringToBytes = function(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat( st.reverse() );
}
// return an array of bytes
return re;
}
var numberOfBytes = function(str) {
var ch, st, re = 0;
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
re = re + st.length;
}
// return an array of bytes
return re;
}
/**
* @ignore
* @api private
*/
var writeToTypedArray = function(buffer, string, index) {
var bytes = stringToBytes(string);
for(var i = 0; i < bytes.length; i++) {
buffer[index + i] = bytes[i];
}
return bytes.length;
}
/**
* @ignore
* @api private
*/
var supportsBuffer = typeof Buffer != 'undefined';
/**
* @ignore
* @api private
*/
var packElement = function(name, value, checkKeys, buffer, index, serializeFunctions) {
var startIndex = index;
switch(typeof value) {
case 'string':
// Encode String type
buffer[index++] = BSON.BSON_DATA_STRING;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Calculate size
var size = supportsBuffer ? Buffer.byteLength(value) + 1 : numberOfBytes(value) + 1;
// Write the size of the string to buffer
buffer[index + 3] = (size >> 24) & 0xff;
buffer[index + 2] = (size >> 16) & 0xff;
buffer[index + 1] = (size >> 8) & 0xff;
buffer[index] = size & 0xff;
// Ajust the index
index = index + 4;
// Write the string
supportsBuffer ? buffer.write(value, index, 'utf8') : writeToTypedArray(buffer, value, index);
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
// Return index
return index;
case 'number':
// We have an integer value
if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) {
// If the value fits in 32 bits encode as int, if it fits in a double
// encode it as a double, otherwise long
if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) {
// Set int type 32 bits or less
buffer[index++] = BSON.BSON_DATA_INT;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the int value
buffer[index++] = value & 0xff;
buffer[index++] = (value >> 8) & 0xff;
buffer[index++] = (value >> 16) & 0xff;
buffer[index++] = (value >> 24) & 0xff;
} else if(value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) {
// Encode as double
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write float
writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
} else {
// Set long type
buffer[index++] = BSON.BSON_DATA_LONG;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
var longVal = Long.fromNumber(value);
var lowBits = longVal.getLowBits();
var highBits = longVal.getHighBits();
// Encode low bits
buffer[index++] = lowBits & 0xff;
buffer[index++] = (lowBits >> 8) & 0xff;
buffer[index++] = (lowBits >> 16) & 0xff;
buffer[index++] = (lowBits >> 24) & 0xff;
// Encode high bits
buffer[index++] = highBits & 0xff;
buffer[index++] = (highBits >> 8) & 0xff;
buffer[index++] = (highBits >> 16) & 0xff;
buffer[index++] = (highBits >> 24) & 0xff;
}
} else {
// Encode as double
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write float
writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
}
return index;
case 'undefined':
// Set long type
buffer[index++] = BSON.BSON_DATA_NULL;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
return index;
case 'boolean':
// Write the type
buffer[index++] = BSON.BSON_DATA_BOOLEAN;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Encode the boolean value
buffer[index++] = value ? 1 : 0;
return index;
case 'object':
if(value === null || value instanceof MinKey || value instanceof MaxKey
|| value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') {
// Write the type of either min or max key
if(value === null) {
buffer[index++] = BSON.BSON_DATA_NULL;
} else if(value instanceof MinKey) {
buffer[index++] = BSON.BSON_DATA_MIN_KEY;
} else {
buffer[index++] = BSON.BSON_DATA_MAX_KEY;
}
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
return index;
} else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') {
// Write the type
buffer[index++] = BSON.BSON_DATA_OID;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write objectid
supportsBuffer ? buffer.write(value.id, index, 'binary') : writeToTypedArray(buffer, value.id, index);
// Ajust index
index = index + 12;
return index;
} else if(value instanceof Date || isDate(value)) {
// Write the type
buffer[index++] = BSON.BSON_DATA_DATE;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the date
var dateInMilis = Long.fromNumber(value.getTime());
var lowBits = dateInMilis.getLowBits();
var highBits = dateInMilis.getHighBits();
// Encode low bits
buffer[index++] = lowBits & 0xff;
buffer[index++] = (lowBits >> 8) & 0xff;
buffer[index++] = (lowBits >> 16) & 0xff;
buffer[index++] = (lowBits >> 24) & 0xff;
// Encode high bits
buffer[index++] = highBits & 0xff;
buffer[index++] = (highBits >> 8) & 0xff;
buffer[index++] = (highBits >> 16) & 0xff;
buffer[index++] = (highBits >> 24) & 0xff;
return index;
} else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
// Write the type
buffer[index++] = BSON.BSON_DATA_BINARY;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Get size of the buffer (current write point)
var size = value.length;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write the default subtype
buffer[index++] = BSON.BSON_BINARY_SUBTYPE_DEFAULT;
// Copy the content form the binary field to the buffer
value.copy(buffer, index, 0, size);
// Adjust the index
index = index + size;
return index;
} else if(value instanceof Long || value instanceof Timestamp || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Timestamp') {
// Write the type
buffer[index++] = value instanceof Long ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_TIMESTAMP;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the date
var lowBits = value.getLowBits();
var highBits = value.getHighBits();
// Encode low bits
buffer[index++] = lowBits & 0xff;
buffer[index++] = (lowBits >> 8) & 0xff;
buffer[index++] = (lowBits >> 16) & 0xff;
buffer[index++] = (lowBits >> 24) & 0xff;
// Encode high bits
buffer[index++] = highBits & 0xff;
buffer[index++] = (highBits >> 8) & 0xff;
buffer[index++] = (highBits >> 16) & 0xff;
buffer[index++] = (highBits >> 24) & 0xff;
return index;
} else if(value instanceof Double || value['_bsontype'] == 'Double') {
// Encode as double
buffer[index++] = BSON.BSON_DATA_NUMBER;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write float
writeIEEE754(buffer, value, index, 'little', 52, 8);
// Ajust index
index = index + 8;
return index;
} else if(value instanceof Code || value['_bsontype'] == 'Code') {
if(value.scope != null && Object.keys(value.scope).length > 0) {
// Write the type
buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Calculate the scope size
var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions);
// Function string
var functionString = value.code.toString();
// Function Size
var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1;
// Calculate full size of the object
var totalSize = 4 + codeSize + scopeSize + 4;
// Write the total size of the object
buffer[index++] = totalSize & 0xff;
buffer[index++] = (totalSize >> 8) & 0xff;
buffer[index++] = (totalSize >> 16) & 0xff;
buffer[index++] = (totalSize >> 24) & 0xff;
// Write the size of the string to buffer
buffer[index++] = codeSize & 0xff;
buffer[index++] = (codeSize >> 8) & 0xff;
buffer[index++] = (codeSize >> 16) & 0xff;
buffer[index++] = (codeSize >> 24) & 0xff;
// Write the string
supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index);
// Update index
index = index + codeSize - 1;
// Write zero
buffer[index++] = 0;
// Serialize the scope object
var scopeObjectBuffer = supportsBuffer ? new Buffer(scopeSize) : new Uint8Array(new ArrayBuffer(scopeSize));
// Execute the serialization into a seperate buffer
serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions);
// Adjusted scope Size (removing the header)
var scopeDocSize = scopeSize;
// Write scope object size
buffer[index++] = scopeDocSize & 0xff;
buffer[index++] = (scopeDocSize >> 8) & 0xff;
buffer[index++] = (scopeDocSize >> 16) & 0xff;
buffer[index++] = (scopeDocSize >> 24) & 0xff;
// Write the scopeObject into the buffer
supportsBuffer ? scopeObjectBuffer.copy(buffer, index, 0, scopeSize) : buffer.set(scopeObjectBuffer, index);
// Adjust index, removing the empty size of the doc (5 bytes 0000000005)
index = index + scopeDocSize - 5;
// Write trailing zero
buffer[index++] = 0;
return index
} else {
buffer[index++] = BSON.BSON_DATA_CODE;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Function string
var functionString = value.code.toString();
// Function Size
var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write the string
supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index);
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
return index;
}
} else if(value instanceof Binary || value['_bsontype'] == 'Binary') {
// Write the type
buffer[index++] = BSON.BSON_DATA_BINARY;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Extract the buffer
var data = value.value(true);
// Calculate size
var size = value.position;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write the subtype to the buffer
buffer[index++] = value.sub_type;
// If we have binary type 2 the 4 first bytes are the size
if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) {
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
}
// Write the data to the object
supportsBuffer ? data.copy(buffer, index, 0, value.position) : buffer.set(data, index);
// Ajust index
index = index + value.position;
return index;
} else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') {
// Write the type
buffer[index++] = BSON.BSON_DATA_SYMBOL;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Calculate size
var size = supportsBuffer ? Buffer.byteLength(value.value) + 1 : numberOfBytes(value.value) + 1;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write the string
buffer.write(value.value, index, 'utf8');
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0x00;
return index;
} else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') {
// Write the type
buffer[index++] = BSON.BSON_DATA_OBJECT;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Set up correct object for serialization
var ordered_values = {
'$ref': value.namespace
, '$id' : value.oid
};
// Add db reference if it exists
if(null != value.db) {
ordered_values['$db'] = value.db;
}
// Message size
var size = BSON.calculateObjectSize(ordered_values, serializeFunctions);
// Serialize the object
var endIndex = BSON.serializeWithBufferAndIndex(ordered_values, checkKeys, buffer, index, serializeFunctions);
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write zero for object
buffer[endIndex++] = 0x00;
// Return the end index
return endIndex;
} else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_REGEXP;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the regular expression string
supportsBuffer ? buffer.write(value.source, index, 'utf8') : writeToTypedArray(buffer, value.source, index);
// Adjust the index
index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source));
// Write zero
buffer[index++] = 0x00;
// Write the parameters
if(value.global) buffer[index++] = 0x73; // s
if(value.ignoreCase) buffer[index++] = 0x69; // i
if(value.multiline) buffer[index++] = 0x6d; // m
// Add ending zero
buffer[index++] = 0x00;
return index;
} else {
// Write the type
buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Adjust the index
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
var endIndex = serializeObject(value, checkKeys, buffer, index + 4, serializeFunctions);
// Write size
var size = endIndex - index;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
return endIndex;
}
case 'function':
// WTF for 0.4.X where typeof /someregexp/ === 'function'
if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') {
// Write the type
buffer[index++] = BSON.BSON_DATA_REGEXP;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Write the regular expression string
buffer.write(value.source, index, 'utf8');
// Adjust the index
index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source));
// Write zero
buffer[index++] = 0x00;
// Write the parameters
if(value.global) buffer[index++] = 0x73; // s
if(value.ignoreCase) buffer[index++] = 0x69; // i
if(value.multiline) buffer[index++] = 0x6d; // m
// Add ending zero
buffer[index++] = 0x00;
return index;
} else {
if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) {
// Write the type
buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Calculate the scope size
var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions);
// Function string
var functionString = value.toString();
// Function Size
var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1;
// Calculate full size of the object
var totalSize = 4 + codeSize + scopeSize;
// Write the total size of the object
buffer[index++] = totalSize & 0xff;
buffer[index++] = (totalSize >> 8) & 0xff;
buffer[index++] = (totalSize >> 16) & 0xff;
buffer[index++] = (totalSize >> 24) & 0xff;
// Write the size of the string to buffer
buffer[index++] = codeSize & 0xff;
buffer[index++] = (codeSize >> 8) & 0xff;
buffer[index++] = (codeSize >> 16) & 0xff;
buffer[index++] = (codeSize >> 24) & 0xff;
// Write the string
supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index);
// Update index
index = index + codeSize - 1;
// Write zero
buffer[index++] = 0;
// Serialize the scope object
var scopeObjectBuffer = new Buffer(scopeSize);
// Execute the serialization into a seperate buffer
serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions);
// Adjusted scope Size (removing the header)
var scopeDocSize = scopeSize - 4;
// Write scope object size
buffer[index++] = scopeDocSize & 0xff;
buffer[index++] = (scopeDocSize >> 8) & 0xff;
buffer[index++] = (scopeDocSize >> 16) & 0xff;
buffer[index++] = (scopeDocSize >> 24) & 0xff;
// Write the scopeObject into the buffer
scopeObjectBuffer.copy(buffer, index, 0, scopeSize);
// Adjust index, removing the empty size of the doc (5 bytes 0000000005)
index = index + scopeDocSize - 5;
// Write trailing zero
buffer[index++] = 0;
return index
} else if(serializeFunctions) {
buffer[index++] = BSON.BSON_DATA_CODE;
// Number of written bytes
var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index);
// Encode the name
index = index + numberOfWrittenBytes + 1;
buffer[index - 1] = 0;
// Function string
var functionString = value.toString();
// Function Size
var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1;
// Write the size of the string to buffer
buffer[index++] = size & 0xff;
buffer[index++] = (size >> 8) & 0xff;
buffer[index++] = (size >> 16) & 0xff;
buffer[index++] = (size >> 24) & 0xff;
// Write the string
supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index);
// Update index
index = index + size - 1;
// Write zero
buffer[index++] = 0;
return index;
}
}
}
// If no value to serialize
return index;
}
/**
* Serialize a Javascript object.
*
* @param {Object} object the Javascript object to serialize.
* @param {Boolean} checkKeys the serializer will check if keys are valid.
* @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**.
* @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**.
* @return {Buffer} returns the Buffer object containing the serialized object.
* @api public
*/
BSON.serialize = function(object, checkKeys, asBuffer, serializeFunctions) {
// Throw error if we are trying serialize an illegal type
if(object == null || typeof object != 'object' || Array.isArray(object))
throw new Error("Only javascript objects supported");
// Emoty target buffer
var buffer = null;
// Calculate the size of the object
var size = BSON.calculateObjectSize(object, serializeFunctions);
// Fetch the best available type for storing the binary data
if(buffer = typeof Buffer != 'undefined') {
buffer = new Buffer(size);
asBuffer = true;
} else if(typeof Uint8Array != 'undefined') {
buffer = new Uint8Array(new ArrayBuffer(size));
} else {
buffer = new Array(size);
}
// If asBuffer is false use typed arrays
BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, 0, serializeFunctions);
return buffer;
}
/**
* Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5
*
* @ignore
* @api private
*/
var functionCache = BSON.functionCache = {};
/**
* Crc state variables shared by function
*
* @ignore
* @api private
*/
var table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D];
/**
* CRC32 hash method, Fast and enough versitility for our usage
*
* @ignore
* @api private
*/
var crc32 = function(string, start, end) {
var crc = 0
var x = 0;
var y = 0;
crc = crc ^ (-1);
for(var i = start, iTop = end; i < iTop;i++) {
y = (crc ^ string[i]) & 0xFF;
x = table[y];
crc = (crc >>> 8) ^ x;
}
return crc ^ (-1);
}
/**
* Deserialize stream data as BSON documents.
*
* Options
* - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized.
* - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse.
* - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function.
* - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits
*
* @param {Buffer} data the buffer containing the serialized set of BSON documents.
* @param {Number} startIndex the start index in the data Buffer where the deserialization is to start.
* @param {Number} numberOfDocuments number of documents to deserialize.
* @param {Array} documents an array where to store the deserialized documents.
* @param {Number} docStartIndex the index in the documents array from where to start inserting documents.
* @param {Object} [options] additional options used for the deserialization.
* @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents.
* @api public
*/
BSON.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) {
// if(numberOfDocuments !== documents.length) throw new Error("Number of expected results back is less than the number of documents");
options = options != null ? options : {};
var index = startIndex;
// Loop over all documents
for(var i = 0; i < numberOfDocuments; i++) {
// Find size of the document
var size = data[index] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24;
// Update options with index
options['index'] = index;
// Parse the document at this point
documents[docStartIndex + i] = BSON.deserialize(data, options);
// Adjust index by the document size
index = index + size;
}
// Return object containing end index of parsing and list of documents
return index;
}
/**
* Ensure eval is isolated.
*
* @ignore
* @api private
*/
var isolateEvalWithHash = function(functionCache, hash, functionString, object) {
// Contains the value we are going to set
var value = null;
// Check for cache hit, eval if missing and return cached function
if(functionCache[hash] == null) {
eval("value = " + functionString);
functionCache[hash] = value;
}
// Set the object
return functionCache[hash].bind(object);
}
/**
* Ensure eval is isolated.
*
* @ignore
* @api private
*/
var isolateEval = function(functionString) {
// Contains the value we are going to set
var value = null;
// Eval the function
eval("value = " + functionString);
return value;
}
/**
* Convert Uint8Array to String
*
* @ignore
* @api private
*/
var convertUint8ArrayToUtf8String = function(byteArray, startIndex, endIndex) {
return BinaryParser.decode_utf8(convertArraytoUtf8BinaryString(byteArray, startIndex, endIndex));
}
var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) {
var result = "";
for(var i = startIndex; i < endIndex; i++) {
result = result + String.fromCharCode(byteArray[i]);
}
return result;
};
/**
* Deserialize data as BSON.
*
* Options
* - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized.
* - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse.
* - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function.
* - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits
*
* @param {Buffer} buffer the buffer containing the serialized set of BSON documents.
* @param {Object} [options] additional options used for the deserialization.
* @param {Boolean} [isArray] ignore used for recursive parsing.
* @return {Object} returns the deserialized Javascript Object.
* @api public
*/
BSON.deserialize = function(buffer, options, isArray) {
// Options
options = options == null ? {} : options;
var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions'];
var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions'];
var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32'];
var promoteLongs = options['promoteLongs'] || true;
// Validate that we have at least 4 bytes of buffer
if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long");
// Set up index
var index = typeof options['index'] == 'number' ? options['index'] : 0;
// Reads in a C style string
var readCStyleString = function() {
// Get the start search index
var i = index;
// Locate the end of the c string
while(buffer[i] !== 0x00) { i++ }
// Grab utf8 encoded string
var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, i) : convertUint8ArrayToUtf8String(buffer, index, i);
// Update index position
index = i + 1;
// Return string
return string;
}
// Create holding object
var object = isArray ? [] : {};
// Read the document size
var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Ensure buffer is valid size
if(size < 5 || size > buffer.length) throw new Error("corrupt bson message");
// While we have more left data left keep parsing
while(true) {
// Read the type
var elementType = buffer[index++];
// If we get a zero it's the last byte, exit
if(elementType == 0) break;
// Read the name of the field
var name = readCStyleString();
// Switch on the type
switch(elementType) {
case BSON.BSON_DATA_OID:
var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('binary', index, index + 12) : convertArraytoUtf8BinaryString(buffer, index, index + 12);
// Decode the oid
object[name] = new ObjectID(string);
// Update index
index = index + 12;
break;
case BSON.BSON_DATA_STRING:
// Read the content of the field
var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Add string to object
object[name] = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1);
// Update parse index position
index = index + stringSize;
break;
case BSON.BSON_DATA_INT:
// Decode the 32bit value
object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
break;
case BSON.BSON_DATA_NUMBER:
// Decode the double value
object[name] = readIEEE754(buffer, index, 'little', 52, 8);
// Update the index
index = index + 8;
break;
case BSON.BSON_DATA_DATE:
// Unpack the low and high bits
var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Set date object
object[name] = new Date(new Long(lowBits, highBits).toNumber());
break;
case BSON.BSON_DATA_BOOLEAN:
// Parse the boolean value
object[name] = buffer[index++] == 1;
break;
case BSON.BSON_DATA_NULL:
// Parse the boolean value
object[name] = null;
break;
case BSON.BSON_DATA_BINARY:
// Decode the size of the binary blob
var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Decode the subtype
var subType = buffer[index++];
// Decode as raw Buffer object if options specifies it
if(buffer['slice'] != null) {
// If we have subtype 2 skip the 4 bytes for the size
if(subType == Binary.SUBTYPE_BYTE_ARRAY) {
binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
}
// Slice the data
object[name] = new Binary(buffer.slice(index, index + binarySize), subType);
} else {
var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize);
// If we have subtype 2 skip the 4 bytes for the size
if(subType == Binary.SUBTYPE_BYTE_ARRAY) {
binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
}
// Copy the data
for(var i = 0; i < binarySize; i++) {
_buffer[i] = buffer[index + i];
}
// Create the binary object
object[name] = new Binary(_buffer, subType);
}
// Update the index
index = index + binarySize;
break;
case BSON.BSON_DATA_ARRAY:
options['index'] = index;
// Decode the size of the array document
var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
// Set the array to the object
object[name] = BSON.deserialize(buffer, options, true);
// Adjust the index
index = index + objectSize;
break;
case BSON.BSON_DATA_OBJECT:
options['index'] = index;
// Decode the size of the object document
var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
// Set the array to the object
object[name] = BSON.deserialize(buffer, options, false);
// Adjust the index
index = index + objectSize;
break;
case BSON.BSON_DATA_REGEXP:
// Create the regexp
var source = readCStyleString();
var regExpOptions = readCStyleString();
// For each option add the corresponding one for javascript
var optionsArray = new Array(regExpOptions.length);
// Parse options
for(var i = 0; i < regExpOptions.length; i++) {
switch(regExpOptions[i]) {
case 'm':
optionsArray[i] = 'm';
break;
case 's':
optionsArray[i] = 'g';
break;
case 'i':
optionsArray[i] = 'i';
break;
}
}
object[name] = new RegExp(source, optionsArray.join(''));
break;
case BSON.BSON_DATA_LONG:
// Unpack the low and high bits
var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Create long object
var long = new Long(lowBits, highBits);
// Promote the long if possible
if(promoteLongs) {
object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long;
} else {
object[name] = long;
}
break;
case BSON.BSON_DATA_SYMBOL:
// Read the content of the field
var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Add string to object
object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1));
// Update parse index position
index = index + stringSize;
break;
case BSON.BSON_DATA_TIMESTAMP:
// Unpack the low and high bits
var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Set the object
object[name] = new Timestamp(lowBits, highBits);
break;
case BSON.BSON_DATA_MIN_KEY:
// Parse the object
object[name] = new MinKey();
break;
case BSON.BSON_DATA_MAX_KEY:
// Parse the object
object[name] = new MaxKey();
break;
case BSON.BSON_DATA_CODE:
// Read the content of the field
var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Function string
var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1);
// If we are evaluating the functions
if(evalFunctions) {
// Contains the value we are going to set
var value = null;
// If we have cache enabled let's look for the md5 of the function in the cache
if(cacheFunctions) {
var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString;
// Got to do this to avoid V8 deoptimizing the call due to finding eval
object[name] = isolateEvalWithHash(functionCache, hash, functionString, object);
} else {
// Set directly
object[name] = isolateEval(functionString);
}
} else {
object[name] = new Code(functionString, {});
}
// Update parse index position
index = index + stringSize;
break;
case BSON.BSON_DATA_CODE_W_SCOPE:
// Read the content of the field
var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24;
// Javascript function
var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1);
// Update parse index position
index = index + stringSize;
// Parse the element
options['index'] = index;
// Decode the size of the object document
var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24;
// Decode the scope object
var scopeObject = BSON.deserialize(buffer, options, false);
// Adjust the index
index = index + objectSize;
// If we are evaluating the functions
if(evalFunctions) {
// Contains the value we are going to set
var value = null;
// If we have cache enabled let's look for the md5 of the function in the cache
if(cacheFunctions) {
var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString;
// Got to do this to avoid V8 deoptimizing the call due to finding eval
object[name] = isolateEvalWithHash(functionCache, hash, functionString, object);
} else {
// Set directly
object[name] = isolateEval(functionString);
}
// Set the scope on the object
object[name].scope = scopeObject;
} else {
object[name] = new Code(functionString, scopeObject);
}
// Add string to object
break;
}
}
// Check if we have a db ref object
if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']);
// Return the final objects
return object;
}
/**
* Check if key name is valid.
*
* @ignore
* @api private
*/
BSON.checkKey = function checkKey (key, dollarsAndDotsOk) {
if (!key.length) return;
// Check if we have a legal key for the object
if (!!~key.indexOf("\x00")) {
// The BSON spec doesn't allow keys with null bytes because keys are
// null-terminated.
throw Error("key " + key + " must not contain null bytes");
}
if (!dollarsAndDotsOk) {
if('$' == key[0]) {
throw Error("key " + key + " must not start with '$'");
} else if (!!~key.indexOf('.')) {
throw Error("key " + key + " must not contain '.'");
}
}
};
/**
* Deserialize data as BSON.
*
* Options
* - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized.
* - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse.
* - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function.
*
* @param {Buffer} buffer the buffer containing the serialized set of BSON documents.
* @param {Object} [options] additional options used for the deserialization.
* @param {Boolean} [isArray] ignore used for recursive parsing.
* @return {Object} returns the deserialized Javascript Object.
* @api public
*/
BSON.prototype.deserialize = function(data, options) {
return BSON.deserialize(data, options);
}
/**
* Deserialize stream data as BSON documents.
*
* Options
* - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized.
* - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse.
* - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function.
*
* @param {Buffer} data the buffer containing the serialized set of BSON documents.
* @param {Number} startIndex the start index in the data Buffer where the deserialization is to start.
* @param {Number} numberOfDocuments number of documents to deserialize.
* @param {Array} documents an array where to store the deserialized documents.
* @param {Number} docStartIndex the index in the documents array from where to start inserting documents.
* @param {Object} [options] additional options used for the deserialization.
* @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents.
* @api public
*/
BSON.prototype.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) {
return BSON.deserializeStream(data, startIndex, numberOfDocuments, documents, docStartIndex, options);
}
/**
* Serialize a Javascript object.
*
* @param {Object} object the Javascript object to serialize.
* @param {Boolean} checkKeys the serializer will check if keys are valid.
* @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**.
* @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**.
* @return {Buffer} returns the Buffer object containing the serialized object.
* @api public
*/
BSON.prototype.serialize = function(object, checkKeys, asBuffer, serializeFunctions) {
return BSON.serialize(object, checkKeys, asBuffer, serializeFunctions);
}
/**
* Calculate the bson size for a passed in Javascript object.
*
* @param {Object} object the Javascript object to calculate the BSON byte size for.
* @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**.
* @return {Number} returns the number of bytes the BSON object will take up.
* @api public
*/
BSON.prototype.calculateObjectSize = function(object, serializeFunctions) {
return BSON.calculateObjectSize(object, serializeFunctions);
}
/**
* Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization.
*
* @param {Object} object the Javascript object to serialize.
* @param {Boolean} checkKeys the serializer will check if keys are valid.
* @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object.
* @param {Number} index the index in the buffer where we wish to start serializing into.
* @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**.
* @return {Number} returns the new write index in the Buffer.
* @api public
*/
BSON.prototype.serializeWithBufferAndIndex = function(object, checkKeys, buffer, startIndex, serializeFunctions) {
return BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, startIndex, serializeFunctions);
}
/**
* @ignore
* @api private
*/
exports.Code = Code;
exports.Symbol = Symbol;
exports.BSON = BSON;
exports.DBRef = DBRef;
exports.Binary = Binary;
exports.ObjectID = ObjectID;
exports.Long = Long;
exports.Timestamp = Timestamp;
exports.Double = Double;
exports.MinKey = MinKey;
exports.MaxKey = MaxKey;
},
'code': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON Code type.
*
* @class Represents the BSON Code type.
* @param {String|Function} code a string or function.
* @param {Object} [scope] an optional scope for the function.
* @return {Code}
*/
function Code(code, scope) {
if(!(this instanceof Code)) return new Code(code, scope);
this._bsontype = 'Code';
this.code = code;
this.scope = scope == null ? {} : scope;
};
/**
* @ignore
* @api private
*/
Code.prototype.toJSON = function() {
return {scope:this.scope, code:this.code};
}
exports.Code = Code;
},
'db_ref': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON DBRef type.
*
* @class Represents the BSON DBRef type.
* @param {String} namespace the collection name.
* @param {ObjectID} oid the reference ObjectID.
* @param {String} [db] optional db name, if omitted the reference is local to the current db.
* @return {DBRef}
*/
function DBRef(namespace, oid, db) {
if(!(this instanceof DBRef)) return new DBRef(namespace, oid, db);
this._bsontype = 'DBRef';
this.namespace = namespace;
this.oid = oid;
this.db = db;
};
/**
* @ignore
* @api private
*/
DBRef.prototype.toJSON = function() {
return {
'$ref':this.namespace,
'$id':this.oid,
'$db':this.db == null ? '' : this.db
};
}
exports.DBRef = DBRef;
},
'double': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON Double type.
*
* @class Represents the BSON Double type.
* @param {Number} value the number we want to represent as a double.
* @return {Double}
*/
function Double(value) {
if(!(this instanceof Double)) return new Double(value);
this._bsontype = 'Double';
this.value = value;
}
/**
* Access the number value.
*
* @return {Number} returns the wrapped double number.
* @api public
*/
Double.prototype.valueOf = function() {
return this.value;
};
/**
* @ignore
* @api private
*/
Double.prototype.toJSON = function() {
return this.value;
}
exports.Double = Double;
},
'float_parser': function(module, exports, global, require, undefined){
// Copyright (c) 2008, Fair Oaks Labs, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//
// Modifications to writeIEEE754 to support negative zeroes made by Brian White
var readIEEE754 = function(buffer, offset, endian, mLen, nBytes) {
var e, m,
bBE = (endian === 'big'),
eLen = nBytes * 8 - mLen - 1,
eMax = (1 << eLen) - 1,
eBias = eMax >> 1,
nBits = -7,
i = bBE ? 0 : (nBytes - 1),
d = bBE ? 1 : -1,
s = buffer[offset + i];
i += d;
e = s & ((1 << (-nBits)) - 1);
s >>= (-nBits);
nBits += eLen;
for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8);
m = e & ((1 << (-nBits)) - 1);
e >>= (-nBits);
nBits += mLen;
for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8);
if (e === 0) {
e = 1 - eBias;
} else if (e === eMax) {
return m ? NaN : ((s ? -1 : 1) * Infinity);
} else {
m = m + Math.pow(2, mLen);
e = e - eBias;
}
return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
};
var writeIEEE754 = function(buffer, value, offset, endian, mLen, nBytes) {
var e, m, c,
bBE = (endian === 'big'),
eLen = nBytes * 8 - mLen - 1,
eMax = (1 << eLen) - 1,
eBias = eMax >> 1,
rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0),
i = bBE ? (nBytes-1) : 0,
d = bBE ? -1 : 1,
s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
value = Math.abs(value);
if (isNaN(value) || value === Infinity) {
m = isNaN(value) ? 1 : 0;
e = eMax;
} else {
e = Math.floor(Math.log(value) / Math.LN2);
if (value * (c = Math.pow(2, -e)) < 1) {
e--;
c *= 2;
}
if (e+eBias >= 1) {
value += rt / c;
} else {
value += rt * Math.pow(2, 1 - eBias);
}
if (value * c >= 2) {
e++;
c /= 2;
}
if (e + eBias >= eMax) {
m = 0;
e = eMax;
} else if (e + eBias >= 1) {
m = (value * c - 1) * Math.pow(2, mLen);
e = e + eBias;
} else {
m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
e = 0;
}
}
for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8);
e = (e << mLen) | m;
eLen += mLen;
for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8);
buffer[offset + i - d] |= s * 128;
};
exports.readIEEE754 = readIEEE754;
exports.writeIEEE754 = writeIEEE754;
},
'index': function(module, exports, global, require, undefined){
try {
exports.BSONPure = require('./bson');
exports.BSONNative = require('../../ext');
} catch(err) {
// do nothing
}
[ './binary_parser'
, './binary'
, './code'
, './db_ref'
, './double'
, './max_key'
, './min_key'
, './objectid'
, './symbol'
, './timestamp'
, './long'].forEach(function (path) {
var module = require('./' + path);
for (var i in module) {
exports[i] = module[i];
}
});
// Exports all the classes for the NATIVE JS BSON Parser
exports.native = function() {
var classes = {};
// Map all the classes
[ './binary_parser'
, './binary'
, './code'
, './db_ref'
, './double'
, './max_key'
, './min_key'
, './objectid'
, './symbol'
, './timestamp'
, './long'
, '../../ext'
].forEach(function (path) {
var module = require('./' + path);
for (var i in module) {
classes[i] = module[i];
}
});
// Return classes list
return classes;
}
// Exports all the classes for the PURE JS BSON Parser
exports.pure = function() {
var classes = {};
// Map all the classes
[ './binary_parser'
, './binary'
, './code'
, './db_ref'
, './double'
, './max_key'
, './min_key'
, './objectid'
, './symbol'
, './timestamp'
, './long'
, '././bson'].forEach(function (path) {
var module = require('./' + path);
for (var i in module) {
classes[i] = module[i];
}
});
// Return classes list
return classes;
}
},
'long': function(module, exports, global, require, undefined){
// 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.
//
// Copyright 2009 Google Inc. All Rights Reserved
/**
* Defines a Long class for representing a 64-bit two's-complement
* integer value, which faithfully simulates the behavior of a Java "Long". This
* implementation is derived from LongLib in GWT.
*
* Constructs a 64-bit two's-complement integer, given its low and high 32-bit
* values as *signed* integers. See the from* functions below for more
* convenient ways of constructing Longs.
*
* The internal representation of a Long is the two given signed, 32-bit values.
* We use 32-bit pieces because these are the size of integers on which
* Javascript performs bit-operations. For operations like addition and
* multiplication, we split each number into 16-bit pieces, which can easily be
* multiplied within Javascript's floating-point representation without overflow
* or change in sign.
*
* In the algorithms below, we frequently reduce the negative case to the
* positive case by negating the input(s) and then post-processing the result.
* Note that we must ALWAYS check specially whether those values are MIN_VALUE
* (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
* a positive number, it overflows back into a negative). Not handling this
* case would often result in infinite recursion.
*
* @class Represents the BSON Long type.
* @param {Number} low the low (signed) 32 bits of the Long.
* @param {Number} high the high (signed) 32 bits of the Long.
*/
function Long(low, high) {
if(!(this instanceof Long)) return new Long(low, high);
this._bsontype = 'Long';
/**
* @type {number}
* @api private
*/
this.low_ = low | 0; // force into 32 signed bits.
/**
* @type {number}
* @api private
*/
this.high_ = high | 0; // force into 32 signed bits.
};
/**
* Return the int value.
*
* @return {Number} the value, assuming it is a 32-bit integer.
* @api public
*/
Long.prototype.toInt = function() {
return this.low_;
};
/**
* Return the Number value.
*
* @return {Number} the closest floating-point representation to this value.
* @api public
*/
Long.prototype.toNumber = function() {
return this.high_ * Long.TWO_PWR_32_DBL_ +
this.getLowBitsUnsigned();
};
/**
* Return the JSON value.
*
* @return {String} the JSON representation.
* @api public
*/
Long.prototype.toJSON = function() {
return this.toString();
}
/**
* Return the String value.
*
* @param {Number} [opt_radix] the radix in which the text should be written.
* @return {String} the textual representation of this value.
* @api public
*/
Long.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
}
if (this.isNegative()) {
if (this.equals(Long.MIN_VALUE)) {
// We need to change the Long value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixLong = Long.fromNumber(radix);
var div = this.div(radixLong);
var rem = div.multiply(radixLong).subtract(this);
return div.toString(radix) + rem.toInt().toString(radix);
} else {
return '-' + this.negate().toString(radix);
}
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = Long.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.div(radixToPower);
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt();
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/**
* Return the high 32-bits value.
*
* @return {Number} the high 32-bits as a signed value.
* @api public
*/
Long.prototype.getHighBits = function() {
return this.high_;
};
/**
* Return the low 32-bits value.
*
* @return {Number} the low 32-bits as a signed value.
* @api public
*/
Long.prototype.getLowBits = function() {
return this.low_;
};
/**
* Return the low unsigned 32-bits value.
*
* @return {Number} the low 32-bits as an unsigned value.
* @api public
*/
Long.prototype.getLowBitsUnsigned = function() {
return (this.low_ >= 0) ?
this.low_ : Long.TWO_PWR_32_DBL_ + this.low_;
};
/**
* Returns the number of bits needed to represent the absolute value of this Long.
*
* @return {Number} Returns the number of bits needed to represent the absolute value of this Long.
* @api public
*/
Long.prototype.getNumBitsAbs = function() {
if (this.isNegative()) {
if (this.equals(Long.MIN_VALUE)) {
return 64;
} else {
return this.negate().getNumBitsAbs();
}
} else {
var val = this.high_ != 0 ? this.high_ : this.low_;
for (var bit = 31; bit > 0; bit--) {
if ((val & (1 << bit)) != 0) {
break;
}
}
return this.high_ != 0 ? bit + 33 : bit + 1;
}
};
/**
* Return whether this value is zero.
*
* @return {Boolean} whether this value is zero.
* @api public
*/
Long.prototype.isZero = function() {
return this.high_ == 0 && this.low_ == 0;
};
/**
* Return whether this value is negative.
*
* @return {Boolean} whether this value is negative.
* @api public
*/
Long.prototype.isNegative = function() {
return this.high_ < 0;
};
/**
* Return whether this value is odd.
*
* @return {Boolean} whether this value is odd.
* @api public
*/
Long.prototype.isOdd = function() {
return (this.low_ & 1) == 1;
};
/**
* Return whether this Long equals the other
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long equals the other
* @api public
*/
Long.prototype.equals = function(other) {
return (this.high_ == other.high_) && (this.low_ == other.low_);
};
/**
* Return whether this Long does not equal the other.
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long does not equal the other.
* @api public
*/
Long.prototype.notEquals = function(other) {
return (this.high_ != other.high_) || (this.low_ != other.low_);
};
/**
* Return whether this Long is less than the other.
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long is less than the other.
* @api public
*/
Long.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* Return whether this Long is less than or equal to the other.
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long is less than or equal to the other.
* @api public
*/
Long.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* Return whether this Long is greater than the other.
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long is greater than the other.
* @api public
*/
Long.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* Return whether this Long is greater than or equal to the other.
*
* @param {Long} other Long to compare against.
* @return {Boolean} whether this Long is greater than or equal to the other.
* @api public
*/
Long.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* Compares this Long with the given one.
*
* @param {Long} other Long to compare against.
* @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater.
* @api public
*/
Long.prototype.compare = function(other) {
if (this.equals(other)) {
return 0;
}
var thisNeg = this.isNegative();
var otherNeg = other.isNegative();
if (thisNeg && !otherNeg) {
return -1;
}
if (!thisNeg && otherNeg) {
return 1;
}
// at this point, the signs are the same, so subtraction will not overflow
if (this.subtract(other).isNegative()) {
return -1;
} else {
return 1;
}
};
/**
* The negation of this value.
*
* @return {Long} the negation of this value.
* @api public
*/
Long.prototype.negate = function() {
if (this.equals(Long.MIN_VALUE)) {
return Long.MIN_VALUE;
} else {
return this.not().add(Long.ONE);
}
};
/**
* Returns the sum of this and the given Long.
*
* @param {Long} other Long to add to this one.
* @return {Long} the sum of this and the given Long.
* @api public
*/
Long.prototype.add = function(other) {
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns the difference of this and the given Long.
*
* @param {Long} other Long to subtract from this.
* @return {Long} the difference of this and the given Long.
* @api public
*/
Long.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given Long.
*
* @param {Long} other Long to multiply with this.
* @return {Long} the product of this and the other.
* @api public
*/
Long.prototype.multiply = function(other) {
if (this.isZero()) {
return Long.ZERO;
} else if (other.isZero()) {
return Long.ZERO;
}
if (this.equals(Long.MIN_VALUE)) {
return other.isOdd() ? Long.MIN_VALUE : Long.ZERO;
} else if (other.equals(Long.MIN_VALUE)) {
return this.isOdd() ? Long.MIN_VALUE : Long.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both Longs are small, use float multiplication
if (this.lessThan(Long.TWO_PWR_24_) &&
other.lessThan(Long.TWO_PWR_24_)) {
return Long.fromNumber(this.toNumber() * other.toNumber());
}
// Divide each Long into 4 chunks of 16 bits, and then add up 4x4 products.
// We can skip products that would overflow.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 * b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 * b00;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c16 += a00 * b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 * b00;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a16 * b16;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a00 * b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
c48 &= 0xFFFF;
return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns this Long divided by the given one.
*
* @param {Long} other Long by which to divide.
* @return {Long} this Long divided by the given one.
* @api public
*/
Long.prototype.div = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return Long.ZERO;
}
if (this.equals(Long.MIN_VALUE)) {
if (other.equals(Long.ONE) ||
other.equals(Long.NEG_ONE)) {
return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE
} else if (other.equals(Long.MIN_VALUE)) {
return Long.ONE;
} else {
// At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
var halfThis = this.shiftRight(1);
var approx = halfThis.div(other).shiftLeft(1);
if (approx.equals(Long.ZERO)) {
return other.isNegative() ? Long.ONE : Long.NEG_ONE;
} else {
var rem = this.subtract(other.multiply(approx));
var result = approx.add(rem.div(other));
return result;
}
}
} else if (other.equals(Long.MIN_VALUE)) {
return Long.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().div(other.negate());
} else {
return this.negate().div(other).negate();
}
} else if (other.isNegative()) {
return this.div(other.negate()).negate();
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = Long.ZERO;
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = Long.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = Long.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = Long.ONE;
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Long modulo the given one.
*
* @param {Long} other Long by which to mod.
* @return {Long} this Long modulo the given one.
* @api public
*/
Long.prototype.modulo = function(other) {
return this.subtract(this.div(other).multiply(other));
};
/**
* The bitwise-NOT of this value.
*
* @return {Long} the bitwise-NOT of this value.
* @api public
*/
Long.prototype.not = function() {
return Long.fromBits(~this.low_, ~this.high_);
};
/**
* Returns the bitwise-AND of this Long and the given one.
*
* @param {Long} other the Long with which to AND.
* @return {Long} the bitwise-AND of this and the other.
* @api public
*/
Long.prototype.and = function(other) {
return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_);
};
/**
* Returns the bitwise-OR of this Long and the given one.
*
* @param {Long} other the Long with which to OR.
* @return {Long} the bitwise-OR of this and the other.
* @api public
*/
Long.prototype.or = function(other) {
return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_);
};
/**
* Returns the bitwise-XOR of this Long and the given one.
*
* @param {Long} other the Long with which to XOR.
* @return {Long} the bitwise-XOR of this and the other.
* @api public
*/
Long.prototype.xor = function(other) {
return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_);
};
/**
* Returns this Long with bits shifted to the left by the given amount.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Long} this shifted to the left by the given amount.
* @api public
*/
Long.prototype.shiftLeft = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var low = this.low_;
if (numBits < 32) {
var high = this.high_;
return Long.fromBits(
low << numBits,
(high << numBits) | (low >>> (32 - numBits)));
} else {
return Long.fromBits(0, low << (numBits - 32));
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Long} this shifted to the right by the given amount.
* @api public
*/
Long.prototype.shiftRight = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >> numBits);
} else {
return Long.fromBits(
high >> (numBits - 32),
high >= 0 ? 0 : -1);
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount, with the new top bits matching the current sign bit.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Long} this shifted to the right by the given amount, with zeros placed into the new leading bits.
* @api public
*/
Long.prototype.shiftRightUnsigned = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >>> numBits);
} else if (numBits == 32) {
return Long.fromBits(high, 0);
} else {
return Long.fromBits(high >>> (numBits - 32), 0);
}
}
};
/**
* Returns a Long representing the given (32-bit) integer value.
*
* @param {Number} value the 32-bit integer in question.
* @return {Long} the corresponding Long value.
* @api public
*/
Long.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = Long.INT_CACHE_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new Long(value | 0, value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
Long.INT_CACHE_[value] = obj;
}
return obj;
};
/**
* Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned.
*
* @param {Number} value the number in question.
* @return {Long} the corresponding Long value.
* @api public
*/
Long.fromNumber = function(value) {
if (isNaN(value) || !isFinite(value)) {
return Long.ZERO;
} else if (value <= -Long.TWO_PWR_63_DBL_) {
return Long.MIN_VALUE;
} else if (value + 1 >= Long.TWO_PWR_63_DBL_) {
return Long.MAX_VALUE;
} else if (value < 0) {
return Long.fromNumber(-value).negate();
} else {
return new Long(
(value % Long.TWO_PWR_32_DBL_) | 0,
(value / Long.TWO_PWR_32_DBL_) | 0);
}
};
/**
* Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits.
*
* @param {Number} lowBits the low 32-bits.
* @param {Number} highBits the high 32-bits.
* @return {Long} the corresponding Long value.
* @api public
*/
Long.fromBits = function(lowBits, highBits) {
return new Long(lowBits, highBits);
};
/**
* Returns a Long representation of the given string, written using the given radix.
*
* @param {String} str the textual representation of the Long.
* @param {Number} opt_radix the radix in which the text is written.
* @return {Long} the corresponding Long value.
* @api public
*/
Long.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return Long.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character: ' + str);
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = Long.fromNumber(Math.pow(radix, 8));
var result = Long.ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = Long.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(Long.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(Long.fromNumber(value));
}
}
return result;
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Long representations of small integer values.
* @type {Object}
* @api private
*/
Long.INT_CACHE_ = {};
// NOTE: the compiler should inline these constant values below and then remove
// these variables, so there should be no runtime penalty for these.
/**
* Number used repeated below in calculations. This must appear before the
* first call to any from* function below.
* @type {number}
* @api private
*/
Long.TWO_PWR_16_DBL_ = 1 << 16;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_24_DBL_ = 1 << 24;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_32_DBL_ = Long.TWO_PWR_16_DBL_ * Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_31_DBL_ = Long.TWO_PWR_32_DBL_ / 2;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_48_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_64_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_32_DBL_;
/**
* @type {number}
* @api private
*/
Long.TWO_PWR_63_DBL_ = Long.TWO_PWR_64_DBL_ / 2;
/** @type {Long} */
Long.ZERO = Long.fromInt(0);
/** @type {Long} */
Long.ONE = Long.fromInt(1);
/** @type {Long} */
Long.NEG_ONE = Long.fromInt(-1);
/** @type {Long} */
Long.MAX_VALUE =
Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0);
/** @type {Long} */
Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0);
/**
* @type {Long}
* @api private
*/
Long.TWO_PWR_24_ = Long.fromInt(1 << 24);
/**
* Expose.
*/
exports.Long = Long;
},
'max_key': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON MaxKey type.
*
* @class Represents the BSON MaxKey type.
* @return {MaxKey}
*/
function MaxKey() {
if(!(this instanceof MaxKey)) return new MaxKey();
this._bsontype = 'MaxKey';
}
exports.MaxKey = MaxKey;
},
'min_key': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON MinKey type.
*
* @class Represents the BSON MinKey type.
* @return {MinKey}
*/
function MinKey() {
if(!(this instanceof MinKey)) return new MinKey();
this._bsontype = 'MinKey';
}
exports.MinKey = MinKey;
},
'objectid': function(module, exports, global, require, undefined){
/**
* Module dependencies.
*/
var BinaryParser = require('./binary_parser').BinaryParser;
/**
* Machine id.
*
* Create a random 3-byte value (i.e. unique for this
* process). Other drivers use a md5 of the machine id here, but
* that would mean an asyc call to gethostname, so we don't bother.
*/
var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10);
// Regular expression that checks for hex value
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
/**
* Create a new ObjectID instance
*
* @class Represents the BSON ObjectID type
* @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number.
* @return {Object} instance of ObjectID.
*/
var ObjectID = function ObjectID(id, _hex) {
if(!(this instanceof ObjectID)) return new ObjectID(id, _hex);
this._bsontype = 'ObjectID';
var __id = null;
// Throw an error if it's not a valid setup
if(id != null && 'number' != typeof id && (id.length != 12 && id.length != 24))
throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");
// Generate id based on the input
if(id == null || typeof id == 'number') {
// convert to 12 byte binary string
this.id = this.generate(id);
} else if(id != null && id.length === 12) {
// assume 12 byte string
this.id = id;
} else if(checkForHexRegExp.test(id)) {
return ObjectID.createFromHexString(id);
} else {
throw new Error("Value passed in is not a valid 24 character hex string");
}
if(ObjectID.cacheHexString) this.__id = this.toHexString();
};
// Allow usage of ObjectId aswell as ObjectID
var ObjectId = ObjectID;
/**
* Return the ObjectID id as a 24 byte hex string representation
*
* @return {String} return the 24 byte hex string representation.
* @api public
*/
ObjectID.prototype.toHexString = function() {
if(ObjectID.cacheHexString && this.__id) return this.__id;
var hexString = ''
, number
, value;
for (var index = 0, len = this.id.length; index < len; index++) {
value = BinaryParser.toByte(this.id[index]);
number = value <= 15
? '0' + value.toString(16)
: value.toString(16);
hexString = hexString + number;
}
if(ObjectID.cacheHexString) this.__id = hexString;
return hexString;
};
/**
* Update the ObjectID index used in generating new ObjectID's on the driver
*
* @return {Number} returns next index value.
* @api private
*/
ObjectID.prototype.get_inc = function() {
return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF;
};
/**
* Update the ObjectID index used in generating new ObjectID's on the driver
*
* @return {Number} returns next index value.
* @api private
*/
ObjectID.prototype.getInc = function() {
return this.get_inc();
};
/**
* Generate a 12 byte id string used in ObjectID's
*
* @param {Number} [time] optional parameter allowing to pass in a second based timestamp.
* @return {String} return the 12 byte id binary string.
* @api private
*/
ObjectID.prototype.generate = function(time) {
if ('number' == typeof time) {
var time4Bytes = BinaryParser.encodeInt(time, 32, true, true);
/* for time-based ObjectID the bytes following the time will be zeroed */
var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false);
var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid);
var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true);
} else {
var unixTime = parseInt(Date.now()/1000,10);
var time4Bytes = BinaryParser.encodeInt(unixTime, 32, true, true);
var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false);
var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid);
var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true);
}
return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes;
};
/**
* Converts the id into a 24 byte hex string for printing
*
* @return {String} return the 24 byte hex string representation.
* @api private
*/
ObjectID.prototype.toString = function() {
return this.toHexString();
};
/**
* Converts to a string representation of this Id.
*
* @return {String} return the 24 byte hex string representation.
* @api private
*/
ObjectID.prototype.inspect = ObjectID.prototype.toString;
/**
* Converts to its JSON representation.
*
* @return {String} return the 24 byte hex string representation.
* @api private
*/
ObjectID.prototype.toJSON = function() {
return this.toHexString();
};
/**
* Compares the equality of this ObjectID with `otherID`.
*
* @param {Object} otherID ObjectID instance to compare against.
* @return {Bool} the result of comparing two ObjectID's
* @api public
*/
ObjectID.prototype.equals = function equals (otherID) {
var id = (otherID instanceof ObjectID || otherID.toHexString)
? otherID.id
: ObjectID.createFromHexString(otherID).id;
return this.id === id;
}
/**
* Returns the generation date (accurate up to the second) that this ID was generated.
*
* @return {Date} the generation date
* @api public
*/
ObjectID.prototype.getTimestamp = function() {
var timestamp = new Date();
timestamp.setTime(Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)) * 1000);
return timestamp;
}
/**
* @ignore
* @api private
*/
ObjectID.index = 0;
ObjectID.createPk = function createPk () {
return new ObjectID();
};
/**
* Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID.
*
* @param {Number} time an integer number representing a number of seconds.
* @return {ObjectID} return the created ObjectID
* @api public
*/
ObjectID.createFromTime = function createFromTime (time) {
var id = BinaryParser.encodeInt(time, 32, true, true) +
BinaryParser.encodeInt(0, 64, true, true);
return new ObjectID(id);
};
/**
* Creates an ObjectID from a hex string representation of an ObjectID.
*
* @param {String} hexString create a ObjectID from a passed in 24 byte hexstring.
* @return {ObjectID} return the created ObjectID
* @api public
*/
ObjectID.createFromHexString = function createFromHexString (hexString) {
// Throw an error if it's not a valid setup
if(typeof hexString === 'undefined' || hexString != null && hexString.length != 24)
throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters");
var len = hexString.length;
if(len > 12*2) {
throw new Error('Id cannot be longer than 12 bytes');
}
var result = ''
, string
, number;
for (var index = 0; index < len; index += 2) {
string = hexString.substr(index, 2);
number = parseInt(string, 16);
result += BinaryParser.fromByte(number);
}
return new ObjectID(result, hexString);
};
/**
* @ignore
*/
Object.defineProperty(ObjectID.prototype, "generationTime", {
enumerable: true
, get: function () {
return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true));
}
, set: function (value) {
var value = BinaryParser.encodeInt(value, 32, true, true);
this.id = value + this.id.substr(4);
// delete this.__id;
this.toHexString();
}
});
/**
* Expose.
*/
exports.ObjectID = ObjectID;
exports.ObjectId = ObjectID;
},
'symbol': function(module, exports, global, require, undefined){
/**
* A class representation of the BSON Symbol type.
*
* @class Represents the BSON Symbol type.
* @param {String} value the string representing the symbol.
* @return {Symbol}
*/
function Symbol(value) {
if(!(this instanceof Symbol)) return new Symbol(value);
this._bsontype = 'Symbol';
this.value = value;
}
/**
* Access the wrapped string value.
*
* @return {String} returns the wrapped string.
* @api public
*/
Symbol.prototype.valueOf = function() {
return this.value;
};
/**
* @ignore
* @api private
*/
Symbol.prototype.toString = function() {
return this.value;
}
/**
* @ignore
* @api private
*/
Symbol.prototype.inspect = function() {
return this.value;
}
/**
* @ignore
* @api private
*/
Symbol.prototype.toJSON = function() {
return this.value;
}
exports.Symbol = Symbol;
},
'timestamp': function(module, exports, global, require, undefined){
// 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.
//
// Copyright 2009 Google Inc. All Rights Reserved
/**
* Defines a Timestamp class for representing a 64-bit two's-complement
* integer value, which faithfully simulates the behavior of a Java "Timestamp". This
* implementation is derived from TimestampLib in GWT.
*
* Constructs a 64-bit two's-complement integer, given its low and high 32-bit
* values as *signed* integers. See the from* functions below for more
* convenient ways of constructing Timestamps.
*
* The internal representation of a Timestamp is the two given signed, 32-bit values.
* We use 32-bit pieces because these are the size of integers on which
* Javascript performs bit-operations. For operations like addition and
* multiplication, we split each number into 16-bit pieces, which can easily be
* multiplied within Javascript's floating-point representation without overflow
* or change in sign.
*
* In the algorithms below, we frequently reduce the negative case to the
* positive case by negating the input(s) and then post-processing the result.
* Note that we must ALWAYS check specially whether those values are MIN_VALUE
* (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
* a positive number, it overflows back into a negative). Not handling this
* case would often result in infinite recursion.
*
* @class Represents the BSON Timestamp type.
* @param {Number} low the low (signed) 32 bits of the Timestamp.
* @param {Number} high the high (signed) 32 bits of the Timestamp.
*/
function Timestamp(low, high) {
if(!(this instanceof Timestamp)) return new Timestamp(low, high);
this._bsontype = 'Timestamp';
/**
* @type {number}
* @api private
*/
this.low_ = low | 0; // force into 32 signed bits.
/**
* @type {number}
* @api private
*/
this.high_ = high | 0; // force into 32 signed bits.
};
/**
* Return the int value.
*
* @return {Number} the value, assuming it is a 32-bit integer.
* @api public
*/
Timestamp.prototype.toInt = function() {
return this.low_;
};
/**
* Return the Number value.
*
* @return {Number} the closest floating-point representation to this value.
* @api public
*/
Timestamp.prototype.toNumber = function() {
return this.high_ * Timestamp.TWO_PWR_32_DBL_ +
this.getLowBitsUnsigned();
};
/**
* Return the JSON value.
*
* @return {String} the JSON representation.
* @api public
*/
Timestamp.prototype.toJSON = function() {
return this.toString();
}
/**
* Return the String value.
*
* @param {Number} [opt_radix] the radix in which the text should be written.
* @return {String} the textual representation of this value.
* @api public
*/
Timestamp.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
}
if (this.isNegative()) {
if (this.equals(Timestamp.MIN_VALUE)) {
// We need to change the Timestamp value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixTimestamp = Timestamp.fromNumber(radix);
var div = this.div(radixTimestamp);
var rem = div.multiply(radixTimestamp).subtract(this);
return div.toString(radix) + rem.toInt().toString(radix);
} else {
return '-' + this.negate().toString(radix);
}
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = Timestamp.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.div(radixToPower);
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt();
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/**
* Return the high 32-bits value.
*
* @return {Number} the high 32-bits as a signed value.
* @api public
*/
Timestamp.prototype.getHighBits = function() {
return this.high_;
};
/**
* Return the low 32-bits value.
*
* @return {Number} the low 32-bits as a signed value.
* @api public
*/
Timestamp.prototype.getLowBits = function() {
return this.low_;
};
/**
* Return the low unsigned 32-bits value.
*
* @return {Number} the low 32-bits as an unsigned value.
* @api public
*/
Timestamp.prototype.getLowBitsUnsigned = function() {
return (this.low_ >= 0) ?
this.low_ : Timestamp.TWO_PWR_32_DBL_ + this.low_;
};
/**
* Returns the number of bits needed to represent the absolute value of this Timestamp.
*
* @return {Number} Returns the number of bits needed to represent the absolute value of this Timestamp.
* @api public
*/
Timestamp.prototype.getNumBitsAbs = function() {
if (this.isNegative()) {
if (this.equals(Timestamp.MIN_VALUE)) {
return 64;
} else {
return this.negate().getNumBitsAbs();
}
} else {
var val = this.high_ != 0 ? this.high_ : this.low_;
for (var bit = 31; bit > 0; bit--) {
if ((val & (1 << bit)) != 0) {
break;
}
}
return this.high_ != 0 ? bit + 33 : bit + 1;
}
};
/**
* Return whether this value is zero.
*
* @return {Boolean} whether this value is zero.
* @api public
*/
Timestamp.prototype.isZero = function() {
return this.high_ == 0 && this.low_ == 0;
};
/**
* Return whether this value is negative.
*
* @return {Boolean} whether this value is negative.
* @api public
*/
Timestamp.prototype.isNegative = function() {
return this.high_ < 0;
};
/**
* Return whether this value is odd.
*
* @return {Boolean} whether this value is odd.
* @api public
*/
Timestamp.prototype.isOdd = function() {
return (this.low_ & 1) == 1;
};
/**
* Return whether this Timestamp equals the other
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp equals the other
* @api public
*/
Timestamp.prototype.equals = function(other) {
return (this.high_ == other.high_) && (this.low_ == other.low_);
};
/**
* Return whether this Timestamp does not equal the other.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp does not equal the other.
* @api public
*/
Timestamp.prototype.notEquals = function(other) {
return (this.high_ != other.high_) || (this.low_ != other.low_);
};
/**
* Return whether this Timestamp is less than the other.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp is less than the other.
* @api public
*/
Timestamp.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* Return whether this Timestamp is less than or equal to the other.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp is less than or equal to the other.
* @api public
*/
Timestamp.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* Return whether this Timestamp is greater than the other.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp is greater than the other.
* @api public
*/
Timestamp.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* Return whether this Timestamp is greater than or equal to the other.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} whether this Timestamp is greater than or equal to the other.
* @api public
*/
Timestamp.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* Compares this Timestamp with the given one.
*
* @param {Timestamp} other Timestamp to compare against.
* @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater.
* @api public
*/
Timestamp.prototype.compare = function(other) {
if (this.equals(other)) {
return 0;
}
var thisNeg = this.isNegative();
var otherNeg = other.isNegative();
if (thisNeg && !otherNeg) {
return -1;
}
if (!thisNeg && otherNeg) {
return 1;
}
// at this point, the signs are the same, so subtraction will not overflow
if (this.subtract(other).isNegative()) {
return -1;
} else {
return 1;
}
};
/**
* The negation of this value.
*
* @return {Timestamp} the negation of this value.
* @api public
*/
Timestamp.prototype.negate = function() {
if (this.equals(Timestamp.MIN_VALUE)) {
return Timestamp.MIN_VALUE;
} else {
return this.not().add(Timestamp.ONE);
}
};
/**
* Returns the sum of this and the given Timestamp.
*
* @param {Timestamp} other Timestamp to add to this one.
* @return {Timestamp} the sum of this and the given Timestamp.
* @api public
*/
Timestamp.prototype.add = function(other) {
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns the difference of this and the given Timestamp.
*
* @param {Timestamp} other Timestamp to subtract from this.
* @return {Timestamp} the difference of this and the given Timestamp.
* @api public
*/
Timestamp.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given Timestamp.
*
* @param {Timestamp} other Timestamp to multiply with this.
* @return {Timestamp} the product of this and the other.
* @api public
*/
Timestamp.prototype.multiply = function(other) {
if (this.isZero()) {
return Timestamp.ZERO;
} else if (other.isZero()) {
return Timestamp.ZERO;
}
if (this.equals(Timestamp.MIN_VALUE)) {
return other.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO;
} else if (other.equals(Timestamp.MIN_VALUE)) {
return this.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both Timestamps are small, use float multiplication
if (this.lessThan(Timestamp.TWO_PWR_24_) &&
other.lessThan(Timestamp.TWO_PWR_24_)) {
return Timestamp.fromNumber(this.toNumber() * other.toNumber());
}
// Divide each Timestamp into 4 chunks of 16 bits, and then add up 4x4 products.
// We can skip products that would overflow.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 * b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 * b00;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c16 += a00 * b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 * b00;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a16 * b16;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a00 * b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
c48 &= 0xFFFF;
return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns this Timestamp divided by the given one.
*
* @param {Timestamp} other Timestamp by which to divide.
* @return {Timestamp} this Timestamp divided by the given one.
* @api public
*/
Timestamp.prototype.div = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return Timestamp.ZERO;
}
if (this.equals(Timestamp.MIN_VALUE)) {
if (other.equals(Timestamp.ONE) ||
other.equals(Timestamp.NEG_ONE)) {
return Timestamp.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE
} else if (other.equals(Timestamp.MIN_VALUE)) {
return Timestamp.ONE;
} else {
// At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
var halfThis = this.shiftRight(1);
var approx = halfThis.div(other).shiftLeft(1);
if (approx.equals(Timestamp.ZERO)) {
return other.isNegative() ? Timestamp.ONE : Timestamp.NEG_ONE;
} else {
var rem = this.subtract(other.multiply(approx));
var result = approx.add(rem.div(other));
return result;
}
}
} else if (other.equals(Timestamp.MIN_VALUE)) {
return Timestamp.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().div(other.negate());
} else {
return this.negate().div(other).negate();
}
} else if (other.isNegative()) {
return this.div(other.negate()).negate();
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = Timestamp.ZERO;
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = Timestamp.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = Timestamp.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = Timestamp.ONE;
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Timestamp modulo the given one.
*
* @param {Timestamp} other Timestamp by which to mod.
* @return {Timestamp} this Timestamp modulo the given one.
* @api public
*/
Timestamp.prototype.modulo = function(other) {
return this.subtract(this.div(other).multiply(other));
};
/**
* The bitwise-NOT of this value.
*
* @return {Timestamp} the bitwise-NOT of this value.
* @api public
*/
Timestamp.prototype.not = function() {
return Timestamp.fromBits(~this.low_, ~this.high_);
};
/**
* Returns the bitwise-AND of this Timestamp and the given one.
*
* @param {Timestamp} other the Timestamp with which to AND.
* @return {Timestamp} the bitwise-AND of this and the other.
* @api public
*/
Timestamp.prototype.and = function(other) {
return Timestamp.fromBits(this.low_ & other.low_, this.high_ & other.high_);
};
/**
* Returns the bitwise-OR of this Timestamp and the given one.
*
* @param {Timestamp} other the Timestamp with which to OR.
* @return {Timestamp} the bitwise-OR of this and the other.
* @api public
*/
Timestamp.prototype.or = function(other) {
return Timestamp.fromBits(this.low_ | other.low_, this.high_ | other.high_);
};
/**
* Returns the bitwise-XOR of this Timestamp and the given one.
*
* @param {Timestamp} other the Timestamp with which to XOR.
* @return {Timestamp} the bitwise-XOR of this and the other.
* @api public
*/
Timestamp.prototype.xor = function(other) {
return Timestamp.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_);
};
/**
* Returns this Timestamp with bits shifted to the left by the given amount.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Timestamp} this shifted to the left by the given amount.
* @api public
*/
Timestamp.prototype.shiftLeft = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var low = this.low_;
if (numBits < 32) {
var high = this.high_;
return Timestamp.fromBits(
low << numBits,
(high << numBits) | (low >>> (32 - numBits)));
} else {
return Timestamp.fromBits(0, low << (numBits - 32));
}
}
};
/**
* Returns this Timestamp with bits shifted to the right by the given amount.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Timestamp} this shifted to the right by the given amount.
* @api public
*/
Timestamp.prototype.shiftRight = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return Timestamp.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >> numBits);
} else {
return Timestamp.fromBits(
high >> (numBits - 32),
high >= 0 ? 0 : -1);
}
}
};
/**
* Returns this Timestamp with bits shifted to the right by the given amount, with the new top bits matching the current sign bit.
*
* @param {Number} numBits the number of bits by which to shift.
* @return {Timestamp} this shifted to the right by the given amount, with zeros placed into the new leading bits.
* @api public
*/
Timestamp.prototype.shiftRightUnsigned = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return Timestamp.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >>> numBits);
} else if (numBits == 32) {
return Timestamp.fromBits(high, 0);
} else {
return Timestamp.fromBits(high >>> (numBits - 32), 0);
}
}
};
/**
* Returns a Timestamp representing the given (32-bit) integer value.
*
* @param {Number} value the 32-bit integer in question.
* @return {Timestamp} the corresponding Timestamp value.
* @api public
*/
Timestamp.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = Timestamp.INT_CACHE_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new Timestamp(value | 0, value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
Timestamp.INT_CACHE_[value] = obj;
}
return obj;
};
/**
* Returns a Timestamp representing the given value, provided that it is a finite number. Otherwise, zero is returned.
*
* @param {Number} value the number in question.
* @return {Timestamp} the corresponding Timestamp value.
* @api public
*/
Timestamp.fromNumber = function(value) {
if (isNaN(value) || !isFinite(value)) {
return Timestamp.ZERO;
} else if (value <= -Timestamp.TWO_PWR_63_DBL_) {
return Timestamp.MIN_VALUE;
} else if (value + 1 >= Timestamp.TWO_PWR_63_DBL_) {
return Timestamp.MAX_VALUE;
} else if (value < 0) {
return Timestamp.fromNumber(-value).negate();
} else {
return new Timestamp(
(value % Timestamp.TWO_PWR_32_DBL_) | 0,
(value / Timestamp.TWO_PWR_32_DBL_) | 0);
}
};
/**
* Returns a Timestamp representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits.
*
* @param {Number} lowBits the low 32-bits.
* @param {Number} highBits the high 32-bits.
* @return {Timestamp} the corresponding Timestamp value.
* @api public
*/
Timestamp.fromBits = function(lowBits, highBits) {
return new Timestamp(lowBits, highBits);
};
/**
* Returns a Timestamp representation of the given string, written using the given radix.
*
* @param {String} str the textual representation of the Timestamp.
* @param {Number} opt_radix the radix in which the text is written.
* @return {Timestamp} the corresponding Timestamp value.
* @api public
*/
Timestamp.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return Timestamp.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character: ' + str);
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = Timestamp.fromNumber(Math.pow(radix, 8));
var result = Timestamp.ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = Timestamp.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(Timestamp.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(Timestamp.fromNumber(value));
}
}
return result;
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Timestamp representations of small integer values.
* @type {Object}
* @api private
*/
Timestamp.INT_CACHE_ = {};
// NOTE: the compiler should inline these constant values below and then remove
// these variables, so there should be no runtime penalty for these.
/**
* Number used repeated below in calculations. This must appear before the
* first call to any from* function below.
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_16_DBL_ = 1 << 16;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_24_DBL_ = 1 << 24;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_32_DBL_ = Timestamp.TWO_PWR_16_DBL_ * Timestamp.TWO_PWR_16_DBL_;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_31_DBL_ = Timestamp.TWO_PWR_32_DBL_ / 2;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_48_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_16_DBL_;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_64_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_32_DBL_;
/**
* @type {number}
* @api private
*/
Timestamp.TWO_PWR_63_DBL_ = Timestamp.TWO_PWR_64_DBL_ / 2;
/** @type {Timestamp} */
Timestamp.ZERO = Timestamp.fromInt(0);
/** @type {Timestamp} */
Timestamp.ONE = Timestamp.fromInt(1);
/** @type {Timestamp} */
Timestamp.NEG_ONE = Timestamp.fromInt(-1);
/** @type {Timestamp} */
Timestamp.MAX_VALUE =
Timestamp.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0);
/** @type {Timestamp} */
Timestamp.MIN_VALUE = Timestamp.fromBits(0, 0x80000000 | 0);
/**
* @type {Timestamp}
* @api private
*/
Timestamp.TWO_PWR_24_ = Timestamp.fromInt(1 << 24);
/**
* Expose.
*/
exports.Timestamp = Timestamp;
},
});
if(typeof module != 'undefined' && module.exports ){
module.exports = bson;
if( !module.parent ){
bson();
}
}
if(typeof window != 'undefined' && typeof require == 'undefined'){
window.require = bson.require;
}
{ "name" : "bson"
, "description" : "A bson parser for node.js and the browser"
, "main": "../lib/bson/bson"
, "directories" : { "lib" : "../lib/bson" }
, "engines" : { "node" : ">=0.6.0" }
, "licenses" : [ { "type" : "Apache License, Version 2.0"
, "url" : "http://www.apache.org/licenses/LICENSE-2.0" } ]
}
require('one');
one('./package.json')
.tie('bson', BSON)
// .exclude('buffer')
.tie('buffer', {})
.save('./browser_build/bson.js')
gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
gyp ERR! stack at failNoPython (C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\lib\configure.js:101:14)
gyp ERR! stack at C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\lib\configure.js:64:11
gyp ERR! stack at Object.oncomplete (fs.js:107:15)
gyp ERR! System Windows_NT 6.1.7601
gyp ERR! command "node" "C:\\Program Files\\nodejs\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild"
gyp ERR! cwd D:\Node\Express4\node_modules\connect-mongo-store\node_modules\mongodb\node_modules\bson
gyp ERR! node -v v0.10.26
gyp ERR! node-gyp -v v0.12.2
gyp ERR! not ok
//===========================================================================
#include <stdarg.h>
#include <cstdlib>
#include <cstring>
#include <string.h>
#include <stdlib.h>
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#include <v8.h>
// this and the above block must be around the v8.h header otherwise
// v8 is not happy
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#include <node.h>
#include <node_version.h>
#include <node_buffer.h>
#include <cmath>
#include <iostream>
#include <limits>
#include <vector>
#ifdef __sun
#include <alloca.h>
#endif
#include "bson.h"
using namespace v8;
using namespace node;
//===========================================================================
void DataStream::WriteObjectId(const Handle<Object>& object, const Handle<String>& key)
{
uint16_t buffer[12];
object->Get(key)->ToString()->Write(buffer, 0, 12);
for(uint32_t i = 0; i < 12; ++i)
{
*p++ = (char) buffer[i];
}
}
void ThrowAllocatedStringException(size_t allocationSize, const char* format, ...)
{
va_list args;
va_start(args, format);
char* string = (char*) malloc(allocationSize);
vsprintf(string, format, args);
va_end(args);
throw string;
}
void DataStream::CheckKey(const Local<String>& keyName)
{
size_t keyLength = keyName->Utf8Length();
if(keyLength == 0) return;
// Allocate space for the key, do not need to zero terminate as WriteUtf8 does it
char* keyStringBuffer = (char*) alloca(keyLength + 1);
// Write the key to the allocated buffer
keyName->WriteUtf8(keyStringBuffer);
// Check for the zero terminator
char* terminator = strchr(keyStringBuffer, 0x00);
// If the location is not at the end of the string we've got an illegal 0x00 byte somewhere
if(terminator != &keyStringBuffer[keyLength]) {
ThrowAllocatedStringException(64+keyLength, "key %s must not contain null bytes", keyStringBuffer);
}
if(keyStringBuffer[0] == '$')
{
ThrowAllocatedStringException(64+keyLength, "key %s must not start with '$'", keyStringBuffer);
}
if(strchr(keyStringBuffer, '.') != NULL)
{
ThrowAllocatedStringException(64+keyLength, "key %s must not contain '.'", keyStringBuffer);
}
}
template<typename T> void BSONSerializer<T>::SerializeDocument(const Handle<Value>& value)
{
void* documentSize = this->BeginWriteSize();
Local<Object> object = bson->GetSerializeObject(value);
// Get the object property names
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6
Local<Array> propertyNames = object->GetPropertyNames();
#else
Local<Array> propertyNames = object->GetOwnPropertyNames();
#endif
// Length of the property
int propertyLength = propertyNames->Length();
for(int i = 0; i < propertyLength; ++i)
{
const Local<String>& propertyName = propertyNames->Get(i)->ToString();
if(checkKeys) this->CheckKey(propertyName);
const Local<Value>& propertyValue = object->Get(propertyName);
if(serializeFunctions || !propertyValue->IsFunction())
{
void* typeLocation = this->BeginWriteType();
this->WriteString(propertyName);
SerializeValue(typeLocation, propertyValue);
}
}
this->WriteByte(0);
this->CommitSize(documentSize);
}
template<typename T> void BSONSerializer<T>::SerializeArray(const Handle<Value>& value)
{
void* documentSize = this->BeginWriteSize();
Local<Array> array = Local<Array>::Cast(value->ToObject());
uint32_t arrayLength = array->Length();
for(uint32_t i = 0; i < arrayLength; ++i)
{
void* typeLocation = this->BeginWriteType();
this->WriteUInt32String(i);
SerializeValue(typeLocation, array->Get(i));
}
this->WriteByte(0);
this->CommitSize(documentSize);
}
// This is templated so that we can use this function to both count the number of bytes, and to serialize those bytes.
// The template approach eliminates almost all of the inspection of values unless they're required (eg. string lengths)
// and ensures that there is always consistency between bytes counted and bytes written by design.
template<typename T> void BSONSerializer<T>::SerializeValue(void* typeLocation, const Handle<Value>& value)
{
if(value->IsNumber())
{
double doubleValue = value->NumberValue();
int intValue = (int) doubleValue;
if(intValue == doubleValue)
{
this->CommitType(typeLocation, BSON_TYPE_INT);
this->WriteInt32(intValue);
}
else
{
this->CommitType(typeLocation, BSON_TYPE_NUMBER);
this->WriteDouble(doubleValue);
}
}
else if(value->IsString())
{
this->CommitType(typeLocation, BSON_TYPE_STRING);
this->WriteLengthPrefixedString(value->ToString());
}
else if(value->IsBoolean())
{
this->CommitType(typeLocation, BSON_TYPE_BOOLEAN);
this->WriteBool(value);
}
else if(value->IsArray())
{
this->CommitType(typeLocation, BSON_TYPE_ARRAY);
SerializeArray(value);
}
else if(value->IsDate())
{
this->CommitType(typeLocation, BSON_TYPE_DATE);
this->WriteInt64(value);
}
else if(value->IsRegExp())
{
this->CommitType(typeLocation, BSON_TYPE_REGEXP);
const Handle<RegExp>& regExp = Handle<RegExp>::Cast(value);
this->WriteString(regExp->GetSource());
int flags = regExp->GetFlags();
if(flags & RegExp::kGlobal) this->WriteByte('s');
if(flags & RegExp::kIgnoreCase) this->WriteByte('i');
if(flags & RegExp::kMultiline) this->WriteByte('m');
this->WriteByte(0);
}
else if(value->IsFunction())
{
this->CommitType(typeLocation, BSON_TYPE_CODE);
this->WriteLengthPrefixedString(value->ToString());
}
else if(value->IsObject())
{
const Local<Object>& object = value->ToObject();
if(object->Has(bson->_bsontypeString))
{
const Local<String>& constructorString = object->GetConstructorName();
if(bson->longString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_LONG);
this->WriteInt32(object, bson->_longLowString);
this->WriteInt32(object, bson->_longHighString);
}
else if(bson->timestampString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_TIMESTAMP);
this->WriteInt32(object, bson->_longLowString);
this->WriteInt32(object, bson->_longHighString);
}
else if(bson->objectIDString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_OID);
this->WriteObjectId(object, bson->_objectIDidString);
}
else if(bson->binaryString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_BINARY);
uint32_t length = object->Get(bson->_binaryPositionString)->Uint32Value();
Local<Object> bufferObj = object->Get(bson->_binaryBufferString)->ToObject();
this->WriteInt32(length);
this->WriteByte(object, bson->_binarySubTypeString); // write subtype
// If type 0x02 write the array length aswell
if(object->Get(bson->_binarySubTypeString)->Int32Value() == 0x02) {
this->WriteInt32(length);
}
// Write the actual data
this->WriteData(Buffer::Data(bufferObj), length);
}
else if(bson->doubleString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_NUMBER);
this->WriteDouble(object, bson->_doubleValueString);
}
else if(bson->symbolString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_SYMBOL);
this->WriteLengthPrefixedString(object->Get(bson->_symbolValueString)->ToString());
}
else if(bson->codeString->StrictEquals(constructorString))
{
const Local<String>& function = object->Get(bson->_codeCodeString)->ToString();
const Local<Object>& scope = object->Get(bson->_codeScopeString)->ToObject();
// For Node < 0.6.X use the GetPropertyNames
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 6
uint32_t propertyNameLength = scope->GetPropertyNames()->Length();
#else
uint32_t propertyNameLength = scope->GetOwnPropertyNames()->Length();
#endif
if(propertyNameLength > 0)
{
this->CommitType(typeLocation, BSON_TYPE_CODE_W_SCOPE);
void* codeWidthScopeSize = this->BeginWriteSize();
this->WriteLengthPrefixedString(function->ToString());
SerializeDocument(scope);
this->CommitSize(codeWidthScopeSize);
}
else
{
this->CommitType(typeLocation, BSON_TYPE_CODE);
this->WriteLengthPrefixedString(function->ToString());
}
}
else if(bson->dbrefString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_OBJECT);
void* dbRefSize = this->BeginWriteSize();
void* refType = this->BeginWriteType();
this->WriteData("$ref", 5);
SerializeValue(refType, object->Get(bson->_dbRefNamespaceString));
void* idType = this->BeginWriteType();
this->WriteData("$id", 4);
SerializeValue(idType, object->Get(bson->_dbRefOidString));
const Local<Value>& refDbValue = object->Get(bson->_dbRefDbString);
if(!refDbValue->IsUndefined())
{
void* dbType = this->BeginWriteType();
this->WriteData("$db", 4);
SerializeValue(dbType, refDbValue);
}
this->WriteByte(0);
this->CommitSize(dbRefSize);
}
else if(bson->minKeyString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_MIN_KEY);
}
else if(bson->maxKeyString->StrictEquals(constructorString))
{
this->CommitType(typeLocation, BSON_TYPE_MAX_KEY);
}
}
else if(Buffer::HasInstance(value))
{
this->CommitType(typeLocation, BSON_TYPE_BINARY);
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(value->ToObject());
uint32_t length = object->length();
#else
uint32_t length = Buffer::Length(value->ToObject());
#endif
this->WriteInt32(length);
this->WriteByte(0);
this->WriteData(Buffer::Data(value->ToObject()), length);
}
else
{
this->CommitType(typeLocation, BSON_TYPE_OBJECT);
SerializeDocument(value);
}
}
else if(value->IsNull() || value->IsUndefined())
{
this->CommitType(typeLocation, BSON_TYPE_NULL);
}
}
// Data points to start of element list, length is length of entire document including '\0' but excluding initial size
BSONDeserializer::BSONDeserializer(BSON* aBson, char* data, size_t length)
: bson(aBson),
pStart(data),
p(data),
pEnd(data + length - 1)
{
if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'");
}
BSONDeserializer::BSONDeserializer(BSONDeserializer& parentSerializer, size_t length)
: bson(parentSerializer.bson),
pStart(parentSerializer.p),
p(parentSerializer.p),
pEnd(parentSerializer.p + length - 1)
{
parentSerializer.p += length;
if(pEnd > parentSerializer.pEnd) ThrowAllocatedStringException(64, "Child document exceeds parent's bounds");
if(*pEnd != '\0') ThrowAllocatedStringException(64, "Missing end of document marker '\\0'");
}
Local<String> BSONDeserializer::ReadCString()
{
char* start = p;
while(*p++) { }
return String::New(start, (int32_t) (p-start-1) );
}
int32_t BSONDeserializer::ReadRegexOptions()
{
int32_t options = 0;
for(;;)
{
switch(*p++)
{
case '\0': return options;
case 's': options |= RegExp::kGlobal; break;
case 'i': options |= RegExp::kIgnoreCase; break;
case 'm': options |= RegExp::kMultiline; break;
}
}
}
uint32_t BSONDeserializer::ReadIntegerString()
{
uint32_t value = 0;
while(*p)
{
if(*p < '0' || *p > '9') ThrowAllocatedStringException(64, "Invalid key for array");
value = value * 10 + *p++ - '0';
}
++p;
return value;
}
Local<String> BSONDeserializer::ReadString()
{
uint32_t length = ReadUInt32();
char* start = p;
p += length;
return String::New(start, length-1);
}
Local<String> BSONDeserializer::ReadObjectId()
{
uint16_t objectId[12];
for(size_t i = 0; i < 12; ++i)
{
objectId[i] = *reinterpret_cast<unsigned char*>(p++);
}
return String::New(objectId, 12);
}
Handle<Value> BSONDeserializer::DeserializeDocument(bool promoteLongs)
{
uint32_t length = ReadUInt32();
if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Document is less than 5 bytes");
BSONDeserializer documentDeserializer(*this, length-4);
return documentDeserializer.DeserializeDocumentInternal(promoteLongs);
}
Handle<Value> BSONDeserializer::DeserializeDocumentInternal(bool promoteLongs)
{
Local<Object> returnObject = Object::New();
while(HasMoreData())
{
BsonType type = (BsonType) ReadByte();
const Local<String>& name = ReadCString();
const Handle<Value>& value = DeserializeValue(type, promoteLongs);
returnObject->ForceSet(name, value);
}
if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Document: Serialize consumed unexpected number of bytes");
// From JavaScript:
// if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']);
if(returnObject->Has(bson->_dbRefIdRefString))
{
Local<Value> argv[] = { returnObject->Get(bson->_dbRefRefString), returnObject->Get(bson->_dbRefIdRefString), returnObject->Get(bson->_dbRefDbRefString) };
return bson->dbrefConstructor->NewInstance(3, argv);
}
else
{
return returnObject;
}
}
Handle<Value> BSONDeserializer::DeserializeArray(bool promoteLongs)
{
uint32_t length = ReadUInt32();
if(length < 5) ThrowAllocatedStringException(64, "Bad BSON: Array Document is less than 5 bytes");
BSONDeserializer documentDeserializer(*this, length-4);
return documentDeserializer.DeserializeArrayInternal(promoteLongs);
}
Handle<Value> BSONDeserializer::DeserializeArrayInternal(bool promoteLongs)
{
Local<Array> returnArray = Array::New();
while(HasMoreData())
{
BsonType type = (BsonType) ReadByte();
uint32_t index = ReadIntegerString();
const Handle<Value>& value = DeserializeValue(type, promoteLongs);
returnArray->Set(index, value);
}
if(p != pEnd) ThrowAllocatedStringException(64, "Bad BSON Array: Serialize consumed unexpected number of bytes");
return returnArray;
}
Handle<Value> BSONDeserializer::DeserializeValue(BsonType type, bool promoteLongs)
{
switch(type)
{
case BSON_TYPE_STRING:
return ReadString();
case BSON_TYPE_INT:
return Integer::New(ReadInt32());
case BSON_TYPE_NUMBER:
return Number::New(ReadDouble());
case BSON_TYPE_NULL:
return Null();
case BSON_TYPE_UNDEFINED:
return Undefined();
case BSON_TYPE_TIMESTAMP:
{
int32_t lowBits = ReadInt32();
int32_t highBits = ReadInt32();
Local<Value> argv[] = { Int32::New(lowBits), Int32::New(highBits) };
return bson->timestampConstructor->NewInstance(2, argv);
}
case BSON_TYPE_BOOLEAN:
return (ReadByte() != 0) ? True() : False();
case BSON_TYPE_REGEXP:
{
const Local<String>& regex = ReadCString();
int32_t options = ReadRegexOptions();
return RegExp::New(regex, (RegExp::Flags) options);
}
case BSON_TYPE_CODE:
{
const Local<Value>& code = ReadString();
const Local<Value>& scope = Object::New();
Local<Value> argv[] = { code, scope };
return bson->codeConstructor->NewInstance(2, argv);
}
case BSON_TYPE_CODE_W_SCOPE:
{
ReadUInt32();
const Local<Value>& code = ReadString();
const Handle<Value>& scope = DeserializeDocument(promoteLongs);
Local<Value> argv[] = { code, scope->ToObject() };
return bson->codeConstructor->NewInstance(2, argv);
}
case BSON_TYPE_OID:
{
Local<Value> argv[] = { ReadObjectId() };
return bson->objectIDConstructor->NewInstance(1, argv);
}
case BSON_TYPE_BINARY:
{
uint32_t length = ReadUInt32();
uint32_t subType = ReadByte();
if(subType == 0x02) {
length = ReadInt32();
}
Buffer* buffer = Buffer::New(p, length);
p += length;
Handle<Value> argv[] = { buffer->handle_, Uint32::New(subType) };
return bson->binaryConstructor->NewInstance(2, argv);
}
case BSON_TYPE_LONG:
{
// Read 32 bit integers
int32_t lowBits = (int32_t) ReadInt32();
int32_t highBits = (int32_t) ReadInt32();
// Promote long is enabled
if(promoteLongs) {
// If value is < 2^53 and >-2^53
if((highBits < 0x200000 || (highBits == 0x200000 && lowBits == 0)) && highBits >= -0x200000) {
// Adjust the pointer and read as 64 bit value
p -= 8;
// Read the 64 bit value
int64_t finalValue = (int64_t) ReadInt64();
return Number::New(finalValue);
}
}
// Decode the Long value
Local<Value> argv[] = { Int32::New(lowBits), Int32::New(highBits) };
return bson->longConstructor->NewInstance(2, argv);
}
case BSON_TYPE_DATE:
return Date::New((double) ReadInt64());
case BSON_TYPE_ARRAY:
return DeserializeArray(promoteLongs);
case BSON_TYPE_OBJECT:
return DeserializeDocument(promoteLongs);
case BSON_TYPE_SYMBOL:
{
const Local<String>& string = ReadString();
Local<Value> argv[] = { string };
return bson->symbolConstructor->NewInstance(1, argv);
}
case BSON_TYPE_MIN_KEY:
return bson->minKeyConstructor->NewInstance();
case BSON_TYPE_MAX_KEY:
return bson->maxKeyConstructor->NewInstance();
default:
ThrowAllocatedStringException(64, "Unhandled BSON Type: %d", type);
}
return v8::Null();
}
static Handle<Value> VException(const char *msg)
{
HandleScope scope;
return ThrowException(Exception::Error(String::New(msg)));
}
Persistent<FunctionTemplate> BSON::constructor_template;
BSON::BSON() : ObjectWrap()
{
// Setup pre-allocated comparision objects
_bsontypeString = Persistent<String>::New(String::New("_bsontype"));
_longLowString = Persistent<String>::New(String::New("low_"));
_longHighString = Persistent<String>::New(String::New("high_"));
_objectIDidString = Persistent<String>::New(String::New("id"));
_binaryPositionString = Persistent<String>::New(String::New("position"));
_binarySubTypeString = Persistent<String>::New(String::New("sub_type"));
_binaryBufferString = Persistent<String>::New(String::New("buffer"));
_doubleValueString = Persistent<String>::New(String::New("value"));
_symbolValueString = Persistent<String>::New(String::New("value"));
_dbRefRefString = Persistent<String>::New(String::New("$ref"));
_dbRefIdRefString = Persistent<String>::New(String::New("$id"));
_dbRefDbRefString = Persistent<String>::New(String::New("$db"));
_dbRefNamespaceString = Persistent<String>::New(String::New("namespace"));
_dbRefDbString = Persistent<String>::New(String::New("db"));
_dbRefOidString = Persistent<String>::New(String::New("oid"));
_codeCodeString = Persistent<String>::New(String::New("code"));
_codeScopeString = Persistent<String>::New(String::New("scope"));
_toBSONString = Persistent<String>::New(String::New("toBSON"));
longString = Persistent<String>::New(String::New("Long"));
objectIDString = Persistent<String>::New(String::New("ObjectID"));
binaryString = Persistent<String>::New(String::New("Binary"));
codeString = Persistent<String>::New(String::New("Code"));
dbrefString = Persistent<String>::New(String::New("DBRef"));
symbolString = Persistent<String>::New(String::New("Symbol"));
doubleString = Persistent<String>::New(String::New("Double"));
timestampString = Persistent<String>::New(String::New("Timestamp"));
minKeyString = Persistent<String>::New(String::New("MinKey"));
maxKeyString = Persistent<String>::New(String::New("MaxKey"));
}
void BSON::Initialize(v8::Handle<v8::Object> target)
{
// Grab the scope of the call from Node
HandleScope scope;
// Define a new function template
Local<FunctionTemplate> t = FunctionTemplate::New(New);
constructor_template = Persistent<FunctionTemplate>::New(t);
constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
constructor_template->SetClassName(String::NewSymbol("BSON"));
// Instance methods
NODE_SET_PROTOTYPE_METHOD(constructor_template, "calculateObjectSize", CalculateObjectSize);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "serialize", BSONSerialize);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "serializeWithBufferAndIndex", SerializeWithBufferAndIndex);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserialize", BSONDeserialize);
NODE_SET_PROTOTYPE_METHOD(constructor_template, "deserializeStream", BSONDeserializeStream);
target->ForceSet(String::NewSymbol("BSON"), constructor_template->GetFunction());
}
// Create a new instance of BSON and passing it the existing context
Handle<Value> BSON::New(const Arguments &args)
{
HandleScope scope;
// Check that we have an array
if(args.Length() == 1 && args[0]->IsArray())
{
// Cast the array to a local reference
Local<Array> array = Local<Array>::Cast(args[0]);
if(array->Length() > 0)
{
// Create a bson object instance and return it
BSON *bson = new BSON();
uint32_t foundClassesMask = 0;
// Iterate over all entries to save the instantiate funtions
for(uint32_t i = 0; i < array->Length(); i++) {
// Let's get a reference to the function
Local<Function> func = Local<Function>::Cast(array->Get(i));
Local<String> functionName = func->GetName()->ToString();
// Save the functions making them persistant handles (they don't get collected)
if(functionName->StrictEquals(bson->longString)) {
bson->longConstructor = Persistent<Function>::New(func);
foundClassesMask |= 1;
} else if(functionName->StrictEquals(bson->objectIDString)) {
bson->objectIDConstructor = Persistent<Function>::New(func);
foundClassesMask |= 2;
} else if(functionName->StrictEquals(bson->binaryString)) {
bson->binaryConstructor = Persistent<Function>::New(func);
foundClassesMask |= 4;
} else if(functionName->StrictEquals(bson->codeString)) {
bson->codeConstructor = Persistent<Function>::New(func);
foundClassesMask |= 8;
} else if(functionName->StrictEquals(bson->dbrefString)) {
bson->dbrefConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x10;
} else if(functionName->StrictEquals(bson->symbolString)) {
bson->symbolConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x20;
} else if(functionName->StrictEquals(bson->doubleString)) {
bson->doubleConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x40;
} else if(functionName->StrictEquals(bson->timestampString)) {
bson->timestampConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x80;
} else if(functionName->StrictEquals(bson->minKeyString)) {
bson->minKeyConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x100;
} else if(functionName->StrictEquals(bson->maxKeyString)) {
bson->maxKeyConstructor = Persistent<Function>::New(func);
foundClassesMask |= 0x200;
}
}
// Check if we have the right number of constructors otherwise throw an error
if(foundClassesMask != 0x3ff) {
delete bson;
return VException("Missing function constructor for either [Long/ObjectID/Binary/Code/DbRef/Symbol/Double/Timestamp/MinKey/MaxKey]");
} else {
bson->Wrap(args.This());
return args.This();
}
}
else
{
return VException("No types passed in");
}
}
else
{
return VException("Argument passed in must be an array of types");
}
}
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
Handle<Value> BSON::BSONDeserialize(const Arguments &args)
{
HandleScope scope;
// Fail if the first argument is not a string or a buffer
if(args.Length() > 1 && !args[0]->IsString() && !Buffer::HasInstance(args[0]))
return VException("First Argument must be a Buffer or String.");
// Promote longs
bool promoteLongs = true;
// If we have an options object
if(args.Length() == 2 && args[1]->IsObject()) {
Local<Object> options = args[1]->ToObject();
if(options->Has(String::New("promoteLongs"))) {
promoteLongs = options->Get(String::New("promoteLongs"))->ToBoolean()->Value();
}
}
// Define pointer to data
Local<Object> obj = args[0]->ToObject();
// Unpack the BSON parser instance
BSON *bson = ObjectWrap::Unwrap<BSON>(args.This());
// If we passed in a buffer, let's unpack it, otherwise let's unpack the string
if(Buffer::HasInstance(obj))
{
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(obj);
char* data = buffer->data();
size_t length = buffer->length();
#else
char* data = Buffer::Data(obj);
size_t length = Buffer::Length(obj);
#endif
// Validate that we have at least 5 bytes
if(length < 5) return VException("corrupt bson message < 5 bytes long");
try
{
BSONDeserializer deserializer(bson, data, length);
// deserializer.promoteLongs = promoteLongs;
return deserializer.DeserializeDocument(promoteLongs);
}
catch(char* exception)
{
Handle<Value> error = VException(exception);
free(exception);
return error;
}
}
else
{
// The length of the data for this encoding
ssize_t len = DecodeBytes(args[0], BINARY);
// Validate that we have at least 5 bytes
if(len < 5) return VException("corrupt bson message < 5 bytes long");
// Let's define the buffer size
char* data = (char *)malloc(len);
DecodeWrite(data, len, args[0], BINARY);
try
{
BSONDeserializer deserializer(bson, data, len);
// deserializer.promoteLongs = promoteLongs;
Handle<Value> result = deserializer.DeserializeDocument(promoteLongs);
free(data);
return result;
}
catch(char* exception)
{
Handle<Value> error = VException(exception);
free(exception);
free(data);
return error;
}
}
}
Local<Object> BSON::GetSerializeObject(const Handle<Value>& argValue)
{
Local<Object> object = argValue->ToObject();
if(object->Has(_toBSONString))
{
const Local<Value>& toBSON = object->Get(_toBSONString);
if(!toBSON->IsFunction()) ThrowAllocatedStringException(64, "toBSON is not a function");
Local<Value> result = Local<Function>::Cast(toBSON)->Call(object, 0, NULL);
if(!result->IsObject()) ThrowAllocatedStringException(64, "toBSON function did not return an object");
return result->ToObject();
}
else
{
return object;
}
}
Handle<Value> BSON::BSONSerialize(const Arguments &args)
{
HandleScope scope;
if(args.Length() == 1 && !args[0]->IsObject()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]");
if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]");
if(args.Length() == 3 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean]");
if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !args[2]->IsBoolean() && !args[3]->IsBoolean()) return VException("One, two or tree arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]");
if(args.Length() > 4) return VException("One, two, tree or four arguments required - [object] or [object, boolean] or [object, boolean, boolean] or [object, boolean, boolean, boolean]");
// Check if we have an array as the object
if(args[0]->IsArray()) return VException("Only javascript objects supported");
// Unpack the BSON parser instance
BSON *bson = ObjectWrap::Unwrap<BSON>(args.This());
// Calculate the total size of the document in binary form to ensure we only allocate memory once
// With serialize function
bool serializeFunctions = (args.Length() >= 4) && args[3]->BooleanValue();
char *serialized_object = NULL;
size_t object_size;
try
{
Local<Object> object = bson->GetSerializeObject(args[0]);
BSONSerializer<CountStream> counter(bson, false, serializeFunctions);
counter.SerializeDocument(object);
object_size = counter.GetSerializeSize();
// Allocate the memory needed for the serialization
serialized_object = (char *)malloc(object_size);
// Check if we have a boolean value
bool checkKeys = args.Length() >= 3 && args[1]->IsBoolean() && args[1]->BooleanValue();
BSONSerializer<DataStream> data(bson, checkKeys, serializeFunctions, serialized_object);
data.SerializeDocument(object);
}
catch(char *err_msg)
{
free(serialized_object);
Handle<Value> error = VException(err_msg);
free(err_msg);
return error;
}
// If we have 3 arguments
if(args.Length() == 3 || args.Length() == 4)
{
Buffer *buffer = Buffer::New(serialized_object, object_size);
free(serialized_object);
return scope.Close(buffer->handle_);
}
else
{
Local<Value> bin_value = Encode(serialized_object, object_size, BINARY)->ToString();
free(serialized_object);
return bin_value;
}
}
Handle<Value> BSON::CalculateObjectSize(const Arguments &args)
{
HandleScope scope;
// Ensure we have a valid object
if(args.Length() == 1 && !args[0]->IsObject()) return VException("One argument required - [object]");
if(args.Length() == 2 && !args[0]->IsObject() && !args[1]->IsBoolean()) return VException("Two arguments required - [object, boolean]");
if(args.Length() > 3) return VException("One or two arguments required - [object] or [object, boolean]");
// Unpack the BSON parser instance
BSON *bson = ObjectWrap::Unwrap<BSON>(args.This());
bool serializeFunctions = (args.Length() >= 2) && args[1]->BooleanValue();
BSONSerializer<CountStream> countSerializer(bson, false, serializeFunctions);
countSerializer.SerializeDocument(args[0]);
// Return the object size
return scope.Close(Uint32::New((uint32_t) countSerializer.GetSerializeSize()));
}
Handle<Value> BSON::SerializeWithBufferAndIndex(const Arguments &args)
{
HandleScope scope;
//BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, ->, buffer, index) {
// Ensure we have the correct values
if(args.Length() > 5) return VException("Four or five parameters required [object, boolean, Buffer, int] or [object, boolean, Buffer, int, boolean]");
if(args.Length() == 4 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32()) return VException("Four parameters required [object, boolean, Buffer, int]");
if(args.Length() == 5 && !args[0]->IsObject() && !args[1]->IsBoolean() && !Buffer::HasInstance(args[2]) && !args[3]->IsUint32() && !args[4]->IsBoolean()) return VException("Four parameters required [object, boolean, Buffer, int, boolean]");
uint32_t index;
size_t object_size;
try
{
BSON *bson = ObjectWrap::Unwrap<BSON>(args.This());
Local<Object> obj = args[2]->ToObject();
char* data = Buffer::Data(obj);
size_t length = Buffer::Length(obj);
index = args[3]->Uint32Value();
bool checkKeys = args.Length() >= 4 && args[1]->IsBoolean() && args[1]->BooleanValue();
bool serializeFunctions = (args.Length() == 5) && args[4]->BooleanValue();
BSONSerializer<DataStream> dataSerializer(bson, checkKeys, serializeFunctions, data+index);
dataSerializer.SerializeDocument(bson->GetSerializeObject(args[0]));
object_size = dataSerializer.GetSerializeSize();
if(object_size + index > length) return VException("Serious error - overflowed buffer!!");
}
catch(char *exception)
{
Handle<Value> error = VException(exception);
free(exception);
return error;
}
return scope.Close(Uint32::New((uint32_t) (index + object_size - 1)));
}
Handle<Value> BSON::BSONDeserializeStream(const Arguments &args)
{
HandleScope scope;
// At least 3 arguments required
if(args.Length() < 5) return VException("Arguments required (Buffer(data), Number(index in data), Number(number of documents to deserialize), Array(results), Number(index in the array), Object(optional))");
// If the number of argumets equals 3
if(args.Length() >= 5)
{
if(!Buffer::HasInstance(args[0])) return VException("First argument must be Buffer instance");
if(!args[1]->IsUint32()) return VException("Second argument must be a positive index number");
if(!args[2]->IsUint32()) return VException("Third argument must be a positive number of documents to deserialize");
if(!args[3]->IsArray()) return VException("Fourth argument must be an array the size of documents to deserialize");
if(!args[4]->IsUint32()) return VException("Sixth argument must be a positive index number");
}
// If we have 4 arguments
if(args.Length() == 6 && !args[5]->IsObject()) return VException("Fifth argument must be an object with options");
// Define pointer to data
Local<Object> obj = args[0]->ToObject();
uint32_t numberOfDocuments = args[2]->Uint32Value();
uint32_t index = args[1]->Uint32Value();
uint32_t resultIndex = args[4]->Uint32Value();
bool promoteLongs = true;
// Check for the value promoteLongs in the options object
if(args.Length() == 6) {
Local<Object> options = args[5]->ToObject();
// Check if we have the promoteLong variable
if(options->Has(String::New("promoteLongs"))) {
promoteLongs = options->Get(String::New("promoteLongs"))->ToBoolean()->Value();
}
}
// Unpack the BSON parser instance
BSON *bson = ObjectWrap::Unwrap<BSON>(args.This());
// Unpack the buffer variable
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3
Buffer *buffer = ObjectWrap::Unwrap<Buffer>(obj);
char* data = buffer->data();
size_t length = buffer->length();
#else
char* data = Buffer::Data(obj);
size_t length = Buffer::Length(obj);
#endif
// Fetch the documents
Local<Object> documents = args[3]->ToObject();
BSONDeserializer deserializer(bson, data+index, length-index);
for(uint32_t i = 0; i < numberOfDocuments; i++)
{
try
{
documents->Set(i + resultIndex, deserializer.DeserializeDocument(promoteLongs));
}
catch (char* exception)
{
Handle<Value> error = VException(exception);
free(exception);
return error;
}
}
// Return new index of parsing
return scope.Close(Uint32::New((uint32_t) (index + deserializer.GetSerializeSize())));
}
// Exporting function
extern "C" void init(Handle<Object> target)
{
HandleScope scope;
BSON::Initialize(target);
}
NODE_MODULE(bson, BSON::Initialize);
//===========================================================================
#ifndef BSON_H_
#define BSON_H_
//===========================================================================
#ifdef __arm__
#define USE_MISALIGNED_MEMORY_ACCESS 0
#else
#define USE_MISALIGNED_MEMORY_ACCESS 1
#endif
#include <node.h>
#include <node_object_wrap.h>
#include <v8.h>
using namespace v8;
using namespace node;
//===========================================================================
enum BsonType
{
BSON_TYPE_NUMBER = 1,
BSON_TYPE_STRING = 2,
BSON_TYPE_OBJECT = 3,
BSON_TYPE_ARRAY = 4,
BSON_TYPE_BINARY = 5,
BSON_TYPE_UNDEFINED = 6,
BSON_TYPE_OID = 7,
BSON_TYPE_BOOLEAN = 8,
BSON_TYPE_DATE = 9,
BSON_TYPE_NULL = 10,
BSON_TYPE_REGEXP = 11,
BSON_TYPE_CODE = 13,
BSON_TYPE_SYMBOL = 14,
BSON_TYPE_CODE_W_SCOPE = 15,
BSON_TYPE_INT = 16,
BSON_TYPE_TIMESTAMP = 17,
BSON_TYPE_LONG = 18,
BSON_TYPE_MAX_KEY = 0x7f,
BSON_TYPE_MIN_KEY = 0xff
};
//===========================================================================
template<typename T> class BSONSerializer;
class BSON : public ObjectWrap {
public:
BSON();
~BSON() {}
static void Initialize(Handle<Object> target);
static Handle<Value> BSONDeserializeStream(const Arguments &args);
// JS based objects
static Handle<Value> BSONSerialize(const Arguments &args);
static Handle<Value> BSONDeserialize(const Arguments &args);
// Calculate size of function
static Handle<Value> CalculateObjectSize(const Arguments &args);
static Handle<Value> SerializeWithBufferAndIndex(const Arguments &args);
// Constructor used for creating new BSON objects from C++
static Persistent<FunctionTemplate> constructor_template;
private:
static Handle<Value> New(const Arguments &args);
static Handle<Value> deserialize(BSON *bson, char *data, uint32_t dataLength, uint32_t startIndex, bool is_array_item);
// BSON type instantiate functions
Persistent<Function> longConstructor;
Persistent<Function> objectIDConstructor;
Persistent<Function> binaryConstructor;
Persistent<Function> codeConstructor;
Persistent<Function> dbrefConstructor;
Persistent<Function> symbolConstructor;
Persistent<Function> doubleConstructor;
Persistent<Function> timestampConstructor;
Persistent<Function> minKeyConstructor;
Persistent<Function> maxKeyConstructor;
// Equality Objects
Persistent<String> longString;
Persistent<String> objectIDString;
Persistent<String> binaryString;
Persistent<String> codeString;
Persistent<String> dbrefString;
Persistent<String> symbolString;
Persistent<String> doubleString;
Persistent<String> timestampString;
Persistent<String> minKeyString;
Persistent<String> maxKeyString;
// Equality speed up comparison objects
Persistent<String> _bsontypeString;
Persistent<String> _longLowString;
Persistent<String> _longHighString;
Persistent<String> _objectIDidString;
Persistent<String> _binaryPositionString;
Persistent<String> _binarySubTypeString;
Persistent<String> _binaryBufferString;
Persistent<String> _doubleValueString;
Persistent<String> _symbolValueString;
Persistent<String> _dbRefRefString;
Persistent<String> _dbRefIdRefString;
Persistent<String> _dbRefDbRefString;
Persistent<String> _dbRefNamespaceString;
Persistent<String> _dbRefDbString;
Persistent<String> _dbRefOidString;
Persistent<String> _codeCodeString;
Persistent<String> _codeScopeString;
Persistent<String> _toBSONString;
Local<Object> GetSerializeObject(const Handle<Value>& object);
template<typename T> friend class BSONSerializer;
friend class BSONDeserializer;
};
//===========================================================================
class CountStream
{
public:
CountStream() : count(0) { }
void WriteByte(int value) { ++count; }
void WriteByte(const Handle<Object>&, const Handle<String>&) { ++count; }
void WriteBool(const Handle<Value>& value) { ++count; }
void WriteInt32(int32_t value) { count += 4; }
void WriteInt32(const Handle<Value>& value) { count += 4; }
void WriteInt32(const Handle<Object>& object, const Handle<String>& key) { count += 4; }
void WriteInt64(int64_t value) { count += 8; }
void WriteInt64(const Handle<Value>& value) { count += 8; }
void WriteDouble(double value) { count += 8; }
void WriteDouble(const Handle<Value>& value) { count += 8; }
void WriteDouble(const Handle<Object>&, const Handle<String>&) { count += 8; }
void WriteUInt32String(uint32_t name) { char buffer[32]; count += sprintf(buffer, "%u", name) + 1; }
void WriteLengthPrefixedString(const Local<String>& value) { count += value->Utf8Length()+5; }
void WriteObjectId(const Handle<Object>& object, const Handle<String>& key) { count += 12; }
void WriteString(const Local<String>& value) { count += value->Utf8Length() + 1; } // This returns the number of bytes exclusive of the NULL terminator
void WriteData(const char* data, size_t length) { count += length; }
void* BeginWriteType() { ++count; return NULL; }
void CommitType(void*, BsonType) { }
void* BeginWriteSize() { count += 4; return NULL; }
void CommitSize(void*) { }
size_t GetSerializeSize() const { return count; }
// Do nothing. CheckKey is implemented for DataStream
void CheckKey(const Local<String>&) { }
private:
size_t count;
};
class DataStream
{
public:
DataStream(char* aDestinationBuffer) : destinationBuffer(aDestinationBuffer), p(aDestinationBuffer) { }
void WriteByte(int value) { *p++ = value; }
void WriteByte(const Handle<Object>& object, const Handle<String>& key) { *p++ = object->Get(key)->Int32Value(); }
#if USE_MISALIGNED_MEMORY_ACCESS
void WriteInt32(int32_t value) { *reinterpret_cast<int32_t*>(p) = value; p += 4; }
void WriteInt64(int64_t value) { *reinterpret_cast<int64_t*>(p) = value; p += 8; }
void WriteDouble(double value) { *reinterpret_cast<double*>(p) = value; p += 8; }
#else
void WriteInt32(int32_t value) { memcpy(p, &value, 4); p += 4; }
void WriteInt64(int64_t value) { memcpy(p, &value, 8); p += 8; }
void WriteDouble(double value) { memcpy(p, &value, 8); p += 8; }
#endif
void WriteBool(const Handle<Value>& value) { WriteByte(value->BooleanValue() ? 1 : 0); }
void WriteInt32(const Handle<Value>& value) { WriteInt32(value->Int32Value()); }
void WriteInt32(const Handle<Object>& object, const Handle<String>& key) { WriteInt32(object->Get(key)); }
void WriteInt64(const Handle<Value>& value) { WriteInt64(value->IntegerValue()); }
void WriteDouble(const Handle<Value>& value) { WriteDouble(value->NumberValue()); }
void WriteDouble(const Handle<Object>& object, const Handle<String>& key) { WriteDouble(object->Get(key)); }
void WriteUInt32String(uint32_t name) { p += sprintf(p, "%u", name) + 1; }
void WriteLengthPrefixedString(const Local<String>& value) { WriteInt32(value->Utf8Length()+1); WriteString(value); }
void WriteObjectId(const Handle<Object>& object, const Handle<String>& key);
void WriteString(const Local<String>& value) { p += value->WriteUtf8(p); } // This returns the number of bytes inclusive of the NULL terminator.
void WriteData(const char* data, size_t length) { memcpy(p, data, length); p += length; }
void* BeginWriteType() { void* returnValue = p; p++; return returnValue; }
void CommitType(void* beginPoint, BsonType value) { *reinterpret_cast<unsigned char*>(beginPoint) = value; }
void* BeginWriteSize() { void* returnValue = p; p += 4; return returnValue; }
#if USE_MISALIGNED_MEMORY_ACCESS
void CommitSize(void* beginPoint) { *reinterpret_cast<int32_t*>(beginPoint) = (int32_t) (p - (char*) beginPoint); }
#else
void CommitSize(void* beginPoint) { int32_t value = (int32_t) (p - (char*) beginPoint); memcpy(beginPoint, &value, 4); }
#endif
size_t GetSerializeSize() const { return p - destinationBuffer; }
void CheckKey(const Local<String>& keyName);
protected:
char *const destinationBuffer; // base, never changes
char* p; // cursor into buffer
};
template<typename T> class BSONSerializer : public T
{
private:
typedef T Inherited;
public:
BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions) : Inherited(), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { }
BSONSerializer(BSON* aBson, bool aCheckKeys, bool aSerializeFunctions, char* parentParam) : Inherited(parentParam), checkKeys(aCheckKeys), serializeFunctions(aSerializeFunctions), bson(aBson) { }
void SerializeDocument(const Handle<Value>& value);
void SerializeArray(const Handle<Value>& value);
void SerializeValue(void* typeLocation, const Handle<Value>& value);
private:
bool checkKeys;
bool serializeFunctions;
BSON* bson;
};
//===========================================================================
class BSONDeserializer
{
public:
BSONDeserializer(BSON* aBson, char* data, size_t length);
BSONDeserializer(BSONDeserializer& parentSerializer, size_t length);
Handle<Value> DeserializeDocument(bool promoteLongs);
bool HasMoreData() const { return p < pEnd; }
Local<String> ReadCString();
uint32_t ReadIntegerString();
int32_t ReadRegexOptions();
Local<String> ReadString();
Local<String> ReadObjectId();
unsigned char ReadByte() { return *reinterpret_cast<unsigned char*>(p++); }
#if USE_MISALIGNED_MEMORY_ACCESS
int32_t ReadInt32() { int32_t returnValue = *reinterpret_cast<int32_t*>(p); p += 4; return returnValue; }
uint32_t ReadUInt32() { uint32_t returnValue = *reinterpret_cast<uint32_t*>(p); p += 4; return returnValue; }
int64_t ReadInt64() { int64_t returnValue = *reinterpret_cast<int64_t*>(p); p += 8; return returnValue; }
double ReadDouble() { double returnValue = *reinterpret_cast<double*>(p); p += 8; return returnValue; }
#else
int32_t ReadInt32() { int32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; }
uint32_t ReadUInt32() { uint32_t returnValue; memcpy(&returnValue, p, 4); p += 4; return returnValue; }
int64_t ReadInt64() { int64_t returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; }
double ReadDouble() { double returnValue; memcpy(&returnValue, p, 8); p += 8; return returnValue; }
#endif
size_t GetSerializeSize() const { return p - pStart; }
private:
Handle<Value> DeserializeArray(bool promoteLongs);
Handle<Value> DeserializeValue(BsonType type, bool promoteLongs);
Handle<Value> DeserializeDocumentInternal(bool promoteLongs);
Handle<Value> DeserializeArrayInternal(bool promoteLongs);
BSON* bson;
char* const pStart;
char* p;
char* const pEnd;
};
//===========================================================================
#endif // BSON_H_
//===========================================================================
var bson = null;
// Load the precompiled win32 binary
if(process.platform == "win32" && process.arch == "x64") {
bson = require('./win32/x64/bson');
} else if(process.platform == "win32" && process.arch == "ia32") {
bson = require('./win32/ia32/bson');
} else {
bson = require('../build/Release/bson');
}
exports.BSON = bson.BSON;
exports.Long = require('../lib/bson/long').Long;
exports.ObjectID = require('../lib/bson/objectid').ObjectID;
exports.DBRef = require('../lib/bson/db_ref').DBRef;
exports.Code = require('../lib/bson/code').Code;
exports.Timestamp = require('../lib/bson/timestamp').Timestamp;
exports.Binary = require('../lib/bson/binary').Binary;
exports.Double = require('../lib/bson/double').Double;
exports.MaxKey = require('../lib/bson/max_key').MaxKey;
exports.MinKey = require('../lib/bson/min_key').MinKey;
exports.Symbol = require('../lib/bson/symbol').Symbol;
// Just add constants tot he Native BSON parser
exports.BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0;
exports.BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1;
exports.BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2;
exports.BSON.BSON_BINARY_SUBTYPE_UUID = 3;
exports.BSON.BSON_BINARY_SUBTYPE_MD5 = 4;
exports.BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128;
NODE = node
name = all
JOBS = 1
all:
rm -rf build .lock-wscript bson.node
node-waf configure build
cp -R ./build/Release/bson.node . || true
all_debug:
rm -rf build .lock-wscript bson.node
node-waf --debug configure build
cp -R ./build/Release/bson.node . || true
clang:
rm -rf build .lock-wscript bson.node
CXX=clang node-waf configure build
cp -R ./build/Release/bson.node . || true
clang_debug:
rm -rf build .lock-wscript bson.node
CXX=clang node-waf --debug configure build
cp -R ./build/Release/bson.node . || true
clean:
rm -rf build .lock-wscript bson.node
.PHONY: all
NODE = node
NPM = npm
NODEUNIT = node_modules/nodeunit/bin/nodeunit
all: clean node_gyp
test: clean node_gyp
npm test
node_gyp: clean
node-gyp configure build
clean:
node-gyp clean
browserify:
node_modules/.bin/onejs build browser_build/package.json browser_build/bson.js
.PHONY: all

Javascript + C++ BSON parser

This BSON parser is primarily meant for usage with the mongodb node.js driver. However thanks to such wonderful tools at onejs we are able to package up a BSON parser that will work in the browser aswell. The current build is located in the browser_build/bson.js file.

A simple example on how to use it

<head>
  <script src="https://raw.github.com/mongodb/js-bson/master/browser_build/bson.js">
  </script>
</head>
<body onload="start();">
<script>
  function start() {
    var BSON = bson().BSON;
    var Long = bson().Long;

    var doc = {long: Long.fromNumber(100)}

    // Serialize a document
    var data = BSON.serialize(doc, false, true, false);
    // De serialize it again
    var doc_2 = BSON.deserialize(data);
  }
</script>
</body>

It's got two simple methods to use in your application.

  • BSON.serialize(object, checkKeys, asBuffer, serializeFunctions)

    • @param {Object} object the Javascript object to serialize.
    • @param {Boolean} checkKeys the serializer will check if keys are valid.
    • @param {Boolean} asBuffer return the serialized object as a Buffer object (ignore).
    • @param {Boolean} serializeFunctions serialize the javascript functions (default:false)
    • @return {TypedArray/Array} returns a TypedArray or Array depending on what your browser supports
  • BSON.deserialize(buffer, options, isArray)

    • Options
      • evalFunctions {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized.
      • cacheFunctions {Boolean, default:false}, cache evaluated functions for reuse.
      • cacheFunctionsCrc32 {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function.
    • @param {TypedArray/Array} a TypedArray/Array containing the BSON data
    • @param {Object} [options] additional options used for the deserialization.
    • @param {Boolean} [isArray] ignore used for recursive parsing.
    • @return {Object} returns the deserialized Javascript Object.

Up to date documentation

Documentation

Install

To install the most recent release from npm, run:

npm install mongodb

That may give you a warning telling you that bugs['web'] should be bugs['url'], it would be safe to ignore it (this has been fixed in the development version)

To install the latest from the repository, run::

npm install path/to/node-mongodb-native

Community

Check out the google group node-mongodb-native for questions/answers from users of the driver.

Live Examples

Introduction

This is a node.js driver for MongoDB. It's a port (or close to a port) of the library for ruby at http://github.com/mongodb/mongo-ruby-driver/.

A simple example of inserting a document.

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;

    var collection = db.collection('test_insert');
    collection.insert({a:2}, function(err, docs) {
      
      collection.count(function(err, count) {
        console.log(format("count = %s", count));
      });

      // Locate all the entries using find
      collection.find().toArray(function(err, results) {
        console.dir(results);
        // Let's close the db
        db.close();
      });      
    });
  })

Data types

To store and retrieve the non-JSON MongoDb primitives (ObjectID, Long, Binary, Timestamp, DBRef, Code).

In particular, every document has a unique _id which can be almost any type, and by default a 12-byte ObjectID is created. ObjectIDs can be represented as 24-digit hexadecimal strings, but you must convert the string back into an ObjectID before you can use it in the database. For example:

  // Get the objectID type
  var ObjectID = require('mongodb').ObjectID;

  var idString = '4e4e1638c85e808431000003';
  collection.findOne({_id: new ObjectID(idString)}, console.log)  // ok
  collection.findOne({_id: idString}, console.log)  // wrong! callback gets undefined

Here are the constructors the non-Javascript BSON primitive types:

  // Fetch the library
  var mongo = require('mongodb');
  // Create new instances of BSON types
  new mongo.Long(numberString)
  new mongo.ObjectID(hexString)
  new mongo.Timestamp()  // the actual unique number is generated on insert.
  new mongo.DBRef(collectionName, id, dbName)
  new mongo.Binary(buffer)  // takes a string or Buffer
  new mongo.Code(code, [context])
  new mongo.Symbol(string)
  new mongo.MinKey()
  new mongo.MaxKey()
  new mongo.Double(number)	// Force double storage

The C/C++ bson parser/serializer

If you are running a version of this library has the C/C++ parser compiled, to enable the driver to use the C/C++ bson parser pass it the option native_parser:true like below

  // using native_parser:
  MongoClient.connect('mongodb://127.0.0.1:27017/test'
    , {db: {native_parser: true}}, function(err, db) {})

The C++ parser uses the js objects both for serialization and deserialization.

GitHub information

The source code is available at http://github.com/mongodb/node-mongodb-native. You can either clone the repository or download a tarball of the latest release.

Once you have the source you can test the driver by running

$ make test

in the main directory. You will need to have a mongo instance running on localhost for the integration tests to pass.

Examples

For examples look in the examples/ directory. You can execute the examples using node.

$ cd examples
$ node queries.js

GridStore

The GridStore class allows for storage of binary files in mongoDB using the mongoDB defined files and chunks collection definition.

For more information have a look at Gridstore

Replicasets

For more information about how to connect to a replicaset have a look at the extensive documentation Documentation

Primary Key Factories

Defining your own primary key factory allows you to generate your own series of id's (this could f.ex be to use something like ISBN numbers). The generated the id needs to be a 12 byte long "string".

Simple example below

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  // Custom factory (need to provide a 12 byte array);
  CustomPKFactory = function() {}
  CustomPKFactory.prototype = new Object();
  CustomPKFactory.createPk = function() {
    return new ObjectID("aaaaaaaaaaaa");
  }

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;

    db.dropDatabase(function(err, done) {
      
      db.createCollection('test_custom_key', function(err, collection) {
        
        collection.insert({'a':1}, function(err, docs) {
          
          collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}).toArray(function(err, items) {
            console.dir(items);
            // Let's close the db
            db.close();
          });
        });
      });
    });
  });

Documentation

If this document doesn't answer your questions, see the source of Collection or Cursor, or the documentation at MongoDB for query and update formats.

Find

The find method is actually a factory method to create Cursor objects. A Cursor lazily uses the connection the first time you call nextObject, each, or toArray.

The basic operation on a cursor is the nextObject method that fetches the next matching document from the database. The convenience methods each and toArray call nextObject until the cursor is exhausted.

Signatures:

  var cursor = collection.find(query, [fields], options);
  cursor.sort(fields).limit(n).skip(m).

  cursor.nextObject(function(err, doc) {});
  cursor.each(function(err, doc) {});
  cursor.toArray(function(err, docs) {});

  cursor.rewind()  // reset the cursor to its initial state.

Useful chainable methods of cursor. These can optionally be options of find instead of method calls:

  • .limit(n).skip(m) to control paging.
  • .sort(fields) Order by the given fields. There are several equivalent syntaxes:
  • .sort({field1: -1, field2: 1}) descending by field1, then ascending by field2.
  • .sort([['field1', 'desc'], ['field2', 'asc']]) same as above
  • .sort([['field1', 'desc'], 'field2']) same as above
  • .sort('field1') ascending by field1

Other options of find:

  • fields the fields to fetch (to avoid transferring the entire document)
  • tailable if true, makes the cursor tailable.
  • batchSize The number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with batchSize(n, function(err){}) after performing the initial query to the database.
  • hint See Optimization: hint.
  • explain turns this into an explain query. You can also call explain() on any cursor to fetch the explanation.
  • snapshot prevents documents that are updated while the query is active from being returned multiple times. See more details about query snapshots.
  • timeout if false, asks MongoDb not to time out this cursor after an inactivity period.

For information on how to create queries, see the MongoDB section on querying.

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;

    var collection = db
      .collection('test')
      .find({})
      .limit(10)
      .toArray(function(err, docs) {
        console.dir(docs);
    });
  });

Insert

Signature:

  collection.insert(docs, options, [callback]);

where docs can be a single document or an array of documents.

Useful options:

  • safe:true Should always set if you have a callback.

See also: MongoDB docs for insert.

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;
    
    db.collection('test').insert({hello: 'world'}, {w:1}, function(err, objects) {
      if (err) console.warn(err.message);
      if (err && err.message.indexOf('E11000 ') !== -1) {
        // this _id was already inserted in the database
      }
    });
  });

Note that there's no reason to pass a callback to the insert or update commands unless you use the safe:true option. If you don't specify safe:true, then your callback will be called immediately.

Update; update and insert (upsert)

The update operation will update the first document that matches your query (or all documents that match if you use multi:true). If safe:true, upsert is not set, and no documents match, your callback will return 0 documents updated.

See the MongoDB docs for the modifier ($inc, $set, $push, etc.) formats.

Signature:

  collection.update(criteria, objNew, options, [callback]);

Useful options:

  • safe:true Should always set if you have a callback.
  • multi:true If set, all matching documents are updated, not just the first.
  • upsert:true Atomically inserts the document if no documents matched.

Example for update:

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;

    db.collection('test').update({hi: 'here'}, {$set: {hi: 'there'}}, {w:1}, function(err) {
      if (err) console.warn(err.message);
      else console.log('successfully updated');
    });
  });

Find and modify

findAndModify is like update, but it also gives the updated document to your callback. But there are a few key differences between findAndModify and update:

  1. The signatures differ.
  2. You can only findAndModify a single item, not multiple items.

Signature:

    collection.findAndModify(query, sort, update, options, callback)

The sort parameter is used to specify which object to operate on, if more than one document matches. It takes the same format as the cursor sort (see Connection.find above).

See the MongoDB docs for findAndModify for more details.

Useful options:

  • remove:true set to a true to remove the object before returning
  • new:true set to true if you want to return the modified object rather than the original. Ignored for remove.
  • upsert:true Atomically inserts the document if no documents matched.

Example for findAndModify:

  var MongoClient = require('mongodb').MongoClient
    , format = require('util').format;    

  MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
    if(err) throw err;
    db.collection('test').findAndModify({hello: 'world'}, [['_id','asc']], {$set: {hi: 'there'}}, {}, function(err, object) {
      if (err) console.warn(err.message);
      else console.dir(object);  // undefined if no matching object exists.
    });
  });

Save

The save method is a shorthand for upsert if the document contains an _id, or an insert if there is no _id.

Sponsors

Just as Felix Geisendörfer I'm also working on the driver for my own startup and this driver is a big project that also benefits other companies who are using MongoDB.

If your company could benefit from a even better-engineered node.js mongodb driver I would appreciate any type of sponsorship you may be able to provide. All the sponsors will get a lifetime display in this readme, priority support and help on problems and votes on the roadmap decisions for the driver. If you are interested contact me on christkv AT g m a i l.com for details.

And I'm very thankful for code contributions. If you are interested in working on features please contact me so we can discuss API design and testing.

Release Notes

See HISTORY

Credits

  1. 10gen
  2. Google Closure Library
  3. Jonas Raoni Soares Silva

Contributors

Aaron Heckmann, Christoph Pojer, Pau Ramon Revilla, Nathan White, Emmerman, Seth LaForge, Boris Filipov, Stefan Schärmeli, Tedde Lundgren, renctan, Sergey Ukustov, Ciaran Jessup, kuno, srimonti, Erik Abele, Pratik Daga, Slobodan Utvic, Kristina Chodorow, Yonathan Randolph, Brian Noguchi, Sam Epstein, James Harrison Fisher, Vladimir Dronnikov, Ben Hockey, Henrik Johansson, Simon Weare, Alex Gorbatchev, Shimon Doodkin, Kyle Mueller, Eran Hammer-Lahav, Marcin Ciszak, François de Metz, Vinay Pulim, nstielau, Adam Wiggins, entrinzikyl, Jeremy Selier, Ian Millington, Public Keating, andrewjstone, Christopher Stott, Corey Jewett, brettkiefer, Rob Holland, Senmiao Liu, heroic, gitfy

License

Copyright 2009 - 2012 Christian Amor Kvalheim.

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.

This file has been truncated, but you can view the full file.
MZ����@��� �!�L�!This program cannot be run in DOS mode.
$������ڣ��ڣ��ڣ��q���ڣ��D���ڣ��p���ڣ�I���ڣ��F���ڣ��ۣ]�ڣ��u���ڣ��A���ڣ��G���ڣRich��ڣPEL���Q�! 
�c0K�@Ѐ-�h<���l�2�_@0T.text� `.rdata�i0j"@@.data0��@�.rsrc���@@.reloc���@B3��A��A�����̋��L$���Q�P�I�H����������^���������̋���^�������̋���^�����̊T$V����t5�N�W�~��Ix��$��I��^y���t W��N����_^���^��t V��N����^������� _���������̋�� _�������̅�t��j�����̋��_�������̊T$V����t5�N�W�~��Ix��$��I� _y���t W�TN����_^�� _��t V�9N����^�����̋��L$������̋�3��$_�H�H���������������̋T$���L$�$_�H�P�������̊T$V����t5�F�W�~� @H� �x�d$�� H� _y���t W�M����_^�� _��t V�M����^�������4_�A _��̋D$����̍A ������������̋A������������̋A������������̊D$�A ������̊A �������������S�\$V����t:�N�W�~��I��x� _��$I�4_�P y���t W��L����_^[��4_�F _��t V�L����^[�������̋D$VP���02����^���������̋�������������̋D$��������̋D$�A������̋A ������������̋D$�A ������̋A������������̋D$�A������̋��L$���Q�P�Q�P�I �H ��V��L$��41��^�����������̋ �%81�������̋��L$�<_�Q�P�����������̋��L$�Q�P���D$V��t9S�^�W�;�4�OxU�-42��������Oy�]�D$t S�SK��_��[^��42�D$t V�5K����^���D_�����������������������̋��D_�������̋��D_�����̊T$V����t5�N�W�~��Ix��$��I�D_y���t W�J����_^��D_��t V�J����^�������L_�����������������������̋��L_�������̋��L_�����̊T$V����t5�N�W�~��Ix��$��I�L_y���t W�J����_^��L_��t V��I����^�����̋��L$���Q�P�Q�P�Q �P �I�H�����������̋T$���L$��P��������������̋��L$���I�H�������������̋��L$������j�X1��������j�82���������T_���������̸�����������3��������������̸��������̋��T_�������̋��T_�����̊T$V����t5�N�W�~��Ix��$��I�T_y���t W�H����_^��T_��t V�H����^�������l_���������̋��l_�������̋��l_�����̋������������̊T$V����t5�N�W�~��Ix��$��I�l_y���t W�H����_^��l_��t V��G����^�����̋D$Vj���P�L$Q��1�T$��� jRP���T1^��̋�Ȁ���t2�ËP��z�u�@ �����
�������������̋�Ȁ���t2�ËP��z�u�@ ������������������̋�@�����������̋ �D$�����̋ �D$������̋ �D$�����̋A�������������V���t_� 1�D$t V��F����^������������Q�Q�̉�L$ � �T$R�l1��2�N��FY��̋��1�Nj@��F�F�jj�P��1F��������̋V�jjj�R��1F����������̃�$���3ĉD$ �D$(SWjj j�L$Q�T$R�T$H�Q�̉��L$ Q���l1��2���13���V�\D�N�~�TD�N�~�TD�N�~�TD�N�TD�~�N�TD�~�N���� r��L$(_[3��<��$����QVP�w=�T$�L$Q��RV�=��h�e�D$P�t$ �q^���������������U��Q���3ʼnE� VW��1���ti�G��� ��jjj�V��1jV�=� >��;�tVh8U�G@�i����>$uVh\U�G@�V���j.V�~=����tVh|U�G@�9����e�_^�M�3��>;��]������̉�T$�H�H�L��H �9th�U�@�����������̋N���@�F��u�+�HPQW��1�� ������������������3��N�A�N��sw���t�$�`���ރ��ك���Ð^TYO2�������̋B�W���<IQPV�z��1�� ��_Ã����3ĉD$�J3�W�9A�Jf�|D�9A�Jf�|D�9A�Jf�|D�9A�Jf�|D
�9A�Jf�|D �9f�|DA���J�� r�j �D$PV��1�L$(�� _3̋��q9������������U����A�����VW�A��sh�U�@�&����1�D$ �D$�D�P��t$�T$�A;Q vh�U�@������:th�U�@������E �}P�ύt$ ���_^��]������̃� ���3ĉD$SUW�D$ ��P�|$��1�N��;N sp�-d1��1��$�F�@��F�ȋ��@�F��u�+�HPQ�D$P�ӋL$<�� QW�T$ R���5�D$jPQ�L$ �ĉ�L$�ՋV;V r��|$�F;F th(V�@� ��������L$ P�p1���������=l1Q�̉�L$�D$ P�׋���Q�̍T$$��L$R�׋���Q�̉�L$(Q�L$�׋�J�D$Pj�T$R�<1��D$�_][�L$3��7�� ��T$ �L$(���_][3��n7�� ����̋H�����V�H��shlV�@�-����0�L$ �L$�L�Q��t$�T$�H;P vh�U�@������:th�U�@������D$PW�t$�
��^����QSU�l$W�D$jP��1�N��;N sg�`1�F�@�F3��8��t(��$���0|W��9R��@�<��|yЋȉF�9u�UR�T$@R�ΉF�M�D$ �L$PW�ӋN;N r��V;V th�V�@�2���h�U�@�#����L$�D$_]�[Y���U������S�ًM �A�VW=�����%�$��$��$����������E�_^[��]� �C�Q��$���R�C�L1��E���_^[��]� �C��T$X�����$R�C��1��E�� �_^[��]� ��$�R�,2��E���_^[��]� �T$`R��1��E���_^[��]� �C��=L1���C�0Q��$���R�C�׋�L$pVQ�D$L�׋����T$@�H(�T$<Rj��$�P�<1��E�_^[��]� �C�@�C��t�T$pR�1��E���_^[��]� ��$�P�$2��E���_^[��]� �|$D���{��������L$DPQ��$�R�(1��E�� �_^[��]� ��$����1����0��$�R��1����D$@��T$<R�t$@�Hj�D$XP�<1��E�_^[��]� �C��$���������U�0R�D$HP���y����L$TQ�L$H�t$@�2���T$@�H�T$<Rj�D$dP�<1��E�_^[��]� �t$d���������T$D�H�T$DRj�D$tP�<1��E�_^[��]� �C�0���C���@�C��u�0���C�SVR�D2s�@�L$|WQ�D$L�L1�����T$@�H�T$<Rj��$�P�<1��E�_^[��]� �C����C�0���}�Ct�� |[u��t]�=L1Q��$�R�׋��$�VQ�D$L�׋����T$@�H �T$<Rj��$�P�<1��E�_^[��]� ����|����C��T$H�H�L$L�l$H��$������C��T$H�H�L$L�l$H����$����$R�C�(2��E�� �_^[��]� �U�}R��������_^[��]� �E�uPV���|�����_^[��]� �t$D�������L$D��D$<P�L$@�J j��$�R�<1��E�_^[��]� ��J,��$�P�@1��E�_^[��]� ��J0�D$HP�@1��E_^�[��]� Qh�V�@�1����[  $�#�"� g".!�#� z!�!/$". � #�$l$�$
 ̃��L$��1�D$j�P�L$Q��1�P�T$R��1�PV�1���L$��1�ƃ��������QSUW���$1�t_3ۉ^ �^�^�^�^�^ �^$�^(�^,�^0�^4�^8�^<�^@�^D�^H�^L�^P�^T�^X�^\�=�1�^`�^d�^h�^l�^p�^t�^x�^|������������������j�����D$h�V���P����׋�- 2�� ;�u3��P�Ճ�j��L$hWQ�F\�׋�� ;�u3��P�Ճ�j��T$hWR�F`�׋�� ;�u3��P�Ճ�j��Fd�D$hWP�׋�� ;�u3��P�Ճ�j��L$hWQ�Fh�׋�� ;�u3��P�Ճ�j��T$h WR�Fl�׋�� ;�u3��P�Ճ�j��Fp�D$h,WP�׋�� ;�u3��P�Ճ�j��L$h4WQ�Ft�׋�� ;�u3��P�Ճ�j��T$h4WR�Fx�׋�� ;�u3��P�Ճ�j��F|�D$h<WP�׋�� ;�u3��P�Ճ�j��L$hDWQ����׋�� ;�u3��P�Ճ�j��T$hHWR����׋�� ;�u3��P�Ճ�j�����D$hLWP�׋�� ;�u3��P�Ճ�j��L$hXWQ����׋�� ;�u3��P�Ճ�j��T$h\WR����׋�� ;�u3��P�Ճ�j�����D$h`WP�׋�� ;�u3��P�Ճ�j��L$hhWQ����׋�� ;�u3��P�Ճ�j��T$hpWR����׋�� ;�u3��P�Ճ�j�����D$hxWP�׋�� ;�u3��P�Ճ�j��L$h�WQ�F4�׋�� ;�u3��P�Ճ�j��T$h�WR�F8�׋�� ;�u3��P�Ճ�j��F<�D$h�WP�׋�� ;�u3��P�Ճ�j��L$h�WQ�F@�׋�� ;�u3��P�Ճ�j��T$h�WR�FD�׋�� ;�u3��P�Ճ�j��FH�D$h�WP�׋�� ;�u3��P�Ճ�j��L$h�WQ�FL�׋�� ;�u3��P�Ճ�j��T$h�WR�FP�׋�� ;�u3��P�Ճ�j��FT�D$h�WP�׋�� ;�u
_]�^X��[Y�P�Ճ�_]�FX��[Y�����������̃�,SUVW�L$,��1�=�13�P3�Q�D$h�-P�׋L$ Q�T$4R��1����L$ Q�ȣ����1�j�P1�5�1j��T$$h�WR�֋� ���� P��1���3�P3�Q�D$h0;P�׋l$$j��L$4h�WQ�֋��jQ�ĉ(�D$$�ˋ�1P�T$$�ӋL$ ��-T1Q���Ջ��3�P3�Q�T$$h06�D$$P�׋L$(j��T$8h�WR�L$0�֋��jQ�L$�D$(�čT$0��L$$R�ӋL$(�Q���Ջ��3�P3�Q�D$(h0=P�T$(�׋L$0j��T$<h�WR�L$8�֋��jQ�L$$�D$�čT$,��L$ R�ӋL$�Q���Ջ��3�P3�Q�D$(h�1P�T$(�׋L$0j��T$<hXR�L$8�֋���D$jQ�L$$�čT$,��L$ R�ӋL$�Q���Ջ��3�P3�Q�D$(h�AP�T$,�׋|$0j��L$<h XQ�֋��jQ�L$$�ĉ8�D$,P�T$$�ӋL$ �Q���Ջ ���T$(R��1�8j��D$(h�WP�֋�� jQ�̉9Q�̉�L$L�d1�L$,��1_^][��,�U�����TSVW�=�1�L$@�׋u �F���e��1���D$P�Ӌ����N��1���<�~�L$Q�Ӌ���\$� �V�T$�ڋ���1����h���1����t ���������3�3��ˉD$ �D$��1���_�2�D$P�L$$Q�L$�h1�0�T$$R���H1��L$Q���2�G4Q�̉�L$�ӄ�t*��u3��L$ �G ��V� 2���L$ �G ���G8Q�̉�L$�ӄ�t*��u3��L$ �G�V� 2���L$ �G��G<Q�̉�L$�ӄ�t*��u3��L$ �G�eV� 2���L$ �G�N�G@Q�̉�L$�ӄ�t*��u3��L$ �G�)V� 2���L$ �G��GDQ�̉�L$�ӄ�tV�t$,�s����L$ �W���GHQ�̉�L$�ӄ�tV�t$0�E����L$ �G ��GLQ�̉�L$�ӄ�tV�t$4�����L$ @�O$��GPQ�̉�L$�ӄ�tV�t$8������L$ ��W(�Z�GTQ�̉�L$�ӄ�tV�t$<�����L$ �G,�,�GXQ�̉�L$�ӄ�tV�t$@�����L$ �O0�t$�L$F�t$��1;�������|$ �t!��t
��j���ЍL$P��1j�h8X�`�u �F��P���1�F�u���L$@���1��_^[��]Ëuh�X��������L$@��1��_^[��]ÍL$P��j�h�X�L$DQ��1��� P�T$<R��1��uPV�1�=�1���L$P�׍L$@��_��^[��]�U��j�h�,d�P��@SVW���3�P�E�d��e��M���1�] �C�=�1��~�� �E P�׃����C��Ȁ���u�@��x�����uQ�{ �U R�׃����CP�@2����u-h�X�u��������M���1�ƋM�d� Y_^[��]��E �C������ �E�P�׃����K����1�����{ �M�Q�׃����K���U�R�2j�hY�E�P�5�1�փ� �P�M��p1��tCj�hY�M�Q�փ� ��U�RQ�̉�E�P�M��l1���1��\1�E ��5�1�{ �M�Q�׃����K�U�R�2�C��P����}�ċM��@2�����Q�ċU��1�؋ċM�� 1����sW�M���1j�h(Y�U R�֋P�E�P��1�P�uV�1���M��=�1�׍M��׋ƋM�d� Y_^[��]��E�P�ˋ׍E�������M Q�uV�M������M���1�ƋM�d� Y_^[��]Ë}�W�u �����W��"���U�E ��E������V4ÍM���1�E�M�d� Y_^[��]Ã{�M�Q��1�����CjP�L2����������W��"�����u�{�M�Q��1�����[jSWV�<2���E�W�΋U�E�������U R�E P�M�����V�/"���u�M ��M���1�ƋM�d� Y_^[��]Ë}�W�u �����W��!�U�R��!�� �E�M ��E������V4���������������̃� VW�=2�T$��R�׋���L$P�p1��t{���Q�ĉ0�L$ �D$P�l1�L$ ��1��uhLY�@�����L$jjQ�L$�T$R�D1�L$��1��uhhY�@������L$S�׋�_^�� ËD$_���^�� �������������U��j�hP,d�P��TSVW���3�P�E�d��e��M��5�1�֋} �G��1��uu�� �E P�Ӄ����O��1��uW�M���j�h�Y�M Q��1�P�U�R��1�P�uV�1���M��=�1�׍M��׋ƋM�d� Y_^[��]ËG��up�� �E P�Ӄ����O��1��uR� �M Q�Ӄ����O���5�1�ք�u3h�Y�u��������M���1�ƋM�d� Y_^[��]Ë5�1�G��u`�� �U R�Ӄ����O��1��uB� �E P�Ӄ����O���ք�u#� �M Q�Ӄ����O���ք��e����G������ �U R�Ӄ����O��1����� �E P�Ӄ����O���ք�uk� �M Q�Ӄ����O���ք�uL� �U R�Ӄ����O�� �ք�u-hZ�u�������M���1�ƋM�d� Y_^[��]ËG��~&�M���1j�h�Z�E P��1�P�M�Q������� �U R�Ӄ����O��1��t-h [�u�-������M���1�ƋM�d� Y_^[��]ËG��P�A �����uԋG��|&�� �E P�Ӄ����O�� �2���Eu�E3��E�E�9G �M�Q�Ӄ����G�E�E�]��������E��E��U�UʼnuȋE�E��E��u��5 �E��؉]�P�������u�G��|[���M�Q��1�����G���E܋���1��t0��U�R��1�����G���E؋��2��t��2��u��u��E��E�E��MԉM��U�U �E P�E��S �E��������tO��tJjSV�M Q�H2���U�R��2�8V����u�>�M���1�ƋM�d� Y_^[��]�SV�D2��V��� �W�M���1�u��M���1�ƋM�d� Y_^[��]ËE�P�O�}�W�u ����W�=�� �M�U ��E������;ÍM���1�E�M�d� Y_^[��]���U�����4SVW�=�1�L$ �׋u �~��1u]�N�ӄ�uT�L$0��j��D$hD[P��1��� P�L$Q��1��uPV�1�=�1���L$0�׍L$ �׋�_^[��]Ã~uT�N�ӄ�uK�~�T$R��1�����N����1��u#�uhh[�������L$ ��1��_^[��]Ã~~T�L$0��j��D$h�[P��1��� P�L$Q��1��uPV�1�=�1���L$0�׍L$ �׋�_^[��]ËF��P�� ��F��3ۃ�|+���T$R��1�����N���2��t��2��\$�\$�D$�|$9^�D$P��1����L$ ��V�T$ �D$ �t$�� �D$P�L$Q�L1���P�L$$��1�u�L$ ���1_��^[��]����������U��j�h0,d�P��PSVW���3�P�E�d��e��M��=�1�׋u �F��~W�M���j�h�[�E P��1�P�M�Q��1�P�uV�1���M��=�1�׍M��׋ƋM�d� Y_^[��]Ë�1������ �U R�Ӄ����N��1�����~ �E P�Ӄ����N����1��uw�~ �M Q�Ӄ����F��P�@2����uP�~ �U R�Ӄ����N�� ��1��u-hD\�u�������M���1�ƋM�d� Y_^[��]ËF������ �E P�Ӄ����N��1�����~ �M Q�Ӄ����N���=�1�ׄ����~ �U R�Ӄ����F��P�@2����uo�~ �E P�Ӄ����N�� ��1��uL�~ �M Q�Ӄ����N���ׄ�u-h�\�u�������M���1�ƋM�d� Y_^[��]��E��F��P����E�~ �U�R�Ӄ����F���E�M Q���2Q�ċU ��1�E�ċM �� 1���EЃ~ �U�R�Ӄ����F�� �E����2��}̋F��|S�� �E�P�Ӄ����F���E܋���1��t,�~ �M�Q�Ӄ����F���E؋��2��t��2ۋF��u/���U�R��1�����F���Eԋ��2��t��2ɋE�ljE��E��]��M��M�M��~�U�R��1����E���v�u�E�]؋M�������E�E�P�E���E�+E��E��;E�v-h�\�u��������M���1�ƋM�d� Y_^[��]��E�����HP�M Q�L1���P�M���1�u��M���1�ƋM�d� Y_^[��]Ë}�W�u ����W����U�E ��E�������AÍM���1�E�M�d� Y_^[��]��������������U��j�hp,d�P��LSVW���3�P�E�d��e��M��=�1�׋u �F��}W�M���j�h�\�E P��1�P�M�Q��1�P�uV�1���M��=�1�׍M��׋ƋM�d� Y_^[��]Ë�1�� �U R�Ӄ����FP�@2����uh�]�u�s������g�~ �E P�Ӄ����N���=�1�ׄ�uh�]�u�9������-�~ �M Q�Ӄ����N���ׄ�uh�]�u���������~ �U R�Ӄ����N�� ��1��uh0^�u����������~ �E P�Ӄ����N���ׄ�uhx^�u��������F��u7�� �M Q�Ӄ����N����1��uh�^�u�Z������N�~ �U R�Ӄ����N�E�P�2�~ �M Q�Ӄ����N���=2�׉E��~ �U R�Ӄ����N���׉E�~ �E P�Ӄ����N���׉E��E��F������ �M�Q�Ӄ����N���U R�2j�hY�E�P�=�1�׃� �P�M �p1��t;j�hY�M�Q�׃� ��U�RQ�̉�E�P�M �l1���1��\1�E�F��P�s��ċM��1�؋ċU�� 1���E �~�E�P��1�����N�� �U�R�2�U��}��E��ȉM�+‹U �D��E��8th�U�@����3��`1;u��}�E��}܋E�P�M Q�M������R�E؍ Q�����E�����F�M��‹}�W�u �e���W����U�E ��E�������EÍM���1�E�M�d� Y_^[��]�+M�M�Q�M Q�L1���P�M���1�u��M���1�ƋM�d� Y_^[��]�������̋D$��u����P� 2���������̃|$uj<h�Th�^��� �L$�|1��j=h�Th�T�r�� �L$��P��z�u�@ ������u�P��z�u�@�3��j��1���������������̃��N�SUW�\$�����L$�D$ P�t1�L$ ��13��D$��~q�2�-h1W�L$$Q�L$�Ջ�T$R��Q�L$�čT$��L$R�l1�~u�L$��1��u�L$���1@�D$Pj���G;|$|��_][����������������̃�SUV���F�N �D$���F�D$(W�\$�����L$�D$P�t1�L$��1��3�|$�����U�L$(Q�L$�h1��T$,R�2�~t �\$,�$���Q�L$0�čT$��L$R�l1�~ u�L$��1��u.�~�L$,jj�Gj�P�F��1F�D$PW�����|$E;��q����N�D$ ��F�v+�_�0^][�����̃�SUVW�|$(����1���t+� 2��舠�D$(�D$(������Dze�_^][�����Ѐ���u�@��x�����t
�D$(P���1��uw���1��tW��� _^][������1��t �_^][������1��t@�?�L$(Q���,1���1@���01�t��t��t��_^][������1���t"�T$(R�2���1��_^][�����1������2�D$(P�ӋN�A\�L$(P�p1����L$(�T$R�x1�=2Q�L$�ĉ�V�J4�ׄ�����Q�L$�ĉ�V�JP�ׄ������Q�L$�ĉ�V�J8�ׄ�t � _^][���Q�L$�ĉ�V�J<��Q�����F�@l�=l1�̉�L$ Q�L$0�׋�2�V�L$QQ�̋�Bt�T$$��L$0R�׋�ӋF��@pQ�̉�L$ Q�L$0�׋�2��u��T$Q�ĉ�1��._^][����L$�ĉ�V�JL�ׄ�����Q�L$�ĉ�V�JH�ׄ�t&�F�@|�L$QQ�̍T$ ��L$0R�l1��L���Q�L$�ĉ�V�J@�ׄ����F����=l1�L$QQ�̍T$$��L$0R�׋�-2�ՋF����L$QQ�̍T$$��L$0R�׋�ӋL$�D$P�t1���1��t6��L$Q�L$�Ջ��1�T$���D$�T$�����_^][����L$�D$P������Q�L$�ĉ�V�JD��Q�����F�
����=l1�̉�L$ Q�L$0��Pj���3����V����Q�̉�L$,�D$ P��Pj��� ����N���Q�̍T$��L$,R�׋D$�+�����u��D$Pj��������_^][����L$�ĉ�V�JT�ׄ�uuQ�L$
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment