Skip to content
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 @@ -1424,6 +1424,85 @@ Object apply(List<?> expressionValue, Document document) {
}
},

$switch {
@Override
Object apply(Object expressionValue, Document document) {
Document switchDocument = requireDocument(expressionValue, 40060);

// Validate that 'branches' field exists
if (!switchDocument.containsKey("branches")) {
throw new MongoServerError(40068, name() + " requires at least one branch");
}

// Validate unsupported parameters
List<String> supportedKeys = asList("branches", "default");
for (String key : switchDocument.keySet()) {
if (!supportedKeys.contains(key)) {
throw new MongoServerError(40067, name() + " found an unknown argument: " + key);
}
}

// Get and validate branches
Object branchesValue = switchDocument.get("branches");
if (!(branchesValue instanceof Collection<?>)) {
throw new MongoServerError(40061, name() + " expected an array for 'branches', found: " + describeType(branchesValue));
}

Collection<?> branches = (Collection<?>) branchesValue;
if (branches.isEmpty()) {
throw new MongoServerError(40060, name() + " requires at least one branch");
}

// Evaluate each branch
for (Object branchValue : branches) {
if (!(branchValue instanceof Document)) {
throw new MongoServerError(40062, name() + " expected each branch to be an object, found: " + describeType(branchValue));
}

Document branch = (Document) branchValue;

// Validate branch has required fields
if (!branch.containsKey("case")) {
throw new MongoServerError(40064, name() + " requires each branch have a 'case' expression");
}
if (!branch.containsKey("then")) {
throw new MongoServerError(40065, name() + " requires each branch have a 'then' expression");
}

// Validate branch has no extra fields
for (String key : branch.keySet()) {
if (!asList("case", "then").contains(key)) {
throw new MongoServerError(40063, name() + " found an unknown argument to a branch: " + key);
}
}

// Evaluate the case expression
Object caseExpression = branch.get("case");
Object caseResult = evaluate(caseExpression, document);

// If case is true, evaluate and return the then expression
if (Utils.isTrue(caseResult)) {
Object thenExpression = branch.get("then");
return evaluate(thenExpression, document);
}
}

// No case matched, check for default
if (switchDocument.containsKey("default")) {
Object defaultExpression = switchDocument.get("default");
return evaluate(defaultExpression, document);
}

// No case matched and no default provided
throw new MongoServerError(40066, name() + " could not find a matching branch for an input, and no default was specified.");
}

@Override
Object apply(List<?> expressionValue, Document document) {
throw new UnsupportedOperationException("must not be invoked");
}
},

$sqrt {
@Override
Object apply(List<?> expressionValue, Document document) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2733,6 +2733,171 @@ void testProjectWithCondition() throws Exception {
);
}

@Test
void testAggregateWithSwitch() throws Exception {
collection.insertOne(json("_id: 1, name: 'Dave', qty: 1"));
collection.insertOne(json("_id: 2, name: 'Carol', qty: 5"));
collection.insertOne(json("_id: 3, name: 'Bob', qty: 10"));
collection.insertOne(json("_id: 4, name: 'Alice', qty: 20"));

List<Document> pipeline = jsonList("""
$project: {
name: 1,
qtyDiscount: {
$switch: {
branches: [
{ case: { $gte: ['$qty', 10] }, then: 0.15 },
{ case: { $gte: ['$qty', 5] }, then: 0.10 },
{ case: { $gte: ['$qty', 1] }, then: 0.05 }
],
default: 0
}
}
}
""");

assertThat(collection.aggregate(pipeline))
.containsExactlyInAnyOrder(
json("_id: 1, name: 'Dave', qtyDiscount: 0.05"),
json("_id: 2, name: 'Carol', qtyDiscount: 0.10"),
json("_id: 3, name: 'Bob', qtyDiscount: 0.15"),
json("_id: 4, name: 'Alice', qtyDiscount: 0.15")
);
}

@Test
void testAggregateWithSwitchDefault() throws Exception {
collection.insertOne(json("_id: 1, status: 'active'"));
collection.insertOne(json("_id: 2, status: 'inactive'"));
collection.insertOne(json("_id: 3, status: 'unknown'"));

List<Document> pipeline = jsonList("""
$project: {
statusCode: {
$switch: {
branches: [
{ case: { $eq: ['$status', 'active'] }, then: 1 },
{ case: { $eq: ['$status', 'inactive'] }, then: 0 }
],
default: -1
}
}
}
""");

assertThat(collection.aggregate(pipeline))
.containsExactlyInAnyOrder(
json("_id: 1, statusCode: 1"),
json("_id: 2, statusCode: 0"),
json("_id: 3, statusCode: -1")
);
}

@Test
void testAggregateWithSwitchMissingDefault() throws Exception {
collection.insertOne(json("_id: 1, value: 100"));

List<Document> pipeline = jsonList("""
$project: {
result: {
$switch: {
branches: [
{ case: { $eq: ['$value', 50] }, then: 'fifty' }
]
}
}
}
""");

assertThatExceptionOfType(MongoCommandException.class)
.isThrownBy(() -> collection.aggregate(pipeline).first())
.withMessageContaining("$switch could not find a matching branch for an input, and no default was specified");
}

@Test
void testAggregateWithSwitchMissingBranches() throws Exception {
collection.insertOne(json("_id: 1, value: 100"));

List<Document> pipeline = jsonList("""
$project: {
result: {
$switch: {
default: 'none'
}
}
}
""");

assertThatExceptionOfType(MongoCommandException.class)
.isThrownBy(() -> collection.aggregate(pipeline).first())
.withMessageContaining("$switch requires at least one branch");
}

@Test
void testAggregateWithSwitchEmptyBranches() throws Exception {
collection.insertOne(json("_id: 1, value: 100"));

List<Document> pipeline = jsonList("""
$project: {
result: {
$switch: {
branches: [
],
default: 'none'
}
}
}
""");

assertThatExceptionOfType(MongoCommandException.class)
.isThrownBy(() -> collection.aggregate(pipeline).first())
.withMessageContaining("$switch requires at least one branch");
}

@Test
void testAggregateWithSwitchInvalidBranch() throws Exception {
collection.insertOne(json("_id: 1, value: 100"));

List<Document> pipeline = jsonList("""
$project: {
result: {
$switch: {
branches: [
{ case: { $eq: ['$value', 100] } }
],
default: 'none'
}
}
}
""");

assertThatExceptionOfType(MongoCommandException.class)
.isThrownBy(() -> collection.aggregate(pipeline).first())
.withMessageContaining("$switch requires each branch have a 'then' expression");
}

@Test
void testAggregateWithSwitchInvalidArgument() throws Exception {
collection.insertOne(json("_id: 1, value: 100"));

List<Document> pipeline = jsonList("""
$project: {
result: {
$switch: {
branches: [
{ case: { $eq: ['$value', 100] }, then: 'one hundred' }
],
default_value: 'none'
}
}
}
""");

assertThatExceptionOfType(MongoCommandException.class)
.isThrownBy(() -> collection.aggregate(pipeline).first())
.withMessageContaining("$switch found an unknown argument: default_value");
}

// https://github.com/bwaldvogel/mongo-java-server/issues/138
@Test
public void testAggregateWithGeoNear() throws Exception {
Expand Down