From 42c3dd14887cd75f2301eec1692bdc292b6bbc56 Mon Sep 17 00:00:00 2001 From: Joshua Kifer Date: Thu, 4 Nov 2010 20:56:11 +0000 Subject: [PATCH] hacked bson for command support, find_and_modify implemented, sequences --- README.md | 2 +- lib/congo.coffee | 49 ++++++++++++++++++++++++ lib/mongo.coffee | 94 ++++++++++++++++++++++++++++++++++------------ lib/vendor/bson.js | 5 +++ 4 files changed, 124 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index aff0912..f8ad540 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Planned ------- * support for more first class array operations -* sub-document loading/updating +* sub-document loading/updating (lazy loading) * full support for the Mongo query api returning hydrated objects * support for all Mongo data types diff --git a/lib/congo.coffee b/lib/congo.coffee index e46eea0..c9d25c9 100644 --- a/lib/congo.coffee +++ b/lib/congo.coffee @@ -100,6 +100,54 @@ class ArrayType @__defineGetter__ index, => @__get__ index +# class ObjectType +# constructor: (@__type__) -> +# @__values__ = {} + +# __set__: (key, val, next) -> +# log '[ Setting ] ' + @__key__ + '.' + key if process.env.DEBUG? +# @__values__[key] = val +# @__relate__ key, val +# @__parent__.__ascend_set__ @__key__ + '.' + key, val, next + +# __ascend_set__: (key, val, next) -> +# log '[ Ascending ] ' + @__key__ + '.' + key if process.env.DEBUG? +# @__parent__.__ascend_set__ @__key__ + '.' + key, val, next + +# __get__: (key) -> +# @__values__[key] + +# __relate__: (key, val) -> +# if val instanceof Model +# log '[ Relating ] ' + @__key__ + '.' + key if process.env.DEBUG? +# val.__parent__ = @ +# val.__key__ = key + +# __dehydrate__: -> +# document = {} +# for key, val of @__values__ +# if val instanceof Model +# document[key] = val.__dehydrate__() +# else +# document[key] = val +# document + +# __hydrate__: (document) -> +# log '[ Hydrating ] ' + @__key__ if process.env.DEBUG? +# for key, val of document +# if @__type__ instanceof Function and @__type__.prototype instanceof Model +# new @__type__ (object) => +# @__relate__ key, object +# @__values__[key] = object +# object.__hydrate__ val +# else +# @__values__[key] = val +# @__defineSetter__ key, (val) => +# @__set__ key, val +# @__defineGetter__ key, => +# @__get__ key +# @ + class DateType extends Type initialize: (val) -> return val if val? @@ -108,6 +156,7 @@ class DateType extends Type __type__ = Identity: (args...) -> new Type args... + # Object: (args...) -> new ObjectType args... Integer: (args...) -> new Type args... Double: (args...) -> new Type args... String: (args...) -> new Type args... diff --git a/lib/mongo.coffee b/lib/mongo.coffee index ea5a420..2ba4920 100644 --- a/lib/mongo.coffee +++ b/lib/mongo.coffee @@ -23,8 +23,7 @@ class Database @connections = [] connection: (next) -> - for i in [0...@connections.length] - connection = @connections[i] + for connection, i in @connections if connection.available log 'Connection: ' + i if process.env.DEBUG? next connection @@ -39,50 +38,97 @@ class Database @connection (connection) => connection.retain() connection.send (@compose collection, 2002, 0, document) - connection.send (@compose '$cmd', 2004, 0, 0, 1, { getLastError: 1 }), (data) => + @last_error connection, (error) -> connection.release() - errors = @decompose data - log 'Mongo error: ' + errors[0].err if errors[0].err? + log 'Mongo error: ' + error.err if error.err? next(document._id) if next? - update: (collection, selector, update, next) -> + update: (collection, query, update, next) -> @connection (connection) => connection.retain() - connection.send (@compose collection, 2001, 0, 0, selector, update) - connection.send (@compose '$cmd', 2004, 0, 0, 1, { getLastError: 1 }), (data) => + connection.send (@compose collection, 2001, 0, 0, query, update) + @last_error connection, (error) -> connection.release() - errors = @decompose data - log 'Mongo error: ' + errors[0].err if errors[0].err? - next(errors[0].n) if next? + log 'Mongo error: ' + error.err if error.err? + next() if next? - query: (collection, selector, next) -> + find: (collection, query, args..., next) -> + path = if args.length > 0 then { (args[0]): 1 } else {} @connection (connection) => connection.retain() - connection.send (@compose collection, 2004, 0, 0, 0, selector), (data) => + connection.send (@compose collection, 2004, 0, 0, 0, query, path), (data) => documents = @decompose data - connection.send (@compose '$cmd', 2004, 0, 0, 1, { getLastError: 1 }), (data) => + @last_error connection, (error) -> connection.release() - errors = @decompose data - log 'Mongo error: ' + errors[0].err if errors[0].err? + log 'Mongo error: ' + error.err if error.err? next(documents) if next? - remove: (collection, selector, next) -> + find_one: (collection, query, args..., next) -> + @find collection, query, args..., (documents) -> + if documents.length > 0 then next documents[0] else next null + + exists: (collection, id, path, next) -> + @find collection, { _id: id, (path): { $exists: true } }, '_id', (documents) -> + if documents.length > 0 then next true else next false + + remove: (collection, query, next) -> @connection (connection) => connection.retain() - connection.send (@compose collection, 2006, 0, 0, selector) + connection.send (@compose collection, 2006, 0, 0, query) connection.send (@compose '$cmd', 2004, 0, 0, 1, { getLastError: 1 }), (data) => connection.release() errors = @decompose data log 'Mongo error: ' + errors[0].err if errors[0].err? next() if next? + clear: (collection, next) -> + @remove collection, {}, next + + last_error: (connection, next) -> + connection.send (@compose '$cmd', 2004, 0, 0, 1, { getLastError: 1 }), (data) => + next (@decompose data)[0] if next? + + command: (command, options, next) -> + options.__command__ = 'findandmodify' + @connection (connection) => + connection.retain() + connection.send (@compose '$cmd', 2004, 0, 0, 1, options), (data) => + connection.release() + document = (@decompose data)[0] + if document['bad cmd']? + log 'Mongo error: ' + document.errmsg + inspect document['bad cmd'] + next null if next? + else if document['$err']? + log 'Mongo error: ' + document['$err'] + next null if next? + else + next document if next? + + modify: (collection, options, next) -> + options.findandmodify = collection + options.query ?= {} + options.sort ?= {} + options.remove ?= false + options.update ?= {} + options.new ?= false + options.fields ?= {} + options.upsert ?= false + @command 'findandmodify', options, (document) -> + next document.value + + sequence: (collection, id, args..., next) -> + key = if args.length > 0 then args[0] else '_root' + @modify collection, { new: true, query: { _id: id }, update: { $inc: { ('_sequence.' + key): 1 }}, fields: { ('_sequence.' + key): 1 }}, (result) -> + next result._sequence[key] + compose: (collection, code, flags, items...) -> data = binary.fromInt(flags) + binary.encode_cstring(@name + '.' + collection) - for i in [0...items.length] - if typeof items[i] == 'object' - data += bson.serialize items[i] + for item in items + if typeof item == 'object' + data += bson.serialize item else - data += binary.fromInt items[i] + data += binary.fromInt item binary.fromInt(data.length + 4 * 4) + binary.fromInt(0) + binary.fromInt(0) + binary.fromInt(code) + data decompose: (data) -> @@ -99,7 +145,7 @@ class Database count = binary.toInt(data.substr i) i += 4 documents = [] - for c in [0...count] + while count-- documents.push bson.deserialize(data.substr i) i += binary.toInt(data.substr i) else @@ -131,8 +177,6 @@ class Connection log 'closed' @stream.addListener 'end', => log 'end' - @stream.addListener 'end', => - log 'end' @stream.addListener 'close', => log 'close' @stream.addListener 'timeout', => diff --git a/lib/vendor/bson.js b/lib/vendor/bson.js index 2050fd1..69aea55 100644 --- a/lib/vendor/bson.js +++ b/lib/vendor/bson.js @@ -347,6 +347,11 @@ BSON.encodeArray = function(array, checkKeys) { BSON.encodeObject = function(object, checkKeys) { var encoded_string = ''; + if (object['__command__']) { + encoded_string += BSON.encodeValue('', object['__command__'], object[object['__command__']], false, checkKeys); + delete object[object['__command__']] + delete object['__command__'] + } // Let's fetch all the variables for the object and encode each for(var variable in object) { if(object[variable] == null || (object[variable] != null && object[variable].constructor != Function)) {