Skip to content

Commit

Permalink
Merge pull request #65 from bfink13/issue-64
Browse files Browse the repository at this point in the history
Issue 64
  • Loading branch information
bwaldvogel committed Apr 19, 2019
2 parents 7ced6e9 + e81f0fe commit 623ad99
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import de.bwaldvogel.mongo.exception.ImmutableFieldException;
import de.bwaldvogel.mongo.exception.MongoServerError;
import de.bwaldvogel.mongo.exception.MongoServerException;
import de.bwaldvogel.mongo.exception.PathNotViableException;
import de.bwaldvogel.mongo.exception.TypeMismatchException;

class FieldUpdates {
Expand Down Expand Up @@ -343,8 +344,15 @@ private void handleRename(String key, Object toField) {

private void applyRenames() {
for (Entry<String, String> entry : renames.entrySet()) {
if (!Utils.canFullyTraverseSubkeyForRename(document, entry.getKey())) {
throw new PathNotViableException("cannot traverse element");
}

Object value = Utils.removeSubdocumentValue(document, entry.getKey(), matchPos);
changeSubdocumentValue(document, entry.getValue(), value);

if (!(value instanceof Missing)) {
changeSubdocumentValue(document, entry.getValue(), value);
}
}
}

Expand Down
36 changes: 33 additions & 3 deletions core/src/main/java/de/bwaldvogel/mongo/backend/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,24 @@ static boolean hasSubdocumentValue(Object document, String key) {
}
}

static boolean canFullyTraverseSubkeyForRename(Object document, String key) {
int dotPos = key.indexOf('.');
if (dotPos > 0) {
String mainKey = key.substring(0, dotPos);
String subKey = getSubkey(key, dotPos, new AtomicReference<>());
Object subObject = Utils.getFieldValueListSafe(document, mainKey);
if (subObject instanceof Document) {
return canFullyTraverseSubkeyForRename(subObject, subKey);
} else if (subObject instanceof Missing) {
return true;
} else {
return false;
}
} else {
return true;
}
}

static String getSubkey(String key, int dotPos, AtomicReference<Integer> matchPos) {
String subKey = key.substring(dotPos + 1);

Expand Down Expand Up @@ -321,9 +339,18 @@ private static void setListSafe(Object document, String key, Object obj) {

private static Object removeListSafe(Object document, String key) {
if (document instanceof Document) {
return ((Document) document).remove(key);
if (((Document) document).containsKey(key)) {
return ((Document) document).remove(key);
}
return Missing.getInstance();
} else if (document instanceof List<?>) {
int pos = Integer.parseInt(key);
int pos;
try {
pos = Integer.parseInt(key);
} catch (final NumberFormatException e) {
return Missing.getInstance();
}

@SuppressWarnings("unchecked")
List<Object> list = ((List<Object>) document);
if (list.size() > pos) {
Expand Down Expand Up @@ -390,11 +417,14 @@ private static Object removeSubdocumentValue(Object document, String key, Atomic
if (dotPos > 0) {
String mainKey = key.substring(0, dotPos);
String subKey = getSubkey(key, dotPos, matchPos);

Assert.notNullOrEmpty(subKey);

Object subObject = getFieldValueListSafe(document, mainKey);
if (subObject instanceof Document || subObject instanceof List<?>) {
return removeSubdocumentValue(subObject, subKey, matchPos);
} else {
throw new MongoServerException("failed to remove subdocument");
return Missing.getInstance();
}
} else {
return removeListSafe(document, key);
Expand Down
52 changes: 47 additions & 5 deletions core/src/test/java/de/bwaldvogel/mongo/backend/UtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,21 +207,63 @@ public void testChangeSubdocumentValue() throws Exception {

@Test
public void testRemoveSubdocumentValue() throws Exception {
Document document = json("_id: 1, foo: {bar: 1, bla: 2}");
Document document = json("_id: 1, foo: {bar: 1, bla: 2}, baz: { bar: { a: 1, b: 2 } }");

Object removedValue = Utils.removeSubdocumentValue(document, "foo.bar");
assertThat(removedValue).isEqualTo(1);
assertThat(document).isEqualTo(json("_id: 1, foo: {bla: 2}"));
assertThat(document).isEqualTo(json("_id: 1, foo: {bla: 2}, baz: { bar: { a: 1, b: 2 } }"));

removedValue = Utils.removeSubdocumentValue(document, "foo.bla");
assertThat(removedValue).isEqualTo(2);
assertThat(document).isEqualTo(json("_id: 1, foo: {}"));
assertThat(document).isEqualTo(json("_id: 1, foo: {}, baz: { bar: { a: 1, b: 2 } }"));

removedValue = Utils.removeSubdocumentValue(document, "foo.missing.a");
assertThat(removedValue).isEqualTo(Missing.getInstance());
assertThat(document).isEqualTo(json("_id: 1, foo: {}, baz: { bar: { a: 1, b: 2 } }"));

Utils.changeSubdocumentValue(document, "foo", json("x: [1, 2, 3]"));
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, 2, 3]}"));
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, 2, 3]}, baz: { bar: { a: 1, b: 2 } }"));

Utils.removeSubdocumentValue(document, "foo.x.1");
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, null, 3]}"));
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, null, 3]}, baz: { bar: { a: 1, b: 2 } }"));

Utils.removeSubdocumentValue(document, "foo.x.a");
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, null, 3]}, baz: { bar: { a: 1, b: 2 } }"));

Utils.removeSubdocumentValue(document, "baz.bar.a.z");
assertThat(document).isEqualTo(json("_id: 1, foo: {x: [1, null, 3]}, baz: { bar: { a: 1, b: 2 } }"));
}

@Test
public void testCanFullyTraverseSubkeyForRename() {
Document document = json("_id: 1, foo: {bar: 1, bla: 2}, baz: { bar: [ { a:1, b:2} , 2, 3] }");

boolean ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "foo.bar");
assertThat(ableToTraverse).isTrue();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "foo.bar.missing");
assertThat(ableToTraverse).isFalse();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "foo.missing");
assertThat(ableToTraverse).isTrue();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "baz.bar");
assertThat(ableToTraverse).isTrue();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "baz.bar.0");
assertThat(ableToTraverse).isFalse();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "baz.bar.0.a");
assertThat(ableToTraverse).isFalse();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "baz.bar.foo");
assertThat(ableToTraverse).isFalse();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "missing");
assertThat(ableToTraverse).isTrue();

ableToTraverse = Utils.canFullyTraverseSubkeyForRename(document, "missing.a");
assertThat(ableToTraverse).isTrue();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2335,6 +2335,9 @@ public void testUpdateUnset() throws Exception {
assertMongoWriteException(() -> collection.updateOne(obj, json("$unset: {_id: ''}")),
66, "ImmutableField", "Performing an update on the path '_id' would modify the immutable field '_id'");

collection.updateOne(json("_id: 1"), json("$unset: {'a.b.z':1}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, a: 1, b: null, c: 'value'"));

collection.updateOne(obj, json("$unset: {a:'', b:''}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, c: 'value'"));

Expand All @@ -2345,6 +2348,9 @@ public void testUpdateUnset() throws Exception {

collection.updateOne(json("_id: 1"), json("$unset: {'a.b':1}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, a: {c: 'bar'}"));

collection.updateOne(json("_id: 1"), json("$unset: {'a.b.z':1}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, a: {c: 'bar'}"));
}

@Test
Expand Down Expand Up @@ -3088,6 +3094,31 @@ public void testRenameField() throws Exception {

collection.updateOne(json("_id: 1"), json("$rename: {'bar2': 'foo', foo2: 'bar'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, bar: 'x', foo: 'y'"));

collection.updateOne(json("_id: 1"), json("$rename: {'bar': 'bar2', 'missing': 'foo'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, bar2: 'x', foo: 'y'"));
}

@Test
public void testRenameField_embeddedDocument() {
Document object = json("_id: 1, foo: { a: 1, b: 2 }, bar: { c: 3, d: 4 }}");
collection.insertOne(object);

collection.updateOne(json("_id: 1"), json("$rename: {'foo.a': 'foo.z', 'bar.c': 'bar.x'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, foo: { z: 1, b: 2 }, bar: { x: 3, d: 4 }}"));

collection.updateOne(json("_id: 1"), json("$rename: {'foo.z': 'foo.a', 'bar.a': 'bar.b'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, foo: { a: 1, b: 2 }, bar: { x: 3, d: 4 }}"));

collection.updateOne(json("_id: 1"), json("$rename: {'missing.a': 'missing.b'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, foo: { a: 1, b: 2 }, bar: { x: 3, d: 4 }}"));

collection.updateOne(json("_id: 1"), json("$rename: {'foo.a': 'a', 'bar.x': 'bar.c'}"));
assertThat(collection.find().first()).isEqualTo(json("_id: 1, foo: { b: 2 }, bar: { c: 3, d: 4 }, a: 1}"));

assertThatExceptionOfType(MongoWriteException.class).isThrownBy(
() -> collection.updateOne(json("_id: 1"), json("$rename: {'foo.b.c': 'foo.b.d'}")
));
}

@Test
Expand Down

0 comments on commit 623ad99

Please sign in to comment.