From 9e665eb25b18226c1c71e3b90f7ac4a4794e3a12 Mon Sep 17 00:00:00 2001 From: Minh Le Date: Sun, 10 Sep 2023 23:39:28 -0700 Subject: [PATCH 1/7] Add support for translation to REgexmatch --- .../BuiltinFunctionVisitor.cs | 7 +++ .../StringBuiltinFunctions.cs | 45 +++++++++++++++++++ .../src/Linq/CosmosLinqExtensions.cs | 24 ++++++++++ .../SqlFunctionCallScalarExpression.cs | 1 + 4 files changed, 77 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs index e3814ba1fd..d7e79b5c50 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; + using System.Collections.Generic; using System.Globalization; using System.Linq.Expressions; using Microsoft.Azure.Cosmos; @@ -49,6 +50,12 @@ public static SqlScalarExpression VisitBuiltinFunctionCall(MethodCallExpression if (methodCallExpression.Method.DeclaringType.GeUnderlyingSystemType() == typeof(CosmosLinqExtensions)) { + // CosmosLinq Extensions are either RegexMatch or Type check functions (IsString, IsBool, etc.) + if (methodCallExpression.Method.Name == nameof(CosmosLinqExtensions.RegexMatch)) + { + return StringBuiltinFunctions.Visit(methodCallExpression, context); + } + return TypeCheckFunctions.Visit(methodCallExpression, context); } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs index b04bd58c3c..e3bc343cc3 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs @@ -327,6 +327,47 @@ protected override SqlScalarExpression VisitExplicit(MethodCallExpression method } } + + private class RegexMatchVisitor : SqlBuiltinFunctionVisitor + { + public RegexMatchVisitor() + : base(SqlFunctionCallScalarExpression.Names.RegexMatch, + isStatic: true, + new List() + { + new Type[]{typeof(string)}, + new Type[]{typeof(string), typeof(string)} + }) + { } + + protected override SqlScalarExpression VisitImplicit(MethodCallExpression methodCallExpression, TranslationContext context) + { + int argumentCount = methodCallExpression.Arguments.Count; + if (argumentCount == 0 || argumentCount > 2 || (methodCallExpression.Arguments[1].NodeType != ExpressionType.Constant)) + { + return null; + } + + List arguments = new List + { + ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Object, context), + ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[0], context) + }; + + if (argumentCount > 1 && (methodCallExpression.Arguments[2].NodeType == ExpressionType.Constant)) + { + arguments.Add(ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[1], context)); + } + + return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.RegexMatch, arguments.ToArray()); + } + + protected override SqlScalarExpression VisitExplicit(MethodCallExpression methodCallExpression, TranslationContext context) + { + return null; + } + } + private class StringVisitToString : SqlBuiltinFunctionVisitor { public StringVisitToString() @@ -432,6 +473,10 @@ static StringBuiltinFunctions() "TrimStart", new StringVisitTrimStart() }, + { + nameof(CosmosLinqExtensions.RegexMatch), + new RegexMatchVisitor() + }, { "Replace", new SqlBuiltinFunctionVisitor(SqlFunctionCallScalarExpression.Names.Replace, diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index 6663f52673..45bd44fc5a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq; using System.Linq.Expressions; using System.Reflection; + using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Diagnostics; @@ -175,6 +176,29 @@ public static bool IsString(this object obj) throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); } + /// + /// Returns a Boolean value indicating if the specified expression matches the supplied regex pattern. + /// Azure Cosmos DB for NoSQL uses PERL compatible regualr expressions (PCRE). + /// For more information, see https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/regexmatch. + /// This method is to be used in LINQ expressions only and will be evaluated on server. + /// There's no implementation provided in the client library. + /// + /// + /// A string expression with a regular expression defined to use when searching. + /// An optional string expression with the selected modifiers to use with the regular expression. + /// Returns true if the string matches the regex expressions; otherwise, false. + /// + /// + /// document.Name.RegexMatch(, )); + /// ]]> + /// + /// + public static bool RegexMatch(this object obj, string regularExpression, [Optional] string searchModifier) + { + throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); + } + /// /// This method generate query definition from LINQ query. /// diff --git a/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs b/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs index bcb38257c6..1197d0ac07 100644 --- a/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs +++ b/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs @@ -332,6 +332,7 @@ public static class Names public const string Power = "POWER"; public const string Radians = "RADIANS"; public const string Rand = "RAND"; + public const string RegexMatch = "RegexMatch"; public const string Replace = "REPLACE"; public const string Replicate = "REPLICATE"; public const string Reverse = "REVERSE"; From 803b32888833a0dc5fd33178ec3a121ee73f2b35 Mon Sep 17 00:00:00 2001 From: Minh Le Date: Mon, 11 Sep 2023 00:14:57 -0700 Subject: [PATCH 2/7] Add test and fix some indexing issues --- .../StringBuiltinFunctions.cs | 19 ++++++++------- .../src/Linq/CosmosLinqExtensions.cs | 24 ++++++++++++++++++- .../SqlFunctionCallScalarExpression.cs | 2 ++ .../LinqTranslationBaselineTests.cs | 17 +++++++++++++ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs index e3bc343cc3..29e3710101 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs @@ -327,7 +327,6 @@ protected override SqlScalarExpression VisitExplicit(MethodCallExpression method } } - private class RegexMatchVisitor : SqlBuiltinFunctionVisitor { public RegexMatchVisitor() @@ -335,28 +334,30 @@ public RegexMatchVisitor() isStatic: true, new List() { - new Type[]{typeof(string)}, - new Type[]{typeof(string), typeof(string)} + new Type[]{ typeof(object), typeof(string)}, + new Type[]{ typeof(object), typeof(string), typeof(string)} }) - { } + { + } protected override SqlScalarExpression VisitImplicit(MethodCallExpression methodCallExpression, TranslationContext context) { int argumentCount = methodCallExpression.Arguments.Count; - if (argumentCount == 0 || argumentCount > 2 || (methodCallExpression.Arguments[1].NodeType != ExpressionType.Constant)) + if (argumentCount == 0 || argumentCount > 3 || (methodCallExpression.Arguments[1].NodeType != ExpressionType.Constant)) { return null; } List arguments = new List { - ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Object, context), - ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[0], context) + // Argument 0 and the Method object is the same + ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[0], context), + ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[1], context) }; - if (argumentCount > 1 && (methodCallExpression.Arguments[2].NodeType == ExpressionType.Constant)) + if (argumentCount > 2 && (methodCallExpression.Arguments[2].NodeType == ExpressionType.Constant)) { - arguments.Add(ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[1], context)); + arguments.Add(ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[2], context)); } return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.RegexMatch, arguments.ToArray()); diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index 45bd44fc5a..d63ef0e520 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -176,6 +176,28 @@ public static bool IsString(this object obj) throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); } + /// + /// Returns a Boolean value indicating if the specified expression matches the supplied regex pattern. + /// Azure Cosmos DB for NoSQL uses PERL compatible regualr expressions (PCRE). + /// For more information, see https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/regexmatch. + /// This method is to be used in LINQ expressions only and will be evaluated on server. + /// There's no implementation provided in the client library. + /// + /// + /// A string expression with a regular expression defined to use when searching. + /// Returns true if the string matches the regex expressions; otherwise, false. + /// + /// + /// document.Name.RegexMatch()); + /// ]]> + /// + /// + public static bool RegexMatch(this object obj, string regularExpression) + { + throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); + } + /// /// Returns a Boolean value indicating if the specified expression matches the supplied regex pattern. /// Azure Cosmos DB for NoSQL uses PERL compatible regualr expressions (PCRE). @@ -194,7 +216,7 @@ public static bool IsString(this object obj) /// ]]> /// /// - public static bool RegexMatch(this object obj, string regularExpression, [Optional] string searchModifier) + public static bool RegexMatch(this object obj, string regularExpression, string searchModifier) { throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); } diff --git a/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs b/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs index 1197d0ac07..651ad5726d 100644 --- a/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs +++ b/Microsoft.Azure.Cosmos/src/SqlObjects/SqlFunctionCallScalarExpression.cs @@ -112,6 +112,7 @@ sealed class SqlFunctionCallScalarExpression : SqlScalarExpression { Names.Power, Identifiers.Power }, { Names.Radians, Identifiers.Radians }, { Names.Rand, Identifiers.Rand }, + { Names.RegexMatch, Identifiers.RegexMatch }, { Names.Replace, Identifiers.Replace }, { Names.Replicate, Identifiers.Replicate }, { Names.Reverse, Identifiers.Reverse }, @@ -475,6 +476,7 @@ public static class Identifiers public static readonly SqlIdentifier Power = SqlIdentifier.Create(Names.Power); public static readonly SqlIdentifier Radians = SqlIdentifier.Create(Names.Radians); public static readonly SqlIdentifier Rand = SqlIdentifier.Create(Names.Rand); + public static readonly SqlIdentifier RegexMatch = SqlIdentifier.Create(Names.RegexMatch); public static readonly SqlIdentifier Replace = SqlIdentifier.Create(Names.Replace); public static readonly SqlIdentifier Replicate = SqlIdentifier.Create(Names.Replicate); public static readonly SqlIdentifier Reverse = SqlIdentifier.Create(Names.Reverse); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index 4c8d6c423f..352f4675ae 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -292,6 +292,23 @@ public void TestTypeCheckFunctions() this.ExecuteTestSuite(inputs); } + [TestMethod] + public void TestRegexMatchFunction() + { + // Similar to the type checking function, RegexMatch are not supported client side. + // Therefore this method is verified with baseline only. + List data = new List(); + IOrderedQueryable query = testContainer.GetItemLinqQueryable(allowSynchronousQueryExecution: true); + Func> getQuery = useQuery => useQuery ? query : data.AsQueryable(); + + List inputs = new List + { + new LinqTestInput("RegexMatch with 1 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd"))), + new LinqTestInput("RegexMatch with 2 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd", "i"))), + }; + this.ExecuteTestSuite(inputs); + } + [TestMethod] public void TestMemberInitializer() { From f1d97a995fb180788429e6742e5d38c49af5c48e Mon Sep 17 00:00:00 2001 From: Minh Le Date: Mon, 11 Sep 2023 00:23:25 -0700 Subject: [PATCH 3/7] remove visit explicit, add some comment. Update public contract and added the baseline for the test --- .../StringBuiltinFunctions.cs | 11 +- ...slationBaselineTests.TestStringCompare.xml | 120 +++++++----------- .../Contracts/DotNetSDKAPI.json | 14 ++ 3 files changed, 62 insertions(+), 83 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs index 29e3710101..7d833a03bd 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/StringBuiltinFunctions.cs @@ -334,8 +334,8 @@ public RegexMatchVisitor() isStatic: true, new List() { - new Type[]{ typeof(object), typeof(string)}, - new Type[]{ typeof(object), typeof(string), typeof(string)} + new Type[]{ typeof(object), typeof(string)}, // search string, regex pattern + new Type[]{ typeof(object), typeof(string), typeof(string)} // search string, regex pattern, search modifier }) { } @@ -350,7 +350,7 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method List arguments = new List { - // Argument 0 and the Method object is the same + // Argument 0 and the Method object is the same, since Regex is an extension method ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[0], context), ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Arguments[1], context) }; @@ -362,11 +362,6 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method return SqlFunctionCallScalarExpression.CreateBuiltin(SqlFunctionCallScalarExpression.Names.RegexMatch, arguments.ToArray()); } - - protected override SqlScalarExpression VisitExplicit(MethodCallExpression methodCallExpression, TranslationContext context) - { - return null; - } } private class StringVisitToString : SqlBuiltinFunctionVisitor diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringCompare.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringCompare.xml index 17538ff1d8..e737413eed 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringCompare.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestStringCompare.xml @@ -5,11 +5,9 @@ (Compare(doc.StringField, doc.StringField2) == 0))]]> - - - + @@ -18,11 +16,9 @@ FROM root]]> (Compare(doc.StringField, doc.StringField2) > 0))]]> - - root["StringField2"]) -FROM root]]> - + root["StringField2"]) +FROM root]]> @@ -31,11 +27,9 @@ FROM root]]> (Compare(doc.StringField, doc.StringField2) >= 0))]]> - - = root["StringField2"]) -FROM root]]> - + = root["StringField2"]) +FROM root]]> @@ -44,11 +38,9 @@ FROM root]]> (Compare(doc.StringField, doc.StringField2) < 0))]]> - - - + @@ -57,11 +49,9 @@ FROM root]]> (Compare(doc.StringField, doc.StringField2) <= 0))]]> - - - + @@ -70,11 +60,9 @@ FROM root]]> (Compare(doc.StringField, "str") == 0))]]> - - - + @@ -83,11 +71,9 @@ FROM root]]> (Compare(doc.StringField, "str") > 0))]]> - - "str") -FROM root]]> - + "str") +FROM root]]> @@ -96,11 +82,9 @@ FROM root]]> (Compare(doc.StringField, "str") >= 0))]]> - - = "str") -FROM root]]> - + = "str") +FROM root]]> @@ -109,11 +93,9 @@ FROM root]]> (Compare(doc.StringField, "str") < 0))]]> - - - + @@ -122,11 +104,9 @@ FROM root]]> (Compare(doc.StringField, "str") <= 0))]]> - - - + @@ -135,11 +115,9 @@ FROM root]]> (0 == Compare(doc.StringField, doc.StringField2)))]]> - - - + @@ -148,11 +126,9 @@ FROM root]]> (0 < Compare(doc.StringField, doc.StringField2)))]]> - - root["StringField2"]) -FROM root]]> - + root["StringField2"]) +FROM root]]> @@ -161,11 +137,9 @@ FROM root]]> (0 <= Compare(doc.StringField, doc.StringField2)))]]> - - = root["StringField2"]) -FROM root]]> - + = root["StringField2"]) +FROM root]]> @@ -174,11 +148,9 @@ FROM root]]> (0 > Compare(doc.StringField, doc.StringField2)))]]> - - - + @@ -187,11 +159,9 @@ FROM root]]> (0 >= Compare(doc.StringField, doc.StringField2)))]]> - - - + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 759762f21f..2ba2523435 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -5367,6 +5367,20 @@ ], "MethodInfo": "Boolean IsString(System.Object);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Boolean RegexMatch(System.Object, System.String, System.String)[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "Type": "Method", + "Attributes": [ + "ExtensionAttribute" + ], + "MethodInfo": "Boolean RegexMatch(System.Object, System.String, System.String);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Boolean RegexMatch(System.Object, System.String)[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "Type": "Method", + "Attributes": [ + "ExtensionAttribute" + ], + "MethodInfo": "Boolean RegexMatch(System.Object, System.String);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.FeedIterator ToStreamIterator[T](System.Linq.IQueryable`1[T])[System.Runtime.CompilerServices.ExtensionAttribute()]": { "Type": "Method", "Attributes": [ From 661d4fc6aabaeb29052c0f994068f07fb27c2f90 Mon Sep 17 00:00:00 2001 From: Minh Le Date: Mon, 11 Sep 2023 02:02:14 -0700 Subject: [PATCH 4/7] add the missing baseline --- ...onBaselineTests.TestRegexMatchFunction.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml new file mode 100644 index 0000000000..f114938183 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml @@ -0,0 +1,26 @@ + + + + + doc.StringField.RegexMatch("abcd"))]]> + + + + + + + + + doc.StringField.RegexMatch("abcd", "i"))]]> + + + + + + \ No newline at end of file From 3be866c01f68cad38d326857e3d8af33341eee79 Mon Sep 17 00:00:00 2001 From: Minh Le Date: Tue, 26 Sep 2023 11:41:22 -0700 Subject: [PATCH 5/7] added test --- .../src/Linq/CosmosLinqExtensions.cs | 2 - ...onBaselineTests.TestRegexMatchFunction.xml | 72 +++++++++++++++++++ .../LinqTranslationBaselineTests.cs | 7 ++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index d63ef0e520..619c1c1ecf 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -178,7 +178,6 @@ public static bool IsString(this object obj) /// /// Returns a Boolean value indicating if the specified expression matches the supplied regex pattern. - /// Azure Cosmos DB for NoSQL uses PERL compatible regualr expressions (PCRE). /// For more information, see https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/regexmatch. /// This method is to be used in LINQ expressions only and will be evaluated on server. /// There's no implementation provided in the client library. @@ -200,7 +199,6 @@ public static bool RegexMatch(this object obj, string regularExpression) /// /// Returns a Boolean value indicating if the specified expression matches the supplied regex pattern. - /// Azure Cosmos DB for NoSQL uses PERL compatible regualr expressions (PCRE). /// For more information, see https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/regexmatch. /// This method is to be used in LINQ expressions only and will be evaluated on server. /// There's no implementation provided in the client library. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml index f114938183..ce1374e24c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml @@ -23,4 +23,76 @@ FROM root WHERE RegexMatch(root["StringField"], "abcd", "i")]]> + + + + doc.StringField.RegexMatch(doc.StringField2))]]> + + + + + + + + + doc.StringField.RegexMatch(doc.IntField.ToString(), "this should error out on the back end"))]]> + + + + + + + + + doc.StringField.RegexMatch(doc.StringField2.ToUpper(), "this should error out on the back end"))]]> + + + + + + + + + doc.StringField.RegexMatch(doc.StringField2.ToLower(), "this should error out on the back end"))]]> + + + + + + + + + doc.StringField.RegexMatch(Concat(doc.StringField, "str"), "this should error out on the back end"))]]> + + + + + + + + + doc.StringField.RegexMatch("abcd", "this should error out on the back end"))]]> + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs index 352f4675ae..bccfcc1672 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationBaselineTests.cs @@ -305,6 +305,13 @@ public void TestRegexMatchFunction() { new LinqTestInput("RegexMatch with 1 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd"))), new LinqTestInput("RegexMatch with 2 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd", "i"))), + new LinqTestInput("RegexMatch with 1st argument member expression", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2))), + new LinqTestInput("RegexMatch with ToString", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.IntField.ToString() , "this should error out on the back end"))), + new LinqTestInput("RegexMatch with StringUpper", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToUpper() , "this should error out on the back end"))), + new LinqTestInput("RegexMatch with StringLower", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToLower() , "this should error out on the back end"))), + new LinqTestInput("RegexMatch with StringConcat", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(string.Concat(doc.StringField, "str") , "this should error out on the back end"))), + + new LinqTestInput("RegexMatch with 2nd argument invalid string options", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd", "this should error out on the back end"))), }; this.ExecuteTestSuite(inputs); } From 289241a5dbda9a0297879c8aa2b1b56495493f11 Mon Sep 17 00:00:00 2001 From: Minh Le Date: Wed, 27 Sep 2023 13:16:26 -0700 Subject: [PATCH 6/7] address code review --- ...onBaselineTests.TestRegexMatchFunction.xml | 88 +++++++++++++++++-- .../Linq/LinqTranslationBaselineTests.cs | 16 +++- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml index ce1374e24c..03f808f8b5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestRegexMatchFunction.xml @@ -38,49 +38,121 @@ WHERE RegexMatch(root["StringField"], root["StringField2"])]]> - doc.StringField.RegexMatch(doc.IntField.ToString(), "this should error out on the back end"))]]> + doc.StringField.RegexMatch(doc.IntField.ToString()))]]> +WHERE RegexMatch(root["StringField"], ToString(root["IntField"]))]]> - doc.StringField.RegexMatch(doc.StringField2.ToUpper(), "this should error out on the back end"))]]> + doc.StringField.RegexMatch(doc.StringField2.ToUpper()))]]> +WHERE RegexMatch(root["StringField"], UPPER(root["StringField2"]))]]> - doc.StringField.RegexMatch(doc.StringField2.ToLower(), "this should error out on the back end"))]]> + doc.StringField.RegexMatch(doc.StringField2.ToLower()))]]> +WHERE RegexMatch(root["StringField"], LOWER(root["StringField2"]))]]> - doc.StringField.RegexMatch(Concat(doc.StringField, "str"), "this should error out on the back end"))]]> + doc.StringField.RegexMatch(Concat(doc.StringField, "str")))]]> +WHERE RegexMatch(root["StringField"], CONCAT(root["StringField"], "str"))]]> + + + + + + doc.IntField.ToString().RegexMatch(doc.StringField))]]> + + + + + + + + + doc.IntField.ToString().RegexMatch(doc.StringField, doc.StringField2.ToString()))]]> + + + + + + + + + (doc.StringField.RegexMatch("abc") AndAlso doc.StringField2.RegexMatch("def")))]]> + + + + + + + + + (doc.StringField.RegexMatch("abc") OrElse doc.StringField2.RegexMatch("def")))]]> + + + + + + + + + doc.StringField.RegexMatch("abc")).Where(doc => doc.StringField2.RegexMatch("abc"))]]> + + + + + + + + + doc.StringField.RegexMatch("abc")).Where(doc => Not(doc.StringField2.RegexMatch("abc")))]]> + + + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs index e73ec6b1a7..f13a92da82 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTranslationBaselineTests.cs @@ -305,10 +305,18 @@ public void TestRegexMatchFunction() new LinqTestInput("RegexMatch with 1 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd"))), new LinqTestInput("RegexMatch with 2 argument", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd", "i"))), new LinqTestInput("RegexMatch with 1st argument member expression", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2))), - new LinqTestInput("RegexMatch with ToString", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.IntField.ToString() , "this should error out on the back end"))), - new LinqTestInput("RegexMatch with StringUpper", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToUpper() , "this should error out on the back end"))), - new LinqTestInput("RegexMatch with StringLower", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToLower() , "this should error out on the back end"))), - new LinqTestInput("RegexMatch with StringConcat", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(string.Concat(doc.StringField, "str") , "this should error out on the back end"))), + new LinqTestInput("RegexMatch with ToString", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.IntField.ToString()))), + new LinqTestInput("RegexMatch with StringUpper", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToUpper()))), + new LinqTestInput("RegexMatch with StringLower", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(doc.StringField2.ToLower()))), + new LinqTestInput("RegexMatch with StringConcat", b => getQuery(b).Where(doc => doc.StringField.RegexMatch(string.Concat(doc.StringField, "str")))), + + new LinqTestInput("RegexMatch with string composition", b => getQuery(b).Where(doc => doc.IntField.ToString().RegexMatch(doc.StringField))), + new LinqTestInput("RegexMatch with string composition 2", b => getQuery(b).Where(doc => doc.IntField.ToString().RegexMatch(doc.StringField, doc.StringField2.ToString()))), + + new LinqTestInput("RegexMatch with conditional", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abc") && doc.StringField2.RegexMatch("def"))), + new LinqTestInput("RegexMatch with conditional 2", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abc") || doc.StringField2.RegexMatch("def"))), + new LinqTestInput("RegexMatch with conditional 3", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abc")).Where(doc => doc.StringField2.RegexMatch("abc"))), + new LinqTestInput("RegexMatch with conditional 4", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abc")).Where(doc => !doc.StringField2.RegexMatch("abc"))), new LinqTestInput("RegexMatch with 2nd argument invalid string options", b => getQuery(b).Where(doc => doc.StringField.RegexMatch("abcd", "this should error out on the back end"))), }; From 4aca46d56133673c8cec30b804252e8dc20c826a Mon Sep 17 00:00:00 2001 From: Minh Le Date: Thu, 28 Sep 2023 15:34:52 -0700 Subject: [PATCH 7/7] update csproj --- .../Microsoft.Azure.Cosmos.EmulatorTests.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 8011b07351..30c29ec5db 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -202,6 +202,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest