diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index 087ed6a3e2..b97a3cfacf 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -34,6 +34,12 @@ namespace Umbraco.Core AddInt(d.GetHashCode()); } + internal void AddString(string s) + { + if (s != null) + AddInt((StringComparer.InvariantCulture).GetHashCode(s)); + } + internal void AddCaseInsensitiveString(string s) { if (s != null) diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index d8507ac681..6feee12f99 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Caching; using System.Security; using System.Security.Permissions; using System.Text; @@ -1709,8 +1710,32 @@ namespace Umbraco.Core.Persistence } public override object ChangeType(object val) { return val; } } - public class PocoData - { + + /// + /// Container for a Memory cache object + /// + /// + /// Better to have one memory cache instance than many so it's memory management can be handled more effectively + /// http://stackoverflow.com/questions/8463962/using-multiple-instances-of-memorycache + /// + internal class ManagedCache + { + public ObjectCache GetCache() + { + return ObjectCache; + } + + static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); + + } + + public class PocoData + { + //USE ONLY FOR TESTING + internal static bool UseLongKeys = false; + //USE ONLY FOR TESTING - default is one hr + internal static int SlidingExpirationSeconds = 3600; + public static PocoData ForObject(object o, string primaryKeyName) { var t = o.GetType(); @@ -1734,7 +1759,7 @@ namespace Umbraco.Core.Persistence #endif return ForType(t); } - static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); + public static PocoData ForType(Type t) { #if !PETAPOCO_NO_DYNAMIC @@ -1742,7 +1767,7 @@ namespace Umbraco.Core.Persistence throw new InvalidOperationException("Can't use dynamic types with this method"); #endif // Check cache - RWLock.EnterReadLock(); + InnerLock.EnterReadLock(); PocoData pd; try { @@ -1751,12 +1776,12 @@ namespace Umbraco.Core.Persistence } finally { - RWLock.ExitReadLock(); + InnerLock.ExitReadLock(); } // Cache it - RWLock.EnterWriteLock(); + InnerLock.EnterWriteLock(); try { // Check again @@ -1770,7 +1795,7 @@ namespace Umbraco.Core.Persistence } finally { - RWLock.ExitWriteLock(); + InnerLock.ExitWriteLock(); } return pd; @@ -1851,224 +1876,237 @@ namespace Umbraco.Core.Persistence return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; } + + // Create factory function that can convert a IDataReader record into a POCO public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) { - // Check cache - var key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns); - RWLock.EnterReadLock(); - try - { - // Have we already created it? - Delegate factory; - if (PocoFactories.TryGetValue(key, out factory)) - return factory; - } - finally - { - RWLock.ExitReadLock(); - } - // Take the writer lock - RWLock.EnterWriteLock(); + //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. + // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are + // returning the same structured data: + // SELECT * FROM MyTable ORDER BY MyColumn + // SELECT * FROM MyTable ORDER BY MyColumn DESC - try - { + string key; + if (UseLongKeys) + { + key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns); + } + else + { + //Create a hashed key, we don't want to store so much string data in memory + var combiner = new HashCodeCombiner(); + combiner.AddCaseInsensitiveString(sql); + combiner.AddCaseInsensitiveString(connString); + combiner.AddObject(ForceDateTimesToUtc); + combiner.AddInt(firstColumn); + combiner.AddInt(countColumns); + key = combiner.GetCombinedHashCode(); + } + - // Check again, just in case - Delegate factory; - if (PocoFactories.TryGetValue(key, out factory)) - return factory; + var objectCache = _managedCache.GetCache(); - // Create the method - var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true); - var il = m.GetILGenerator(); + Func factory = () => + { + // Create the method + var m = new DynamicMethod("petapoco_factory_" + objectCache.GetCount(), type, new Type[] { typeof(IDataReader) }, true); + var il = m.GetILGenerator(); #if !PETAPOCO_NO_DYNAMIC - if (type == typeof(object)) - { - // var poco=new T() - il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj + if (type == typeof(object)) + { + // var poco=new T() + il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj - MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); + MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - var srcType = r.GetFieldType(i); + // Enumerate all fields generating a set assignment for the column + for (int i = firstColumn; i < firstColumn + countColumns; i++) + { + var srcType = r.GetFieldType(i); - il.Emit(OpCodes.Dup); // obj, obj - il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname + il.Emit(OpCodes.Dup); // obj, obj + il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname - // Get the converter - Func converter = null; - if (Database.Mapper != null) - converter = Database.Mapper.GetFromDbConverter(null, srcType); - if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; + // Get the converter + Func converter = null; + if (Database.Mapper != null) + converter = Database.Mapper.GetFromDbConverter(null, srcType); + if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) + converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - // Setup stack for call to converter - AddConverterToStack(il, converter); + // Setup stack for call to converter + AddConverterToStack(il, converter); - // r[i] - il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr - il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value + // r[i] + il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr + il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i + il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value - // Convert DBNull to null - il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value - il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) - var lblNotNull = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value - il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? - if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, - il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null - if (converter != null) - { - var lblReady = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblReady); - il.MarkLabel(lblNotNull); - il.Emit(OpCodes.Callvirt, fnInvoke); - il.MarkLabel(lblReady); - } - else - { - il.MarkLabel(lblNotNull); - } + // Convert DBNull to null + il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value + il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) + var lblNotNull = il.DefineLabel(); + il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value + il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? + if (converter != null) + il.Emit(OpCodes.Pop); // obj, obj, fieldname, + il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null + if (converter != null) + { + var lblReady = il.DefineLabel(); + il.Emit(OpCodes.Br_S, lblReady); + il.MarkLabel(lblNotNull); + il.Emit(OpCodes.Callvirt, fnInvoke); + il.MarkLabel(lblReady); + } + else + { + il.MarkLabel(lblNotNull); + } - il.Emit(OpCodes.Callvirt, fnAdd); - } - } - else + il.Emit(OpCodes.Callvirt, fnAdd); + } + } + else #endif - if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) - { - // Do we need to install a converter? - var srcType = r.GetFieldType(0); - var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type); + if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) + { + // Do we need to install a converter? + var srcType = r.GetFieldType(0); + var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type); - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool - var lblCont = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblCont); - il.Emit(OpCodes.Ldnull); // null - var lblFin = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblFin); + // "if (!rdr.IsDBNull(i))" + il.Emit(OpCodes.Ldarg_0); // rdr + il.Emit(OpCodes.Ldc_I4_0); // rdr,0 + il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool + var lblCont = il.DefineLabel(); + il.Emit(OpCodes.Brfalse_S, lblCont); + il.Emit(OpCodes.Ldnull); // null + var lblFin = il.DefineLabel(); + il.Emit(OpCodes.Br_S, lblFin); - il.MarkLabel(lblCont); + il.MarkLabel(lblCont); - // Setup stack for call to converter - AddConverterToStack(il, converter); + // Setup stack for call to converter + AddConverterToStack(il, converter); - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnGetValue); // value + il.Emit(OpCodes.Ldarg_0); // rdr + il.Emit(OpCodes.Ldc_I4_0); // rdr,0 + il.Emit(OpCodes.Callvirt, fnGetValue); // value - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); + // Call the converter + if (converter != null) + il.Emit(OpCodes.Callvirt, fnInvoke); - il.MarkLabel(lblFin); - il.Emit(OpCodes.Unbox_Any, type); // value converted - } - else - { - // var poco=new T() - il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); + il.MarkLabel(lblFin); + il.Emit(OpCodes.Unbox_Any, type); // value converted + } + else + { + // var poco=new T() + il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - // Get the PocoColumn for this db column, ignore if not known - PocoColumn pc; - if (!Columns.TryGetValue(r.GetName(i), out pc)) - continue; + // Enumerate all fields generating a set assignment for the column + for (int i = firstColumn; i < firstColumn + countColumns; i++) + { + // Get the PocoColumn for this db column, ignore if not known + PocoColumn pc; + if (!Columns.TryGetValue(r.GetName(i), out pc)) + continue; - // Get the source type for this column - var srcType = r.GetFieldType(i); - var dstType = pc.PropertyInfo.PropertyType; + // Get the source type for this column + var srcType = r.GetFieldType(i); + var dstType = pc.PropertyInfo.PropertyType; - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // poco,rdr - il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i - il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool - var lblNext = il.DefineLabel(); - il.Emit(OpCodes.Brtrue_S, lblNext); // poco + // "if (!rdr.IsDBNull(i))" + il.Emit(OpCodes.Ldarg_0); // poco,rdr + il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i + il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool + var lblNext = il.DefineLabel(); + il.Emit(OpCodes.Brtrue_S, lblNext); // poco - il.Emit(OpCodes.Dup); // poco,poco + il.Emit(OpCodes.Dup); // poco,poco - // Do we need to install a converter? - var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType); + // Do we need to install a converter? + var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType); - // Fast - bool Handled = false; - if (converter == null) - { - var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); - if (valuegetter != null - && valuegetter.ReturnType == srcType - && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) - { - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, valuegetter); // *,value + // Fast + bool Handled = false; + if (converter == null) + { + var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); + if (valuegetter != null + && valuegetter.ReturnType == srcType + && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) + { + il.Emit(OpCodes.Ldarg_0); // *,rdr + il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i + il.Emit(OpCodes.Callvirt, valuegetter); // *,value - // Convert to Nullable - if (Nullable.GetUnderlyingType(dstType) != null) - { - il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); - } + // Convert to Nullable + if (Nullable.GetUnderlyingType(dstType) != null) + { + il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); + } - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - Handled = true; - } - } + il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco + Handled = true; + } + } - // Not so fast - if (!Handled) - { - // Setup stack for call to converter - AddConverterToStack(il, converter); + // Not so fast + if (!Handled) + { + // Setup stack for call to converter + AddConverterToStack(il, converter); - // "value = rdr.GetValue(i)" - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // *,value + // "value = rdr.GetValue(i)" + il.Emit(OpCodes.Ldarg_0); // *,rdr + il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i + il.Emit(OpCodes.Callvirt, fnGetValue); // *,value - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); + // Call the converter + if (converter != null) + il.Emit(OpCodes.Callvirt, fnInvoke); - // Assign it - il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - } + // Assign it + il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value + il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco + } - il.MarkLabel(lblNext); - } + il.MarkLabel(lblNext); + } - var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - if (fnOnLoaded != null) - { - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Callvirt, fnOnLoaded); - } - } + var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); + if (fnOnLoaded != null) + { + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Callvirt, fnOnLoaded); + } + } - il.Emit(OpCodes.Ret); + il.Emit(OpCodes.Ret); + + // return it + var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); + + return del; + }; + + //lazy usage of AddOrGetExisting ref: http://stackoverflow.com/questions/10559279/how-to-deal-with-costly-building-operations-using-memorycache/15894928#15894928 + var newValue = new Lazy(factory); + // the line belows returns existing item or adds the new value if it doesn't exist + var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy + { + //sliding expiration of 1 hr, if the same key isn't used in this + // timeframe it will be removed from the cache + SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) + }); + return (value ?? newValue).Value; // Lazy handles the locking itself - // Cache it, return it - var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - PocoFactories.Add(key, del); - return del; - } - finally - { - RWLock.ExitWriteLock(); - } } private static void AddConverterToStack(ILGenerator il, Func converter) @@ -2144,7 +2182,7 @@ namespace Umbraco.Core.Persistence return default(T); } - + ManagedCache _managedCache = new ManagedCache(); static Dictionary m_PocoDatas = new Dictionary(); static List> m_Converters = new List>(); static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); @@ -2156,7 +2194,46 @@ namespace Umbraco.Core.Persistence public string[] QueryColumns { get; private set; } public TableInfo TableInfo { get; private set; } public Dictionary Columns { get; private set; } - Dictionary PocoFactories = new Dictionary(); + static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); + + /// + /// Returns a report of the current cache being utilized by PetaPoco + /// + /// + public static string PrintDebugCacheReport(out double totalBytes, out IEnumerable allKeys) + { + var managedCache = new ManagedCache(); + + var sb = new StringBuilder(); + sb.AppendLine("m_PocoDatas:"); + foreach (var pocoData in m_PocoDatas) + { + sb.AppendFormat("\t{0}\n", pocoData.Key); + sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); + } + + var cache = managedCache.GetCache(); + allKeys = cache.Select(x => x.Key).ToArray(); + + sb.AppendFormat("\tTotal Poco data count:{0}\n", allKeys.Count()); + + var keys = string.Join("", cache.Select(x => x.Key)); + //Bytes in .Net are stored as utf-16 = unicode little endian + totalBytes = Encoding.Unicode.GetByteCount(keys); + + sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); + + sb.AppendLine("\tAll Poco cache items:"); + + foreach (var item in cache) + { + sb.AppendFormat("\t\t Key -> {0}\n", item.Key); + sb.AppendFormat("\t\t Value -> {0}\n", item.Value); + } + + sb.AppendLine("-------------------END REPORT------------------------"); + return sb.ToString(); + } } diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 4e1a3de28f..c03d2ca06f 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence var expresionist = new PocoToSqlExpressionHelper(); string whereExpression = expresionist.Visit(predicate); - return sql.Where(whereExpression); + return sql.Where(whereExpression, expresionist.GetSqlParameters()); } public static Sql OrderBy(this Sql sql, Expression> columnMember) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 57f076064b..36bcaa8c3a 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -1,28 +1,562 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Text; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { + internal abstract class BaseExpressionHelper : BaseExpressionHelper + { + protected abstract string VisitMemberAccess(MemberExpression m); + + protected internal virtual string Visit(Expression exp) + { + + if (exp == null) return string.Empty; + switch (exp.NodeType) + { + case ExpressionType.Lambda: + return VisitLambda(exp as LambdaExpression); + case ExpressionType.MemberAccess: + return VisitMemberAccess(exp as MemberExpression); + case ExpressionType.Constant: + return VisitConstant(exp as ConstantExpression); + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.Divide: + case ExpressionType.Modulo: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + case ExpressionType.Coalesce: + case ExpressionType.ArrayIndex: + case ExpressionType.RightShift: + case ExpressionType.LeftShift: + case ExpressionType.ExclusiveOr: + return VisitBinary(exp as BinaryExpression); + case ExpressionType.Negate: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.ArrayLength: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return VisitUnary(exp as UnaryExpression); + case ExpressionType.Parameter: + return VisitParameter(exp as ParameterExpression); + case ExpressionType.Call: + return VisitMethodCall(exp as MethodCallExpression); + case ExpressionType.New: + return VisitNew(exp as NewExpression); + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + return VisitNewArray(exp as NewArrayExpression); + default: + return exp.ToString(); + } + } + + protected virtual string VisitLambda(LambdaExpression lambda) + { + if (lambda.Body.NodeType == ExpressionType.MemberAccess) + { + var m = lambda.Body as MemberExpression; + + if (m.Expression != null) + { + //This deals with members that are boolean (i.e. x => IsTrashed ) + string r = VisitMemberAccess(m); + SqlParameters.Add(true); + return string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + + //return string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + + } + return Visit(lambda.Body); + } + + protected virtual string VisitBinary(BinaryExpression b) + { + string left, right; + var operand = BindOperant(b.NodeType); + if (operand == "AND" || operand == "OR") + { + MemberExpression m = b.Left as MemberExpression; + if (m != null && m.Expression != null) + { + string r = VisitMemberAccess(m); + + SqlParameters.Add(1); + left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + + //left = string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + else + { + left = Visit(b.Left); + } + m = b.Right as MemberExpression; + if (m != null && m.Expression != null) + { + string r = VisitMemberAccess(m); + + SqlParameters.Add(1); + right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1); + + //right = string.Format("{0}={1}", r, GetQuotedTrueValue()); + } + else + { + right = Visit(b.Right); + } + } + else + { + left = Visit(b.Left); + right = Visit(b.Right); + } + + if (operand == "=" && right == "null") operand = "is"; + else if (operand == "<>" && right == "null") operand = "is not"; + else if (operand == "=" || operand == "<>") + { + //if (IsTrueExpression(right)) right = GetQuotedTrueValue(); + //else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); + + //if (IsTrueExpression(left)) left = GetQuotedTrueValue(); + //else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); + + } + + switch (operand) + { + case "MOD": + case "COALESCE": + return string.Format("{0}({1},{2})", operand, left, right); + default: + return left + " " + operand + " " + right; + } + } + + protected virtual List VisitExpressionList(ReadOnlyCollection original) + { + var list = new List(); + for (int i = 0, n = original.Count; i < n; i++) + { + if (original[i].NodeType == ExpressionType.NewArrayInit || + original[i].NodeType == ExpressionType.NewArrayBounds) + { + + list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); + } + else + list.Add(Visit(original[i])); + + } + return list; + } + + protected virtual string VisitNew(NewExpression nex) + { + // TODO : check ! + var member = Expression.Convert(nex, typeof(object)); + var lambda = Expression.Lambda>(member); + try + { + var getter = lambda.Compile(); + object o = getter(); + + SqlParameters.Add(o); + return string.Format("@{0}", SqlParameters.Count - 1); + + //return GetQuotedValue(o, o.GetType()); + } + catch (InvalidOperationException) + { + // FieldName ? + List exprs = VisitExpressionList(nex.Arguments); + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.AppendFormat("{0}{1}", + r.Length > 0 ? "," : "", + e); + } + return r.ToString(); + } + + } + + protected virtual string VisitParameter(ParameterExpression p) + { + return p.Name; + } + + protected virtual string VisitConstant(ConstantExpression c) + { + if (c.Value == null) + return "null"; + + SqlParameters.Add(c.Value); + return string.Format("@{0}", SqlParameters.Count - 1); + + //if (c.Value is bool) + //{ + // object o = GetQuotedValue(c.Value, c.Value.GetType()); + // return string.Format("({0}={1})", GetQuotedTrueValue(), o); + //} + //return GetQuotedValue(c.Value, c.Value.GetType()); + } + + protected virtual string VisitUnary(UnaryExpression u) + { + switch (u.NodeType) + { + case ExpressionType.Not: + var o = Visit(u.Operand); + + //use a Not equal operator instead of <> since we don't know that <> works in all sql servers + + switch (u.Operand.NodeType) + { + case ExpressionType.MemberAccess: + //In this case it wil be a false property , i.e. x => !Trashed + SqlParameters.Add(true); + return string.Format("NOT ({0} = @0)", o); + default: + //In this case it could be anything else, such as: x => !x.Path.StartsWith("-20") + return string.Format("NOT ({0})", o); + } + default: + return Visit(u.Operand); + } + } + + protected virtual string VisitNewArray(NewArrayExpression na) + { + + List exprs = VisitExpressionList(na.Expressions); + var r = new StringBuilder(); + foreach (Object e in exprs) + { + r.Append(r.Length > 0 ? "," + e : e); + } + + return r.ToString(); + } + + protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) + { + + List exprs = VisitExpressionList(na.Expressions); + return exprs; + } + + protected virtual string BindOperant(ExpressionType e) + { + + switch (e) + { + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "<>"; + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.AndAlso: + return "AND"; + case ExpressionType.OrElse: + return "OR"; + case ExpressionType.Add: + return "+"; + case ExpressionType.Subtract: + return "-"; + case ExpressionType.Multiply: + return "*"; + case ExpressionType.Divide: + return "/"; + case ExpressionType.Modulo: + return "MOD"; + case ExpressionType.Coalesce: + return "COALESCE"; + default: + return e.ToString(); + } + } + + protected virtual string VisitMethodCall(MethodCallExpression m) + { + //Here's what happens with a MethodCallExpression: + // If a method is called that contains a single argument, + // then m.Object is the object on the left hand side of the method call, example: + // x.Path.StartsWith(content.Path) + // m.Object = x.Path + // and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path + // If a method is called that contains multiple arguments, then m.Object == null and the + // m.Arguments collection contains the left hand side of the method call, example: + // x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar) + // m.Object == null + // m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar + // So, we need to cater for these scenarios. + + var objectForMethod = m.Object ?? m.Arguments[0]; + var visitedObjectForMethod = Visit(objectForMethod); + var methodArgs = m.Object == null + ? m.Arguments.Skip(1).ToArray() + : m.Arguments.ToArray(); + + switch (m.Method.Name) + { + case "ToString": + SqlParameters.Add(objectForMethod.ToString()); + return string.Format("@{0}", SqlParameters.Count - 1); + case "ToUpper": + return string.Format("upper({0})", visitedObjectForMethod); + case "ToLower": + return string.Format("lower({0})", visitedObjectForMethod); + case "SqlWildcard": + case "StartsWith": + case "EndsWith": + case "Contains": + case "Equals": + case "SqlStartsWith": + case "SqlEndsWith": + case "SqlContains": + case "SqlEquals": + case "InvariantStartsWith": + case "InvariantEndsWith": + case "InvariantContains": + case "InvariantEquals": + + string compareValue; + + if (methodArgs[0].NodeType != ExpressionType.Constant) + { + //This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path) + // So we'll go get the value: + var member = Expression.Convert(methodArgs[0], typeof(object)); + var lambda = Expression.Lambda>(member); + var getter = lambda.Compile(); + compareValue = getter().ToString(); + } + else + { + compareValue = methodArgs[0].ToString(); + } + + //special case, if it is 'Contains' and the member that Contains is being called on is not a string, then + // we should be doing an 'In' clause - but we currently do not support this + if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(methodArgs[0].Type)) + { + throw new NotSupportedException("An array Contains method is not supported"); + } + + //default column type + var colType = TextColumnType.NVarchar; + + //then check if the col type argument has been passed to the current method (this will be the case for methods like + // SqlContains and other Sql methods) + if (methodArgs.Length > 1) + { + var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + if (colTypeArg != null) + { + colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; + } + } + + return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); + //case "Substring": + // var startIndex = Int32.Parse(args[0].ToString()) + 1; + // if (args.Count == 2) + // { + // var length = Int32.Parse(args[1].ToString()); + // return string.Format("substring({0} from {1} for {2})", + // r, + // startIndex, + // length); + // } + // else + // return string.Format("substring({0} from {1})", + // r, + // startIndex); + //case "Round": + //case "Floor": + //case "Ceiling": + //case "Coalesce": + //case "Abs": + //case "Sum": + // return string.Format("{0}({1}{2})", + // m.Method.Name, + // r, + // args.Count == 1 ? string.Format(",{0}", args[0]) : ""); + //case "Concat": + // var s = new StringBuilder(); + // foreach (Object e in args) + // { + // s.AppendFormat(" || {0}", e); + // } + // return string.Format("{0}{1}", r, s); + + //case "In": + + // var member = Expression.Convert(m.Arguments[0], typeof(object)); + // var lambda = Expression.Lambda>(member); + // var getter = lambda.Compile(); + + // var inArgs = (object[])getter(); + + // var sIn = new StringBuilder(); + // foreach (var e in inArgs) + // { + // SqlParameters.Add(e); + + // sIn.AppendFormat("{0}{1}", + // sIn.Length > 0 ? "," : "", + // string.Format("@{0}", SqlParameters.Count - 1)); + + // //sIn.AppendFormat("{0}{1}", + // // sIn.Length > 0 ? "," : "", + // // GetQuotedValue(e, e.GetType())); + // } + + // return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); + //case "Desc": + // return string.Format("{0} DESC", r); + //case "Alias": + //case "As": + // return string.Format("{0} As {1}", r, + // GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); + + default: + + throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name); + + //var s2 = new StringBuilder(); + //foreach (Object e in args) + //{ + // s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); + //} + //return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); + } + } + + public virtual string GetQuotedTableName(string tableName) + { + return string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return string.Format("\"{0}\"", name); + } + + //private string GetQuotedTrueValue() + //{ + // return GetQuotedValue(true, typeof(bool)); + //} + + //private string GetQuotedFalseValue() + //{ + // return GetQuotedValue(false, typeof(bool)); + //} + + //public virtual string GetQuotedValue(object value, Type fieldType) + //{ + // return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); + //} + + //private string GetTrueExpression() + //{ + // object o = GetQuotedTrueValue(); + // return string.Format("({0}={1})", o, o); + //} + + //private string GetFalseExpression() + //{ + + // return string.Format("({0}={1})", + // GetQuotedTrueValue(), + // GetQuotedFalseValue()); + //} + + //private bool IsTrueExpression(string exp) + //{ + // return (exp == GetTrueExpression()); + //} + + //private bool IsFalseExpression(string exp) + //{ + // return (exp == GetFalseExpression()); + //} + } + /// /// Logic that is shared with the expression helpers /// - internal class BaseExpressionHelper + internal class BaseExpressionHelper { + protected List SqlParameters = new List(); + + public object[] GetSqlParameters() + { + return SqlParameters.ToArray(); + } + protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType) { switch (verb) { case "SqlWildcard": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, RemoveQuote(val), columnType); + SqlParameters.Add(RemoveQuote(val)); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); case "Equals": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, RemoveQuote(val), columnType); + SqlParameters.Add(RemoveQuote(val)); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType); case "StartsWith": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, RemoveQuote(val), columnType); + SqlParameters.Add(string.Format("{0}{1}", + RemoveQuote(val), + SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder())); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); case "EndsWith": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, RemoveQuote(val), columnType); + SqlParameters.Add(string.Format("{0}{1}", + SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(), + RemoveQuote(val))); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); case "Contains": - return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, RemoveQuote(val), columnType); + SqlParameters.Add(string.Format("{0}{1}{0}", + SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(), + RemoveQuote(val))); + return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType); case "InvariantEquals": case "SqlEquals": //recurse @@ -44,54 +578,54 @@ namespace Umbraco.Core.Persistence.Querying } } - public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) - { - if (value == null) return "NULL"; + //public virtual string GetQuotedValue(object value, Type fieldType, Func escapeCallback = null, Func shouldQuoteCallback = null) + //{ + // if (value == null) return "NULL"; - if (escapeCallback == null) - { - escapeCallback = EscapeParam; - } - if (shouldQuoteCallback == null) - { - shouldQuoteCallback = ShouldQuoteValue; - } + // if (escapeCallback == null) + // { + // escapeCallback = EscapeParam; + // } + // if (shouldQuoteCallback == null) + // { + // shouldQuoteCallback = ShouldQuoteValue; + // } - if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) - { - //if (TypeSerializer.CanCreateFromString(fieldType)) - //{ - // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; - //} + // if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string)) + // { + // //if (TypeSerializer.CanCreateFromString(fieldType)) + // //{ + // // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'"; + // //} - throw new NotSupportedException( - string.Format("Property of type: {0} is not supported", fieldType.FullName)); - } + // throw new NotSupportedException( + // string.Format("Property of type: {0} is not supported", fieldType.FullName)); + // } - if (fieldType == typeof(int)) - return ((int)value).ToString(CultureInfo.InvariantCulture); + // if (fieldType == typeof(int)) + // return ((int)value).ToString(CultureInfo.InvariantCulture); - if (fieldType == typeof(float)) - return ((float)value).ToString(CultureInfo.InvariantCulture); + // if (fieldType == typeof(float)) + // return ((float)value).ToString(CultureInfo.InvariantCulture); - if (fieldType == typeof(double)) - return ((double)value).ToString(CultureInfo.InvariantCulture); + // if (fieldType == typeof(double)) + // return ((double)value).ToString(CultureInfo.InvariantCulture); - if (fieldType == typeof(decimal)) - return ((decimal)value).ToString(CultureInfo.InvariantCulture); + // if (fieldType == typeof(decimal)) + // return ((decimal)value).ToString(CultureInfo.InvariantCulture); - if (fieldType == typeof(DateTime)) - { - return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; - } + // if (fieldType == typeof(DateTime)) + // { + // return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'"; + // } - if (fieldType == typeof(bool)) - return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); + // if (fieldType == typeof(bool)) + // return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture); - return shouldQuoteCallback(fieldType) - ? "'" + escapeCallback(value) + "'" - : value.ToString(); - } + // return shouldQuoteCallback(fieldType) + // ? "'" + escapeCallback(value) + "'" + // : value.ToString(); + //} public virtual string EscapeParam(object paramValue) { @@ -107,16 +641,12 @@ namespace Umbraco.Core.Persistence.Querying protected virtual string RemoveQuote(string exp) { - if (exp.StartsWith("'") && exp.EndsWith("'")) - { - exp = exp.Remove(0, 1); - exp = exp.Remove(exp.Length - 1, 1); - } - return exp; - } - - protected virtual string RemoveQuoteFromAlias(string exp) - { + //if (exp.StartsWith("'") && exp.EndsWith("'")) + //{ + // exp = exp.Remove(0, 1); + // exp = exp.Remove(exp.Length - 1, 1); + //} + //return exp; if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) && @@ -127,5 +657,18 @@ namespace Umbraco.Core.Persistence.Querying } return exp; } + + //protected virtual string RemoveQuoteFromAlias(string exp) + //{ + + // if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'")) + // && + // (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'"))) + // { + // exp = exp.Remove(0, 1); + // exp = exp.Remove(exp.Length - 1, 1); + // } + // return exp; + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index 1d634b9d90..b484465540 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,10 +1,26 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; namespace Umbraco.Core.Persistence.Querying { + /// + /// Represents a query for building Linq translatable SQL queries + /// + /// public interface IQuery { + /// + /// Adds a where clause to the query + /// + /// + /// This instance so calls to this method are chainable IQuery Where(Expression> predicate); + + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + IEnumerable> GetWhereClauses(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs index e3ff272cee..1a561b7bf4 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionHelper.cs @@ -10,149 +10,21 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal class ModelToSqlExpressionHelper : BaseExpressionHelper + internal class ModelToSqlExpressionHelper : BaseExpressionHelper { - private string sep = " "; - private BaseMapper _mapper; + + private readonly BaseMapper _mapper; public ModelToSqlExpressionHelper() { _mapper = MappingResolver.Current.ResolveMapperByType(typeof(T)); } - - protected internal virtual string Visit(Expression exp) + + protected override string VisitMemberAccess(MemberExpression m) { - - if (exp == null) return string.Empty; - switch (exp.NodeType) - { - case ExpressionType.Lambda: - return VisitLambda(exp as LambdaExpression); - case ExpressionType.MemberAccess: - return VisitMemberAccess(exp as MemberExpression); - case ExpressionType.Constant: - return VisitConstant(exp as ConstantExpression); - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - return VisitBinary(exp as BinaryExpression); - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - return VisitUnary(exp as UnaryExpression); - case ExpressionType.Parameter: - return VisitParameter(exp as ParameterExpression); - case ExpressionType.Call: - return VisitMethodCall(exp as MethodCallExpression); - case ExpressionType.New: - return VisitNew(exp as NewExpression); - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - return VisitNewArray(exp as NewArrayExpression); - default: - return exp.ToString(); - } - } - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ") - { - MemberExpression m = lambda.Body as MemberExpression; - - if (m.Expression != null) - { - string r = VisitMemberAccess(m); - return string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - string left, right; - var operand = BindOperant(b.NodeType); //sep= " " ?? - if (operand == "AND" || operand == "OR") - { - MemberExpression m = b.Left as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - left = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - left = Visit(b.Left); - } - m = b.Right as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - right = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - right = Visit(b.Right); - } - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - return string.Format("{0}({1},{2})", operand, left, right); - default: - return left + sep + operand + sep + right; - } - } - - protected virtual string VisitMemberAccess(MemberExpression m) - { - if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T)) + if (m.Expression != null && + m.Expression.NodeType == ExpressionType.Parameter + && m.Expression.Type == typeof(T)) { var field = _mapper.Map(m.Member.Name); return field; @@ -168,324 +40,18 @@ namespace Umbraco.Core.Persistence.Querying var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); object o = getter(); - return GetQuotedValue(o, o != null ? o.GetType() : null); + + SqlParameters.Add(o); + return string.Format("@{0}", SqlParameters.Count - 1); + + //return GetQuotedValue(o, o != null ? o.GetType() : null); } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - object o = getter(); - return GetQuotedValue(o, o.GetType()); - } - catch (System.InvalidOperationException) - { // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.AppendFormat("{0}{1}", r.Length > 0 ? "," : "", e); - } - return r.ToString(); - } - - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - if (c.Value is bool) - { - object o = GetQuotedValue(c.Value, c.Value.GetType()); - return string.Format("({0}={1})", GetQuotedTrueValue(), o); - } - return GetQuotedValue(c.Value, c.Value.GetType()); - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - string o = Visit(u.Operand); - if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool)); - return "NOT (" + o + ")"; - default: - return Visit(u.Operand); - } - - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - List args = this.VisitExpressionList(m.Arguments); - - Object r; - if (m.Object != null) - r = Visit(m.Object); - else - { - r = args[0]; - args.RemoveAt(0); - } - - switch (m.Method.Name) - { - case "ToUpper": - return string.Format("upper({0})", r); - case "ToLower": - return string.Format("lower({0})", r); - case "SqlWildcard": - case "StartsWith": - case "EndsWith": - case "Contains": - case "Equals": - case "SqlStartsWith": - case "SqlEndsWith": - case "SqlContains": - case "SqlEquals": - case "InvariantStartsWith": - case "InvariantEndsWith": - case "InvariantContains": - case "InvariantEquals": - //default - var colType = TextColumnType.NVarchar; - //then check if this arg has been passed in - if (m.Arguments.Count > 1) - { - var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); - if (colTypeArg != null) - { - colType = (TextColumnType) ((ConstantExpression) colTypeArg).Value; - } - } - return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType); - case "Substring": - var startIndex = Int32.Parse(args[0].ToString()) + 1; - if (args.Count == 2) - { - var length = Int32.Parse(args[1].ToString()); - return string.Format("substring({0} from {1} for {2})", - r, - startIndex, - length); - } - else - return string.Format("substring({0} from {1})", - r, - startIndex); - case "Round": - case "Floor": - case "Ceiling": - case "Coalesce": - case "Abs": - case "Sum": - return string.Format("{0}({1}{2})", - m.Method.Name, - r, - args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - case "Concat": - var s = new StringBuilder(); - foreach (Object e in args) - { - s.AppendFormat(" || {0}", e); - } - return string.Format("{0}{1}", r, s.ToString()); - - case "In": - - var member = Expression.Convert(m.Arguments[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - - var inArgs = getter() as object[]; - - var sIn = new StringBuilder(); - foreach (Object e in inArgs) - { - if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]") - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(e, e.GetType())); - } - else - { - var listArgs = e as IList; - foreach (Object el in listArgs) - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(el, el.GetType())); - } - } - } - - return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); - case "Desc": - return string.Format("{0} DESC", r); - case "Alias": - case "As": - return string.Format("{0} As {1}", r, - GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - case "ToString": - return r.ToString(); - default: - var s2 = new StringBuilder(); - foreach (Object e in args) - { - s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - } - return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - list.Add(Visit(original[i])); - - } - return list; - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - - protected virtual string BindOperant(ExpressionType e) - { - - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return string.Format("\"{0}\"", name); - } - - private string GetQuotedTrueValue() - { - return GetQuotedValue(true, typeof(bool)); - } - - private string GetQuotedFalseValue() - { - return GetQuotedValue(false, typeof(bool)); - } - - public virtual string GetQuotedValue(object value, Type fieldType) - { - return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); - } - - private string GetTrueExpression() - { - object o = GetQuotedTrueValue(); - return string.Format("({0}={1})", o, o); - } - - private string GetFalseExpression() - { - - return string.Format("({0}={1})", - GetQuotedTrueValue(), - GetQuotedFalseValue()); - } - - private bool IsTrueExpression(string exp) - { - return (exp == GetTrueExpression()); - } - - private bool IsFalseExpression(string exp) - { - return (exp == GetFalseExpression()); - } - - protected bool IsFieldName(string quotedExp) - { - //Not entirely sure this is reliable, but its better then simply returning true - return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length; - } + + //protected bool IsFieldName(string quotedExp) + //{ + // //Not entirely sure this is reliable, but its better then simply returning true + // return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length; + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index 9a27cc8183..bbdb7a5509 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -8,159 +9,28 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { - internal class PocoToSqlExpressionHelper : BaseExpressionHelper + internal class PocoToSqlExpressionHelper : BaseExpressionHelper { - private string sep = " "; - private Database.PocoData pd; + private readonly Database.PocoData _pd; public PocoToSqlExpressionHelper() { - pd = new Database.PocoData(typeof(T)); + _pd = new Database.PocoData(typeof(T)); } - - protected internal virtual string Visit(Expression exp) - { - - if (exp == null) return string.Empty; - switch (exp.NodeType) - { - case ExpressionType.Lambda: - return VisitLambda(exp as LambdaExpression); - case ExpressionType.MemberAccess: - return VisitMemberAccess(exp as MemberExpression); - case ExpressionType.Constant: - return VisitConstant(exp as ConstantExpression); - case ExpressionType.Add: - case ExpressionType.AddChecked: - case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: - case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: - case ExpressionType.Divide: - case ExpressionType.Modulo: - case ExpressionType.And: - case ExpressionType.AndAlso: - case ExpressionType.Or: - case ExpressionType.OrElse: - case ExpressionType.LessThan: - case ExpressionType.LessThanOrEqual: - case ExpressionType.GreaterThan: - case ExpressionType.GreaterThanOrEqual: - case ExpressionType.Equal: - case ExpressionType.NotEqual: - case ExpressionType.Coalesce: - case ExpressionType.ArrayIndex: - case ExpressionType.RightShift: - case ExpressionType.LeftShift: - case ExpressionType.ExclusiveOr: - return VisitBinary(exp as BinaryExpression); - case ExpressionType.Negate: - case ExpressionType.NegateChecked: - case ExpressionType.Not: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - case ExpressionType.ArrayLength: - case ExpressionType.Quote: - case ExpressionType.TypeAs: - return VisitUnary(exp as UnaryExpression); - case ExpressionType.Parameter: - return VisitParameter(exp as ParameterExpression); - case ExpressionType.Call: - return VisitMethodCall(exp as MethodCallExpression); - case ExpressionType.New: - return VisitNew(exp as NewExpression); - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - return VisitNewArray(exp as NewArrayExpression); - default: - return exp.ToString(); - } - } - - protected virtual string VisitLambda(LambdaExpression lambda) - { - if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ") - { - MemberExpression m = lambda.Body as MemberExpression; - - if (m.Expression != null) - { - string r = VisitMemberAccess(m); - return string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - - } - return Visit(lambda.Body); - } - - protected virtual string VisitBinary(BinaryExpression b) - { - string left, right; - var operand = BindOperant(b.NodeType); //sep= " " ?? - if (operand == "AND" || operand == "OR") - { - MemberExpression m = b.Left as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - left = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - left = Visit(b.Left); - } - m = b.Right as MemberExpression; - if (m != null && m.Expression != null) - { - string r = VisitMemberAccess(m); - right = string.Format("{0}={1}", r, GetQuotedTrueValue()); - } - else - { - right = Visit(b.Right); - } - } - else - { - left = Visit(b.Left); - right = Visit(b.Right); - } - - if (operand == "=" && right == "null") operand = "is"; - else if (operand == "<>" && right == "null") operand = "is not"; - else if (operand == "=" || operand == "<>") - { - if (IsTrueExpression(right)) right = GetQuotedTrueValue(); - else if (IsFalseExpression(right)) right = GetQuotedFalseValue(); - - if (IsTrueExpression(left)) left = GetQuotedTrueValue(); - else if (IsFalseExpression(left)) left = GetQuotedFalseValue(); - - } - - switch (operand) - { - case "MOD": - case "COALESCE": - return string.Format("{0}({1},{2})", operand, left, right); - default: - return left + sep + operand + sep + right; - } - } - - protected virtual string VisitMemberAccess(MemberExpression m) + + protected override string VisitMemberAccess(MemberExpression m) { if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T)) { - string field = GetFieldName(pd, m.Member.Name); + string field = GetFieldName(_pd, m.Member.Name); return field; } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - string field = GetFieldName(pd, m.Member.Name); + string field = GetFieldName(_pd, m.Member.Name); return field; } @@ -168,303 +38,14 @@ namespace Umbraco.Core.Persistence.Querying var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); object o = getter(); - return GetQuotedValue(o, o != null ? o.GetType() : null); + + SqlParameters.Add(o); + return string.Format("@{0}", SqlParameters.Count - 1); + + //return GetQuotedValue(o, o != null ? o.GetType() : null); } - - protected virtual string VisitNew(NewExpression nex) - { - // TODO : check ! - var member = Expression.Convert(nex, typeof(object)); - var lambda = Expression.Lambda>(member); - try - { - var getter = lambda.Compile(); - object o = getter(); - return GetQuotedValue(o, o.GetType()); - } - catch (System.InvalidOperationException) - { // FieldName ? - List exprs = VisitExpressionList(nex.Arguments); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.AppendFormat("{0}{1}", - r.Length > 0 ? "," : "", - e); - } - return r.ToString(); - } - - } - - protected virtual string VisitParameter(ParameterExpression p) - { - return p.Name; - } - - protected virtual string VisitConstant(ConstantExpression c) - { - if (c.Value == null) - return "null"; - else if (c.Value.GetType() == typeof(bool)) - { - object o = GetQuotedValue(c.Value, c.Value.GetType()); - return string.Format("({0}={1})", GetQuotedTrueValue(), o); - } - else - return GetQuotedValue(c.Value, c.Value.GetType()); - } - - protected virtual string VisitUnary(UnaryExpression u) - { - switch (u.NodeType) - { - case ExpressionType.Not: - string o = Visit(u.Operand); - if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool)); - return "NOT (" + o + ")"; - default: - return Visit(u.Operand); - } - - } - - protected virtual string VisitMethodCall(MethodCallExpression m) - { - List args = this.VisitExpressionList(m.Arguments); - - Object r; - if (m.Object != null) - r = Visit(m.Object); - else - { - r = args[0]; - args.RemoveAt(0); - } - - //TODO: We should probably add the same logic we've done for ModelToSqlExpressionHelper with checking for: - // InvariantStartsWith, InvariantEndsWith, SqlWildcard, etc... - // since we should be able to easily handle that with the Poco objects too. - - switch (m.Method.Name) - { - case "ToUpper": - return string.Format("upper({0})", r); - case "ToLower": - return string.Format("lower({0})", r); - case "SqlWildcard": - case "StartsWith": - case "EndsWith": - case "Contains": - case "Equals": - case "SqlStartsWith": - case "SqlEndsWith": - case "SqlContains": - case "SqlEquals": - case "InvariantStartsWith": - case "InvariantEndsWith": - case "InvariantContains": - case "InvariantEquals": - //default - var colType = TextColumnType.NVarchar; - //then check if this arg has been passed in - if (m.Arguments.Count > 1) - { - var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); - if (colTypeArg != null) - { - colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; - } - } - return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType); - case "Substring": - var startIndex = Int32.Parse(args[0].ToString()) + 1; - if (args.Count == 2) - { - var length = Int32.Parse(args[1].ToString()); - return string.Format("substring({0} from {1} for {2})", - r, - startIndex, - length); - } - else - return string.Format("substring({0} from {1})", - r, - startIndex); - case "Round": - case "Floor": - case "Ceiling": - case "Coalesce": - case "Abs": - case "Sum": - return string.Format("{0}({1}{2})", - m.Method.Name, - r, - args.Count == 1 ? string.Format(",{0}", args[0]) : ""); - case "Concat": - var s = new StringBuilder(); - foreach (Object e in args) - { - s.AppendFormat(" || {0}", e); - } - return string.Format("{0}{1}", r, s.ToString()); - - case "In": - - var member = Expression.Convert(m.Arguments[1], typeof(object)); - var lambda = Expression.Lambda>(member); - var getter = lambda.Compile(); - - var inArgs = getter() as object[]; - - var sIn = new StringBuilder(); - foreach (Object e in inArgs) - { - if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]") - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(e, e.GetType())); - } - else - { - var listArgs = e as IList; - foreach (Object el in listArgs) - { - sIn.AppendFormat("{0}{1}", - sIn.Length > 0 ? "," : "", - GetQuotedValue(el, el.GetType())); - } - } - } - - return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString()); - case "Desc": - return string.Format("{0} DESC", r); - case "Alias": - case "As": - return string.Format("{0} As {1}", r, - GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString())))); - case "ToString": - return r.ToString(); - default: - var s2 = new StringBuilder(); - foreach (Object e in args) - { - s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType())); - } - return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString()); - } - } - - protected virtual List VisitExpressionList(ReadOnlyCollection original) - { - var list = new List(); - for (int i = 0, n = original.Count; i < n; i++) - { - if (original[i].NodeType == ExpressionType.NewArrayInit || - original[i].NodeType == ExpressionType.NewArrayBounds) - { - - list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression)); - } - else - list.Add(Visit(original[i])); - - } - return list; - } - - protected virtual string VisitNewArray(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - var r = new StringBuilder(); - foreach (Object e in exprs) - { - r.Append(r.Length > 0 ? "," + e : e); - } - - return r.ToString(); - } - - protected virtual List VisitNewArrayFromExpressionList(NewArrayExpression na) - { - - List exprs = VisitExpressionList(na.Expressions); - return exprs; - } - - - protected virtual string BindOperant(ExpressionType e) - { - - switch (e) - { - case ExpressionType.Equal: - return "="; - case ExpressionType.NotEqual: - return "<>"; - case ExpressionType.GreaterThan: - return ">"; - case ExpressionType.GreaterThanOrEqual: - return ">="; - case ExpressionType.LessThan: - return "<"; - case ExpressionType.LessThanOrEqual: - return "<="; - case ExpressionType.AndAlso: - return "AND"; - case ExpressionType.OrElse: - return "OR"; - case ExpressionType.Add: - return "+"; - case ExpressionType.Subtract: - return "-"; - case ExpressionType.Multiply: - return "*"; - case ExpressionType.Divide: - return "/"; - case ExpressionType.Modulo: - return "MOD"; - case ExpressionType.Coalesce: - return "COALESCE"; - default: - return e.ToString(); - } - } - - public virtual string GetQuotedTableName(string tableName) - { - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return string.Format("\"{0}\"", name); - } - - private string GetQuotedTrueValue() - { - return GetQuotedValue(true, typeof(bool)); - } - - private string GetQuotedFalseValue() - { - return GetQuotedValue(false, typeof(bool)); - } - - public virtual string GetQuotedValue(object value, Type fieldType) - { - return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue); - } - + protected virtual string GetFieldName(Database.PocoData pocoData, string name) { var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); @@ -472,34 +53,10 @@ namespace Umbraco.Core.Persistence.Querying SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName), SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName)); } - - private string GetTrueExpression() - { - object o = GetQuotedTrueValue(); - return string.Format("({0}={1})", o, o); - } - - private string GetFalseExpression() - { - - return string.Format("({0}={1})", - GetQuotedTrueValue(), - GetQuotedFalseValue()); - } - - private bool IsTrueExpression(string exp) - { - return (exp == GetTrueExpression()); - } - - private bool IsFalseExpression(string exp) - { - return (exp == GetFalseExpression()); - } - - protected bool IsFieldName(string quotedExp) - { - return true; - } + + //protected bool IsFieldName(string quotedExp) + //{ + // return true; + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index b426619dcf..4dd268268f 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; namespace Umbraco.Core.Persistence.Querying @@ -10,37 +11,46 @@ namespace Umbraco.Core.Persistence.Querying /// public class Query : IQuery { - //private readonly ExpressionHelper _expresionist = new ExpressionHelper(); - private readonly ModelToSqlExpressionHelper _expresionist = new ModelToSqlExpressionHelper(); - private readonly List _wheres = new List(); - - public Query() - : base() - { - - } + private readonly List> _wheres = new List>(); + /// + /// Helper method to be used instead of manually creating an instance + /// public static IQuery Builder { - get - { - return new Query(); - } + get { return new Query(); } } + /// + /// Adds a where clause to the query + /// + /// + /// This instance so calls to this method are chainable public virtual IQuery Where(Expression> predicate) { if (predicate != null) { - string whereExpression = _expresionist.Visit(predicate); - _wheres.Add(whereExpression); + var expressionHelper = new ModelToSqlExpressionHelper(); + string whereExpression = expressionHelper.Visit(predicate); + + _wheres.Add(new Tuple(whereExpression, expressionHelper.GetSqlParameters())); } return this; } - - public List WhereClauses() + + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + public IEnumerable> GetWhereClauses() { return _wheres; } + + [Obsolete("This is no longer used, use the GetWhereClauses method which includes the SQL parameters")] + public List WhereClauses() + { + return _wheres.Select(x => x.Item1).ToList(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs index ef5e14a019..b012293340 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs @@ -15,14 +15,10 @@ namespace Umbraco.Core.Persistence.Querying if (sql == null) throw new Exception("Sql cannot be null"); - var query1 = query as Query; - if (query1 == null) - throw new Exception("Query cannot be null"); - _sql = sql; - foreach (var clause in query1.WhereClauses()) + foreach (var clause in query.GetWhereClauses()) { - _sql.Where(clause); + _sql.Where(clause.Item1, clause.Item2); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index c935d1626c..a96d8b3bbf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -66,15 +66,31 @@ namespace Umbraco.Core.Persistence.Repositories { bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key).Append(GetGroupBy(isContent, isMedia)); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); + var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); + + if (isMedia) + { + //for now treat media differently + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); - return entity; + return entities.FirstOrDefault(); + } + else + { + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + } public virtual IUmbracoEntity Get(int id) @@ -94,61 +110,101 @@ namespace Umbraco.Core.Persistence.Repositories { bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id).Append(GetGroupBy(isContent, isMedia)); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; + var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); + + if (isMedia) + { + //for now treat media differently + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); + return entities.FirstOrDefault(); + } + else + { + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; - return entity; + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + } public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) { if (ids.Any()) { - foreach (var id in ids) + return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new {ids = ids})); + } + else + { + return PerformGetAll(objectTypeId); + } + } + + public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) + { + if (keys.Any()) + { + return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { keys = keys })); + } + else + { + return PerformGetAll(objectTypeId); + } + } + + private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) + { + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); + + var factory = new UmbracoEntityFactory(); + + if (isMedia) + { + //for now treat media differently + //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields + var entities = _work.Database.Fetch( + new UmbracoEntityRelator().Map, sql); + foreach (var entity in entities) { - yield return Get(id, objectTypeId); + yield return entity; } } else { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - var sql = GetBaseWhere(GetBase, isContent, isMedia, string.Empty, objectTypeId).Append(GetGroupBy(isContent, isMedia)); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) + var dtos = _work.Database.Fetch(sql); + foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) { - //for now treat media differently - //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - foreach (var entity in entities) - { - yield return entity; - } - } - else - { - var dtos = _work.Database.Fetch(sql); - foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) - { - yield return entity; - } + yield return entity; } } } + public virtual IEnumerable GetByQuery(IQuery query) { - var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query) query).WhereClauses())); - var sqlClause = GetBase(false, false, wheres); + //TODO: We need to fix all of this and how it handles parameters! + + var wheres = query.GetWhereClauses().ToArray(); + + var sqlClause = GetBase(false, false, sql1 => + { + //adds the additional filters + foreach (var whereClause in wheres) + { + sql1.Where(whereClause.Item1, whereClause.Item2); + } + }); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); @@ -162,28 +218,48 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query)query).WhereClauses())); - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, wheres, objectTypeId); + var wheres = query.GetWhereClauses().ToArray(); + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql1 => + { + //adds the additional filters + foreach (var whereClause in wheres) + { + sql1.Where(whereClause.Item1, whereClause.Item2); + } + + }, objectTypeId); + var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy(isContent, isMedia)); + var entitySql = translator.Translate(); var factory = new UmbracoEntityFactory(); if (isMedia) { + var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql => + { + //adds the additional filters + foreach (var whereClause in wheres) + { + sql.Where(whereClause.Item1, whereClause.Item2); + } + }); + //treat media differently for now //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); + new UmbracoEntityRelator().Map, mediaSql); return entities; } else { //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData - var dtos = _work.Database.Fetch(sql); + var dtos = _work.Database.Fetch(entitySql.Append(GetGroupBy(isContent, false))); return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); } } @@ -193,7 +269,68 @@ namespace Umbraco.Core.Persistence.Repositories #region Sql Statements - protected virtual Sql GetBase(bool isContent, bool isMedia, string additionWhereStatement = "") + protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); + } + + protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); + } + + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) + { + var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId); + + if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); + + return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); + } + + private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) + { + //this will add any dataNvarchar property to the output which can be added to the additional properties + + var joinSql = new Sql() + .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .InnerJoin() + .On(dto => dto.Id, dto => dto.PropertyTypeId) + .InnerJoin() + .On(dto => dto.DataTypeId, dto => dto.DataTypeId) + .Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media}); + + if (filter != null) + { + filter(joinSql); + } + + //We're going to create a query to query against the entity SQL + // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join + // the entitySql query, we have to join the wrapped query to get the ntext in the result + + var wrappedSql = new Sql("SELECT * FROM (") + .Append(entitySql) + .Append(new Sql(") tmpTbl LEFT JOIN (")) + .Append(joinSql) + .Append(new Sql(") as property ON id = property.contentNodeId")) + .OrderBy("sortOrder"); + + return wrappedSql; + } + + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { var columns = new List { @@ -221,13 +358,9 @@ namespace Umbraco.Core.Persistence.Repositories columns.Add("contenttype.isContainer"); } - if (isMedia) - { - columns.Add("property.dataNvarchar as umbracoFile"); - columns.Add("property.controlId"); - } + //Creates an SQL query to return a single row for the entity - var sql = new Sql() + var entitySql = new Sql() .Select(columns.ToArray()) .From("umbracoNode umbracoNode") .LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); @@ -235,7 +368,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isContent || isMedia) { - sql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id") + entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id") .LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType") .LeftJoin( "(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published") @@ -245,60 +378,54 @@ namespace Umbraco.Core.Persistence.Repositories .On("umbracoNode.id = latest.nodeId"); } - if (isMedia) + if (customFilter != null) { - sql.LeftJoin( - "(SELECT contentNodeId, versionId, dataNvarchar, controlId FROM cmsPropertyData " + - "INNER JOIN umbracoNode ON cmsPropertyData.contentNodeId = umbracoNode.id " + - "INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid " + - "INNER JOIN cmsDataType ON cmsPropertyType.dataTypeId = cmsDataType.nodeId "+ - "WHERE umbracoNode.nodeObjectType = '" + Constants.ObjectTypes.Media + "'" + additionWhereStatement + ") as property") - .On("umbracoNode.id = property.contentNodeId"); + customFilter(entitySql); } - return sql; + return entitySql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, string additionWhereStatement, Guid nodeObjectType) + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType) { - var sql = baseQuery(isContent, isMedia, additionWhereStatement) + var sql = baseQuery(isContent, isMedia, filter) .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, int id) + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") + var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.id = @Id", new { Id = id }) .Append(GetGroupBy(isContent, isMedia)); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid key) + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'") + var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key }) .Append(GetGroupBy(isContent, isMedia)); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'") + var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", - new { Id = id, NodeObjectType = nodeObjectType }); + new {Id = id, NodeObjectType = nodeObjectType}); return sql; } - protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) + protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) { - var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'") + var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", new { UniqueID = key, NodeObjectType = nodeObjectType }); return sql; } - protected virtual Sql GetGroupBy(bool isContent, bool isMedia) + protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) { var columns = new List { @@ -324,19 +451,18 @@ namespace Umbraco.Core.Persistence.Repositories columns.Add("contenttype.thumbnail"); columns.Add("contenttype.isContainer"); } + + var sql = new Sql() + .GroupBy(columns.ToArray()); - if (isMedia) + if (includeSort) { - columns.Add("property.dataNvarchar"); - columns.Add("property.controlId"); + sql = sql.OrderBy("umbracoNode.sortOrder"); } - var sql = new Sql() - .GroupBy(columns.ToArray()) - .OrderBy("umbracoNode.sortOrder"); return sql; } - + #endregion /// @@ -418,10 +544,10 @@ namespace Umbraco.Core.Persistence.Repositories Current.UmbracoProperties = new List(); } Current.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty - { - DataTypeControlId = p.DataTypeControlId, - Value = p.UmbracoFile - }); + { + DataTypeControlId = p.DataTypeControlId, + Value = p.UmbracoFile + }); // Return null to indicate we're not done with this UmbracoEntity yet return null; } @@ -433,7 +559,7 @@ namespace Umbraco.Core.Persistence.Repositories var prev = Current; // Setup the new current UmbracoEntity - + Current = _factory.BuildEntityFromDynamic(a); //add the property/create the prop list if null diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 22e3970e1c..ace637764b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -188,6 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories /// /// protected PropertyCollection GetPropertyCollection(int id, Guid versionId, IContentTypeComposition contentType, DateTime createDate, DateTime updateDate) + + { var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index b4c0020305..899a462172 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -13,10 +13,19 @@ namespace Umbraco.Core.Persistence.SqlSyntax { string EscapeString(string val); + string GetWildcardPlaceholder(); + string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); + + [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); string GetQuotedTableName(string tableName); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs new file mode 100644 index 0000000000..84c6e6e824 --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs @@ -0,0 +1,120 @@ +using System; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence.SqlSyntax +{ + /// + /// Abstract class for defining MS sql implementations + /// + /// + public abstract class MicrosoftSqlSyntaxProviderBase : SqlSyntaxProviderBase + where TSyntax : ISqlSyntaxProvider + { + public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } } + + public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } } + + public override string GetQuotedTableName(string tableName) + { + return string.Format("[{0}]", tableName); + } + + public override string GetQuotedColumnName(string columnName) + { + return string.Format("[{0}]", columnName); + } + + public override string GetQuotedName(string name) + { + return string.Format("[{0}]", name); + } + + public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEqualComparison(column, paramIndex, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for = comparison with NText columns but allows this syntax + return string.Format("{0} LIKE @{1}", column, paramIndex); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnWildcardComparison(column, paramIndex, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE @{1}", column, paramIndex); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnStartsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnEndsWithComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '%{1}%'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + + [Obsolete("Use the overload with the parameter index instead")] + public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + switch (columnType) + { + case TextColumnType.NVarchar: + return base.GetStringColumnContainsComparison(column, value, columnType); + case TextColumnType.NText: + //MSSQL doesn't allow for upper methods with NText columns + return string.Format("{0} LIKE '{1}'", column, value); + default: + throw new ArgumentOutOfRangeException("columnType"); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 170b10cb5e..44b8a761c9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// Represents an SqlSyntaxProvider for Sql Ce /// [SqlSyntaxProviderAttribute("System.Data.SqlServerCe.4.0")] - public class SqlCeSyntaxProvider : SqlSyntaxProviderBase + public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlCeSyntaxProvider() { @@ -60,6 +60,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax return indexType; } + [Obsolete("Use the overload with the parameter index instead")] public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) { switch (columnType) @@ -74,76 +75,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax } } - public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnStartsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '{1}%'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEndsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '%{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '%{1}%'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetQuotedTableName(string tableName) - { - return string.Format("[{0}]", tableName); - } - - public override string GetQuotedColumnName(string columnName) - { - return string.Format("[{0}]", columnName); - } - - public override string GetQuotedName(string name) - { - return string.Format("[{0}]", name); - } + public override string FormatColumnRename(string tableName, string oldName, string newName) { @@ -285,10 +217,10 @@ ORDER BY TABLE_NAME, INDEX_NAME"); } } - public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } } + public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } - public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index ca9e88fb1e..3388b984ee 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { @@ -10,7 +9,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// Represents an SqlSyntaxProvider for Sql Server /// [SqlSyntaxProviderAttribute("System.Data.SqlClient")] - public class SqlServerSyntaxProvider : SqlSyntaxProviderBase + public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { public SqlServerSyntaxProvider() { @@ -33,91 +32,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// Gets/sets the version of the current SQL server instance /// internal Lazy VersionName { get; set; } - - public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEqualComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for = comparison with NText columns but allows this syntax - return string.Format("{0} LIKE '{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnStartsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '{1}%'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnEndsWithComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '%{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '%{1}%'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - switch (columnType) - { - case TextColumnType.NVarchar: - return base.GetStringColumnContainsComparison(column, value, columnType); - case TextColumnType.NText: - //MSSQL doesn't allow for upper methods with NText columns - return string.Format("{0} LIKE '{1}'", column, value); - default: - throw new ArgumentOutOfRangeException("columnType"); - } - } - - public override string GetQuotedTableName(string tableName) - { - return string.Format("[{0}]", tableName); - } - - public override string GetQuotedColumnName(string columnName) - { - return string.Format("[{0}]", columnName); - } - - public override string GetQuotedName(string name) - { - return string.Format("[{0}]", name); - } + + public override IEnumerable GetTablesInSchema(Database db) { @@ -218,12 +134,11 @@ order by T.name, I.name"); get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; } } - public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } } - + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } } - public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 1ceeddafb7..08d85e0573 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -33,6 +33,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax }; } + public string GetWildcardPlaceholder() + { + return "%"; + } + public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; @@ -108,34 +113,51 @@ namespace Umbraco.Core.Persistence.SqlSyntax return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); } + public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = upper(@{1})", column, paramIndex); + } + + public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex); + } + + [Obsolete("Use the overload with the parameter index instead")] public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); } + [Obsolete("Use the overload with the parameter index instead")] public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) like '{1}%'", column, value.ToUpper()); + return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper()); } + [Obsolete("Use the overload with the parameter index instead")] public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) like '%{1}'", column, value.ToUpper()); + return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper()); } + [Obsolete("Use the overload with the parameter index instead")] public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper()); + return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper()); } + [Obsolete("Use the overload with the parameter index instead")] public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) { //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) like '{1}'", column, value.ToUpper()); + return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); } public virtual string GetQuotedTableName(string tableName) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index 8bcc965af7..d664ca53f9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -10,12 +10,12 @@ /// /// See: http://issues.umbraco.org/issue/U4-3876 /// - public static string GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery) + public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery) { - return string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)", - sqlProvider.GetQuotedTableName(tableName), - sqlProvider.GetQuotedColumnName(columnName), - subQuery.SQL); + return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)", + sqlProvider.GetQuotedTableName(tableName), + sqlProvider.GetQuotedColumnName(columnName), + subQuery.SQL), subQuery.Arguments); } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index d24f39d726..6988189cdc 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -484,6 +484,10 @@ namespace Umbraco.Core.Services public IEnumerable GetDescendants(int id) { var content = GetById(id); + if (content == null) + { + return Enumerable.Empty(); + } return GetDescendants(content); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 6b237e652d..5402290872 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -402,6 +402,10 @@ namespace Umbraco.Core.Services public IEnumerable GetDescendants(int id) { var media = GetById(id); + if (media == null) + { + return Enumerable.Empty(); + } return GetDescendants(media); } @@ -1198,4 +1202,4 @@ namespace Umbraco.Core.Services public static event TypedEventHandler EmptiedRecycleBin; #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 31abc05e87..36192f03ac 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -233,6 +233,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs b/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs new file mode 100644 index 0000000000..eb177981c3 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs @@ -0,0 +1,133 @@ +//using System; +//using System.Linq; +//using NUnit.Framework; +//using Umbraco.Core.Models; +//using Umbraco.Core.Persistence; +//using Umbraco.Core.Persistence.SqlSyntax; +//using Umbraco.Tests.TestHelpers; +//using Umbraco.Tests.TestHelpers.Entities; + +//namespace Umbraco.Tests.Persistence +//{ +// [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] +// [TestFixture] +// public class PetaPocoDynamicQueryTests : BaseDatabaseFactoryTest +// { +// [Test] +// public void Check_Poco_Storage_Growth() +// { +// //CreateStuff(); + +// for (int i = 0; i < 1000; i++) +// { +// DatabaseContext.Database.Fetch( +// "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='" + i + "'"); +// } + +// //var oc11 = Database.PocoData.GetCachedPocoData().Count(); +// //var oc12 = Database.PocoData.GetConverters().Count(); +// //var oc13 = Database.GetAutoMappers().Count(); +// //var oc14 = Database.GetMultiPocoFactories().Count(); + +// //for (int i = 0; i < 2; i++) +// //{ +// // i1 = DatabaseContext.Database.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); +// // r1 = i1.Select(x => x.TABLE_NAME).Cast().ToList(); +// //} + +// //var oc21 = Database.PocoData.GetCachedPocoData().Count(); +// //var oc22 = Database.PocoData.GetConverters().Count(); +// //var oc23 = Database.GetAutoMappers().Count(); +// //var oc24 = Database.GetMultiPocoFactories().Count(); + +// //var roots = ServiceContext.ContentService.GetRootContent(); +// //foreach (var content in roots) +// //{ +// // var d = ServiceContext.ContentService.GetDescendants(content); +// //} + +// //var oc31 = Database.PocoData.GetCachedPocoData().Count(); +// //var oc32 = Database.PocoData.GetConverters().Count(); +// //var oc33 = Database.GetAutoMappers().Count(); +// //var oc34 = Database.GetMultiPocoFactories().Count(); + +// //for (int i = 0; i < 2; i++) +// //{ +// // roots = ServiceContext.ContentService.GetRootContent(); +// // foreach (var content in roots) +// // { +// // var d = ServiceContext.ContentService.GetDescendants(content); +// // } +// //} + +// //var oc41 = Database.PocoData.GetCachedPocoData().Count(); +// //var oc42 = Database.PocoData.GetConverters().Count(); +// //var oc43 = Database.GetAutoMappers().Count(); +// //var oc44 = Database.GetMultiPocoFactories().Count(); + +// //var i2 = DatabaseContext.Database.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); +// //var r2 = +// // i2.Select( +// // item => +// // new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, +// // item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + + +// var pocoData = Database.PocoData.GetCachedPocoData(); +// Console.WriteLine("GetCachedPocoData: " + pocoData.Count()); +// foreach (var keyValuePair in pocoData) +// { +// Console.WriteLine(keyValuePair.Value.GetFactories().Count()); +// } + +// Console.WriteLine("GetConverters: " + Database.PocoData.GetConverters().Count()); +// Console.WriteLine("GetAutoMappers: " + Database.GetAutoMappers().Count()); +// Console.WriteLine("GetMultiPocoFactories: " + Database.GetMultiPocoFactories().Count()); + +// //Assert.AreEqual(oc11, oc21); +// //Assert.AreEqual(oc12, oc22); +// //Assert.AreEqual(oc13, oc23); +// //Assert.AreEqual(oc14, oc24); + +// //Assert.AreEqual(oc31, oc41); +// //Assert.AreEqual(oc32, oc42); +// //Assert.AreEqual(oc33, oc43); +// //Assert.AreEqual(oc34, oc44); +// } + +// public void CreateStuff() +// { +// var contentType1 = MockedContentTypes.CreateTextpageContentType("test1", "test1"); +// var contentType2 = MockedContentTypes.CreateTextpageContentType("test2", "test2"); +// var contentType3 = MockedContentTypes.CreateTextpageContentType("test3", "test3"); +// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 }); +// contentType1.AllowedContentTypes = new[] +// { +// new ContentTypeSort(new Lazy(() => contentType2.Id), 0, contentType2.Alias), +// new ContentTypeSort(new Lazy(() => contentType3.Id), 1, contentType3.Alias) +// }; +// contentType2.AllowedContentTypes = new[] +// { +// new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias), +// new ContentTypeSort(new Lazy(() => contentType3.Id), 1, contentType3.Alias) +// }; +// contentType3.AllowedContentTypes = new[] +// { +// new ContentTypeSort(new Lazy(() => contentType1.Id), 0, contentType1.Alias), +// new ContentTypeSort(new Lazy(() => contentType2.Id), 1, contentType2.Alias) +// }; +// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 }); + +// var roots = MockedContent.CreateTextpageContent(contentType1, -1, 3); +// ServiceContext.ContentService.Save(roots); +// foreach (var root in roots) +// { +// var item1 = MockedContent.CreateTextpageContent(contentType1, root.Id, 3); +// var item2 = MockedContent.CreateTextpageContent(contentType2, root.Id, 3); +// var item3 = MockedContent.CreateTextpageContent(contentType3, root.Id, 3); + +// ServiceContext.ContentService.Save(item1.Concat(item2).Concat(item3)); +// } +// } +// } +//} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs index b0b7247edb..6348fadc9f 100644 --- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs @@ -1,14 +1,195 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Persistence { + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture, NUnit.Framework.Ignore] + public class PetaPocoCachesTest : BaseServiceTest + { + /// + /// This tests the peta poco caches + /// + /// + /// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as + /// ContentTypeRepository.GetAll(1,2,3,4); + /// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length + /// arrays will produce a unique query which then gets added to the cache. + /// + /// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow. + /// + [Test] + public void Check_Peta_Poco_Caches() + { + var result = new List>>(); + + Database.PocoData.UseLongKeys = true; + + for (int i = 0; i < 2; i++) + { + int id1, id2, id3; + string alias; + CreateStuff(out id1, out id2, out id3, out alias); + QueryStuff(id1, id2, id3, alias); + + double totalBytes1; + IEnumerable keys; + Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); + + result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); + } + + for (int index = 0; index < result.Count; index++) + { + var tuple = result[index]; + Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); + if (index != 0) + { + Console.WriteLine("----------------DIFFERENCE---------------------"); + var diff = tuple.Item3.Except(result[index - 1].Item3); + foreach (var d in diff) + { + Console.WriteLine(d); + } + } + + } + + var allByteResults = result.Select(x => x.Item1).Distinct(); + var totalKeys = result.Select(x => x.Item2).Distinct(); + + Assert.AreEqual(1, allByteResults.Count()); + Assert.AreEqual(1, totalKeys.Count()); + } + + [Test] + public void Verify_Memory_Expires() + { + Database.PocoData.SlidingExpirationSeconds = 2; + + var managedCache = new Database.ManagedCache(); + + int id1, id2, id3; + string alias; + CreateStuff(out id1, out id2, out id3, out alias); + QueryStuff(id1, id2, id3, alias); + + var count1 = managedCache.GetCache().GetCount(); + Console.WriteLine("Keys = " + count1); + Assert.Greater(count1, 0); + + Thread.Sleep(10000); + + var count2 = managedCache.GetCache().GetCount(); + Console.WriteLine("Keys = " + count2); + Assert.Less(count2, count1); + } + + private void QueryStuff(int id1, int id2, int id3, string alias1) + { + var contentService = ServiceContext.ContentService; + + + + contentService.CountDescendants(id3); + + contentService.CountChildren(id3); + + contentService.Count(contentTypeAlias: alias1); + + contentService.Count(); + + contentService.GetById(Guid.NewGuid()); + + contentService.GetByLevel(2); + + contentService.GetChildren(id1); + + contentService.GetDescendants(id2); + + contentService.GetVersions(id3); + + contentService.GetRootContent(); + + contentService.GetContentForExpiration(); + + contentService.GetContentForRelease(); + + contentService.GetContentInRecycleBin(); + + ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1)) + { + Id = id1, + Path = "-1," + id1 + }); + + contentService.GetByVersion(Guid.NewGuid()); + } + + private void CreateStuff(out int id1, out int id2, out int id3, out string alias) + { + var contentService = ServiceContext.ContentService; + + var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N"); + alias = ctAlias; + + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0); + } + var contentTypeService = ServiceContext.ContentTypeService; + var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type"); + contentTypeService.Save(contentType); + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); + } + var parent = contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); + id1 = parent.Id; + + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", parent, ctAlias); + } + IContent current = parent; + for (int i = 0; i < 20; i++) + { + current = contentService.CreateContentWithIdentity("Test", current, ctAlias); + } + contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true); + contentType.PropertyGroups.First().PropertyTypes.Add( + new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Ntext) + { + Alias = "tags", + DataTypeDefinitionId = 1041 + }); + contentTypeService.Save(contentType); + var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); + + contentService.Publish(content1); + id2 = content1.Id; + + var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); + + contentService.Publish(content2); + id3 = content2.Id; + + contentService.MoveToRecycleBin(content1); + } + } + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] [TestFixture] public class PetaPocoExtensionsTest : BaseDatabaseFactoryTest diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index 78254f6f32..4b1c71901d 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")); var sql = new Sql(); sql.Select("*") @@ -36,6 +36,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } @@ -50,8 +56,8 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'") - .Where("[umbracoNode].[id] = 1050"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) + .Where("[umbracoNode].[id] = @0", 1050); var sql = new Sql(); sql.Select("*") @@ -67,6 +73,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } @@ -82,9 +94,9 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'") - .Where("[umbracoNode].[id] = 1050") - .Where("[cmsContentVersion].[VersionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'") + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) + .Where("[umbracoNode].[id] = @0", 1050) + .Where("[cmsContentVersion].[VersionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) .OrderBy("[cmsContentVersion].[VersionDate] DESC"); var sql = new Sql(); @@ -102,6 +114,11 @@ namespace Umbraco.Tests.Persistence.Querying .OrderByDescending(x => x.VersionDate); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } Console.WriteLine(sql.SQL); } @@ -116,8 +133,8 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*"); expected.From("[cmsPropertyData]"); expected.InnerJoin("[cmsPropertyType]").On("[cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id]"); - expected.Where("[cmsPropertyData].[contentNodeId] = 1050"); - expected.Where("[cmsPropertyData].[versionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'"); + expected.Where("[cmsPropertyData].[contentNodeId] = @0", 1050); + expected.Where("[cmsPropertyData].[versionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")); var sql = new Sql(); sql.Select("*") @@ -128,6 +145,11 @@ namespace Umbraco.Tests.Persistence.Querying .Where(x => x.VersionId == versionId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } Console.WriteLine(sql.SQL); } diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index 6cc5824e92..e25b95383e 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -22,8 +22,8 @@ namespace Umbraco.Tests.Persistence.Querying .On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]") .InnerJoin("[umbracoNode]") .On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'") - .Where("[cmsDocumentType].[IsDefault] = 1"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) + .Where("[cmsDocumentType].[IsDefault] = @0", true); var sql = new Sql(); sql.Select("*") @@ -37,6 +37,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } @@ -52,9 +58,9 @@ namespace Umbraco.Tests.Persistence.Querying .On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]") .InnerJoin("[umbracoNode]") .On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'") - .Where("[cmsDocumentType].[IsDefault] = 1") - .Where("[umbracoNode].[id] = 1050"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) + .Where("[cmsDocumentType].[IsDefault] = @0", true) + .Where("[umbracoNode].[id] = @0", 1050); var sql = new Sql(); sql.Select("*") @@ -64,11 +70,17 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin() .On(left => left.NodeId, right => right.NodeId) .Where(x => x.NodeObjectType == NodeObjectType) - .Where(x => x.IsDefault == true) + .Where(x => x.IsDefault) .Where(x => x.NodeId == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } @@ -100,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Querying var expected = new Sql(); expected.Select("*") .From("[cmsContentTypeAllowedContentType]") - .Where("[cmsContentTypeAllowedContentType].[Id] = 1050"); + .Where("[cmsContentTypeAllowedContentType].[Id] = @0", 1050); var sql = new Sql(); sql.Select("*") @@ -109,6 +121,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } @@ -120,7 +138,7 @@ namespace Umbraco.Tests.Persistence.Querying .From("[cmsPropertyTypeGroup]") .RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]") .InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]") - .Where("[cmsPropertyType].[contentTypeId] = 1050"); + .Where("[cmsPropertyType].[contentTypeId] = @0", 1050); var sql = new Sql(); sql.Select("*") @@ -133,6 +151,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } } diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 1862c45952..113ed68e81 100644 --- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*") .From("[cmsDataType]") .InnerJoin("[umbracoNode]").On("[cmsDataType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = '30a2a501-1978-4ddb-a57b-f7efed43ba3c'"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c")); var sql = new Sql(); sql.Select("*") @@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } } diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index ba1592635f..29c73232d3 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("upper([umbracoNode].[path]) like '-1%'", result); + Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result); + Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]); } [Test] @@ -36,7 +37,8 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("[umbracoNode].[parentID] = -1", result); + Assert.AreEqual("[umbracoNode].[parentID] = @0", result); + Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]); } [Test] @@ -48,7 +50,8 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("[umbracoUser].[userLogin] = 'hello@@world.com'", result); + Assert.AreEqual("[umbracoUser].[userLogin] = @0", result); + Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); } [Test] @@ -60,7 +63,8 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("upper([umbracoUser].[userLogin]) = 'HELLO@@WORLD.COM'", result); + Assert.AreEqual("upper([umbracoUser].[userLogin]) = upper(@0)", result); + Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]); } [Test] @@ -75,7 +79,9 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = 'MYDOMAIN\\\\MYUSER'", result); + Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = upper(@0)", result); + Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]); + } [Test] @@ -90,7 +96,8 @@ namespace Umbraco.Tests.Persistence.Querying Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result); - Assert.AreEqual("upper(`umbracoUser`.`userLogin`) like 'MYDOMAIN\\\\MYUSER%'", result); + Assert.AreEqual("upper(`umbracoUser`.`userLogin`) LIKE upper(@0)", result); + Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]); } } diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index bbe90637f4..5e680a426e 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.Querying .From("[cmsContentVersion]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c'"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c")); var sql = new Sql(); sql.Select("*") @@ -33,6 +33,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } } diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index 2bdb58d5b8..31fc08d822 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying expected.Select("*") .From("[cmsContentType]") .InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]") - .Where("[umbracoNode].[nodeObjectType] = '4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e'"); + .Where("[umbracoNode].[nodeObjectType] = @0", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e")); var sql = new Sql(); sql.Select("*") @@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); + Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); + for (int i = 0; i < expected.Arguments.Length; i++) + { + Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); + } + Console.WriteLine(sql.SQL); } } diff --git a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs index 881e4af110..f934b0c536 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -6,12 +7,141 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Tests.Persistence.Querying { [TestFixture] public class PetaPocoSqlTests : BaseUsingSqlCeSyntax { + //x => + + [Test] + public void Where_Clause_With_Starts_With_Additional_Parameters() + { + var content = new NodeDto() { NodeId = 123, Path = "-1,123" }; + var sql = new Sql("SELECT *").From().Where(x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(content.Path + "%", sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_Starts_With_By_Variable() + { + var content = new NodeDto() {NodeId = 123, Path = "-1,123"}; + var sql = new Sql("SELECT *").From().Where(x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0) AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(2, sql.Arguments.Length); + Assert.AreEqual(content.Path + "%", sql.Arguments[0]); + Assert.AreEqual(content.NodeId, sql.Arguments[1]); + } + + [Test] + public void Where_Clause_With_Not_Starts_With() + { + var level = 1; + var sql = new Sql("SELECT *").From().Where(x => x.Level == level && !x.Path.StartsWith("-20")); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[level] = @0 AND NOT (upper([umbracoNode].[path]) LIKE upper(@1)))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(2, sql.Arguments.Length); + Assert.AreEqual(level, sql.Arguments[0]); + Assert.AreEqual("-20%", sql.Arguments[1]); + } + + [Test] + public void Where_Clause_With_Equals_Clause() + { + var sql = new Sql("SELECT *").From().Where(x => x.Text.Equals("Hello@world.com")); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual("Hello@world.com", sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_False_Boolean() + { + var sql = new Sql("SELECT *").From().Where(x => !x.Trashed); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(true, sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_Boolean() + { + var sql = new Sql("SELECT *").From().Where(x => x.Trashed); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(true, sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_ToUpper() + { + var sql = new Sql("SELECT *").From().Where(x => x.Text.ToUpper() == "hello".ToUpper()); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual("hello", sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_ToString() + { + var sql = new Sql("SELECT *").From().Where(x => x.Text == 1.ToString()); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual("1", sql.Arguments[0]); + } + + [Test] + public void Where_Clause_With_Wildcard() + { + var sql = new Sql("SELECT *").From().Where(x => x.Text.StartsWith("D")); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual("D%", sql.Arguments[0]); + } + + [Test] + public void Where_Clause_Single_Constant() + { + var sql = new Sql("SELECT *").From().Where(x => x.NodeId == 2); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] = @0)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(2, sql.Arguments[0]); + } + + [Test] + public void Where_Clause_And_Constant() + { + var sql = new Sql("SELECT *").From().Where(x => x.NodeId != 2 && x.NodeId != 3); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] <> @0 AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(2, sql.Arguments.Length); + Assert.AreEqual(2, sql.Arguments[0]); + Assert.AreEqual(3, sql.Arguments[1]); + } + + [Test] + public void Where_Clause_Or_Constant() + { + var sql = new Sql("SELECT *").From().Where(x => x.Text == "hello" || x.NodeId == 3); + + Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0 OR [umbracoNode].[id] = @1)", sql.SQL.Replace("\n", " ")); + Assert.AreEqual(2, sql.Arguments.Length); + Assert.AreEqual("hello", sql.Arguments[0]); + Assert.AreEqual(3, sql.Arguments[1]); + } [Test] public void Can_Select_From_With_Type() @@ -78,7 +208,7 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Use_Where_Predicate() { var expected = new Sql(); - expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = 1045"); + expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = @0", 1045); var sql = new Sql(); sql.Select("*").From().Where(x => x.NodeId == 1045); @@ -94,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Querying var expected = new Sql(); expected.Select("*") .From("[cmsContent]") - .Where("[cmsContent].[nodeId] = 1045") - .Where("[cmsContent].[contentType] = 1050"); + .Where("[cmsContent].[nodeId] = @0", 1045) + .Where("[cmsContent].[contentType] = @0", 1050); var sql = new Sql(); sql.Select("*") diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index 25fc2c3d35..28cdd36e3c 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -12,21 +12,7 @@ namespace Umbraco.Tests.Persistence.Querying [TestFixture] public class QueryBuilderTests : BaseUsingSqlCeSyntax { - [Test] - public void Dates_Formatted_Properly() - { - var sql = new Sql(); - sql.Select("*").From(); - - var dt = new DateTime(2013, 11, 21, 13, 25, 55); - - var query = Query.Builder.Where(x => x.ExpireDate <= dt); - var translator = new SqlTranslator(sql, query); - - var result = translator.Translate(); - - Assert.IsTrue(result.SQL.Contains("[expireDate] <= '2013-11-21 13:25:55'")); - } + [Test] public void Can_Build_StartsWith_Query_For_IContent() @@ -43,11 +29,15 @@ namespace Umbraco.Tests.Persistence.Querying var result = translator.Translate(); var strResult = result.SQL; - string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) like '-1%')"; + string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) LIKE upper(@0))"; // Assert Assert.That(strResult, Is.Not.Empty); Assert.That(strResult, Is.EqualTo(expectedResult)); + + Assert.AreEqual(1, result.Arguments.Length); + Assert.AreEqual("-1%", sql.Arguments[0]); + Console.WriteLine(strResult); } @@ -66,11 +56,15 @@ namespace Umbraco.Tests.Persistence.Querying var result = translator.Translate(); var strResult = result.SQL; - string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = -1)"; + string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = @0)"; // Assert Assert.That(strResult, Is.Not.Empty); Assert.That(strResult, Is.EqualTo(expectedResult)); + + Assert.AreEqual(1, result.Arguments.Length); + Assert.AreEqual(-1, sql.Arguments[0]); + Console.WriteLine(strResult); } @@ -89,11 +83,14 @@ namespace Umbraco.Tests.Persistence.Querying var result = translator.Translate(); var strResult = result.SQL; - string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = 'umbTextpage')"; + string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = @0)"; // Assert Assert.That(strResult, Is.Not.Empty); Assert.That(strResult, Is.EqualTo(expectedResult)); + Assert.AreEqual(1, result.Arguments.Length); + Assert.AreEqual("umbTextpage", sql.Arguments[0]); + Console.WriteLine(strResult); } diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index 4862333b07..1c6eaca2bb 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -38,7 +38,11 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider FROM [cmsContentXml] INNER JOIN [umbracoNode] ON [cmsContentXml].[nodeId] = [umbracoNode].[id] -WHERE ([umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c')) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sql.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); +WHERE ([umbracoNode].[nodeObjectType] = @0)) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), + sql.SQL.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); + + Assert.AreEqual(1, sql.Arguments.Length); + Assert.AreEqual(mediaObjectType, sql.Arguments[0]); } [NUnit.Framework.Ignore("This doesn't actually test anything")]