Query: Adds DOCUMENTID extension method for LINQ (#4489)

* Add extension method for doucmentid

* test coverage

* additional test

* update contract

* update dotnet api

* address code review

* update csproj file

* update missing baseline

---------

Co-authored-by: Minh Le (from Dev Box) <leminh@microsoft.com>
This commit is contained in:
leminh98 2024-06-04 13:06:16 -07:00 коммит произвёл GitHub
Родитель 5a28704392
Коммит 18a677ace9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 160 добавлений и 3 удалений

Просмотреть файл

@ -50,11 +50,16 @@ namespace Microsoft.Azure.Cosmos.Linq
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);
}

Просмотреть файл

@ -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<string, BuiltinFunctionVisitor> FunctionsDefinitions { get; set; }
static OtherBuiltinSystemFunctions()
{
FunctionsDefinitions = new Dictionary<string, BuiltinFunctionVisitor>
{
[nameof(CosmosLinqExtensions.DocumentId)] = new SqlBuiltinFunctionVisitor(
sqlName: "DOCUMENTID",
isStatic: true,
argumentLists: new List<Type[]>()
{
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));
}
}
}

Просмотреть файл

@ -19,7 +19,26 @@ namespace Microsoft.Azure.Cosmos.Linq
/// This class provides extension methods for cosmos LINQ code.
/// </summary>
public static class CosmosLinqExtensions
{
{
/// <summary>
/// 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.
/// </summary>
/// <param name="obj">The root object</param>
/// <returns>Returns the integer identifier corresponding to a specific item within a physical partition.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var documentIdQuery = documents.Where(root => root.DocumentId());
/// ]]>
/// </code>
/// </example>
public static int DocumentId(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}
/// <summary>
/// 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.

Просмотреть файл

@ -0,0 +1,63 @@
<Results>
<Result>
<Input>
<Description><![CDATA[In Select clause]]></Description>
<Expression><![CDATA[query.Select(doc => doc.DocumentId())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE DOCUMENTID(root)
FROM root]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[In Filter clause]]></Description>
<Expression><![CDATA[query.Where(doc => (doc.DocumentId() > 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (DOCUMENTID(root) > 123)]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[With non root term]]></Description>
<Expression><![CDATA[query.Where(doc => (Convert(doc.BooleanField, Object).DocumentId() > 123))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE (DOCUMENTID(root["BooleanField"]) > 123)]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[With JOIN]]></Description>
<Expression><![CDATA[query.SelectMany(doc => doc.EnumerableField.Where(number => (doc.DocumentId() > 0)).Select(number => number))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE number0
FROM root
JOIN number0 IN root["EnumerableField"]
WHERE (DOCUMENTID(root) > 0)]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[In Order by clause]]></Description>
<Expression><![CDATA[query.OrderBy(doc => doc.DocumentId())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
ORDER BY DOCUMENTID(root) ASC]]></SqlQuery>
<ErrorMessage><![CDATA[Status Code: BadRequest,{"errors":[{"severity":"Error","code":2206,"message":"Unsupported ORDER BY clause. ORDER BY item expression could not be mapped to a document path."}]},0x800A0B00]]></ErrorMessage>
</Output>
</Result>
</Results>

Просмотреть файл

@ -290,6 +290,28 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests
};
this.ExecuteTestSuite(inputs);
}
[TestMethod]
public void TestDocumentIdBuiltinFunction()
{
List<DataObject> data = new List<DataObject>();
IOrderedQueryable<DataObject> query = testContainer.GetItemLinqQueryable<DataObject>(allowSynchronousQueryExecution: true);
Func<bool, IQueryable<DataObject>> getQuery = useQuery => useQuery ? query : data.AsQueryable();
List<LinqTestInput> inputs = new List<LinqTestInput>
{
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()

Просмотреть файл

@ -229,6 +229,9 @@
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestDateTimeJsonConverterTimezones.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestDocumentIdBuiltinFunction.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="BaselineTest\TestBaseline\LinqTranslationBaselineTests.TestMemberAccessWithNullableTypes.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Просмотреть файл

@ -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": [