浏览代码

Merge branch 'feature/mongo_2.6.5_expressions' into feature/mongo_2.6.5_expressions_Date

Jason Walton 11 年之前
父节点
当前提交
4ee53c278c
共有 2 个文件被更改,包括 147 次插入74 次删除
  1. 37 21
      lib/pipeline/Document.js
  2. 110 53
      test/lib/pipeline/Document.js

+ 37 - 21
lib/pipeline/Document.js

@@ -11,7 +11,8 @@ var Document = module.exports = function Document(){
 	if(this.constructor == Document) throw new Error("Never create instances! Use static helpers only.");
 }, klass = Document, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
 
-var Value = require("./Value");
+var Value = require("./Value"),
+	FieldPath = require("./FieldPath");
 
 /**
  * Shared "_id"
@@ -37,20 +38,28 @@ klass.toJson = function toJson(doc) {
 
 //SKIPPED: most of MutableDocument except for getNestedField and setNestedField, squashed into Document here (because that's how they use it)
 function getNestedFieldHelper(obj, path) {
-	// NOTE: DEVIATION FROM MONGO: from MutableDocument, implemented a bit differently here but should be same basic functionality
-	var paths = Array.isArray(path) ? path : path.split(".");
-	for (var i = 0, l = paths.length, o = obj; i < l && o instanceof Object; i++) {
-		o = o[paths[i]];
+	// NOTE: DEVIATION FROM MONGO: from MutableDocument; similar but necessarily different
+	var keys = Array.isArray(path) ? path : (path instanceof FieldPath ? path.fields : path.split(".")),
+		lastKey = keys[keys.length - 1];
+	for (var i = 0, l = keys.length - 1, cur = obj; i < l && cur instanceof Object; i++) {
+		var next = cur[keys[i]];
+		if (!(next instanceof Object)) return undefined;
+		cur = next;
 	}
-	return o;
-};
-klass.getNestedField = klass.getNestedFieldHelper;  // NOTE: due to ours being static these are the same
+	return cur[lastKey];
+}
+klass.getNestedField = getNestedFieldHelper;  // NOTE: ours is static so these are the same
 klass.setNestedField = function setNestedField(obj, path, val) {
-	// NOTE: DEVIATION FROM MONGO: from MutableDocument, implemented a bit differently here but should be same basic functionality
-	var paths = Array.isArray(path) ? path : path.split("."),
-		key = paths.pop(),
-		parent = klass.getNestedField(obj, paths);
-	if (parent) parent[key] = val;
+	// NOTE: DEVIATION FROM MONGO: from MutableDocument; similar but necessarily different
+	var keys = Array.isArray(path) ? path : (path instanceof FieldPath ? path.fields : path.split(".")),
+		lastKey = keys[keys.length - 1];
+	for (var i = 0, l = keys.length - 1, cur = obj; i < l && cur instanceof Object; i++) {
+		var next = cur[keys[i]];
+		if (!(next instanceof Object)) cur[keys[i]] = next = {};
+		cur = next;
+	}
+	cur[lastKey] = val;
+	return val;
 };
 //SKIPPED: getApproximateSize -- not implementing mem usage right now
 //SKIPPED: hash_combine
@@ -104,7 +113,7 @@ klass.serializeForSorter = function serializeForSorter(doc) {
 };
 
 klass.deserializeForSorter = function deserializeForSorter(docStr, sorterDeserializeSettings) {
-	JSON.parse(docStr);
+	return JSON.parse(docStr);
 };
 
 //SKIPPED: swap
@@ -120,22 +129,29 @@ klass.empty = function(obj) {
 
 /**
  * Clone a document
+ * This should only be called by MutableDocument and tests
+ * The new document shares all the fields' values with the original.
+ * This is not a deep copy.  Only the fields on the top-level document
+ * are cloned.
  * @static
  * @method clone
  * @param doc
  */
 klass.clone = function clone(doc) {
+	var obj = {};
+	for (var key in doc) {
+		if (doc.hasOwnProperty(key)) {
+			obj[key] = doc[key];
+		}
+	}
+	return obj;
+};
+klass.cloneDeep = function cloneDeep(doc) {	//there are casese this is actually what we want
 	var obj = {};
 	for (var key in doc) {
 		if (doc.hasOwnProperty(key)) {
 			var val = doc[key];
-			if (val === undefined || val === null) { // necessary to handle null values without failing
-				obj[key] = val;
-			} else if (val instanceof Object && val.constructor === Object) {
-				obj[key] = Document.clone(val);
-			} else {
-				obj[key] = val;
-			}
+			obj[key] = val instanceof Object && val.constructor === Object ? Document.clone(val) : val;
 		}
 	}
 	return obj;

+ 110 - 53
test/lib/pipeline/Document.js

@@ -1,91 +1,148 @@
 "use strict";
 var assert = require("assert"),
-	Document = require("../../../lib/pipeline/Document");
+	Document = require("../../../lib/pipeline/Document"),
+	FieldPath = require("../../../lib/pipeline/FieldPath");
 
 // Mocha one-liner to make these tests self-hosted
 if(!module.parent)return(require.cache[__filename]=null,(new(require("mocha"))({ui:"exports",reporter:"spec",grep:process.env.TEST_GREP})).addFile(__filename).run(process.exit));
 
 exports.Document = {
 
-	"Json conversion": {
-
-		"convert to Json": function toJson() {
-			var aDocument = {"prop1":0},
-				result = Document.toJson(aDocument);
-			assert.equal(result, '{"prop1":0}');
-		},
-
-		"convert to Json with metadata": function toJsonWithMetaData() {
-			var aDocument = {"prop1": 0,"metadata":"stuff"},
-				result = Document.toJsonWithMetaData(aDocument);
-			assert.equal(result, '{"prop1":0,"metadata":"stuff"}');
+	//SKIPPED: Create -- ours is a static class so we have no constructor
+
+	//SKIPPED: CreateFromBsonObj -- no ctor again, would just use JSON.parse
+
+	//SKIPPED: AddField - no need because we use:  obj[key] = val
+
+	//SKIPPED: GetValue - no need because we use:  val = obj[key]
+
+	//SKIPPED: SetField - no need because we usually use:  obj[key] = val  though setNestedField *does* is implemented now
+
+	".compare()": {
+
+		"should work": function testCompare() {
+            assertComparison(0, {}, {});
+            assertComparison(0, {a:1}, {a:1});
+            assertComparison(-1, {}, {a:1});
+            assertComparison(-1, {a:1}, {c:1});
+            assertComparison(0, {a:1,r:2}, {a:1,r:2});
+            assertComparison(-1, {a:1}, {a:1,r:2});
+            assertComparison(0, {a:2}, {a:2});
+            assertComparison(-1, {a:1}, {a:2});
+            assertComparison(-1, {a:1,b:1}, {a:1,b:2});
+            // numbers sort before strings
+            assertComparison(-1, {a:1}, {a:"foo"});
+			// helpers for the above
+			function cmp(a, b) {
+				var result = Document.compare(a, b);
+				return result < 0 ? -1 : // sign
+					result > 0 ? 1 :
+					0;
+			}
+			function assertComparison(expectedResult, a, b) {
+				assert.strictEqual(expectedResult, cmp(a, b));
+				assert.strictEqual(-expectedResult, cmp(b, a));
+				if (expectedResult === 0) {
+					var hash = JSON.stringify; // approximating real hash
+					assert.strictEqual(hash(a), hash(b));
+				}
+			}
 		},
 
-		"convert from Json": function fromJsonWithMetaData() {
-			var aDocumentString = '{\"prop1\":0,\"metadata\":1}',
-				jsonDocument = {"prop1":0,"metadata":1},
-				result = Document.fromJsonWithMetaData(aDocumentString);
-			assert.deepEqual(result, jsonDocument);
+		"should work for a null": function testCompareNamedNull(){
+			var obj1 = {z:null},
+				obj2 = {a:1};
+            //// Comparsion with type precedence.
+			// assert(obj1.woCompare(obj2) < 0); //NOTE: probably will not need this
+            // Comparison with field name precedence.
+			assert(Document.compare(obj1, obj2) > 0);
 		},
 
-	},
-
-	"compare 2 Documents": {
-
-		"should return 0 if Documents are identical": function compareDocumentsIdentical() {
-			var lDocument = {"prop1": 0},
-				rDocument = {"prop1": 0},
+		"should return 0 if Documents are identical": function() {
+			var lDocument = {prop1: 0},
+				rDocument = {prop1: 0},
 				result = Document.compare(lDocument, rDocument);
 			assert.equal(result, 0);
 		},
 
-		"should return -1 if left Document is shorter": function compareLeftDocumentShorter() {
-			var lDocument = {"prop1": 0},
-				rDocument = {"prop1": 0, "prop2": 0},
+		"should return -1 if left Document is shorter": function() {
+			var lDocument = {prop1: 0},
+				rDocument = {prop1: 0, prop2: 0},
 				result = Document.compare(lDocument, rDocument);
 			assert.equal(result, -1);
 		},
 
-		"should return 1 if right Document is shorter": function compareRightDocumentShorter() {
-			var lDocument = {"prop1": 0, "prop2": 0},
-				rDocument = {"prop1": 0},
+		"should return 1 if right Document is shorter": function() {
+			var lDocument = {prop1: 0, prop2: 0},
+				rDocument = {prop1: 0},
 				result = Document.compare(lDocument, rDocument);
 			assert.equal(result, 1);
 		},
 
-		"should return nameCmp result -1 if left Document field value is less": function compareLeftDocumentFieldLess() {
-			var lDocument = {"prop1": 0},
-				rDocument = {"prop1": 1},
+		"should return nameCmp result -1 if left Document field value is less": function() {
+			var lDocument = {prop1: 0},
+				rDocument = {prop1: 1},
 				result = Document.compare(lDocument, rDocument);
 			assert.equal(result, -1);
 		},
 
-		"should return nameCmp result 1 if right Document field value is less": function compareRightDocumentFieldLess() {
-			var lDocument = {"prop1": 1},
-				rDocument = {"prop1": 0},
+		"should return nameCmp result 1 if right Document field value is less": function() {
+			var lDocument = {prop1: 1},
+				rDocument = {prop1: 0},
 				result = Document.compare(lDocument, rDocument);
 			assert.equal(result, 1);
 		},
 
 	},
 
-	"clone a Document": {
+	".clone()": {
+
+		"should shallow clone a single field document": function testClone() {
+			var doc = {a:{b:1}},
+				clone = doc;
+
+			//NOTE: silly since we use static helpers but here for consistency
+			// Check equality
+			assert.strictEqual(clone, doc);
+			// Check pointer equality of sub document
+			assert.strictEqual(clone.a, doc.a);
+
+			// Change field in clone and ensure the original document's field is unchanged.
+			clone = Document.clone(doc);
+			clone.a = 2;
+			assert.strictEqual(Document.getNestedField(doc, new FieldPath("a.b")), 1);
+
+			// setNestedField and ensure the original document is unchanged.
+			clone = Document.cloneDeep(doc);
+			assert.strictEqual(Document.getNestedField(doc, "a.b"), 1);
 
-		"should return same field and value from cloned Document ": function clonedDocumentSingleFieldValue() {
-			var doc = {"prop1": 17},
-				res = Document.clone(doc);
-			assert(res instanceof Object);
-			assert.deepEqual(doc, res);
-			assert.equal(res.prop1, 17);
+			Document.setNestedField(clone, "a.b", 2);
+
+			assert.strictEqual(Document.getNestedField(doc, "a.b"), 1);
+			assert.strictEqual(Document.getNestedField(clone, "a.b"), 2);
+			assert.deepEqual(doc, {a:{b:1}});
+			assert.deepEqual(clone, {a:{b:2}});
+		},
+
+		"should shallow clone a multi field document": function testCloneMultipleFields() {
+			var doc = {a:1,b:['ra',4],c:{z:1},d:'lal'},
+				clone = Document.clone(doc);
+			assert.deepEqual(doc, clone);
 		},
 
-		"should return same fields and values from cloned Document ": function clonedDocumentMultiFieldValue() {
-			var doc = {"prop1": 17, "prop2": "a string"},
-				res = Document.clone(doc);
-			assert.deepEqual(doc, res);
-			assert(res instanceof Object);
-			assert.equal(res.prop1, 17);
-			assert.equal(res.prop2, "a string");
+	},
+
+	//SKIPPED: FieldIteratorEmpty
+
+	//SKIPPED: FieldIteratorSingle
+
+	//SKIPPED: FieldIteratorMultiple
+
+	".toJson()": {
+
+		"should convert to JSON Object": function() {
+			var doc = {prop1:0};
+			assert.deepEqual(Document.toJson(doc), {prop1:0});
 		},
 
 	},
@@ -93,14 +150,14 @@ exports.Document = {
 	"serialize and deserialize for sorter": {
 
 		"should return a string": function serializeDocument() {
-			var doc = {"prop1":1},
+			var doc = {prop1:1},
 				res = Document.serializeForSorter(doc);
 			assert.equal(res, "{\"prop1\":1}");
 		},
 
 		"should return a Document": function deserializeToDocument() {
 			var str = "{\"prop1\":1}",
-				doc = {"prop1":1},
+				doc = {prop1:1},
 				res = Document.deserializeForSorter(str);
 			assert.deepEqual(res, doc);
 		},