-
Notifications
You must be signed in to change notification settings - Fork 533
Expand file tree
/
Copy pathBuiltinFunctionVisitor.cs
More file actions
138 lines (121 loc) · 6.72 KB
/
BuiltinFunctionVisitor.cs
File metadata and controls
138 lines (121 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Linq
{
using System;
using System.Globalization;
using System.Linq.Expressions;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Spatial;
using Microsoft.Azure.Cosmos.SqlObjects;
using Microsoft.Azure.Documents;
internal abstract class BuiltinFunctionVisitor
{
public SqlScalarExpression Visit(MethodCallExpression methodCallExpression, TranslationContext context)
{
SqlScalarExpression result = this.VisitExplicit(methodCallExpression, context);
if (result != null)
{
return result;
}
result = this.VisitImplicit(methodCallExpression, context);
if (result != null)
{
return result;
}
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.MethodNotSupported, methodCallExpression.Method.Name));
}
public static SqlScalarExpression VisitBuiltinFunctionCall(MethodCallExpression methodCallExpression, TranslationContext context)
{
Type declaringType;
bool isExtensionMethod = methodCallExpression.Method.IsExtensionMethod();
// Method could be an extension method
// RRF doesn't have "this" qualifier, so it's not considered an extension method by the compiler, and so needed to be checked separately
if (methodCallExpression.Method.IsStatic &&
(methodCallExpression.Method.IsExtensionMethod()
|| methodCallExpression.Method.Name.Equals(nameof(CosmosLinqExtensions.RRF))))
{
if (methodCallExpression.Arguments.Count < 1)
{
// Extension methods should has at least 1 argument, this should never happen
// Throwing ArgumentException instead of assert
throw new ArgumentException();
}
declaringType = methodCallExpression.Arguments[0].Type;
if (methodCallExpression.Method.DeclaringType.GeUnderlyingSystemType() == typeof(CosmosLinqExtensions))
{
// CosmosLinq Extensions can be RegexMatch, DocumentId or Type check functions (IsString, IsBool, etc.)
switch (methodCallExpression.Method.Name)
{
case nameof(CosmosLinqExtensions.RegexMatch):
case nameof(CosmosLinqExtensions.FullTextContains):
case nameof(CosmosLinqExtensions.FullTextContainsAll):
case nameof(CosmosLinqExtensions.FullTextContainsAny):
return StringBuiltinFunctions.Visit(methodCallExpression, context);
case nameof(CosmosLinqExtensions.ArrayContainsAll):
case nameof(CosmosLinqExtensions.ArrayContainsAny):
case nameof(CosmosLinqExtensions.DocumentId):
case nameof(CosmosLinqExtensions.RRF):
case nameof(CosmosLinqExtensions.FullTextScore):
case nameof(CosmosLinqExtensions.VectorDistance):
return OtherBuiltinSystemFunctions.Visit(methodCallExpression, context);
default:
return TypeCheckFunctions.Visit(methodCallExpression, context);
}
}
}
else
{
declaringType = methodCallExpression.Method.DeclaringType;
}
// Check order matters, some extension methods work for both strings and arrays
// Math functions
if (declaringType == typeof(Math))
{
return MathBuiltinFunctions.Visit(methodCallExpression, context);
}
// ToString with String and Guid only becomes passthrough
if (methodCallExpression.Method.Name == "ToString" &&
methodCallExpression.Arguments.Count == 0 &&
methodCallExpression.Object != null &&
((methodCallExpression.Object.Type == typeof(string)) ||
(methodCallExpression.Object.Type == typeof(Guid))))
{
return ExpressionToSql.VisitNonSubqueryScalarExpression(methodCallExpression.Object, context);
}
// String functions or ToString with Objects that are not strings and guids
if ((declaringType == typeof(string)) ||
(methodCallExpression.Method.Name == "ToString" &&
methodCallExpression.Arguments.Count == 0 &&
methodCallExpression.Object != null))
{
return StringBuiltinFunctions.Visit(methodCallExpression, context);
}
// Array functions
// Note: In .NET 10+, array.Contains() may resolve to MemoryExtensions.Contains(ReadOnlySpan<T>, T)
// ReadOnlySpan<T> does not implement IEnumerable<T>, so we also check for MemoryExtensions
if (declaringType.IsEnumerable() || IsMemoryExtensionsMethod(methodCallExpression))
{
return ArrayBuiltinFunctions.Visit(methodCallExpression, context);
}
// Spatial functions
if (typeof(Geometry).IsAssignableFrom(declaringType))
{
return SpatialBuiltinFunctions.Visit(methodCallExpression, context);
}
throw new DocumentQueryException(string.Format(CultureInfo.CurrentCulture, ClientResources.MethodNotSupported, methodCallExpression.Method.Name));
}
/// <summary>
/// Checks if the method is from MemoryExtensions class (e.g., Contains on ReadOnlySpan).
/// In .NET 10+, array.Contains() resolves to MemoryExtensions.Contains(ReadOnlySpan, T).
/// </summary>
private static bool IsMemoryExtensionsMethod(MethodCallExpression methodCallExpression)
{
Type declaringType = methodCallExpression.Method.DeclaringType;
return declaringType != null && declaringType.FullName == "System.MemoryExtensions";
}
protected abstract SqlScalarExpression VisitExplicit(MethodCallExpression methodCallExpression, TranslationContext context);
protected abstract SqlScalarExpression VisitImplicit(MethodCallExpression methodCallExpression, TranslationContext context);
}
}