Skip to content

Commit 2d02931

Browse files
craiggwilsonrstam
authored andcommitted
CSHARP-1620: added support for $zip.
1 parent 90689e4 commit 2d02931

File tree

8 files changed

+194
-1
lines changed

8 files changed

+194
-1
lines changed

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal enum ExtensionExpressionType
3838
Take,
3939
Union,
4040
Where,
41+
Zip,
4142

4243
// Bindings
4344
Accumulator,

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionVisitor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,12 @@ protected internal virtual Expression VisitWhere(WhereExpression node)
224224
Visit(node.Source),
225225
Visit(node.Predicate));
226226
}
227+
228+
protected internal virtual Expression VisitZip(ZipExpression node)
229+
{
230+
return node.Update(
231+
Visit(node.Source),
232+
Visit(node.Other));
233+
}
227234
}
228235
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* Copyright 2015 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq.Expressions;
18+
using MongoDB.Driver.Core.Misc;
19+
20+
namespace MongoDB.Driver.Linq.Expressions
21+
{
22+
internal sealed class ZipExpression : ExtensionExpression, ISourcedExpression
23+
{
24+
private readonly Expression _other;
25+
private readonly Expression _source;
26+
private readonly Type _type;
27+
28+
public ZipExpression(Type type, Expression source, Expression other)
29+
{
30+
_type = Ensure.IsNotNull(type, nameof(type));
31+
_source = Ensure.IsNotNull(source, nameof(source));
32+
_other = Ensure.IsNotNull(other, nameof(other));
33+
}
34+
35+
public override ExtensionExpressionType ExtensionType
36+
{
37+
get { return ExtensionExpressionType.Zip; }
38+
}
39+
40+
public Expression Other
41+
{
42+
get { return _other; }
43+
}
44+
45+
public Expression Source
46+
{
47+
get { return _source; }
48+
}
49+
50+
public override Type Type
51+
{
52+
get { return _type; }
53+
}
54+
55+
public override string ToString()
56+
{
57+
return string.Format("{0}.Zip({1})", _source.ToString(), _other.ToString());
58+
}
59+
60+
public ZipExpression Update(Expression source, Expression other)
61+
{
62+
if (source != _source ||
63+
other != _other)
64+
{
65+
return new ZipExpression(_type, source, other);
66+
}
67+
68+
return this;
69+
}
70+
71+
protected internal override Expression Accept(ExtensionExpressionVisitor visitor)
72+
{
73+
return visitor.VisitZip(this);
74+
}
75+
}
76+
}

src/MongoDB.Driver/Linq/Processors/EmbeddedPipeline/EmbeddedPipelineBinder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static EmbeddedPipelineBinder()
5454
infoBinder.Register(new ToListBinder(), ToListBinder.GetSupportedMethods());
5555
infoBinder.Register(new UnionBinder(), UnionBinder.GetSupportedMethods());
5656
infoBinder.Register(new WhereBinder(), WhereBinder.GetSupportedMethods());
57+
infoBinder.Register(new ZipBinder(), ZipBinder.GetSupportedMethods());
5758

5859
var nameBinder = new NameBasedMethodCallBinder<EmbeddedPipelineBindingContext>();
5960
nameBinder.Register(new ContainsBinder(), ContainsBinder.IsSupported, ContainsBinder.SupportedMethodNames);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* Copyright 2015 MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Linq.Expressions;
20+
using System.Reflection;
21+
using MongoDB.Bson.Serialization;
22+
using MongoDB.Bson.Serialization.Serializers;
23+
using MongoDB.Driver.Linq.Expressions;
24+
25+
namespace MongoDB.Driver.Linq.Processors.EmbeddedPipeline.MethodCallBinders
26+
{
27+
internal sealed class ZipBinder : IMethodCallBinder<EmbeddedPipelineBindingContext>
28+
{
29+
public static IEnumerable<MethodInfo> GetSupportedMethods()
30+
{
31+
yield return MethodHelper.GetMethodDefinition(() => Enumerable.Zip<object, object, object>(null, null, null));
32+
yield return MethodHelper.GetMethodDefinition(() => Queryable.Zip<object, object, object>(null, null, null));
33+
}
34+
35+
public Expression Bind(PipelineExpression pipeline, EmbeddedPipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable<Expression> arguments)
36+
{
37+
var source = new ZipExpression(
38+
node.Type,
39+
pipeline.Source,
40+
arguments.First());
41+
42+
var lambda = ExpressionHelper.GetLambda(arguments.Last());
43+
var input = new DocumentExpression(bindingContext.GetSerializer(typeof(object[]), null));
44+
45+
var itemA = new ArrayIndexExpression(
46+
input,
47+
Expression.Constant(0),
48+
bindingContext.GetSerializer(lambda.Parameters[0].Type, source.Source));
49+
var itemB = new ArrayIndexExpression(
50+
input,
51+
Expression.Constant(1),
52+
bindingContext.GetSerializer(lambda.Parameters[1].Type, source.Other));
53+
54+
bindingContext.AddExpressionMapping(lambda.Parameters[0], itemA);
55+
bindingContext.AddExpressionMapping(lambda.Parameters[1], itemB);
56+
57+
var selector = bindingContext.Bind(lambda.Body);
58+
59+
var serializer = bindingContext.GetSerializer(selector.Type, selector);
60+
61+
return new PipelineExpression(
62+
new SelectExpression(
63+
source,
64+
lambda.Parameters[0].Name + "_" + lambda.Parameters[1].Name,
65+
selector),
66+
new DocumentExpression(serializer));
67+
}
68+
}
69+
}

src/MongoDB.Driver/Linq/Translators/AggregateLanguageTranslator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ private BsonValue TranslateValue(Expression node)
133133
return TranslateUnion((UnionExpression)node);
134134
case ExtensionExpressionType.Where:
135135
return TranslateWhere((WhereExpression)node);
136+
case ExtensionExpressionType.Zip:
137+
return TranslateZip((ZipExpression)node);
136138
}
137139
}
138140
break;
@@ -553,6 +555,13 @@ private BsonValue TranslateWhere(WhereExpression node)
553555
});
554556
}
555557

558+
private BsonValue TranslateZip(ZipExpression node)
559+
{
560+
var inputs = new[] { TranslateValue(node.Source), TranslateValue(node.Other) };
561+
562+
return new BsonDocument("$zip", new BsonDocument("inputs", new BsonArray(inputs)));
563+
}
564+
556565
private bool TryTranslateAvgResultOperator(PipelineExpression node, out BsonValue result)
557566
{
558567
var resultOperator = node.ResultOperator as AverageResultOperator;

src/MongoDB.Driver/MongoDB.Driver.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<Compile Include="Linq\Expressions\IntersectExpression.cs" />
125125
<Compile Include="Linq\Expressions\SampleExpression.cs" />
126126
<Compile Include="Linq\Expressions\ReverseExpression.cs" />
127+
<Compile Include="Linq\Expressions\ZipExpression.cs" />
127128
<Compile Include="Linq\Expressions\UnionExpression.cs" />
128129
<Compile Include="Linq\Expressions\WhereExpression.cs" />
129130
<Compile Include="Linq\Expressions\ISerializationExpression.cs" />
@@ -167,6 +168,7 @@
167168
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\TakeBinder.cs" />
168169
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\ToHashSetBinder.cs" />
169170
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\ReverseBinder.cs" />
171+
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\ZipBinder.cs" />
170172
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\UnionBinder.cs" />
171173
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\IntersectBinder.cs" />
172174
<Compile Include="Linq\Processors\EmbeddedPipeline\MethodCallBinders\ExceptBinder.cs" />
@@ -453,4 +455,4 @@
453455
<Target Name="AfterBuild">
454456
</Target>
455457
-->
456-
</Project>
458+
</Project>

tests/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,34 @@ public void Should_translate_year()
13351335
result.Value.Result.Should().Be(2012);
13361336
}
13371337

1338+
[SkippableFact]
1339+
public void Should_translate_zip_with_operation()
1340+
{
1341+
RequireServer.Where(minimumVersion: "3.3.4");
1342+
1343+
var result = Project(x => new { Result = x.M.Zip(x.O, (a, b) => a + b) });
1344+
1345+
result.Projection.Should().Be("{ Result: { \"$map\": { input: { \"$zip\": { inputs: [\"$M\", \"$O\"] } }, as: \"a_b\", in: { $add: [{ $arrayElemAt: [\"$$a_b\", 0] }, { $arrayElemAt: [\"$$a_b\", 1] }] } } }, _id: 0 }");
1346+
1347+
result.Value.Result.Should().BeEquivalentTo(12L, 24L, 35L);
1348+
}
1349+
1350+
[SkippableFact]
1351+
public void Should_translate_zip_with_anonymous_type()
1352+
{
1353+
RequireServer.Where(minimumVersion: "3.3.4");
1354+
1355+
var result = Project(x => new { Result = x.M.Zip(x.O, (a, b) => new { a, b }) });
1356+
1357+
result.Projection.Should().Be("{ Result: { \"$map\": { input: { \"$zip\": { inputs: [\"$M\", \"$O\"] } }, as: \"a_b\", in: { a: { $arrayElemAt: [\"$$a_b\", 0] }, b: { $arrayElemAt: [\"$$a_b\", 1] } } } }, _id: 0 }");
1358+
1359+
var aResults = result.Value.Result.Select(x => x.a);
1360+
var bResults = result.Value.Result.Select(x => x.b);
1361+
1362+
aResults.Should().BeEquivalentTo(2, 4, 5);
1363+
bResults.Should().BeEquivalentTo(10L, 20L, 30L);
1364+
}
1365+
13381366
[Fact]
13391367
public void Should_translate_array_projection()
13401368
{

0 commit comments

Comments
 (0)