Fixing tests - all green or ignored

This commit is contained in:
Stephan
2017-06-27 16:09:33 +02:00
parent e87be7ad9d
commit faec8f7d94
21 changed files with 478 additions and 395 deletions

View File

@@ -785,7 +785,8 @@ WHERE (@path LIKE {5})",
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
// fixme - missing the 'include all versions' thing here - see 7.6 ProcessQuery
// "many" corresponds to 7.6 "includeAllVersions"
// fixme - we are not implementing the double-query thing for pagination from 7.6?
//
private IEnumerable<IContent> MapQueryDtos(List<DocumentDto> dtos, bool withCache = false, bool many = false)
{
@@ -794,16 +795,32 @@ WHERE (@path LIKE {5})",
var contentTypes = new Dictionary<int, IContentType>();
var templateIds = new List<int>();
// in case of data corruption we may have more than 1 "newest" - cleanup
var ix = new Dictionary<int, DocumentDto>();
foreach (var dto in dtos)
{
if (ix.TryGetValue(dto.NodeId, out DocumentDto ixDto) == false || ixDto.UpdateDate < dto.UpdateDate)
ix[dto.NodeId] = dto;
}
dtos = ix.Values.ToList();
// populate published data
if (many)
{
var publishedDtoIndex = Database.FetchByGroups<DocumentPublishedReadOnlyDto, int>(dtos.Select(x => x.NodeId), 2000, batch
var roDtos = Database.FetchByGroups<DocumentPublishedReadOnlyDto, int>(dtos.Select(x => x.NodeId), 2000, batch
=> Sql()
.Select<DocumentPublishedReadOnlyDto>()
.From<DocumentDto>()
.WhereIn<DocumentDto>(x => x.NodeId, batch)
.Where<DocumentDto>(x => x.Published))
.ToDictionary(x => x.NodeId, x => x);
.Where<DocumentDto>(x => x.Published));
// in case of data corruption we may have more than 1 "published" - cleanup
var publishedDtoIndex = new Dictionary<int, DocumentPublishedReadOnlyDto>();
foreach (var roDto in roDtos)
{
if (publishedDtoIndex.TryGetValue(roDto.NodeId, out DocumentPublishedReadOnlyDto ixDto) == false || ixDto.VersionDate < roDto.VersionDate)
publishedDtoIndex[roDto.NodeId] = roDto;
}
foreach (var dto in dtos)
{

View File

@@ -1079,103 +1079,92 @@ AND umbracoNode.id <> @id",
// query below is not safe + pointless if array is empty
if (contentTypeIds.Length == 0) return;
var sql = @"SELECT
pt.contentTypeId as contentTypeId, pt.uniqueID as ptUniqueID, pt.id as ptId,
pt.Alias as ptAlias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" as ptDesc, pt.mandatory as ptMandatory,
pt.Name as ptName, pt.sortOrder as ptSortOrder, pt.validationRegExp as ptRegExp,
var sqlGroups = @"SELECT
pg.contenttypeNodeId AS contentTypeId,
pg.id AS id, pg.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
pg.sortOrder AS sortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" AS text
FROM cmsPropertyTypeGroup pg
WHERE pg.contenttypeNodeId IN (@ids)
ORDER BY contentTypeId, id";
dt.nodeId as dtId, dt.dbType as dtDbType, dt.propertyEditorAlias as dtPropEdAlias,
pg.id as pgId, pg.uniqueID as pgKey, pg.sortorder as pgSortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" as pgText
FROM cmsPropertyType as pt
var sqlProps = @"SELECT
pt.contentTypeId AS contentTypeId,
pt.id AS id, pt.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
pt.propertyTypeGroupId AS groupId,
pt.Alias AS alias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" AS " + sqlSyntax.GetQuotedColumnName("desc") + @", pt.mandatory AS mandatory,
pt.Name AS name, pt.sortOrder AS sortOrder, pt.validationRegExp AS regexp,
dt.nodeId as dataTypeId, dt.dbType as dbType, dt.propertyEditorAlias as editorAlias
FROM cmsPropertyType pt
INNER JOIN cmsDataType as dt ON pt.dataTypeId = dt.nodeId
LEFT JOIN cmsPropertyTypeGroup as pg ON pg.id = pt.propertyTypeGroupId
WHERE pt.contentTypeId IN (@ids)
ORDER BY contentTypeId, groupId, id";
WHERE pt.contentTypeId IN (@contentTypeIds)
ORDER BY pgId
";
//NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count!
// Since there are 2 groups of params, it will be half!
if (((contentTypeIds.Length / 2) - 1) > 2000)
if (contentTypeIds.Length > 2000)
throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
var result = db.Fetch<dynamic>(sql, new { contentTypeIds = contentTypeIds });
var groups = db.Fetch<dynamic>(sqlGroups, new { ids = contentTypeIds });
var groupsEnumerator = groups.GetEnumerator();
var group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
var props = db.Fetch<dynamic>(sqlProps, new { ids = contentTypeIds });
var propsEnumerator = props.GetEnumerator();
var prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
// groups are ordered by content type, group id
// props are ordered by content type, group id, prop id
foreach (var contentTypeId in contentTypeIds)
{
//from this we need to make :
// * PropertyGroupCollection - Contains all property groups along with all property types associated with a group
// * PropertyTypeCollection - Contains all property types that do not belong to a group
var propertyTypeCollection = allPropertyTypeCollection[contentTypeId] = new PropertyTypeCollection();
var propertyGroupCollection = allPropertyGroupCollection[contentTypeId] = new PropertyGroupCollection();
//create the property group collection first, this means all groups (even empty ones) and all groups with properties
while (prop != null && prop.contentTypeId == contentTypeId && prop.groupId == null)
{
AddPropertyType(propertyTypeCollection, prop);
prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
}
int currId = contentTypeId;
var propertyGroupCollection = new PropertyGroupCollection(result
//get all rows that have a group id
.Where(x => x.pgId != null)
//filter based on the current content type
.Where(x => x.contentTypeId == currId)
//turn that into a custom object containing only the group info
.Select(x => new { GroupId = x.pgId, SortOrder = x.pgSortOrder, Text = x.pgText, Key = x.pgKey })
//get distinct data by id
.DistinctBy(x => (int)x.GroupId)
//for each of these groups, create a group object with it's associated properties
.Select(group => new PropertyGroup(new PropertyTypeCollection(
result
.Where(row => row.pgId == group.GroupId && row.ptId != null)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType), row.ptAlias)
{
//fill in the rest of the property type properties
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Key = row.ptUniqueID,
Mandatory = Convert.ToBoolean(row.ptMandatory),
Name = row.ptName,
PropertyGroupId = new Lazy<int>(() => group.GroupId, false),
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
})))
while (group != null && group.contentTypeId == contentTypeId)
{
var propertyGroup = new PropertyGroup(new PropertyTypeCollection())
{
//fill in the rest of the group properties
Id = group.GroupId,
Name = group.Text,
SortOrder = group.SortOrder,
Key = group.Key
}).ToArray());
Id = group.id,
Name = group.text,
SortOrder = group.sortOrder,
Key = group.key
};
propertyGroupCollection.Add(propertyGroup);
allPropertyGroupCollection[currId] = propertyGroupCollection;
//Create the property type collection now (that don't have groups)
var propertyTypeCollection = new PropertyTypeCollection(result
.Where(x => x.pgId == null)
//filter based on the current content type
.Where(x => x.contentTypeId == currId)
.Select(row => new PropertyType(row.dtPropEdAlias, Enum<DataTypeDatabaseType>.Parse(row.dtDbType), row.ptAlias)
while (prop != null && prop.groupId == group.id)
{
//fill in the rest of the property type properties
Description = row.ptDesc,
DataTypeDefinitionId = row.dtId,
Id = row.ptId,
Key = row.ptUniqueID,
Mandatory = Convert.ToBoolean(row.ptMandatory),
Name = row.ptName,
PropertyGroupId = null,
SortOrder = row.ptSortOrder,
ValidationRegExp = row.ptRegExp
}).ToArray());
AddPropertyType(propertyGroup.PropertyTypes, prop, propertyGroup);
prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
}
allPropertyTypeCollection[currId] = propertyTypeCollection;
group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
}
}
propsEnumerator.Dispose();
groupsEnumerator.Dispose();
}
private static void AddPropertyType(PropertyTypeCollection propertyTypes, dynamic prop, PropertyGroup propertyGroup = null)
{
var propertyType = new PropertyType(prop.editorAlias, Enum<DataTypeDatabaseType>.Parse(prop.dbType), prop.alias)
{
Description = prop.desc,
DataTypeDefinitionId = prop.dataTypeId,
Id = prop.id,
Key = prop.key,
Mandatory = Convert.ToBoolean(prop.mandatory),
Name = prop.name,
PropertyGroupId = propertyGroup == null ? null : new Lazy<int>(() => propertyGroup.Id),
SortOrder = prop.sortOrder,
ValidationRegExp = prop.regexp
};
propertyTypes.Add(propertyType);
}
}
protected abstract TEntity PerformGet(Guid id);

View File

@@ -14,14 +14,13 @@ namespace Umbraco.Core.Persistence.UnitOfWork
// creates a unit of work
// redefine the method to indicate it returns an IScopeUnitOfWork and
// not anymore only an IDatabaseUnitOfWork as IDatabaseUnitOfWorkProvider does
// fixme - merge IScope... and IDatabase...
new IScopeUnitOfWork CreateUnitOfWork();
// creates a unit of work
// support specifying an isolation level
// support auto-commit - but beware! it will be committed, whatever happens
// fixme in v8 this should all be merged as one single method with optional args
IScopeUnitOfWork CreateUnitOfWork(bool readOnly);
IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel, bool readOnly = false);
IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false);
// fixme explain
IDatabaseContext DatabaseContext { get; }

View File

@@ -31,11 +31,12 @@ namespace Umbraco.Core.Persistence.UnitOfWork
/// <param name="repositoryFactory"></param>
/// <param name="isolationLevel"></param>
/// <param name="readOnly"></param>
/// <param name="immediate"></param>
/// <remarks>
/// This should normally not be used directly and should be created with the UnitOfWorkProvider
/// </remarks>
internal ScopeUnitOfWork(IScopeProvider scopeProvider, RepositoryFactory repositoryFactory, IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false)
: base(repositoryFactory, readOnly)
internal ScopeUnitOfWork(IScopeProvider scopeProvider, RepositoryFactory repositoryFactory, IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false)
: base(repositoryFactory, readOnly, immediate)
{
_scopeProvider = scopeProvider;
_isolationLevel = isolationLevel;

View File

@@ -37,21 +37,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork
}
/// <inheritdoc />
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel)
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel = IsolationLevel.Unspecified, bool readOnly = false, bool immediate = false)
{
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel);
}
/// <inheritdoc />
public IScopeUnitOfWork CreateUnitOfWork(bool readOnly)
{
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, readOnly: readOnly);
}
/// <inheritdoc />
public IScopeUnitOfWork CreateUnitOfWork(IsolationLevel isolationLevel, bool readOnly)
{
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel, readOnly: readOnly);
return new ScopeUnitOfWork(ScopeProvider, _repositoryFactory, isolationLevel, readOnly, immediate);
}
}
}

View File

@@ -7,20 +7,28 @@ namespace Umbraco.Core.Persistence.UnitOfWork
{
public abstract class UnitOfWorkBase : DisposableObject, IUnitOfWork
{
private readonly Queue<Operation> _operations = new Queue<Operation>();
private Queue<Operation> _operations;
// fixme - explain readonly
// it means that the unit of work *will* complete no matter what
// but if an exception is thrown from within the 'using' block
// the calling code still can deal with it and cancel everything
//
// fixme - explain immediate
// do not queue things - save allocations + in some complex operations
// that are transactional queueing makes no sense (or we keep flushing)
protected UnitOfWorkBase(RepositoryFactory repositoryFactory, bool readOnly = false)
protected UnitOfWorkBase(RepositoryFactory repositoryFactory, bool readOnly = false, bool immediate = false)
{
RepositoryFactory = repositoryFactory;
ReadOnly = readOnly;
Immediate = immediate;
}
private Queue<Operation> Operations => _operations ?? (_operations = new Queue<Operation>());
protected bool ReadOnly { get; }
protected bool Immediate { get; }
protected RepositoryFactory RepositoryFactory { get; }
@@ -39,12 +47,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
Completed = false;
_operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Insert
});
if (Immediate)
repository.PersistNewItem(entity);
else
Operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Insert
});
}
/// <summary>
@@ -59,12 +70,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
Completed = false;
_operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Update
});
if (Immediate)
repository.PersistUpdatedItem(entity);
else
Operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Update
});
}
/// <summary>
@@ -79,12 +93,15 @@ namespace Umbraco.Core.Persistence.UnitOfWork
Completed = false;
_operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Delete
});
if (Immediate)
repository.PersistDeletedItem(entity);
else
Operations.Enqueue(new Operation
{
Entity = entity,
Repository = repository,
Type = OperationType.Delete
});
}
// fixme - we don't need Begin, really, or do we?
@@ -98,6 +115,9 @@ namespace Umbraco.Core.Persistence.UnitOfWork
Begin();
if (_operations == null)
return;
while (_operations.Count > 0)
{
var operation = _operations.Dequeue();
@@ -137,7 +157,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
{
// whatever hasn't been commited is lost
// not sure we need this as we are being disposed...
_operations.Clear();
_operations?.Clear();
}
/// <summary>