diff --git a/README.md b/README.md index 1e72507068c..1231b7291b2 100644 --- a/README.md +++ b/README.md @@ -133,5 +133,7 @@ Please see our [guidelines](CONTRIBUTING.md) for contributing to the driver. * Vyacheslav Stroy https://github.com/kreig * Testo test1@doramail.com * Zhmayev Yaroslav https://github.com/salaros +* Alexey Skalozub https://github.com/pieceofsummer@gmail +* Elliott Millar https://github.com/elliott-pd If you have contributed and we have neglected to add you to this list please contact one of the maintainers to be added to the list (with apologies). \ No newline at end of file diff --git a/src/MongoDB.Driver.Core/Core/Connections/KeepAliveValues.cs b/src/MongoDB.Driver.Core/Core/Connections/KeepAliveValues.cs index 66734108034..f093f5c5901 100644 --- a/src/MongoDB.Driver.Core/Core/Connections/KeepAliveValues.cs +++ b/src/MongoDB.Driver.Core/Core/Connections/KeepAliveValues.cs @@ -26,7 +26,7 @@ internal struct KeepAliveValues public byte[] ToBytes() { // set the tcp_keepalive struct at the following page for documentation of the buffer layout - /// https://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx var bytes = new byte[24]; Array.Copy(BitConverter.GetBytes(OnOff), 0, bytes, 0, 8); Array.Copy(BitConverter.GetBytes(KeepAliveTime), 0, bytes, 8, 8); diff --git a/src/MongoDB.Driver/Linq/Translators/AggregateLanguageTranslator.cs b/src/MongoDB.Driver/Linq/Translators/AggregateLanguageTranslator.cs index 8686f2dd268..9ac80d63bda 100644 --- a/src/MongoDB.Driver/Linq/Translators/AggregateLanguageTranslator.cs +++ b/src/MongoDB.Driver/Linq/Translators/AggregateLanguageTranslator.cs @@ -20,7 +20,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; using MongoDB.Bson; using MongoDB.Driver.Linq.Expressions; using MongoDB.Driver.Linq.Expressions.ResultOperators; @@ -89,6 +88,9 @@ private BsonValue TranslateValue(Expression node) case ExpressionType.Multiply: case ExpressionType.MultiplyChecked: return TranslateOperation((BinaryExpression)node, "$multiply", true); + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + return TranslateNegate((UnaryExpression)node); case ExpressionType.New: return TranslateNew((NewExpression)node); case ExpressionType.NewArrayInit: @@ -342,6 +344,14 @@ private BsonValue TranslateMemberAccess(MemberExpression node) return new BsonDocument("$size", TranslateValue(node.Expression)); } + // + result = TranslateValue(node.Expression); + if (result.BsonType == BsonType.String && result.AsString.StartsWith("$")) + { + // this looks like variable access expression + return BsonValue.Create(result.AsString + "." + node.Member.Name); + } + var message = string.Format("Member {0} of type {1} in the expression tree {2} cannot be translated.", node.Member.Name, node.Member.DeclaringType, @@ -388,7 +398,7 @@ private BsonValue TranslateMethodCall(MethodCallExpression node) } if (node.Object.Type == typeof(DateTime) - && TryTranslateDateTimeCall(node, out result)) + && TryTranslateDateTimeMethodCall(node, out result)) { return result; } @@ -437,6 +447,11 @@ private BsonValue TranslateNew(NewExpression node) return TranslateMapping(mapping); } + private BsonValue TranslateNegate(UnaryExpression node) + { + var operand = TranslateValue(node.Operand); + return new BsonDocument("$subtract", new BsonArray(new BsonValue[] { 0, operand })); + } private BsonValue TranslateNewArrayInit(NewArrayExpression node) { var bsonArray = new BsonArray(); @@ -804,69 +819,46 @@ private bool TryTranslateCountResultOperator(PipelineExpression node, out BsonVa result = null; return false; } - - private bool TryTranslateDateTimeCall(MethodCallExpression node, out BsonValue result) - { - result = null; - var field = TranslateValue(node.Object); - - switch (node.Method.Name) - { - case "ToString": - if (node.Arguments.Count == 1) - { - var format = TranslateValue(node.Arguments[0]); - result = new BsonDocument("$dateToString", new BsonDocument - { - { "format", format }, - { "date", field } - }); - return true; - } - break; - } - - return false; - } - + private bool TryTranslateDateTimeMemberAccess(MemberExpression node, out BsonValue result) { result = null; - var field = TranslateValue(node.Expression); + var date = TranslateValue(node.Expression); + switch (node.Member.Name) { case "Day": - result = new BsonDocument("$dayOfMonth", field); + result = new BsonDocument("$dayOfMonth", date); return true; case "DayOfWeek": // The server's day of week values are 1 greater than // .NET's DayOfWeek enum values result = new BsonDocument("$subtract", new BsonArray { - new BsonDocument("$dayOfWeek", field), + new BsonDocument("$dayOfWeek", date), (BsonInt32)1 }); return true; case "DayOfYear": - result = new BsonDocument("$dayOfYear", field); + result = new BsonDocument("$dayOfYear", date); return true; case "Hour": - result = new BsonDocument("$hour", field); + result = new BsonDocument("$hour", date); return true; case "Millisecond": - result = new BsonDocument("$millisecond", field); + result = new BsonDocument("$millisecond", date); return true; case "Minute": - result = new BsonDocument("$minute", field); + result = new BsonDocument("$minute", date); return true; case "Month": - result = new BsonDocument("$month", field); + result = new BsonDocument("$month", date); return true; case "Second": - result = new BsonDocument("$second", field); + result = new BsonDocument("$second", date); return true; case "Year": - result = new BsonDocument("$year", field); + result = new BsonDocument("$year", date); return true; } @@ -890,6 +882,94 @@ private bool TryTranslateStringMemberAccess(MemberExpression node, out BsonValue return false; } + private bool TryTranslateDateTimeAddXXX(MethodCallExpression node, long multiplier, out BsonValue result) + { + result = null; + if (node.Arguments.Count != 1) + { + // All DateTime.AddXXX() functions take single argument + return false; + } + + var date = TranslateValue(node.Object); + BsonValue arg; + + var expr = node.Arguments[0]; + if (expr.NodeType == ExpressionType.Constant) + { + // argument value is constant + // no need to multiply on server, do it right away + double value = (double)((ConstantExpression)expr).Value; + + // trim to long + long value_ms = (long)Math.Round(value * multiplier); + + arg = BsonValue.Create(value_ms); + } + else + { + // value derived from another expression + // will be calculated on the server + arg = TranslateValue(expr); + + if (multiplier != 1) + { + arg = new BsonDocument("$multiply", new BsonArray(new[] { multiplier, arg })); + } + } + + result = new BsonDocument("$add", new BsonArray(new[] { date, arg })); + return true; + } + + private bool TryTranslateDateTimeMethodCall(MethodCallExpression node, out BsonValue result) + { + switch (node.Method.Name) + { + case "ToString": + if (node.Arguments.Count <= 1) + { + // $dateToString + var field = TranslateValue(node.Object); + // default format YYYY-MM-DD HH:mm + var format = node.Arguments.Count == 1 ? TranslateValue(node.Arguments[0]) : "%Y-%m-%d %H:%M"; + result = new BsonDocument("$dateToString", new BsonDocument + { + { "format", format }, + { "date", field } + }); + return true; + } + break; + case "AddYears": + case "AddMonths": + // adding years/months has potential issues with leap years, + // so don't bother with them for now + throw new NotSupportedException("Adding months/years is not supported"); + + case "AddDays": + return TryTranslateDateTimeAddXXX(node, 24 * 60 * 60 * 1000, out result); + case "AddHours": + return TryTranslateDateTimeAddXXX(node, 60 * 60 * 1000, out result); + case "AddMinutes": + return TryTranslateDateTimeAddXXX(node, 60 * 1000, out result); + case "AddSeconds": + return TryTranslateDateTimeAddXXX(node, 1000, out result); + case "AddMilliseconds": + return TryTranslateDateTimeAddXXX(node, 1, out result); + + case "Add": + case "Subtract": + // TimeSpan is not mapped into BsonValue, + // so don't bother with it for now + break; + } + + // other methods are not supported + result = null; + return false; + } + private bool TryTranslateFirstResultOperator(PipelineExpression node, out BsonValue result) { @@ -1020,29 +1100,37 @@ private bool TryTranslateStaticEnumerableMethodCall(MethodCallExpression node, o private bool TryTranslateStaticMathMethodCall(MethodCallExpression node, out BsonValue result) { result = null; + if (node.Arguments.Count == 0) + { + // None of these methods take no args + return false; + } + + var field = TranslateValue(node.Arguments[0]); + switch (node.Method.Name) { case "Abs": - result = new BsonDocument("$abs", TranslateValue(node.Arguments[0])); + result = new BsonDocument("$abs", field); return true; case "Ceiling": - result = new BsonDocument("$ceil", TranslateValue(node.Arguments[0])); + result = new BsonDocument("$ceil", field); return true; case "Exp": result = new BsonDocument("$exp", new BsonArray { - TranslateValue(node.Arguments[0]) + field }); return true; case "Floor": - result = new BsonDocument("$floor", TranslateValue(node.Arguments[0])); + result = new BsonDocument("$floor", field); return true; case "Log": if (node.Arguments.Count == 2) { result = new BsonDocument("$log", new BsonArray { - TranslateValue(node.Arguments[0]), + field, TranslateValue(node.Arguments[1]) }); } @@ -1050,51 +1138,132 @@ private bool TryTranslateStaticMathMethodCall(MethodCallExpression node, out Bso { result = new BsonDocument("$ln", new BsonArray { - TranslateValue(node.Arguments[0]) + field }); } return true; case "Log10": result = new BsonDocument("$log10", new BsonArray { - TranslateValue(node.Arguments[0]) + field }); return true; case "Pow": + if (node.Arguments.Count == 2) + { result = new BsonDocument("$pow", new BsonArray { - TranslateValue(node.Arguments[0]), + field, TranslateValue(node.Arguments[1]) }); return true; + } + break; case "Sqrt": result = new BsonDocument("$sqrt", new BsonArray { - TranslateValue(node.Arguments[0]) + field }); return true; case "Truncate": - result = new BsonDocument("$trunc", TranslateValue(node.Arguments[0])); + result = new BsonDocument("$trunc", field); return true; } return false; } + private BsonValue StringEqualsHelper(BsonValue a, BsonValue b, Expression stringComparisonExpr) + { + StringComparison comparisonType = StringComparison.Ordinal; + + if (stringComparisonExpr != null && stringComparisonExpr.NodeType == ExpressionType.Constant) + { + // has comparison type specified + comparisonType = (StringComparison)((ConstantExpression)stringComparisonExpr).Value; + } + + switch (comparisonType) + { + case StringComparison.OrdinalIgnoreCase: + // $strcasecmp returns -1/0/1, so additional operation is needed: + return new BsonDocument("$eq", new BsonArray(new[] + { + new BsonDocument("$strcasecmp", new BsonArray(new[] { a, b })), + (BsonValue)0 + })); + case StringComparison.Ordinal: + return new BsonDocument("$eq", new BsonArray(new[] { a, b })); + default: + throw new NotSupportedException("Only Ordinal and OrdinalIgnoreCase are supported for string comparisons."); + } + } + private bool TryTranslateStaticStringMethodCall(MethodCallExpression node, out BsonValue result) { result = null; + if (node.Arguments.Count == 0) + { + // None of these methods take no args + return false; + } + + var field = TranslateValue(node.Arguments[0]); switch (node.Method.Name) { case "IsNullOrEmpty": - var field = TranslateValue(node.Arguments[0]); - result = new BsonDocument("$or", + if (node.Arguments.Count == 1) + { + result = new BsonDocument("$or", new BsonArray { - new BsonDocument("$eq", new BsonArray { field, BsonNull.Value }), - new BsonDocument("$eq", new BsonArray { field, BsonString.Empty }) + new BsonDocument("$eq", new BsonArray + { + field, + BsonNull.Value + }), + new BsonDocument("$eq", new BsonArray + { + field, + BsonString.Empty + }) }); - return true; + return true; + } + break; + case "Equals": + if (node.Arguments.Count >= 2 && node.Arguments.Count <= 3) + { + result = StringEqualsHelper(field, + TranslateValue(node.Arguments[1]), + (node.Arguments.Count == 3) ? node.Arguments[2] : null); + return true; + } + break; + case "Concat": + if (node.Arguments.Count == 1 && node.Arguments[0].Type == typeof(string[])) + { + // this is .Concat(params string[] args) overload + result = new BsonDocument("$concat", field); + return true; + } + else if (node.Arguments.Count >= 2 && node.Arguments.Count <= 4 && + node.Arguments.All(p => p.Type == typeof(string))) + { + // this is one of .Concat(string, string[, string[, string]]) overloads + var array = new BsonArray(); + foreach (var arg in node.Arguments) + { + array.Add(TranslateValue(arg)); + } + + result = new BsonDocument("$concat", array); + return true; + } + else + { + throw new NotSupportedException("Some overloads of String.Concat() are not supported"); + } } return false; @@ -1104,6 +1273,7 @@ private bool TryTranslateStringMethodCall(MethodCallExpression node, out BsonVal { result = null; var field = TranslateValue(node.Object); + switch (node.Method.Name) { case "Equals": @@ -1130,12 +1300,10 @@ private bool TryTranslateStringMethodCall(MethodCallExpression node, out BsonVal break; case "IndexOf": var indexOfArgs = new BsonArray { field }; - if (node.Arguments.Count < 1 || node.Arguments.Count > 3) { return false; } - if (node.Arguments[0].Type != typeof(char) && node.Arguments[0].Type != typeof(string)) { return false; @@ -1146,34 +1314,28 @@ private bool TryTranslateStringMethodCall(MethodCallExpression node, out BsonVal value = new BsonString(new string((char)value.AsInt32, 1)); } indexOfArgs.Add(value); - if (node.Arguments.Count > 1) { if (node.Arguments[1].Type != typeof(int)) { return false; } - var startIndex = TranslateValue(node.Arguments[1]); indexOfArgs.Add(startIndex); } - if (node.Arguments.Count > 2) { if (node.Arguments[2].Type != typeof(int)) { return false; } - var count = TranslateValue(node.Arguments[2]); var endIndex = new BsonDocument("$add", new BsonArray { indexOfArgs[2], count }); indexOfArgs.Add(endIndex); } - var indexOpName = _stringTranslationMode == AggregateStringTranslationMode.CodePoints ? "$indexOfCP" : "$indexOfBytes"; - result = new BsonDocument(indexOpName, indexOfArgs); return true; case "Split": @@ -1215,17 +1377,17 @@ private bool TryTranslateStringMethodCall(MethodCallExpression node, out BsonVal }); return true; case "Substring": - if (node.Arguments.Count == 2) + var substrOpName = _stringTranslationMode == AggregateStringTranslationMode.CodePoints ? + "$substrCP" : + "$substr"; + if (node.Arguments.Count == 2 || node.Arguments.Count == 1) { - var substrOpName = _stringTranslationMode == AggregateStringTranslationMode.CodePoints ? - "$substrCP" : - "$substr"; result = new BsonDocument(substrOpName, new BsonArray(new[] - { - field, - TranslateValue(node.Arguments[0]), - TranslateValue(node.Arguments[1]) - })); + { + field, + TranslateValue(node.Arguments[0]), + node.Arguments.Count == 2 ? TranslateValue(node.Arguments[1]) : -1 + })); return true; } break; diff --git a/tests/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs index 62b741dead5..6dc223b3130 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Translators/AggregateProjectTranslatorTests.cs @@ -17,12 +17,10 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Threading.Tasks; using FluentAssertions; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.TestHelpers.XunitExtensions; -using MongoDB.Driver.Core; using MongoDB.Driver.Core.TestHelpers.XunitExtensions; using MongoDB.Driver.Linq; using MongoDB.Driver.Linq.Translators; @@ -429,6 +427,18 @@ public void Should_translate_dateToString() result.Value.Result.Should().Be("2012-12-01"); } + [SkippableFact] + public void Should_translate_dateToString_without_format() + { + RequireServer.Check().VersionGreaterThanOrEqualTo("3.2.0"); + + var result = Project(x => new { Result = x.J.ToString() }); + + result.Projection.Should().Be("{ Result: { \"$dateToString\": {format: \"%Y-%m-%d %H:%M\", date: \"$J\" } }, _id: 0 }"); + + result.Value.Result.Should().Be("2012-12-01 13:14"); + } + [Fact] public void Should_translate_day_of_month() { @@ -459,6 +469,46 @@ public void Should_translate_day_of_year() result.Value.Result.Should().Be(336); } + [Fact] + public void Should_translate_datetime_add_xxx() + { + var result = Project(x => new { J = x.J, Result = x.J.AddDays(2.5) }); + + result.Projection.Should().Be("{ J: \"$J\", Result: { \"$add\": [\"$J\", NumberLong(216000000)] }, _id: 0 }"); + + result.Value.Result.Should().Be(result.Value.J.AddDays(2.5)); + } + + [Fact] + public void Should_translate_datetime_add_xxx_negative() + { + var result = Project(x => new { J = x.J, Result = x.J.AddMinutes(-3) }); + + result.Projection.Should().Be("{ J: \"$J\", Result: { \"$add\": [\"$J\", NumberLong(-180000)] }, _id: 0 }"); + + result.Value.Result.Should().Be(result.Value.J.AddMinutes(-3)); + } + + [Fact] + public void Should_translate_datetime_add_xxx_from_variable() + { + var result = Project(x => new { J = x.J, N = x.C.E.F, Result = x.J.AddHours(x.C.E.F) }); + + result.Projection.Should().Be("{ J: \"$J\", N: \"$C.E.F\", Result: { \"$add\": [\"$J\", { \"$multiply\": [NumberLong(3600000), \"$C.E.F\"] } ] }, _id: 0 }"); + + result.Value.Result.Should().Be(result.Value.J.AddHours(result.Value.N)); + } + + [Fact] + public void Should_translate_datetime_add_xxx_negative_from_variable() + { + var result = Project(x => new { J = x.J, N = x.C.E.H, Result = x.J.AddHours(-x.C.E.H) }); + + result.Projection.Should().Be("{ J: \"$J\", N: \"$C.E.H\", Result: { \"$add\": [\"$J\", { \"$multiply\": [NumberLong(3600000), { \"$subtract\": [ 0, \"$C.E.H\" ] } ] } ] }, _id: 0 }"); + + result.Value.Result.Should().Be(result.Value.J.AddHours(-result.Value.N)); + } + [Fact] public void Should_translate_divide() { @@ -814,6 +864,16 @@ public void Should_translate_multiply_flattened() result.Value.Result.Should().Be(2420); } + + [Fact] + public void Should_translate_negation() + { + var result = Project(x => new { Result = -x.C.E.F }); + + result.Projection.Should().Be("{ Result: { \"$subtract\": [0, \"$C.E.F\"] }, _id: 0 }"); + + result.Value.Result.Should().Be(-11); + } [SkippableFact] public void Should_translate_new_DateTime() @@ -1327,7 +1387,7 @@ public void Should_translate_string_equals() result.Value.Result.Should().BeTrue(); } - + [Fact] public void Should_translate_string_equals_using_comparison() { @@ -1337,7 +1397,7 @@ public void Should_translate_string_equals_using_comparison() result.Value.Result.Should().BeTrue(); } - + [Fact] public void Should_translate_string_case_insensitive_equals() { @@ -1362,6 +1422,50 @@ public void Should_throw_for_a_not_supported_string_comparison_type(StringCompar act.ShouldThrow(); } + [Fact] + public void Should_translate_static_string_equals() + { + var result = Project(x => new { Result = string.Equals(x.B, "Balloon") }); + + result.Projection.Should().Be("{ Result: { \"$eq\": [\"$B\", \"Balloon\"] }, _id: 0 }"); + + result.Value.Result.Should().BeTrue(); + } + + [Fact] + public void Should_translate_static_string_equals_using_comparison() + { + var result = Project(x => new { Result = string.Equals(x.B, "Balloon", StringComparison.Ordinal) }); + + result.Projection.Should().Be("{ Result: { \"$eq\": [\"$B\", \"Balloon\"] }, _id: 0 }"); + + result.Value.Result.Should().BeTrue(); + } + + [Fact] + public void Should_translate_static_string_case_insensitive_equals() + { + var result = Project(x => new { Result = string.Equals(x.B, "balloon", StringComparison.OrdinalIgnoreCase) }); + + result.Projection.Should().Be("{ Result: { \"$eq\": [{ \"$strcasecmp\": [\"$B\", \"balloon\"] }, 0] }, _id: 0 }"); + + result.Value.Result.Should().BeTrue(); + } + + [Theory] + [InlineData(StringComparison.CurrentCulture)] + [InlineData(StringComparison.CurrentCultureIgnoreCase)] +#if NET45 + [InlineData(StringComparison.InvariantCulture)] + [InlineData(StringComparison.InvariantCultureIgnoreCase)] +#endif + public void Should_throw_for_a_not_supported_static_string_comparison_type(StringComparison comparison) + { + Action act = () => Project(x => new { Result = string.Equals(x.B, "balloon", comparison) }); + + act.ShouldThrow(); + } + [Fact] public void Should_translate_string_is_null_or_empty() { @@ -1371,7 +1475,103 @@ public void Should_translate_string_is_null_or_empty() result.Value.Result.Should().BeFalse(); } + + [Fact] + public void Should_translate_string_concat_with_2_strings() + { + var result = Project(x => new { Result = string.Concat(x.A, x.B) }); + + result.Projection.Should().Be("{ Result: { \"$concat\": [\"$A\", \"$B\"] }, _id: 0 }"); + + result.Value.Result.Should().Be("AwesomeBalloon"); + } + + [Fact] + public void Should_translate_string_concat_with_3_strings() + { + var result = Project(x => new { Result = string.Concat(x.B, "&", x.B) }); + + result.Projection.Should().Be("{ Result: { \"$concat\": [\"$B\", \"&\", \"$B\"] }, _id: 0 }"); + + result.Value.Result.Should().Be("Balloon&Balloon"); + } + + [Fact] + public void Should_translate_string_concat_with_4_strings() + { + var result = Project(x => new { Result = string.Concat(x.A, " ", x.B, "!") }); + + result.Projection.Should().Be("{ Result: { \"$concat\": [\"$A\", \" \", \"$B\", \"!\"] }, _id: 0 }"); + + result.Value.Result.Should().Be("Awesome Balloon!"); + } + + [Fact] + public void Should_translate_string_concat_with_params_string_array() + { + var result = Project(x => new { Result = string.Concat(x.A, "+", x.B, "+", x.C.D) }); + + result.Projection.Should().Be("{ Result: { \"$concat\": [\"$A\", \"+\", \"$B\", \"+\", \"$C.D\"] }, _id: 0 }"); + + result.Value.Result.Should().Be("Awesome+Balloon+Dexter"); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_enumerable_of_string() + { + Action act = () => Project(x => new { Result = string.Concat(x.C.E.I) }); + + act.ShouldThrow(); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_1_object() + { + Action act = () => Project(x => new { Result = string.Concat((object)x.A) }); + + act.ShouldThrow(); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_2_objects() + { + Action act = () => Project(x => new { Result = string.Concat(x.A, x.C) }); + + act.ShouldThrow(); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_3_objects() + { + Action act = () => Project(x => new { Result = string.Concat(x.C, x.C, x.C) }); + + act.ShouldThrow(); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_4_objects() + { + Action act = () => Project(x => new { Result = string.Concat(x.C, x.B, x.C, x.C) }); + + act.ShouldThrow(); + } + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_params_object_array() + { + Action act = () => Project(x => new { Result = string.Concat(x.A, 1, x.C, false, x.G) }); + + act.ShouldThrow(); + } + + [Fact] + public void Should_throw_for_a_not_supported_string_concat_with_generic_enumerable() + { + Action act = () => Project(x => new { Result = string.Concat(x.G) }); + + act.ShouldThrow(); + } + [Fact] public void Should_translate_substr() { @@ -1394,6 +1594,16 @@ public void Should_translate_substrCP() result.Value.Result.Should().Be("loon"); } + [Fact] + public void Should_translate_substring_with_1_arg() + { + var result = Project(x => new { Result = x.B.Substring(3) }); + + result.Projection.Should().Be("{ Result: { \"$substr\": [\"$B\", 3, -1] }, _id: 0 }"); + + result.Value.Result.Should().Be("loon"); + } + [Fact] public void Should_translate_subtract() {