Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
jobily
Efcore.Pg
提交
b79fd0cf
E
Efcore.Pg
项目概览
jobily
/
Efcore.Pg
11 个月 前同步成功
通知
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,发现更多精彩内容 >>
提交
b79fd0cf
编写于
11月 03, 2016
作者:
S
Shay Rojansky
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Array operation translation
Array indexing, length, SequenceEqual, Contains Closes #120
上级
53fe45a1
变更
11
隐藏空白更改
内联
并排
Showing
11 changed file
with
706 addition
and
27 deletion
+706
-27
src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs
...EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs
+2
-0
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArraySequenceEqualTranslator.cs
...ranslators/Internal/NpgsqlArraySequenceEqualTranslator.cs
+63
-0
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs
...anslators/Internal/NpgsqlCompositeMethodCallTranslator.cs
+3
-2
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
...pressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
+83
-0
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitorFactory.cs
...nVisitors/NpgsqlSqlTranslatingExpressionVisitorFactory.cs
+41
-0
src/EFCore.PG/Query/Expressions/Internal/ArrayAnyExpression.cs
...FCore.PG/Query/Expressions/Internal/ArrayAnyExpression.cs
+167
-0
src/EFCore.PG/Query/Expressions/Internal/AtTimeZoneExpression.cs
...ore.PG/Query/Expressions/Internal/AtTimeZoneExpression.cs
+2
-4
src/EFCore.PG/Query/Expressions/Internal/RegexMatchExpression.cs
...ore.PG/Query/Expressions/Internal/RegexMatchExpression.cs
+3
-1
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
+83
-19
src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs
...ore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs
+19
-1
test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs
test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs
+240
-0
未找到文件。
src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs
浏览文件 @
b79fd0cf
...
...
@@ -33,6 +33,7 @@
using
Microsoft.EntityFrameworkCore.Query
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors
;
using
Microsoft.EntityFrameworkCore.Query.Internal
;
using
Microsoft.EntityFrameworkCore.Query.Sql
;
using
Microsoft.EntityFrameworkCore.Query.Sql.Internal
;
...
...
@@ -104,6 +105,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql([NotNull] this IServic
.
TryAdd
<
IMemberTranslator
,
NpgsqlCompositeMemberTranslator
>()
.
TryAdd
<
ICompositeMethodCallTranslator
,
NpgsqlCompositeMethodCallTranslator
>()
.
TryAdd
<
IQuerySqlGeneratorFactory
,
NpgsqlQuerySqlGeneratorFactory
>()
.
TryAdd
<
ISqlTranslatingExpressionVisitorFactory
,
NpgsqlSqlTranslatingExpressionVisitorFactory
>()
.
TryAddProviderSpecificServices
(
b
=>
b
.
TryAddSingleton
<
INpgsqlValueGeneratorCache
,
NpgsqlValueGeneratorCache
>()
.
TryAddScoped
<
INpgsqlSequenceValueGeneratorFactory
,
NpgsqlSequenceValueGeneratorFactory
>()
...
...
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlArraySequenceEqualTranslator.cs
0 → 100644
浏览文件 @
b79fd0cf
#
region
License
// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// 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.Linq
;
using
System.Linq.Expressions
;
using
System.Reflection
;
using
System.Text.RegularExpressions
;
using
JetBrains.Annotations
;
using
Microsoft.EntityFrameworkCore.Query.Expressions.Internal
;
namespace
Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal
{
/// <summary>
/// Translates Enumerable.SequenceEqual on arrays into PostgreSQL array equality operations.
/// </summary>
/// <remarks>
/// https://www.postgresql.org/docs/current/static/functions-array.html
/// </remarks>
public
class
NpgsqlArraySequenceEqualTranslator
:
IMethodCallTranslator
{
static
readonly
MethodInfo
SequenceEqualMethodInfo
=
typeof
(
Enumerable
).
GetTypeInfo
().
GetDeclaredMethods
(
nameof
(
Enumerable
.
SequenceEqual
)).
Single
(
m
=>
m
.
IsGenericMethodDefinition
&&
m
.
GetParameters
().
Length
==
2
);
[
CanBeNull
]
public
Expression
Translate
(
MethodCallExpression
methodCallExpression
)
{
var
method
=
methodCallExpression
.
Method
;
if
(
method
.
IsGenericMethod
&&
ReferenceEquals
(
method
.
GetGenericMethodDefinition
(),
SequenceEqualMethodInfo
)
&&
methodCallExpression
.
Arguments
.
All
(
a
=>
a
.
Type
.
IsArray
))
{
return
Expression
.
MakeBinary
(
ExpressionType
.
Equal
,
methodCallExpression
.
Arguments
[
0
],
methodCallExpression
.
Arguments
[
1
]);
}
return
null
;
}
}
}
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCompositeMethodCallTranslator.cs
浏览文件 @
b79fd0cf
...
...
@@ -28,8 +28,9 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal
{
public
class
NpgsqlCompositeMethodCallTranslator
:
RelationalCompositeMethodCallTranslator
{
private
static
readonly
IMethodCallTranslator
[]
_methodCallTranslators
=
static
readonly
IMethodCallTranslator
[]
_methodCallTranslators
=
{
new
NpgsqlArraySequenceEqualTranslator
(),
new
NpgsqlConvertTranslator
(),
new
NpgsqlStringSubstringTranslator
(),
new
NpgsqlLikeTranslator
(),
...
...
@@ -46,7 +47,7 @@ public class NpgsqlCompositeMethodCallTranslator : RelationalCompositeMethodCall
new
NpgsqlStringTrimTranslator
(),
new
NpgsqlStringTrimEndTranslator
(),
new
NpgsqlStringTrimStartTranslator
(),
new
NpgsqlRegexIsMatchTranslator
()
,
new
NpgsqlRegexIsMatchTranslator
()
};
public
NpgsqlCompositeMethodCallTranslator
(
...
...
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitor.cs
0 → 100644
浏览文件 @
b79fd0cf
using
System.Linq
;
using
System.Linq.Expressions
;
using
JetBrains.Annotations
;
using
Microsoft.EntityFrameworkCore.Query.Expressions
;
using
Microsoft.EntityFrameworkCore.Query.Expressions.Internal
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal
;
using
Remotion.Linq.Clauses.Expressions
;
using
Remotion.Linq.Clauses.ResultOperators
;
namespace
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors
{
public
class
NpgsqlSqlTranslatingExpressionVisitor
:
SqlTranslatingExpressionVisitor
{
private
readonly
RelationalQueryModelVisitor
_queryModelVisitor
;
public
NpgsqlSqlTranslatingExpressionVisitor
(
[
NotNull
]
SqlTranslatingExpressionVisitorDependencies
dependencies
,
[
NotNull
]
RelationalQueryModelVisitor
queryModelVisitor
,
[
CanBeNull
]
SelectExpression
targetSelectExpression
=
null
,
[
CanBeNull
]
Expression
topLevelPredicate
=
null
,
bool
inProjection
=
false
)
:
base
(
dependencies
,
queryModelVisitor
,
targetSelectExpression
,
topLevelPredicate
,
inProjection
)
{
_queryModelVisitor
=
queryModelVisitor
;
}
protected
override
Expression
VisitSubQuery
(
SubQueryExpression
expression
)
{
// Prefer the default EF Core translation if one exists
var
result
=
base
.
VisitSubQuery
(
expression
);
if
(
result
!=
null
)
return
result
;
var
subQueryModel
=
expression
.
QueryModel
;
var
fromExpression
=
subQueryModel
.
MainFromClause
.
FromExpression
;
var
properties
=
MemberAccessBindingExpressionVisitor
.
GetPropertyPath
(
fromExpression
,
_queryModelVisitor
.
QueryCompilationContext
,
out
var
qsre
);
if
(
properties
.
Count
==
0
)
return
null
;
var
lastPropertyType
=
properties
[
properties
.
Count
-
1
].
ClrType
;
if
(
lastPropertyType
.
IsArray
&&
lastPropertyType
.
GetArrayRank
()
==
1
)
{
// Translate someArray.Length
if
(
subQueryModel
.
ResultOperators
.
First
()
is
CountResultOperator
)
return
Expression
.
ArrayLength
(
Visit
(
fromExpression
));
// Translate someArray.Contains(someValue)
if
(
subQueryModel
.
ResultOperators
.
First
()
is
ContainsResultOperator
contains
)
{
var
containsItem
=
Visit
(
contains
.
Item
);
if
(
containsItem
!=
null
)
return
new
ArrayAnyExpression
(
containsItem
,
Visit
(
fromExpression
));
}
}
return
null
;
}
protected
override
Expression
VisitBinary
(
BinaryExpression
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
);
return
left
!=
null
&&
right
!=
null
?
Expression
.
MakeBinary
(
ExpressionType
.
ArrayIndex
,
left
,
right
)
:
null
;
}
}
return
base
.
VisitBinary
(
expression
);
}
}
}
src/EFCore.PG/Query/ExpressionVisitors/NpgsqlSqlTranslatingExpressionVisitorFactory.cs
0 → 100644
浏览文件 @
b79fd0cf
using
System.Linq.Expressions
;
using
JetBrains.Annotations
;
using
Microsoft.EntityFrameworkCore.Query.Expressions
;
using
Microsoft.EntityFrameworkCore.Query.ExpressionTranslators
;
using
Microsoft.EntityFrameworkCore.Storage
;
using
Microsoft.EntityFrameworkCore.Utilities
;
namespace
Microsoft.EntityFrameworkCore.Query.ExpressionVisitors
{
public
class
NpgsqlSqlTranslatingExpressionVisitorFactory
:
SqlTranslatingExpressionVisitorFactory
{
/// <summary>
/// Creates a new instance of <see cref="SqlTranslatingExpressionVisitorFactory" />.
/// </summary>
/// <param name="dependencies"> Parameter object containing dependencies for this service. </param>
public
NpgsqlSqlTranslatingExpressionVisitorFactory
([
NotNull
]
SqlTranslatingExpressionVisitorDependencies
dependencies
)
:
base
(
dependencies
)
{}
/// <summary>
/// Creates a new NpgsqlTranslatingExpressionVisitor.
/// </summary>
/// <param name="queryModelVisitor"> The query model visitor. </param>
/// <param name="targetSelectExpression"> The target select expression. </param>
/// <param name="topLevelPredicate"> The top level predicate. </param>
/// <param name="inProjection"> true if we are translating a projection. </param>
/// <returns>
/// A SqlTranslatingExpressionVisitor.
/// </returns>
public
override
SqlTranslatingExpressionVisitor
Create
(
RelationalQueryModelVisitor
queryModelVisitor
,
SelectExpression
targetSelectExpression
=
null
,
Expression
topLevelPredicate
=
null
,
bool
inProjection
=
false
)
=>
new
NpgsqlSqlTranslatingExpressionVisitor
(
Dependencies
,
Check
.
NotNull
(
queryModelVisitor
,
nameof
(
queryModelVisitor
)),
targetSelectExpression
,
topLevelPredicate
,
inProjection
);
}
}
src/EFCore.PG/Query/Expressions/Internal/ArrayAnyExpression.cs
0 → 100644
浏览文件 @
b79fd0cf
#
region
License
// The PostgreSQL License
//
// Copyright (C) 2016 The Npgsql Development Team
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// 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
Microsoft.EntityFrameworkCore.Query.Sql.Internal
;
using
Microsoft.EntityFrameworkCore.Utilities
;
namespace
Microsoft.EntityFrameworkCore.Query.Expressions.Internal
{
/// <summary>
/// Represents a PostgreSQL ANY expression (e.g. scalar = ANY (array))
/// </summary>
/// <remarks>
/// See https://www.postgresql.org/docs/current/static/functions-comparisons.html
/// </remarks>
public
class
ArrayAnyExpression
:
Expression
{
/// <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
);
Operand
=
operand
;
Array
=
array
;
}
/// <summary>
/// Gets the operand.
/// </summary>
/// <value>
/// The operand.
/// </value>
public
virtual
Expression
Operand
{
get
;
}
/// <summary>
/// Gets the array.
/// </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" />.)
/// </summary>
/// <returns>The <see cref="ExpressionType" /> that represents this expression.</returns>
public
override
ExpressionType
NodeType
=>
ExpressionType
.
Extension
;
/// <summary>
/// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression" />.)
/// </summary>
/// <returns>The <see cref="Type" /> that represents the static type of the expression.</returns>
public
override
Type
Type
=>
typeof
(
bool
);
/// <summary>
/// Dispatches to the specific visit method for this node type.
/// </summary>
protected
override
Expression
Accept
(
ExpressionVisitor
visitor
)
{
Check
.
NotNull
(
visitor
,
nameof
(
visitor
));
return
visitor
is
NpgsqlQuerySqlGenerator
npsgqlGenerator
?
npsgqlGenerator
.
VisitArrayAny
(
this
)
:
base
.
Accept
(
visitor
);
}
/// <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>
///
protected
override
Expression
VisitChildren
(
ExpressionVisitor
visitor
)
{
var
newOperand
=
visitor
.
Visit
(
Operand
);
var
newArray
=
visitor
.
Visit
(
Array
);
return
newOperand
!=
Operand
||
newArray
!=
Array
?
new
ArrayAnyExpression
(
newOperand
,
newArray
)
:
this
;
}
/// <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>
public
override
bool
Equals
(
object
obj
)
{
if
(
ReferenceEquals
(
null
,
obj
))
{
return
false
;
}
if
(
ReferenceEquals
(
this
,
obj
))
{
return
true
;
}
return
obj
.
GetType
()
==
GetType
()
&&
Equals
((
ArrayAnyExpression
)
obj
);
}
bool
Equals
(
ArrayAnyExpression
other
)
=>
Operand
.
Equals
(
other
.
Operand
)
&&
Array
.
Equals
(
other
.
Array
);
/// <summary>
/// Returns a hash code for this object.
/// </summary>
/// <returns>
/// A hash code for this object.
/// </returns>
public
override
int
GetHashCode
()
{
unchecked
{
return
(
Operand
.
GetHashCode
()
*
397
)
^
Array
.
GetHashCode
();
}
}
/// <summary>
/// Creates a <see cref="string" /> representation of the Expression.
/// </summary>
/// <returns>A <see cref="string" /> representation of the Expression.</returns>
public
override
string
ToString
()
=>
$"
{
Operand
}
= ANY (
{
Array
}
)"
;
}
}
src/EFCore.PG/Query/Expressions/Internal/AtTimeZoneExpression.cs
浏览文件 @
b79fd0cf
...
...
@@ -51,10 +51,8 @@ protected override Expression Accept([NotNull] ExpressionVisitor visitor)
{
Check
.
NotNull
(
visitor
,
nameof
(
visitor
));
var
specificVisitor
=
visitor
as
NpgsqlQuerySqlGenerator
;
return
specificVisitor
!=
null
?
specificVisitor
.
VisitAtTimeZone
(
this
)
return
visitor
is
NpgsqlQuerySqlGenerator
npgsqlGenerator
?
npgsqlGenerator
.
VisitAtTimeZone
(
this
)
:
base
.
Accept
(
visitor
);
}
...
...
src/EFCore.PG/Query/Expressions/Internal/RegexMatchExpression.cs
浏览文件 @
b79fd0cf
...
...
@@ -54,7 +54,9 @@ protected override Expression Accept([NotNull] ExpressionVisitor visitor)
{
Check
.
NotNull
(
visitor
,
nameof
(
visitor
));
return
(
visitor
as
NpgsqlQuerySqlGenerator
)?.
VisitRegexMatch
(
this
)
??
base
.
Accept
(
visitor
);
return
visitor
is
NpgsqlQuerySqlGenerator
npsgqlGenerator
?
npsgqlGenerator
.
VisitRegexMatch
(
this
)
:
base
.
Accept
(
visitor
);
}
protected
override
Expression
VisitChildren
(
ExpressionVisitor
visitor
)
...
...
src/EFCore.PG/Query/Sql/Internal/NpgsqlQuerySqlGenerator.cs
浏览文件 @
b79fd0cf
...
...
@@ -22,18 +22,14 @@
#endregion
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Diagnostics
;
using
System.Linq.Expressions
;
using
System.Reflection
;
using
System.Text.RegularExpressions
;
using
JetBrains.Annotations
;
using
Microsoft.EntityFrameworkCore.Internal
;
using
Microsoft.EntityFrameworkCore.Query.Expressions
;
using
Microsoft.EntityFrameworkCore.Query.Expressions.Internal
;
using
Microsoft.EntityFrameworkCore.Storage
;
using
Microsoft.EntityFrameworkCore.Utilities
;
using
Remotion.Linq.Parsing
;
namespace
Microsoft.EntityFrameworkCore.Query.Sql.Internal
{
...
...
@@ -49,12 +45,12 @@ public class NpgsqlQuerySqlGenerator : DefaultQuerySqlGenerator
{
}
protected
override
void
GenerateTop
(
[
NotNull
]
SelectExpression
selectExpression
)
protected
override
void
GenerateTop
(
SelectExpression
selectExpression
)
{
// No TOP() in PostgreSQL, see GenerateLimitOffset
}
protected
override
void
GenerateLimitOffset
(
[
NotNull
]
SelectExpression
selectExpression
)
protected
override
void
GenerateLimitOffset
(
SelectExpression
selectExpression
)
{
Check
.
NotNull
(
selectExpression
,
nameof
(
selectExpression
));
...
...
@@ -103,24 +99,90 @@ public override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExp
return
expr
;
}
protected
override
Expression
VisitBinary
(
BinaryExpression
binaryE
xpression
)
protected
override
Expression
VisitBinary
(
BinaryExpression
e
xpression
)
{
// PostgreSQL 9.4 and below has some weird operator precedence fixed in 9.5 and described here:
// http://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=c6b3c939b7e0f1d35f4ed4996e71420a993810d2
// As a result we must surround string concatenation with parentheses
if
(
binaryExpression
.
NodeType
==
ExpressionType
.
Add
&&
binaryExpression
.
Left
.
Type
==
typeof
(
string
)
&&
binaryExpression
.
Right
.
Type
==
typeof
(
string
))
switch
(
expression
.
NodeType
)
{
case
ExpressionType
.
Add
:
{
Sql
.
Append
(
"("
);
var
exp
=
base
.
VisitBinary
(
binaryExpression
);
Sql
.
Append
(
")"
);
return
exp
;
// PostgreSQL 9.4 and below has some weird operator precedence fixed in 9.5 and described here:
// http://git.postgresql.org/gitweb/?p=postgresql.git&a=commitdiff&h=c6b3c939b7e0f1d35f4ed4996e71420a993810d2
// As a result we must surround string concatenation with parentheses
if
(
expression
.
Left
.
Type
==
typeof
(
string
)
&&
expression
.
Right
.
Type
==
typeof
(
string
))
{
Sql
.
Append
(
"("
);
var
exp
=
base
.
VisitBinary
(
expression
);
Sql
.
Append
(
")"
);
return
exp
;
}
break
;
}
return
base
.
VisitBinary
(
binaryExpression
);
case
ExpressionType
.
ArrayIndex
:
GenerateArrayIndex
(
expression
);
return
expression
;
}
return
base
.
VisitBinary
(
expression
);
}
protected
override
Expression
VisitUnary
(
UnaryExpression
expression
)
{
if
(
expression
.
NodeType
==
ExpressionType
.
ArrayLength
)
{
VisitSqlFunction
(
new
SqlFunctionExpression
(
"array_length"
,
typeof
(
int
),
new
[]
{
expression
.
Operand
,
Expression
.
Constant
(
1
)
}));
return
expression
;
}
return
base
.
VisitUnary
(
expression
);
}
void
GenerateArrayIndex
([
NotNull
]
BinaryExpression
expression
)
{
Debug
.
Assert
(
expression
.
NodeType
==
ExpressionType
.
ArrayIndex
);
if
(
expression
.
Left
.
Type
==
typeof
(
byte
[]))
{
// bytea cannot be subscripted, but there's get_byte
VisitSqlFunction
(
new
SqlFunctionExpression
(
"get_byte"
,
typeof
(
byte
),
new
[]
{
expression
.
Left
,
expression
.
Right
}));
return
;
}
if
(
expression
.
Left
.
Type
==
typeof
(
string
))
{
// text cannot be subscripted, use substr
// PostgreSQL substr() is 1-based.
VisitSqlFunction
(
new
SqlFunctionExpression
(
"substr"
,
typeof
(
char
),
new
[]
{
expression
.
Left
,
expression
.
Right
,
Expression
.
Constant
(
1
)
}));
return
;
}
// Regular array from here
Visit
(
expression
.
Left
);
Sql
.
Append
(
'['
);
Visit
(
GenerateOneBasedIndexExpression
(
expression
.
Right
));
Sql
.
Append
(
']'
);
}
public
Expression
VisitArrayAny
(
ArrayAnyExpression
arrayAnyExpression
)
{
Visit
(
arrayAnyExpression
.
Operand
);
Sql
.
Append
(
" = ANY ("
);
Visit
(
arrayAnyExpression
.
Array
);
Sql
.
Append
(
")"
);
return
arrayAnyExpression
;
}
// 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
)
=>
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
public
Expression
VisitRegexMatch
([
NotNull
]
RegexMatchExpression
regexMatchExpression
)
{
...
...
@@ -158,6 +220,7 @@ public Expression VisitRegexMatch([NotNull] RegexMatchExpression regexMatchExpre
Sql
.
Append
(
")' || "
);
Visit
(
regexMatchExpression
.
Pattern
);
Sql
.
Append
(
')'
);
return
regexMatchExpression
;
}
...
...
@@ -170,6 +233,7 @@ public Expression VisitAtTimeZone([NotNull] AtTimeZoneExpression atTimeZoneExpre
Sql
.
Append
(
" AT TIME ZONE '"
);
Sql
.
Append
(
atTimeZoneExpression
.
TimeZone
);
Sql
.
Append
(
'\''
);
return
atTimeZoneExpression
;
}
...
...
src/EFCore.PG/Storage/Internal/Mapping/NpgsqlArrayTypeMapping.cs
浏览文件 @
b79fd0cf
...
...
@@ -26,6 +26,7 @@
using
System.Data
;
using
System.Data.Common
;
using
System.Linq
;
using
System.Text
;
using
JetBrains.Annotations
;
using
Npgsql
;
using
NpgsqlTypes
;
...
...
@@ -49,6 +50,23 @@ public override RelationalTypeMapping Clone(string storeType, int? size)
=>
new
NpgsqlTypeMapping
(
storeType
,
ClrType
,
NpgsqlDbType
);
protected
override
string
GenerateNonNullSqlLiteral
(
object
value
)
=>
throw
new
NotSupportedException
(
"Can't generate array literals (yet)"
);
{
// Only support one-dimensional arrays (at least for now)
var
arr
=
(
Array
)
value
;
if
(
arr
.
Rank
!=
1
)
throw
new
NotSupportedException
(
"Multidimensional array literals aren't supported yet"
);
var
sb
=
new
StringBuilder
();
sb
.
Append
(
"ARRAY["
);
for
(
var
i
=
0
;
i
<
arr
.
Length
;
i
++)
{
sb
.
Append
(
ElementMapping
.
GenerateSqlLiteral
(
arr
.
GetValue
(
i
)));
if
(
i
<
arr
.
Length
-
1
)
sb
.
Append
(
","
);
}
sb
.
Append
(
"]"
);
return
sb
.
ToString
();
}
}
}
test/EFCore.PG.FunctionalTests/Query/ArrayQueryTest.cs
0 → 100644
浏览文件 @
b79fd0cf
using
System
;
using
System.Linq
;
using
Microsoft.EntityFrameworkCore
;
using
Microsoft.EntityFrameworkCore.Utilities
;
using
Microsoft.Extensions.DependencyInjection
;
using
Microsoft.Extensions.Logging
;
using
Xunit
;
namespace
Microsoft.EntityFrameworkCore.Query
{
public
class
ArrayQueryTest
:
IClassFixture
<
ArrayFixture
>
{
[
Fact
]
public
void
Roundtrip
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
Id
==
1
);
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
}
}
[
Fact
]
public
void
Index_with_constant
()
{
using
(
var
ctx
=
CreateContext
())
{
var
actual
=
ctx
.
SomeEntities
.
Where
(
e
=>
e
.
SomeArray
[
0
]
==
3
).
ToList
();
Assert
.
Equal
(
1
,
actual
.
Count
);
AssertContainsInSql
(
@"WHERE (""e"".""SomeArray""[1]) = 3"
);
}
}
[
Fact
]
public
void
Index_with_non_constant
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
0
;
var
actual
=
ctx
.
SomeEntities
.
Where
(
e
=>
e
.
SomeArray
[
x
]
==
3
).
ToList
();
Assert
.
Equal
(
1
,
actual
.
Count
);
AssertContainsInSql
(
@"WHERE (""e"".""SomeArray""[@__x_0 + 1]) = 3"
);
}
}
[
Fact
]
public
void
Index_bytea_with_constant
()
{
using
(
var
ctx
=
CreateContext
())
{
var
actual
=
ctx
.
SomeEntities
.
Where
(
e
=>
e
.
SomeBytea
[
0
]
==
3
).
ToList
();
Assert
.
Equal
(
1
,
actual
.
Count
);
AssertContainsInSql
(
@"WHERE (get_byte(""e"".""SomeBytea"", 0)) = 3"
);
}
}
[
Fact
]
public
void
Index_multidimensional
()
{
using
(
var
ctx
=
CreateContext
())
{
// Operations on multidimensional arrays aren't mapped to SQL yet
var
actual
=
ctx
.
SomeEntities
.
Where
(
e
=>
e
.
SomeMatrix
[
0
,
0
]
==
5
).
ToList
();
Assert
.
Equal
(
1
,
actual
.
Count
);
}
}
[
Fact
]
public
void
SequenceEqual_with_parameter
()
{
using
(
var
ctx
=
CreateContext
())
{
var
arr
=
new
[]
{
3
,
4
};
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
SequenceEqual
(
arr
));
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE ""e"".""SomeArray"" = @"
);
}
}
[
Fact
]
public
void
SequenceEqual_with_array_literal
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
SequenceEqual
(
new
[]
{
3
,
4
}));
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE ""e"".""SomeArray"" = ARRAY[3,4]"
);
}
}
[
Fact
]
public
void
Contains_with_literal
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
Contains
(
3
));
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE 3 = ANY (""e"".""SomeArray"")"
);
}
}
[
Fact
]
public
void
Contains_with_parameter
()
{
using
(
var
ctx
=
CreateContext
())
{
var
p
=
3
;
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
Contains
(
p
));
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE @__p_0 = ANY (""e"".""SomeArray"")"
);
}
}
[
Fact
]
public
void
Contains_with_column
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
Contains
(
e
.
Id
+
2
));
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE ""e"".""Id"" + 2 = ANY (""e"".""SomeArray"")"
);
}
}
[
Fact
]
public
void
Length
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
e
.
SomeArray
.
Length
==
2
);
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE array_length(""e"".""SomeArray"", 1) = 2"
);
}
}
[
Fact
(
Skip
=
"https://github.com/aspnet/EntityFramework/issues/9242"
)]
public
void
Length_on_EF_Property
()
{
using
(
var
ctx
=
CreateContext
())
{
// TODO: This fails
var
x
=
ctx
.
SomeEntities
.
Single
(
e
=>
EF
.
Property
<
int
[
]>
(
e
,
nameof
(
SomeEntity
.
SomeArray
)).
Length
==
2
);
Assert
.
Equal
(
new
[]
{
3
,
4
},
x
.
SomeArray
);
AssertContainsInSql
(
@"WHERE array_length(""e"".""SomeArray"", 1) = 2"
);
}
}
[
Fact
]
public
void
Length_on_literal_not_translated
()
{
using
(
var
ctx
=
CreateContext
())
{
var
x
=
ctx
.
SomeEntities
.
Where
(
e
=>
new
[]
{
1
,
2
,
3
}.
Length
==
e
.
Id
).
ToList
();
AssertDoesNotContainInSql
(
"array_length"
);
}
}
#
region
Support
ArrayFixture
Fixture
{
get
;
}
public
ArrayQueryTest
(
ArrayFixture
fixture
)
{
Fixture
=
fixture
;
Fixture
.
TestSqlLoggerFactory
.
Clear
();
}
ArrayContext
CreateContext
()
=>
Fixture
.
CreateContext
();
void
AssertContainsInSql
(
string
expected
)
=>
Assert
.
Contains
(
expected
,
Fixture
.
TestSqlLoggerFactory
.
Sql
);
void
AssertDoesNotContainInSql
(
string
expected
)
=>
Assert
.
DoesNotContain
(
expected
,
Fixture
.
TestSqlLoggerFactory
.
Sql
);
#
endregion
Support
}
public
class
ArrayContext
:
DbContext
{
public
DbSet
<
SomeEntity
>
SomeEntities
{
get
;
set
;
}
public
ArrayContext
(
DbContextOptions
options
)
:
base
(
options
)
{}
protected
override
void
OnModelCreating
(
ModelBuilder
builder
)
{
}
}
public
class
SomeEntity
{
public
int
Id
{
get
;
set
;
}
public
int
[]
SomeArray
{
get
;
set
;
}
public
int
[,]
SomeMatrix
{
get
;
set
;
}
public
byte
[]
SomeBytea
{
get
;
set
;
}
public
string
SomeText
{
get
;
set
;
}
}
public
class
ArrayFixture
:
IDisposable
{
readonly
DbContextOptions
_options
;
public
TestSqlLoggerFactory
TestSqlLoggerFactory
{
get
;
}
=
new
TestSqlLoggerFactory
();
public
ArrayFixture
()
{
_testStore
=
NpgsqlTestStore
.
CreateScratch
();
_options
=
new
DbContextOptionsBuilder
()
.
UseNpgsql
(
_testStore
.
Connection
,
b
=>
b
.
ApplyConfiguration
())
.
UseInternalServiceProvider
(
new
ServiceCollection
()
.
AddEntityFrameworkNpgsql
()
.
AddSingleton
<
ILoggerFactory
>(
TestSqlLoggerFactory
)
.
BuildServiceProvider
())
.
Options
;
using
(
var
ctx
=
CreateContext
())
{
ctx
.
Database
.
EnsureCreated
();
ctx
.
SomeEntities
.
Add
(
new
SomeEntity
{
Id
=
1
,
SomeArray
=
new
[]
{
3
,
4
},
SomeBytea
=
new
byte
[]
{
3
,
4
},
SomeMatrix
=
new
[,]
{
{
5
,
6
},
{
7
,
8
}
}
});
ctx
.
SomeEntities
.
Add
(
new
SomeEntity
{
Id
=
2
,
SomeArray
=
new
[]
{
5
,
6
,
7
},
SomeBytea
=
new
byte
[]
{
5
,
6
,
7
},
SomeMatrix
=
new
[,]
{
{
10
,
11
},
{
12
,
13
}
}
});
ctx
.
SaveChanges
();
}
}
readonly
NpgsqlTestStore
_testStore
;
public
ArrayContext
CreateContext
()
=>
new
ArrayContext
(
_options
);
public
void
Dispose
()
=>
_testStore
.
Dispose
();
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录