Browse Source

LINQ-To-Profiler

- implemented correct handling of string constants
- GLOB and LIKE operators

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5023 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Siegfried Pammer 17 years ago
parent
commit
a6b74847d6
  1. 32
      src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs
  2. 26
      src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs
  3. 84
      src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

32
src/AddIns/Misc/Profiler/Controller/Data/Linq/ExpressionSqlWriter.cs

@ -34,6 +34,11 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
this.callTreeNodeParameter = callTreeNodeParameter; this.callTreeNodeParameter = callTreeNodeParameter;
} }
string EscapeString(string str)
{
return "'" + str.Replace("'", "''") + "'";
}
public void Write(Expression expression) public void Write(Expression expression)
{ {
switch (expression.NodeType) { switch (expression.NodeType) {
@ -57,7 +62,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
break; break;
} }
case ExpressionType.Constant: case ExpressionType.Constant:
w.Write((int)((ConstantExpression)expression).Value); var ce = (ConstantExpression)expression;
if (ce.Type == typeof(int))
w.Write((int)ce.Value);
else if (ce.Type == typeof(string))
w.Write(EscapeString((string)ce.Value));
else
throw new NotSupportedException("constant of type not supported: " + ce.Type.FullName);
break; break;
case ExpressionType.MemberAccess: case ExpressionType.MemberAccess:
{ {
@ -81,6 +92,9 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
if (me.Member == KnownMembers.NameMapping_ID) { if (me.Member == KnownMembers.NameMapping_ID) {
w.Write(nameSet.NameID); w.Write(nameSet.NameID);
break; break;
} else if (me.Member == KnownMembers.NameMapping_Name) {
w.Write("(SELECT name FROM namemapping WHERE namemapping.id = " + nameSet.NameID + ")");
break;
} else { } else {
throw new NotSupportedException(me.Member.ToString()); throw new NotSupportedException(me.Member.ToString());
} }
@ -102,6 +116,20 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
} }
w.Write(')'); w.Write(')');
break; break;
} else if (mc.Method == KnownMembers.Like) {
w.Write("( ");
Write(mc.Arguments[0]);
w.Write(" LIKE ");
Write(mc.Arguments[1]);
w.Write(" ESCAPE '\' )");
break;
} else if (mc.Method == KnownMembers.Glob) {
w.Write("( ");
Write(mc.Arguments[0]);
w.Write(" GLOB ");
Write(mc.Arguments[1]);
w.Write(" )");
break;
} else { } else {
throw new NotSupportedException(mc.Method.ToString()); throw new NotSupportedException(mc.Method.ToString());
} }
@ -109,7 +137,7 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
case ExpressionType.Not: case ExpressionType.Not:
w.Write("(NOT "); w.Write("(NOT ");
UnaryExpression unary = (UnaryExpression)expression; UnaryExpression unary = (UnaryExpression)expression;
w.Write(unary.Operand); Write(unary.Operand);
w.Write(")"); w.Write(")");
break; break;
default: default:

26
src/AddIns/Misc/Profiler/Controller/Data/Linq/KnownMembers.cs

@ -26,9 +26,13 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
public static readonly PropertyInfo CallTreeNode_NameMapping = PropertyOf((CallTreeNode c) => c.NameMapping); public static readonly PropertyInfo CallTreeNode_NameMapping = PropertyOf((CallTreeNode c) => c.NameMapping);
public static readonly PropertyInfo CallTreeNode_Children = PropertyOf((CallTreeNode c) => c.Children); public static readonly PropertyInfo CallTreeNode_Children = PropertyOf((CallTreeNode c) => c.Children);
public static readonly PropertyInfo NameMapping_ID = PropertyOf((NameMapping n) => n.Id); public static readonly PropertyInfo NameMapping_ID = PropertyOf((NameMapping n) => n.Id);
public static readonly PropertyInfo NameMapping_Name = PropertyOf((NameMapping n) => n.Name);
public static readonly MethodInfo QuerableOfCallTreeNode_Select = MethodOf((IQueryable<CallTreeNode> q) => q.Select(x => x)); public static readonly MethodInfo QuerableOfCallTreeNode_Select = MethodOf((IQueryable<CallTreeNode> q) => q.Select(x => x));
public static readonly MethodInfo QuerableOfCallTreeNode_Where = MethodOf((IQueryable<CallTreeNode> q) => q.Where(x => true)); public static readonly MethodInfo QuerableOfCallTreeNode_Where = MethodOf((IQueryable<CallTreeNode> q) => q.Where(x => true));
public static readonly MethodInfo Merge = MethodOf((IQueryable<CallTreeNode> q) => q.Merge()); public static readonly MethodInfo Merge = MethodOf((IQueryable<CallTreeNode> q) => q.Merge());
public static readonly MethodInfo String_StartsWith = MethodOf((string s) => s.StartsWith(s, default(StringComparison)));
public static readonly MethodInfo Like = MethodOf(() => LikeImpl("", ""));
public static readonly MethodInfo Glob = MethodOf(() => GlobImpl("", ""));
#region InfoOf Helper Methods #region InfoOf Helper Methods
static MethodInfo MethodOf<T, R>(Expression<Func<T, R>> ex) static MethodInfo MethodOf<T, R>(Expression<Func<T, R>> ex)
@ -36,12 +40,22 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
return (MethodInfo)InfoOf(ex); return (MethodInfo)InfoOf(ex);
} }
static MethodInfo MethodOf<T1, T2, R>(Expression<Func<T1, T2, R>> ex)
{
return (MethodInfo)InfoOf(ex);
}
static MethodInfo MethodOf<R>(Expression<Func<R>> ex)
{
return (MethodInfo)InfoOf(ex);
}
static PropertyInfo PropertyOf<T, R>(Expression<Func<T, R>> ex) static PropertyInfo PropertyOf<T, R>(Expression<Func<T, R>> ex)
{ {
return (PropertyInfo)InfoOf(ex); return (PropertyInfo)InfoOf(ex);
} }
static MemberInfo InfoOf<T, R>(Expression<Func<T, R>> ex) static MemberInfo InfoOf(LambdaExpression ex)
{ {
var me = ex.Body as MemberExpression; var me = ex.Body as MemberExpression;
if (me != null) if (me != null)
@ -52,5 +66,15 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
throw new ArgumentException("InfoOf must be called with a member access or method call."); throw new ArgumentException("InfoOf must be called with a member access or method call.");
} }
#endregion #endregion
static bool LikeImpl(string input, string pattern)
{
throw new NotImplementedException();
}
static bool GlobImpl(string input, string pattern)
{
throw new NotImplementedException();
}
} }
} }

84
src/AddIns/Misc/Profiler/Controller/Data/Linq/SQLiteQueryProvider.cs

@ -46,12 +46,14 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
Only a limited set of expressions are valid in conditions and sort descriptors. Only a limited set of expressions are valid in conditions and sort descriptors.
These are checked by the SafeExpressionImporter. These are checked by the SafeExpressionImporter.
The set of valid expressions is: The set of valid expressions is:
- Integer constants - Integer constants
- Binary operators: < <= > >= == != && || - String constants
- Unary operator: ! - Binary operators: < <= > >= == != && || LIKE GLOB
- value(List<int>).Contains(validExpr) - Unary operator: !
- if c is the lambda parameter, then these expressions are valid: - value(List<int>).Contains(validExpr)
c.NameMapping.ID - if c is the lambda parameter, then these expressions are valid:
c.NameMapping.ID
c.NameMapping.Name
Additionally, field references on a lambda parameter of type SingleCall are valid inside Additionally, field references on a lambda parameter of type SingleCall are valid inside
filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()). filters that operate directly on "AllCalls" (e.g. AllCalls.Filter()).
@ -86,6 +88,8 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
Any valid expressions (as defined in 'valid expressions in QueryAst nodes') are copied over directly. Any valid expressions (as defined in 'valid expressions in QueryAst nodes') are copied over directly.
Moreover, these expressions are be converted into valid expressions: Moreover, these expressions are be converted into valid expressions:
c.IsUserCode -> c.NameMapping.ID > 0 c.IsUserCode -> c.NameMapping.ID > 0
c.NameMapping.Name.StartsWith(constantString, StringComparison.Ordinal) -> Glob(c.NameMapping.Name, constantString);
c.NameMapping.Name.StartsWith(constantString, StringComparison.OrdinalIgnoreCase) -> Like(c.NameMapping.Name, constantString);
Optimization of QueryAst: Optimization of QueryAst:
The OptimizeQueryExpressionVisitor is performing these optimizations: The OptimizeQueryExpressionVisitor is performing these optimizations:
@ -265,15 +269,27 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
} }
} }
object GetConstantValue(Expression expr)
{
return ((ConstantExpression)expr).Value;
}
/// <summary>
/// The list of characters forbidden in GLOB arguments.
/// </summary>
static readonly char[] forbiddenGlobChars = new[] { '*', '?', '[', ']' };
public Expression Import(Expression expr) public Expression Import(Expression expr)
{ {
switch (expr.NodeType) { switch (expr.NodeType) {
case ExpressionType.Constant: case ExpressionType.Constant:
// Only integer constants are supported // Only integer and string constants are supported
if (expr.Type == typeof(int)) if (expr.Type == typeof(int))
return expr; return expr;
else else if (expr.Type == typeof(string))
return null; return expr;
return null;
case ExpressionType.MemberAccess: case ExpressionType.MemberAccess:
{ {
MemberExpression me = (MemberExpression)expr; MemberExpression me = (MemberExpression)expr;
@ -289,6 +305,28 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
if (me.Member == KnownMembers.NameMapping_ID) if (me.Member == KnownMembers.NameMapping_ID)
return me; return me;
} }
}
return null;
case ExpressionType.Call:
{
MethodCallExpression mc = (MethodCallExpression)expr;
if (IsMemberOnNameMappingOnParameter(mc.Object, KnownMembers.NameMapping_Name)) {
if (mc.Arguments[0].NodeType == ExpressionType.Constant && mc.Arguments[1].NodeType == ExpressionType.Constant) {
StringComparison cmp = (StringComparison)GetConstantValue(mc.Arguments[1]);
string pattern = (string)GetConstantValue(mc.Arguments[0]);
if (mc.Method == KnownMembers.String_StartsWith) {
if (cmp == StringComparison.Ordinal && pattern.IndexOfAny(forbiddenGlobChars) == -1)
return Expression.Call(KnownMembers.Glob, mc.Object, Expression.Constant(pattern + "*"));
else if (cmp == StringComparison.OrdinalIgnoreCase)
return Expression.Call(KnownMembers.Like, mc.Object, Expression.Constant(EscapeLikeExpr(pattern, "\\") + "%"));
else
return null;
}
}
}
return null; return null;
} }
case ExpressionType.AndAlso: case ExpressionType.AndAlso:
@ -320,14 +358,38 @@ namespace ICSharpCode.Profiler.Controller.Data.Linq
} }
} }
string EscapeLikeExpr(string expression, string escape)
{
return expression.Replace(escape, escape + escape)
.Replace("%", escape + "%")
.Replace("_", escape + "_");
}
string EscapeGlobExpr(string expression, string escape)
{
return expression.Replace(escape, escape + escape)
.Replace("*", escape + "*")
.Replace("?", escape + "?");
}
bool IsNameMappingOnParameter(Expression expr) bool IsNameMappingOnParameter(Expression expr)
{ {
if (expr.NodeType == ExpressionType.MemberAccess) { if (expr.NodeType == ExpressionType.MemberAccess) {
var me = (MemberExpression)expr; var me = (MemberExpression)expr;
return me.Expression == callTreeNodeParameter && me.Member == KnownMembers.CallTreeNode_NameMapping; return me.Expression == callTreeNodeParameter && me.Member == KnownMembers.CallTreeNode_NameMapping;
} else {
return false;
} }
return false;
}
bool IsMemberOnNameMappingOnParameter(Expression expr, MemberInfo member)
{
if (expr.NodeType == ExpressionType.MemberAccess) {
var me = (MemberExpression)expr;
return IsNameMappingOnParameter(me.Expression) && me.Member == member;
}
return false;
} }
} }
#endregion #endregion

Loading…
Cancel
Save