Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LINQ : Adds User Defined Function Translation Support #2206

Merged
merged 15 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Linq/ConstantEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static bool CanBeEvaluated(Expression expression)
if (methodCallExpression != null)
{
Type type = methodCallExpression.Method.DeclaringType;
if (type == typeof(Enumerable) || type == typeof(Queryable) || type == typeof(UserDefinedFunctionProvider))
if (type == typeof(Enumerable) || type == typeof(Queryable) || type == typeof(CosmosLinq))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Linq
{
using System;
using System.Globalization;
using Microsoft.Azure.Cosmos.Scripts;

/// <summary>
/// Helper class to invoke User Defined Functions via Linq queries in the Azure Cosmos DB service.
/// This class provides methods for cosmos LINQ code.
/// </summary>
internal static class UserDefinedFunctionProvider
public static class CosmosLinq
{
/// <summary>
/// Helper method to invoke User Defined Functions via Linq queries in the Azure Cosmos DB service.
/// </summary>
/// <param name="udfName">the UserDefinedFunction name</param>
/// <param name="arguments">the arguments of the UserDefinedFunction</param>
/// <param name="udfName">The UserDefinedFunction name</param>
/// <param name="arguments">The arguments of the UserDefinedFunction</param>
/// <remarks>
/// This is a stub helper method for use within LINQ expressions. Cannot be called directly.
/// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-linq-to-sql for more details about the LINQ provider.
Expand All @@ -25,19 +26,31 @@ internal static class UserDefinedFunctionProvider
/// <example>
/// <code language="c#">
/// <![CDATA[
/// await client.CreateUserDefinedFunctionAsync(collectionLink, new UserDefinedFunction { Id = "calculateTax", Body = @"function(amt) { return amt * 0.05; }" });
/// var queryable = client.CreateDocumentQuery<Book>(collectionLink).Select(b => UserDefinedFunctionProvider.Invoke("calculateTax", b.Price));
///
/// // Equivalent to SELECT * FROM books b WHERE udf.toLowerCase(b.title) = 'war and peace'"
/// await client.CreateUserDefinedFunctionAsync(collectionLink, new UserDefinedFunction { Id = "toLowerCase", Body = @"function(s) { return s.ToLowerCase(); }" });
/// queryable = client.CreateDocumentQuery<Book>(collectionLink).Where(b => UserDefinedFunctionProvider.Invoke("toLowerCase", b.Title) == "war and peace");
/// IQueryable<Book> queryable = client
/// .GetContainer("database", "container")
/// .GetItemLinqQueryable<Book>()
/// .Where(b => CosmosLinq.InvokeUserDefinedFunction("toLowerCase", b.Title) == "war and peace");
///
/// FeedIterator<Book> bookIterator = queryable.ToFeedIterator();
/// while (feedIterator.HasMoreResults)
/// {
/// FeedResponse<Book> responseMessage = await feedIterator.ReadNextAsync();
/// DoSomethingWithResponse(responseMessage);
/// }
/// ]]>
/// </code>
/// </example>
/// <seealso cref="UserDefinedFunctionProperties"/>
public static object Invoke(string udfName, params object[] arguments)
/// <returns>Placeholder for the udf result.</returns>
#pragma warning disable IDE0060 // Remove unused parameter
public static object InvokeUserDefinedFunction(string udfName, params object[] arguments)
#pragma warning restore IDE0060 // Remove unused parameter
{
throw new Exception(string.Format(CultureInfo.CurrentCulture, ClientResources.InvalidCallToUserDefinedFunctionProvider));
throw new NotSupportedException(
string.Format(
CultureInfo.CurrentCulture,
ClientResources.InvalidCallToUserDefinedFunctionProvider));
}
}
}
}
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -724,4 +724,4 @@ private static MethodInfo GetMethodInfoOf<T1, T2>(Func<T1, T2> func)
return func.GetMethodInfo();
}
}
}
}
2 changes: 1 addition & 1 deletion Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ internal static SqlScalarExpression VisitNonSubqueryScalarExpression(Expression
private static SqlScalarExpression VisitMethodCallScalar(MethodCallExpression methodCallExpression, TranslationContext context)
{
// Check if it is a UDF method call
if (methodCallExpression.Method.Equals(typeof(UserDefinedFunctionProvider).GetMethod("Invoke")))
if (methodCallExpression.Method.Equals(typeof(CosmosLinq).GetMethod("InvokeUserDefinedFunction")))
{
string udfName = ((ConstantExpression)methodCallExpression.Arguments[0]).Value as string;
if (string.IsNullOrEmpty(udfName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Result>
<Input>
<Description><![CDATA[No param]]></Description>
<Expression><![CDATA[query.Select(f => Invoke("NoParameterUDF", new [] {}))]]></Expression>
<Expression><![CDATA[query.Select(f => InvokeUserDefinedFunction("NoParameterUDF", new [] {}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -13,7 +13,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Single param]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("SingleParameterUDF", new [] {Convert(doc.NumericField, Object)}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("SingleParameterUDF", new [] {Convert(doc.NumericField, Object)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -24,7 +24,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Single param w/ array]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("SingleParameterUDFWithArray", new [] {doc.ArrayField}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("SingleParameterUDFWithArray", new [] {doc.ArrayField}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -35,7 +35,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Multi param]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("MultiParamterUDF", new [] {Convert(doc.NumericField, Object), doc.StringField, doc.Point}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("MultiParamterUDF", new [] {Convert(doc.NumericField, Object), doc.StringField, doc.Point}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -46,7 +46,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Multi param w/ array]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("MultiParamterUDWithArrayF", new [] {doc.ArrayField, Convert(doc.NumericField, Object), doc.Point}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("MultiParamterUDWithArrayF", new [] {doc.ArrayField, Convert(doc.NumericField, Object), doc.Point}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -57,7 +57,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[ArrayCount]]></Description>
<Expression><![CDATA[query.Where(doc => (Convert(Invoke("ArrayCount", new [] {doc.ArrayField}), Int32) > 2))]]></Expression>
<Expression><![CDATA[query.Where(doc => (Convert(InvokeUserDefinedFunction("ArrayCount", new [] {doc.ArrayField}), Int32) > 2))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -69,7 +69,7 @@ WHERE (udf.ArrayCount(root["ArrayField"]) > 2) ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[ArrayCount && SomeBooleanUDF]]></Description>
<Expression><![CDATA[query.Where(doc => ((Convert(Invoke("ArrayCount", new [] {doc.ArrayField}), Int32) > 2) AndAlso Convert(Invoke("SomeBooleanUDF", new [] {}), Boolean)))]]></Expression>
<Expression><![CDATA[query.Where(doc => ((Convert(InvokeUserDefinedFunction("ArrayCount", new [] {doc.ArrayField}), Int32) > 2) AndAlso Convert(InvokeUserDefinedFunction("SomeBooleanUDF", new [] {}), Boolean)))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -81,7 +81,7 @@ WHERE ((udf.ArrayCount(root["ArrayField"]) > 2) AND udf.SomeBooleanUDF()) ]]></S
<Result>
<Input>
<Description><![CDATA[expression]]></Description>
<Expression><![CDATA[query.Where(doc => ((Convert(Invoke("SingleParameterUDF", new [] {Convert(doc.NumericField, Object)}), Int32) + 2) == 4))]]></Expression>
<Expression><![CDATA[query.Where(doc => ((Convert(InvokeUserDefinedFunction("SingleParameterUDF", new [] {Convert(doc.NumericField, Object)}), Int32) + 2) == 4))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -93,7 +93,7 @@ WHERE ((udf.SingleParameterUDF(root["NumericField"]) + 2) = 4) ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Single constant param]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("SingleParameterUDF", new [] {Convert(1, Object)}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("SingleParameterUDF", new [] {Convert(1, Object)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -104,7 +104,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Single constant int array param]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("SingleParameterUDFWithArray", new [] {new [] {1, 2, 3}}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("SingleParameterUDFWithArray", new [] {new [] {1, 2, 3}}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -115,7 +115,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Single constant string array param]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("SingleParameterUDFWithArray", new [] {"1", "2"}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("SingleParameterUDFWithArray", new [] {"1", "2"}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -126,7 +126,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Multi constant params]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("MultiParamterUDF", new [] {Convert(1, Object), "str", Convert(True, Object)}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("MultiParamterUDF", new [] {Convert(1, Object), "str", Convert(True, Object)}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -137,7 +137,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Multi constant array params]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("MultiParamterUDWithArrayF", new [] {new [] {1, 2, 3}, Convert(1, Object), "str"}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("MultiParamterUDWithArrayF", new [] {new [] {1, 2, 3}, Convert(1, Object), "str"}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -148,7 +148,7 @@ FROM root ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[ArrayCount with constant param]]></Description>
<Expression><![CDATA[query.Where(doc => (Convert(Invoke("ArrayCount", new [] {new [] {1, 2, 3}}), Int32) > 2))]]></Expression>
<Expression><![CDATA[query.Where(doc => (Convert(InvokeUserDefinedFunction("ArrayCount", new [] {new [] {1, 2, 3}}), Int32) > 2))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -160,7 +160,7 @@ WHERE (udf.ArrayCount([1, 2, 3]) > 2) ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[different type parameters including objects]]></Description>
<Expression><![CDATA[query.Where(doc => Convert(Invoke("MultiParamterUDF2", new [] {doc.Point, "str", Convert(1, Object)}), Boolean))]]></Expression>
<Expression><![CDATA[query.Where(doc => Convert(InvokeUserDefinedFunction("MultiParamterUDF2", new [] {doc.Point, "str", Convert(1, Object)}), Boolean))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
Expand All @@ -172,7 +172,7 @@ WHERE udf.MultiParamterUDF2(root["Point"], "str", 1) ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Null udf name]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke(null, new [] {}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction(null, new [] {}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
Expand All @@ -182,7 +182,7 @@ WHERE udf.MultiParamterUDF2(root["Point"], "str", 1) ]]></SqlQuery>
<Result>
<Input>
<Description><![CDATA[Empty udf name]]></Description>
<Expression><![CDATA[query.Select(doc => Invoke("", new [] {}))]]></Expression>
<Expression><![CDATA[query.Select(doc => InvokeUserDefinedFunction("", new [] {}))]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[]]></SqlQuery>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,6 @@ public void TestStringCompareTo()
}

[TestMethod]
[TestCategory("Ignore")]
public void TestUDFs()
{
// The UDFs invokation are not supported on the client side.
Expand All @@ -896,26 +895,26 @@ public void TestUDFs()

List<LinqTestInput> inputs = new List<LinqTestInput>
{
new LinqTestInput("No param", b => getQuery(b).Select(f => UserDefinedFunctionProvider.Invoke("NoParameterUDF"))),
new LinqTestInput("Single param", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("SingleParameterUDF", doc.NumericField))),
new LinqTestInput("Single param w/ array", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("SingleParameterUDFWithArray", doc.ArrayField))),
new LinqTestInput("Multi param", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("MultiParamterUDF", doc.NumericField, doc.StringField, doc.Point))),
new LinqTestInput("Multi param w/ array", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("MultiParamterUDWithArrayF", doc.ArrayField, doc.NumericField, doc.Point))),
new LinqTestInput("ArrayCount", b => getQuery(b).Where(doc => (int)UserDefinedFunctionProvider.Invoke("ArrayCount", doc.ArrayField) > 2)),
new LinqTestInput("ArrayCount && SomeBooleanUDF", b => getQuery(b).Where(doc => (int)UserDefinedFunctionProvider.Invoke("ArrayCount", doc.ArrayField) > 2 && (bool)UserDefinedFunctionProvider.Invoke("SomeBooleanUDF"))),
new LinqTestInput("expression", b => getQuery(b).Where(doc => (int)UserDefinedFunctionProvider.Invoke("SingleParameterUDF", doc.NumericField) + 2 == 4)),
new LinqTestInput("No param", b => getQuery(b).Select(f => CosmosLinq.InvokeUserDefinedFunction("NoParameterUDF"))),
new LinqTestInput("Single param", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDF", doc.NumericField))),
new LinqTestInput("Single param w/ array", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDFWithArray", doc.ArrayField))),
new LinqTestInput("Multi param", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("MultiParamterUDF", doc.NumericField, doc.StringField, doc.Point))),
new LinqTestInput("Multi param w/ array", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("MultiParamterUDWithArrayF", doc.ArrayField, doc.NumericField, doc.Point))),
new LinqTestInput("ArrayCount", b => getQuery(b).Where(doc => (int)CosmosLinq.InvokeUserDefinedFunction("ArrayCount", doc.ArrayField) > 2)),
new LinqTestInput("ArrayCount && SomeBooleanUDF", b => getQuery(b).Where(doc => (int)CosmosLinq.InvokeUserDefinedFunction("ArrayCount", doc.ArrayField) > 2 && (bool)CosmosLinq.InvokeUserDefinedFunction("SomeBooleanUDF"))),
new LinqTestInput("expression", b => getQuery(b).Where(doc => (int)CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDF", doc.NumericField) + 2 == 4)),
// UDF with constant parameters
new LinqTestInput("Single constant param", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("SingleParameterUDF", 1))),
new LinqTestInput("Single constant int array param", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("SingleParameterUDFWithArray", new int[] { 1, 2, 3 }))),
new LinqTestInput("Single constant string array param", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("SingleParameterUDFWithArray", new string[] { "1", "2" }))),
new LinqTestInput("Multi constant params", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("MultiParamterUDF", 1, "str", true))),
new LinqTestInput("Multi constant array params", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("MultiParamterUDWithArrayF", new int[] { 1, 2, 3 }, 1, "str"))),
new LinqTestInput("ArrayCount with constant param", b => getQuery(b).Where(doc => (int)UserDefinedFunctionProvider.Invoke("ArrayCount", new int[] { 1, 2, 3 }) > 2)),
new LinqTestInput("Single constant param", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDF", 1))),
new LinqTestInput("Single constant int array param", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDFWithArray", new int[] { 1, 2, 3 }))),
new LinqTestInput("Single constant string array param", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("SingleParameterUDFWithArray", new string[] { "1", "2" }))),
new LinqTestInput("Multi constant params", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("MultiParamterUDF", 1, "str", true))),
new LinqTestInput("Multi constant array params", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("MultiParamterUDWithArrayF", new int[] { 1, 2, 3 }, 1, "str"))),
new LinqTestInput("ArrayCount with constant param", b => getQuery(b).Where(doc => (int)CosmosLinq.InvokeUserDefinedFunction("ArrayCount", new int[] { 1, 2, 3 }) > 2)),
// regression (different type parameters including objects)
new LinqTestInput("different type parameters including objects", b => getQuery(b).Where(doc => (bool)UserDefinedFunctionProvider.Invoke("MultiParamterUDF2", doc.Point, "str", 1))),
new LinqTestInput("different type parameters including objects", b => getQuery(b).Where(doc => (bool)CosmosLinq.InvokeUserDefinedFunction("MultiParamterUDF2", doc.Point, "str", 1))),
// errors
new LinqTestInput("Null udf name", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke(null))),
new LinqTestInput("Empty udf name", b => getQuery(b).Select(doc => UserDefinedFunctionProvider.Invoke("")))
new LinqTestInput("Null udf name", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction(null))),
new LinqTestInput("Empty udf name", b => getQuery(b).Select(doc => CosmosLinq.InvokeUserDefinedFunction("")))
};
this.ExecuteTestSuite(inputs);
}
Expand Down
Loading