diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs index 3def094702..940524cd1a 100644 --- a/src/Umbraco.Core/Constants-SqlTemplates.cs +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -27,8 +27,14 @@ internal static class NuCacheDatabaseDataSource { - public const string ContentSourcesSelect1 = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect1"; + public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect"; + public const string ContentSourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelectUmbracoNodeJoin"; public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount"; + public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect"; + public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount"; + public const string ObjectTypeNotTrashedFilter = "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter"; + public const string OrderByLevelIdSortOrder = "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder"; + } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index 98dbf1f85c..a49a97568b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -28,9 +28,9 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource // we want arrays, we want them all loaded, not an enumerable - private Sql ContentSourcesSelect(IScope scope, Func, Sql> joins = null) + private Sql SqlContentSourcesSelect(IScope scope, Func, Sql> joins = null) { - var sqlTemplate = scope.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ContentSourcesSelect1, tsql => + var sqlTemplate = scope.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ContentSourcesSelect, tsql => tsql.Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) @@ -75,12 +75,48 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource return sql; } + private Sql SqlContentSourcesSelectUmbracoNodeJoin(Sql s) + { + var syntax = s.SqlContext.SqlSyntax; + var sqlTemplate = s.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ContentSourcesSelectUmbracoNodeJoin, s => + s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")); + var sql = sqlTemplate.Sql(); + return sql; + } + + private Sql SqlWhereNodeId(Sql s, int id) + { + var syntax = s.SqlContext.SqlSyntax; + var sqlTemplate = s.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ContentSourcesSelectUmbracoNodeJoin, s => + s.Where(x => x.NodeId == SqlTemplate.Arg("id"), "x")); + var sql = sqlTemplate.Sql(id); + return sql; + } + + private Sql SqlOrderByLevelIdSortOrder(Sql s) + { + var syntax = s.SqlContext.SqlSyntax; + var sqlTemplate = s.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.OrderByLevelIdSortOrder, s => + s.OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder)); + var sql = sqlTemplate.Sql(); + return sql; + } + + private Sql SqlObjectTypeNotTrashed(Sql s, Guid nodeObjectType) + { + var syntax = s.SqlContext.SqlSyntax; + var sqlTemplate = s.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ObjectTypeNotTrashedFilter, s => + s.Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && !x.Trashed)); + var sql = sqlTemplate.Sql(nodeObjectType); + return sql; + } + /// /// Returns a slightly more optimized query to use for the document counting when paging over the content sources /// /// /// - private Sql ContentSourcesCount(IScope scope, Func, Sql> joins = null) + private Sql SqlContentSourcesCount(IScope scope, Func, Sql> joins = null) { var sqlTemplate = scope.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.ContentSourcesCount, tsql => tsql.Select(x => Alias(x.NodeId, "Id")) @@ -104,11 +140,55 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource return sql; } + private Sql SqlMediaSourcesSelect(IScope scope, Func, Sql> joins = null) + { + var sqlTemplate = scope.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.MediaSourcesSelect, tsql => + tsql.Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), + x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), + x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) + .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) + .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) + .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) + .AndSelect("nuEdit", x => Alias(x.RawData, "EditDataRaw")) + .From()); + + var sql = sqlTemplate.Sql(); + + if (joins != null) + sql = joins(sql); + + // TODO: We can't use a template with this one because of the 'right.Published' ends up being a parameter so not sure how we can do that + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); + + return sql; + } + private Sql SqlMediaSourcesCount(IScope scope, Func, Sql> joins = null) + { + var sqlTemplate = scope.SqlContext.Templates.Get(Constants.SqlTemplates.NuCacheDatabaseDataSource.MediaSourcesCount, tsql => + tsql.Select(x => Alias(x.NodeId, "Id")).From()); + + var sql = sqlTemplate.Sql(); + + if (joins != null) + sql = joins(sql); + + // TODO: We can't use a template with this one because of the 'right.Current' ends up being a parameter so not sure how we can do that + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current); + + return sql; + } + public ContentNodeKit GetContentSource(IScope scope, int id) { - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + var sql = SqlContentSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); var dto = scope.Database.Fetch(sql).FirstOrDefault(); @@ -120,14 +200,14 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource public IEnumerable GetAllContentSources(IScope scope) { - // Create a different query for the SQL vs the COUNT Sql since the auto-generated COUNT Sql will be inneficient - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + var sql = SqlContentSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); + + // Use a more efficient COUNT query + var sqlCountQuery = SqlContentSourcesCount(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)); - // create a more efficient COUNT query without the join on the cmsContentNu table - var sqlCountQuery = ContentSourcesCount(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); var serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document); @@ -138,26 +218,22 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateContentNodeKit(row, serializer); - } + } } public IEnumerable GetBranchContentSources(IScope scope, int id) { - var syntax = scope.SqlContext.SqlSyntax; - var sql = ContentSourcesSelect(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .Where(x => x.NodeId == id, "x") - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + var sql = SqlContentSourcesSelect(scope, SqlContentSourcesSelectUmbracoNodeJoin) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); - // create a more efficient COUNT query without the join on the cmsContentNu table - var sqlCountQuery = ContentSourcesCount(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .Where(x => x.NodeId == id, "x"); + // Use a more efficient COUNT query + var sqlCountQuery = SqlContentSourcesCount(scope, SqlContentSourcesSelectUmbracoNodeJoin) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); - var serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document); // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. @@ -166,20 +242,21 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateContentNodeKit(row, serializer); - } + } } public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) { if (!ids.Any()) yield break; - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + var sql = SqlContentSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) .WhereIn(x => x.ContentTypeId, ids) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); - var sqlCountQuery = ContentSourcesCount(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + // Use a more efficient COUNT query + var sqlCountQuery = SqlContentSourcesCount(scope, SqlContentSourcesSelectUmbracoNodeJoin) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Document)) .WhereIn(x => x.ContentTypeId, ids); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); @@ -191,54 +268,15 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateContentNodeKit(row, serializer); - } - } - - private Sql MediaSourcesSelect(IScope scope, Func, Sql> joins = null) - { - var sql = scope.SqlContext.Sql() - - .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), - x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), - x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) - .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) - .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) - .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) - .AndSelect("nuEdit", x => Alias(x.RawData, "EditDataRaw")) - .From(); - - if (joins != null) - sql = joins(sql); - - sql = sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); - - return sql; - } - private Sql MediaSourcesCount(IScope scope, Func, Sql> joins = null) - { - var sql = scope.SqlContext.Sql() - - .Select(x => Alias(x.NodeId, "Id")) - .From(); - - if (joins != null) - sql = joins(sql); - - sql = sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current); - - return sql; + } } public ContentNodeKit GetMediaSource(IScope scope, int id) { - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && x.NodeId == id && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + var sql = SqlMediaSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); var dto = scope.Database.Fetch(sql).FirstOrDefault(); @@ -250,13 +288,13 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource public IEnumerable GetAllMediaSources(IScope scope) { - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - var sqlCountQuery = MediaSourcesCount(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed); + var sql = SqlMediaSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); + // Use a more efficient COUNT query + var sqlCountQuery = SqlMediaSourcesCount(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); var serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); @@ -267,23 +305,20 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateMediaNodeKit(row, serializer); - } + } } public IEnumerable GetBranchMediaSources(IScope scope, int id) { - var syntax = scope.SqlContext.SqlSyntax; - var sql = MediaSourcesSelect(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .Where(x => x.NodeId == id, "x") - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - var sqlCountQuery = MediaSourcesCount(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .Where(x => x.NodeId == id, "x"); + var sql = SqlMediaSourcesSelect(scope, SqlContentSourcesSelectUmbracoNodeJoin) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)) + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); + // Use a more efficient COUNT query + var sqlCountQuery = SqlMediaSourcesCount(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) + .Append(SqlWhereNodeId(scope.SqlContext.Sql(), id)); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); var serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); @@ -294,22 +329,22 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateMediaNodeKit(row, serializer); - } + } } public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) { if (!ids.Any()) yield break; - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) + var sql = SqlMediaSourcesSelect(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) .WhereIn(x => x.ContentTypeId, ids) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - var sqlCountQuery = MediaSourcesCount(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .WhereIn(x => x.ContentTypeId, ids); + .Append(SqlOrderByLevelIdSortOrder(scope.SqlContext.Sql())); + // Use a more efficient COUNT query + var sqlCountQuery = SqlMediaSourcesCount(scope) + .Append(SqlObjectTypeNotTrashed(scope.SqlContext.Sql(), Constants.ObjectTypes.Media)) + .WhereIn(x => x.ContentTypeId, ids); var sqlCount = scope.SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl"); var serializer = _contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media); @@ -320,7 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource foreach (var row in scope.Database.QueryPaged(PageSize, sql, sqlCount)) { yield return CreateMediaNodeKit(row, serializer); - } + } } private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto, IContentCacheDataSerializer serializer)