added support for 0 based sequential ids to our object db.

pull/23/head
kay.one 12 years ago
parent a0c7ccfe7c
commit c6fa3cc02b

@ -18,7 +18,7 @@ namespace NzbDrone.Api.RootFolders
Get["/"] = x => GetRootFolders(); Get["/"] = x => GetRootFolders();
Post["/"] = x => AddRootFolder(); Post["/"] = x => AddRootFolder();
Delete["/{id}"] = x => DeleteRootFolder((long)x.id); Delete["/{id}"] = x => DeleteRootFolder((int)x.id);
} }
private Response AddRootFolder() private Response AddRootFolder()
@ -32,7 +32,7 @@ namespace NzbDrone.Api.RootFolders
return _rootFolderService.All().AsResponse(); return _rootFolderService.All().AsResponse();
} }
private Response DeleteRootFolder(long folderId) private Response DeleteRootFolder(int folderId)
{ {
_rootFolderService.Remove(folderId); _rootFolderService.Remove(folderId);
return new Response { StatusCode = HttpStatusCode.OK }; return new Response { StatusCode = HttpStatusCode.OK };

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Repository;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore
{
[TestFixture]
public class IndexProviderFixture : ObjectDbTest<IndexProvider>
{
[SetUp]
public void Setup()
{
WithObjectDb();
}
[Test]
public void should_be_able_to_get_sequential_numbers()
{
var indexs = new List<int>();
for (var i = 0; i < 1000; i++)
{
indexs.Add(Subject.Next(GetType()));
}
indexs.Should().OnlyHaveUniqueItems();
}
[Test]
public void diffrentTypes_should_get_their_own_counter()
{
var seriesIndex = new List<int>();
var episodeIndex = new List<int>();
for (var i = 0; i < 200; i++)
{
seriesIndex.Add(Subject.Next(typeof(Series)));
}
for (var i = 0; i < 100; i++)
{
episodeIndex.Add(Subject.Next(typeof(Episode)));
}
seriesIndex.Should().OnlyHaveUniqueItems();
episodeIndex.Should().OnlyHaveUniqueItems();
seriesIndex.Min(c => c).Should().Be(1);
seriesIndex.Max(c => c).Should().Be(200);
episodeIndex.Min(c => c).Should().Be(1);
episodeIndex.Max(c => c).Should().Be(100);
}
}
}

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Eloquera.Client; using Eloquera.Client;
using FizzWare.NBuilder; using FizzWare.NBuilder;
@ -85,13 +86,40 @@ namespace NzbDrone.Core.Test.Datastore
[Test] [Test]
public void new_objects_should_get_id() public void new_objects_should_get_id()
{ {
testSeries.Id = 0;
Db.Insert(testSeries); Db.Insert(testSeries);
testSeries.Id.Should().NotBe(0); testSeries.Id.Should().NotBe(0);
} }
[Test]
public void new_existing_object_should_get_new_id()
{
testSeries.Id = 0;
Db.Insert(testSeries);
Db.Insert(testSeries);
Db.AsQueryable<Series>().Should().HaveCount(1);
testSeries.Id.Should().Be(1);
}
[Test]
public void should_be_able_to_assign_ids_to_nested_objects()
{
var nested = new NestedModel();
nested.List.Add(new NestedModel());
Db.Insert(nested);
nested.Id.Should().Be(1);
nested.List.Should().OnlyContain(c => c.Id > 0);
}
[Test] [Test]
public void should_have_id_when_returned_from_database() public void should_have_id_when_returned_from_database()
{ {
testSeries.Id = 0;
Db.Insert(testSeries); Db.Insert(testSeries);
var item = Db.AsQueryable<Series>(); var item = Db.AsQueryable<Series>();
@ -122,5 +150,15 @@ namespace NzbDrone.Core.Test.Datastore
{ {
public string Field1 { get; set; } public string Field1 { get; set; }
} }
public class NestedModel : BaseRepositoryModel
{
public NestedModel()
{
List = new List<NestedModel> { this };
}
public IList<NestedModel> List { get; set; }
}
} }

@ -6,8 +6,35 @@ using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Test.Framework namespace NzbDrone.Core.Test.Framework
{ {
public abstract class ObjectDbTest<TSubject> : ObjectDbTest where TSubject : class
{
private TSubject _subject;
[SetUp]
public void CoreTestSetup()
{
_subject = null;
}
protected TSubject Subject
{
get
{
if (_subject == null)
{
_subject = Mocker.Resolve<TSubject>();
}
return _subject;
}
}
}
public abstract class ObjectDbTest : CoreTest public abstract class ObjectDbTest : CoreTest
{ {
private EloqueraDb _db; private EloqueraDb _db;
protected EloqueraDb Db protected EloqueraDb Db
{ {
@ -32,6 +59,7 @@ namespace NzbDrone.Core.Test.Framework
} }
Mocker.SetConstant(Db); Mocker.SetConstant(Db);
Mocker.SetConstant(Db.Db);
} }
[TearDown] [TearDown]

@ -146,6 +146,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Datastore\IndexProviderFixture.cs" />
<Compile Include="Datastore\ObjectDatabaseFixture.cs" /> <Compile Include="Datastore\ObjectDatabaseFixture.cs" />
<Compile Include="Framework\CoreTest.cs" /> <Compile Include="Framework\CoreTest.cs" />
<Compile Include="Framework\ObjectDbTest.cs" /> <Compile Include="Framework\ObjectDbTest.cs" />

@ -30,6 +30,30 @@
<RegexTestSelector> <RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.Integeration\.ServiceIntegerationFixture\..*</RegularExpression> <RegularExpression>NzbDrone\.Core\.Test\.Integeration\.ServiceIntegerationFixture\..*</RegularExpression>
</RegexTestSelector> </RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.QualityProfileTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.TvDbProviderTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.MediaFileProviderTest\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.EpisodeProviderTests\.EpisodeProviderTest_DeleteInvalidEpisodes\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.UpgradeHistorySpecificationFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ProviderTests\.DecisionEngineTests\.QualityAllowedByProfileSpecificationFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ParserTests\.QualityParserFixture\..*</RegularExpression>
</RegexTestSelector>
<RegexTestSelector>
<RegularExpression>NzbDrone\.Core\.Test\.ParserTests\.ParserFixture\..*</RegularExpression>
</RegexTestSelector>
</IgnoredTests> </IgnoredTests>
<AdditionalFilesToInclude>..\NzbDrone.Core\bin\Debug\Eloquera.Server.exe</AdditionalFilesToInclude> <AdditionalFilesToInclude>..\NzbDrone.Core\bin\Debug\Eloquera.Server.exe</AdditionalFilesToInclude>
<HiddenWarnings>PostBuildEventDisabled</HiddenWarnings> <HiddenWarnings>PostBuildEventDisabled</HiddenWarnings>

@ -1,7 +1,4 @@
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Eloquera.Client; using Eloquera.Client;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
@ -9,6 +6,8 @@ namespace NzbDrone.Core.Datastore
public abstract class BaseRepositoryModel public abstract class BaseRepositoryModel
{ {
[ID] [ID]
public long Id; private long _eqId;
public int Id { get; set; }
} }
} }

@ -6,9 +6,9 @@ namespace NzbDrone.Core.Datastore
public interface IBasicRepository<TModel> public interface IBasicRepository<TModel>
{ {
List<TModel> All(); List<TModel> All();
TModel Get(long rootFolderId); TModel Get(int rootFolderId);
TModel Add(TModel rootFolder); TModel Add(TModel rootFolder);
void Delete(long rootFolderId); void Delete(int rootFolderId);
} }
public class BasicRepository<TModel> : IBasicRepository<TModel> where TModel : BaseRepositoryModel, new() public class BasicRepository<TModel> : IBasicRepository<TModel> where TModel : BaseRepositoryModel, new()
@ -25,7 +25,7 @@ namespace NzbDrone.Core.Datastore
return EloqueraDb.AsQueryable<TModel>().ToList(); return EloqueraDb.AsQueryable<TModel>().ToList();
} }
public TModel Get(long id) public TModel Get(int id)
{ {
return EloqueraDb.AsQueryable<TModel>().Single(c => c.Id == id); return EloqueraDb.AsQueryable<TModel>().Single(c => c.Id == id);
} }
@ -35,7 +35,7 @@ namespace NzbDrone.Core.Datastore
return EloqueraDb.Insert(model); return EloqueraDb.Insert(model);
} }
public void Delete(long id) public void Delete(int id)
{ {
var itemToDelete = Get(id); var itemToDelete = Get(id);
EloqueraDb.Delete(itemToDelete); EloqueraDb.Delete(itemToDelete);

@ -1,49 +1,56 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Eloquera.Client; using Eloquera.Client;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
public class EloqueraDb : IDisposable public class EloqueraDb : IDisposable
{ {
private readonly DB _db; private readonly IdService _idService;
public DB Db { get; private set; }
public EloqueraDb(DB db) public EloqueraDb(DB db, IdService idService)
{ {
_db = db; _idService = idService;
Db = db;
} }
public IEnumerable<T> AsQueryable<T>() public IEnumerable<T> AsQueryable<T>()
{ {
return _db.Query<T>(); return Db.Query<T>();
} }
public T Insert<T>(T obj) where T : BaseRepositoryModel public T Insert<T>(T obj) where T : BaseRepositoryModel
{ {
obj.Id = _db.Store(obj); _idService.EnsureIds(obj, new HashSet<object>());
Db.Store(obj);
return obj; return obj;
} }
public IList<T> InsertMany<T>(IEnumerable<T> objects) where T : BaseRepositoryModel public IList<T> InsertMany<T>(IList<T> objects) where T : BaseRepositoryModel
{ {
_idService.EnsureIds(objects, new HashSet<object>());
return DoMany(objects, Insert); return DoMany(objects, Insert);
} }
public T Update<T>(T obj) public T Update<T>(T obj)
{ {
_db.Store(obj); Db.Store(obj);
return obj; return obj;
} }
public IList<T> UpdateMany<T>(IEnumerable<T> objects) public IList<T> UpdateMany<T>(IList<T> objects)
{ {
_idService.EnsureIds(objects, new HashSet<object>());
return DoMany(objects, Update); return DoMany(objects, Update);
} }
public void Delete<T>(T obj) where T : new() public void Delete<T>(T obj) where T : new()
{ {
_db.Delete(obj); Db.Delete(obj);
} }
public void DeleteMany<T>(IEnumerable<T> objects) where T : new() public void DeleteMany<T>(IEnumerable<T> objects) where T : new()
@ -59,9 +66,10 @@ namespace NzbDrone.Core.Datastore
return objects.Select(function).ToList(); return objects.Select(function).ToList();
} }
public void Dispose() public void Dispose()
{ {
_db.Dispose(); Db.Dispose();
} }
} }
} }

@ -57,7 +57,7 @@ namespace NzbDrone.Core.Datastore
RegisterTypeRules(); RegisterTypeRules();
RegisterTypes(db); RegisterTypes(db);
return new EloqueraDb(db); return new EloqueraDb(db, new IdService(new IndexProvider(db)));
} }
private void RegisterTypeRules() private void RegisterTypeRules()

@ -0,0 +1,96 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NzbDrone.Core.Datastore
{
public class IdService
{
private readonly IndexProvider _indexProvider;
private static readonly ConcurrentDictionary<string, IList<PropertyInfo>> propertyCache = new ConcurrentDictionary<string, IList<PropertyInfo>>();
public IdService(IndexProvider indexProvider)
{
_indexProvider = indexProvider;
}
public void EnsureIds<T>(T obj, HashSet<object> context)
{
//context is use to prevent infinite loop if objects are recursively looped.
if (obj == null || context.Contains(obj))
{
return;
}
context.Add(obj);
var modelBase = obj as BaseRepositoryModel;
if (modelBase != null && modelBase.Id == 0)
{
modelBase.Id = _indexProvider.Next(obj.GetType());
}
foreach (var propertyInfo in GetPotentialProperties(obj.GetType()))
{
var propValue = propertyInfo.GetValue(obj, null);
var list = propValue as IEnumerable;
if (list != null)
{
foreach (var item in list)
{
EnsureIds(item, context);
}
}
else
{
EnsureIds(propValue, context);
}
}
}
private IList<PropertyInfo> GetPotentialProperties(Type type)
{
IList<PropertyInfo> result;
if (!propertyCache.TryGetValue(type.FullName, out result))
{
result = type.GetProperties().Where(ShouldCrawl).ToList();
propertyCache.TryAdd(type.FullName, result);
}
return result;
}
private bool ShouldCrawl(PropertyInfo propertyInfo)
{
return propertyInfo.CanRead && ShouldCrawl(propertyInfo.PropertyType);
}
private bool ShouldCrawl(Type type)
{
if (type.IsGenericType)
{
var genericArg = type.GetGenericArguments()[0];
//skip if generic argument type isn't interesting
if (!ShouldCrawl(genericArg))
{
return false;
}
var listType = typeof(IList<>).MakeGenericType(genericArg);
return listType.IsAssignableFrom(type);
}
return type.IsClass && type.FullName.StartsWith("NzbDrone");
}
}
}

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Eloquera.Client;
namespace NzbDrone.Core.Datastore
{
public interface IProvideIndex
{
int Next(Type type);
}
public class IndexProvider : IProvideIndex
{
private readonly DB _db;
private static object _lock = new object();
public IndexProvider(DB db)
{
_db = db;
if (db.IsTypeRegistered(typeof(IndexList)))
{
db.RegisterType(typeof(IndexList));
}
lock (_lock)
{
try
{
_db.Query<IndexList>().Count();
}
catch (EloqueraException ex)
{
_db.Store(new IndexList());
}
}
}
public int Next(Type type)
{
if (type == null)
{
throw new ArgumentException();
}
var key = type.Name;
lock (_lock)
{
var indexList = _db.Query<IndexList>().Single();
var indexInfo = indexList.SingleOrDefault(c => c.Type == key);
if (indexInfo == null)
{
indexInfo = new IndexInfo { Type = key };
indexList.Add(indexInfo);
}
indexInfo.Index++;
_db.Store(indexList);
return indexInfo.Index;
}
}
public class IndexList : List<IndexInfo> { }
public class IndexInfo
{
public string Type { get; set; }
public int Index { get; set; }
}
}
}

@ -236,6 +236,8 @@
<Compile Include="Datastore\ConnectionFactory.cs" /> <Compile Include="Datastore\ConnectionFactory.cs" />
<Compile Include="Datastore\EloqueraDb.cs" /> <Compile Include="Datastore\EloqueraDb.cs" />
<Compile Include="Datastore\EloqueraDbFactory.cs" /> <Compile Include="Datastore\EloqueraDbFactory.cs" />
<Compile Include="Datastore\IdService.cs" />
<Compile Include="Datastore\IndexProvider.cs" />
<Compile Include="Datastore\MigrationLogger.cs" /> <Compile Include="Datastore\MigrationLogger.cs" />
<Compile Include="Datastore\MigrationsHelper.cs" /> <Compile Include="Datastore\MigrationsHelper.cs" />
<Compile Include="Datastore\CustomeMapper.cs" /> <Compile Include="Datastore\CustomeMapper.cs" />

@ -13,7 +13,7 @@ namespace NzbDrone.Core.RootFolders
{ {
List<RootFolder> All(); List<RootFolder> All();
RootFolder Add(RootFolder rootDir); RootFolder Add(RootFolder rootDir);
void Remove(long rootDirId); void Remove(int rootDirId);
List<String> GetUnmappedFolders(string path); List<String> GetUnmappedFolders(string path);
Dictionary<string, ulong> FreeSpaceOnDrives(); Dictionary<string, ulong> FreeSpaceOnDrives();
} }
@ -63,7 +63,7 @@ namespace NzbDrone.Core.RootFolders
return rootFolder; return rootFolder;
} }
public virtual void Remove(long rootDirId) public virtual void Remove(int rootDirId)
{ {
_rootFolderRepository.Delete(rootDirId); _rootFolderRepository.Delete(rootDirId);
} }

@ -150,7 +150,7 @@
<WebProjectProperties> <WebProjectProperties>
<UseIIS>False</UseIIS> <UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort> <AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>1306</DevelopmentServerPort> <DevelopmentServerPort>28501</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath> <DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:1306/</IISUrl> <IISUrl>http://localhost:1306/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication> <NTLMAuthentication>False</NTLMAuthentication>

@ -366,7 +366,7 @@
<WebProjectProperties> <WebProjectProperties>
<UseIIS>False</UseIIS> <UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort> <AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>17584</DevelopmentServerPort> <DevelopmentServerPort>28496</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath> <DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:62182/</IISUrl> <IISUrl>http://localhost:62182/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication> <NTLMAuthentication>False</NTLMAuthentication>

@ -1,6 +1,6 @@
<SolutionConfiguration> <SolutionConfiguration>
<FileVersion>1</FileVersion> <FileVersion>1</FileVersion>
<AutoEnableOnStartup>False</AutoEnableOnStartup> <AutoEnableOnStartup>True</AutoEnableOnStartup>
<AllowParallelTestExecution>true</AllowParallelTestExecution> <AllowParallelTestExecution>true</AllowParallelTestExecution>
<AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves> <AllowTestsToRunInParallelWithThemselves>true</AllowTestsToRunInParallelWithThemselves>
<FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit> <FrameworkUtilisationTypeForNUnit>UseDynamicAnalysis</FrameworkUtilisationTypeForNUnit>

Loading…
Cancel
Save