From 88f79cfca5fe65d283dfa64b736f340a4d42aae3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 25 Sep 2014 13:38:32 +1000 Subject: [PATCH] Fixes remaining service tests and adds a couple more expression tests --- .../Querying/BaseExpressionHelper.cs | 56 ++++++++++--------- .../Repositories/EntityRepository.cs | 33 +++++++++-- .../Persistence/Querying/PetaPocoSqlTests.cs | 12 ++++ 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index e075ef2e50..f3cbe5a583 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -310,29 +310,34 @@ namespace Umbraco.Core.Persistence.Querying protected virtual string VisitMethodCall(MethodCallExpression m) { - //List args = this.VisitExpressionList(m.Arguments); - //Object r; - //if (m.Object != null) - //{ - // r = Visit(m.Object); - //} - //else - //{ - // //TODO: I have no idea what this does and if it's ever used. - // var args = VisitExpressionList(m.Arguments); - // r = args[0]; - // args.RemoveAt(0); - //} + //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(m.Object.ToString()); + SqlParameters.Add(objectForMethod.ToString()); return string.Format("@{0}", SqlParameters.Count - 1); case "ToUpper": - return string.Format("upper({0})", Visit(m.Object)); + return string.Format("upper({0})", visitedObjectForMethod); case "ToLower": - return string.Format("lower({0})", Visit(m.Object)); + return string.Format("lower({0})", visitedObjectForMethod); case "SqlWildcard": case "StartsWith": case "EndsWith": @@ -346,48 +351,45 @@ namespace Umbraco.Core.Persistence.Querying case "InvariantEndsWith": case "InvariantContains": case "InvariantEquals": - - var r = Visit(m.Object); - + string compareValue; - if (m.Arguments[0].NodeType != ExpressionType.Constant) + 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(m.Arguments[0], typeof (object)); + var member = Expression.Convert(methodArgs[0], typeof(object)); var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); compareValue = getter().ToString(); } else { - compareValue = m.Arguments[0].ToString(); + 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 (m.Arguments[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom(m.Arguments[0].Type)) + 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 (m.Arguments.Count > 1) + if (methodArgs.Length > 1) { - var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); + var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType)); if (colTypeArg != null) { colType = (TextColumnType)((ConstantExpression)colTypeArg).Value; } } - return HandleStringComparison(r, compareValue, m.Method.Name, colType); + return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType); //case "Substring": // var startIndex = Int32.Parse(args[0].ToString()) + 1; // if (args.Count == 2) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 2820e76200..838a6bbe23 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -195,9 +195,16 @@ namespace Umbraco.Core.Persistence.Repositories { //TODO: We need to fix all of this and how it handles parameters! - var wheres = string.Join(" AND ", query.GetWhereClauses()); + var wheres = query.GetWhereClauses().ToArray(); - var sqlClause = GetBase(false, false, sql1 => sql1.Where(wheres)); + 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)); @@ -211,14 +218,21 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { - //TODO: We need to fix all of this and how it handles parameters! bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - var wheres = string.Join(" AND ", query.GetWhereClauses()); + var wheres = query.GetWhereClauses().ToArray(); - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql1 => sql1.Where(wheres), objectTypeId); + 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(); @@ -227,7 +241,14 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql => sql.Where(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.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs index 5aebfa2cd2..f934b0c536 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs @@ -7,6 +7,7 @@ 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 { @@ -15,6 +16,17 @@ namespace Umbraco.Tests.Persistence.Querying { //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() {