Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
jobily
Efcore.Pg
提交
41678ef4
E
Efcore.Pg
项目概览
jobily
/
Efcore.Pg
12 个月 前同步成功
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
Efcore.Pg
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
41678ef4
编写于
5月 29, 2018
作者:
A
Austin Drenski
提交者:
Shay Rojansky
5月 29, 2018
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Translate LIKE ANY expressions (#430)
Adds simple support to translates LIKE ANY and LIKE ALL expressions
上级
d39db2ee
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
554 addition
and
38 deletion
+554
-38
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
...pressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
+138
-26
src/EFCore.PG/Query/Expressions/Internal/ArrayAnyAllExpression.cs
...re.PG/Query/Expressions/Internal/ArrayAnyAllExpression.cs
+156
-0
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
+24
-12
test/EFCore.PG.FunctionalTests/Query/LikeAnyAllQueryNpgsqlTest.cs
...ore.PG.FunctionalTests/Query/LikeAnyAllQueryNpgsqlTest.cs
+236
-0
未找到文件。
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
浏览文件 @
41678ef4
#
region
License
// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
...
...
@@ -19,16 +20,20 @@
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion
using
System.Linq
;
using
System.Linq.Expressions
;
using
System.Reflection
;
using
JetBrains.Annotations
;
using
Microsoft.EntityFrameworkCore
;
using
Microsoft.EntityFrameworkCore.Query
;
using
Microsoft.EntityFrameworkCore.Query.Expressions
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal
;
using
Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal
;
using
Remotion.Linq.Clauses
;
using
Remotion.Linq.Clauses.Expressions
;
using
Remotion.Linq.Clauses.ResultOperators
;
...
...
@@ -36,8 +41,42 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionVisitors
{
public
class
NpgsqlSqlTranslatingExpressionVisitor
:
SqlTranslatingExpressionVisitor
{
private
readonly
RelationalQueryModelVisitor
_queryModelVisitor
;
/// <summary>
/// The <see cref="MethodInfo"/> for <see cref="DbFunctionsExtensions.Like(DbFunctions,string,string)"/>.
/// </summary>
[
NotNull
]
static
readonly
MethodInfo
Like2MethodInfo
=
typeof
(
DbFunctionsExtensions
)
.
GetRuntimeMethod
(
nameof
(
DbFunctionsExtensions
.
Like
),
new
[]
{
typeof
(
DbFunctions
),
typeof
(
string
),
typeof
(
string
)
});
/// <summary>
/// The <see cref="MethodInfo"/> for <see cref="DbFunctionsExtensions.Like(DbFunctions,string,string, string)"/>.
/// </summary>
[
NotNull
]
static
readonly
MethodInfo
Like3MethodInfo
=
typeof
(
DbFunctionsExtensions
)
.
GetRuntimeMethod
(
nameof
(
DbFunctionsExtensions
.
Like
),
new
[]
{
typeof
(
DbFunctions
),
typeof
(
string
),
typeof
(
string
),
typeof
(
string
)
});
// ReSharper disable once InconsistentNaming
/// <summary>
/// The <see cref="MethodInfo"/> for <see cref="NpgsqlDbFunctionsExtensions.ILike(DbFunctions,string,string)"/>.
/// </summary>
[
NotNull
]
static
readonly
MethodInfo
ILike2MethodInfo
=
typeof
(
NpgsqlDbFunctionsExtensions
)
.
GetRuntimeMethod
(
nameof
(
NpgsqlDbFunctionsExtensions
.
ILike
),
new
[]
{
typeof
(
DbFunctions
),
typeof
(
string
),
typeof
(
string
)
});
// ReSharper disable once InconsistentNaming
/// <summary>
/// The <see cref="MethodInfo"/> for <see cref="NpgsqlDbFunctionsExtensions.ILike(DbFunctions,string,string,string)"/>.
/// </summary>
[
NotNull
]
static
readonly
MethodInfo
ILike3MethodInfo
=
typeof
(
NpgsqlDbFunctionsExtensions
)
.
GetRuntimeMethod
(
nameof
(
NpgsqlDbFunctionsExtensions
.
ILike
),
new
[]
{
typeof
(
DbFunctions
),
typeof
(
string
),
typeof
(
string
),
typeof
(
string
)
});
/// <summary>
/// The query model visitor.
/// </summary>
[
NotNull
]
readonly
RelationalQueryModelVisitor
_queryModelVisitor
;
/// <inheritdoc />
public
NpgsqlSqlTranslatingExpressionVisitor
(
[
NotNull
]
SqlTranslatingExpressionVisitorDependencies
dependencies
,
[
NotNull
]
RelationalQueryModelVisitor
queryModelVisitor
,
...
...
@@ -45,22 +84,51 @@ public class NpgsqlSqlTranslatingExpressionVisitor : SqlTranslatingExpressionVis
[
CanBeNull
]
Expression
topLevelPredicate
=
null
,
bool
inProjection
=
false
)
:
base
(
dependencies
,
queryModelVisitor
,
targetSelectExpression
,
topLevelPredicate
,
inProjection
)
{
_queryModelVisitor
=
queryModelVisitor
;
}
=>
_queryModelVisitor
=
queryModelVisitor
;
/// <inheritdoc />
protected
override
Expression
VisitSubQuery
(
SubQueryExpression
expression
)
=>
base
.
VisitSubQuery
(
expression
)
??
VisitLikeAnyAll
(
expression
)
??
VisitEqualsAny
(
expression
);
/// <inheritdoc />
protected
override
Expression
VisitBinary
(
BinaryExpression
expression
)
{
// Prefer the default EF Core translation if one exists
var
result
=
base
.
VisitSubQuery
(
expression
);
if
(
result
!=
null
)
return
result
;
if
(
expression
.
NodeType
==
ExpressionType
.
ArrayIndex
)
{
var
properties
=
MemberAccessBindingExpressionVisitor
.
GetPropertyPath
(
expression
.
Left
,
_queryModelVisitor
.
QueryCompilationContext
,
out
_
);
if
(
properties
.
Count
==
0
)
return
base
.
VisitBinary
(
expression
);
var
lastPropertyType
=
properties
[
properties
.
Count
-
1
].
ClrType
;
if
(
lastPropertyType
.
IsArray
&&
lastPropertyType
.
GetArrayRank
()
==
1
)
{
var
left
=
Visit
(
expression
.
Left
);
var
right
=
Visit
(
expression
.
Right
);
return
left
!=
null
&&
right
!=
null
?
Expression
.
MakeBinary
(
ExpressionType
.
ArrayIndex
,
left
,
right
)
:
null
;
}
}
return
base
.
VisitBinary
(
expression
);
}
/// <summary>
/// Visits a <see cref="SubQueryExpression"/> and attempts to translate a '= ANY' expression.
/// </summary>
/// <param name="expression">The expression to visit.</param>
/// <returns>
/// An '= ANY' expression or null.
/// </returns>
[
CanBeNull
]
protected
virtual
Expression
VisitEqualsAny
([
NotNull
]
SubQueryExpression
expression
)
{
var
subQueryModel
=
expression
.
QueryModel
;
var
fromExpression
=
subQueryModel
.
MainFromClause
.
FromExpression
;
var
properties
=
MemberAccessBindingExpressionVisitor
.
GetPropertyPath
(
fromExpression
,
_queryModelVisitor
.
QueryCompilationContext
,
out
var
qsre
);
fromExpression
,
_queryModelVisitor
.
QueryCompilationContext
,
out
_
);
if
(
properties
.
Count
==
0
)
return
null
;
...
...
@@ -76,33 +144,77 @@ protected override Expression VisitSubQuery(SubQueryExpression expression)
{
var
containsItem
=
Visit
(
contains
.
Item
);
if
(
containsItem
!=
null
)
return
new
ArrayAny
Expression
(
containsItem
,
Visit
(
fromExpression
));
return
new
ArrayAny
AllExpression
(
ArrayComparisonType
.
ANY
,
"="
,
containsItem
,
Visit
(
fromExpression
));
}
}
return
null
;
}
protected
override
Expression
VisitBinary
(
BinaryExpression
expression
)
/// <summary>
/// Visits a <see cref="SubQueryExpression"/> and attempts to translate a LIKE/ILIKE ANY/ALL expression.
/// </summary>
/// <param name="expression">The expression to visit.</param>
/// <returns>
/// A 'LIKE ANY', 'LIKE ALL', 'ILIKE ANY', or 'ILIKE ALL' expression or null.
/// </returns>
[
CanBeNull
]
protected
virtual
Expression
VisitLikeAnyAll
([
NotNull
]
SubQueryExpression
expression
)
{
if
(
expression
.
NodeType
==
ExpressionType
.
ArrayIndex
)
{
var
properties
=
MemberAccessBindingExpressionVisitor
.
GetPropertyPath
(
expression
.
Left
,
_queryModelVisitor
.
QueryCompilationContext
,
out
var
qsre
);
if
(
properties
.
Count
==
0
)
return
base
.
VisitBinary
(
expression
);
var
lastPropertyType
=
properties
[
properties
.
Count
-
1
].
ClrType
;
if
(
lastPropertyType
.
IsArray
&&
lastPropertyType
.
GetArrayRank
()
==
1
)
{
var
left
=
Visit
(
expression
.
Left
);
var
right
=
Visit
(
expression
.
Right
);
var
queryModel
=
expression
.
QueryModel
;
var
results
=
queryModel
.
ResultOperators
;
var
body
=
queryModel
.
BodyClauses
;
return
left
!=
null
&&
right
!=
null
?
Expression
.
MakeBinary
(
ExpressionType
.
ArrayIndex
,
left
,
right
)
if
(
results
.
Count
!=
1
)
return
null
;
ArrayComparisonType
comparisonType
;
MethodCallExpression
call
;
switch
(
results
[
0
])
{
case
AnyResultOperator
_
:
comparisonType
=
ArrayComparisonType
.
ANY
;
call
=
body
.
Count
==
1
&&
body
[
0
]
is
WhereClause
whereClause
&&
whereClause
.
Predicate
is
MethodCallExpression
methocCall
?
methocCall
:
null
;
}
break
;
case
AllResultOperator
allResult
:
comparisonType
=
ArrayComparisonType
.
ALL
;
call
=
allResult
.
Predicate
as
MethodCallExpression
;
break
;
default
:
return
null
;
}
return
base
.
VisitBinary
(
expression
);
if
(
call
is
null
)
return
null
;
var
source
=
queryModel
.
MainFromClause
.
FromExpression
;
// ReSharper disable AssignNullToNotNullAttribute
switch
(
call
.
Method
)
{
case
MethodInfo
m
when
m
==
Like2MethodInfo
:
return
new
ArrayAnyAllExpression
(
comparisonType
,
"LIKE"
,
Visit
(
call
.
Arguments
[
1
]),
Visit
(
source
));
case
MethodInfo
m
when
m
==
Like3MethodInfo
:
return
new
ArrayAnyAllExpression
(
comparisonType
,
"LIKE"
,
Visit
(
call
.
Arguments
[
1
]),
Visit
(
source
));
case
MethodInfo
m
when
m
==
ILike2MethodInfo
:
return
new
ArrayAnyAllExpression
(
comparisonType
,
"ILIKE"
,
Visit
(
call
.
Arguments
[
1
]),
Visit
(
source
));
case
MethodInfo
m
when
m
==
ILike3MethodInfo
:
return
new
ArrayAnyAllExpression
(
comparisonType
,
"ILIKE"
,
Visit
(
call
.
Arguments
[
1
]),
Visit
(
source
));
default
:
return
null
;
}
// ReSharper restore AssignNullToNotNullAttribute
}
}
}
src/EFCore.PG/Query/Expressions/Internal/ArrayAnyExpression.cs
→
src/EFCore.PG/Query/Expressions/Internal/ArrayAny
All
Expression.cs
浏览文件 @
41678ef4
#
region
License
// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
...
...
@@ -19,10 +20,10 @@
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion
using
System
;
using
System.Diagnostics
;
using
System.Linq.Expressions
;
using
JetBrains.Annotations
;
using
Npgsql.EntityFrameworkCore.PostgreSQL.Query.Sql.Internal
;
...
...
@@ -31,137 +32,125 @@
namespace
Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal
{
/// <summary>
///
Represents a PostgreSQL ANY expression (e.g. scalar = ANY (array))
///
Represents a PostgreSQL array ANY or ALL expression.
/// </summary>
/// <example>
/// 1 = ANY ('{0,1,2}'), 'cat' LIKE ANY ('{a%,b%,c%}')
/// </example>
/// <remarks>
/// See https://www.postgresql.org/docs/current/static/functions-comparisons.html
/// </remarks>
public
class
ArrayAny
Expression
:
Expression
public
class
ArrayAny
AllExpression
:
Expression
,
IEquatable
<
ArrayAnyAllExpression
>
{
/// <summary>
/// Creates a new instance of InExpression.
/// </summary>
/// <param name="operand"> The operand. </param>
/// <param name="array"> The array. </param>
public
ArrayAnyExpression
(
[
NotNull
]
Expression
operand
,
[
NotNull
]
Expression
array
)
{
Check
.
NotNull
(
operand
,
nameof
(
operand
));
Check
.
NotNull
(
array
,
nameof
(
array
));
Debug
.
Assert
(
array
.
Type
.
IsArray
);
/// <inheritdoc />
public
override
ExpressionType
NodeType
{
get
;
}
=
ExpressionType
.
Extension
;
Operand
=
operand
;
Array
=
array
;
}
/// <inheritdoc />
public
override
Type
Type
{
get
;
}
=
typeof
(
bool
);
/// <summary>
///
Gets the operand
.
///
The value to test against the <see cref="Array"/>
.
/// </summary>
/// <value>
/// The operand.
/// </value>
public
virtual
Expression
Operand
{
get
;
}
/// <summary>
///
Gets the array
.
///
The array of values or patterns to test for the <see cref="Operand"/>
.
/// </summary>
/// <value>
/// The array.
/// </value>
public
virtual
Expression
Array
{
get
;
}
/// <summary>
///
Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
///
The operator.
/// </summary>
/// <returns>The <see cref="ExpressionType" /> that represents this expression.</returns>
public
override
ExpressionType
NodeType
=>
ExpressionType
.
Extension
;
public
virtual
string
Operator
{
get
;
}
/// <summary>
///
Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression" />.)
///
The comparison type.
/// </summary>
/// <returns>The <see cref="Type" /> that represents the static type of the expression.</returns>
public
override
Type
Type
=>
typeof
(
bool
);
public
virtual
ArrayComparisonType
ArrayComparisonType
{
get
;
}
/// <summary>
///
Dispatches to the specific visit method for this node type
.
///
Constructs a <see cref="ArrayAnyAllExpression"/>
.
/// </summary>
protected
override
Expression
Accept
(
ExpressionVisitor
visitor
)
/// <param name="arrayComparisonType">The comparison type.</param>
/// <param name="operatorSymbol">The operator symbol to the array expression.</param>
/// <param name="operand">The value to find.</param>
/// <param name="array">The array to search.</param>
/// <exception cref="ArgumentNullException" />
public
ArrayAnyAllExpression
(
ArrayComparisonType
arrayComparisonType
,
[
NotNull
]
string
operatorSymbol
,
[
NotNull
]
Expression
operand
,
[
NotNull
]
Expression
array
)
{
Check
.
NotNull
(
visitor
,
nameof
(
visitor
));
Check
.
NotNull
(
array
,
nameof
(
operatorSymbol
));
Check
.
NotNull
(
operand
,
nameof
(
operand
));
Check
.
NotNull
(
array
,
nameof
(
array
));
return
visitor
is
NpgsqlQuerySqlGenerator
npsgqlGenerator
?
npsgqlGenerator
.
VisitArrayAny
(
this
)
:
base
.
Accept
(
visitor
);
ArrayComparisonType
=
arrayComparisonType
;
Operator
=
operatorSymbol
;
Operand
=
operand
;
Array
=
array
;
}
/// <summary>
/// Reduces the node and then calls the <see cref="ExpressionVisitor.Visit(System.Linq.Expressions.Expression)" /> method passing the
/// reduced expression.
/// Throws an exception if the node isn't reducible.
/// </summary>
/// <param name="visitor"> An instance of <see cref="ExpressionVisitor" />. </param>
/// <returns> The expression being visited, or an expression which should replace it in the tree. </returns>
/// <remarks>
/// Override this method to provide logic to walk the node's children.
/// A typical implementation will call visitor.Visit on each of its
/// children, and if any of them change, should return a new copy of
/// itself with the modified children.
/// </remarks>
///
/// <inheritdoc />
protected
override
Expression
Accept
(
ExpressionVisitor
visitor
)
=>
visitor
is
NpgsqlQuerySqlGenerator
npsgqlGenerator
?
npsgqlGenerator
.
VisitArrayAnyAll
(
this
)
:
base
.
Accept
(
visitor
);
/// <inheritdoc />
protected
override
Expression
VisitChildren
(
ExpressionVisitor
visitor
)
{
var
newOperand
=
visitor
.
Visit
(
Operand
);
var
newArray
=
visitor
.
Visit
(
Array
);
if
(!(
visitor
.
Visit
(
Operand
)
is
Expression
operand
))
throw
new
ArgumentException
(
$"The
{
nameof
(
operand
)}
of a
{
nameof
(
ArrayAnyAllExpression
)}
cannot be null."
);
return
newOperand
!=
Operand
||
newArray
!=
Array
?
new
ArrayAnyExpression
(
newOperand
,
newArray
)
:
this
;
if
(!(
visitor
.
Visit
(
Array
)
is
Expression
collection
))
throw
new
ArgumentException
(
$"The
{
nameof
(
collection
)}
of a
{
nameof
(
ArrayAnyAllExpression
)}
cannot be null."
);
return
operand
==
Operand
&&
collection
==
Array
?
this
:
new
ArrayAnyAllExpression
(
ArrayComparisonType
,
Operator
,
operand
,
collection
);
}
/// <summary>
/// Tests if this object is considered equal to another.
/// </summary>
/// <param name="obj"> The object to compare with the current object. </param>
/// <returns>
/// true if the objects are considered equal, false if they are not.
/// </returns>
/// <inheritdoc />
public
override
bool
Equals
(
object
obj
)
{
if
(
ReferenceEquals
(
null
,
obj
))
{
return
false
;
}
=>
obj
is
ArrayAnyAllExpression
likeAnyExpression
&&
Equals
(
likeAnyExpression
);
if
(
ReferenceEquals
(
this
,
obj
))
{
return
true
;
}
/// <inheritdoc />
public
bool
Equals
(
ArrayAnyAllExpression
other
)
=>
Operand
.
Equals
(
other
?.
Operand
)
&&
Operator
.
Equals
(
other
?.
Operator
)
&&
ArrayComparisonType
.
Equals
(
other
?.
ArrayComparisonType
)
&&
Array
.
Equals
(
other
?.
Array
);
return
obj
.
GetType
()
==
GetType
()
&&
Equals
((
ArrayAnyExpression
)
obj
);
}
bool
Equals
(
ArrayAnyExpression
other
)
=>
Operand
.
Equals
(
other
.
Operand
)
&&
Array
.
Equals
(
other
.
Array
);
/// <inheritdoc />
public
override
int
GetHashCode
()
=>
unchecked
((
397
*
Operand
.
GetHashCode
())
^
(
397
*
Operator
.
GetHashCode
())
^
(
397
*
ArrayComparisonType
.
GetHashCode
())
^
(
397
*
Array
.
GetHashCode
()));
/// <inheritdoc />
public
override
string
ToString
()
=>
$"
{
Operand
}
{
Operator
}
{
ArrayComparisonType
.
ToString
()}
(
{
Array
}
)"
;
}
/// <summary>
/// Represents whether an array comparison is ANY or ALL.
/// </summary>
public
enum
ArrayComparisonType
{
// ReSharper disable once InconsistentNaming
/// <summary>
///
Returns a hash code for this object
.
///
Represents an ANY array comparison
.
/// </summary>
/// <returns>
/// A hash code for this object.
/// </returns>
public
override
int
GetHashCode
()
{
unchecked
{
return
(
Operand
.
GetHashCode
()
*
397
)
^
Array
.
GetHashCode
();
}
}
ANY
,
// ReSharper disable once InconsistentNaming
/// <summary>
///
Creates a <see cref="string" /> representation of the Expressi
on.
///
Represents an ALL array comparis
on.
/// </summary>
/// <returns>A <see cref="string" /> representation of the Expression.</returns>
public
override
string
ToString
()
=>
$"
{
Operand
}
= ANY (
{
Array
}
)"
;
ALL
}
}
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
浏览文件 @
41678ef4
...
...
@@ -41,8 +41,9 @@ public class NpgsqlQuerySqlGenerator : DefaultQuerySqlGenerator
{
readonly
bool
_reverseNullOrderingEnabled
;
protected
override
string
TypedTrueLiteral
=>
"TRUE::bool"
;
protected
override
string
TypedFalseLiteral
=>
"FALSE::bool"
;
protected
override
string
TypedTrueLiteral
{
get
;
}
=
"TRUE::bool"
;
protected
override
string
TypedFalseLiteral
{
get
;
}
=
"FALSE::bool"
;
public
NpgsqlQuerySqlGenerator
(
[
NotNull
]
QuerySqlGeneratorDependencies
dependencies
,
...
...
@@ -176,23 +177,34 @@ protected virtual void VisitArrayIndex([NotNull] BinaryExpression expression)
Sql
.
Append
(
']'
);
}
public
Expression
VisitArrayAny
(
ArrayAnyExpression
arrayAnyExpression
)
/// <summary>
/// Produces expressions like: 1 = ANY ('{0,1,2}') or 'cat' LIKE ANY ('{a%,b%,c%}').
/// </summary>
public
Expression
VisitArrayAnyAll
(
ArrayAnyAllExpression
arrayAnyAllExpression
)
{
Visit
(
arrayAnyExpression
.
Operand
);
Sql
.
Append
(
" = ANY ("
);
Visit
(
arrayAnyExpression
.
Array
);
Sql
.
Append
(
")"
);
return
arrayAnyExpression
;
Visit
(
arrayAnyAllExpression
.
Operand
);
Sql
.
Append
(
' '
);
Sql
.
Append
(
arrayAnyAllExpression
.
Operator
);
Sql
.
Append
(
' '
);
Sql
.
Append
(
arrayAnyAllExpression
.
ArrayComparisonType
.
ToString
());
Sql
.
Append
(
" ("
);
Visit
(
arrayAnyAllExpression
.
Array
);
Sql
.
Append
(
')'
);
return
arrayAnyAllExpression
;
}
// PostgreSQL array indexing is 1-based. If the index happens to be a constant,
// just increment it. Otherwise, append a +1 in the SQL.
Expression
GenerateOneBasedIndexExpression
(
Expression
expression
)
/// <summary>
/// PostgreSQL array indexing is 1-based. If the index happens to be a constant,
/// just increment it. Otherwise, append a +1 in the SQL.
/// </summary>
static
Expression
GenerateOneBasedIndexExpression
(
Expression
expression
)
=>
expression
is
ConstantExpression
constantExpression
?
Expression
.
Constant
(
Convert
.
ToInt32
(
constantExpression
.
Value
)
+
1
)
:
(
Expression
)
Expression
.
Add
(
expression
,
Expression
.
Constant
(
1
));
// See http://www.postgresql.org/docs/current/static/functions-matching.html
/// <summary>
/// See: http://www.postgresql.org/docs/current/static/functions-matching.html
/// </summary>
public
Expression
VisitRegexMatch
([
NotNull
]
RegexMatchExpression
regexMatchExpression
)
{
Check
.
NotNull
(
regexMatchExpression
,
nameof
(
regexMatchExpression
));
...
...
test/EFCore.PG.FunctionalTests/Query/LikeAnyAllQueryNpgsqlTest.cs
0 → 100644
浏览文件 @
41678ef4
using
System
;
using
System.Linq
;
using
Microsoft.EntityFrameworkCore
;
using
Microsoft.EntityFrameworkCore.TestUtilities
;
using
Microsoft.Extensions.DependencyInjection
;
using
Microsoft.Extensions.Logging
;
using
Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities
;
using
Xunit
;
namespace
Npgsql.EntityFrameworkCore.PostgreSQL.Query
{
public
class
LikeAnyAllQueryNpgsqlTest
:
IClassFixture
<
LikeAnyAllQueryNpgsqlTest
.
LikeAnyQueryNpgsqlFixture
>
{
#
region
Setup
/// <summary>
/// Provides resources for unit tests.
/// </summary>
LikeAnyQueryNpgsqlFixture
Fixture
{
get
;
}
/// <summary>
/// Initializes resources for unit tests.
/// </summary>
/// <param name="fixture">The fixture of resources for testing.</param>
public
LikeAnyAllQueryNpgsqlTest
(
LikeAnyQueryNpgsqlFixture
fixture
)
{
Fixture
=
fixture
;
Fixture
.
TestSqlLoggerFactory
.
Clear
();
}
#
endregion
#
region
LikeTests
[
Fact
]
public
void
Array_Any_Like
()
{
using
(
LikeAnyContext
context
=
Fixture
.
CreateContext
())
{
var
collection
=
new
string
[]
{
"a"
,
"b"
,
"c"
};
LikeAnyTestEntity
[]
_
=
context
.
LikeAnyTestEntities
.
Where
(
x
=>
collection
.
Any
(
y
=>
EF
.
Functions
.
Like
(
x
.
Animal
,
y
)))
.
ToArray
();
AssertContainsSql
(
"WHERE x.\"Animal\" LIKE ANY (@__collection_0) = TRUE"
);
}
}
[
Fact
]
public
void
Array_All_Like
()
{
using
(
LikeAnyContext
context
=
Fixture
.
CreateContext
())
{
var
collection
=
new
string
[]
{
"a"
,
"b"
,
"c"
};
LikeAnyTestEntity
[]
_
=
context
.
LikeAnyTestEntities
.
Where
(
x
=>
collection
.
All
(
y
=>
EF
.
Functions
.
Like
(
x
.
Animal
,
y
)))
.
ToArray
();
AssertContainsSql
(
"WHERE x.\"Animal\" LIKE ALL (@__collection_0) = TRUE"
);
}
}
#
endregion
#
region
ILikeTests
[
Fact
]
public
void
Array_Any_ILike
()
{
using
(
LikeAnyContext
context
=
Fixture
.
CreateContext
())
{
var
collection
=
new
string
[]
{
"a"
,
"b"
,
"c%"
};
LikeAnyTestEntity
[]
_
=
context
.
LikeAnyTestEntities
.
Where
(
x
=>
collection
.
Any
(
y
=>
EF
.
Functions
.
ILike
(
x
.
Animal
,
y
)))
.
ToArray
();
AssertContainsSql
(
"WHERE x.\"Animal\" ILIKE ANY (@__collection_0) = TRUE"
);
}
}
[
Fact
]
public
void
Array_All_ILike
()
{
using
(
LikeAnyContext
context
=
Fixture
.
CreateContext
())
{
var
collection
=
new
string
[]
{
"a"
,
"b"
,
"c%"
};
LikeAnyTestEntity
[]
_
=
context
.
LikeAnyTestEntities
.
Where
(
x
=>
collection
.
All
(
y
=>
EF
.
Functions
.
ILike
(
x
.
Animal
,
y
)))
.
ToArray
();
AssertContainsSql
(
"WHERE x.\"Animal\" ILIKE ALL (@__collection_0) = TRUE"
);
}
}
#
endregion
#
region
Fixtures
/// <summary>
/// Represents a fixture suitable for testing LIKE ANY expressions.
/// </summary>
public
class
LikeAnyQueryNpgsqlFixture
:
IDisposable
{
/// <summary>
/// The <see cref="NpgsqlTestStore"/> used for testing.
/// </summary>
readonly
NpgsqlTestStore
_testStore
;
/// <summary>
/// The <see cref="DbContextOptions"/> used for testing.
/// </summary>
readonly
DbContextOptions
_options
;
/// <summary>
/// The logger factory used for testing.
/// </summary>
public
TestSqlLoggerFactory
TestSqlLoggerFactory
{
get
;
}
/// <summary>
/// Initializes a <see cref="LikeAnyQueryNpgsqlFixture"/>.
/// </summary>
public
LikeAnyQueryNpgsqlFixture
()
{
TestSqlLoggerFactory
=
new
TestSqlLoggerFactory
();
_testStore
=
NpgsqlTestStore
.
CreateScratch
();
_options
=
new
DbContextOptionsBuilder
()
.
UseNpgsql
(
_testStore
.
ConnectionString
,
b
=>
b
.
ApplyConfiguration
())
.
UseInternalServiceProvider
(
new
ServiceCollection
()
.
AddEntityFrameworkNpgsql
()
.
AddSingleton
<
ILoggerFactory
>(
TestSqlLoggerFactory
)
.
BuildServiceProvider
())
.
Options
;
using
(
LikeAnyContext
context
=
CreateContext
())
{
context
.
Database
.
EnsureCreated
();
context
.
LikeAnyTestEntities
.
AddRange
(
new
LikeAnyTestEntity
{
Id
=
1
,
Animal
=
"cat"
},
new
LikeAnyTestEntity
{
Id
=
2
,
Animal
=
"dog"
},
new
LikeAnyTestEntity
{
Id
=
3
,
Animal
=
"turtle"
},
new
LikeAnyTestEntity
{
Id
=
4
,
Animal
=
"bird"
});
context
.
SaveChanges
();
}
}
/// <summary>
/// Creates a new <see cref="LikeAnyContext"/>.
/// </summary>
/// <returns>
/// A <see cref="LikeAnyContext"/> for testing.
/// </returns>
public
LikeAnyContext
CreateContext
()
=>
new
LikeAnyContext
(
_options
);
/// <inheritdoc />
public
void
Dispose
()
=>
_testStore
.
Dispose
();
}
/// <summary>
/// Represents an entity suitable for testing LIKE ANY expressions.
/// </summary>
public
class
LikeAnyTestEntity
{
/// <summary>
/// The primary key.
/// </summary>
public
int
Id
{
get
;
set
;
}
/// <summary>
/// The value.
/// </summary>
public
string
Animal
{
get
;
set
;
}
}
/// <summary>
/// Represents a database suitable for testing range operators.
/// </summary>
public
class
LikeAnyContext
:
DbContext
{
/// <summary>
/// Represents a set of entities with a string property.
/// </summary>
public
DbSet
<
LikeAnyTestEntity
>
LikeAnyTestEntities
{
get
;
set
;
}
/// <summary>
/// Initializes a <see cref="LikeAnyContext"/>.
/// </summary>
/// <param name="options">
/// The options to be used for configuration.
/// </param>
public
LikeAnyContext
(
DbContextOptions
options
)
:
base
(
options
)
{
}
}
#
endregion
#
region
Helpers
/// <summary>
/// Asserts that the SQL fragment appears in the logs.
/// </summary>
/// <param name="sql">The SQL statement or fragment to search for in the logs.</param>
public
void
AssertContainsSql
(
string
sql
)
=>
Assert
.
Contains
(
sql
,
Fixture
.
TestSqlLoggerFactory
.
Sql
);
#
endregion
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录