From 03ba8c9e14ab97f6d0c612f40cf33c64b3616bc3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 25 Aug 2014 13:46:21 +0200 Subject: [PATCH 01/17] Update to 6.2.2, removes airinstallbadge --- build/NuSpecs/build/UmbracoCms.targets | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 +- .../dashboard/DesktopMediaUploader.ascx | 39 ++++-------------- .../dashboard/swfs/AIRInstallBadge.swf | Bin 68008 -> 0 bytes src/Umbraco.Web/Umbraco.Web.csproj | 4 +- .../dashboard/DesktopMediaUploader.ascx | 39 ++++-------------- 6 files changed, 19 insertions(+), 68 deletions(-) delete mode 100644 src/Umbraco.Web.UI/umbraco/dashboard/swfs/AIRInstallBadge.swf diff --git a/build/NuSpecs/build/UmbracoCms.targets b/build/NuSpecs/build/UmbracoCms.targets index 8958216d78..894e29409d 100644 --- a/build/NuSpecs/build/UmbracoCms.targets +++ b/build/NuSpecs/build/UmbracoCms.targets @@ -1,7 +1,7 @@  - 6.2.1 + 6.2.2 diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c625eb0d98..904eb17624 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1611,7 +1611,6 @@ - @@ -2676,7 +2675,7 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 6210 + 6220 / http://localhost:6220 False diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/DesktopMediaUploader.ascx b/src/Umbraco.Web.UI/umbraco/dashboard/DesktopMediaUploader.ascx index 3869aa8547..8dc49e6760 100644 --- a/src/Umbraco.Web.UI/umbraco/dashboard/DesktopMediaUploader.ascx +++ b/src/Umbraco.Web.UI/umbraco/dashboard/DesktopMediaUploader.ascx @@ -1,50 +1,25 @@ <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DesktopMediaUploader.ascx.cs" Inherits="umbraco.presentation.umbraco.dashboard.DesktopMediaUploader" %> -<%@ Register Assembly="controls" Namespace="umbraco.uicontrols" TagPrefix="umb" %> <%@ Register Assembly="ClientDependency.Core" Namespace="ClientDependency.Core.Controls" TagPrefix="umb" %> -

Desktop Media Uploader

Umbraco

Desktop Media Uploader is a small desktop application that you can install on your computer which allows you to easily upload media items directly to the media section.

-

The badge below will auto configure itself based upon whether you already have Desktop Media Uploader installed or not.

-

Just click the Install Now / Upgrade Now / Launch Now link to perform that action.

-

-

- Download Desktop Media Uploader now.

This application requires Adobe® AIR™ to be installed for Mac OS or Windows. -
-

- +
+

+ This application requires Adobe® AIR™ to be installed for Mac OS or Windows.
+ After Air is installed you can install the Desktop Media Uploader by clicking here. +

+
-
\ No newline at end of file + diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/swfs/AIRInstallBadge.swf b/src/Umbraco.Web.UI/umbraco/dashboard/swfs/AIRInstallBadge.swf deleted file mode 100644 index 1200ffd0ce8fab4f7ede235a88ead8ce6d83e822..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68008 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

RbnDgLB~efo5haur%9O*df3R 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%0DY~E#-Y}}IC#~D@Ucc1K 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 DivdQd diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 97a48d0f92..b0e93f5afa 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1795,7 +1795,9 @@ - + + ASPXCodeBehind + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/DesktopMediaUploader.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/DesktopMediaUploader.ascx index 3869aa8547..8dc49e6760 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/DesktopMediaUploader.ascx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dashboard/DesktopMediaUploader.ascx @@ -1,50 +1,25 @@ <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DesktopMediaUploader.ascx.cs" Inherits="umbraco.presentation.umbraco.dashboard.DesktopMediaUploader" %> -<%@ Register Assembly="controls" Namespace="umbraco.uicontrols" TagPrefix="umb" %> <%@ Register Assembly="ClientDependency.Core" Namespace="ClientDependency.Core.Controls" TagPrefix="umb" %> -

Desktop Media Uploader

Umbraco

Desktop Media Uploader is a small desktop application that you can install on your computer which allows you to easily upload media items directly to the media section.

-

The badge below will auto configure itself based upon whether you already have Desktop Media Uploader installed or not.

-

Just click the Install Now / Upgrade Now / Launch Now link to perform that action.

-

-

- Download Desktop Media Uploader now.

This application requires Adobe® AIR™ to be installed for Mac OS or Windows. -
-

- +
+

+ This application requires Adobe® AIR™ to be installed for Mac OS or Windows.
+ After Air is installed you can install the Desktop Media Uploader by clicking here. +

+
-
\ No newline at end of file + From 33ccf490abf3698caa6d07e3b2f25c846ac3c40a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 25 Aug 2014 15:53:45 +0200 Subject: [PATCH 02/17] Update nuspec to ask for a valid nuget pkg --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index daa170dfbc..5e5443ffd2 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -17,7 +17,7 @@ - + From ba52f9235e01e5d369864c8e438a25753d8fe903 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 17:11:58 +1000 Subject: [PATCH 03/17] Backports fixes to SQL parameterization and PetaPoco mem updates --- src/Umbraco.Core/HashCodeCombiner.cs | 6 + src/Umbraco.Core/Persistence/PetaPoco.cs | 451 +++++++----- .../Persistence/PetaPocoSqlExtensions.cs | 2 +- .../Querying/BaseExpressionHelper.cs | 651 ++++++++++++++++-- .../Persistence/Querying/IQuery.cs | 16 + .../Querying/ModelToSqlExpressionHelper.cs | 472 +------------ .../Querying/PocoToSqlExpressionHelper.cs | 481 +------------ .../Persistence/Querying/Query.cs | 44 +- .../Persistence/Querying/SqlTranslator.cs | 8 +- .../Repositories/EntityRepository.cs | 300 +++++--- .../Repositories/VersionableRepositoryBase.cs | 2 + .../SqlSyntax/ISqlSyntaxProvider.cs | 9 + .../MicrosoftSqlSyntaxProviderBase.cs | 120 ++++ .../SqlSyntax/SqlCeSyntaxProvider.cs | 78 +-- .../SqlSyntax/SqlServerSyntaxProvider.cs | 95 +-- .../SqlSyntax/SqlSyntaxProviderBase.cs | 30 +- .../SqlSyntax/SqlSyntaxProviderExtensions.cs | 10 +- src/Umbraco.Core/Services/ContentService.cs | 4 + src/Umbraco.Core/Services/MediaService.cs | 6 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Persistence/PetaPocoDynamicQueryTests.cs | 133 ++++ .../Persistence/PetaPocoExtensionsTest.cs | 181 +++++ .../ContentRepositorySqlClausesTest.cs | 38 +- .../ContentTypeRepositorySqlClausesTest.cs | 40 +- ...aTypeDefinitionRepositorySqlClausesTest.cs | 8 +- .../Persistence/Querying/ExpressionTests.cs | 19 +- .../Querying/MediaRepositorySqlClausesTest.cs | 8 +- .../MediaTypeRepositorySqlClausesTest.cs | 8 +- .../Persistence/Querying/PetaPocoSqlTests.cs | 136 +++- .../Persistence/Querying/QueryBuilderTests.cs | 33 +- .../SqlCeSyntaxProviderTests.cs | 6 +- 31 files changed, 1909 insertions(+), 1487 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs create mode 100644 src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs 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")] From b9e4950e36c5c52485e527ad390df27734a0fabb Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 17:30:09 +1000 Subject: [PATCH 04/17] fixes up entity repo merges to support old db schema --- src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index a96d8b3bbf..f2b899ac7b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -301,7 +301,7 @@ namespace Umbraco.Core.Persistence.Repositories //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") + .Select("contentNodeId, versionId, dataNvarchar, dataNtext, controlId, alias as propertyTypeAlias") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) From 4ee4f296fc489b8f68f4bcd82a88217d741584e9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 17:31:24 +1000 Subject: [PATCH 05/17] updates version --- build/Build.bat | 2 +- build/NuSpecs/UmbracoCms.Core.nuspec | 2 +- build/NuSpecs/UmbracoCms.nuspec | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index 48d3439a47..669c9cb555 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET release=6.2.2 +SET release=6.2.3 SET comment= SET version=%release% diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 5e5443ffd2..13986641bf 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -2,7 +2,7 @@ UmbracoCms.Core - 6.2.2 + 6.2.3 Umbraco Cms Core Binaries Umbraco HQ Umbraco HQ diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 8fddfada58..e681c7f4ff 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -2,7 +2,7 @@ UmbracoCms - 6.2.2 + 6.2.3 Umbraco Cms Umbraco HQ Umbraco HQ diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 50ee876c64..1ad70b5b8d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("6.2.2"); + private static readonly Version Version = new Version("6.2.3"); /// /// Gets the current version of Umbraco. From 6f7ec84f186767d14af51c2e72b451d5468a9618 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 14:06:40 +1000 Subject: [PATCH 06/17] Backports fixes to SQL parameterization and PetaPoco mem updates --- src/Umbraco.Core/HashCodeCombiner.cs | 6 + src/Umbraco.Core/Persistence/PetaPoco.cs | 451 +++++++----- .../Persistence/PetaPocoSqlExtensions.cs | 4 +- .../Querying/BaseExpressionHelper.cs | 651 ++++++++++++++++-- .../Persistence/Querying/IQuery.cs | 16 + .../Querying/ModelToSqlExpressionHelper.cs | 472 +------------ .../Querying/PocoToSqlExpressionHelper.cs | 481 +------------ .../Persistence/Querying/Query.cs | 44 +- .../Persistence/Querying/SqlTranslator.cs | 8 +- .../Repositories/EntityRepository.cs | 38 +- .../Repositories/TagsRepository.cs | 2 + .../Repositories/VersionableRepositoryBase.cs | 2 + .../SqlSyntax/ISqlSyntaxProvider.cs | 9 + .../MicrosoftSqlSyntaxProviderBase.cs | 120 ++++ .../SqlSyntax/SqlCeSyntaxProvider.cs | 78 +-- .../SqlSyntax/SqlServerSyntaxProvider.cs | 95 +-- .../SqlSyntax/SqlSyntaxProviderBase.cs | 30 +- .../SqlSyntax/SqlSyntaxProviderExtensions.cs | 10 +- src/Umbraco.Core/Services/ContentService.cs | 4 + src/Umbraco.Core/Services/MediaService.cs | 4 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Persistence/PetaPocoDynamicQueryTests.cs | 133 ++++ .../Persistence/PetaPocoExtensionsTest.cs | 187 +++++ .../ContentRepositorySqlClausesTest.cs | 38 +- .../ContentTypeRepositorySqlClausesTest.cs | 40 +- ...aTypeDefinitionRepositorySqlClausesTest.cs | 8 +- .../Persistence/Querying/ExpressionTests.cs | 19 +- .../Querying/MediaRepositorySqlClausesTest.cs | 8 +- .../MediaTypeRepositorySqlClausesTest.cs | 8 +- .../Persistence/Querying/PetaPocoSqlTests.cs | 136 +++- .../Persistence/Querying/QueryBuilderTests.cs | 33 +- .../DataTypeDefinitionRepositoryTest.cs | 2 +- .../SqlCeSyntaxProviderTests.cs | 6 +- .../Models/Mapping/MediaModelMapper.cs | 1 + .../Models/Mapping/MemberModelMapper.cs | 4 + 35 files changed, 1742 insertions(+), 1407 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/SqlSyntax/MicrosoftSqlSyntaxProviderBase.cs create mode 100644 src/Umbraco.Tests/Persistence/PetaPocoDynamicQueryTests.cs 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 c606c24a59..708097dea9 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -26,7 +26,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) @@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence public static Sql OrderByDescending(this Sql sql, params object[] columns) { - return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString() + " DESC").ToArray()))); + return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 1a087ad8d7..f3cbe5a583 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 d0b81f1b8e..530c679f7a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -178,8 +178,18 @@ namespace Umbraco.Core.Persistence.Repositories 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)); @@ -193,12 +203,21 @@ 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 entitySql = translator.Translate(); @@ -207,7 +226,14 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), wheres); + 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 diff --git a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs index 1d687d1974..dd2e05e7a3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs @@ -160,6 +160,8 @@ namespace Umbraco.Core.Persistence.Repositories //TODO: Consider caching implications. + //TODO: We need to add lookups for parentId or path! (i.e. get content in tag group that are descendants of x) + public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup) { diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 8729c7d8f2..45cd292993 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -230,6 +230,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("cmsPropertyData.*, cmsDataTypePreValues.id as preValId, cmsDataTypePreValues.value, cmsDataTypePreValues.sortorder, cmsDataTypePreValues.alias, cmsDataTypePreValues.datatypeNodeId") 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 1fa235720f..af18d9c3d9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -22,12 +22,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); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index be81292bb0..a63bfed35c 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -520,6 +520,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 518be0ef07..fce1f00261 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); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c317594937..796d232903 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -394,6 +394,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 154b0c147f..9d17fc17f6 100644 --- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs +++ b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs @@ -1,14 +1,201 @@ 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; + + ServiceContext.TagService.GetTagsForEntity(id1); + + ServiceContext.TagService.GetAllContentTags(); + + ServiceContext.TagService.GetTagsForEntity(id2); + + ServiceContext.TagService.GetTagsForEntity(id3); + + 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("test", DataTypeDatabaseType.Ntext) + { + Alias = "tags", + DataTypeDefinitionId = 1041 + }); + contentTypeService.Save(contentType); + var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); + content1.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); + contentService.Publish(content1); + id2 = content1.Id; + + var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); + content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); + 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/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index e59945ac91..fbf7f53414 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dataTypeDefinitions, Is.Not.Null); Assert.That(dataTypeDefinitions.Any(), Is.True); Assert.That(dataTypeDefinitions.Any(x => x == null), Is.False); - Assert.That(dataTypeDefinitions.Count(), Is.EqualTo(21)); + Assert.That(dataTypeDefinitions.Count(), Is.EqualTo(25)); } } 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")] diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 3fc9c1de39..d425c74024 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -47,6 +47,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Published, expression => expression.Ignore()) .ForMember(display => display.Updator, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) + .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.Tabs, expression => expression.ResolveUsing()) .AfterMap(AfterMap); diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index d5f5f8a64e..8369acebf1 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -81,6 +81,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Published, expression => expression.Ignore()) .ForMember(display => display.Updator, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) + .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) + .ForMember(display => display.IsContainer, expression => expression.Ignore()) + .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, member, display)); //FROM IMember TO ContentItemBasic @@ -98,6 +101,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Updator, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()); + .ForMember(x => x.ContentTypeAlias, expression => expression.Ignore()); //FROM IMember TO ContentItemDto config.CreateMap>() .ForMember( From 88ec514630b2a3737c50abf58dab49e2a1516be2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 14:38:03 +1000 Subject: [PATCH 07/17] fixes entity repo merges, this also means that the n+1 fixes for entity repository will be in this release. --- .../Repositories/EntityRepository.cs | 107 +++++++++++------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 530c679f7a..838a6bbe23 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -141,41 +141,56 @@ namespace Umbraco.Core.Persistence.Repositories { 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 = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, string.Empty); - - 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) { //TODO: We need to fix all of this and how it handles parameters! @@ -272,16 +287,16 @@ namespace Umbraco.Core.Persistence.Repositories return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); } - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, string additionalWhereClause) + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) { - var entitySql = GetBaseWhere(GetBase, isContent, isMedia, additionalWhereClause, objectTypeId); + 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))); + return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); } - private Sql GetFullSqlForMedia(Sql entitySql, string additionWhereStatement = "") + 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 @@ -294,7 +309,12 @@ namespace Umbraco.Core.Persistence.Repositories .On(dto => dto.Id, dto => dto.PropertyTypeId) .InnerJoin() .On(dto => dto.DataTypeId, dto => dto.DataTypeId) - .Where("umbracoNode.nodeObjectType = @nodeObjectType" + additionWhereStatement, new {nodeObjectType = Constants.ObjectTypes.Media}); + .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 @@ -310,7 +330,7 @@ namespace Umbraco.Core.Persistence.Repositories return wrappedSql; } - protected virtual Sql GetBase(bool isContent, bool isMedia, string additionWhereStatement = "") + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { var columns = new List { @@ -358,43 +378,48 @@ namespace Umbraco.Core.Persistence.Repositories .On("umbracoNode.id = latest.nodeId"); } + if (customFilter != null) + { + customFilter(entitySql); + } + 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}); 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; From b975a02bfa7bd580ded41d931072e59d11496a1d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 16:27:02 +1000 Subject: [PATCH 08/17] Updates version to 7.1.7 --- build/UmbracoVersion.txt | 2 +- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- .../Repositories/DataTypeDefinitionRepositoryTest.cs | 2 +- src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs | 1 - src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs | 5 ----- src/umbraco.sln | 4 +++- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index c32f54ca04..c92484044e 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1 +1 @@ -7.1.6 \ No newline at end of file +7.1.7 \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 847be99a0d..1b05407755 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.1.6"); + private static readonly Version Version = new Version("7.1.7"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index fbf7f53414..e59945ac91 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dataTypeDefinitions, Is.Not.Null); Assert.That(dataTypeDefinitions.Any(), Is.True); Assert.That(dataTypeDefinitions.Any(x => x == null), Is.False); - Assert.That(dataTypeDefinitions.Count(), Is.EqualTo(25)); + Assert.That(dataTypeDefinitions.Count(), Is.EqualTo(21)); } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index d425c74024..3fc9c1de39 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -47,7 +47,6 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Published, expression => expression.Ignore()) .ForMember(display => display.Updator, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) - .ForMember(display => display.IsContainer, expression => expression.Ignore()) .ForMember(display => display.Tabs, expression => expression.ResolveUsing()) .AfterMap(AfterMap); diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index 8369acebf1..1d8ee7abc4 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -81,9 +81,6 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Published, expression => expression.Ignore()) .ForMember(display => display.Updator, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) - .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) - .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, member, display)); //FROM IMember TO ContentItemBasic @@ -100,8 +97,6 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Published, expression => expression.Ignore()) .ForMember(x => x.Updator, expression => expression.Ignore()) .ForMember(x => x.Alias, expression => expression.Ignore()); - - .ForMember(x => x.ContentTypeAlias, expression => expression.Ignore()); //FROM IMember TO ContentItemDto config.CreateMap>() .ForMember( diff --git a/src/umbraco.sln b/src/umbraco.sln index 61add3f5d5..7b71ba11d2 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30501.0 +VisualStudioVersion = 12.0.30723.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}" EndProject @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 ..\build\RevertToCleanInstall.bat = ..\build\RevertToCleanInstall.bat ..\build\RevertToEmptyInstall.bat = ..\build\RevertToEmptyInstall.bat umbraco.presentation.targets = umbraco.presentation.targets + ..\build\UmbracoVersion.txt = ..\build\UmbracoVersion.txt + ..\build\webpihash.txt = ..\build\webpihash.txt EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" From 1e04735826b72ad5a48b3d949724eadc1a25917e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Sep 2014 16:28:07 +1000 Subject: [PATCH 09/17] updates iis port --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index c82a4a0a2d..964e8f10b6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2530,9 +2530,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7160 + 7170 / - http://localhost:7160 + http://localhost:7170 False False From 71242430da46f0115ceb2b07cf35d563ee1b560d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 1 Oct 2014 16:37:42 +1000 Subject: [PATCH 10/17] Fixes a query that would cause the new petapoco changes to have issues since it was asking for the wrong type --- src/UmbracoExamine/DataServices/UmbracoContentService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 93029091cc..76789b19ee 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -123,8 +123,8 @@ namespace UmbracoExamine.DataServices { try { - var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); - return result.Select(r => r.Alias).ToList(); + var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias"); + return result; } catch (Exception ex) { From 340328aeba22288f455271e2bce582dc50e09138 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 2 Oct 2014 11:44:41 +0200 Subject: [PATCH 11/17] Fixes merge issue --- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2421d240d1..83f500a112 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1853,7 +1853,7 @@ - + ASPXCodeBehind From 414dbe45a69cc4def3daaa916be03e658f88767b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Sep 2014 16:50:05 +1000 Subject: [PATCH 12/17] Fixes: U4-5460 User with start content set won't see the selected node. --- .../Trees/ContentTreeController.cs | 19 +--- .../Trees/ContentTreeControllerBase.cs | 98 ++++++++++++------- src/Umbraco.Web/Trees/MediaTreeController.cs | 8 -- 3 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 0c0a4e7a27..2aea1c97a2 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -64,24 +64,7 @@ namespace Umbraco.Web.Trees { get { return Security.CurrentUser.StartContentId; } } - - /// - /// Gets the tree nodes for the given id - /// - /// - /// - /// - /// - /// If the content item is a container node then we will not return anything - /// - protected override TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - var entities = GetChildEntities(id); - nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null)); - return nodes; - } - + /// /// Creates a tree node for a content item based on an UmbracoEntity /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2eff025407..3708c6fd4f 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence; @@ -69,7 +70,51 @@ namespace Umbraco.Web.Trees /// protected abstract int UserStartNode { get; } - protected abstract TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings); + /// + /// Gets the tree nodes for the given id + /// + /// + /// + /// + protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + var altStartId = string.Empty; + if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId)) + altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); + + //check if a request has been made to render from a specific start node + if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture)) + { + id = altStartId; + + //we need to verify that the user has access to view this node, otherwise we'll render an empty tree collection + // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access + if (HasPathAccess(id, queryStrings) == false) + { + LogHelper.Warn("The user " + Security.CurrentUser.Username + " does not have access to the tree node " + id); + return new TreeNodeCollection(); + } + + // So there's an alt id specified, it's not the root node and the user has access to it, great! But there's one thing we + // need to consider: + // If the tree is being rendered in a dialog view we want to render only the children of the specified id, but + // when the tree is being rendered normally in a section and the current user's start node is not -1, then + // we want to include their start node in the tree as well. + // Therefore, in the latter case, we want to change the id to -1 since we want to render the current user's root node + // and the GetChildEntities method will take care of rendering the correct root node. + // If it is in dialog mode, then we don't need to change anything and the children will just render as per normal. + if (IsDialog(queryStrings) == false && UserStartNode != Constants.System.Root) + { + id = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + } + } + + var entities = GetChildEntities(id); + nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null)); + return nodes; + } protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings); @@ -108,59 +153,44 @@ namespace Umbraco.Web.Trees protected abstract bool HasPathAccess(string id, FormDataCollection queryStrings); /// - /// This will automatically check if the recycle bin needs to be rendered (i.e. its the first level) - /// and will automatically append it to the result of GetChildNodes. + /// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode) /// /// /// /// + /// + /// This method is overwritten strictly to render the recycle bin, it should serve no other purpose + /// protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { //check if we're rendering the root - if (id == Constants.System.Root.ToInvariantString()) + if (id == Constants.System.Root.ToInvariantString() && UserStartNode == Constants.System.Root) { - //when rendering the root, 3 things can happen: - //1. we return -1 children without modifications - //2. the user has a non -1 content root set and we return that - //3. the tree has a non -1 content root set and we return that - if the user has access to it. + var altStartId = string.Empty; - var hasUserRoot = UserStartNode != Constants.System.Root; - var hasTreeRoot = queryStrings.HasKey(TreeQueryStringParameters.StartNodeId); + if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId)) + altStartId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); - //initial id - var idToLoad = id; - - //user permission override root - if (hasUserRoot) - idToLoad = UserStartNode.ToString(CultureInfo.InvariantCulture); - - //tree overrides root - if (hasTreeRoot) + //check if a request has been made to render from a specific start node + if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture)) { - //but only if the user is allowed to access this node - var altId = queryStrings.GetValue(TreeQueryStringParameters.StartNodeId); - - //so if we dont have a user content root or the user has access - if (hasUserRoot == false || HasPathAccess(altId, queryStrings)) - { - idToLoad = altId; - } + id = altStartId; } - - //load whatever root nodes we concluded was the user/tree root - var nodes = GetTreeNodesInternal(idToLoad, queryStrings); - - //only render the recycle bin if we are not in dialog and the start id is still the root - if (IsDialog(queryStrings) == false && idToLoad == Constants.System.Root.ToInvariantString()) + + var nodes = GetTreeNodesInternal(id, queryStrings); + + //only render the recycle bin if we are not in dialog and the start id id still the root + if (IsDialog(queryStrings) == false && id == Constants.System.Root.ToInvariantString()) { nodes.Add(CreateTreeNode( RecycleBinId.ToInvariantString(), - idToLoad, + id, queryStrings, ui.GetText("general", "recycleBin"), "icon-trash", RecycleBinSmells, queryStrings.GetValue("application") + TreeAlias.EnsureStartsWith('/') + "/recyclebin")); + } return nodes; diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 7589aa1cc4..737e4bffdd 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -58,14 +58,6 @@ namespace Umbraco.Web.Trees get { return Security.CurrentUser.StartMediaId; } } - protected override TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings) - { - var nodes = new TreeNodeCollection(); - var entities = GetChildEntities(id); - nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null)); - return nodes; - } - /// /// Creates a tree node for a content item based on an UmbracoEntity /// From a13ead0d10821f05187b2637f0c2d34975f3f746 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Oct 2014 00:06:09 +1000 Subject: [PATCH 13/17] unescapes @ symbols now that sql params are in place --- .../Repositories/MemberGroupRepository.cs | 7 +++++-- .../Persistence/Repositories/MemberRepository.cs | 4 ++-- .../Persistence/Repositories/UserRepository.cs | 4 ++-- src/Umbraco.Tests/Services/MemberServiceTests.cs | 12 ++++++++++-- src/Umbraco.Tests/Services/UserServiceTests.cs | 3 ++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 0d6fc90016..041d0ee622 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -203,13 +203,13 @@ namespace Umbraco.Core.Persistence.Repositories //find the member by username var memberSql = new Sql(); var memberObjectType = new Guid(Constants.ObjectTypes.Member); - var escapedUsername = PetaPocoExtensions.EscapeAtSymbols(username); + memberSql.Select("umbracoNode.id") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) .Where(x => x.NodeObjectType == memberObjectType) - .Where(x => x.LoginName == escapedUsername); + .Where(x => x.LoginName == username); var memberIdUsername = Database.Fetch(memberSql).FirstOrDefault(); if (memberIdUsername.HasValue == false) { @@ -280,6 +280,9 @@ namespace Umbraco.Core.Persistence.Repositories public void AssignRolesInternal(int[] memberIds, string[] roleNames) { + //ensure they're unique + memberIds = memberIds.Distinct().ToArray(); + //create the missing roles first var existingSql = new Sql() diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index f84cb9c1b6..4085432c7e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -529,10 +529,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); - var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username); + sql.Select("COUNT(*)") .From() - .Where(x => x.LoginName == escapedUserName); + .Where(x => x.LoginName == username); return Database.ExecuteScalar(sql) > 0; } diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 67775cca54..ca7b1339e0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -261,10 +261,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); - var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username); + sql.Select("COUNT(*)") .From() - .Where(x => x.UserName == escapedUserName); + .Where(x => x.UserName == username); return Database.ExecuteScalar(sql) > 0; } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 1ff9947a6f..7ee8d56cb5 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -94,15 +94,20 @@ namespace Umbraco.Tests.Services ServiceContext.MemberTypeService.Save(memberType); IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); ServiceContext.MemberService.Save(member); + //need to test with '@' symbol in the lookup + IMember member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2@test.com"); + ServiceContext.MemberService.Save(member2); ServiceContext.MemberService.AddRole("MyTestRole1"); ServiceContext.MemberService.AddRole("MyTestRole2"); ServiceContext.MemberService.AddRole("MyTestRole3"); - ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" }); + ServiceContext.MemberService.AssignRoles(new[] { member.Id, member2.Id }, new[] { "MyTestRole1", "MyTestRole2" }); var memberRoles = ServiceContext.MemberService.GetAllRoles("test"); - Assert.AreEqual(2, memberRoles.Count()); + + var memberRoles2 = ServiceContext.MemberService.GetAllRoles("test2@test.com"); + Assert.AreEqual(2, memberRoles2.Count()); } [Test] @@ -325,9 +330,12 @@ namespace Umbraco.Tests.Services ServiceContext.MemberTypeService.Save(memberType); IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); ServiceContext.MemberService.Save(member); + IMember member2 = MockedMember.CreateSimpleMember(memberType, "test", "test2@test.com", "pass", "test2@test.com"); + ServiceContext.MemberService.Save(member2); Assert.IsTrue(ServiceContext.MemberService.Exists("test")); Assert.IsFalse(ServiceContext.MemberService.Exists("notFound")); + Assert.IsTrue(ServiceContext.MemberService.Exists("test2@test.com")); } [Test] diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index f8c9429e80..2a099c9001 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -130,9 +130,10 @@ namespace Umbraco.Tests.Services var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io", userType); - + var user2 = ServiceContext.UserService.CreateUserWithIdentity("john2@umbraco.io", "john2@umbraco.io", userType); Assert.IsTrue(ServiceContext.UserService.Exists("JohnDoe")); Assert.IsFalse(ServiceContext.UserService.Exists("notFound")); + Assert.IsTrue(ServiceContext.UserService.Exists("john2@umbraco.io")); } [Test] From 737f2fb7071828c5744c7c7af413505f26a0a62f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Oct 2014 00:07:40 +1000 Subject: [PATCH 14/17] Fixes: U4-5566 Quite a few hard coded queries with incorrect escape syntax for mysql --- .../Repositories/TagsRepository.cs | 8 ++-- .../TagsAutoCompleteHandler.ashx.cs | 3 +- src/umbraco.cms/businesslogic/Tags/Tag.cs | 39 ++++++++++--------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs index dd2e05e7a3..543ac9b8e5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs @@ -287,7 +287,7 @@ namespace Umbraco.Core.Persistence.Repositories if (withGrouping) { - sql = sql.Select("cmsTags.Id, cmsTags.Tag, cmsTags.[Group], Count(*) NodeCount"); + sql = sql.Select("cmsTags.Id, cmsTags.Tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @", Count(*) NodeCount"); } else { @@ -317,7 +317,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql ApplyGroupByToTagsQuery(Sql sql) { - return sql.GroupBy(new string[] { "cmsTags.Id", "cmsTags.Tag", "cmsTags.[Group]" }); + return sql.GroupBy(new string[] { "cmsTags.Id", "cmsTags.Tag", "cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @"" }); } private IEnumerable ExecuteTagsQuery(Sql sql) @@ -440,7 +440,7 @@ namespace Umbraco.Core.Persistence.Repositories " AND tagId IN ", "(SELECT id FROM cmsTags INNER JOIN ", tagSetSql, - " ON (TagSet.Tag = cmsTags.Tag and TagSet.[Group] = cmsTags.[Group]))"); + " ON (TagSet.Tag = cmsTags.Tag and TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @" = cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @"))"); Database.Execute(deleteSql); } @@ -488,7 +488,7 @@ namespace Umbraco.Core.Persistence.Repositories { var array = tagsToInsert .Select(tag => - string.Format("select '{0}' as Tag, '{1}' as [Group]", + string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @"", PetaPocoExtensions.EscapeAtSymbols(tag.Text.Replace("'", "''")), tag.Group)) .ToArray(); return "(" + string.Join(" union ", array).Replace(" ", " ") + ") as TagSet"; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/TagsAutoCompleteHandler.ashx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/TagsAutoCompleteHandler.ashx.cs index bf89d5bc84..3ba7a1a6b8 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/TagsAutoCompleteHandler.ashx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/TagsAutoCompleteHandler.ashx.cs @@ -5,6 +5,7 @@ using System.Web; using System.Web.Script.Serialization; using System.Web.Services; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; using umbraco.DataLayer; using umbraco.BusinessLogic; using umbraco.presentation.webservices; @@ -45,7 +46,7 @@ namespace umbraco.presentation.umbraco.webservices if (!String.IsNullOrEmpty(group) && !String.IsNullOrEmpty(id)) { sql = @"SELECT TOP (20) tag FROM cmsTags WHERE tag LIKE @prefix AND cmsTags.id not in - (SELECT tagID FROM cmsTagRelationShip WHERE NodeId = @nodeId) AND cmstags.[group] = @group;"; + (SELECT tagID FROM cmsTagRelationShip WHERE NodeId = @nodeId) AND cmstags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + " = @group;"; rr = SqlHelper.ExecuteReader(sql, SqlHelper.CreateParameter("@count", count), diff --git a/src/umbraco.cms/businesslogic/Tags/Tag.cs b/src/umbraco.cms/businesslogic/Tags/Tag.cs index e2cef52308..5917c0629c 100644 --- a/src/umbraco.cms/businesslogic/Tags/Tag.cs +++ b/src/umbraco.cms/businesslogic/Tags/Tag.cs @@ -5,6 +5,7 @@ using System.Text; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.SqlSyntax; using umbraco.DataLayer; using umbraco.BusinessLogic; using umbraco.interfaces; @@ -157,7 +158,7 @@ namespace umbraco.cms.businesslogic.Tags sql += " ("; sql += " select NewTags.Id from "; sql += " " + TagSet + " "; - sql += " inner join cmsTags as NewTags on (TagSet.Tag = NewTags.Tag and TagSet.[Group] = TagSet.[Group]) "; + sql += " inner join cmsTags as NewTags on (TagSet.Tag = NewTags.Tag and TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + " = TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + ") "; sql += " ) as NewTagsSet "; sql += " on (cmsTagRelationship.TagId = NewTagsSet.Id and cmsTagRelationship.NodeId = " + string.Format("{0}", nodeId) + ") "; sql += " inner join cmsTags as OldTags on (cmsTagRelationship.tagId = OldTags.Id) "; @@ -166,10 +167,10 @@ namespace umbraco.cms.businesslogic.Tags SqlHelper.ExecuteNonQuery(sql); //adds any tags found in csv that aren't in cmsTag for that group - sql = "insert into cmsTags (Tag,[Group]) "; - sql += " select TagSet.[Tag], TagSet.[Group] from "; + sql = "insert into cmsTags (Tag," + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @") "; + sql += " select TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Tag") + @", TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @" from "; sql += " " + TagSet + " "; - sql += " left outer join cmsTags on (TagSet.Tag = cmsTags.Tag and TagSet.[Group] = cmsTags.[Group])"; + sql += " left outer join cmsTags on (TagSet.Tag = cmsTags.Tag and TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + " = cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + ")"; sql += " where cmsTags.Id is null "; SqlHelper.ExecuteNonQuery(sql); @@ -179,7 +180,7 @@ namespace umbraco.cms.businesslogic.Tags sql += "( "; sql += "select NewTags.Id from "; sql += " " + TagSet + " "; - sql += "inner join cmsTags as NewTags on (TagSet.Tag = NewTags.Tag and TagSet.[Group] = TagSet.[Group]) "; + sql += "inner join cmsTags as NewTags on (TagSet.Tag = NewTags.Tag and TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + " = TagSet." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + ") "; sql += ") as NewTagsSet "; sql += "left outer join cmsTagRelationship "; sql += "on (cmsTagRelationship.TagId = NewTagsSet.Id and cmsTagRelationship.NodeId = " + string.Format("{0}", nodeId) + ") "; @@ -217,7 +218,7 @@ namespace umbraco.cms.businesslogic.Tags /// public static void RemoveTagsFromNode(int nodeId, string group) { - SqlHelper.ExecuteNonQuery("DELETE FROM cmsTagRelationship WHERE (nodeId = @nodeId) AND EXISTS (SELECT id FROM cmsTags WHERE (cmsTagRelationship.tagId = id) AND ([group] = @group));", + SqlHelper.ExecuteNonQuery("DELETE FROM cmsTagRelationship WHERE (nodeId = @nodeId) AND EXISTS (SELECT id FROM cmsTags WHERE (cmsTagRelationship.tagId = id) AND (" + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + " = @group));", SqlHelper.CreateParameter("@nodeId", nodeId), SqlHelper.CreateParameter("@group", group)); } @@ -241,7 +242,7 @@ namespace umbraco.cms.businesslogic.Tags public static int AddTag(string tag, string group) { - SqlHelper.ExecuteNonQuery("INSERT INTO cmsTags(tag,[group]) VALUES (@tag,@group)", + SqlHelper.ExecuteNonQuery("INSERT INTO cmsTags(tag," + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + ") VALUES (@tag,@group)", SqlHelper.CreateParameter("@tag", tag.Trim()), SqlHelper.CreateParameter("@group", group)); return GetTagId(tag, group); @@ -250,7 +251,7 @@ namespace umbraco.cms.businesslogic.Tags public static int GetTagId(string tag, string group) { int retval = 0; - string sql = "SELECT id FROM cmsTags where tag=@tag AND [group]=@group;"; + string sql = "SELECT id FROM cmsTags where tag=@tag AND " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + "=@group;"; object result = SqlHelper.ExecuteScalar(sql, SqlHelper.CreateParameter("@tag", tag), SqlHelper.CreateParameter("@group", group)); @@ -263,10 +264,10 @@ namespace umbraco.cms.businesslogic.Tags public static IEnumerable GetTags(int nodeId, string group) { - var sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags.[group], count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags + var sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @", count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags INNER JOIN cmsTagRelationship ON cmsTagRelationShip.tagId = cmsTags.id - WHERE cmsTags.[group] = @group AND cmsTagRelationship.nodeid = @nodeid - GROUP BY cmsTags.id, cmsTags.tag, cmsTags.[group]"; + WHERE cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @" = @group AND cmsTagRelationship.nodeid = @nodeid + GROUP BY cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group"); return ConvertSqlToTags(sql, SqlHelper.CreateParameter("@group", group), @@ -282,10 +283,10 @@ namespace umbraco.cms.businesslogic.Tags public static IEnumerable GetTags(int nodeId) { - string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags.[group], count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags + string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @", count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags INNER JOIN cmsTagRelationShip ON cmsTagRelationShip.tagid = cmsTags.id WHERE cmsTagRelationShip.nodeid = @nodeId - GROUP BY cmsTags.id, cmsTags.tag, cmsTags.[group]"; + GROUP BY cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group"); return ConvertSqlToTags(sql, SqlHelper.CreateParameter("@nodeId", nodeId)); @@ -299,10 +300,10 @@ namespace umbraco.cms.businesslogic.Tags public static IEnumerable GetTags(string group) { - string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags.[group], count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags + string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @", count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags INNER JOIN cmsTagRelationShip ON cmsTagRelationShip.tagid = cmsTags.id - WHERE cmsTags.[group] = @group - GROUP BY cmsTags.id, cmsTags.tag, cmsTags.[group]"; + WHERE cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @" = @group + GROUP BY cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group"); return ConvertSqlToTags(sql, SqlHelper.CreateParameter("@group", group)); @@ -316,9 +317,9 @@ namespace umbraco.cms.businesslogic.Tags public static IEnumerable GetTags() { - string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags.[group], count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags + string sql = @"SELECT cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group") + @", count(cmsTagRelationShip.tagid) AS nodeCount FROM cmsTags LEFT JOIN cmsTagRelationShip ON cmsTagRelationShip.tagid = cmsTags.id - GROUP BY cmsTags.id, cmsTags.tag, cmsTags.[group]"; + GROUP BY cmsTags.id, cmsTags.tag, cmsTags." + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("group"); return ConvertSqlToTags(sql); @@ -366,7 +367,7 @@ namespace umbraco.cms.businesslogic.Tags private static string GetSqlSet(string commaSeparatedArray, string group) { // create array - var array = commaSeparatedArray.Trim().Split(',').ToList().ConvertAll(tag => string.Format("select '{0}' as Tag, '{1}' as [Group]", tag.Replace("'", ""), group)).ToArray(); + var array = commaSeparatedArray.Trim().Split(',').ToList().ConvertAll(tag => string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group"), tag.Replace("'", ""), group)).ToArray(); return "(" + string.Join(" union ", array).Replace(" ", " ") + ") as TagSet"; } private static string GetSqlStringArray(string commaSeparatedArray) From 72dc5fc477e21ea10f9c970aa5ea9c64b254bc5f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Oct 2014 00:23:28 +1000 Subject: [PATCH 15/17] Removes unused method --- .../Persistence/Querying/BaseExpressionHelper.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index f3cbe5a583..2715456719 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -626,13 +626,6 @@ namespace Umbraco.Core.Persistence.Querying // ? "'" + escapeCallback(value) + "'" // : value.ToString(); //} - - public virtual string EscapeParam(object paramValue) - { - return paramValue == null - ? string.Empty - : SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString()); - } public virtual bool ShouldQuoteValue(Type fieldType) { From 3f8b779ae43dcc3e08ad94ca552db1480856fc15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Oct 2014 00:23:38 +1000 Subject: [PATCH 16/17] adds note --- src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs index 543ac9b8e5..acb178bb77 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagsRepository.cs @@ -486,6 +486,10 @@ namespace Umbraco.Core.Persistence.Repositories /// private static string GetTagSet(IEnumerable tagsToInsert) { + //TODO: Fix this query, since this is going to be basically a unique query each time, this will cause some mem usage in peta poco, + // and surely there's a nicer way! + //TODO: When we fix, be sure to remove the @ symbol escape + var array = tagsToInsert .Select(tag => string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("Group") + @"", From 7d189a6fb89da16616057df92792197b2c402130 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 3 Oct 2014 00:28:49 +1000 Subject: [PATCH 17/17] Backports the @ symbol escaping fixes --- .../Persistence/Querying/BaseExpressionHelper.cs | 7 ------- .../Repositories/MemberGroupRepository.cs | 7 +++++-- .../Persistence/Repositories/MemberRepository.cs | 4 ++-- .../Persistence/Repositories/UserRepository.cs | 4 ++-- src/Umbraco.Tests/Services/MemberServiceTests.cs | 12 ++++++++++-- src/Umbraco.Tests/Services/UserServiceTests.cs | 3 ++- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 36bcaa8c3a..d0945c8d04 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -626,13 +626,6 @@ namespace Umbraco.Core.Persistence.Querying // ? "'" + escapeCallback(value) + "'" // : value.ToString(); //} - - public virtual string EscapeParam(object paramValue) - { - return paramValue == null - ? string.Empty - : SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString()); - } public virtual bool ShouldQuoteValue(Type fieldType) { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index ef1790033c..c053e630fa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -202,13 +202,13 @@ namespace Umbraco.Core.Persistence.Repositories //find the member by username var memberSql = new Sql(); var memberObjectType = new Guid(Constants.ObjectTypes.Member); - var escapedUsername = PetaPocoExtensions.EscapeAtSymbols(username); + memberSql.Select("umbracoNode.id") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.NodeId) .Where(x => x.NodeObjectType == memberObjectType) - .Where(x => x.LoginName == escapedUsername); + .Where(x => x.LoginName == username); var memberIdUsername = Database.Fetch(memberSql).FirstOrDefault(); if (memberIdUsername.HasValue == false) { @@ -279,6 +279,9 @@ namespace Umbraco.Core.Persistence.Repositories public void AssignRolesInternal(int[] memberIds, string[] roleNames) { + //ensure they're unique + memberIds = memberIds.Distinct().ToArray(); + //create the missing roles first var existingSql = new Sql() diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index b4ad2af710..c917a928d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -520,10 +520,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); - var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username); + sql.Select("COUNT(*)") .From() - .Where(x => x.LoginName == escapedUserName); + .Where(x => x.LoginName == username); return Database.ExecuteScalar(sql) > 0; } diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 07714b36a7..fcf7d7ddcd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -262,10 +262,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); - var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username); + sql.Select("COUNT(*)") .From() - .Where(x => x.UserName == escapedUserName); + .Where(x => x.UserName == username); return Database.ExecuteScalar(sql) > 0; } diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 8091923ca0..37cf5204bb 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -93,15 +93,20 @@ namespace Umbraco.Tests.Services ServiceContext.MemberTypeService.Save(memberType); IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); ServiceContext.MemberService.Save(member); + //need to test with '@' symbol in the lookup + IMember member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2@test.com"); + ServiceContext.MemberService.Save(member2); ServiceContext.MemberService.AddRole("MyTestRole1"); ServiceContext.MemberService.AddRole("MyTestRole2"); ServiceContext.MemberService.AddRole("MyTestRole3"); - ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" }); + ServiceContext.MemberService.AssignRoles(new[] { member.Id, member2.Id }, new[] { "MyTestRole1", "MyTestRole2" }); var memberRoles = ServiceContext.MemberService.GetAllRoles("test"); - Assert.AreEqual(2, memberRoles.Count()); + + var memberRoles2 = ServiceContext.MemberService.GetAllRoles("test2@test.com"); + Assert.AreEqual(2, memberRoles2.Count()); } [Test] @@ -324,9 +329,12 @@ namespace Umbraco.Tests.Services ServiceContext.MemberTypeService.Save(memberType); IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test"); ServiceContext.MemberService.Save(member); + IMember member2 = MockedMember.CreateSimpleMember(memberType, "test", "test2@test.com", "pass", "test2@test.com"); + ServiceContext.MemberService.Save(member2); Assert.IsTrue(ServiceContext.MemberService.Exists("test")); Assert.IsFalse(ServiceContext.MemberService.Exists("notFound")); + Assert.IsTrue(ServiceContext.MemberService.Exists("test2@test.com")); } [Test] diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index 5c41895037..a5e0211164 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -127,9 +127,10 @@ namespace Umbraco.Tests.Services var userType = MockedUserType.CreateUserType(); ServiceContext.UserService.SaveUserType(userType); var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io", userType); - + var user2 = ServiceContext.UserService.CreateUserWithIdentity("john2@umbraco.io", "john2@umbraco.io", userType); Assert.IsTrue(ServiceContext.UserService.Exists("JohnDoe")); Assert.IsFalse(ServiceContext.UserService.Exists("notFound")); + Assert.IsTrue(ServiceContext.UserService.Exists("john2@umbraco.io")); } [Test]