-
Notifications
You must be signed in to change notification settings - Fork 228
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
Added basic support for HStore operations query translation #240
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using System.Text; | ||
using System.Linq; | ||
using System.Reflection; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal | ||
{ | ||
public class NpgsqlDictionaryIndexTranslator : IMethodCallTranslator | ||
{ | ||
static readonly PropertyInfo DictionaryPropertyIndexAccessor = | ||
typeof(IDictionary<string, string>).GetProperties(BindingFlags.Public | BindingFlags.Instance) | ||
.Single(p => p.Name == "Item"); | ||
|
||
public Expression Translate(MethodCallExpression methodCallExpression) | ||
{ | ||
if (methodCallExpression.NodeType == ExpressionType.Call | ||
&& methodCallExpression.Method.Name == "get_Item" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be easy to compare with actual MethodInfo rather than string name. |
||
&& typeof(IDictionary<string, string>).IsAssignableFrom(methodCallExpression.Method.DeclaringType)) | ||
{ | ||
return Expression.MakeIndex(methodCallExpression.Object, DictionaryPropertyIndexAccessor, methodCallExpression.Arguments); | ||
} | ||
return null; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using Microsoft.EntityFrameworkCore.Query.Expressions; | ||
using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; | ||
using System; | ||
using System.Linq; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using System.Text; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.Internal | ||
{ | ||
public class NpgsqlHStoreKeysTranslator : IMethodCallTranslator | ||
{ | ||
public Expression Translate(MethodCallExpression methodCallExpression) | ||
{ | ||
if(methodCallExpression.Method == | ||
typeof(NpgsqlDbFunctionsExtensions) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. store in static field to avoid doing reflection for every translation. |
||
.GetRuntimeMethod( | ||
nameof(NpgsqlDbFunctionsExtensions.HStoreKeys), new Type[] { typeof(DbFunctions), typeof(IDictionary<string, string>) })) | ||
{ | ||
return new SqlFunctionExpression("akeys", typeof(string[]), new Expression[] { methodCallExpression.Arguments[1] }); | ||
} | ||
|
||
if (methodCallExpression.Method == | ||
typeof(NpgsqlDbFunctionsExtensions) | ||
.GetRuntimeMethod( | ||
nameof(NpgsqlDbFunctionsExtensions.HStoreValues), new Type[] { typeof(DbFunctions), typeof(IDictionary<string, string>) })) | ||
{ | ||
return new SqlFunctionExpression("avals", typeof(string[]), new Expression[] { methodCallExpression.Arguments[1] }); | ||
} | ||
|
||
if(typeof(IDictionary<string, string>).IsAssignableFrom(methodCallExpression.Method.DeclaringType) | ||
&& methodCallExpression.Method.Name == nameof(IDictionary<string, string>.ContainsKey)) | ||
{ | ||
return new DictionaryContainsKeyExpression(methodCallExpression.Arguments.Single(), methodCallExpression.Object); | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using System.Text; | ||
using JetBrains.Annotations; | ||
using Microsoft.EntityFrameworkCore.Utilities; | ||
using System.Diagnostics; | ||
using Microsoft.EntityFrameworkCore.Query.Sql.Internal; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query.Expressions.Internal | ||
{ | ||
public class DictionaryContainsKeyExpression : Expression | ||
{ | ||
/// <summary> | ||
/// Creates a new instance of expression that is used by EF translator to fetch | ||
/// HStore keys from database. | ||
/// </summary> | ||
/// <param name="key"> The key. </param> | ||
/// <param name="dictionary"> The dictionary. </param> | ||
public DictionaryContainsKeyExpression( | ||
[NotNull] Expression key, | ||
[NotNull] Expression dictionary) | ||
{ | ||
Check.NotNull(dictionary, nameof(dictionary)); | ||
Check.NotNull(key, nameof(key)); | ||
Debug.Assert(typeof(IDictionary<string, string>).IsAssignableFrom(dictionary.Type)); | ||
|
||
Dictionary = dictionary; | ||
Key = key; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the dictionary. | ||
/// </summary> | ||
/// <value> | ||
/// The dictionary. | ||
/// </value> | ||
public virtual Expression Dictionary { get; } | ||
|
||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value> | ||
/// The key. | ||
/// </value> | ||
public virtual Expression Key { 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.VisitDictionaryContainsKey(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 newDictionary = visitor.Visit(Dictionary); | ||
var newKey = visitor.Visit(Key); | ||
|
||
return newKey != Key || newDictionary != Dictionary | ||
? new DictionaryContainsKeyExpression(newKey, newDictionary) | ||
: 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((DictionaryContainsKeyExpression)obj); | ||
} | ||
|
||
bool Equals(DictionaryContainsKeyExpression other) | ||
=> Dictionary.Equals(other.Dictionary) && Key.Equals(other.Key); | ||
|
||
/// <summary> | ||
/// Returns a hash code for this object. | ||
/// </summary> | ||
/// <returns> | ||
/// A hash code for this object. | ||
/// </returns> | ||
public override int GetHashCode() | ||
{ | ||
unchecked | ||
{ | ||
return (Key.GetHashCode() * 397) ^ Dictionary.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() => $"{Dictionary} ? {Key}"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; | ||
using Microsoft.EntityFrameworkCore.Storage; | ||
using Microsoft.EntityFrameworkCore.Utilities; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.EntityFrameworkCore.Query.Sql.Internal | ||
{ | ||
|
@@ -138,6 +139,21 @@ protected override Expression VisitUnary(UnaryExpression expression) | |
return base.VisitUnary(expression); | ||
} | ||
|
||
protected override Expression VisitIndex(IndexExpression expression) | ||
{ | ||
if (expression.Indexer.Name == "Item" | ||
&& typeof(IDictionary<string, string>).IsAssignableFrom(expression.Object.Type)) | ||
{ | ||
Debug.Assert(expression.Arguments.Count == 1); | ||
var expr = Visit(expression.Object); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the other. Can be SqlFunctionExpression. |
||
Sql.Append(" -> "); | ||
Visit(expression.Arguments[0]); | ||
return null; | ||
} | ||
|
||
return base.VisitIndex(expression); | ||
} | ||
|
||
void GenerateArrayIndex([NotNull] BinaryExpression expression) | ||
{ | ||
Debug.Assert(expression.NodeType == ExpressionType.ArrayIndex); | ||
|
@@ -176,6 +192,15 @@ public Expression VisitArrayAny(ArrayAnyExpression arrayAnyExpression) | |
return arrayAnyExpression; | ||
} | ||
|
||
public Expression VisitDictionaryContainsKey(DictionaryContainsKeyExpression expr) | ||
{ | ||
Visit(expr.Dictionary); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be written using SqlFunctionExpression in 2.1 passing in dictionary as instance. That would avoid creating new expression type |
||
Sql.Append(" ? "); | ||
Visit(expr.Key); | ||
|
||
return expr; | ||
} | ||
|
||
// 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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MethodCallExpression is always ET.Call? Redundant check.