Browse Source

DATAMONGO-1326 - Add support for $lookup to aggregation.

Original Pull Request: #344
CLA: 164120160221125037 (Alessio Fachechi)
pull/347/head
Alessio Fachechi 10 years ago committed by Christoph Strobl
parent
commit
78e99e6df2
  1. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AdditionalFieldsExposingAggregationOperation.java
  2. 28
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
  3. 32
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java
  4. 76
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java
  5. 58
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AdditionalFieldsExposingAggregationOperation.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
/**
* {@link AggregationOperation} that exposes <b>additional</b> {@link ExposedFields} that can be used for later
* aggregation pipeline {@code AggregationOperation}s, e.g. lookup operation produces a field which has to be added to
* the current ones.
*
* @author Alessio Fachechi
*/
public interface AdditionalFieldsExposingAggregationOperation extends FieldsExposingAggregationOperation {
}

28
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java

@ -41,6 +41,7 @@ import com.mongodb.DBObject; @@ -41,6 +41,7 @@ import com.mongodb.DBObject;
* @author Tobias Trelle
* @author Thomas Darimont
* @author Oliver Gierke
* @author Alessio Fachechi
* @since 1.3
*/
public class Aggregation {
@ -270,12 +271,32 @@ public class Aggregation { @@ -270,12 +271,32 @@ public class Aggregation {
return new MatchOperation(criteria);
}
/**
* Creates a new {@link LookupOperation} for the given fields.
*
* @param fields must not be {@literal null}.
* @return
*/
public static LookupOperation lookup(String from, String localField, String foreignField, String as) {
return lookup(field(from), field(localField), field(foreignField), field(as));
}
/**
* Creates a new {@link LookupOperation} for the given {@link Fields}.
*
* @param fields must not be {@literal null}.
* @return
*/
public static LookupOperation lookup(Field from, Field localField, Field foreignField, Field as) {
return new LookupOperation(from, localField, foreignField, as);
}
/**
* Creates a new {@link Fields} instance for the given field names.
*
* @see Fields#fields(String...)
* @param fields must not be {@literal null}.
* @return
* @see Fields#fields(String...)
*/
public static Fields fields(String... fields) {
return Fields.fields(fields);
@ -331,8 +352,11 @@ public class Aggregation { @@ -331,8 +352,11 @@ public class Aggregation {
operationDocuments.add(operation.toDBObject(context));
if (operation instanceof FieldsExposingAggregationOperation) {
boolean additional = operation instanceof AdditionalFieldsExposingAggregationOperation ? true : false;
FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation;
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), rootContext);
context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), rootContext,
additional);
}
}

32
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java

@ -17,7 +17,9 @@ package org.springframework.data.mongodb.core.aggregation; @@ -17,7 +17,9 @@ package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DBObject;
@ -27,12 +29,14 @@ import com.mongodb.DBObject; @@ -27,12 +29,14 @@ import com.mongodb.DBObject;
*
* @author Thomas Darimont
* @author Oliver Gierke
* @author Alessio Fachechi
* @since 1.4
*/
class ExposedFieldsAggregationOperationContext implements AggregationOperationContext {
private final ExposedFields exposedFields;
private final AggregationOperationContext rootContext;
private final boolean additional;
/**
* Creates a new {@link ExposedFieldsAggregationOperationContext} from the given {@link ExposedFields}. Uses the given
@ -41,13 +45,29 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo @@ -41,13 +45,29 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
* @param exposedFields must not be {@literal null}.
* @param rootContext must not be {@literal null}.
*/
public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields, AggregationOperationContext rootContext) {
public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields,
AggregationOperationContext rootContext) {
this(exposedFields, rootContext, false);
}
/**
* Creates a new {@link ExposedFieldsAggregationOperationContext} from the given {@link ExposedFields}. Uses the given
* {@link AggregationOperationContext} to perform a mapping to mongo types if necessary.
*
* @param exposedFields must not be {@literal null}.
* @param rootContext must not be {@literal null}.
* @param additional {@literal true} if the context exposes new fields in addition to the previous ones, e.g. in the
* case of a lookup operation, {@literal false} otherwise.
*/
public ExposedFieldsAggregationOperationContext(ExposedFields exposedFields, AggregationOperationContext rootContext,
boolean additional) {
Assert.notNull(exposedFields, "ExposedFields must not be null!");
Assert.notNull(rootContext, "RootContext must not be null!");
this.exposedFields = exposedFields;
this.rootContext = rootContext;
this.additional = additional;
}
/*
@ -112,6 +132,16 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo @@ -112,6 +132,16 @@ class ExposedFieldsAggregationOperationContext implements AggregationOperationCo
}
}
if (additional) {
// if no exposed fields found propagate to root context.
if (field != null) {
return rootContext.getReference(field);
} else if (StringUtils.hasText(name)) {
return rootContext.getReference(name);
}
}
throw new IllegalArgumentException(String.format("Invalid reference '%s'!", name));
}
}

76
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Encapsulates the aggregation framework {@code $lookup}-operation.
* We recommend to use the static factory method {@link Aggregation#lookup(String, String, String, String)} instead of
* creating instances of this class directly.
*
* @author Alessio Fachechi
* @see http://docs.mongodb.org/manual/reference/aggregation/lookup/#stage._S_lookup
* @since 1.9
*/
public class LookupOperation implements AdditionalFieldsExposingAggregationOperation {
private ExposedField from;
private ExposedField localField;
private ExposedField foreignField;
private ExposedField as;
/**
* Creates a new {@link LookupOperation} for the given {@link Field}s.
*
* @param from must not be {@literal null}.
* @param localField must not be {@literal null}.
* @param foreignField must not be {@literal null}.
* @param as must not be {@literal null}.
*/
public LookupOperation(Field from, Field localField, Field foreignField, Field as) {
Assert.notNull(from, "From must not be null!");
Assert.notNull(localField, "LocalField must not be null!");
Assert.notNull(foreignField, "ForeignField must not be null!");
Assert.notNull(as, "As must not be null!");
this.from = new ExposedField(from, true);
this.localField = new ExposedField(localField, true);
this.foreignField = new ExposedField(foreignField, true);
this.as = new ExposedField(as, true);
}
@Override
public ExposedFields getFields() {
return ExposedFields.from(as);
}
@Override
public DBObject toDBObject(AggregationOperationContext context) {
BasicDBObject lookupObject = new BasicDBObject();
lookupObject.append("from", from.getTarget());
lookupObject.append("localField", localField.getTarget());
lookupObject.append("foreignField", foreignField.getTarget());
lookupObject.append("as", as.getTarget());
return new BasicDBObject("$lookup", lookupObject);
}
}

58
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import com.mongodb.DBObject;
import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
/**
* Unit tests for {@link LookupOperation}.
*
* @author Alessio Fachechi
*/
public class LookupOperationUnitTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsNullFields() {
new LookupOperation((Field) null, (Field) null, (Field) null, (Field) null);
}
@Test
public void lookupOperationWithValues() {
LookupOperation lookupOperation = Aggregation.lookup("a", "b", "c", "d");
DBObject lookupClause = extractDbObjectFromLookupOperation(lookupOperation);
assertThat((String) lookupClause.get("from"), is(new String("a")));
assertThat((String) lookupClause.get("localField"), is(new String("b")));
assertThat((String) lookupClause.get("foreignField"), is(new String("c")));
assertThat((String) lookupClause.get("as"), is(new String("d")));
assertThat(lookupOperation.getFields().exposesNoFields(), is(false));
assertThat(lookupOperation.getFields().exposesSingleFieldOnly(), is(true));
}
private DBObject extractDbObjectFromLookupOperation(LookupOperation lookupOperation) {
DBObject dbObject = lookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject lookupClause = DBObjectTestUtils.getAsDBObject(dbObject, "$lookup");
return lookupClause;
}
}
Loading…
Cancel
Save