├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── ant.xml
├── encodings.xml
├── vcs.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── modules.xml
├── compiler.xml
├── misc.xml
└── uiDesigner.xml
├── .npmignore
├── .gitignore
├── .DS_Store
├── .travis.yml
├── mongolian.js
├── Node-mongolian.iml
├── package.json
├── LICENSE
├── lib
├── util.js
├── gridfs.js
├── connection.js
├── db.js
├── cursor.js
├── gridfile.js
├── server.js
└── collection.js
├── test
├── collection3.js
├── distinct.js
├── misc.js
├── collection10000.js
├── async.js
├── collection2.js
├── index.js
├── collection1000.js
├── collection1.js
└── gridfs.js
├── Changelog.md
├── examples
└── mongolian_trainer.js
└── Readme.md
/.idea/.name:
--------------------------------------------------------------------------------
1 | node-mongolian
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea/workspace.xml
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcello3d/node-mongolian/HEAD/.DS_Store
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | services: mongodb
3 | node_js:
4 | - 0.6
5 | - 0.8
--------------------------------------------------------------------------------
/mongolian.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | module.exports = require('./lib/server')
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/ant.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Node-mongolian.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mongolian",
3 | "description": "Mongolian DeadBeef is an awesome Mongo DB node.js driver",
4 | "version": "0.1.18",
5 | "homepage": "https://github.com/marcello3d/node-mongolian",
6 | "repository": "git://github.com/marcello3d/node-mongolian.git",
7 | "author": "Marcello Bastéa-Forte (http://marcello.cellosoft.com/)",
8 | "main": "mongolian.js",
9 | "keywords": ["mongo", "mongodb", "database", "db", "nosql"],
10 | "dependencies": {
11 | "buffalo": "0.1.3",
12 | "waiter": "0.1.1",
13 | "taxman": "0.1.1"
14 | },
15 | "devDependencies": {
16 | "nodeunit": "0.6.4"
17 | },
18 | "scripts": {
19 | "test": "node_modules/.bin/nodeunit test"
20 | },
21 | "engines": {
22 | "node": ">=0.4.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Marcello Bastéa-Forte (marcello@cellosoft.com)
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 |
16 | 2. Altered source versions must be plainly marked as such, and must not be
17 | misrepresented as being the original software.
18 |
19 | 3. This notice may not be removed or altered from any source
20 | distribution.
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var util = require('util')
3 |
4 | //////////////////////////////////////////////////////////////////////////////////
5 | // Internal
6 |
7 | /**
8 | * Convenience method for handling async value callbacks
9 | *
10 | * @param callback the target async callback
11 | * @param body the body to call
12 | * @returns an async function(err,value)
13 | */
14 | exports.safetyNet = function(callback,body) {
15 | return function(error, result) {
16 | if (error) {
17 | callback && callback(error)
18 | } else {
19 | body(result)
20 | }
21 | }
22 | }
23 |
24 | exports.extend = function(destination, source) {
25 | if (source) for (var key in source) destination[key] = source[key]
26 | return destination
27 | }
28 |
29 | exports.callback = function(argumentsObject, required, offset) {
30 | var callback = argumentsObject[argumentsObject.length - (offset || 1)]
31 | if (typeof callback === 'function') return callback
32 | if (required) throw new Error(required + ' callback is required')
33 | return undefined
34 | }
--------------------------------------------------------------------------------
/test/collection3.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | db.dropDatabase(function(error) {
11 | test.ifError(error)
12 | test.done()
13 | })
14 | },
15 |
16 | "check isCapped": function(test) {
17 | db.eval(
18 | function() {
19 | return db.createCollection("test3", { capped:true, size:10 })
20 | },
21 | function(error, result) {
22 | test.ifError(error)
23 | collection = db.collection("test3")
24 | collection.isCapped(function(error, capped, size) {
25 | test.equal(capped, true)
26 | test.equal(size, 10)
27 | test.done()
28 | })
29 | }
30 | )
31 | },
32 |
33 | "close connection": function(test) {
34 | db.server.close()
35 | test.done()
36 | }
37 | }
--------------------------------------------------------------------------------
/test/distinct.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('test_distinct')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 |
18 | "insert some rows": function(test) {
19 | var array = [{
20 | i: 0,
21 | color: 'yellow'
22 | },{
23 | i:1,
24 | color: 'white'
25 | },{
26 | i:2,
27 | color: 'red'
28 | },{
29 | i:3,
30 | color: 'red'
31 | },{
32 | i:4,
33 | color: 'green'
34 | }]
35 |
36 | collection.insert(array, function(error, insertedRows) {
37 | test.ifError(error)
38 | test.equal(insertedRows.length, 5)
39 | test.done()
40 | })
41 | },
42 | "test for distinct colors": function(test) {
43 | collection.distinct("color", function(error, values) {
44 | test.deepEqual(values, ['yellow', 'white', 'red', 'green'])
45 | test.done()
46 | })
47 | },
48 | "test for distinct colors with condition i<4": function(test) {
49 | collection.distinct("color", {i: { $lt: 4 }}, function(error, values) {
50 | test.deepEqual(values, ['yellow', 'white', 'red'])
51 | test.done()
52 | })
53 | },
54 |
55 | "close connection": function(test) {
56 | db.server.close()
57 | test.done()
58 | }
59 | }
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | /bin/sh#/bin/bash
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/misc.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | db.dropDatabase(function(error) {
11 | test.ifError(error)
12 | test.done()
13 | })
14 | },
15 |
16 | "db is not null": function(test) {
17 | test.ok(db)
18 | test.done()
19 | },
20 | "its name is right": function(test) {
21 | test.equal(db.name, "mongolian_test")
22 | test.done()
23 | },
24 | "its collectionNames": function(test) {
25 | db.collectionNames(function(error, names) {
26 | test.ifError(error)
27 | test.equal(names.length, 0)
28 | test.done()
29 | })
30 | },
31 | "eval": function(test) {
32 | db.eval(function() {
33 | return 5
34 | }, function(error, result) {
35 | test.ifError(error)
36 | test.equal(result,5)
37 | test.done()
38 | })
39 | },
40 | "eval with parameter": function(test) {
41 | db.eval(
42 | function(x) {
43 | return x
44 | },
45 | 5,
46 | function(error, result) {
47 | test.ifError(error)
48 | test.equal(result,5)
49 | test.done()
50 | }
51 | )
52 | },
53 | "eval with two parameters": function(test) {
54 | db.eval(
55 | function(x, y) {
56 | return x + y
57 | },
58 | 5, 6,
59 | function(error, result) {
60 | test.ifError(error)
61 | test.equal(result,11)
62 | test.done()
63 | }
64 | )
65 | },
66 |
67 | "close connection": function(test) {
68 | db.server.close()
69 | test.done()
70 | }
71 | }
--------------------------------------------------------------------------------
/test/collection10000.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('test_10000')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 |
18 | "insert 10000 documents": function(test) {
19 | var array = []
20 | for (var i=0; i<10000; i++) {
21 | array.push({
22 | i:i,
23 | even:(i%2) == 0
24 | })
25 | }
26 | collection.insert(array, function(error,insertedRows) {
27 | test.ifError(error)
28 | test.equal(insertedRows.length, 10000)
29 | test.done()
30 | })
31 | },
32 | "count": function(test) {
33 | collection.find().count(function(error, count) {
34 | test.ifError(error)
35 | test.equal(count, 10000)
36 | test.done()
37 | })
38 | },
39 | "size": function(test) {
40 | collection.find().size(function(error, count) {
41 | test.ifError(error)
42 | test.equal(count, 10000)
43 | test.done()
44 | })
45 | },
46 | "find().sort({i:-1}).limit(10000)": function(test) {
47 | collection.find().sort({i:-1}).limit(10000).toArray(function(error, array) {
48 | test.ifError(error)
49 | test.equal(array.length, 10000)
50 | test.done()
51 | })
52 | },
53 | "forEach counter": function(test) {
54 | var counter = 0
55 | collection.find().forEach(function(item) {
56 | counter++
57 | }, function(error) {
58 | test.ifError(error)
59 | test.equal(counter, 10000)
60 | test.done()
61 | })
62 | },
63 |
64 | "close connection": function(test) {
65 | db.server.close()
66 | test.done()
67 | }
68 | }
--------------------------------------------------------------------------------
/lib/gridfs.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var buffalo = require('buffalo')
3 |
4 | var safetyNet = require('./util').safetyNet
5 | var MongolianGridFile = require('./gridfile')
6 |
7 | var MongolianGridFS = module.exports = function(db, name) {
8 | this.server = db.server
9 | this.db = db
10 | this.name = name || "fs"
11 | var files = this.files = db.collection(name+".files")
12 | var chunks = this.chunks = db.collection(name+".chunks")
13 | files.count(safetyNet(null, function(count) {
14 | if (count < 1000) files.ensureIndex({ filename:1, uploadDate:1 })
15 | }))
16 | chunks.count(safetyNet(null, function(count) {
17 | if (count < 1000) chunks.ensureIndex({ files_id:1, n:1 }, { unique: true })
18 | }))
19 | }
20 |
21 | /**
22 | * Creates a new MongolianGridFile
23 | */
24 | MongolianGridFS.prototype.create = function(descriptor) {
25 | if (typeof descriptor === 'string') {
26 | descriptor = { filename:descriptor }
27 | }
28 | return new MongolianGridFile(this, descriptor)
29 | }
30 |
31 | /**
32 | * Returns a MongolianCursor mapped to MongolianGridFile of all grid files matching the searchBy query.
33 | *
34 | * find() - all results
35 | * find(object) - mongodb query on the fs.files collection
36 | * find(string/regexp) - shorthand for find({ filename: string })
37 | * find(objectId) - shorthand to find({ _id:objectId })
38 | */
39 | MongolianGridFS.prototype.find = function(searchBy) {
40 | var query
41 | if (typeof searchBy === 'string' || searchBy instanceof RegExp) {
42 | query = { filename:searchBy }
43 | } else if (searchBy instanceof buffalo.ObjectId) {
44 | query = { _id:searchBy }
45 | } else {
46 | query = searchBy
47 | }
48 | var self = this
49 | return self.files.find(query).map(function (document) {
50 | return document && new MongolianGridFile(self, document)
51 | })
52 | }
53 | /**
54 | * Returns the first result that matches the searchBy query (see find(searchBy))
55 | *
56 | * Shorthand for find(searchBy).limit(1).next(callback)
57 | */
58 | MongolianGridFS.prototype.findOne = function(searchBy, callback) {
59 | this.find(searchBy).limit(1).next(callback)
60 | }
61 |
62 | MongolianGridFS.prototype.toString = function() {
63 | return this.db+"/"+this.name+"[gridfs]"
64 | }
--------------------------------------------------------------------------------
/test/async.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var Waiter = require('waiter')
6 |
7 | var db,collection
8 |
9 | module.exports = {
10 | "create connection": function(test) {
11 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
12 | collection = db.collection('test_async')
13 | db.dropDatabase(function(error) {
14 | test.ifError(error)
15 | test.done()
16 | })
17 | },
18 |
19 | "insert 1000 documents": function(test) {
20 | var array = []
21 | var data = ''
22 | for (var i=0; i<1000; i++) {
23 | array.push({
24 | i:i,
25 | even:(i%2) == 0,
26 | data:data+='1234567890'
27 | })
28 | }
29 | collection.insert(array, function(error,insertedRows) {
30 | test.ifError(error)
31 | test.equal(insertedRows.length, 1000)
32 | test.done()
33 | })
34 | },
35 | "async 10": function(test) {
36 | var waiter = new Waiter
37 | for (var i=0; i<10; i++) {
38 | collection.find({i:{$lt:i}}).toArray(waiter())
39 | }
40 | waiter.waitForAll(function() {
41 | test.done()
42 | })
43 | },
44 | "async 100": function(test) {
45 | var waiter = new Waiter
46 | for (var i=0; i<100; i++) {
47 | collection.find({i:{$lt:i}}).toArray(waiter())
48 | }
49 | waiter.waitForAll(function() {
50 | test.done()
51 | })
52 | },
53 |
54 | "insert 1000 documents and async read": function(test) {
55 | var array = []
56 | var idMap = {}
57 | var data = ''
58 | for (var i=0; i<1000; i++) {
59 | var id = new Mongolian.ObjectId
60 | array.push({
61 | _id:id,
62 | i:i,
63 | name:'async',
64 | data:data+='X'
65 | })
66 | idMap[id.toString()] = true
67 | }
68 | collection.insert(array, function(error,insertedRows) {
69 | test.ifError(error)
70 | test.equal(insertedRows.length, 1000)
71 | collection.find({name:'async'}).toArray(function(error, array) {
72 | test.ifError(error)
73 | test.equal(array.length, 1000)
74 | var validIds = 0
75 | array.forEach(function(item) {
76 | var idString = item._id.toString()
77 | // Check that this is an id we inserted
78 | if (idMap[idString]) validIds++
79 | // Remove it from our map so we can catch duplicates
80 | delete idMap[idString]
81 | })
82 | test.equal(validIds, 1000)
83 | test.done()
84 | })
85 | })
86 | },
87 |
88 | "close connection": function(test) {
89 | db.server.close()
90 | test.done()
91 | }
92 | }
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | Mongolian DeadBeef Changelog
2 | ----------------------------
3 |
4 | v0.1.18
5 |
6 | + GH-90 gridfs now allows multiple files with the same name
7 | + GH-96 cursor.forEach ignores cursor.map()
8 |
9 | v0.1.17
10 |
11 | + major bugfix release
12 | + GH-77,GH-86 fixed authentication regression from 0.1.15
13 | + GH-82 unit tests
14 | + GH-83 avoid buffer reuse bug with updated buffalo
15 | + GH-84 pass number of updated/deleted rows to update() and remove() methods
16 | + GH-87 catch Query Failure and Cursor Not Found errors and return them as errors
17 |
18 | v0.1.16
19 |
20 | + GH-72 - fixes Buffer is not large enough exception
21 |
22 | v0.1.15
23 |
24 | + removed mongodb-native dependency, now using node-buffalo (https://github.com/marcello3d/node-buffalo)
25 | + GH-69 - exceptions in user callbacks should now bubble up correctly
26 | + GH-59 - fixes ensureIndex throws error about key name
27 |
28 | v0.1.14
29 |
30 | + GH-28 - tweak for mongos support from yosefd (I haven't personally tested a sharded setup yet)
31 | + GH-61 - getLastError -> getlasterror compatibility fix
32 | + GH-63 GH-64 GH-65 - update to latest mongodb-native socket handling code (thanks to xcoderzach)
33 | + peg mongodb@0.9.7-2-2 to avoid untested version conflicts
34 |
35 | v0.1.13
36 |
37 | + GH-46 - Rewrote cursor.forEach to use nextBatch instead of next, fixes stack overflow
38 | + GH-60 - upgrade to new mongodb@0.9.7 socket connection code
39 | + new test framework (using nodeunit instead of vowsjs)
40 | + new index tests
41 | + tweaks to ensureIndex/createIndex
42 | + other code style changes
43 |
44 | v0.1.12
45 |
46 | + GH-53 - regression if save has no callback
47 |
48 | v0.1.11
49 |
50 | + GH-24 - Remove hack for mongodb-native bug
51 | + GH-31 - Save callback gets no data
52 | + GH-34 - updated taxman dependency
53 | + GH-37 - mapReduce wasn't converting optional finalize function to bson.Code instance
54 | + GH-41 - Add collection.distinct (Filirom1)
55 | + GH-51 - support for mongodb://-style url (mschuetz)
56 | + GH-52 - migrated from x instanceof Function to typeof x === 'function'
57 | + logging/error tweaks
58 | + documentation tweaks
59 | + removed broken/unsupported server.closeWhenDone method
60 | + error in url parsing error message (the irony!)
61 |
62 | v0.1.10
63 |
64 | + added collection.runCommand
65 | + renamed db.queryCommand -> db.runCommand, matching mongo shell syntax
66 | + gridfs tweaks
67 | + fixed collection._indexCache creation (bug caught by dvv) and improved ensureIndex return value consistency
68 | + GH-21 - collection.drop() and db.dropDatabase() not checking if callback is valid
69 | + GH-22 - EventEmitter error
70 | + GH-15/GH-29 - more robust error catching!
71 | + GH-24 - temporary workaround for (mongodb 0.9.4-4 ~ 0.9.6.1) incompatibility
72 | + more tests
73 |
74 | v0.1.9
75 |
76 | + added replicaset support (automatically finds primary and detects secondaries)
77 | + removed keepAlive functionality (GH-19)
78 | + added collection.findAndModify
79 | + added db.eval (GH-12)
80 | + renamed db.drop to db.dropDatabase
81 | + renamed cursor.mapper to cursor.map
82 | + fixed BSON type exports (GH-18)
83 | + various documentation/error message tweaks
84 | + couple more tests
--------------------------------------------------------------------------------
/lib/connection.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var tls = require('tls')
4 | var net = require('net')
5 | var events = require('events')
6 | var util = require('util')
7 |
8 | function Connection(options) {
9 | var initialBufferSize = options && options.initialBufferSize || 1024*10
10 | var maxMessageSize = options && options.maxMessageSize || 1024*1024*32
11 |
12 | var thiz = this
13 |
14 | var socket = new net.Socket
15 |
16 | // Dynamicly sized buffer
17 | var buffer = new Buffer(initialBufferSize)
18 | var readIndex = 0
19 | var writeIndex = 0
20 |
21 | function grow(minAmount) {
22 | var buffer2 = new Buffer(buffer.length + Math.max(minAmount, buffer.length))
23 | buffer.copy(buffer2, 0, readIndex)
24 | writeIndex -= readIndex
25 | readIndex = 0
26 | buffer = buffer2
27 | }
28 |
29 | function append(data) {
30 | var bytesNeeded = (writeIndex + data.length) - buffer.length
31 | if (bytesNeeded > 0) grow(bytesNeeded)
32 | data.copy(buffer, writeIndex)
33 | writeIndex += data.length
34 | }
35 | var expectedMessageSize = 4
36 | var readMessageLength = false
37 |
38 | // Configure socket
39 | socket.setNoDelay(false)
40 |
41 | // SSL support
42 | var readWriteStream = socket
43 | if (options && options.ssl) {
44 | var pair = tls.createSecurePair(options.tlsCredentials)
45 | pair.encrypted.pipe(socket)
46 | socket.pipe(pair.encrypted)
47 |
48 | readWriteStream = pair.cleartext
49 | }
50 |
51 | // Setup write command
52 | this.write = function(buffer, callback) {
53 | readWriteStream.write(buffer, callback)
54 | }
55 |
56 | // Setup data listener
57 | readWriteStream.on('data', function(data) {
58 | append(data)
59 | while (writeIndex - readIndex >= expectedMessageSize) {
60 | if (readMessageLength) {
61 | thiz.emit('message', buffer.slice(readIndex, readIndex += expectedMessageSize))
62 | if (readIndex == writeIndex) {
63 | readIndex = writeIndex = 0
64 | }
65 | expectedMessageSize = 4
66 | readMessageLength = false
67 | } else {
68 | expectedMessageSize = (buffer[readIndex]) |
69 | (buffer[readIndex+1] << 8) |
70 | (buffer[readIndex+2] << 16) |
71 | (buffer[readIndex+3] << 24)
72 | readMessageLength = true
73 | if (expectedMessageSize > maxMessageSize) {
74 | thiz.emit('error', 'message too large: ' + expectedMessageSize + ' (max=' + maxMessageSize + ')')
75 | thiz.close()
76 | return
77 | }
78 | }
79 | }
80 | })
81 | socket.on('connect', function() { thiz.emit('connect') })
82 | socket.on('error', function(message) { thiz.emit('error', message) })
83 | socket.on('close', function() { thiz.emit('close') })
84 | this.connect = function(port,host) { socket.connect(port,host) }
85 | this.close = function() { socket.end() }
86 | this.destroy = function() { socket.destroy() }
87 | }
88 | util.inherits(Connection, events.EventEmitter)
89 |
90 | module.exports = Connection
91 |
--------------------------------------------------------------------------------
/test/collection2.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('test2')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 | "insert multiple documents": function(test) {
18 | collection.insert([
19 | { i:1, j:true },
20 | { i:2, j:true },
21 | { i:3, j:false },
22 | { i:4, j:false },
23 | { i:5, j:true }
24 | ], function(error, insertedRows) {
25 | test.ifError(error)
26 | test.equal(insertedRows.length, 5)
27 | collection.findOne(function(error, foundRow) {
28 | test.ifError(error)
29 | test.ok(foundRow.i > 0)
30 |
31 | collection.find().toArray(function(error, array) {
32 | test.ifError(error)
33 | test.equal(array.length, 5)
34 | test.equal(array[0].i, 1)
35 | test.equal(array[1].i, 2)
36 | test.equal(array[2].i, 3)
37 | test.equal(array[3].i, 4)
38 | test.equal(array[4].i, 5)
39 | test.done()
40 | })
41 | })
42 | })
43 | },
44 | "sorted find": function(test) {
45 | collection.find().sort({i:1}).toArray(function(error, array) {
46 | test.ifError(error)
47 | test.equal(array.length, 5)
48 | test.equal(array[0].i, 1)
49 | test.equal(array[1].i, 2)
50 | test.equal(array[2].i, 3)
51 | test.equal(array[3].i, 4)
52 | test.equal(array[4].i, 5)
53 | test.done()
54 | })
55 | },
56 | "reverse sorted find": function(test) {
57 | collection.find().sort({i:-1}).toArray(function(error, array) {
58 | test.ifError(error)
59 | test.equal(array.length, 5)
60 | test.equal(array[0].i, 5)
61 | test.equal(array[1].i, 4)
62 | test.equal(array[2].i, 3)
63 | test.equal(array[3].i, 2)
64 | test.equal(array[4].i, 1)
65 | test.done()
66 | })
67 | },
68 | "find().limit(3).sort({i:1})": function(test) {
69 | collection.find().limit(3).sort({i:1}).toArray(function(error, array) {
70 | test.ifError(error)
71 | test.equal(array.length, 3)
72 | test.equal(array[0].i, 1)
73 | test.equal(array[1].i, 2)
74 | test.equal(array[2].i, 3)
75 | test.done()
76 | })
77 | },
78 | "find().skip(1).limit(3).sort({i:1})": function(test) {
79 | collection.find().skip(1).limit(3).sort({i:1}).toArray(function(error, array) {
80 | test.ifError(error)
81 | test.equal(array.length, 3)
82 | test.equal(array[0].i, 2)
83 | test.equal(array[1].i, 3)
84 | test.equal(array[2].i, 4)
85 | test.done()
86 | })
87 | },
88 | "query failure": function(test) {
89 | collection.find([undefined]).toArray(function(error, array) {
90 | test.ok(error)
91 | test.equal(array, undefined)
92 | test.equal(error.message, "Query failure: can't have undefined in a query expression")
93 | test.done()
94 | })
95 | },
96 |
97 | "close connection": function(test) {
98 | db.server.close()
99 | test.done()
100 | }
101 | }
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('testindex')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 | "createIndex": function(test) {
18 | collection.createIndex({foo: 1}, function(error,index) {
19 | test.ifError(error)
20 | test.deepEqual(index.key,{foo: 1})
21 | test.done()
22 | })
23 | },
24 | "createIndex with options": function(test) {
25 | collection.createIndex({foo2: 1}, {background: 1}, function(error,index) {
26 | test.ifError(error)
27 | test.deepEqual(index.key,{foo2: 1})
28 | test.done()
29 | })
30 | },
31 |
32 | "createIndex with dot type": function(test) {
33 | collection.createIndex({"foo3.bar": 1}, function(error,index) {
34 | test.ifError(error)
35 | test.deepEqual(index.key,{"foo3.bar": 1})
36 | test.done()
37 | })
38 | },
39 | "createIndex with dot type and options": function(test) {
40 | collection.createIndex({"foo4.bar": 1}, {background: 1}, function(error,index) {
41 | test.ifError(error)
42 | test.deepEqual(index.key,{"foo4.bar": 1})
43 | test.done()
44 | })
45 | },
46 | "ensureIndex": function(test) {
47 | collection.ensureIndex({foo5: 1}, function(error,index) {
48 | test.ifError(error)
49 | test.deepEqual(index.key,{foo5: 1})
50 | test.done()
51 | })
52 | },
53 | "ensureIndex with options": function(test) {
54 | collection.ensureIndex({foo6: 1}, {background: 1}, function(error,index) {
55 | test.ifError(error)
56 | test.deepEqual(index.key,{foo6: 1})
57 | test.done()
58 | })
59 | },
60 |
61 | "ensureIndex with dot type": function(test) {
62 | collection.ensureIndex({"foo7.bar": 1}, function(error,index) {
63 | test.ifError(error)
64 | test.deepEqual(index.key,{"foo7.bar": 1})
65 | test.done()
66 | })
67 | },
68 | "ensureIndex with dot type and options": function(test) {
69 | collection.ensureIndex({"foo8.bar": 1}, {background: 1}, function(error,index) {
70 | test.ifError(error)
71 | test.deepEqual(index.key,{"foo8.bar": 1})
72 | test.done()
73 | })
74 | },
75 | "retry ensureIndex": function(test) {
76 | collection.ensureIndex({foo5: 1}, function(error,index) {
77 | test.ifError(error)
78 | test.deepEqual(index.key,{foo5: 1})
79 | test.done()
80 | })
81 | },
82 | "retry ensureIndex with options": function(test) {
83 | collection.ensureIndex({foo6: 1}, {background: 1}, function(error,index) {
84 | test.ifError(error)
85 | test.deepEqual(index.key,{foo6: 1})
86 | test.done()
87 | })
88 | },
89 |
90 | "retry ensureIndex with dot type": function(test) {
91 | collection.ensureIndex({"foo7.bar": 1}, function(error,index) {
92 | test.ifError(error)
93 | test.deepEqual(index.key,{"foo7.bar": 1})
94 | test.done()
95 | })
96 | },
97 | "retry ensureIndex with dot type and options": function(test) {
98 | collection.ensureIndex({"foo8.bar": 1}, {background: 1}, function(error,index) {
99 | test.ifError(error)
100 | test.deepEqual(index.key,{"foo8.bar": 1})
101 | test.done()
102 | })
103 | },
104 |
105 | "close connection": function(test) {
106 | db.server.close()
107 | test.done()
108 | }
109 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | http://www.w3.org/1999/xhtml
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/test/collection1000.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('test_1000')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 | "insert 1000 documents": function(test) {
18 | var array = []
19 | for (var i=0; i<1000; i++) {
20 | array.push({
21 | i:i,
22 | even:(i%2) == 0
23 | })
24 | }
25 | collection.insert(array, function(error,insertedRows) {
26 | test.ifError(error)
27 | test.equal(insertedRows.length, 1000)
28 | test.done()
29 | })
30 | },
31 | "count": function(test) {
32 | collection.find().count(function(error, count) {
33 | test.ifError(error)
34 | test.equal(count, 1000)
35 | test.done()
36 | })
37 | },
38 | "size": function(test) {
39 | collection.find().size(function(error, count) {
40 | test.ifError(error)
41 | test.equal(count, 1000)
42 | test.done()
43 | })
44 | },
45 | "forEach counter": function(test) {
46 | var counter = 0
47 | collection.find().forEach(function(item) {
48 | counter++
49 | }, function(error) {
50 | test.ifError(error)
51 | test.equal(counter, 1000)
52 | test.done()
53 | })
54 | },
55 | "group check": function (test) {
56 | collection.group({
57 | 'ns': 'test_1000',
58 | 'key':'even',
59 | 'initial': {evenCount:0, oddCount: 0, total:0},
60 | '$reduce': function (doc, out) {
61 | ++out[doc.even ? 'evenCount' : 'oddCount'];
62 | ++out.total;
63 | },
64 | finalize: function (out) {}
65 | }, function (error,group) {
66 | var ret = group.retval[0];
67 | test.ifError(error);
68 | test.equal(ret.total, 1000);
69 | test.equal(ret.evenCount, 500);
70 | test.equal(ret.oddCount,500);
71 | test.done();
72 | });
73 | },
74 | "mapped forEach": function(test) {
75 | var counter = 0
76 | collection.find().map(function(item) {
77 | return item.i
78 | }).forEach(function(item) {
79 | counter += item
80 | }, function(error) {
81 | test.ifError(error)
82 | test.equal(counter, 499500)
83 | test.done()
84 | })
85 | },
86 | "sort({i:-1}).count": function(test) {
87 | collection.find().sort({i:-1}).count(function(error, count) {
88 | test.ifError(error)
89 | test.equal(count, 1000)
90 | test.done()
91 | })
92 | },
93 | "sort({i:-1}).size": function(test) {
94 | collection.find().sort({i:-1}).size(function(error, count) {
95 | test.ifError(error)
96 | test.equal(count, 1000)
97 | test.done()
98 | })
99 | },
100 | "limit(50).count": function(test) {
101 | collection.find().limit(50).count(function(error, count) {
102 | test.ifError(error)
103 | test.equal(count, 1000)
104 | test.done()
105 | })
106 | },
107 | "limit(50).size": function(test) {
108 | collection.find().limit(50).size(function(error, count) {
109 | test.ifError(error)
110 | test.equal(count, 50)
111 | test.done()
112 | })
113 | },
114 | "limit(50).skip(50).count": function(test) {
115 | collection.find().limit(50).skip(50).count(function(error, count) {
116 | test.ifError(error)
117 | test.equal(count, 1000)
118 | test.done()
119 | })
120 | },
121 | "limit(50).skip(50).size": function(test) {
122 | collection.find().limit(50).skip(50).size(function(error, count) {
123 | test.ifError(error)
124 | test.equal(count, 50)
125 | test.done()
126 | })
127 | },
128 | "limit(50).skip(990).count": function(test) {
129 | collection.find().limit(50).skip(990).count(function(error, count) {
130 | test.ifError(error)
131 | test.equal(count, 1000)
132 | test.done()
133 | })
134 | },
135 | "limit(50).skip(990).size": function(test) {
136 | collection.find().limit(50).skip(990).size(function(error, count) {
137 | test.ifError(error)
138 | test.equal(count, 10)
139 | test.done()
140 | })
141 | },
142 | "sort({i:-1}).limit(1000).size": function(test) {
143 | collection.find().sort({i:-1}).limit(1000).toArray(function(error, array) {
144 | test.ifError(error)
145 | test.equal(array.length, 1000)
146 | test.done()
147 | })
148 | },
149 |
150 | "close connection": function(test) {
151 | db.server.close()
152 | test.done()
153 | }
154 | }
--------------------------------------------------------------------------------
/examples/mongolian_trainer.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var fs = require("fs")
4 |
5 | var Mongolian = require('mongolian')
6 |
7 | var server = new Mongolian
8 |
9 | var db = server.db("mongolian_trainer"),
10 | small = db.collection("small"),
11 | medium = db.collection("medium"),
12 | large = db.collection("large")
13 |
14 | //db.dropDatabase(asyncLog("dropped database"))
15 |
16 | small.ensureIndex({foo:1},asyncLog("ensuredIndex!"))
17 | medium.ensureIndex({foo:1})
18 | large.ensureIndex({foo:1})
19 |
20 | // Adds some junk to the database
21 | function fillCollection(collection, max, callback) {
22 | collection.count(function(err,count) {
23 | console.log(collection+" count = "+count)
24 | while (count < max) {
25 | var toInsert = []
26 | while (count < max) {
27 | toInsert.push({ foo: Math.random(), index: count++ })
28 | if (toInsert.length == 300) {
29 | break
30 | }
31 | }
32 | console.log("inserting "+toInsert.length+" document(s) into "+collection)
33 | collection.insert(toInsert)
34 | }
35 | callback()
36 | })
37 | }
38 |
39 | // Creates function(err,value) that prints out the result to console
40 | function asyncLog(prefix) {
41 | return function(err,value) {
42 | if (err) {
43 | console.warn("Error getting "+prefix+': '+err, err.stack)
44 | } else {
45 | console.log(prefix+':',value)
46 | }
47 | }
48 | }
49 |
50 |
51 | fillCollection(small, 50, function() {
52 | small.find().limit(5).toArray(asyncLog('small find limit 5'))
53 | small.find({foo:{$lte:0.5}},{foo:true,_id:false}).limit(49).toArray(asyncLog('small find foo<0.5 limit 49'))
54 | small.find().limit(100).sort({foo:1}).toArray(asyncLog('small find sorted limit 100'))
55 | small.find().batchSize(10).toArray(asyncLog('small find all, batchsize=10'))
56 | })
57 | fillCollection(medium, 500, function() {
58 | medium.findOne(asyncLog('medium findOne'))
59 | })
60 | fillCollection(large, 50000, function() {
61 | large.findOne({foo:{$gt:0.5}}, asyncLog('large foo>0.5 findOne'))
62 |
63 | })
64 |
65 | // Get a list of databases on this server
66 | server.dbNames(asyncLog("db names"))
67 |
68 | // Get a list of collections on this database (should be [ 'system.indexes', 'small', 'medium', 'large' ])
69 | db.collectionNames(asyncLog("collection names"))
70 |
71 | ////// Map reduce
72 | large.mapReduce(
73 | function map() {
74 | emit('count', {
75 | count:1,
76 | sum: this.foo
77 | })
78 | },
79 | function reduce(key, values) {
80 | var count = 0, sum = 0
81 | values.forEach(function(value) {
82 | count += value.count
83 | sum += values.sum
84 | })
85 | return {
86 | count:count,
87 | sum:sum
88 | }
89 | },{
90 | out: 'periscope'
91 | },function (error, result) {
92 | result.find().toArray(asyncLog('map reduce result'))
93 | }
94 | )
95 |
96 |
97 |
98 | ////// Grid FS
99 |
100 | var gridfs = db.gridfs()
101 |
102 | // Create new file write stream
103 | var stream = gridfs.create({
104 | filename:"License",
105 | contentType: "text/plain"
106 | }).writeStream()
107 |
108 | // Pipe license file to gridfile
109 | fs.createReadStream(__dirname+'/../LICENSE').pipe(stream)
110 |
111 | // Read file back from gridfs
112 | stream.on('close', function() {
113 | gridfs.findOne("License", function (err, file) {
114 | if (err) throw err
115 | console.log("opened file:",file)
116 | var read = file.readStream()
117 | read.on('data', function (chunk) {
118 | console.log("chunk["+chunk.length+"] = <"+chunk+">")
119 | })
120 | read.on('end', function() {
121 | console.log("all done")
122 | })
123 | })
124 | })
125 |
126 |
127 | // Create new file write stream
128 | var stream2 = gridfs.create({
129 | filename:"License",
130 | contentType: "text/plain"
131 | }).writeStream()
132 |
133 | // Pipe license file to gridfile
134 | fs.createReadStream(__dirname+'/../LICENSE').pipe(stream2)
135 |
136 | // Read file back from gridfs
137 | stream2.on('close', function() {
138 | gridfs.findOne("License", function (err, file) {
139 | if (err) throw err
140 | console.log("opened file:",file)
141 | var read = file.readStream()
142 | read.on('data', function (chunk) {
143 | console.log("chunk["+chunk.length+"] = <"+chunk+">")
144 | })
145 | read.on('end', function() {
146 | console.log("all done")
147 | })
148 | })
149 | })
150 |
151 | // Generates alphabet buffers
152 | function funBuffer(size) {
153 | var buffer = new Buffer(size)
154 | for (var i=0; i 100 ? chunk.slice(0,100) + "..." : chunk)+">")
172 | })
173 | read.on('end', function() {
174 | console.log("all done")
175 | })
176 | })
177 | })
178 |
179 | stream3.write(new Buffer("hello world 100"))
180 | stream3.write(funBuffer(100))
181 | stream3.write(new Buffer("hello world 10000"))
182 | stream3.write(funBuffer(10000))
183 | stream3.write(new Buffer("hello world 500000"))
184 | stream3.write(funBuffer(500000))
185 | stream3.write(new Buffer("hello world 500000 again"))
186 | stream3.write(funBuffer(500000))
187 | stream3.end()
188 |
--------------------------------------------------------------------------------
/test/collection1.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | module.exports = {
8 | "create connection": function(test) {
9 | db = new Mongolian('mongo://localhost/mongolian_test', { log:false })
10 | collection = db.collection('test')
11 | db.dropDatabase(function(error) {
12 | test.ifError(error)
13 | test.done()
14 | })
15 | },
16 |
17 | "can insert into 'test'": function (test) {
18 | collection.insert({ name:'hello world' }, function(error, insertedRow) {
19 | test.ifError(error)
20 | test.equal(insertedRow.name, "hello world")
21 | test.ok(insertedRow._id)
22 |
23 | collection.findOne(function(error, foundRow) {
24 | test.ifError(error)
25 | test.equal(foundRow._id.toString(), insertedRow._id.toString())
26 | test.equal(foundRow.name, "hello world")
27 |
28 | collection.findOne({
29 | _id:new Mongolian.ObjectId(insertedRow._id.toString())
30 | }, function(error, foundRow) {
31 | test.ifError(error)
32 | test.equal(foundRow._id.toString(), insertedRow._id.toString())
33 | test.equal(foundRow.name, "hello world")
34 |
35 | test.done()
36 | })
37 |
38 | })
39 | })
40 | },
41 | "can insert and find by _id": function (test) {
42 | collection.insert({ name:'hello world' }, function(error, insertedRow) {
43 | test.ifError(error)
44 | collection.findOne({
45 | _id:new Mongolian.ObjectId(insertedRow._id.toString())
46 | }, function(error, foundRow) {
47 | test.ifError(error)
48 | test.equal(foundRow._id.toString(), insertedRow._id.toString())
49 | test.equal(foundRow.name, "hello world")
50 |
51 | test.done()
52 | })
53 | })
54 | },
55 | "can insert and update by _id": function (test) {
56 | collection.insert({ name:'hello sun' }, function(error, insertedRow) {
57 | test.ifError(error)
58 | test.ok(insertedRow)
59 |
60 | collection.update({name:'hello sun'}, {age:45}, function(error, rowsUpdated) {
61 | test.ifError(error)
62 | test.equal(rowsUpdated, 1)
63 |
64 | collection.findOne({
65 | _id:new Mongolian.ObjectId(insertedRow._id.toString())
66 | }, function(error, row) {
67 | test.ifError(error)
68 | test.equal(row.age, 45)
69 | test.equal(row.name, undefined)
70 | test.done()
71 | })
72 | })
73 | })
74 | },
75 | "can insert and remove by _id": function (test) {
76 | collection.insert({ name:'hello moon' }, function(error, insertedRow) {
77 | test.ifError(error)
78 | test.ok(insertedRow)
79 |
80 | collection.remove({name:'hello moon'}, function(error, rowsDeleted) {
81 | test.ifError(error)
82 | test.equal(rowsDeleted, 1)
83 |
84 | collection.findOne({
85 | _id:new Mongolian.ObjectId(insertedRow._id.toString())
86 | }, function(error, row) {
87 | test.ifError(error)
88 | test.equal(row, undefined)
89 | test.done()
90 | })
91 | })
92 | })
93 | },
94 | "can insert and remove multiple by _id": function (test) {
95 | collection.insert([
96 | { name:'hello sky' },
97 | { name:'hello sky' },
98 | { name:'hello sky' },
99 | { name:'hello sky' },
100 | { name:'hello sky' }
101 | ], function(error, insertedRow) {
102 | test.ifError(error)
103 | test.ok(insertedRow)
104 |
105 | collection.remove({name:'hello sky'}, function(error, rowsDeleted) {
106 | test.ifError(error)
107 | test.equal(rowsDeleted, 5)
108 |
109 | collection.find({name:'hello sky'}).count(function(error, count) {
110 | test.ifError(error)
111 | test.equal(count, 0)
112 | test.done()
113 | })
114 | })
115 | })
116 | },
117 | "test updateAll({name:'hello world'}, {age:45}) fails": function(test) {
118 | collection.updateAll({}, {age:45}, function(error) {
119 | test.ok(error)
120 | test.equal(error.message, "Server Error: multi update only works with $ operators")
121 | test.done()
122 | })
123 | },
124 | "saving into 'test'": function(test) {
125 | collection.save({ name:'hello there' }, function(error, insertedRow) {
126 | test.ifError(error)
127 | test.equal(insertedRow.name, "hello there")
128 | test.ok(insertedRow._id)
129 |
130 | insertedRow.name = "awesome sauce"
131 | collection.save(insertedRow, function(error, savedRow) {
132 | test.ifError(error)
133 | test.ok(insertedRow._id)
134 | test.equal(insertedRow.name, "awesome sauce")
135 | test.equal(insertedRow, savedRow)
136 | collection.findOne({_id:insertedRow._id}, function(error, foundRow) {
137 | test.equal(foundRow.name, "awesome sauce")
138 | foundRow.name = "awesome hot sauce"
139 | collection.save(foundRow)
140 | test.equal(foundRow.name, "awesome hot sauce")
141 | test.done()
142 | })
143 | })
144 | })
145 | },
146 | "saving with bad id": function(test) {
147 | var id = new Mongolian.ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
148 | collection.save({ _id:id, name:'hello there' }, function(error, insertedRow) {
149 | test.ifError(error)
150 | test.equal(insertedRow.name, "hello there")
151 | test.equal(insertedRow._id.toString(), id.toString())
152 | collection.findOne({_id:insertedRow._id}, function(error, foundRow) {
153 | test.equal(foundRow.name, "hello there")
154 | test.equal(foundRow._id.toString(), id.toString())
155 | test.done()
156 | })
157 | })
158 | },
159 |
160 | "db.collectionNames contains 'test'": function(test) {
161 | db.collectionNames(function(error,names) {
162 | test.ifError(error)
163 | test.ok(names.some(function(name) {
164 | return name == "test"
165 | }), "'test' found")
166 | test.ok(names.some(function(name) {
167 | return name == "system.indexes"
168 | }), "'system.indexes' found")
169 | test.done()
170 | })
171 | },
172 |
173 | "close connection": function(test) {
174 | db.server.close()
175 | test.done()
176 | }
177 | }
--------------------------------------------------------------------------------
/lib/db.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var crypto = require('crypto')
3 | var util = require('util')
4 |
5 | var safetyNet = require('./util').safetyNet
6 | var MongolianCollection = require('./collection')
7 | var MongolianGridFS = require('./gridfs')
8 |
9 | var MongolianDB = module.exports = function(server, name) {
10 | this.server = server
11 | this.name = name
12 | this._collections = {}
13 | this._gridfss = {}
14 | }
15 |
16 | /**
17 | * Get a collection
18 | */
19 | MongolianDB.prototype.collection = function(name) {
20 | return this._collections[name] ||
21 | (this._collections[name] = new MongolianCollection(this, name))
22 | }
23 |
24 | /**
25 | * Get a gridfs
26 | */
27 | MongolianDB.prototype.gridfs = function(name) {
28 | name = name || "fs"
29 | return this._gridfss[name] ||
30 | (this._gridfss[name] = new MongolianGridFS(this, name))
31 | }
32 |
33 | /**
34 | * Get list of collection names
35 | */
36 | MongolianDB.prototype.collectionNames = function(callback) {
37 | if (typeof callback !== 'function') throw new Error("callback is not a function!")
38 | // Remove "dbname." from front
39 | var prefixLength = this.name.length+1
40 | this.collection("system.namespaces").find().toArray(safetyNet(callback, function(namespaces) {
41 | callback(
42 | null,
43 | // Extract name from namespace object
44 | namespaces.map(function(namespace) {
45 | return namespace.name.substring(prefixLength)
46 | // Filter out internal collections
47 | }).filter(function (name) {
48 | return !/\$/.test(name)
49 | })
50 | )
51 | }))
52 | }
53 |
54 | /**
55 | * Executes a command (equivalent to db.collection("$cmd").findOne(query, callback))
56 | */
57 | MongolianDB.prototype.runCommand = function(query, callback) {
58 | if (typeof query === 'string') {
59 | var n = {}
60 | n[query] = 1
61 | query = n
62 | }
63 | this.collection('$cmd').findOne(query, safetyNet(callback, function(result) {
64 | if (!result.ok || result.err || result.$err) {
65 | var error = new Error("Server Error: " + (result.err || result.$err || util.inspect(result)))
66 | error.result = result
67 | callback && callback(error)
68 | } else {
69 | callback && callback(null, result)
70 | }
71 | }))
72 | }
73 |
74 | /**
75 | * Sends a command to the server with authentication queueing
76 | */
77 | MongolianDB.prototype.sendCommand = function(command, callback) {
78 | if (!this._authorize || this._authorized || (command.query && (command.query.getnonce || command.query.authenticate))) {
79 | this.server.sendCommand(command,callback)
80 | } else if (this._authorizeError) {
81 | callback(this._authorizeError)
82 | } else {
83 | this._authorizeQueue.push([command,callback])
84 | }
85 | }
86 |
87 | function md5hash(string) {
88 | return crypto.createHash('md5').update(string).digest('hex')
89 | }
90 |
91 | /**
92 | * Adds user authentication to this database
93 | */
94 | MongolianDB.prototype.addUser = function(username, password, readOnly, callback) {
95 | if (!callback && typeof readOnly === 'function') {
96 | callback = readOnly
97 | readOnly = false
98 | }
99 | readOnly = readOnly || false
100 | var users = this.collection("system.users")
101 |
102 | users.findOne({ user:username }, safetyNet(callback, function(user) {
103 | user = user || { user:username }
104 | user.readOnly = readOnly
105 | user.pwd = md5hash(username + ":mongo:" + password)
106 | console.log("saving user: ",user)
107 | users.save(user, callback)
108 | }))
109 | }
110 |
111 | /**
112 | * Removes a user (you'll need to be authenticated first)
113 | */
114 | MongolianDB.prototype.removeUser = function(username, callback) {
115 | this.collection("system.users").remove({ user:username }, callback)
116 | }
117 |
118 | /**
119 | * Authenticate the database
120 | */
121 | MongolianDB.prototype.auth = function(username, password, callback) {
122 | var self = this
123 | self._authorize = function(callback) {
124 | if (!self._authorizeQueue) self._authorizeQueue = []
125 | delete self._authorizeError
126 | self.runCommand({ getnonce:1 }, safetyNet(callback, function(nonceResult) {
127 | self.runCommand({
128 | authenticate:1,
129 | user:username,
130 | nonce:nonceResult.nonce,
131 | key:md5hash(nonceResult.nonce + username + md5hash(username + ":mongo:" + password))
132 | }, function (error) {
133 | var queue = self._authorizeQueue
134 | delete self._authorizeQueue
135 | if (error) {
136 | self._authorizeError = error
137 | self.server.log.warn("Authentication failed for `"+username+"` @ "+self+": "+error)
138 | } else {
139 | self._authorized = true
140 | self.server.log.info("Authenticated `"+username+"` @ "+self)
141 | }
142 | if (callback) callback(error)
143 | queue && queue.forEach(function(queued) {
144 | if (error) {
145 | queued[1] && queued[1](error)
146 | } else {
147 | self.server.sendCommand(queued[0], queued[1])
148 | }
149 | })
150 | })
151 | }))
152 | }
153 | self._authorize(callback)
154 | }
155 |
156 | /**
157 | * !!! Removes the entire database !!!
158 | */
159 | MongolianDB.prototype.dropDatabase = function(callback) {
160 | this.runCommand({ dropDatabase:1 }, callback)
161 | }
162 |
163 | MongolianDB.prototype.eval = function(execFunction, args, callback) {
164 | var command = { $eval:execFunction }
165 | if (arguments.length > 1) {
166 | if (typeof arguments[arguments.length-1] === 'function') {
167 | command.args = Array.prototype.slice.call(arguments, 1, arguments.length-1)
168 | callback = arguments[arguments.length-1]
169 | } else {
170 | command.args = Array.prototype.slice.call(arguments, 1)
171 | callback = undefined
172 | }
173 | }
174 | this.runCommand(command, safetyNet(callback,function(result) {
175 | callback(null, result.retval)
176 | }))
177 | }
178 |
179 | /**
180 | * Gets the last error message from the server on this connection
181 | *
182 | * Note: this will return an async style error - i.e. an error in the *first* callback function argument
183 | */
184 | MongolianDB.prototype.lastError = function(replicants, timeout, callback) {
185 | if (!callback && typeof timeout === 'function') {
186 | callback = timeout
187 | timeout = undefined
188 | }
189 | if (!callback && typeof replicants === 'function') {
190 | callback = replicants
191 | replicants = undefined
192 | }
193 | if (typeof callback !== 'function') throw new Error("callback is not a function!")
194 |
195 | var command = { getlasterror:1 }
196 | if (replicants) command.w = replicants
197 | if (timeout) command.wtimeout = timeout
198 |
199 | this.runCommand(command, callback)
200 | }
201 |
202 | MongolianDB.prototype.toString = function() {
203 | return this.server + "/" + this.name
204 | }
--------------------------------------------------------------------------------
/test/gridfs.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 |
3 | var Mongolian = require('../mongolian')
4 |
5 | var db,collection
6 |
7 | function testRandomFile(numberOfBytes, writeChunkSize, chunkSize) {
8 | return function (test) {
9 | // Delete any previous file
10 | gridfs.findOne('random',function(error,oldFile) {
11 | test.ifError(error)
12 | if (oldFile) oldFile.remove()
13 |
14 | // Generate random buffer
15 | var fileBuffer = new Buffer(numberOfBytes)
16 | for (var i=0; i
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/lib/cursor.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var safetyNet = require('./util').safetyNet
3 | var buffalo = require('buffalo')
4 | var mongo = buffalo.mongo
5 |
6 | var MongolianCursor = module.exports = function(collection, criteria, fields) {
7 | this.server = collection.db.server
8 | this.db = collection.db
9 | this.collection = collection
10 | this.criteria = criteria
11 | this.fields = fields
12 | this._retrieved = 0
13 | }
14 | /**
15 | * Resets the cursor to its initial state (this will cause the query to be executed again)
16 | */
17 | MongolianCursor.prototype.reset = function() {
18 | this.close()
19 | this._retrieved = 0
20 | delete this._currentBatch
21 | }
22 |
23 | /**
24 | * Closes the cursor
25 | */
26 | MongolianCursor.prototype.close = function(callback) {
27 | if (this._currentBatch && this._currentBatch.cursor) {
28 | this.db.sendCommand(mongo.serializeKillCursors([this._currentBatch.cursor]), callback)
29 | delete this._currentBatch.cursor
30 | }
31 | }
32 |
33 | /**
34 | * Adds a special value to this query
35 | */
36 | MongolianCursor.prototype.addSpecial = function(name, value) {
37 | if (this._currentBatch) throw new Error("Cannot modify cursor - query already executed")
38 | if (!this._specials) {
39 | this._specials = { $query: this.criteria || {} }
40 | }
41 | this._specials[name] = value
42 | return this
43 | }
44 |
45 | /**
46 | * Specify the number of documents to retrieve per network request
47 | */
48 | MongolianCursor.prototype.batchSize = function(size) {
49 | if (this._currentBatch) throw new Error("Cannot modify cursor - query already executed")
50 | if (size <= 1) throw new Error("Batch size must be > 1")
51 | this._batchSize = size
52 | return this
53 | }
54 | /**
55 | * Limit the total number of documents to get back from the server
56 | */
57 | MongolianCursor.prototype.limit = function(limit) {
58 | if (this._currentBatch) throw new Error("Cannot modify cursor - query already executed")
59 | this._limit = limit
60 | return this
61 | }
62 | /**
63 | * Number of documents to skip before returning values (for paging)
64 | */
65 | MongolianCursor.prototype.skip = function(skip) {
66 | if (this._currentBatch) throw new Error("Cannot modify cursor - query already executed")
67 | this._skip = skip
68 | return this
69 | }
70 | /**
71 | * Set document mapper (convert/modify raw BSON objects to something else)
72 | */
73 | MongolianCursor.prototype.map = function(mapper) {
74 | if (this._currentBatch) throw new Error("Cannot modify cursor - query already executed")
75 | this._mapper = mapper
76 | return this
77 | }
78 | /**
79 | * Specify sort using MongoDB's { "ascendingField": 1, "descendingField": -1 } format
80 | * Shorthand for cursor._addSpecial("orderby", sort)
81 | */
82 | MongolianCursor.prototype.sort = function(sort) {
83 | return this.addSpecial("$orderby", sort)
84 | }
85 |
86 | /**
87 | * Shorthand for cursor._addSpecial("$snapshot", true)
88 | */
89 | MongolianCursor.prototype.snapshot = function() {
90 | return this.addSpecial("$snapshot", true)
91 | }
92 |
93 | /**
94 | * Shorthand for cursor._addSpecial("$explain", true)
95 | */
96 | MongolianCursor.prototype.explain = function() {
97 | return this.addSpecial("$explain", true)
98 | }
99 |
100 | /**
101 | * Returns the next raw BSON result, or null if there are no more. No mapping/transformation is performed.
102 | */
103 | MongolianCursor.prototype.nextBatch = function(callback) {
104 | if (typeof callback !== 'function') throw new Error("callback is not a function!")
105 | var self = this
106 | if (self._currentIndex && self._currentIndex < self._currentBatch.documents.length) throw new Error("nextBatch cannot be mixed with next")
107 | var filterBatch = safetyNet(callback, function(batch) {
108 | if ((batch.flags & 2) != 0) {
109 | var error = new Error("Query failure: "+batch.documents[0].$err)
110 | error.result = batch.documents[0]
111 | return callback(error)
112 | }
113 | if ((batch.flags & 1) != 0) {
114 | return callback(new Error("Cursor not found"))
115 | }
116 | // self.server.log.debug("<<<<<<---",batch.numberReturned,batch.cursor)
117 | self._currentIndex = 0
118 | self._currentBatch = batch
119 | self._retrieved += batch.documents.length
120 | if (batch.cursor && batch.cursor.isZero()) {
121 | delete batch.cursor
122 | } else if (self._limit && self._retrieved >= self._limit) {
123 | self.close()
124 | }
125 | callback(null, batch)
126 | })
127 |
128 |
129 | var retrieveCount = self._batchSize ?
130 | Math.min(self._batchSize, self._limit - self._retrieved) :
131 | self._limit
132 |
133 | if (!self._currentBatch) {
134 | var query = self._specials || self.criteria || {}
135 | var queryCommand = mongo.serializeQuery(
136 | self.collection.fullName,
137 | 0,
138 | self._skip,
139 | retrieveCount,
140 | query,
141 | self.fields
142 | )
143 | queryCommand.query = query
144 | self.db.sendCommand(queryCommand, filterBatch)
145 | } else if (self._currentBatch.cursor) {
146 | var getMoreCommand = mongo.serializeGetMore(
147 | self.collection.fullName,
148 | retrieveCount,
149 | self._currentBatch.cursor
150 | )
151 | self.db.sendCommand(getMoreCommand, filterBatch)
152 | } else {
153 | callback(null, null)
154 | }
155 | }
156 |
157 | /**
158 | * Returns the next available document, or undefined if there is none
159 | */
160 | MongolianCursor.prototype.next = function(callback) {
161 | if (typeof callback !== 'function') throw new Error("callback is not a function!")
162 | var self = this
163 | // We have a retrieved batch that hasn't been exhausted
164 | if (self._currentBatch && self._currentIndex < self._currentBatch.documents.length) {
165 | var document = self._currentBatch.documents[self._currentIndex++]
166 | // self.server.log.debug("<<<<<<---",document)
167 | callback(null, self._mapper ? self._mapper(document) : document)
168 | // We don't have a batch or the cursor hasn't been closed yet
169 | } else if (!self._currentBatch || self._currentBatch.cursor) {
170 | self.nextBatch(safetyNet(callback,function() {
171 | self.next(callback)
172 | }))
173 | // We have nothing left
174 | } else {
175 | callback(null)
176 | }
177 | }
178 |
179 | /**
180 | * Calls callback(doc) on every document in this cursor. On completion or error, finalCallback([err]) is called.
181 | */
182 | MongolianCursor.prototype.forEach = function(callback, finalCallback) {
183 | if (this._retrieved) throw new Error("forEach must be called on an unused cursor or reset cursor")
184 | var self = this
185 | var handleNext = safetyNet(finalCallback,function(batch) {
186 | if (batch) {
187 | var documents = batch.documents
188 | var length = documents.length
189 | for (var i = 0; i < length; i++) {
190 | callback(self._mapper ? self._mapper(documents[i]) : documents[i])
191 | }
192 | self.nextBatch(handleNext)
193 | } else {
194 | finalCallback && finalCallback()
195 | }
196 | })
197 | self.nextBatch(handleNext)
198 | }
199 | /**
200 | * Combines all the documents from this cursor into a single array
201 | */
202 | MongolianCursor.prototype.toArray = function(callback) {
203 | if (this._retrieved) throw new Error("toArray must be called on an unused cursor or reset cursor")
204 | var self = this
205 | var array = []
206 | var handleNext = safetyNet(callback,function(batch) {
207 | if (batch) {
208 | array.push.apply(array, batch.documents)
209 | self.nextBatch(handleNext)
210 | } else {
211 | // self.server.log.debug("<<<<<<---",array)
212 | callback(null, self._mapper ? array.map(self._mapper) : array)
213 | }
214 | })
215 | self.nextBatch(handleNext)
216 | }
217 |
218 | /**
219 | * Returns the number of rows that match this criteria, ignoring skip and limits
220 | */
221 | MongolianCursor.prototype.count = function(callback) {
222 | _count(this, false, callback)
223 | }
224 |
225 | /**
226 | * Returns the minimum of cursor.count(), honoring skip and limit
227 | */
228 | MongolianCursor.prototype.size = function(callback) {
229 | _count(this, true, callback)
230 | }
231 |
232 | //////////////////////////////////////////////////////////////////////////////////
233 | // Internal
234 |
235 | function _count(self, usingSkipAndLimit, callback) {
236 | if (typeof callback !== 'function') throw new Error("callback is not a function!")
237 | var query = { count: self.collection.name }
238 | if (self.criteria) {
239 | query.query = self.criteria
240 | }
241 | if (usingSkipAndLimit) {
242 | query.skip = self._skip || 0
243 | query.limit = self._limit
244 | }
245 | self.db.runCommand(query, safetyNet(callback, function(result) {
246 | callback(null, result.n)
247 | }))
248 | }
249 |
--------------------------------------------------------------------------------
/lib/gridfile.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var stream = require('stream')
3 | var util = require('util')
4 | var crypto = require('crypto')
5 | var Waiter = require('waiter')
6 | var buffalo = require('buffalo')
7 |
8 | var safetyNet = require('./util').safetyNet
9 | var callback = require('./util').callback
10 | var Long = buffalo.Long
11 |
12 | var MongolianGridFile = module.exports = function(gridfs, document) {
13 | this.gridfs = gridfs
14 | this._id = document._id
15 | this.chunkSize = flattenLong(document.chunkSize || 256 * 1024)
16 | this.length = flattenLong(document.length)
17 | this.filename = document.filename
18 | this.aliases = document.aliases
19 | this.md5 = document.md5
20 | this.contentType = document.contentType
21 | if (document.uploadDate) this.uploadDate = document.uploadDate
22 | if (document.metadata) this.metadata = document.metadata
23 | }
24 | /**
25 | * Saves all the metadata associated with this file
26 | */
27 | MongolianGridFile.prototype.save = function(callback) {
28 | if (!this._id) this._id = new buffalo.ObjectId
29 |
30 | var document = {
31 | _id: this._id,
32 | chunkSize: this.chunkSize,
33 | length: this.length,
34 | filename: this.filename,
35 | md5: this.md5,
36 | contentType: this.contentType,
37 | uploadDate: this.uploadDate
38 | }
39 | // Optional fields
40 | if (this.aliases) document.aliases = this.aliases
41 | if (this.metadata) document.metadata = this.metadata
42 | this.gridfs.files.save(document, callback)
43 | }
44 |
45 | /**
46 | * Creates a Writeable Stream for this file
47 | */
48 | MongolianGridFile.prototype.writeStream = function() {
49 | return new MongolianGridFileWriteStream(this)
50 | }
51 | /**
52 | * Retrieves the nth chunk from this file
53 | */
54 | MongolianGridFile.prototype.getChunk = function(chunkIndex, callback) {
55 | if (!this._id) throw new Error("Can only read files in the db")
56 | this.gridfs.chunks.findOne({ files_id:this._id, n:chunkIndex }, { data:true }, callback)
57 | }
58 | /**
59 | * Returns the number of chunks this file *should* have
60 | */
61 | MongolianGridFile.prototype.chunkCount = function() {
62 | var chunkCount = divideCeilLongs(this.length, this.chunkSize)
63 | if (chunkCount instanceof Long) throw new Error("More chunks than we can handle!")
64 | return chunkCount
65 | }
66 |
67 | /**
68 | * Creates a Readable Stream for this file (bind to the 'data' 'error', and 'done' events)
69 | */
70 | MongolianGridFile.prototype.readStream = function() {
71 | return new MongolianGridFileReadStream(this)
72 | }
73 |
74 | /**
75 | * Remove this file from the gridfs
76 | */
77 | MongolianGridFile.prototype.remove = function(callback) {
78 | var waiter = new Waiter
79 | this.gridfs.files.remove({ _id:this._id }, waiter())
80 | this.gridfs.chunks.remove({ files_id:this._id }, waiter())
81 | waiter.waitForAll(callback)
82 | }
83 |
84 | MongolianGridFile.prototype.toString = function() {
85 | return this.gridfs + ":" + this.document
86 | }
87 |
88 | //////////////////////////////////////////////////////////////////////////////////
89 | // Internal
90 |
91 | /** Converts Numbers to Longs */
92 | function toLong(l) {
93 | return l instanceof Long ? l : Long.fromNumber(l)
94 | }
95 | /** Converts a Long to a Number if possible */
96 | function flattenLong(l) {
97 | return l instanceof Long && !l.high_ ? l.low_ : l
98 | }
99 | function addLongs(a,b) {
100 | return flattenLong(toLong(a).add(toLong(b)))
101 |
102 | }
103 | function divideCeilLongs(a,b) {
104 | if (a instanceof Long || b instanceof Long) {
105 | var r = toLong(a).div(toLong(b))
106 | return flattenLong(r.multiply(b).lessThan(a) ? r.add(Long.ONE) : r)
107 | }
108 | return Math.ceil(a / b)
109 | }
110 |
111 |
112 | /**
113 | * Treats a MongolianGridFile as a node.js Writeable Stream
114 | */
115 | function MongolianGridFileWriteStream(file) {
116 | if (file.chunkSize instanceof Long) throw new Error("Long (64bit) chunkSize unsupported")
117 | if (file.chunkSize <= 0) throw new Error("File has invalid chunkSize: "+file.chunkSize)
118 | stream.Stream.call(this)
119 | this.file = file
120 | this.writable = true
121 | this.encoding = 'binary'
122 | this._hash = crypto.createHash('md5')
123 | this._chunkIndex = 0
124 | file.length = 0
125 | this._chunkSize = file.chunkSize
126 | }
127 | util.inherits(MongolianGridFileWriteStream,stream.Stream)
128 |
129 | MongolianGridFileWriteStream.prototype.write = function(data, encoding, callback) {
130 | if (!this.writable) throw new Error('Stream not writable')
131 | var file = this.file
132 | if (this._chunkSize != file.chunkSize) throw new Error("Chunk size changed between writes!")
133 |
134 | if (typeof encoding === 'function') {
135 | callback = encoding
136 | encoding = "utf8"
137 | }
138 | if (!Buffer.isBuffer(data)) {
139 | data = new Buffer(data, encoding)
140 | }
141 |
142 | for (var index = 0; index < data.length; ) {
143 | if (!this._partialChunk) {
144 | this._partialChunk = new Buffer(this._chunkSize)
145 | this._partialIndex = 0
146 | }
147 | var copySize = Math.min(this._partialChunk.length - this._partialIndex, data.length - index)
148 | data.copy(this._partialChunk, this._partialIndex, index, index + copySize)
149 | this._hash.update(data.slice(index, index + copySize))
150 |
151 | this._partialIndex += copySize
152 | index += copySize
153 | file.length = addLongs(file.length, copySize)
154 |
155 | delete file.md5
156 | file.uploadDate = new Date
157 |
158 | if (this._partialIndex === this._partialChunk.length) {
159 | this.flush()
160 | }
161 | }
162 | }
163 | MongolianGridFileWriteStream.prototype.flush = function(callback) {
164 | var waiter = new Waiter
165 | this.file.save(waiter())
166 | if (this._partialIndex) {
167 | this.file.gridfs.chunks.upsert({
168 | files_id:this.file._id,
169 | n:this._chunkIndex
170 | },{
171 | data:this._partialChunk.slice(0, this._partialIndex),
172 | files_id:this.file._id,
173 | n:this._chunkIndex
174 | }, waiter())
175 |
176 | if (this._partialIndex === this._partialChunk.length) {
177 | this._chunkIndex++
178 | delete this._partialIndex
179 | delete this._partialChunk
180 | }
181 | }
182 | waiter.waitForAll(callback)
183 | }
184 | MongolianGridFileWriteStream.prototype.end = function(data, encoding) {
185 | if (!this.writable) throw new Error("Stream is not writable")
186 | if (data) this.write(data,encoding)
187 | this.writable = false
188 | if (this.file.length) this.file.md5 = this._hash.digest('hex')
189 | var self = this
190 | this.flush(function(error) {
191 | if (self.destroyed) return
192 | if (error) {
193 | self.emit('error',error)
194 | self.destroy()
195 | } else {
196 | self.emit('close')
197 | }
198 | })
199 | }
200 | /**
201 | * Destroys the stream, without flushing pending data
202 | */
203 | MongolianGridFileWriteStream.prototype.destroy = function() {
204 | this.writable = false
205 | this.destroyed = true
206 | }
207 |
208 | /**
209 | * Treats a MongolianGridFile as a node.js Readable Stream
210 | */
211 | function MongolianGridFileReadStream(file) {
212 | if (!file._id) throw new Error("Can only read files retrieved from the database")
213 | if (file.chunkSize instanceof Long) throw new Error("Long (64bit) chunkSize unsupported")
214 | if (file.chunkSize <= 0) throw new Error("File has invalid chunkSize: "+file.chunkSize)
215 |
216 | stream.Stream.call(this)
217 |
218 | this.file = file
219 | this.readable = true
220 | this._chunkCount = file.chunkCount()
221 | this._chunkIndex = 0
222 |
223 | process.nextTick(this._nextChunk.bind(this))
224 | }
225 | util.inherits(MongolianGridFileReadStream,stream.Stream)
226 |
227 | MongolianGridFileReadStream.prototype._nextChunk = function() {
228 | var self = this;
229 | if (self._chunkIndex < self._chunkCount) {
230 | var chunkIndex = self._chunkIndex++
231 | self.file.getChunk(chunkIndex, function(error, chunk) {
232 | if (!self.readable) return
233 | if (error || !chunk) {
234 | self.emit('error', error || new Error("Chunk not found: "+chunkIndex+"/"+self._chunkIndex))
235 | self.destroy()
236 | } else {
237 | if (self.paused) {
238 | self._pauseData = chunk.data
239 | } else {
240 | self.emit('data', chunk.data)
241 | self._nextChunk()
242 | }
243 | }
244 | })
245 | } else {
246 | this.readable = false
247 | self.emit('end')
248 | }
249 | }
250 | MongolianGridFileReadStream.prototype.destroy = function() {
251 | this.readable = false
252 | }
253 | MongolianGridFileReadStream.prototype.pause = function() {
254 | if (!this.paused) {
255 | this.paused = true
256 | this.emit('pause')
257 | }
258 | }
259 | MongolianGridFileReadStream.prototype.resume = function() {
260 | if (this.paused) {
261 | this.paused = false
262 | this.emit('resume')
263 | if (this._pauseData) {
264 | this.emit('data', this._pauseData)
265 | delete this._pauseData
266 | this._nextChunk()
267 | }
268 | }
269 | }
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var Waiter = require('waiter')
3 | var taxman = require('taxman')
4 | var EventEmitter = require('events').EventEmitter
5 | var buffalo = require('buffalo')
6 | var mongo = buffalo.mongo
7 |
8 | var safetyNet = require('./util').safetyNet
9 | var extend = require('./util').extend
10 | var MongolianDB = require('./db')
11 | var Connection = require('./connection')
12 |
13 | // [mongo://][username:password@]hostname[:port][/databasename]
14 |
15 | var UrlMatcher = /^(?:mongo(?:db)?:\/\/)?(?:([^:]+):([^@]+)@)?(.+?)(?::([0-9]+))?(?:\/(.*))?$/
16 |
17 | function parseUrl(url) {
18 | var match = UrlMatcher.exec(url)
19 | if (!match || !match[3]) throw new Error("Invalid connection string: " + url)
20 | url = {}
21 | if (match[1]) url.user = decodeURIComponent(match[1])
22 | if (match[2]) url.password = decodeURIComponent(match[2])
23 | url.host = match[3] || 'localhost'
24 | url.port = (+match[4]) || 27017
25 | if (match[5]) url.database = decodeURIComponent(match[5])
26 | return url
27 | }
28 |
29 | /**
30 | * Constructs a new MongolianServer object
31 | */
32 | var Mongolian = module.exports = function(serversOrOptions) {
33 | var inlineDatabase
34 |
35 | this._replicaSet = new EventEmitter
36 | this._servers = []
37 | this._serverNames = {}
38 | this._dbs = {}
39 | this._callbacks = {}
40 | this._callbackCount = 0
41 |
42 | var self = this
43 | var waitingServers = 0
44 | var scanErrors = []
45 |
46 | function scanServer(server) {
47 | waitingServers++
48 | // Swap out the send command method temporarily
49 | self.sendCommand = function(command,callback) {
50 | delete self.sendCommand
51 | server.sendCommand(command,callback)
52 | }
53 | self.db('admin').runCommand({ ismaster:1 }, function(error, result) {
54 | if (error) {
55 | scanErrors.push(error)
56 | } else {
57 | server._type = result.ismaster ? 'primary' :
58 | result.secondary ? 'secondary' :
59 | result.arbiterOnly ? 'arbiter' : 'unknown'
60 |
61 | self.log.debug(server+": Initialized as "+server._type)
62 | result.primary && addServer(parseUrl(result.primary))
63 | result.hosts && result.hosts.map(parseUrl).forEach(addServer)
64 |
65 | if (result.ismaster) {
66 | self._primary = server
67 | self.log.info(server+": Connected to primary")
68 | self._replicaSet.emit('primary',server)
69 | } else {
70 | server.close()
71 | }
72 | }
73 | waitingServers--
74 | if (!waitingServers) {
75 | self.log.debug("Finished scanning... primary? " + (self._primary || 'no'))
76 | self._replicaSet.emit('scanned', scanErrors)
77 | scanErrors = []
78 | }
79 | })
80 | }
81 |
82 | this._replicaSet.on('lost',function(server) {
83 | if (server != self._primary) return
84 | self.log.warn(server+": Lost primary")
85 | delete self._primary
86 | })
87 | this._replicaSet.setMaxListeners(0)
88 |
89 | function addServer(url) {
90 | var key = url.host+':'+url.port
91 | if (!self._serverNames[key]) {
92 | self._serverNames[key] = true
93 | var server = new MongolianServer(self, url)
94 | self._servers.push(server)
95 | scanServer(server)
96 | }
97 | }
98 |
99 | // Browse the constructor arguments
100 | for (var i=0; i>> "+require('util').inspect(command,undefined,5,true).slice(0,5000)+'\n'+new Error().stack)
255 | connection.write(command)
256 | }))
257 | }
258 |
259 | MongolianServer.prototype.toString = function() {
260 | return 'mongo://' + this.url.host + ':' + this.url.port
261 | }
262 |
263 | /**
264 | * Closes the current connection, passing the optional error object to any pending request callbacks.
265 | */
266 | MongolianServer.prototype.close = function(error) {
267 | error = error || new Error("Connection closed")
268 | if (this._connection.value) {
269 | var callbacks = this._callbacks
270 | this._callbacks = {}
271 | this._callbackCount = 0
272 | this._connection.value.close()
273 | this._connection.reset()
274 | delete this._type
275 | for (var requestId in callbacks) {
276 | callbacks[requestId](error)
277 | }
278 | this.mongolian._replicaSet.emit('lost', this)
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/lib/collection.js:
--------------------------------------------------------------------------------
1 | /* Mongolian DeadBeef by Marcello Bastea-Forte - zlib license */
2 | var util = require('util')
3 | var buffalo = require('buffalo')
4 | var mongo = buffalo.mongo
5 |
6 | var safetyNet = require('./util').safetyNet
7 | var extend = require('./util').extend
8 | var MongolianCursor = require('./cursor')
9 |
10 | var MongolianCollection = module.exports = function(db, name) {
11 | this.server = db.server
12 | this.db = db
13 | this.name = name
14 | this.fullName = db.name + "." + name
15 | this._indexCache = {}
16 | }
17 |
18 | /**
19 | * Returns a new MongolianCursor object
20 | */
21 | MongolianCollection.prototype.find = function(criteria, fields) {
22 | return new MongolianCursor(this, criteria, fields)
23 | }
24 |
25 | /**
26 | * Shorthand for collection.find(criteria).count(callback)
27 | * function(callback): count all documents
28 | * function(criteria, callback): count documents matching criteria
29 | */
30 | MongolianCollection.prototype.count = function(criteria, callback) {
31 | if (!callback) {
32 | callback = criteria
33 | criteria = undefined
34 | }
35 | this.find(criteria).count(callback)
36 | }
37 |
38 | /**
39 | * Shorthand for collection.find(criteria,fields).limit(1).next(callback)
40 | *
41 | * function(callback)
42 | * function(criteria, callback)
43 | * function(criteria, fields, callback)
44 | */
45 | MongolianCollection.prototype.findOne = function(criteria, fields, callback) {
46 | if (!callback && typeof fields === 'function') {
47 | callback = fields
48 | fields = undefined
49 | }
50 | if (!callback && typeof criteria === 'function') {
51 | callback = criteria
52 | criteria = undefined
53 | }
54 | this.find(criteria, fields).limit(1).next(callback)
55 | }
56 |
57 | /**
58 | * Insert an object or array of objects
59 | */
60 | MongolianCollection.prototype.insert = function(object, callback) {
61 | if (!object) throw new Error("No object to insert!")
62 | if (callback && typeof callback !== 'function') throw new Error("callback is not a function!")
63 | var objects = Array.isArray(object) ? object : [object]
64 |
65 | // Assign ids
66 | objects.forEach(function(object) {
67 | if (!object._id) object._id = new buffalo.ObjectId
68 | })
69 | this.db.sendCommand(mongo.serializeInsert(this.fullName, objects, false))
70 | if (callback) {
71 | this.db.lastError(safetyNet(callback, function() {
72 | callback(null, object)
73 | }))
74 | }
75 | }
76 |
77 | /**
78 | * Update an existing object
79 | * function(criteria, objNew, callback)
80 | * function(criteria, objNew, upsert, callback)
81 | * function(criteria, objNew, upsert, multi, callback)
82 | */
83 | MongolianCollection.prototype.update = function(criteria, objNew, upsert, multi, callback) {
84 | if (!callback && typeof multi === 'function') {
85 | callback = multi
86 | multi = false
87 | }
88 | if (!callback && typeof upsert === 'function') {
89 | callback = upsert
90 | upsert = false
91 | }
92 | if (callback && typeof callback !== 'function') throw new Error("callback is not a function!")
93 |
94 | this.db.sendCommand(mongo.serializeUpdate(this.fullName, criteria, objNew, upsert, multi))
95 | if (callback) {
96 | this.db.lastError(safetyNet(callback, function(result) {
97 | callback(null, result.n)
98 | }))
99 | }
100 | }
101 |
102 | /**
103 | * Shorthand update(criteria, objNew, true, false, callback)
104 | */
105 | MongolianCollection.prototype.upsert = function(criteria, objNew, callback) {
106 | this.update(criteria, objNew, true, false, callback)
107 | }
108 |
109 | /**
110 | * Shorthand update(criteria, objNew, false, true, callback)
111 | */
112 | MongolianCollection.prototype.updateAll = function(criteria, objNew, callback) {
113 | this.update(criteria, objNew, false, true, callback)
114 | }
115 |
116 | /**
117 | * Shorthand for update if object has an _id, otherwise insert
118 | */
119 | MongolianCollection.prototype.save = function(object, callback) {
120 | if (object._id) {
121 | this.upsert({_id:object._id}, object, safetyNet(callback, function(rowsUpdated) {
122 | if (callback) {
123 | if (!rowsUpdated) return callback(new Error("No rows updated!"))
124 | callback(null, object)
125 | }
126 | }))
127 | } else {
128 | this.insert(object, callback)
129 | }
130 | }
131 | /**
132 | * Removes documents from this collection using the given criteria
133 | * function(callback)
134 | * function(criteria, callback)
135 | */
136 | MongolianCollection.prototype.remove = function(criteria, callback) {
137 | if (!callback && typeof criteria === 'function') {
138 | callback = criteria
139 | criteria = {}
140 | }
141 | if (callback && typeof callback !== 'function') throw new Error("callback is not a function!")
142 | this.db.sendCommand(mongo.serializeDelete(this.fullName, criteria))
143 | if (callback) {
144 | this.db.lastError(safetyNet(callback, function(result) {
145 | callback(null, result.n)
146 | }))
147 | }
148 | }
149 |
150 | /**
151 | * Creates a new index on this collection (can be slow)
152 | */
153 | MongolianCollection.prototype.createIndex = function(keys, options, callback) {
154 | if (!callback && typeof options === 'function') {
155 | callback = options
156 | options = undefined
157 | }
158 | if (callback && typeof callback !== 'function') throw new Error("callback is not a function!")
159 | var self = this
160 | var index = this._indexSpec(keys, options)
161 | self._indexCache[index.name] = index
162 | this.db.collection("system.indexes").insert(index, callback)
163 | }
164 |
165 | /**
166 | * Creates a new index on this collection (uses a local cache to avoid multiple creates)
167 | *
168 | * Possible options:
169 | * name
170 | * unique
171 | */
172 | MongolianCollection.prototype.ensureIndex = function(keys, options, callback) {
173 | if (!callback && typeof options === 'function') {
174 | callback = options
175 | options = undefined
176 | }
177 | if (callback && typeof callback !== 'function') throw new Error("callback is not a function!")
178 | var name = this._indexSpec(keys).name
179 | if (this._indexCache[name]) {
180 | callback(null, this._indexCache[name])
181 | return
182 | }
183 | this.createIndex(keys, options, callback)
184 | }
185 |
186 | /**
187 | * Clears the local index cache used by ensureIndex
188 | */
189 | MongolianCollection.prototype.resetIndexCache = function(){
190 | this._indexCache = {}
191 | }
192 |
193 | /**
194 | * Removes a given index from the database
195 | */
196 | MongolianCollection.prototype.dropIndex = function(indexName, callback) {
197 | this.resetIndexCache()
198 |
199 | this.db.runCommand({
200 | deleteIndexes: this.name,
201 | index: indexName
202 | }, callback)
203 | }
204 |
205 | /**
206 | * Returns all indexes for this collection
207 | */
208 | MongolianCollection.prototype.indexes = function(callback) {
209 | this.db.collection("system.indexes").find({ ns:this.fullName }).toArray(callback)
210 | }
211 |
212 | /**
213 | * !!! Removes this collection from the database !!!
214 | */
215 | MongolianCollection.prototype.drop = function(callback) {
216 | this.db.runCommand({ drop:this.name }, callback)
217 | }
218 |
219 | MongolianCollection.prototype.runCommand = function(commandNameOrCommand, options, callback) {
220 | if (!callback && typeof options === 'function') {
221 | callback = options
222 | options = undefined
223 | }
224 | var command = commandNameOrCommand
225 | if (typeof commandNameOrCommand === 'string') {
226 | command = {}
227 | command[commandNameOrCommand] = this.name
228 | extend(command, options)
229 | }
230 | this.db.runCommand(command, callback)
231 | }
232 |
233 | MongolianCollection.prototype.mapReduce = function(mapFunction, reduceFunction, options, callback) {
234 | var command = {
235 | mapreduce:this.name,
236 | map:mapFunction,
237 | reduce:reduceFunction
238 | }
239 | if (typeof options === 'string') {
240 | options = { out:options }
241 | }
242 | extend(command, options)
243 |
244 | var self = this
245 | this.db.runCommand(command, safetyNet(callback, function(result) {
246 | callback(null, new MapReduceResult(self.db, result))
247 | }))
248 | }
249 |
250 | function MapReduceResult(db, result) {
251 | if (result.results) this.results = result.results
252 | this.timeMillis = result.timeMillis
253 | this.counts = result.counts
254 | if (result.db) db = db.server.db(result.db)
255 | if (result.result) this.collection = db.collection(result.result)
256 | }
257 | MapReduceResult.prototype.find = function(criteria, fields) {
258 | if (!this.collection) throw new Error("Map reduce returned no collection")
259 | return this.collection.find(criteria, fields)
260 | }
261 | MapReduceResult.prototype.drop = function(){
262 | this.collection && this.collection.drop()
263 | }
264 |
265 | /**
266 | *
267 | */
268 | MongolianCollection.prototype.findAndModify = function(options, callback) {
269 | var command = {
270 | findandmodify:this.name
271 | }
272 | for (var key in options) command[key] = options[key]
273 | this.db.runCommand(command, safetyNet(callback, function(result) {
274 | callback(null, result.value)
275 | }))
276 | }
277 |
278 | MongolianCollection.prototype.group = function (command, callback) {
279 | this.db.runCommand({group: command}, callback);
280 | }
281 |
282 | MongolianCollection.prototype.distinct = function(key, query, callback) {
283 | if (!callback && typeof query === 'function') {
284 | callback = query
285 | query = undefined
286 | }
287 | var command = {
288 | distinct: this.name,
289 | query: query,
290 | key: key
291 | }
292 | this.db.runCommand(command, safetyNet(callback, function(result) {
293 | callback(null, result.values)
294 | }))
295 | }
296 |
297 | MongolianCollection.prototype.isCapped = function(callback) {
298 | collName = this.db.name + "." + this.name;
299 | this.db.collection("system.namespaces").findOne({name:collName}, function(err, result) {
300 | if (typeof callback !== 'function') return;
301 | if (err) {
302 | callback(err)
303 | }
304 | if (!result || !result.options) {
305 | callback("Could not find this Collection.")
306 | } else {
307 | callback(null, result.options.capped, result.options.size)
308 | }
309 | })
310 | }
311 |
312 | MongolianCollection.prototype.toString = function() {
313 | return this.db + "." + this.name
314 | }
315 |
316 | //////////////////////////////////////////////////////////////////////////////////
317 | // Internal
318 |
319 | MongolianCollection.prototype._indexSpec = function(keys, options) {
320 | var index = {
321 | ns: this.fullName,
322 | key: keys
323 | }
324 | if (options) {
325 | for (var x in options) {
326 | index[x] = options[x]
327 | }
328 | }
329 | if (!index.name) {
330 | index.name = ""
331 | for (var key in keys) {
332 | if (index.name.length) {
333 | index.name += "_"
334 | }
335 | index.name += key + "_" + keys[key]
336 | }
337 | }
338 | return index
339 | }
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | Project Inactive
2 | ----------------
3 |
4 | I am no longer have the time to work on this project. Feel free to fork!
5 |
6 | Mongolian DeadBeef
7 | ==================
8 | Mongolian DeadBeef is an awesome Mongo DB node.js driver that attempts to closely approximate the [mongodb shell][1].
9 |
10 | [](http://travis-ci.org/marcello3d/node-mongolian)
11 |
12 | Introduction
13 | ------------
14 | Mongolian DeadBeef and its documentation is super under construction! Go check out [examples/mongolian_trainer.js][2]
15 | and the rest of the source!
16 |
17 | Unlike other MongoDB node.js drivers, Mongolian DeadBeef is built from the ground up for node.js, using
18 | [node-buffalo][3] for BSON/message serialization.
19 |
20 | v0.1.15 Upgrade notes
21 | ---------------------
22 | 0.1.15 uses [node-buffalo][3] instead of mongodb-native for serialization, this means a few incompatibilities:
23 |
24 | + The helper methods on `ObjectId` are removed, use the `ObjectId` constructor to parse hex strings
25 | + `Code` type is removed, use vanilla function instances instead
26 | + `DBRef` is not supported
27 | + Error messages may be different
28 |
29 | Installation
30 | ------------
31 | **DISCLAIMER: The API is experimental (but stabilizing). I will be adding, removing, and changing the API in the
32 | interest of a solid API. Use at your own risk**
33 |
34 | You can either clone the source and install with `npm link`, or install the latest published version from npm with
35 | `npm install mongolian`.
36 |
37 | Running Tests
38 | -------------
39 | Run the tests with `npm test`.
40 |
41 | Motivation
42 | ----------
43 | Not a fan of existing asynchronous mongodb apis for node.js, I set out to write my own. To avoid completely reinventing
44 | the wheel, much of the Mongolian DeadBeef API is inspired by the [mongodb shell][1].
45 |
46 | High level principles:
47 |
48 | * Less is more
49 | * Nothing is added without careful consideration
50 | * Remove everything but the essentials
51 | * Each refactor should remove as much unnecessary lines of code as possible
52 | * Fail early and often
53 | * If I can easily detect a programmer error, an exception will be thrown
54 |
55 | Notes:
56 |
57 | * mongodb is pretty simple, much of its functionality is defined as queries on special databases
58 | * This allows for lots of code reuse
59 | * Avoid callbacks unless they are absolutely necessary
60 |
61 | Basics
62 | ------
63 | Most of the work in MongolianDeadBeef doesn't occur until a query is actually made. This means that simple operations
64 | are fast and synchronous. Currently there is one connection per server.
65 |
66 | Examples
67 | --------
68 | ```javascript
69 | var Mongolian = require("mongolian")
70 |
71 | // Create a server instance with default host and port
72 | var server = new Mongolian
73 |
74 | // Get database
75 | var db = server.db("awesome_blog")
76 |
77 | // Get some collections
78 | var posts = db.collection("posts")
79 | var comments = db.collection("comments")
80 |
81 | // Insert some data
82 | posts.insert({
83 | pageId: "hallo",
84 | title: "Hallo",
85 | created: new Date,
86 | body: "Welcome to my new blog!"
87 | })
88 |
89 | // Get a single document
90 | posts.findOne({ pageId: "hallo" }, function(err, post) {
91 | ...
92 | })
93 |
94 | // Document cursors
95 | posts.find().limit(5).sort({ created: 1 }).toArray(function (err, array) {
96 | // do something with the array
97 | })
98 | posts.find({ title: /^hal/ }).forEach(function (post) {
99 | // do something with a single post
100 | }, function(err) {
101 | // handle errors/completion
102 | })
103 | ```
104 | Connections and Authentication
105 | ------------------------------
106 | ```javascript
107 | // Create a server with a specific host/port
108 | var server = new Mongolian("mongo.example.com:12345")
109 |
110 |
111 | // Authenticate a database
112 | db.auth(username, password)
113 |
114 |
115 | // Supported connection url format: [mongo://][username:password@]hostname[:port][/databasename]
116 | // Use uri-encoding for special characters in the username/password/database name
117 |
118 | // Database/auth shorthand (equivalent to calling db() and auth() on the resulting server)
119 | var db = new Mongolian("mongo://username:password@mongo.example.com:12345/database")
120 |
121 | // Connecting to replicasets:
122 | var server = new Monglian(
123 | "server1.local",
124 | "server2.local",
125 | "server3.local:27018"
126 | )
127 | ```
128 | Logging
129 | -------
130 | By default, Mongolian logs to console.log, but you can override this by specifying your own log object (any object that
131 | provides `debug`, `info`, `warn`, and `error` methods):
132 | ```javascript
133 | var server = new Mongolian({
134 | log: {
135 | debug: function(message) { ... },
136 | info: function(message) { ... },
137 | warn: function(message) { ... },
138 | error: function(message) { ... }
139 | }
140 | })
141 |
142 | var server = new Mongolian('server1.local', 'server2.local', {
143 | log: { ... }
144 | })
145 | ```
146 | BSON Data Types
147 | ---------------
148 | Mongolian DeadBeef uses [node-buffalo][3]'s BSON serialization code. Most BSON types map directly to JavaScript types,
149 | here are the ones that don't:
150 | ```javascript
151 | var Long = require('mongolian').Long // goog.math.Long - http://closure-library.googlecode.com/svn/docs/class_goog_math_Long.html
152 | var ObjectId = require('mongolian').ObjectId // new ObjectId(byteBuffer or hexString)
153 | var Timestamp = require('mongolian').Timestamp // == Long
154 | var DBRef = require('mongolian').DBRef // not supported yet
155 | ```
156 | GridFS
157 | ------
158 | The Mongo shell doesn't support gridfs, so Mongolian DeadBeef provides a custom Stream-based GridFS implementation.
159 | It consists of two main classes, `MongolianGridFS` and `MongolianGridFile`. You can get a MongolianGridFS object from a
160 | database with the `gridfs([gridfs name])` function.
161 | ```javascript
162 | // Get a GridFS from a database
163 | var gridfs = db.gridfs() // name defaults to 'fs'
164 |
165 | // Writing to GridFS consists of creating a GridFS file:
166 | var file = gridfs.create({
167 | filename:"License",
168 | contentType:"text/plain"
169 | })
170 | // And getting writable Stream (see http://nodejs.org/docs/v0.4/api/streams.html#writable_Stream )
171 | var stream = file.writeStream()
172 |
173 | // You can then pipe a local file to that stream easily with:
174 | fs.createReadStream('LICENSE').pipe(stream)
175 |
176 | // Reading a file from GridFS is similar:
177 | gridfs.findOne("License", function (err, file) {
178 | if (!err && file) {
179 | // Get the read stream:
180 | var stream = file.readStream()
181 |
182 | // You could then pipe the file out to a http response, for example:
183 | stream.pipe(httpResponse)
184 | }
185 | })
186 |
187 | // You can access metadata fields from the file object:
188 | file.length // might be a Long
189 | file.chunkSize
190 | file.md5
191 | file.filename
192 | file.contentType // mime-type
193 | file.uploadDate
194 | // These two are optional and may not be defined:
195 | file.metadata
196 | file.aliases
197 |
198 | // If you make any changes, save them:
199 | file.save()
200 | ```
201 | Mongodb Shell Command Support
202 | -----------------------------
203 |
204 | Nearly all commands are identical in syntax to the mongodb shell. However, asynchronous commands that go to the server
205 | will have an _optional_ node.js style callback parameter.
206 |
207 | Currently most commands starting with `get` are named without the `get`. Some of the getters are implemented as values
208 | instead of functions.
209 |
210 | + Bold functions are supported
211 | + Italicized functions are supported with different syntax
212 | + Everything else is currently unsupported
213 |
214 | There will likely be methods below that are never supported by Mongolian DeadBeef, since I'm targetting a slightly
215 | different use case.
216 |
217 | ### Databases
218 | From http://api.mongodb.org/js/1.8.1/symbols/src/shell_db.js.html
219 |
220 | + db.addUser(username, password[, readOnly=false][, callback])
221 | + db.auth(username, password)
222 | + db.cloneDatabase(fromhost)
223 | + db.commandHelp(name) returns the help for the command
224 | + db.copyDatabase(fromdb, todb, fromhost)
225 | + db.createCollection(name, { size : ..., capped : ..., max : ... } )
226 | + db.currentOp() displays the current operation in the db
227 | + db.dropDatabase() - see callback note below
228 | + db.eval(func[, arg1, arg2, ...][, callback]) run code server-side - see callback note below
229 | + db.getCollection(cname) implemented as db.collection(cname)
230 | + db.getCollectionNames() implemented as db.collectionNames(callback)
231 | + db.getLastError() - just returns the err msg string
232 | + db.getLastErrorObj() implemented as db.lastError(callback) - return full status object
233 | + db.getMongo() get the server connection object implemented as db.server
234 | + db.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair
235 | + db.getName() implemented as db.name
236 | + db.getPrevError() _(deprecated?)_
237 | + db.getProfilingStatus() - returns if profiling is on and slow threshold
238 | + db.getReplicationInfo()
239 | + db.getSiblingDB(name) get the db at the same server as this one
240 | + db.isMaster() check replica primary status
241 | + db.killOp(opid) kills the current operation in the db
242 | + db.listCommands() lists all the db commands
243 | + db.printCollectionStats()
244 | + db.printReplicationInfo()
245 | + db.printSlaveReplicationInfo()
246 | + db.printShardingStatus()
247 | + db.removeUser(username[, callback]) - see callback note below
248 | + db.repairDatabase()
249 | + db.resetError()
250 | + db.runCommand(cmdObj[, callback]) run a database command. if cmdObj is a string, turns it into { cmdObj : 1 }
251 | + db.serverStatus()
252 | + db.setProfilingLevel(level,) 0=off 1=slow 2=all
253 | + db.shutdownServer()
254 | + db.stats()
255 | + db.version() current version of the server
256 |
257 | ### Collections
258 | From http://api.mongodb.org/js/1.8.1/symbols/src/shell_collection.js.html
259 |
260 | + collection.find().help() - show DBCursor help
261 | + collection.count(callback)
262 | + collection.dataSize()
263 | + collection.distinct(key[, query], callback) - eg. collection.distinct( 'x' )
264 | + collection.drop([callback]) drop the collection - see callback note below
265 | + collection.dropIndex(name[, callback]) - see callback note below
266 | + collection.dropIndexes()
267 | + collection.ensureIndex(keypattern[,options][, callback]) - options is an object with these possible fields: name, unique, dropDups - see callback note below
268 | + collection.reIndex()
269 | + collection.find([query],[fields]) - query is an optional query filter. fields is optional set of fields to return.
270 | e.g. collection.find( {x:77} , {name:1, x:1} ) - returns a cursor object
271 | + collection.find(...).count()
272 | + collection.find(...).limit(n)
273 | + collection.find(...).skip(n)
274 | + collection.find(...).sort(...)
275 | + collection.findOne([query][callback])
276 | + collection.findAndModify( { update : ... , remove : bool [, query: {}, sort: {}, 'new': false] } )
277 | ex: finds document with comment value 0, increase its 'count' field by 1, and return the updated document.
278 | collection.findAndModify( {query: {comment:'0'}, update : {"$inc":{"count":1}}, 'new': true}, function (err, doc) {
279 | console.log(doc)
280 | })
281 | + collection.getDB() get DB object associated with collection implemented as collection.db
282 | + collection.getIndexes() implemented as collection.indexes(callback)
283 | + collection.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )
284 | + collection.mapReduce( mapFunction , reduceFunction , [optional params][, callback])
285 | + collection.remove(query[, callback]) - see callback note below
286 | + collection.renameCollection( newName , [dropTarget] ) renames the collection.
287 | + collection.runCommand( name , [options][, callback]) runs a db command with the given name where the first param is the collection name
288 | + collection.save(obj[, callback]) - see callback note below
289 | + collection.stats()
290 | + collection.storageSize() - includes free space allocated to this collection
291 | + collection.totalIndexSize() - size in bytes of all the indexes
292 | + collection.totalSize() - storage allocated for all data and indexes
293 | + collection.update(query, object[, upsert\_bool, multi\_bool][, callback]) - see callback note below
294 | + collection.validate() - SLOW
295 | + collection.getShardVersion() - only for use with sharding
296 |
297 | ### Cursors
298 | From http://api.mongodb.org/js/1.8.1/symbols/src/shell_query.js.html
299 |
300 | + cursor.sort( {...} )
301 | + cursor.limit( n )
302 | + cursor.skip( n )
303 | + cursor.count() - total # of objects matching query, ignores skip,limit
304 | + cursor.size() - total # of objects cursor would return, honors skip,limit
305 | + cursor.explain([verbose])
306 | + cursor.hint(...)
307 | + cursor.showDiskLoc() - adds a $diskLoc field to each returned object
308 | + cursor.toArray(callback) - unique to Mongolian DeadBeef
309 | + cursor.forEach(func, callback) - calls func for each document, and callback upon completion or error
310 | + cursor.print() - output to console in full pretty format
311 | + cursor.map( func ) - map documents before they're returned in next, toArray, forEach
312 | + cursor.hasNext()
313 | + cursor.next([callback]) - returns the next document or null if there are no more
314 |
315 |
316 | ### Callbacks
317 | Callbacks take the standard node.js format: `function(error, value)`
318 |
319 | Mongodb handles write operations (insert, update, save, drop, etc.) asynchronously. If you pass a callback into one of
320 | these methods, this is equivalent to immediately calling `db.lastError(callback)` on the same server/connection. Passing
321 | a null value will not send a getLastError command to the server.
322 |
323 | Currently there is no way to specify the write concern on these inlined callbacks.
324 |
325 | Todo
326 | ----
327 |
328 | * Connection pooling
329 | * Various utility methods
330 | * More unit tests
331 | * Documentation
332 | * Cleanup
333 |
334 | Contributing
335 | ------------
336 | Try it out and send me feedback! That's the best help I could use right now. Unit tests are good, too.
337 |
338 | License
339 | -------
340 | Mongolian DeadBeef is open source software under the [zlib license][4].
341 |
342 | [1]: http://www.mongodb.org/display/DOCS/dbshell+Reference
343 | [2]: https://github.com/marcello3d/node-mongolian/blob/master/examples/mongolian_trainer.js
344 | [3]: https://github.com/marcello3d/node-buffalo
345 | [4]: https://github.com/marcello3d/node-mongolian/blob/master/LICENSE
346 |
--------------------------------------------------------------------------------