Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #184 - Projection Save #689

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
package com.redhat.lightblue.eval;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.redhat.lightblue.crud.CrudConstants;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.metadata.FieldTreeNode;
Expand All @@ -39,6 +41,9 @@
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.metadata.types.Arith;
import com.redhat.lightblue.query.FieldAndRValue;
import com.redhat.lightblue.query.MaskedSetExpression;
import com.redhat.lightblue.query.Projection;
import com.redhat.lightblue.query.ProjectionList;
import com.redhat.lightblue.query.RValueExpression;
import com.redhat.lightblue.query.SetExpression;
import com.redhat.lightblue.query.UpdateOperator;
Expand All @@ -56,6 +61,8 @@ public class SetExpressionEvaluator extends Updater {
private final List<FieldData> setValues = new ArrayList<>();
private final UpdateOperator op;
private final JsonNodeFactory factory;
private JsonDoc project;
private boolean masked;

private static final class FieldData {
/**
Expand All @@ -73,14 +80,12 @@ private static final class FieldData {
private final Type fieldType;

/**
* If the field is to be set from another field, the referenced relative
* path to the source field
* If the field is to be set from another field, the referenced relative path to the source field
*/
private final Path refPath;

/**
* If the field is to be set from another field, the type of the source
* field
* If the field is to be set from another field, the type of the source field
*/
private final Type refType;

Expand Down Expand Up @@ -112,9 +117,15 @@ public FieldData(Path field, Type t, Path refPath, Type refType, RValueExpressio
public SetExpressionEvaluator(JsonNodeFactory factory, FieldTreeNode context, SetExpression expr) {
this.factory = factory;
op = expr.getOp();
List<JsonDoc> docs = new ArrayList<JsonDoc>();
Projector projector = null;
if (expr instanceof MaskedSetExpression) {
MaskedSetExpression mExpr = (MaskedSetExpression) expr;
masked = true;
projector = Projector.getInstance(new ProjectionList(mExpr.getMaskFields()), Path.EMPTY, context);
}
for (FieldAndRValue fld : expr.getFields()) {
Path field = fld.getField();
LOGGER.debug("Parsing setter for {}", field);
RValueExpression rvalue = fld.getRValue();
Path refPath = null;
FieldTreeNode refMdNode = null;
Expand All @@ -127,6 +138,10 @@ public SetExpressionEvaluator(JsonNodeFactory factory, FieldTreeNode context, Se
}
LOGGER.debug("Refpath {}", refPath);
}
if(rvalue.getType() == RValueExpression.RValueType._value && rvalue.getValue().getValue() instanceof ObjectNode && projector != null){
ObjectNode node = (ObjectNode) rvalue.getValue().getValue();
docs.add(projector.project(new JsonDoc(node), factory));
}
FieldTreeNode mdNode = context.resolve(field);
if (mdNode == null) {
throw new EvaluationError(CrudConstants.ERR_CANT_ACCESS + field);
Expand All @@ -141,6 +156,7 @@ public SetExpressionEvaluator(JsonNodeFactory factory, FieldTreeNode context, Se
}
setValues.add(data);
}
project = new JsonDoc(JsonDoc.listToDoc(docs, factory));
}

private FieldData initializeSimple(RValueExpression rvalue, FieldTreeNode refMdNode, FieldTreeNode mdNode, Path field, Path refPath) {
Expand Down Expand Up @@ -196,6 +212,7 @@ public void getUpdateFields(Set<Path> fields) {
@Override
public boolean update(JsonDoc doc, FieldTreeNode contextMd, Path contextPath) {
boolean ret = false;

LOGGER.debug("Starting");
for (FieldData df : setValues) {
LOGGER.debug("Set field {} in ctx: {} to {}/{}", df.field, contextPath, df.value, df.value.getType());
Expand Down Expand Up @@ -240,11 +257,23 @@ private JsonNode setOrAdd(JsonDoc doc, Path contextPath, FieldData df, JsonNode
oldValueNode = doc.get(fieldPath);
if (newValueNode != null && oldValueNode != null) {
newValueNode = df.fieldType.toJson(factory, Arith.add(df.fieldType.fromJson(oldValueNode), newValue, Arith.promote(df.fieldType, newValueType)));
doc.modify(fieldPath, newValueNode, false);
if (masked) {
copyProjection(project, doc, fieldPath);
} else {
doc.modify(fieldPath, newValueNode, false);
}
}
}
return oldValueNode;
}

private void copyProjection(JsonDoc projection, JsonDoc docToModify, Path fieldPath){
Iterator<Entry<String, JsonNode>> fieldsIt = project.getRoot().fields();
while (fieldsIt.hasNext()) {
Entry<String, JsonNode> next = fieldsIt.next();
docToModify.modify(fieldPath.add(new Path(next.getKey())), next.getValue(), false);
}
}

private boolean oldAndNewAreDifferent(JsonNode oldValueNode, JsonNode newValueNode) {
if (oldValueNode == null && newValueNode != null) {
Expand Down
70 changes: 70 additions & 0 deletions crud/src/test/java/com/redhat/lightblue/eval/UpdaterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.redhat.lightblue.metadata.EntityMetadata;
import com.redhat.lightblue.query.UpdateExpression;
Expand Down Expand Up @@ -168,6 +169,75 @@ public void array_insert() throws Exception {
Assert.assertEquals(7, jsonDoc.get(new Path("field6.nf6#")).asInt());
Assert.assertEquals(7, jsonDoc.get(new Path("field6.nf6")).size());
}

@Test
public void array_foreach_set_this() throws Exception {
UpdateExpression expr = EvalTestContext.updateExpressionFromJson("{ '$foreach' : { 'field7' : '$all' , '$update' : {'$set': { '$this': {} } } } }");
Updater updater = Updater.getInstance(JSON_NODE_FACTORY, md, expr);
Assert.assertTrue(updater.update(jsonDoc, md.getFieldTreeRoot(), new Path()));

Assert.assertEquals(4, jsonDoc.get(new Path("field7")).size());
for (JsonNode node : jsonDoc.get(new Path("field7"))) {
Assert.assertEquals(JsonNodeFactory.instance.objectNode(), node);
Assert.assertEquals(0, node.size());
Assert.assertTrue(!node.fields().hasNext());
}
}

@Test
public void array_foreach_set_partial_this() throws Exception {
UpdateExpression expr = EvalTestContext.updateExpressionFromJson(
"{ '$foreach' : { 'field7' : '$all' , '$update' : {'$set': { '$this': {'elemf1': 'NA', 'elemf2': 'NA', 'elemf3': -1 } }, 'fields': [ { 'field': 'elemf2' }, { 'field': 'elemf3' } ] } } }");
Updater updater = Updater.getInstance(JSON_NODE_FACTORY, md, expr);
Assert.assertTrue(updater.update(jsonDoc, md.getFieldTreeRoot(), new Path()));

Assert.assertEquals(4, jsonDoc.get(new Path("field7")).size());
int i = 0;
for (JsonNode node : jsonDoc.get(new Path("field7"))) {
Assert.assertEquals("elvalue" + i + "_1", node.get("elemf1").asText());
Assert.assertEquals("NA", node.get("elemf2").asText());
Assert.assertEquals(-1, node.get("elemf3").asInt());
Assert.assertEquals(3, node.size());
i++;
}
}

@Test
public void array_foreach_set_partial_this_no_fields() throws Exception {
UpdateExpression expr = EvalTestContext.updateExpressionFromJson(
"{ '$foreach' : { 'field7' : '$all' , '$update' : {'$set': { '$this': {'elemf1': 'NA', 'elemf2': 'NA', 'elemf3': -1 } }, 'fields': [ ] } } }");
Updater updater = Updater.getInstance(JSON_NODE_FACTORY, md, expr);
Assert.assertTrue(updater.update(jsonDoc, md.getFieldTreeRoot(), new Path()));

Assert.assertEquals(4, jsonDoc.get(new Path("field7")).size());
int i = 0;
for (JsonNode node : jsonDoc.get(new Path("field7"))) {
Assert.assertEquals("elvalue" + i + "_1", node.get("elemf1").asText());
Assert.assertEquals("elvalue" + i + "_2", node.get("elemf2").asText());
Assert.assertEquals(3 + i, node.get("elemf3").asInt());
Assert.assertEquals(3, node.size());
i++;
}
}

@Test
public void array_foreach_set_partial_this_invalid_fields() throws Exception {
UpdateExpression expr = EvalTestContext.updateExpressionFromJson(
"{ '$foreach' : { 'field7' : '$all' , '$update' : {'$set': { '$this': {'elemf1': 'NA', 'elemf2': 'NA', 'elemf3': -1 } }, 'fields': [ { 'field': 'elemf4' } ] } } }");
// should do nothing
Updater updater = Updater.getInstance(JSON_NODE_FACTORY, md, expr);
Assert.assertTrue(updater.update(jsonDoc, md.getFieldTreeRoot(), new Path()));

Assert.assertEquals(4, jsonDoc.get(new Path("field7")).size());
int i = 0;
for (JsonNode node : jsonDoc.get(new Path("field7"))) {
Assert.assertEquals("elvalue" + i + "_1", node.get("elemf1").asText());
Assert.assertEquals("elvalue" + i + "_2", node.get("elemf2").asText());
Assert.assertEquals(3 + i, node.get("elemf3").asInt());
Assert.assertEquals(3, node.size());
i++;
}
}

@Test
public void array_foreach_removeall() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ protected FieldTreeNode resolve(Path p, int level) {
} else if (name.equals(Path.ANY)) {
throw Error.get(MetadataConstants.ERR_INVALID_ARRAY_REFERENCE, name + " in " + p.toString());
} else if (name.equals(Path.THIS)) {
if (level + 1 >= p.numSegments()) {
return this.parent;
}
return this.resolve(p, level + 1);
} else if (name.equals(Path.PARENT)) {
if (parent != null && !(parent instanceof EntitySchema.RootNode)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.redhat.lightblue.query;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.redhat.lightblue.util.Path;

public class MaskedSetExpression extends SetExpression {
private static final long serialVersionUID = 1L;
private List<FieldProjection> maskFields;

public MaskedSetExpression(UpdateOperator op, List<FieldAndRValue> list, List<FieldProjection> maskFields) {
super(op, list);
this.maskFields = maskFields;
}

public List<FieldProjection> getMaskFields() {
return maskFields;
}

@Override
public JsonNode toJson() {
ArrayNode node = getFactory().arrayNode();
for (FieldProjection x : maskFields) {
node.add(x.toJson());
}

ObjectNode objectNode = getFactory().objectNode();
JsonNode setJson = super.toJson();
if (setJson.has(UpdateOperator._set.toString())) {
objectNode.set(UpdateOperator._set.toString(), setJson.get(UpdateOperator._set.toString()));
} else if (setJson.has(UpdateOperator._add.toString())) {
objectNode.set(UpdateOperator._add.toString(), setJson.get(UpdateOperator._add.toString()));
}
objectNode.set("fields", node);
return objectNode;
}

public static MaskedSetExpression fromJson(ObjectNode node) {
ObjectNode setNode = getFactory().objectNode();
if (node.has(UpdateOperator._set.toString())) {
setNode.set(UpdateOperator._set.toString(), node.get(UpdateOperator._set.toString()));
} else if (node.has(UpdateOperator._add.toString())) {
setNode.set(UpdateOperator._add.toString(), node.get(UpdateOperator._add.toString()));
}
SetExpression fromJson = SetExpression.fromJson(setNode);
List<FieldProjection> mf = new ArrayList<FieldProjection>();
Iterator<JsonNode> nodeIt = node.get("fields").elements();
while (nodeIt.hasNext()) {
JsonNode n = nodeIt.next();
mf.add((FieldProjection) FieldProjection.fromJson(n));
}
return new MaskedSetExpression(fromJson.getOp(), fromJson.getFields(), mf);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package com.redhat.lightblue.query;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.redhat.lightblue.util.Error;

Expand All @@ -39,6 +41,9 @@ public abstract class PrimitiveUpdateExpression extends PartialUpdateExpression
*/
public static PrimitiveUpdateExpression fromJson(ObjectNode node) {
if (node.has(UpdateOperator._add.toString()) || node.has(UpdateOperator._set.toString())) {
if (node.has("fields")) {
return MaskedSetExpression.fromJson(node);
}
return SetExpression.fromJson(node);
} else if (node.has(UpdateOperator._unset.toString())) {
return UnsetExpression.fromJson(node);
Expand Down