diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/ArrayBuiltinFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/ArrayBuiltinFunctions.cs
index d96ac56a22..19ff8a7701 100644
--- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/ArrayBuiltinFunctions.cs
+++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/ArrayBuiltinFunctions.cs
@@ -42,10 +42,13 @@ public ArrayContainsVisitor()
{
}
+ public bool UsePartialMatchParameter { get; set; }
+
protected override SqlScalarExpression VisitImplicit(MethodCallExpression methodCallExpression, TranslationContext context)
{
Expression searchList = null;
Expression searchExpression = null;
+ Expression partialMatchExpression = null;
// If non static Contains
if (methodCallExpression.Arguments.Count == 1)
@@ -59,6 +62,13 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method
searchList = methodCallExpression.Arguments[0];
searchExpression = methodCallExpression.Arguments[1];
}
+ // if CosmosLinqExtensions.ArrayContains extension method which includes partial match parameter
+ else if (this.UsePartialMatchParameter && methodCallExpression.Arguments.Count == 3)
+ {
+ searchList = methodCallExpression.Arguments[0];
+ searchExpression = methodCallExpression.Arguments[1];
+ partialMatchExpression = methodCallExpression.Arguments[2];
+ }
if (searchList == null || searchExpression == null)
{
@@ -72,7 +82,20 @@ protected override SqlScalarExpression VisitImplicit(MethodCallExpression method
SqlScalarExpression array = ExpressionToSql.VisitScalarExpression(searchList, context);
SqlScalarExpression expression = ExpressionToSql.VisitScalarExpression(searchExpression, context);
- return SqlFunctionCallScalarExpression.CreateBuiltin("ARRAY_CONTAINS", array, expression);
+
+ SqlScalarExpression[] arrayContainsArgs;
+
+ if (partialMatchExpression is null)
+ {
+ arrayContainsArgs = new[] { array, expression };
+ }
+ else
+ {
+ SqlScalarExpression partialMatch = ExpressionToSql.VisitScalarExpression(partialMatchExpression, context);
+ arrayContainsArgs = new[] { array, expression, partialMatch };
+ }
+
+ return SqlFunctionCallScalarExpression.CreateBuiltin("ARRAY_CONTAINS", arrayContainsArgs);
}
private SqlScalarExpression VisitIN(Expression expression, ConstantExpression constantExpressionList, TranslationContext context)
@@ -177,6 +200,10 @@ static ArrayBuiltinFunctions()
{
"ToList",
new ArrayToArrayVisitor()
+ },
+ {
+ nameof(CosmosLinqExtensions.ArrayContains),
+ new ArrayContainsVisitor() { UsePartialMatchParameter = true }
}
};
}
diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs
index 82f427c206..d4083fe6a0 100644
--- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs
+++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs
@@ -59,7 +59,12 @@ public static SqlScalarExpression VisitBuiltinFunctionCall(MethodCallExpression
if (methodCallExpression.Method.Name == nameof(CosmosLinqExtensions.DocumentId))
{
return OtherBuiltinSystemFunctions.Visit(methodCallExpression, context);
- }
+ }
+
+ if (methodCallExpression.Method.Name == nameof(CosmosLinqExtensions.ArrayContains))
+ {
+ return ArrayBuiltinFunctions.Visit(methodCallExpression, context);
+ }
return TypeCheckFunctions.Visit(methodCallExpression, context);
}
diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
index a45b282d15..514566196e 100644
--- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
+++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
@@ -5,6 +5,7 @@
namespace Microsoft.Azure.Cosmos.Linq
{
using System;
+ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -237,6 +238,62 @@ public static bool RegexMatch(this object obj, string regularExpression, string
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}
+ ///
+ /// Returns a boolean indicating whether the array contains the specified value.
+ /// You can check for a partial or full match of an object by using a boolean expression within the function.
+ /// For more information, see https://learn.microsoft.com/en-gb/azure/cosmos-db/nosql/query/array-contains.
+ /// 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.
+ ///
+ ///
+ /// The value to search within the array.
+ /// Returns true if the array array contains the specified value; otherwise, false.
+ ///
+ ///
+ /// document.Namess.ArrayContains(, ));
+ /// // To do a partial match on an array of objects, pass in an anonymous object set partialMatch to true
+ /// var matched = documents.Where(document => document.ObjectArray.ArrayContains(new { Name = }, true));
+ /// ]]>
+ ///
+ ///
+ public static bool ArrayContains(this IEnumerable obj, object itemToMatch)
+ {
+ // The signature for this is not generic so the user can pass in anonymous type for the item to match
+ // e.g documents.Where(document => document.FooItems.ArrayContains(new { Name = "Bar" }, true)
+ throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
+ }
+
+ ///
+ /// Returns a boolean indicating whether the array contains the specified value.
+ /// You can check for a partial or full match of an object by using a boolean expression within the function.
+ /// For more information, see https://learn.microsoft.com/en-gb/azure/cosmos-db/nosql/query/array-contains.
+ /// 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.
+ ///
+ ///
+ /// The value to search within the array.
+ /// Indicating whether the search should check for a partial match (true) or a full match (false).
+ /// Returns true if the array array contains the specified value; otherwise, false.
+ ///
+ ///
+ /// document.Namess.ArrayContains(, ));
+ /// // To do a partial match on an array of objects, pass in an anonymous object set partialMatch to true
+ /// var matched = documents.Where(document => document.ObjectArray.ArrayContains(new { Name = }, true));
+ /// ]]>
+ ///
+ ///
+ public static bool ArrayContains(this IEnumerable obj, object itemToMatch, bool partialMatch)
+ {
+ // The signature for this is not generic so the user can pass in anonymous type for the item to match
+ // e.g documents.Where(document => document.FooItems.ArrayContains(new { Name = "Bar" }, true)
+ //
+ // Has 2 overloads instead of partialMatch with default value (i.e `bool partialMatch = false`) as
+ // default values cannot be used in expressions (error CS0854), and this method will only be used in expressions
+ throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
+ }
+
///
/// This method generate query definition from LINQ query.
///
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestArrayContainsBuiltinFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestArrayContainsBuiltinFunction.xml
new file mode 100644
index 0000000000..d5668344b4
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestArrayContainsBuiltinFunction.xml
@@ -0,0 +1,139 @@
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object)))]]>
+
+
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object)))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc")))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc")))]]>
+
+
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object), True))]]>
+
+
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object), True))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc"), True))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc"), True))]]>
+
+
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object), False))]]>
+
+
+
+
+
+
+ doc.ArrayField.ArrayContains(Convert(1, Object), False))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc"), False))]]>
+
+
+
+
+
+
+ doc.ObjectArrayField.ArrayContains(new AnonymousType(Field = "abc"), False))]]>
+
+
+
+
\ No newline at end of file
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 e53ec6a9f6..a29deab66e 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
@@ -117,6 +117,7 @@ internal class DataObject : LinqTestObject
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value false
public bool BooleanField;
public SimpleObject ObjectField = new SimpleObject();
+ public SimpleObject[] ObjectArrayField = new SimpleObject[0];
public Guid GuidField;
#pragma warning restore // Field is never assigned to, and will always have its default value false
@@ -346,6 +347,39 @@ public void TestRegexMatchFunction()
this.ExecuteTestSuite(inputs);
}
+ [TestMethod]
+ public void TestArrayContainsBuiltinFunction()
+ {
+ // Similar to the type checking function, Array_Contains are not supported client side.
+ // Therefore these methods are 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("ArrayContains in Select clause with int value", b => getQuery(b).Select(doc => doc.ArrayField.ArrayContains(1))),
+ new LinqTestInput("ArrayContains in Filter clause with int value", b => getQuery(b).Where(doc => doc.ArrayField.ArrayContains(1))),
+ new LinqTestInput("ArrayContains in Select clause with object value", b => getQuery(b).Select(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }))),
+ new LinqTestInput("ArrayContains in Filter clause with object value", b => getQuery(b).Where(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }))),
+
+ // Same tests, but with 3rd `partialMatch` parameter being true
+ new LinqTestInput("ArrayContains in Select clause with int value and match partial true", b => getQuery(b).Select(doc => doc.ArrayField.ArrayContains(1, true))),
+ new LinqTestInput("ArrayContains in Filter clause with int value and match partial true", b => getQuery(b).Where(doc => doc.ArrayField.ArrayContains(1, true))),
+ new LinqTestInput("ArrayContains in Select clause with object value and match partial true", b => getQuery(b).Select(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }, true))),
+ new LinqTestInput("ArrayContains in Filter clause with object value and match partial true", b => getQuery(b).Where(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }, true))),
+
+ // Same tests, but with 3rd `partialMatch` parameter being false
+ new LinqTestInput("ArrayContains in Select clause with int value and match partial false", b => getQuery(b).Select(doc => doc.ArrayField.ArrayContains(1, false))),
+ new LinqTestInput("ArrayContains in Filter clause with int value and match partial false", b => getQuery(b).Where(doc => doc.ArrayField.ArrayContains(1, false))),
+ new LinqTestInput("ArrayContains in Select clause with object value and match partial false", b => getQuery(b).Select(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }, false))),
+ new LinqTestInput("ArrayContains in Filter clause with object value and match partial false", b => getQuery(b).Where(doc => doc.ObjectArrayField.ArrayContains(new { Field = "abc" }, false))),
+ };
+
+ this.ExecuteTestSuite(inputs);
+ }
+
[TestMethod]
public void TestMemberInitializer()
{
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 edcd384b97..ee56131f01 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
@@ -112,6 +112,9 @@
PreserveNewest
+
+ PreserveNewest
+ PreserveNewest
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 f318710c77..8b1661cddc 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
@@ -6009,6 +6009,20 @@
"Microsoft.Azure.Cosmos.Linq.CosmosLinqExtensions;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
+ "Boolean ArrayContains(System.Collections.IEnumerable, System.Object, Boolean)[System.Runtime.CompilerServices.ExtensionAttribute()]": {
+ "Type": "Method",
+ "Attributes": [
+ "ExtensionAttribute"
+ ],
+ "MethodInfo": "Boolean ArrayContains(System.Collections.IEnumerable, System.Object, Boolean);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
+ "Boolean ArrayContains(System.Collections.IEnumerable, System.Object)[System.Runtime.CompilerServices.ExtensionAttribute()]": {
+ "Type": "Method",
+ "Attributes": [
+ "ExtensionAttribute"
+ ],
+ "MethodInfo": "Boolean ArrayContains(System.Collections.IEnumerable, System.Object);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
"Boolean IsArray(System.Object)[System.Runtime.CompilerServices.ExtensionAttribute()]": {
"Type": "Method",
"Attributes": [