diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs index d7e79b5c50..82f427c206 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/BuiltinFunctionVisitor.cs @@ -50,11 +50,16 @@ public static SqlScalarExpression VisitBuiltinFunctionCall(MethodCallExpression if (methodCallExpression.Method.DeclaringType.GeUnderlyingSystemType() == typeof(CosmosLinqExtensions)) { - // CosmosLinq Extensions are either RegexMatch or Type check functions (IsString, IsBool, etc.) + // CosmosLinq Extensions can be RegexMatch, DocumentId or Type check functions (IsString, IsBool, etc.) if (methodCallExpression.Method.Name == nameof(CosmosLinqExtensions.RegexMatch)) { return StringBuiltinFunctions.Visit(methodCallExpression, context); - } + } + + if (methodCallExpression.Method.Name == nameof(CosmosLinqExtensions.DocumentId)) + { + return OtherBuiltinSystemFunctions.Visit(methodCallExpression, context); + } return TypeCheckFunctions.Visit(methodCallExpression, context); } diff --git a/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs new file mode 100644 index 0000000000..ea748b9a74 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/BuiltinFunctions/OtherBuiltinSystemFunctions.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq.Expressions; + using Microsoft.Azure.Cosmos.SqlObjects; + + internal static class OtherBuiltinSystemFunctions + { + private static Dictionary FunctionsDefinitions { get; set; } + + static OtherBuiltinSystemFunctions() + { + FunctionsDefinitions = new Dictionary + { + [nameof(CosmosLinqExtensions.DocumentId)] = new SqlBuiltinFunctionVisitor( + sqlName: "DOCUMENTID", + isStatic: true, + argumentLists: new List() + { + new Type[]{typeof(object)}, + }) + }; + } + + public static SqlScalarExpression Visit(MethodCallExpression methodCallExpression, TranslationContext context) + { + return FunctionsDefinitions.TryGetValue(methodCallExpression.Method.Name, out BuiltinFunctionVisitor visitor) + ? visitor.Visit(methodCallExpression, context) + : throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.MethodNotSupported, methodCallExpression.Method.Name)); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index 7f7a22b466..a45b282d15 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -19,7 +19,26 @@ namespace Microsoft.Azure.Cosmos.Linq /// This class provides extension methods for cosmos LINQ code. /// public static class CosmosLinqExtensions - { + { + /// + /// Returns the integer identifier corresponding to a specific item within a physical partition. + /// 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 root object + /// Returns the integer identifier corresponding to a specific item within a physical partition. + /// + /// + /// root.DocumentId()); + /// ]]> + /// + /// + public static int DocumentId(this object obj) + { + throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented); + } + /// /// Returns a Boolean value indicating if the type of the specified expression is an array. /// This method is to be used in LINQ expressions only and will be evaluated on server. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDocumentIdBuiltinFunction.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDocumentIdBuiltinFunction.xml new file mode 100644 index 0000000000..041a57932e --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationBaselineTests.TestDocumentIdBuiltinFunction.xml @@ -0,0 +1,63 @@ + + + + + doc.DocumentId())]]> + + + + + + + + + (doc.DocumentId() > 123))]]> + + + 123)]]> + + + + + + (Convert(doc.BooleanField, Object).DocumentId() > 123))]]> + + + 123)]]> + + + + + + doc.EnumerableField.Where(number => (doc.DocumentId() > 0)).Select(number => number))]]> + + + 0)]]> + + + + + + doc.DocumentId())]]> + + + + + + + \ 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 58aef32721..7590ec20ac 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 @@ -290,6 +290,28 @@ public void TestTypeCheckFunctions() }; this.ExecuteTestSuite(inputs); } + + [TestMethod] + public void TestDocumentIdBuiltinFunction() + { + List data = new List(); + IOrderedQueryable query = testContainer.GetItemLinqQueryable(allowSynchronousQueryExecution: true); + Func> getQuery = useQuery => useQuery ? query : data.AsQueryable(); + + List inputs = new List + { + new LinqTestInput("In Select clause", b => getQuery(b).Select(doc => doc.DocumentId())), + new LinqTestInput("In Filter clause", b => getQuery(b).Where(doc => doc.DocumentId() > 123)), + new LinqTestInput("With non root term", b => getQuery(b).Where(doc => doc.BooleanField.DocumentId() > 123)), + new LinqTestInput("With JOIN", b => getQuery(b).SelectMany(doc => doc.EnumerableField + .Where(number => doc.DocumentId() > 0) + .Select(number => number))), + // Negative case + new LinqTestInput("In Order by clause", b => getQuery(b).OrderBy(doc => doc.DocumentId())), + }; + + this.ExecuteTestSuite(inputs); + } [TestMethod] public void TestRegexMatchFunction() 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 4f7d46256d..345d61ed83 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 @@ -229,6 +229,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 e106719208..89074f0fef 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 @@ -5650,6 +5650,13 @@ ], "MethodInfo": "Boolean RegexMatch(System.Object, System.String);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Int32 DocumentId(System.Object)[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "Type": "Method", + "Attributes": [ + "ExtensionAttribute" + ], + "MethodInfo": "Int32 DocumentId(System.Object);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": [