From f83ccb6ca41f5ad8064d9be985b09175f99e3206 Mon Sep 17 00:00:00 2001 From: ta264 Date: Wed, 18 Dec 2019 21:56:41 +0000 Subject: [PATCH] Swap to dapper Mk. 2 --- src/NzbDrone.Api/History/HistoryModule.cs | 2 +- .../Indexers/ReleaseModuleBase.cs | 2 +- src/NzbDrone.Api/Movies/MovieLookupModule.cs | 1 - .../ServiceFactoryFixture.cs | 9 +- .../Extensions/IEnumerableExtensions.cs | 5 - .../Instrumentation/NzbDroneLogger.cs | 6 +- .../BulkImport/AddMultiMoviesFixture.cs | 11 +- .../Datastore/BasicRepositoryFixture.cs | 259 ++++++++++-- .../Converters/BooleanIntConverterFixture.cs | 59 --- .../Converters/CommandConverterFixture.cs | 45 +-- .../Converters/DictionaryConverterFixture.cs | 41 ++ .../Converters/DoubleConverterFixture.cs | 70 ---- .../Converters/EnumIntConverterFixture.cs | 57 --- .../Converters/GuidConverterFixture.cs | 33 +- .../Converters/Int32ConverterFixture.cs | 58 --- .../Converters/OsPathConverterFixture.cs | 30 +- .../ProviderSettingConverterFixture.cs | 26 +- .../Converters/QualityIntConverterFixture.cs | 41 +- .../Converters/TimeSpanConverterFixture.cs | 65 --- .../Converters/UtcConverterFixture.cs | 39 +- .../Datastore/DatabaseFixture.cs | 7 +- .../Datastore/DatabaseRelationshipFixture.cs | 44 --- .../Datastore/MarrDataLazyLoadingFixture.cs | 50 --- .../PagingOffsetFixture.cs | 28 -- .../ToSortDirectionFixture.cs | 53 --- .../ReflectionStrategyFixture/Benchmarks.cs | 39 -- ...entionFixture.cs => TableMapperFixture.cs} | 14 +- .../Datastore/WhereBuilderFixture.cs | 165 ++++++++ ...matAllowedByProfileSpecificationFixture.cs | 15 +- .../LanguageSpecificationFixture.cs | 17 +- ...ityAllowedByProfileSpecificationFixture.cs | 7 +- .../QueueSpecificationFixture.cs | 10 +- .../RssSync/DelaySpecificationFixture.cs | 8 +- .../DiskSpace/DiskSpaceServiceFixture.cs | 4 +- .../PendingReleaseServiceTests/AddFixture.cs | 3 +- .../RemoveGrabbedFixture.cs | 3 +- .../RemoveRejectedFixture.cs | 3 +- src/NzbDrone.Core.Test/Framework/DbTest.cs | 3 +- .../Framework/DirectDataMapper.cs | 18 +- .../Checks/RootFolderCheckFixture.cs | 8 +- .../CleanupAdditionalUsersFixture.cs | 13 +- .../Housekeepers/CleanupUnusedTagsFixture.cs | 11 +- .../DatabaseTargetFixture.cs | 18 - .../SameFileSpecificationFixture.cs | 11 +- .../UpgradeSpecificationFixture.cs | 9 +- .../MovieRepositoryFixture.cs | 25 +- .../ParsingServiceTests/MapFixture.cs | 3 +- .../Radarr.Core.Test.csproj | 1 + .../Authentication/UserRepository.cs | 4 +- .../Backup/MakeDatabaseBackup.cs | 2 +- .../Blacklisting/BlacklistRepository.cs | 31 +- .../Configuration/ConfigFileProvider.cs | 8 +- .../Configuration/ConfigRepository.cs | 2 +- .../CustomFormats/CustomFormat.cs | 8 +- .../CustomFormats/CustomFormatDefinition.cs | 14 + .../CustomFormats/CustomFormatRepository.cs | 4 +- .../CustomFormats/CustomFormatService.cs | 15 +- .../Datastore/BasicRepository.cs | 372 +++++++++++++----- .../Converters/BooleanIntConverter.cs | 51 --- .../Datastore/Converters/CommandConverter.cs | 31 +- .../Converters/CustomFormatIntConverter.cs | 81 ++-- .../Datastore/Converters/DoubleConverter.cs | 46 --- .../Converters/EmbeddedDocumentConverter.cs | 79 ++-- .../Datastore/Converters/EnumIntConverter.cs | 36 -- .../Datastore/Converters/GuidConverter.cs | 32 +- .../Datastore/Converters/Int32Converter.cs | 36 -- .../Converters/LanguageIntConverter.cs | 80 ++-- .../Datastore/Converters/OsPathConverter.cs | 35 +- .../Converters/ProviderSettingConverter.cs | 40 +- .../Converters/QualityIntConverter.cs | 66 +--- .../Converters/QualityTagStringConverter.cs | 61 +-- .../Datastore/Converters/TimeSpanConverter.cs | 38 +- .../Datastore/Converters/UtcConverter.cs | 38 +- src/NzbDrone.Core/Datastore/Database.cs | 22 +- src/NzbDrone.Core/Datastore/DbFactory.cs | 12 +- .../Datastore/ExpressionVisitor.cs | 148 +++++++ .../Datastore/Extensions/BuilderExtensions.cs | 135 +++++++ .../Datastore/Extensions/MappingExtensions.cs | 67 ---- .../Extensions/PagingSpecExtensions.cs | 46 --- .../Extensions/RelationshipExtensions.cs | 50 --- src/NzbDrone.Core/Datastore/LazyList.cs | 28 -- src/NzbDrone.Core/Datastore/LogDatabase.cs | 6 +- src/NzbDrone.Core/Datastore/MainDatabase.cs | 6 +- .../036_update_with_quality_converters.cs | 16 +- ..._add_language_to_file_history_blacklist.cs | 13 +- src/NzbDrone.Core/Datastore/TableMapper.cs | 116 ++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 179 +++++---- src/NzbDrone.Core/Datastore/WhereBuilder.cs | 323 +++++++++++++++ .../DownloadDecisionComparer.cs | 9 +- ...stomFormatAllowedByProfileSpecification.cs | 2 +- .../Specifications/CutoffSpecification.cs | 4 +- .../Specifications/LanguageSpecification.cs | 2 +- .../QualityAllowedByProfileSpecification.cs | 2 +- .../Specifications/QueueSpecification.cs | 2 +- .../RssSync/DelaySpecification.cs | 4 +- .../UpgradeAllowedSpecification.cs | 2 +- .../DiskSpace/DiskSpaceService.cs | 6 +- .../Pending/PendingReleaseRepository.cs | 6 +- .../Download/Pending/PendingReleaseService.cs | 2 +- src/NzbDrone.Core/Extras/ExtraService.cs | 3 +- .../Extras/Files/ExtraFileRepository.cs | 8 +- .../HealthCheck/Checks/MountCheck.cs | 10 +- .../HealthCheck/Checks/RootFolderCheck.cs | 10 +- .../History/HistoryRepository.cs | 69 ++-- .../CleanupAbsolutePathMetadataFiles.cs | 7 +- .../CleanupAdditionalNamingSpecs.cs | 7 +- .../Housekeepers/CleanupAdditionalUsers.cs | 13 +- ...ownloadClientUnavailablePendingReleases.cs | 21 +- .../CleanupDuplicateMetadataFiles.cs | 9 +- .../CleanupOrphanedAlternativeTitles.cs | 7 +- .../Housekeepers/CleanupOrphanedBlacklist.cs | 7 +- .../CleanupOrphanedDownloadClientStatus.cs | 7 +- .../CleanupOrphanedHistoryItems.cs | 5 +- .../CleanupOrphanedIndexerStatus.cs | 5 +- .../CleanupOrphanedMetadataFiles.cs | 13 +- .../Housekeepers/CleanupOrphanedMovieFiles.cs | 15 +- .../CleanupOrphanedPendingReleases.cs | 17 +- .../Housekeepers/CleanupUnusedTags.cs | 18 +- .../FixFutureRunScheduledTasks.cs | 12 +- .../FixWronglyMatchedMovieFiles.cs | 2 +- .../Instrumentation/ReconfigureLogging.cs | 14 + .../Jobs/ScheduledTaskRepository.cs | 2 +- .../MediaFiles/MediaFileRepository.cs | 4 +- .../MediaFiles/MediaFileService.cs | 25 +- src/NzbDrone.Core/MediaFiles/MovieFile.cs | 3 +- .../Messaging/Commands/CommandRepository.cs | 45 +-- .../AlternativeTitles/AlternativeTitle.cs | 2 - .../AlternativeTitleRepository.cs | 10 +- src/NzbDrone.Core/Movies/Movie.cs | 3 +- src/NzbDrone.Core/Movies/MovieRepository.cs | 233 ++++++----- src/NzbDrone.Core/Movies/MovieService.cs | 73 +++- src/NzbDrone.Core/Movies/QueryExtensions.cs | 9 - .../ImportExclusionsRepository.cs | 7 +- .../NetImport/NetImportDefinition.cs | 3 - .../Profiles/ProfileRepository.cs | 2 +- src/NzbDrone.Core/Qualities/Revision.cs | 3 +- src/NzbDrone.Core/Radarr.Core.csproj | 4 +- src/NzbDrone.Core/Tags/TagRepository.cs | 4 +- .../ThingiProvider/ProviderRepository.cs | 46 ++- .../Status/ProviderStatusRepository.cs | 2 +- .../Paths/MovieAncestorValidator.cs | 2 +- src/NzbDrone.Host.Test/ContainerFixture.cs | 6 + src/NzbDrone.Test.Common/NzbDroneRunner.cs | 6 +- src/Radarr.Api.V3/History/HistoryModule.cs | 2 +- .../Indexers/ReleaseModuleBase.cs | 2 +- .../MovieFiles/MovieFileResource.cs | 6 +- 146 files changed, 2460 insertions(+), 2190 deletions(-) delete mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/BooleanIntConverterFixture.cs create mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/DictionaryConverterFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/DoubleConverterFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/Int32ConverterFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/PagingOffsetFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/ToSortDirectionFixture.cs delete mode 100644 src/NzbDrone.Core.Test/Datastore/ReflectionStrategyFixture/Benchmarks.cs rename src/NzbDrone.Core.Test/Datastore/{MappingExtentionFixture.cs => TableMapperFixture.cs} (70%) create mode 100644 src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs create mode 100644 src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs delete mode 100644 src/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs delete mode 100644 src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs delete mode 100644 src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs delete mode 100644 src/NzbDrone.Core/Datastore/Converters/Int32Converter.cs create mode 100644 src/NzbDrone.Core/Datastore/ExpressionVisitor.cs create mode 100644 src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs delete mode 100644 src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs delete mode 100644 src/NzbDrone.Core/Datastore/Extensions/PagingSpecExtensions.cs delete mode 100644 src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs delete mode 100644 src/NzbDrone.Core/Datastore/LazyList.cs create mode 100644 src/NzbDrone.Core/Datastore/TableMapper.cs create mode 100644 src/NzbDrone.Core/Datastore/WhereBuilder.cs diff --git a/src/NzbDrone.Api/History/HistoryModule.cs b/src/NzbDrone.Api/History/HistoryModule.cs index 6c09582d8..06b2106ae 100644 --- a/src/NzbDrone.Api/History/HistoryModule.cs +++ b/src/NzbDrone.Api/History/HistoryModule.cs @@ -35,7 +35,7 @@ protected HistoryResource MapToResource(Core.History.History model) if (model.Movie != null) { - resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Movie.Profile.Value, model.Quality); + resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); } return resource; diff --git a/src/NzbDrone.Api/Indexers/ReleaseModuleBase.cs b/src/NzbDrone.Api/Indexers/ReleaseModuleBase.cs index 3dca0acce..018228c92 100644 --- a/src/NzbDrone.Api/Indexers/ReleaseModuleBase.cs +++ b/src/NzbDrone.Api/Indexers/ReleaseModuleBase.cs @@ -29,7 +29,7 @@ protected virtual ReleaseResource MapDecision(DownloadDecision decision, int ini if (decision.RemoteMovie.Movie != null) { release.QualityWeight = decision.RemoteMovie.Movie - .Profile.Value + .Profile .Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100; } diff --git a/src/NzbDrone.Api/Movies/MovieLookupModule.cs b/src/NzbDrone.Api/Movies/MovieLookupModule.cs index 2ee8ea965..1829887fc 100644 --- a/src/NzbDrone.Api/Movies/MovieLookupModule.cs +++ b/src/NzbDrone.Api/Movies/MovieLookupModule.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Nancy; diff --git a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs index 830e2af44..1b58b79c7 100644 --- a/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs +++ b/src/NzbDrone.Common.Test/ServiceFactoryFixture.cs @@ -1,7 +1,9 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; @@ -20,6 +22,11 @@ public void event_handlers_should_be_unique() container.Register(new MainDatabase(null)); container.Resolve().Register(); + // A dummy custom format repository since this isn't a DB test + var mockCustomFormat = Mocker.GetMock(); + mockCustomFormat.Setup(x => x.All()).Returns(new List()); + container.Register(mockCustomFormat.Object); + Mocker.SetConstant(container); var handlers = Subject.BuildAll>() diff --git a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs index 627bfd583..c9578a84e 100644 --- a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs +++ b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs @@ -146,11 +146,6 @@ private static IEnumerable InternalDropLast(IEnumerable source, int n) } } - public static bool In(this T source, List list) - { - return list.Contains(source); - } - public static string ConcatToString(this IEnumerable source, string separator = ", ") { return string.Join(separator, source.Select(x => x.ToString())); diff --git a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs index 6a83ce8c5..7fb385c71 100644 --- a/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs +++ b/src/NzbDrone.Common/Instrumentation/NzbDroneLogger.cs @@ -118,9 +118,9 @@ private static void RegisterConsole() private static void RegisterAppFile(IAppFolderInfo appFolderInfo) { - RegisterAppFile(appFolderInfo, "appFileInfo", "radarr.txt", 5, LogLevel.Info); - RegisterAppFile(appFolderInfo, "appFileDebug", "radarr.debug.txt", 50, LogLevel.Off); - RegisterAppFile(appFolderInfo, "appFileTrace", "radarr.trace.txt", 50, LogLevel.Off); + RegisterAppFile(appFolderInfo, "appFileInfo", "radarr.txt", 50, LogLevel.Info); + RegisterAppFile(appFolderInfo, "appFileDebug", "radarr.debug.txt", 500, LogLevel.Off); + RegisterAppFile(appFolderInfo, "appFileTrace", "radarr.trace.txt", 500, LogLevel.Off); } private static void RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles, LogLevel minLogLevel) diff --git a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs index 6980224a0..0d33b7a45 100644 --- a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs +++ b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs @@ -32,6 +32,9 @@ public void movies_added_event_should_have_proper_path() .Setup(s => s.GetMovieFolder(It.IsAny(), null)) .Returns((Movie m, NamingConfig n) => m.Title); + Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) + .Returns(new List()); + var movies = Subject.AddMovies(_fakeMovies); foreach (Movie movie in movies) @@ -39,7 +42,7 @@ public void movies_added_event_should_have_proper_path() movie.Path.Should().NotBeNullOrEmpty(); } - //Subject.GetAllMovies().Should().HaveCount(3); + // Subject.GetAllMovies().Should().HaveCount(3); } [Test] @@ -49,7 +52,8 @@ public void movies_added_should_ignore_already_added() .Setup(s => s.GetMovieFolder(It.IsAny(), null)) .Returns((Movie m, NamingConfig n) => m.Title); - Mocker.GetMock().Setup(s => s.All()).Returns(new List { _fakeMovies[0] }); + Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) + .Returns(new List { _fakeMovies[0] }); var movies = Subject.AddMovies(_fakeMovies); @@ -63,6 +67,9 @@ public void movies_added_should_ignore_duplicates() .Setup(s => s.GetMovieFolder(It.IsAny(), null)) .Returns((Movie m, NamingConfig n) => m.Title); + Mocker.GetMock().Setup(s => s.FindByTmdbId(It.IsAny>())) + .Returns(new List()); + _fakeMovies[2].TmdbId = _fakeMovies[0].TmdbId; var movies = Subject.AddMovies(_fakeMovies); diff --git a/src/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs b/src/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs index 68f7d9fb5..0740fa7ca 100644 --- a/src/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/BasicRepositoryFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using FluentAssertions; @@ -10,34 +11,230 @@ namespace NzbDrone.Core.Test.Datastore { [TestFixture] - public class - BasicRepositoryFixture : DbTest, ScheduledTask> + public class BasicRepositoryFixture : DbTest, ScheduledTask> { - private ScheduledTask _basicType; + private List _basicList; [SetUp] public void Setup() { - _basicType = Builder - .CreateNew() - .With(c => c.Id = 0) - .With(c => c.LastExecution = DateTime.UtcNow) - .Build(); + _basicList = Builder + .CreateListOfSize(5) + .All() + .With(x => x.Id = 0) + .BuildList(); } [Test] - public void should_be_able_to_add() + public void should_be_able_to_insert() { - Subject.Insert(_basicType); + Subject.Insert(_basicList[0]); Subject.All().Should().HaveCount(1); } + [Test] + public void should_be_able_to_insert_many() + { + Subject.InsertMany(_basicList); + Subject.All().Should().HaveCount(5); + } + + [Test] + public void insert_many_should_throw_if_id_not_zero() + { + _basicList[1].Id = 999; + Assert.Throws(() => Subject.InsertMany(_basicList)); + } + + [Test] + public void should_be_able_to_get_count() + { + Subject.InsertMany(_basicList); + Subject.Count().Should().Be(_basicList.Count); + } + + [Test] + public void should_be_able_to_find_by_id() + { + Subject.InsertMany(_basicList); + var storeObject = Subject.Get(_basicList[1].Id); + + storeObject.Should().BeEquivalentTo(_basicList[1], o => o.IncludingAllRuntimeProperties()); + } + + [Test] + public void should_be_able_to_update() + { + Subject.InsertMany(_basicList); + + var item = _basicList[1]; + item.Interval = 999; + + Subject.Update(item); + + Subject.All().Should().BeEquivalentTo(_basicList); + } + + [Test] + public void should_be_able_to_upsert_new() + { + Subject.Upsert(_basicList[0]); + Subject.All().Should().HaveCount(1); + } + + [Test] + public void should_be_able_to_upsert_existing() + { + Subject.InsertMany(_basicList); + + var item = _basicList[1]; + item.Interval = 999; + + Subject.Upsert(item); + + Subject.All().Should().BeEquivalentTo(_basicList); + } + + [Test] + public void should_be_able_to_update_single_field() + { + Subject.InsertMany(_basicList); + + var item = _basicList[1]; + var executionBackup = item.LastExecution; + item.Interval = 999; + item.LastExecution = DateTime.UtcNow; + + Subject.SetFields(item, x => x.Interval); + + item.LastExecution = executionBackup; + Subject.All().Should().BeEquivalentTo(_basicList); + } + + [Test] + public void set_fields_should_throw_if_id_zero() + { + Subject.InsertMany(_basicList); + _basicList[1].Id = 0; + _basicList[1].LastExecution = DateTime.UtcNow; + + Assert.Throws(() => Subject.SetFields(_basicList[1], x => x.Interval)); + } + + [Test] + public void should_be_able_to_delete_model_by_id() + { + Subject.InsertMany(_basicList); + Subject.All().Should().HaveCount(5); + + Subject.Delete(_basicList[0].Id); + Subject.All().Select(x => x.Id).Should().BeEquivalentTo(_basicList.Skip(1).Select(x => x.Id)); + } + + [Test] + public void should_be_able_to_delete_model_by_object() + { + Subject.InsertMany(_basicList); + Subject.All().Should().HaveCount(5); + + Subject.Delete(_basicList[0]); + Subject.All().Select(x => x.Id).Should().BeEquivalentTo(_basicList.Skip(1).Select(x => x.Id)); + } + + [Test] + public void get_many_should_return_empty_list_if_no_ids() + { + Subject.Get(new List()).Should().BeEquivalentTo(new List()); + } + + [Test] + public void get_many_should_throw_if_not_all_found() + { + Subject.InsertMany(_basicList); + Assert.Throws(() => Subject.Get(new[] { 999 })); + } + + [Test] + public void should_be_able_to_find_by_multiple_id() + { + Subject.InsertMany(_basicList); + var storeObject = Subject.Get(_basicList.Take(2).Select(x => x.Id)); + storeObject.Select(x => x.Id).Should().BeEquivalentTo(_basicList.Take(2).Select(x => x.Id)); + } + + [Test] + public void should_be_able_to_update_many() + { + Subject.InsertMany(_basicList); + _basicList.ForEach(x => x.Interval = 999); + + Subject.UpdateMany(_basicList); + Subject.All().Should().BeEquivalentTo(_basicList); + } + + [Test] + public void update_many_should_throw_if_id_zero() + { + Subject.InsertMany(_basicList); + _basicList[1].Id = 0; + Assert.Throws(() => Subject.UpdateMany(_basicList)); + } + + [Test] + public void should_be_able_to_update_many_single_field() + { + Subject.InsertMany(_basicList); + + var executionBackup = _basicList.Select(x => x.LastExecution).ToList(); + _basicList.ForEach(x => x.Interval = 999); + _basicList.ForEach(x => x.LastExecution = DateTime.UtcNow); + + Subject.SetFields(_basicList, x => x.Interval); + + for (int i = 0; i < _basicList.Count; i++) + { + _basicList[i].LastExecution = executionBackup[i]; + } + + Subject.All().Should().BeEquivalentTo(_basicList); + } + + [Test] + public void set_fields_should_throw_if_any_id_zero() + { + Subject.InsertMany(_basicList); + _basicList.ForEach(x => x.Interval = 999); + _basicList[1].Id = 0; + + Assert.Throws(() => Subject.SetFields(_basicList, x => x.Interval)); + } + + [Test] + public void should_be_able_to_delete_many_by_model() + { + Subject.InsertMany(_basicList); + Subject.All().Should().HaveCount(5); + + Subject.DeleteMany(_basicList.Take(2).ToList()); + Subject.All().Select(x => x.Id).Should().BeEquivalentTo(_basicList.Skip(2).Select(x => x.Id)); + } + + [Test] + public void should_be_able_to_delete_many_by_id() + { + Subject.InsertMany(_basicList); + Subject.All().Should().HaveCount(5); + + Subject.DeleteMany(_basicList.Take(2).Select(x => x.Id).ToList()); + Subject.All().Select(x => x.Id).Should().BeEquivalentTo(_basicList.Skip(2).Select(x => x.Id)); + } + [Test] public void purge_should_delete_all() { - Subject.InsertMany(Builder.CreateListOfSize(10).BuildListOfNew()); + Subject.InsertMany(_basicList); - AllStoredModels.Should().HaveCount(10); + AllStoredModels.Should().HaveCount(5); Subject.Purge(); @@ -45,29 +242,29 @@ public void purge_should_delete_all() } [Test] - public void should_be_able_to_delete_model() + public void has_items_should_return_false_with_no_items() { - Subject.Insert(_basicType); - Subject.All().Should().HaveCount(1); - - Subject.Delete(_basicType.Id); - Subject.All().Should().BeEmpty(); + Subject.HasItems().Should().BeFalse(); } [Test] - public void should_be_able_to_find_by_id() + public void has_items_should_return_true_with_items() { - Subject.Insert(_basicType); - var storeObject = Subject.Get(_basicType.Id); + Subject.InsertMany(_basicList); + Subject.HasItems().Should().BeTrue(); + } - storeObject.Should().BeEquivalentTo(_basicType, o => o.IncludingAllRuntimeProperties()); + [Test] + public void single_should_throw_on_empty() + { + Assert.Throws(() => Subject.Single()); } [Test] public void should_be_able_to_get_single() { - Subject.Insert(_basicType); - Subject.SingleOrDefault().Should().NotBeNull(); + Subject.Insert(_basicList[0]); + Subject.Single().Should().BeEquivalentTo(_basicList[0]); } [Test] @@ -89,9 +286,21 @@ public void get_all_with_empty_db_should_return_empty_list() } [Test] - public void should_be_able_to_call_ToList_on_empty_quariable() + public void should_be_able_to_call_ToList_on_empty_queryable() { Subject.All().ToList().Should().BeEmpty(); } + + [Test] + public void get_paged_should_work() + { + Subject.InsertMany(_basicList); + var data = Subject.GetPaged(new PagingSpec() { Page = 0, PageSize = 2, SortKey = "LastExecution", SortDirection = SortDirection.Descending }); + + data.Page.Should().Be(0); + data.PageSize.Should().Be(2); + data.TotalRecords.Should().Be(_basicList.Count); + data.Records.Should().BeEquivalentTo(_basicList.OrderByDescending(x => x.LastExecution).Take(2)); + } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/BooleanIntConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/BooleanIntConverterFixture.cs deleted file mode 100644 index 50e1b892b..000000000 --- a/src/NzbDrone.Core.Test/Datastore/Converters/BooleanIntConverterFixture.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using FluentAssertions; -using Marr.Data.Converters; -using NUnit.Framework; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore.Converters -{ - [TestFixture] - public class BooleanIntConverterFixture : CoreTest - { - [TestCase(true, 1)] - [TestCase(false, 0)] - public void should_return_int_when_saving_boolean_to_db(bool input, int expected) - { - Subject.ToDB(input).Should().Be(expected); - } - - [Test] - public void should_return_db_null_for_null_value_when_saving_to_db() - { - Subject.ToDB(null).Should().Be(DBNull.Value); - } - - [TestCase(1, true)] - [TestCase(0, false)] - public void should_return_bool_when_getting_int_from_db(int input, bool expected) - { - var context = new ConverterContext - { - DbValue = (long)input - }; - - Subject.FromDB(context).Should().Be(expected); - } - - [Test] - public void should_return_db_null_for_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(DBNull.Value); - } - - [Test] - public void should_throw_for_non_boolean_equivalent_number_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = 2L - }; - - Assert.Throws(() => Subject.FromDB(context)); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/CommandConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/CommandConverterFixture.cs index 4921bfda2..ef5b5dc1a 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/CommandConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/CommandConverterFixture.cs @@ -1,10 +1,6 @@ -using System; -using System.Data; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; -using Moq; using NUnit.Framework; -using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Movies.Commands; using NzbDrone.Core.Test.Framework; @@ -14,51 +10,42 @@ namespace NzbDrone.Core.Test.Datastore.Converters [TestFixture] public class CommandConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_json_string_when_saving_boolean_to_db() { var command = new RefreshMovieCommand(); - Subject.ToDB(command).Should().BeOfType(); + Subject.SetValue(_param, command); + _param.Value.Should().BeOfType(); } [Test] public void should_return_null_for_null_value_when_saving_to_db() { - Subject.ToDB(null).Should().Be(null); - } - - [Test] - public void should_return_db_null_for_db_null_value_when_saving_to_db() - { - Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value); + Subject.SetValue(_param, null); + _param.Value.Should().BeNull(); } [Test] public void should_return_command_when_getting_json_from_db() { - var dataRecordMock = new Mock(); - dataRecordMock.Setup(s => s.GetOrdinal("Name")).Returns(0); - dataRecordMock.Setup(s => s.GetString(0)).Returns("RefreshMovie"); + var data = "{\"name\": \"RefreshMovie\"}"; - var context = new ConverterContext - { - DataRecord = dataRecordMock.Object, - DbValue = new RefreshMovieCommand().ToJson() - }; - - Subject.FromDB(context).Should().BeOfType(); + Subject.Parse(data).Should().BeOfType(); } [Test] public void should_return_null_for_null_value_when_getting_from_db() { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(null); + Subject.Parse(null).Should().BeNull(); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/DictionaryConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/DictionaryConverterFixture.cs new file mode 100644 index 000000000..43144308e --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/Converters/DictionaryConverterFixture.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Data.SQLite; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore.Converters; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore.Converters +{ + [TestFixture] + public class DictionaryConverterFixture : CoreTest>> + { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + + [Test] + public void should_serialize_in_camel_case() + { + var dict = new Dictionary + { + { "Data", "Should be lowercased" }, + { "CamelCase", "Should be cameled" } + }; + + Subject.SetValue(_param, dict); + + var result = (string)_param.Value; + + result.Should().Contain("data"); + result.Should().NotContain("Data"); + + result.Should().Contain("camelCase"); + result.Should().NotContain("CamelCase"); + } + } +} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/DoubleConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/DoubleConverterFixture.cs deleted file mode 100644 index 1b5c09c7a..000000000 --- a/src/NzbDrone.Core.Test/Datastore/Converters/DoubleConverterFixture.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using FluentAssertions; -using Marr.Data.Converters; -using NUnit.Framework; -using NzbDrone.Core.Datastore.Converters; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore.Converters -{ - [TestFixture] - public class DoubleConverterFixture : CoreTest - { - [Test] - public void should_return_double_when_saving_double_to_db() - { - var input = 10.5D; - - Subject.ToDB(input).Should().Be(input); - } - - [Test] - public void should_return_null_for_null_value_when_saving_to_db() - { - Subject.ToDB(null).Should().Be(null); - } - - [Test] - public void should_return_db_null_for_db_null_value_when_saving_to_db() - { - Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value); - } - - [Test] - public void should_return_double_when_getting_double_from_db() - { - var expected = 10.5D; - - var context = new ConverterContext - { - DbValue = expected - }; - - Subject.FromDB(context).Should().Be(expected); - } - - [Test] - public void should_return_double_when_getting_string_from_db() - { - var expected = 10.5D; - - var context = new ConverterContext - { - DbValue = $"{expected}" - }; - - Subject.FromDB(context).Should().Be(expected); - } - - [Test] - public void should_return_null_for_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(DBNull.Value); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs deleted file mode 100644 index 494bba760..000000000 --- a/src/NzbDrone.Core.Test/Datastore/Converters/EnumIntConverterFixture.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Reflection; -using FluentAssertions; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Download.Pending; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore.Converters -{ - [TestFixture] - public class EnumIntConverterFixture : CoreTest - { - [Test] - public void should_return_int_when_saving_enum_to_db() - { - Subject.ToDB(PendingReleaseReason.Delay).Should().Be((int)PendingReleaseReason.Delay); - } - - [Test] - public void should_return_db_null_for_null_value_when_saving_to_db() - { - Subject.ToDB(null).Should().Be(DBNull.Value); - } - - [Test] - public void should_return_enum_when_getting_int_from_db() - { - var mockMemberInfo = new Mock(); - mockMemberInfo.SetupGet(s => s.DeclaringType).Returns(typeof(PendingRelease)); - mockMemberInfo.SetupGet(s => s.Name).Returns("Reason"); - - var expected = PendingReleaseReason.Delay; - - var context = new ConverterContext - { - ColumnMap = new ColumnMap(mockMemberInfo.Object) { FieldType = typeof(PendingReleaseReason) }, - DbValue = (long)expected - }; - - Subject.FromDB(context).Should().Be(expected); - } - - [Test] - public void should_return_null_for_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(null); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/GuidConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/GuidConverterFixture.cs index 2d4a8e530..2bf9ea3eb 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/GuidConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/GuidConverterFixture.cs @@ -1,6 +1,6 @@ using System; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; using NUnit.Framework; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Test.Framework; @@ -10,18 +10,21 @@ namespace NzbDrone.Core.Test.Datastore.Converters [TestFixture] public class GuidConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_string_when_saving_guid_to_db() { var guid = Guid.NewGuid(); - Subject.ToDB(guid).Should().Be(guid.ToString()); - } - - [Test] - public void should_return_db_null_for_null_value_when_saving_to_db() - { - Subject.ToDB(null).Should().Be(DBNull.Value); + Subject.SetValue(_param, guid); + _param.Value.Should().Be(guid.ToString()); } [Test] @@ -29,23 +32,13 @@ public void should_return_guid_when_getting_string_from_db() { var guid = Guid.NewGuid(); - var context = new ConverterContext - { - DbValue = guid.ToString() - }; - - Subject.FromDB(context).Should().Be(guid); + Subject.Parse(guid.ToString()).Should().Be(guid); } [Test] public void should_return_empty_guid_for_db_null_value_when_getting_from_db() { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(Guid.Empty); + Subject.Parse(null).Should().Be(Guid.Empty); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/Int32ConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/Int32ConverterFixture.cs deleted file mode 100644 index 793eb5f58..000000000 --- a/src/NzbDrone.Core.Test/Datastore/Converters/Int32ConverterFixture.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using FluentAssertions; -using Marr.Data.Converters; -using NUnit.Framework; -using NzbDrone.Core.Datastore.Converters; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore.Converters -{ - [TestFixture] - public class Int32ConverterFixture : CoreTest - { - [Test] - public void should_return_int_when_saving_int_to_db() - { - var i = 5; - - Subject.ToDB(i).Should().Be(i); - } - - [Test] - public void should_return_int_when_getting_int_from_db() - { - var i = 5; - - var context = new ConverterContext - { - DbValue = i - }; - - Subject.FromDB(context).Should().Be(i); - } - - [Test] - public void should_return_int_when_getting_string_from_db() - { - var i = 5; - - var context = new ConverterContext - { - DbValue = i.ToString() - }; - - Subject.FromDB(context).Should().Be(i); - } - - [Test] - public void should_return_db_null_for_db_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(DBNull.Value); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/OsPathConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/OsPathConverterFixture.cs index c0c92efd3..66688b540 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/OsPathConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/OsPathConverterFixture.cs @@ -1,6 +1,5 @@ -using System; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.Datastore.Converters; @@ -12,13 +11,22 @@ namespace NzbDrone.Core.Test.Datastore.Converters [TestFixture] public class OsPathConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_string_when_saving_os_path_to_db() { var path = @"C:\Test\TV".AsOsAgnostic(); var osPath = new OsPath(path); - Subject.ToDB(osPath).Should().Be(path); + Subject.SetValue(_param, osPath); + _param.Value.Should().Be(path); } [Test] @@ -27,23 +35,13 @@ public void should_return_os_path_when_getting_string_from_db() var path = @"C:\Test\TV".AsOsAgnostic(); var osPath = new OsPath(path); - var context = new ConverterContext - { - DbValue = path - }; - - Subject.FromDB(context).Should().Be(osPath); + Subject.Parse(path).Should().Be(osPath); } [Test] - public void should_return_db_null_for_db_null_value_when_getting_from_db() + public void should_return_empty_for_null_value_when_getting_from_db() { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(DBNull.Value); + Subject.Parse(null).IsEmpty.Should().BeTrue(); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs index c36758be2..67a29c14a 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/ProviderSettingConverterFixture.cs @@ -1,6 +1,5 @@ -using System; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; using NUnit.Framework; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Test.Framework; @@ -8,30 +7,29 @@ namespace NzbDrone.Core.Test.Datastore.Converters { + [Ignore("To reinstate once dapper changes worked out")] [TestFixture] public class ProviderSettingConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_null_config_if_config_is_null() { - var result = Subject.FromDB(new ConverterContext() - { - DbValue = DBNull.Value - }); - - result.Should().Be(NullConfig.Instance); + Subject.Parse(null).Should().Be(NullConfig.Instance); } [TestCase(null)] [TestCase("")] public void should_return_null_config_if_config_is_empty(object dbValue) { - var result = Subject.FromDB(new ConverterContext() - { - DbValue = dbValue - }); - - result.Should().Be(NullConfig.Instance); + Subject.Parse(dbValue).Should().Be(NullConfig.Instance); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/QualityIntConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/QualityIntConverterFixture.cs index 6cad0566a..9ccb345a8 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/QualityIntConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/QualityIntConverterFixture.cs @@ -1,6 +1,5 @@ -using System; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; using NUnit.Framework; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Qualities; @@ -9,26 +8,30 @@ namespace NzbDrone.Core.Test.Datastore.Converters { [TestFixture] - public class QualityIntConverterFixture : CoreTest + public class QualityIntConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_int_when_saving_quality_to_db() { var quality = Quality.Bluray1080p; - Subject.ToDB(quality).Should().Be(quality.Id); + Subject.SetValue(_param, quality); + _param.Value.Should().Be(quality.Id); } [Test] public void should_return_0_when_saving_db_null_to_db() { - Subject.ToDB(DBNull.Value).Should().Be(0); - } - - [Test] - public void should_throw_when_saving_another_object_to_db() - { - Assert.Throws(() => Subject.ToDB("Not a quality")); + Subject.SetValue(_param, null); + _param.Value.Should().Be(0); } [Test] @@ -36,23 +39,13 @@ public void should_return_quality_when_getting_string_from_db() { var quality = Quality.Bluray1080p; - var context = new ConverterContext - { - DbValue = quality.Id - }; - - Subject.FromDB(context).Should().Be(quality); + Subject.Parse(quality.Id).Should().Be(quality); } [Test] - public void should_return_db_null_for_db_null_value_when_getting_from_db() + public void should_return_unknown_for_null_value_when_getting_from_db() { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(Quality.Unknown); + Subject.Parse(null).Should().Be(Quality.Unknown); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs deleted file mode 100644 index a590bbe62..000000000 --- a/src/NzbDrone.Core.Test/Datastore/Converters/TimeSpanConverterFixture.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Globalization; -using FluentAssertions; -using Marr.Data.Converters; -using NUnit.Framework; -using NzbDrone.Core.Datastore.Converters; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore.Converters -{ - [TestFixture] - public class TimeSpanConverterFixture : CoreTest - { - [Test] - public void should_return_string_when_saving_timespan_to_db() - { - var timeSpan = TimeSpan.FromMinutes(5); - - Subject.ToDB(timeSpan).Should().Be(timeSpan.ToString("c", CultureInfo.InvariantCulture)); - } - - [Test] - public void should_return_null_when_saving_empty_string_to_db() - { - Subject.ToDB("").Should().Be(null); - } - - [Test] - public void should_return_time_span_when_getting_time_span_from_db() - { - var timeSpan = TimeSpan.FromMinutes(5); - - var context = new ConverterContext - { - DbValue = timeSpan - }; - - Subject.FromDB(context).Should().Be(timeSpan); - } - - [Test] - public void should_return_time_span_when_getting_string_from_db() - { - var timeSpan = TimeSpan.FromMinutes(5); - - var context = new ConverterContext - { - DbValue = timeSpan.ToString("c", CultureInfo.InvariantCulture) - }; - - Subject.FromDB(context).Should().Be(timeSpan); - } - - [Test] - public void should_return_time_span_zero_for_db_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(TimeSpan.Zero); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/Converters/UtcConverterFixture.cs b/src/NzbDrone.Core.Test/Datastore/Converters/UtcConverterFixture.cs index 51c07a1b3..61b2697ce 100644 --- a/src/NzbDrone.Core.Test/Datastore/Converters/UtcConverterFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Converters/UtcConverterFixture.cs @@ -1,6 +1,6 @@ using System; +using System.Data.SQLite; using FluentAssertions; -using Marr.Data.Converters; using NUnit.Framework; using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Test.Framework; @@ -8,20 +8,23 @@ namespace NzbDrone.Core.Test.Datastore.Converters { [TestFixture] - public class UtcConverterFixture : CoreTest + public class UtcConverterFixture : CoreTest { + private SQLiteParameter _param; + + [SetUp] + public void Setup() + { + _param = new SQLiteParameter(); + } + [Test] public void should_return_date_time_when_saving_date_time_to_db() { var dateTime = DateTime.Now; - Subject.ToDB(dateTime).Should().Be(dateTime.ToUniversalTime()); - } - - [Test] - public void should_return_db_null_when_saving_db_null_to_db() - { - Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value); + Subject.SetValue(_param, dateTime); + _param.Value.Should().Be(dateTime.ToUniversalTime()); } [Test] @@ -29,23 +32,7 @@ public void should_return_time_span_when_getting_time_span_from_db() { var dateTime = DateTime.Now.ToUniversalTime(); - var context = new ConverterContext - { - DbValue = dateTime - }; - - Subject.FromDB(context).Should().Be(dateTime); - } - - [Test] - public void should_return_db_null_for_db_null_value_when_getting_from_db() - { - var context = new ConverterContext - { - DbValue = DBNull.Value - }; - - Subject.FromDB(context).Should().Be(DBNull.Value); + Subject.Parse(dateTime).Should().Be(dateTime); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs index ca59bb729..8735845d6 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Dapper; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Datastore; @@ -14,14 +15,14 @@ public class DatabaseFixture : DbTest public void SingleOrDefault_should_return_null_on_empty_db() { Mocker.Resolve() - .GetDataMapper().Query() - .SingleOrDefault(c => c.CleanTitle == "SomeTitle") + .OpenConnection().Query("SELECT * FROM Movies") + .SingleOrDefault() .Should() .BeNull(); } [Test] - public void vaccume() + public void vacuum() { Mocker.Resolve().Vacuum(); } diff --git a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs index 652a90f7b..b79ce1725 100644 --- a/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/DatabaseRelationshipFixture.cs @@ -4,8 +4,6 @@ using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Languages; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Movies; using NzbDrone.Core.Qualities; using NzbDrone.Core.Test.Framework; @@ -20,48 +18,6 @@ public void Setup() // This is kinda hacky here, since we are kinda testing if the QualityDef converter works as well. } - [Ignore("MovieFile isnt lazy loaded anymore so this will fail.")] - [Test] - - //TODO: Update this! - public void one_to_one() - { - var episodeFile = Builder.CreateNew() - .With(c => c.Quality = new QualityModel()) - .BuildNew(); - - Db.Insert(episodeFile); - - var episode = Builder.CreateNew() - .With(c => c.MovieFileId = episodeFile.Id) - .BuildNew(); - - Db.Insert(episode); - - var loadedEpisode = Db.Single(); - var loadedEpisodeFile = loadedEpisode.MovieFile; - - loadedEpisodeFile.Should().NotBeNull(); - loadedEpisodeFile.Should().BeEquivalentTo(episodeFile, - options => options - .IncludingAllRuntimeProperties() - .Excluding(c => c.DateAdded) - .Excluding(c => c.Path) - .Excluding(c => c.Movie)); - } - - [Test] - public void one_to_one_should_not_query_db_if_foreign_key_is_zero() - { - var episode = Builder.CreateNew() - .With(c => c.MovieFileId = 0) - .BuildNew(); - - Db.Insert(episode); - - Db.Single().MovieFile.Should().BeNull(); - } - [Test] public void embedded_document_as_json() { diff --git a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs b/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs deleted file mode 100644 index 05cfd3dfb..000000000 --- a/src/NzbDrone.Core.Test/Datastore/MarrDataLazyLoadingFixture.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FizzWare.NBuilder; -using NUnit.Framework; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Profiles; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.Datastore -{ - [TestFixture] - public class MarrDataLazyLoadingFixture : DbTest - { - [SetUp] - public void Setup() - { - var profile = new Profile - { - Name = "Test", - Cutoff = Quality.WEBDL720p.Id, - Items = Qualities.QualityFixture.GetDefaultQualities() - }; - - profile = Db.Insert(profile); - - var series = Builder.CreateListOfSize(1) - .All() - .With(v => v.ProfileId = profile.Id) - .BuildListOfNew(); - - Db.InsertMany(series); - - var episodeFiles = Builder.CreateListOfSize(1) - .All() - .With(v => v.MovieId = series[0].Id) - .With(v => v.Quality = new QualityModel()) - .BuildListOfNew(); - - Db.InsertMany(episodeFiles); - - var episodes = Builder.CreateListOfSize(10) - .All() - .With(v => v.Monitored = true) - .With(v => v.MovieFileId = episodeFiles[0].Id) - .BuildListOfNew(); - - Db.InsertMany(episodes); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/PagingOffsetFixture.cs b/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/PagingOffsetFixture.cs deleted file mode 100644 index 8a51ab9ff..000000000 --- a/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/PagingOffsetFixture.cs +++ /dev/null @@ -1,28 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Datastore.Extensions; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Test.Datastore.PagingSpecExtensionsTests -{ - public class PagingOffsetFixture - { - [TestCase(1, 10, 0)] - [TestCase(2, 10, 10)] - [TestCase(3, 20, 40)] - [TestCase(1, 100, 0)] - public void should_calcuate_expected_offset(int page, int pageSize, int expected) - { - var pagingSpec = new PagingSpec - { - Page = page, - PageSize = pageSize, - SortDirection = SortDirection.Ascending, - SortKey = "AirDate" - }; - - pagingSpec.PagingOffset().Should().Be(expected); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/ToSortDirectionFixture.cs b/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/ToSortDirectionFixture.cs deleted file mode 100644 index 2cb954636..000000000 --- a/src/NzbDrone.Core.Test/Datastore/PagingSpecExtensionsTests/ToSortDirectionFixture.cs +++ /dev/null @@ -1,53 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Datastore.Extensions; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Test.Datastore.PagingSpecExtensionsTests -{ - public class ToSortDirectionFixture - { - [Test] - public void should_convert_default_to_asc() - { - var pagingSpec = new PagingSpec - { - Page = 1, - PageSize = 10, - SortDirection = SortDirection.Default, - SortKey = "AirDate" - }; - - pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Asc); - } - - [Test] - public void should_convert_ascending_to_asc() - { - var pagingSpec = new PagingSpec - { - Page = 1, - PageSize = 10, - SortDirection = SortDirection.Ascending, - SortKey = "AirDate" - }; - - pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Asc); - } - - [Test] - public void should_convert_descending_to_desc() - { - var pagingSpec = new PagingSpec - { - Page = 1, - PageSize = 10, - SortDirection = SortDirection.Descending, - SortKey = "AirDate" - }; - - pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Desc); - } - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/ReflectionStrategyFixture/Benchmarks.cs b/src/NzbDrone.Core.Test/Datastore/ReflectionStrategyFixture/Benchmarks.cs deleted file mode 100644 index 810ae5790..000000000 --- a/src/NzbDrone.Core.Test/Datastore/ReflectionStrategyFixture/Benchmarks.cs +++ /dev/null @@ -1,39 +0,0 @@ -using NUnit.Framework; - -namespace NzbDrone.Core.Test.Datastore.ReflectionStrategyFixture -{ - [TestFixture] - public class Benchmarks - { - /* private const int iterations = 5000000; - private object _target; - private IReflectionStrategy _simpleReflectionStrategy; - - [SetUp] - public void Setup() - { - // _simpleReflectionStrategy = new DelegateReflectionStrategy(); - } - - [Test] - public void clr_reflection_test() - { - _target = new Series(); - - var del = _simpleReflectionStrategy.BuildSetter(typeof(Series), "Title"); - - for (int i = 0; i < iterations; i++) - { - del(_target, "TestTile"); - //_simpleReflectionStrategy.SetFieldValue(_target, "Title", "TestTile"); - } - } - - - private void SetField() - { - - - }*/ - } -} diff --git a/src/NzbDrone.Core.Test/Datastore/MappingExtentionFixture.cs b/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs similarity index 70% rename from src/NzbDrone.Core.Test/Datastore/MappingExtentionFixture.cs rename to src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs index 4780896f3..492e0a754 100644 --- a/src/NzbDrone.Core.Test/Datastore/MappingExtentionFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; +using Dapper; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Converters; -using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Movies; namespace NzbDrone.Core.Test.Datastore { [TestFixture] - public class MappingExtensionFixture + public class TableMapperFixture { public class EmbeddedType : IEmbeddedDocument { @@ -37,9 +36,8 @@ public class TypeWithNoMappableProperties [SetUp] public void Setup() { - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(EmbeddedType), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(int), new Int32Converter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); } [Test] @@ -47,7 +45,7 @@ public void test_mappable_types() { var properties = typeof(TypeWithAllMappableProperties).GetProperties(); properties.Should().NotBeEmpty(); - properties.Should().OnlyContain(c => MappingExtensions.IsMappableProperty(c)); + properties.Should().OnlyContain(c => ColumnMapper.IsMappableProperty(c)); } [Test] @@ -55,7 +53,7 @@ public void test_un_mappable_types() { var properties = typeof(TypeWithNoMappableProperties).GetProperties(); properties.Should().NotBeEmpty(); - properties.Should().NotContain(c => MappingExtensions.IsMappableProperty(c)); + properties.Should().NotContain(c => ColumnMapper.IsMappableProperty(c)); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs new file mode 100644 index 000000000..d90111c56 --- /dev/null +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.Datastore +{ + [TestFixture] + public class WhereBuilderFixture : CoreTest + { + private WhereBuilder _subject; + + [OneTimeSetUp] + public void MapTables() + { + // Generate table mapping + Mocker.Resolve(); + } + + private WhereBuilder Where(Expression> filter) + { + return new WhereBuilder(filter, true); + } + + [Test] + public void where_equal_const() + { + _subject = Where(x => x.Id == 10); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @{name})"); + _subject.Parameters.Get(name).Should().Be(10); + } + + [Test] + public void where_equal_variable() + { + var id = 10; + _subject = Where(x => x.Id == id); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @{name})"); + _subject.Parameters.Get(name).Should().Be(id); + } + + [Test] + public void where_throws_without_concrete_condition_if_requiresConcreteCondition() + { + var movie = new Movie(); + Expression> filter = (x) => x.Id == movie.Id; + _subject = new WhereBuilder(filter, true); + Assert.Throws(() => _subject.ToString()); + } + + [Test] + public void where_allows_abstract_condition_if_not_requiresConcreteCondition() + { + var movie = new Movie(); + Expression> filter = (x) => x.Id == movie.Id; + _subject = new WhereBuilder(filter, false); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = \"Movies\".\"Id\")"); + } + + [Test] + public void where_column_contains_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.Contains(test)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @{name} || '%')"); + _subject.Parameters.Get(name).Should().Be(test); + } + + [Test] + public void where_string_contains_column() + { + var test = "small"; + _subject = Where(x => test.Contains(x.CleanTitle)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(@{name} LIKE '%' || \"Movies\".\"CleanTitle\" || '%')"); + _subject.Parameters.Get(name).Should().Be(test); + } + + [Test] + public void where_column_starts_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.StartsWith(test)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE @{name} || '%')"); + _subject.Parameters.Get(name).Should().Be(test); + } + + [Test] + public void where_column_ends_with_string() + { + var test = "small"; + _subject = Where(x => x.CleanTitle.EndsWith(test)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @{name})"); + _subject.Parameters.Get(name).Should().Be(test); + } + + [Test] + public void where_in_list() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => list.Contains(x.Id)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" IN @{name})"); + + var param = _subject.Parameters.Get>(name); + param.Should().BeEquivalentTo(list); + } + + [Test] + public void where_in_list_2() + { + var list = new List { 1, 2, 3 }; + _subject = Where(x => x.CleanTitle == "test" && list.Contains(x.Id)); + + var names = _subject.Parameters.ParameterNames.ToList(); + _subject.ToString().Should().Be($"((\"Movies\".\"CleanTitle\" = @{names[0]}) AND (\"Movies\".\"Id\" IN @{names[1]}))"); + } + + [Test] + public void enum_as_int() + { + _subject = Where(x => x.PathState == MoviePathState.Static); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" = @{name})"); + } + + [Test] + public void enum_in_list() + { + var allowed = new List { MoviePathState.Dynamic, MoviePathState.Static }; + _subject = Where(x => allowed.Contains(x.PathState)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" IN @{name})"); + } + + [Test] + public void enum_in_array() + { + var allowed = new MoviePathState[] { MoviePathState.Dynamic, MoviePathState.Static }; + _subject = Where(x => allowed.Contains(x.PathState)); + + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"PathState\" IN @{name})"); + } + } +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs index 38076fdc9..edfde2e3b 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/CustomFormatAllowedByProfileSpecificationFixture.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Movies; @@ -32,7 +31,7 @@ public void Setup() _format2.Id = 2; var fakeSeries = Builder.CreateNew() - .With(c => c.Profile = (LazyLoaded)new Profile { Cutoff = Quality.Bluray1080p.Id }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id }) .Build(); _remoteMovie = new RemoteMovie @@ -48,7 +47,7 @@ public void Setup() public void should_allow_if_format_is_defined_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format1 }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } @@ -57,7 +56,7 @@ public void should_allow_if_format_is_defined_in_profile() public void should_deny_if_format_is_defined_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2 }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } @@ -66,7 +65,7 @@ public void should_deny_if_format_is_defined_in_profile() public void should_deny_if_one_format_is_defined_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2, _format1 }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } @@ -75,7 +74,7 @@ public void should_deny_if_one_format_is_defined_in_profile() public void should_allow_if_all_format_is_defined_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { _format2, _format1 }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } @@ -84,7 +83,7 @@ public void should_allow_if_all_format_is_defined_in_profile() public void should_deny_if_no_format_was_parsed_and_none_not_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(_format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } @@ -93,7 +92,7 @@ public void should_deny_if_no_format_was_parsed_and_none_not_in_profile() public void should_allow_if_no_format_was_parsed_and_none_in_profile() { _remoteMovie.ParsedMovieInfo.Quality.CustomFormats = new List { }; - _remoteMovie.Movie.Profile.Value.FormatItems = CustomFormatsFixture.GetSampleFormatItems(CustomFormats.CustomFormat.None.Name, _format1.Name, _format2.Name); + _remoteMovie.Movie.Profile.FormatItems = CustomFormatsFixture.GetSampleFormatItems(CustomFormats.CustomFormat.None.Name, _format1.Name, _format2.Name); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs index 919fb3ecf..77fccb4a9 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/LanguageSpecificationFixture.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Languages; @@ -27,12 +26,12 @@ public void Setup() Languages = new List { Language.English } }, Movie = new Movie - { - Profile = new LazyLoaded(new Profile - { - Language = Language.English - }) - } + { + Profile = new Profile + { + Language = Language.English + } + } }; } @@ -65,10 +64,10 @@ public void should_return_false_if_language_is_german() [Test] public void should_return_true_if_allowed_language_any() { - _remoteMovie.Movie.Profile = new LazyLoaded(new Profile + _remoteMovie.Movie.Profile = new Profile { Language = Language.Any - }); + }; WithGermanRelease(); diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs index ddaebf8de..1c74da844 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QualityAllowedByProfileSpecificationFixture.cs @@ -1,6 +1,5 @@ using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; using NzbDrone.Core.Movies; @@ -35,7 +34,7 @@ public class QualityAllowedByProfileSpecificationFixture : CoreTest.CreateNew() - .With(c => c.Profile = (LazyLoaded)new Profile { Cutoff = Quality.Bluray1080p.Id }) + .With(c => c.Profile = new Profile { Cutoff = Quality.Bluray1080p.Id }) .Build(); _remoteMovie = new RemoteMovie @@ -50,7 +49,7 @@ public void Setup() public void should_allow_if_quality_is_defined_in_profile(Quality qualityType) { _remoteMovie.ParsedMovieInfo.Quality.Quality = qualityType; - _remoteMovie.Movie.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); + _remoteMovie.Movie.Profile.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeTrue(); } @@ -60,7 +59,7 @@ public void should_allow_if_quality_is_defined_in_profile(Quality qualityType) public void should_not_allow_if_quality_is_not_defined_in_profile(Quality qualityType) { _remoteMovie.ParsedMovieInfo.Quality.Quality = qualityType; - _remoteMovie.Movie.Profile.Value.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); + _remoteMovie.Movie.Profile.Items = Qualities.QualityFixture.GetDefaultQualities(Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p); Subject.IsSatisfiedBy(_remoteMovie, null).Accepted.Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs index f0ab6b0f9..7d9c2f0fc 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/QueueSpecificationFixture.cs @@ -84,7 +84,7 @@ public void should_return_true_when_movie_doesnt_match() [Test] public void should_return_true_when_quality_in_queue_is_lower() { - _movie.Profile.Value.Cutoff = Quality.Bluray1080p.Id; + _movie.Profile.Cutoff = Quality.Bluray1080p.Id; var remoteMovie = Builder.CreateNew() .With(r => r.Movie = _movie) @@ -116,7 +116,7 @@ public void should_return_false_when_qualities_are_the_same() [Test] public void should_return_false_when_quality_in_queue_is_better() { - _movie.Profile.Value.Cutoff = Quality.Bluray1080p.Id; + _movie.Profile.Cutoff = Quality.Bluray1080p.Id; var remoteMovie = Builder.CreateNew() .With(r => r.Movie = _movie) @@ -133,7 +133,7 @@ public void should_return_false_when_quality_in_queue_is_better() [Test] public void should_return_false_if_quality_in_queue_meets_cutoff() { - _movie.Profile.Value.Cutoff = _remoteMovie.ParsedMovieInfo.Quality.Quality.Id; + _movie.Profile.Cutoff = _remoteMovie.ParsedMovieInfo.Quality.Quality.Id; var remoteMovie = Builder.CreateNew() .With(r => r.Movie = _movie) @@ -151,8 +151,8 @@ public void should_return_false_if_quality_in_queue_meets_cutoff() [Test] public void should_return_false_when_quality_is_better_and_upgrade_allowed_is_false_for_quality_profile() { - _movie.Profile.Value.Cutoff = Quality.Bluray1080p.Id; - _movie.Profile.Value.UpgradeAllowed = false; + _movie.Profile.Cutoff = Quality.Bluray1080p.Id; + _movie.Profile.UpgradeAllowed = false; var remoteMovie = Builder.CreateNew() .With(r => r.Movie = _movie) diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs index e4f526595..74aa8b901 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RssSync/DelaySpecificationFixture.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using Moq; using NUnit.Framework; using NzbDrone.Core.DecisionEngine.Specifications; @@ -70,12 +69,7 @@ public void Setup() private void GivenExistingFile(QualityModel quality) { //_remoteEpisode.Episodes.First().EpisodeFileId = 1; - - //_remoteEpisode.Episodes.First().EpisodeFile = new LazyLoaded(new EpisodeFile - // { - // Quality = quality - // }); - _remoteEpisode.Movie.MovieFile = new LazyLoaded(new MovieFile { Quality = quality }); + _remoteEpisode.Movie.MovieFile = new MovieFile { Quality = quality }; } private void GivenUpgradeForExistingFile() diff --git a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs index 36f15cc59..1ed1e43dc 100644 --- a/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs +++ b/src/NzbDrone.Core.Test/DiskSpace/DiskSpaceServiceFixture.cs @@ -47,8 +47,8 @@ public void SetUp() private void GivenMovies(params Movie[] movies) { Mocker.GetMock() - .Setup(v => v.GetAllMovies()) - .Returns(movies.ToList()); + .Setup(v => v.AllMoviePaths()) + .Returns(movies.Select(x => x.Path).ToList()); } private void GivenExistingFolder(string folder) diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs index f2922753d..3d595f9f4 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/AddFixture.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; -using Marr.Data; using Moq; using NUnit.Framework; using NzbDrone.Common.Extensions; @@ -45,7 +44,7 @@ public void Setup() }, }; - _movie.Profile = new LazyLoaded(_profile); + _movie.Profile = _profile; _release = Builder.CreateNew().Build(); diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs index 0eee3f04a..6296e44c0 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; -using Marr.Data; using Moq; using NUnit.Framework; using NzbDrone.Common.Extensions; @@ -45,7 +44,7 @@ public void Setup() }, }; - _movie.Profile = new LazyLoaded(_profile); + _movie.Profile = _profile; _release = Builder.CreateNew().Build(); diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs index a0ee68987..ba64e3559 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using FizzWare.NBuilder; -using Marr.Data; using Moq; using NUnit.Framework; using NzbDrone.Common.Extensions; @@ -46,7 +45,7 @@ public void Setup() }, }; - _movie.Profile = new LazyLoaded(_profile); + _movie.Profile = _profile; _release = Builder.CreateNew().Build(); diff --git a/src/NzbDrone.Core.Test/Framework/DbTest.cs b/src/NzbDrone.Core.Test/Framework/DbTest.cs index f548c2c41..afb6dbdb6 100644 --- a/src/NzbDrone.Core.Test/Framework/DbTest.cs +++ b/src/NzbDrone.Core.Test/Framework/DbTest.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data.SQLite; using System.Linq; -using Marr.Data; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; @@ -112,7 +111,7 @@ protected void SetupContainer() Mocker.SetConstant(Mocker.Resolve()); Mocker.SetConstant(Mocker.Resolve()); - MapRepository.Instance.EnableTraceLogging = true; + SqlBuilderExtensions.LogSql = true; } [SetUp] diff --git a/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs b/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs index 45f919245..9b7039022 100644 --- a/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs +++ b/src/NzbDrone.Core.Test/Framework/DirectDataMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Linq; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore; @@ -17,27 +16,16 @@ List Query(string sql) public class DirectDataMapper : IDirectDataMapper { - private readonly DbProviderFactory _providerFactory; - private readonly string _connectionString; + private readonly IDatabase _database; public DirectDataMapper(IDatabase database) { - var dataMapper = database.GetDataMapper(); - _providerFactory = dataMapper.ProviderFactory; - _connectionString = dataMapper.ConnectionString; - } - - private DbConnection OpenConnection() - { - var connection = _providerFactory.CreateConnection(); - connection.ConnectionString = _connectionString; - connection.Open(); - return connection; + _database = database; } public DataTable GetDataTable(string sql) { - using (var connection = OpenConnection()) + using (var connection = _database.OpenConnection()) { using (var cmd = connection.CreateCommand()) { diff --git a/src/NzbDrone.Core.Test/HealthCheck/Checks/RootFolderCheckFixture.cs b/src/NzbDrone.Core.Test/HealthCheck/Checks/RootFolderCheckFixture.cs index cd8f87211..d7884d396 100644 --- a/src/NzbDrone.Core.Test/HealthCheck/Checks/RootFolderCheckFixture.cs +++ b/src/NzbDrone.Core.Test/HealthCheck/Checks/RootFolderCheckFixture.cs @@ -20,8 +20,8 @@ private void GivenMissingRootFolder() .ToList(); Mocker.GetMock() - .Setup(s => s.GetAllMovies()) - .Returns(movies); + .Setup(s => s.AllMoviePaths()) + .Returns(movies.Select(x => x.Path).ToList()); Mocker.GetMock() .Setup(s => s.GetParentFolder(movies.First().Path)) @@ -36,8 +36,8 @@ private void GivenMissingRootFolder() public void should_not_return_error_when_no_movie() { Mocker.GetMock() - .Setup(s => s.GetAllMovies()) - .Returns(new List()); + .Setup(s => s.AllMoviePaths()) + .Returns(new List()); Subject.Check().ShouldBeOk(); } diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAdditionalUsersFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAdditionalUsersFixture.cs index aea416ef1..df20e02ef 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAdditionalUsersFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupAdditionalUsersFixture.cs @@ -1,4 +1,5 @@ -using FizzWare.NBuilder; +using System; +using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; using NzbDrone.Core.Authentication; @@ -14,7 +15,11 @@ public class CleanupAdditionalUsersFixture : DbTest.CreateListOfSize(5) - .BuildListOfNew(); + .All() + .With(x => x.Id = 0) + .BuildListOfNew(); + + specs.ForEach(x => x.Identifier = Guid.NewGuid()); Db.InsertMany(specs); @@ -26,7 +31,9 @@ public void should_delete_additional_users() public void should_not_delete_if_only_one_user() { var spec = Builder.CreateNew() - .BuildNew(); + .With(x => x.Id = 0) + .With(x => x.Identifier = Guid.NewGuid()) + .BuildNew(); Db.Insert(spec); diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupUnusedTagsFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupUnusedTagsFixture.cs index b69727d04..d32e16e63 100644 --- a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupUnusedTagsFixture.cs +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/CleanupUnusedTagsFixture.cs @@ -14,7 +14,10 @@ public class CleanupUnusedTagsFixture : DbTest [Test] public void should_delete_unused_tags() { - var tags = Builder.CreateListOfSize(2).BuildList(); + var tags = Builder.CreateListOfSize(2) + .All() + .With(x => x.Id = 0) + .BuildList(); Db.InsertMany(tags); Subject.Clean(); @@ -24,11 +27,15 @@ public void should_delete_unused_tags() [Test] public void should_not_delete_used_tags() { - var tags = Builder.CreateListOfSize(2).BuildList(); + var tags = Builder.CreateListOfSize(2) + .All() + .With(x => x.Id = 0) + .BuildList(); Db.InsertMany(tags); var restrictions = Builder.CreateListOfSize(2) .All() + .With(v => v.Id = 0) .With(v => v.Tags.Add(tags[0].Id)) .BuildList(); Db.InsertMany(restrictions); diff --git a/src/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs b/src/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs index f9874e012..fd75b5d84 100644 --- a/src/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs +++ b/src/NzbDrone.Core.Test/InstrumentationTests/DatabaseTargetFixture.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using FluentAssertions; -using Marr.Data; using NLog; using NUnit.Framework; using NzbDrone.Common.Instrumentation; @@ -10,7 +9,6 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; -using NzbDrone.Test.Common.Categories; namespace NzbDrone.Core.Test.InstrumentationTests { @@ -64,22 +62,6 @@ public void write_long_log() VerifyLog(StoredModel, LogLevel.Info); } - [Test] - [Explicit] - [ManualTest] - public void perf_test() - { - MapRepository.Instance.EnableTraceLogging = false; - for (int i = 0; i < 1000; i++) - { - _logger.Info(Guid.NewGuid()); - } - - Thread.Sleep(1000); - - MapRepository.Instance.EnableTraceLogging = true; - } - [Test] public void write_log_exception() { diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/SameFileSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/SameFileSpecificationFixture.cs index 8f63ff25c..961fa3956 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/SameFileSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/SameFileSpecificationFixture.cs @@ -1,6 +1,5 @@ using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.MovieImport.Specifications; @@ -38,11 +37,11 @@ public void should_be_accepted_if_file_size_is_different() { _localMovie.Movie = Builder.CreateNew() .With(e => e.MovieFileId = 1) - .With(e => e.MovieFile = new LazyLoaded( + .With(e => e.MovieFile = new MovieFile { Size = _localMovie.Size + 100.Megabytes() - })) + }) .Build(); Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); @@ -53,11 +52,11 @@ public void should_be_reject_if_file_size_is_the_same() { _localMovie.Movie = Builder.CreateNew() .With(e => e.MovieFileId = 1) - .With(e => e.MovieFile = new LazyLoaded( + .With(e => e.MovieFile = new MovieFile { Size = _localMovie.Size - })) + }) .Build(); Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeFalse(); @@ -68,7 +67,7 @@ public void should_be_accepted_if_file_cannot_be_fetched() { _localMovie.Movie = Builder.CreateNew() .With(e => e.MovieFileId = 1) - .With(e => e.MovieFile = new LazyLoaded((MovieFile)null)) + .With(e => e.MovieFile = null) .Build(); Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/UpgradeSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/UpgradeSpecificationFixture.cs index 36d8e5970..a2020a741 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/UpgradeSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/UpgradeSpecificationFixture.cs @@ -1,6 +1,5 @@ using FizzWare.NBuilder; using FluentAssertions; -using Marr.Data; using NUnit.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.MovieImport.Specifications; @@ -46,11 +45,11 @@ public void should_return_true_if_no_existing_episodeFile() public void should_return_true_if_upgrade_for_existing_episodeFile() { _localMovie.Movie.MovieFileId = 1; - _localMovie.Movie.MovieFile = new LazyLoaded( + _localMovie.Movie.MovieFile = new MovieFile { Quality = new QualityModel(Quality.SDTV, new Revision(version: 1)) - }); + }; Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); } @@ -59,11 +58,11 @@ public void should_return_true_if_upgrade_for_existing_episodeFile() public void should_return_false_if_not_an_upgrade_for_existing_episodeFile() { _localMovie.Movie.MovieFileId = 1; - _localMovie.Movie.MovieFile = new LazyLoaded( + _localMovie.Movie.MovieFile = new MovieFile { Quality = new QualityModel(Quality.Bluray720p, new Revision(version: 1)) - }); + }; Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs b/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs index 0f0e6a6cc..77407131c 100644 --- a/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs +++ b/src/NzbDrone.Core.Test/MovieTests/MovieRepositoryTests/MovieRepositoryFixture.cs @@ -1,3 +1,4 @@ +using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -12,31 +13,35 @@ namespace NzbDrone.Core.Test.MovieTests.MovieRepositoryTests public class MovieRepositoryFixture : DbTest { + private IProfileRepository _profileRepository; + [SetUp] public void Setup() { + _profileRepository = Mocker.Resolve(); + Mocker.SetConstant(_profileRepository); } [Test] - public void should_lazyload_quality_profile() + public void should_load_quality_profile() { var profile = new Profile - { - Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), - FormatItems = CustomFormat.CustomFormatsFixture.GetDefaultFormatItems(), - FormatCutoff = CustomFormats.CustomFormat.None.Id, - Cutoff = Quality.Bluray1080p.Id, - Name = "TestProfile" - }; + { + Items = Qualities.QualityFixture.GetDefaultQualities(Quality.Bluray1080p, Quality.DVD, Quality.HDTV720p), + FormatItems = CustomFormat.CustomFormatsFixture.GetDefaultFormatItems(), + FormatCutoff = CustomFormats.CustomFormat.None.Id, + Cutoff = Quality.Bluray1080p.Id, + Name = "TestProfile" + }; - Mocker.Resolve().Insert(profile); + _profileRepository.Insert(profile); var movie = Builder.CreateNew().BuildNew(); movie.ProfileId = profile.Id; Subject.Insert(movie); - StoredModel.Profile.Should().NotBeNull(); + Subject.All().Single().Profile.Should().NotBeNull(); } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs index 9d713b592..52ca3b1f5 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs @@ -4,7 +4,6 @@ using FluentAssertions; using Moq; using NUnit.Framework; -using NzbDrone.Core.Datastore; using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.Movies; using NzbDrone.Core.Movies.AlternativeTitles; @@ -34,7 +33,7 @@ public void Setup() .With(m => m.Title = "Fack Ju Göthe 2") .With(m => m.CleanTitle = "fackjugoethe2") .With(m => m.Year = 2015) - .With(m => m.AlternativeTitles = new LazyList(new List { new AlternativeTitle("Fack Ju Göthe 2: Same same") })) + .With(m => m.AlternativeTitles = new List { new AlternativeTitle("Fack Ju Göthe 2: Same same") }) .Build(); _parsedMovieInfo = new ParsedMovieInfo diff --git a/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj b/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj index 9dd7c95a7..50e0e2b8e 100644 --- a/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/Radarr.Core.Test.csproj @@ -7,6 +7,7 @@ + diff --git a/src/NzbDrone.Core/Authentication/UserRepository.cs b/src/NzbDrone.Core/Authentication/UserRepository.cs index fd5873190..b439149b3 100644 --- a/src/NzbDrone.Core/Authentication/UserRepository.cs +++ b/src/NzbDrone.Core/Authentication/UserRepository.cs @@ -20,12 +20,12 @@ public UserRepository(IMainDatabase database, IEventAggregator eventAggregator) public User FindUser(string username) { - return Query.Where(u => u.Username == username).SingleOrDefault(); + return Query(x => x.Username == username).SingleOrDefault(); } public User FindUser(Guid identifier) { - return Query.Where(u => u.Identifier == identifier).SingleOrDefault(); + return Query(x => x.Identifier == identifier).SingleOrDefault(); } } } diff --git a/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs b/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs index 961744127..dfc105fb4 100644 --- a/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs +++ b/src/NzbDrone.Core/Backup/MakeDatabaseBackup.cs @@ -22,7 +22,7 @@ public MakeDatabaseBackup(Logger logger) public void BackupDatabase(IDatabase database, string targetDirectory) { var sourceConnectionString = ""; - using (var db = database.GetDataMapper()) + using (var db = database.OpenConnection()) { sourceConnectionString = db.ConnectionString; } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs index 6c93ec5d3..4f4f4e063 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; -using Marr.Data.QGen; +using System.Linq; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; @@ -22,26 +23,36 @@ public BlacklistRepository(IMainDatabase database, IEventAggregator eventAggrega public List BlacklistedByTitle(int movieId, string sourceTitle) { - return Query.Where(e => e.MovieId == movieId) - .AndWhere(e => e.SourceTitle.Contains(sourceTitle)).ToList(); + return Query(x => x.MovieId == movieId && x.SourceTitle.Contains(sourceTitle)); } public List BlacklistedByTorrentInfoHash(int movieId, string torrentInfoHash) { - return Query.Where(e => e.MovieId == movieId) - .AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash)).ToList(); + return Query(x => x.MovieId == movieId && x.TorrentInfoHash.Contains(torrentInfoHash)); } public List BlacklistedByMovie(int movieId) { - return Query.Where(b => b.MovieId == movieId).ToList(); + return Query(x => x.MovieId == movieId); } - protected override SortBuilder GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) + private IEnumerable SelectJoined(SqlBuilder.Template sql) { - var baseQuery = query.Join(JoinType.Inner, h => h.Movie, (h, s) => h.MovieId == s.Id); - - return base.GetPagedQuery(baseQuery, pagingSpec); + using (var conn = _database.OpenConnection()) + { + return conn.Query( + sql.RawSql, + (bl, movie) => + { + bl.Movie = movie; + return bl; + }, + sql.Parameters) + .ToList(); + } } + + protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join((b, m) => b.MovieId == m.Id); + protected override IEnumerable PagedSelector(SqlBuilder.Template sql) => SelectJoined(sql); } } diff --git a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs index e6fae6394..1659e15c3 100644 --- a/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/src/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -33,6 +33,8 @@ public interface IConfigFileProvider : IHandleAsync, bool AnalyticsEnabled { get; } string LogLevel { get; } string ConsoleLogLevel { get; } + bool LogSql { get; } + int LogRotate { get; } bool FilterSentryEvents { get; } string Branch { get; } string ApiKey { get; } @@ -180,6 +182,8 @@ public AuthenticationType AuthenticationMethod public string LogLevel => GetValue("LogLevel", "info"); public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false); + public bool LogSql => GetValueBoolean("LogSql", false, persist: false); + public int LogRotate => GetValueInt("LogRotate", 50, persist: false); public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false); public string SslCertPath => GetValue("SslCertPath", ""); public string SslCertPassword => GetValue("SslCertPassword", ""); @@ -207,9 +211,9 @@ public string UrlBase public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false); - public int GetValueInt(string key, int defaultValue) + public int GetValueInt(string key, int defaultValue, bool persist = true) { - return Convert.ToInt32(GetValue(key, defaultValue)); + return Convert.ToInt32(GetValue(key, defaultValue, persist)); } public bool GetValueBoolean(string key, bool defaultValue, bool persist = true) diff --git a/src/NzbDrone.Core/Configuration/ConfigRepository.cs b/src/NzbDrone.Core/Configuration/ConfigRepository.cs index c8ae80c21..7a6721bb6 100644 --- a/src/NzbDrone.Core/Configuration/ConfigRepository.cs +++ b/src/NzbDrone.Core/Configuration/ConfigRepository.cs @@ -19,7 +19,7 @@ public ConfigRepository(IMainDatabase database, IEventAggregator eventAggregator public Config Get(string key) { - return Query.Where(c => c.Key == key).SingleOrDefault(); + return Query(c => c.Key == key).SingleOrDefault(); } public Config Upsert(string key, string value) diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormat.cs b/src/NzbDrone.Core/CustomFormats/CustomFormat.cs index 8e2e2e270..5d9c0a62e 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormat.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormat.cs @@ -7,6 +7,10 @@ namespace NzbDrone.Core.CustomFormats { public class CustomFormat : ModelBase, IEquatable { + public string Name { get; set; } + + public List FormatTags { get; set; } + public CustomFormat() { } @@ -17,9 +21,7 @@ public CustomFormat(string name, params string[] tags) FormatTags = tags.Select(t => new FormatTag(t)).ToList(); } - public string Name { get; set; } - - public List FormatTags { get; set; } + public static implicit operator CustomFormatDefinition(CustomFormat format) => new CustomFormatDefinition { Id = format.Id, Name = format.Name, FormatTags = format.FormatTags }; public override string ToString() { diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs new file mode 100644 index 000000000..029699c68 --- /dev/null +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatDefinition.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.CustomFormats +{ + public class CustomFormatDefinition : ModelBase + { + public string Name { get; set; } + + public List FormatTags { get; set; } + + public static implicit operator CustomFormat(CustomFormatDefinition def) => new CustomFormat { Id = def.Id, Name = def.Name, FormatTags = def.FormatTags }; + } +} diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs index 0a8e868d9..01ec3d00e 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatRepository.cs @@ -3,11 +3,11 @@ namespace NzbDrone.Core.CustomFormats { - public interface ICustomFormatRepository : IBasicRepository + public interface ICustomFormatRepository : IBasicRepository { } - public class CustomFormatRepository : BasicRepository, ICustomFormatRepository + public class CustomFormatRepository : BasicRepository, ICustomFormatRepository { public CustomFormatRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) diff --git a/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs b/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs index 3846f8d51..ce85b69eb 100644 --- a/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs +++ b/src/NzbDrone.Core/CustomFormats/CustomFormatService.cs @@ -7,9 +7,7 @@ using NzbDrone.Core.Blacklisting; using NzbDrone.Core.Datastore; using NzbDrone.Core.History; -using NzbDrone.Core.Lifecycle; using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Profiles; namespace NzbDrone.Core.CustomFormats @@ -23,7 +21,7 @@ public interface ICustomFormatService void Delete(int id); } - public class CustomFormatService : ICustomFormatService, IHandle + public class CustomFormatService : ICustomFormatService { private readonly ICustomFormatRepository _formatRepository; private readonly IHistoryService _historyService; @@ -59,6 +57,9 @@ public CustomFormatService(ICustomFormatRepository formatRepository, _cache = cacheManager.GetCache>(typeof(CustomFormat), "formats"); _historyService = historyService; _logger = logger; + + // Fill up the cache for subsequent DB lookups + All(); } public void Update(CustomFormat customFormat) @@ -158,7 +159,7 @@ private Dictionary AllDictionary() { return _cache.Get("all", () => { - var all = _formatRepository.All().ToDictionary(m => m.Id); + var all = _formatRepository.All().Select(x => (CustomFormat)x).ToDictionary(m => m.Id); AllCustomFormats = all; return all; }); @@ -208,11 +209,5 @@ public static Dictionary> Templates }; } } - - public void Handle(ApplicationStartedEvent message) - { - // Fillup cache for DataMapper. - All(); - } } } diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index ea70a8db2..d73160107 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -3,11 +3,10 @@ using System.Data; using System.Linq; using System.Linq.Expressions; -using Marr.Data; -using Marr.Data.QGen; -using NzbDrone.Common.Extensions; +using System.Reflection; +using System.Text; +using Dapper; using NzbDrone.Core.Datastore.Events; -using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Datastore @@ -18,58 +17,104 @@ public interface IBasicRepository IEnumerable All(); int Count(); TModel Get(int id); - IEnumerable Get(IEnumerable ids); - TModel SingleOrDefault(); TModel Insert(TModel model); TModel Update(TModel model); TModel Upsert(TModel model); - void Delete(int id); + void SetFields(TModel model, params Expression>[] properties); void Delete(TModel model); + void Delete(int id); + IEnumerable Get(IEnumerable ids); void InsertMany(IList model); void UpdateMany(IList model); + void SetFields(IList models, params Expression>[] properties); void DeleteMany(List model); + void DeleteMany(IEnumerable ids); void Purge(bool vacuum = false); bool HasItems(); - void DeleteMany(IEnumerable ids); - void SetFields(TModel model, params Expression>[] properties); TModel Single(); + TModel SingleOrDefault(); PagingSpec GetPaged(PagingSpec pagingSpec); } public class BasicRepository : IBasicRepository where TModel : ModelBase, new() { - private readonly IDatabase _database; private readonly IEventAggregator _eventAggregator; + private readonly PropertyInfo _keyProperty; + private readonly List _properties; + private readonly string _updateSql; + private readonly string _insertSql; - protected IDataMapper DataMapper => _database.GetDataMapper(); + protected readonly IDatabase _database; + protected readonly string _table; + protected string _selectTemplate; + protected string _deleteTemplate; public BasicRepository(IDatabase database, IEventAggregator eventAggregator) { _database = database; _eventAggregator = eventAggregator; + + var type = typeof(TModel); + + _table = TableMapping.Mapper.TableNameMapping(type); + _keyProperty = type.GetProperty(nameof(ModelBase.Id)); + + var excluded = TableMapping.Mapper.ExcludeProperties(type).Select(x => x.Name).ToList(); + excluded.Add(_keyProperty.Name); + _properties = type.GetProperties().Where(x => !excluded.Contains(x.Name)).ToList(); + + _insertSql = GetInsertSql(); + _updateSql = GetUpdateSql(_properties); + + _selectTemplate = $"SELECT /**select**/ FROM {_table} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**orderby**/"; + _deleteTemplate = $"DELETE FROM {_table} /**where**/"; } - protected QueryBuilder Query => AddJoinQueries(DataMapper.Query()); + protected virtual SqlBuilder BuilderBase() => new SqlBuilder(); + protected virtual SqlBuilder Builder() => BuilderBase().SelectAll(); - protected void Delete(Expression> filter) + protected virtual IEnumerable GetResults(SqlBuilder.Template sql) { - DataMapper.Delete(filter); + using (var conn = _database.OpenConnection()) + { + return conn.Query(sql.RawSql, sql.Parameters); + } } - public IEnumerable All() + protected List Query(Expression> where) { - return AddJoinQueries(DataMapper.Query()).ToList(); + return Query(Builder().Where(where)); + } + + protected List Query(SqlBuilder builder) + { + return Query(builder, GetResults); + } + + protected List Query(SqlBuilder builder, Func> queryFunc) + { + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + + return queryFunc(sql).ToList(); } public int Count() { - return DataMapper.Query().GetRowCount(); + using (var conn = _database.OpenConnection()) + { + return conn.ExecuteScalar($"SELECT COUNT(*) FROM {_table}"); + } + } + + public virtual IEnumerable All() + { + return Query(Builder()); } public TModel Get(int id) { - var model = Query.Where(c => c.Id == id).SingleOrDefault(); + var model = Query(x => x.Id == id).FirstOrDefault(); if (model == null) { @@ -81,13 +126,16 @@ public TModel Get(int id) public IEnumerable Get(IEnumerable ids) { - var idList = ids.ToList(); - var query = string.Format("Id IN ({0})", string.Join(",", idList)); - var result = Query.Where(m => m.Id.In(idList)).ToList(); - - if (result.Count != idList.Count()) + if (!ids.Any()) { - throw new ApplicationException($"Expected query to return {idList.Count} rows but returned {result.Count}"); + return new List(); + } + + var result = Query(x => ids.Contains(x.Id)); + + if (result.Count != ids.Count()) + { + throw new ApplicationException($"Expected query to return {ids.Count()} rows but returned {result.Count}"); } return result; @@ -110,13 +158,73 @@ public TModel Insert(TModel model) throw new InvalidOperationException("Can't insert model with existing ID " + model.Id); } - DataMapper.Insert(model); + using (var conn = _database.OpenConnection()) + { + model = Insert(conn, null, model); + } ModelCreated(model); return model; } + private string GetInsertSql() + { + var sbColumnList = new StringBuilder(null); + for (var i = 0; i < _properties.Count; i++) + { + var property = _properties[i]; + sbColumnList.AppendFormat("\"{0}\"", property.Name); + if (i < _properties.Count - 1) + { + sbColumnList.Append(", "); + } + } + + var sbParameterList = new StringBuilder(null); + for (var i = 0; i < _properties.Count; i++) + { + var property = _properties[i]; + sbParameterList.AppendFormat("@{0}", property.Name); + if (i < _properties.Count - 1) + { + sbParameterList.Append(", "); + } + } + + return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id"; + } + + private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model) + { + var multi = connection.QueryMultiple(_insertSql, model, transaction); + var id = (int)multi.Read().First().id; + _keyProperty.SetValue(model, id); + + return model; + } + + public void InsertMany(IList models) + { + if (models.Any(x => x.Id != 0)) + { + throw new InvalidOperationException("Can't insert model with existing ID != 0"); + } + + using (var conn = _database.OpenConnection()) + { + using (IDbTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted)) + { + foreach (var model in models) + { + Insert(conn, tran, model); + } + + tran.Commit(); + } + } + } + public TModel Update(TModel model) { if (model.Id == 0) @@ -124,52 +232,59 @@ public TModel Update(TModel model) throw new InvalidOperationException("Can't update model with ID 0"); } - DataMapper.Update(model, c => c.Id == model.Id); + using (var conn = _database.OpenConnection()) + { + UpdateFields(conn, null, model, _properties); + } ModelUpdated(model); return model; } + public void UpdateMany(IList models) + { + if (models.Any(x => x.Id == 0)) + { + throw new InvalidOperationException("Can't update model with ID 0"); + } + + using (var conn = _database.OpenConnection()) + { + UpdateFields(conn, null, models, _properties); + } + } + + protected void Delete(Expression> where) + { + Delete(Builder().Where(where)); + } + + protected void Delete(SqlBuilder builder) + { + var sql = builder.AddTemplate(_deleteTemplate).LogQuery(); + + using (var conn = _database.OpenConnection()) + { + conn.Execute(sql.RawSql, sql.Parameters); + } + } + public void Delete(TModel model) { Delete(model.Id); } - public void InsertMany(IList models) + public void Delete(int id) { - using (var unitOfWork = new UnitOfWork(() => DataMapper)) - { - unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted); - - foreach (var model in models) - { - unitOfWork.DB.Insert(model); - } - - unitOfWork.Commit(); - } + Delete(x => x.Id == id); } - public void UpdateMany(IList models) + public void DeleteMany(IEnumerable ids) { - using (var unitOfWork = new UnitOfWork(() => DataMapper)) + if (ids.Any()) { - unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted); - - foreach (var model in models) - { - var localModel = model; - - if (model.Id == 0) - { - throw new InvalidOperationException("Can't update model with ID 0"); - } - - unitOfWork.DB.Update(model, c => c.Id == localModel.Id); - } - - unitOfWork.Commit(); + Delete(x => ids.Contains(x.Id)); } } @@ -190,31 +305,13 @@ public TModel Upsert(TModel model) return model; } - public void Delete(int id) - { - DataMapper.Delete(c => c.Id == id); - } - - public void DeleteMany(IEnumerable ids) - { - using (var unitOfWork = new UnitOfWork(() => DataMapper)) - { - unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted); - - foreach (var id in ids) - { - var localId = id; - - unitOfWork.DB.Delete(c => c.Id == localId); - } - - unitOfWork.Commit(); - } - } - public void Purge(bool vacuum = false) { - DataMapper.Delete(c => c.Id > -1); + using (var conn = _database.OpenConnection()) + { + conn.Execute($"DELETE FROM [{_table}]"); + } + if (vacuum) { Vacuum(); @@ -235,43 +332,117 @@ public void SetFields(TModel model, params Expression>[] pr { if (model.Id == 0) { - throw new InvalidOperationException("Attempted to updated model without ID"); + throw new InvalidOperationException("Attempted to update model without ID"); } - DataMapper.Update() - .Where(c => c.Id == model.Id) - .ColumnsIncluding(properties) - .Entity(model) - .Execute(); + var propertiesToUpdate = properties.Select(x => x.GetMemberName()).ToList(); + + using (var conn = _database.OpenConnection()) + { + UpdateFields(conn, null, model, propertiesToUpdate); + } ModelUpdated(model); } + public void SetFields(IList models, params Expression>[] properties) + { + if (models.Any(x => x.Id == 0)) + { + throw new InvalidOperationException("Attempted to update model without ID"); + } + + var propertiesToUpdate = properties.Select(x => x.GetMemberName()).ToList(); + + using (var conn = _database.OpenConnection()) + { + UpdateFields(conn, null, models, propertiesToUpdate); + } + + foreach (var model in models) + { + ModelUpdated(model); + } + } + + private string GetUpdateSql(List propertiesToUpdate) + { + var sb = new StringBuilder(); + sb.AppendFormat("update {0} set ", _table); + + for (var i = 0; i < propertiesToUpdate.Count; i++) + { + var property = propertiesToUpdate[i]; + sb.AppendFormat("\"{0}\" = @{1}", property.Name, property.Name); + if (i < propertiesToUpdate.Count - 1) + { + sb.Append(", "); + } + } + + sb.Append($" where \"{_keyProperty.Name}\" = @{_keyProperty.Name}"); + + return sb.ToString(); + } + + private void UpdateFields(IDbConnection connection, IDbTransaction transaction, TModel model, List propertiesToUpdate) + { + var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate); + + connection.Execute(sql, model, transaction: transaction); + } + + private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList models, List propertiesToUpdate) + { + var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate); + + connection.Execute(sql, models, transaction: transaction); + } + + protected virtual SqlBuilder PagedBuilder() => BuilderBase(); + protected virtual IEnumerable PagedSelector(SqlBuilder.Template sql) => GetResults(sql); + public virtual PagingSpec GetPaged(PagingSpec pagingSpec) { - pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList(); - pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount(); + pagingSpec.Records = GetPagedRecords(PagedBuilder().SelectAll(), pagingSpec, PagedSelector); + pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().SelectCount(), pagingSpec); return pagingSpec; } - protected virtual SortBuilder GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) + private void AddFilters(SqlBuilder builder, PagingSpec pagingSpec) { - var filterExpressions = pagingSpec.FilterExpressions; - var sortQuery = query.Where(filterExpressions.FirstOrDefault()); + var filters = pagingSpec.FilterExpressions; - if (filterExpressions.Count > 1) + foreach (var filter in filters) { - // Start at the second item for the AndWhere clauses - for (var i = 1; i < filterExpressions.Count; i++) - { - sortQuery.AndWhere(filterExpressions[i]); - } + builder.Where(filter); } + } - return sortQuery.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); + protected List GetPagedRecords(SqlBuilder builder, PagingSpec pagingSpec, Func> queryFunc) + { + AddFilters(builder, pagingSpec); + + var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC"; + var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize; + builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); + + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + + return queryFunc(sql).ToList(); + } + + protected int GetPagedRecordCount(SqlBuilder builder, PagingSpec pagingSpec) + { + AddFilters(builder, pagingSpec); + + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + + using (var conn = _database.OpenConnection()) + { + return conn.ExecuteScalar(sql.RawSql, sql.Parameters); + } } protected void ModelCreated(TModel model) @@ -297,11 +468,6 @@ private void PublishModelEvent(TModel model, ModelAction action) } } - protected virtual QueryBuilder AddJoinQueries(QueryBuilder baseQuery) - { - return baseQuery; - } - protected virtual bool PublishModelEvents => false; } } diff --git a/src/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs deleted file mode 100644 index bf68e751a..000000000 --- a/src/NzbDrone.Core/Datastore/Converters/BooleanIntConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; - -namespace NzbDrone.Core.Datastore.Converters -{ - public class BooleanIntConverter : IConverter - { - public object FromDB(ConverterContext context) - { - if (context.DbValue == DBNull.Value) - { - return DBNull.Value; - } - - var val = (long)context.DbValue; - - switch (val) - { - case 1: - return true; - case 0: - return false; - default: - throw new ConversionException(string.Format("The BooleanCharConverter could not convert the value '{0}' to a Boolean.", context.DbValue)); - } - } - - public object FromDB(ColumnMap map, object dbValue) - { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - var val = (bool?)clrValue; - - switch (val) - { - case true: - return 1; - case false: - return 0; - default: - return DBNull.Value; - } - } - - public Type DbType => typeof(int); - } -} diff --git a/src/NzbDrone.Core/Datastore/Converters/CommandConverter.cs b/src/NzbDrone.Core/Datastore/Converters/CommandConverter.cs index a6c641206..ad6e53545 100644 --- a/src/NzbDrone.Core/Datastore/Converters/CommandConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/CommandConverter.cs @@ -1,30 +1,28 @@ -using System; -using Marr.Data.Converters; +using System.Data; +using System.Text.Json; using NzbDrone.Common.Extensions; using NzbDrone.Common.Reflection; -using NzbDrone.Common.Serializer; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Datastore.Converters { - public class CommandConverter : EmbeddedDocumentConverter + public class CommandConverter : EmbeddedDocumentConverter { - public override object FromDB(ConverterContext context) + public override Command Parse(object value) { - if (context.DbValue == DBNull.Value) - { - return null; - } - - var stringValue = (string)context.DbValue; + var stringValue = (string)value; if (stringValue.IsNullOrWhiteSpace()) { return null; } - var ordinal = context.DataRecord.GetOrdinal("Name"); - var contract = context.DataRecord.GetString(ordinal); + string contract; + using (JsonDocument body = JsonDocument.Parse(stringValue)) + { + contract = body.RootElement.GetProperty("name").GetString(); + } + var impType = typeof(Command).Assembly.FindTypeByName(contract + "Command"); if (impType == null) @@ -32,7 +30,12 @@ public override object FromDB(ConverterContext context) throw new CommandNotFoundException(contract); } - return Json.Deserialize(stringValue, impType); + return (Command)JsonSerializer.Deserialize(stringValue, impType, SerializerSettings); + } + + public override void SetValue(IDbDataParameter parameter, Command value) + { + parameter.Value = value == null ? null : JsonSerializer.Serialize(value, SerializerSettings); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs index f6dfc810d..69db3e979 100644 --- a/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/CustomFormatIntConverter.cs @@ -1,92 +1,57 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Newtonsoft.Json; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; +using NzbDrone.Common.Serializer; using NzbDrone.Core.CustomFormats; namespace NzbDrone.Core.Datastore.Converters { - public class CustomFormatIntConverter : JsonConverter, IConverter + public class DapperCustomFormatIntConverter : SqlMapper.TypeHandler { - //TODO think of something better. - public object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, CustomFormat value) { - if (context.DbValue == DBNull.Value) + parameter.Value = value.Id; + } + + public override CustomFormat Parse(object value) + { + Console.WriteLine(value.ToJson()); + + if (value is DBNull) { return null; } - var val = Convert.ToInt32(context.DbValue); + var val = Convert.ToInt32(value); if (val == 0) { return CustomFormat.None; } - if (CustomFormatService.AllCustomFormats == null) - { - throw new Exception("***FATAL*** WE TRIED ACCESSING ALL CUSTOM FORMATS BEFORE IT WAS INITIALIZED. PLEASE SAVE THIS LOG AND OPEN AN ISSUE ON GITHUB."); - } - return CustomFormatService.AllCustomFormats[val]; } + } - public object FromDB(ColumnMap map, object dbValue) + public class CustomFormatIntConverter : JsonConverter + { + public override CustomFormat Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - if (clrValue == DBNull.Value) - { - return null; - } - - if (!(clrValue is CustomFormat)) - { - throw new InvalidOperationException("Attempted to save a quality definition that isn't really a quality definition"); - } - - var quality = (CustomFormat)clrValue; - - if (CustomFormatService.AllCustomFormats?.ContainsKey(quality.Id) == false) - { - //throw new Exception("Attempted to save an unknown custom format! Make sure you do not have stale custom formats lying around!"); - } - - return quality.Id; - } - - public Type DbType => typeof(int); - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(CustomFormat); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var item = reader.Value; - - var val = Convert.ToInt32(item); + var val = reader.GetInt32(); if (val == 0) { return CustomFormat.None; } - if (CustomFormatService.AllCustomFormats == null) - { - throw new Exception("***FATAL*** WE TRIED ACCESSING ALL CUSTOM FORMATS BEFORE IT WAS INITIALIZED. PLEASE SAVE THIS LOG AND OPEN AN ISSUE ON GITHUB."); - } - return CustomFormatService.AllCustomFormats[val]; } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, CustomFormat value, JsonSerializerOptions options) { - writer.WriteValue(ToDB(value)); + writer.WriteNumberValue(value.Id); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs b/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs deleted file mode 100644 index 82f50e326..000000000 --- a/src/NzbDrone.Core/Datastore/Converters/DoubleConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; - -namespace NzbDrone.Core.Datastore.Converters -{ - public class DoubleConverter : IConverter - { - public object FromDB(ConverterContext context) - { - if (context.DbValue == DBNull.Value) - { - return DBNull.Value; - } - - if (context.DbValue is double) - { - return context.DbValue; - } - - return Convert.ToDouble(context.DbValue); - } - - public object FromDB(ColumnMap map, object dbValue) - { - if (dbValue == DBNull.Value) - { - return DBNull.Value; - } - - if (dbValue is double) - { - return dbValue; - } - - return Convert.ToDouble(dbValue); - } - - public object ToDB(object clrValue) - { - return clrValue; - } - - public Type DbType { get; private set; } - } -} diff --git a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs index 045ba64e2..3bbb0835d 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -1,73 +1,50 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; namespace NzbDrone.Core.Datastore.Converters { - public class EmbeddedDocumentConverter : IConverter + public class EmbeddedDocumentConverter : SqlMapper.TypeHandler { - private readonly JsonSerializerSettings _serializerSetting; + protected readonly JsonSerializerOptions SerializerSettings; - public EmbeddedDocumentConverter(params JsonConverter[] converters) + public EmbeddedDocumentConverter() { - _serializerSetting = new JsonSerializerSettings + var serializerSettings = new JsonSerializerOptions { - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - NullValueHandling = NullValueHandling.Ignore, - Formatting = Formatting.Indented, - DefaultValueHandling = DefaultValueHandling.Include, - ContractResolver = new CamelCasePropertyNamesContractResolver() + AllowTrailingCommas = true, + IgnoreNullValues = false, + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true }; - _serializerSetting.Converters.Add(new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() }); - _serializerSetting.Converters.Add(new VersionConverter()); + serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)); + serializerSettings.Converters.Add(new TimeSpanConverter()); + serializerSettings.Converters.Add(new UtcConverter()); + SerializerSettings = serializerSettings; + } + + public EmbeddedDocumentConverter(params JsonConverter[] converters) + : this() + { foreach (var converter in converters) { - _serializerSetting.Converters.Add(converter); + SerializerSettings.Converters.Add(converter); } } - public virtual object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, T value) { - if (context.DbValue == DBNull.Value) - { - return DBNull.Value; - } - - var stringValue = (string)context.DbValue; - - if (string.IsNullOrWhiteSpace(stringValue)) - { - return null; - } - - return JsonConvert.DeserializeObject(stringValue, context.ColumnMap.FieldType, _serializerSetting); + parameter.Value = JsonSerializer.Serialize(value, SerializerSettings); } - public object FromDB(ColumnMap map, object dbValue) + public override T Parse(object value) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + return JsonSerializer.Deserialize((string)value, SerializerSettings); } - - public object ToDB(object clrValue) - { - if (clrValue == null) - { - return null; - } - - if (clrValue == DBNull.Value) - { - return DBNull.Value; - } - - return JsonConvert.SerializeObject(clrValue, _serializerSetting); - } - - public Type DbType => typeof(string); } } diff --git a/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs deleted file mode 100644 index ae1e8c46d..000000000 --- a/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; - -namespace NzbDrone.Core.Datastore.Converters -{ - public class EnumIntConverter : IConverter - { - public Type DbType => typeof(int); - - public object FromDB(ConverterContext context) - { - if (context.DbValue != null && context.DbValue != DBNull.Value) - { - return Enum.ToObject(context.ColumnMap.FieldType, (long)context.DbValue); - } - - return null; - } - - public object FromDB(ColumnMap map, object dbValue) - { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - if (clrValue != null) - { - return (int)clrValue; - } - - return DBNull.Value; - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs b/src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs index 1ad387513..256ab6502 100644 --- a/src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/GuidConverter.cs @@ -1,40 +1,24 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; +using System.Data; +using Dapper; namespace NzbDrone.Core.Datastore.Converters { - public class GuidConverter : IConverter + public class GuidConverter : SqlMapper.TypeHandler { - public object FromDB(ConverterContext context) + public override Guid Parse(object value) { - if (context.DbValue == DBNull.Value) + if (value == null) { return Guid.Empty; } - var value = (string)context.DbValue; - - return new Guid(value); + return new Guid((string)value); } - public object FromDB(ColumnMap map, object dbValue) + public override void SetValue(IDbDataParameter parameter, Guid value) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + parameter.Value = value.ToString(); } - - public object ToDB(object clrValue) - { - if (clrValue == null) - { - return DBNull.Value; - } - - var value = clrValue; - - return value.ToString(); - } - - public Type DbType => typeof(string); } } diff --git a/src/NzbDrone.Core/Datastore/Converters/Int32Converter.cs b/src/NzbDrone.Core/Datastore/Converters/Int32Converter.cs deleted file mode 100644 index 29aae4c00..000000000 --- a/src/NzbDrone.Core/Datastore/Converters/Int32Converter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; - -namespace NzbDrone.Core.Datastore.Converters -{ - public class Int32Converter : IConverter - { - public object FromDB(ConverterContext context) - { - if (context.DbValue == DBNull.Value) - { - return DBNull.Value; - } - - if (context.DbValue is int) - { - return context.DbValue; - } - - return Convert.ToInt32(context.DbValue); - } - - public object FromDB(ColumnMap map, object dbValue) - { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - return clrValue; - } - - public Type DbType { get; private set; } - } -} diff --git a/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs index f0c4d705b..b7981367f 100644 --- a/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/LanguageIntConverter.cs @@ -1,68 +1,48 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Newtonsoft.Json; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; using NzbDrone.Core.Languages; namespace NzbDrone.Core.Datastore.Converters { - public class LanguageIntConverter : JsonConverter, IConverter + public class DapperLanguageIntConverter : SqlMapper.TypeHandler { - public object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, Language value) { - if (context.DbValue == DBNull.Value) + if (value == null) + { + throw new InvalidOperationException("Attempted to save a language that isn't really a language"); + } + else + { + parameter.Value = (int)value; + } + } + + public override Language Parse(object value) + { + if (value == null || value is DBNull) { return Language.Unknown; } - var val = Convert.ToInt32(context.DbValue); + return (Language)Convert.ToInt32(value); + } + } - return (Language)val; + public class LanguageIntConverter : JsonConverter + { + public override Language Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var item = reader.GetInt32(); + return (Language)item; } - public object FromDB(ColumnMap map, object dbValue) + public override void Write(Utf8JsonWriter writer, Language value, JsonSerializerOptions options) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - if (clrValue == DBNull.Value) - { - return 0; - } - - if (clrValue as Language == null) - { - throw new InvalidOperationException("Attempted to save a language that isn't really a language"); - } - - var language = clrValue as Language; - return (int)language; - } - - public Type DbType - { - get - { - return typeof(int); - } - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Language); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var item = reader.Value; - return (Language)Convert.ToInt32(item); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(ToDB(value)); + writer.WriteNumberValue((int)value); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs b/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs index ba2b239fd..068c421fc 100644 --- a/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/OsPathConverter.cs @@ -1,36 +1,25 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; +using System.Data; +using Dapper; using NzbDrone.Common.Disk; namespace NzbDrone.Core.Datastore.Converters { - public class OsPathConverter : IConverter + public class OsPathConverter : SqlMapper.TypeHandler { - public object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, OsPath value) { - if (context.DbValue == DBNull.Value) + parameter.Value = value.FullPath; + } + + public override OsPath Parse(object value) + { + if (value == null || value is DBNull) { - return DBNull.Value; + return new OsPath(null); } - var value = (string)context.DbValue; - - return new OsPath(value); + return new OsPath((string)value); } - - public object FromDB(ColumnMap map, object dbValue) - { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - var value = (OsPath)clrValue; - - return value.FullPath; - } - - public Type DbType => typeof(string); } } diff --git a/src/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs b/src/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs index 17590c4f1..99c9bef1d 100644 --- a/src/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/ProviderSettingConverter.cs @@ -1,38 +1,22 @@ -using System; -using Marr.Data.Converters; -using NzbDrone.Common.Reflection; -using NzbDrone.Common.Serializer; +using System.Data; +using System.Text.Json; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Datastore.Converters { - public class ProviderSettingConverter : EmbeddedDocumentConverter + public class ProviderSettingConverter : EmbeddedDocumentConverter { - public override object FromDB(ConverterContext context) + public override IProviderConfig Parse(object value) { - if (context.DbValue == DBNull.Value) - { - return NullConfig.Instance; - } + // We can't deserialize based on another column, happens in ProviderRepository instead + return null; + } - var stringValue = (string)context.DbValue; - - if (string.IsNullOrWhiteSpace(stringValue)) - { - return NullConfig.Instance; - } - - var ordinal = context.DataRecord.GetOrdinal("ConfigContract"); - var contract = context.DataRecord.GetString(ordinal); - - var impType = typeof(IProviderConfig).Assembly.FindTypeByName(contract); - - if (impType == null) - { - throw new ConfigContractNotFoundException(contract); - } - - return Json.Deserialize(stringValue, impType); + public override void SetValue(IDbDataParameter parameter, IProviderConfig value) + { + // Cast to object to get all properties written out + // https://github.com/dotnet/corefx/issues/38650 + parameter.Value = JsonSerializer.Serialize((object)value, SerializerSettings); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs index 8487780de..13b595d35 100644 --- a/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/QualityIntConverter.cs @@ -1,62 +1,36 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Newtonsoft.Json; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Datastore.Converters { - public class QualityIntConverter : JsonConverter, IConverter + public class QualityIntConverter : JsonConverter { - public object FromDB(ConverterContext context) + public override Quality Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (context.DbValue == DBNull.Value) - { - return Quality.Unknown; - } - - var val = Convert.ToInt32(context.DbValue); - - return (Quality)val; + var item = reader.GetInt32(); + return (Quality)item; } - public object FromDB(ColumnMap map, object dbValue) + public override void Write(Utf8JsonWriter writer, Quality value, JsonSerializerOptions options) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + writer.WriteNumberValue((int)value); + } + } + + public class DapperQualityIntConverter : SqlMapper.TypeHandler + { + public override void SetValue(IDbDataParameter parameter, Quality value) + { + parameter.Value = value == null ? 0 : (int)value; } - public object ToDB(object clrValue) + public override Quality Parse(object value) { - if (clrValue == DBNull.Value) - { - return 0; - } - - if (clrValue as Quality == null) - { - throw new InvalidOperationException("Attempted to save a quality that isn't really a quality"); - } - - var quality = clrValue as Quality; - return (int)quality; - } - - public Type DbType => typeof(int); - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Quality); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var item = reader.Value; - return (Quality)Convert.ToInt32(item); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(ToDB(value)); + return (Quality)Convert.ToInt32(value); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/QualityTagStringConverter.cs b/src/NzbDrone.Core/Datastore/Converters/QualityTagStringConverter.cs index ea2e8ea72..6c4f99bd7 100644 --- a/src/NzbDrone.Core/Datastore/Converters/QualityTagStringConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/QualityTagStringConverter.cs @@ -1,62 +1,41 @@ using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using Newtonsoft.Json; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; using NzbDrone.Core.CustomFormats; namespace NzbDrone.Core.Datastore.Converters { - public class QualityTagStringConverter : JsonConverter, IConverter + public class DapperQualityTagStringConverter : SqlMapper.TypeHandler { - public object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, FormatTag value) { - if (context.DbValue == DBNull.Value) + parameter.Value = value.Raw; + } + + public override FormatTag Parse(object value) + { + if (value == null || value is DBNull) { return new FormatTag(""); //Will throw argument exception! } - var val = Convert.ToString(context.DbValue); - - return new FormatTag(val); + return new FormatTag(Convert.ToString(value)); } + } - public object FromDB(ColumnMap map, object dbValue) + public class QualityTagStringConverter : JsonConverter + { + public override FormatTag Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); - } - - public object ToDB(object clrValue) - { - if (clrValue == DBNull.Value) - { - return 0; - } - - if (!(clrValue is FormatTag)) - { - throw new InvalidOperationException("Attempted to save a quality tag that isn't really a quality tag"); - } - - var quality = (FormatTag)clrValue; - return quality.Raw; - } - - public Type DbType => typeof(string); - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(FormatTag); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var item = reader.Value; + var item = reader.GetString(); return new FormatTag(Convert.ToString(item)); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, FormatTag value, JsonSerializerOptions options) { - writer.WriteValue(ToDB(value)); + writer.WriteStringValue(value.Raw); } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs b/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs index c94551055..07f2d9314 100644 --- a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs @@ -1,43 +1,19 @@ using System; -using System.Globalization; -using Marr.Data.Converters; -using Marr.Data.Mapping; -using NzbDrone.Common.Extensions; +using System.Text.Json; +using System.Text.Json.Serialization; namespace NzbDrone.Core.Datastore.Converters { - public class TimeSpanConverter : IConverter + public class TimeSpanConverter : JsonConverter { - public object FromDB(ConverterContext context) + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (context.DbValue == DBNull.Value) - { - return TimeSpan.Zero; - } - - if (context.DbValue is TimeSpan) - { - return context.DbValue; - } - - return TimeSpan.Parse(context.DbValue.ToString(), CultureInfo.InvariantCulture); + return TimeSpan.Parse(reader.GetString()); } - public object FromDB(ColumnMap map, object dbValue) + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + writer.WriteStringValue(value.ToString()); } - - public object ToDB(object clrValue) - { - if (clrValue.ToString().IsNullOrWhiteSpace()) - { - return null; - } - - return ((TimeSpan)clrValue).ToString("c", CultureInfo.InvariantCulture); - } - - public Type DbType { get; private set; } } } diff --git a/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs b/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs index 3fd28d8a8..db70f2359 100644 --- a/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs @@ -1,32 +1,34 @@ -using System; -using Marr.Data.Converters; -using Marr.Data.Mapping; +using System; +using System.Data; +using System.Text.Json; +using System.Text.Json.Serialization; +using Dapper; namespace NzbDrone.Core.Datastore.Converters { - public class UtcConverter : IConverter + public class DapperUtcConverter : SqlMapper.TypeHandler { - public object FromDB(ConverterContext context) + public override void SetValue(IDbDataParameter parameter, DateTime value) { - return context.DbValue; + parameter.Value = value.ToUniversalTime(); } - public object FromDB(ColumnMap map, object dbValue) + public override DateTime Parse(object value) { - return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue }); + return DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc); + } + } + + public class UtcConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()); } - public object ToDB(object clrValue) + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { - if (clrValue == DBNull.Value) - { - return clrValue; - } - - var dateTime = (DateTime)clrValue; - return dateTime.ToUniversalTime(); + writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")); } - - public Type DbType => typeof(DateTime); } } diff --git a/src/NzbDrone.Core/Datastore/Database.cs b/src/NzbDrone.Core/Datastore/Database.cs index 047cac773..a9b6e807f 100644 --- a/src/NzbDrone.Core/Datastore/Database.cs +++ b/src/NzbDrone.Core/Datastore/Database.cs @@ -1,5 +1,6 @@ using System; -using Marr.Data; +using System.Data; +using Dapper; using NLog; using NzbDrone.Common.Instrumentation; @@ -7,7 +8,7 @@ namespace NzbDrone.Core.Datastore { public interface IDatabase { - IDataMapper GetDataMapper(); + IDbConnection OpenConnection(); Version Version { get; } int Migration { get; } void Vacuum(); @@ -16,17 +17,17 @@ public interface IDatabase public class Database : IDatabase { private readonly string _databaseName; - private readonly Func _datamapperFactory; + private readonly Func _datamapperFactory; private readonly Logger _logger = NzbDroneLogger.GetLogger(typeof(Database)); - public Database(string databaseName, Func datamapperFactory) + public Database(string databaseName, Func datamapperFactory) { _databaseName = databaseName; _datamapperFactory = datamapperFactory; } - public IDataMapper GetDataMapper() + public IDbConnection OpenConnection() { return _datamapperFactory(); } @@ -37,7 +38,7 @@ public Version Version { using (var db = _datamapperFactory()) { - var version = db.ExecuteScalar("SELECT sqlite_version()").ToString(); + var version = db.QueryFirstOrDefault("SELECT sqlite_version()"); return new Version(version); } } @@ -47,9 +48,10 @@ public int Migration { get { - var migration = _datamapperFactory() - .ExecuteScalar("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1").ToString(); - return Convert.ToInt32(migration); + using (var db = _datamapperFactory()) + { + return db.QueryFirstOrDefault("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1"); + } } } @@ -60,7 +62,7 @@ public void Vacuum() _logger.Info("Vacuuming {0} database", _databaseName); using (var db = _datamapperFactory()) { - db.ExecuteNonQuery("Vacuum;"); + db.Execute("Vacuum;"); } _logger.Info("{0} database compressed", _databaseName); diff --git a/src/NzbDrone.Core/Datastore/DbFactory.cs b/src/NzbDrone.Core/Datastore/DbFactory.cs index 1aeb21ff8..51a441f70 100644 --- a/src/NzbDrone.Core/Datastore/DbFactory.cs +++ b/src/NzbDrone.Core/Datastore/DbFactory.cs @@ -1,7 +1,5 @@ using System; using System.Data.SQLite; -using Marr.Data; -using Marr.Data.Reflection; using NLog; using NzbDrone.Common.Composition; using NzbDrone.Common.Disk; @@ -30,7 +28,6 @@ static DbFactory() { InitializeEnvironment(); - MapRepository.Instance.ReflectionStrategy = new SimpleReflectionStrategy(); TableMapping.Map(); } @@ -99,12 +96,11 @@ public IDatabase Create(MigrationContext migrationContext) var db = new Database(migrationContext.MigrationType.ToString(), () => { - var dataMapper = new DataMapper(SQLiteFactory.Instance, connectionString) - { - SqlMode = SqlModes.Text, - }; + var conn = SQLiteFactory.Instance.CreateConnection(); + conn.ConnectionString = connectionString; + conn.Open(); - return dataMapper; + return conn; }); return db; diff --git a/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs b/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs new file mode 100644 index 000000000..bcb977af1 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/ExpressionVisitor.cs @@ -0,0 +1,148 @@ +/* This class was copied from Mehfuz's LinqExtender project, which is available from github. + * http://mehfuzh.github.com/LinqExtender/ + */ + +using System; +using System.Linq.Expressions; + +namespace NzbDrone.Core.Datastore +{ + /// + /// Expression visitor + /// + public class ExpressionVisitor + { + /// + /// Visits expression and delegates call to different to branch. + /// + /// + /// + protected virtual Expression Visit(Expression expression) + { + if (expression == null) + { + return null; + } + + switch (expression.NodeType) + { + case ExpressionType.Lambda: + return VisitLamda((LambdaExpression)expression); + case ExpressionType.ArrayLength: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + case ExpressionType.Negate: + case ExpressionType.UnaryPlus: + case ExpressionType.NegateChecked: + case ExpressionType.Not: + case ExpressionType.Quote: + case ExpressionType.TypeAs: + return VisitUnary((UnaryExpression)expression); + case ExpressionType.Add: + case ExpressionType.AddChecked: + case ExpressionType.And: + case ExpressionType.AndAlso: + case ExpressionType.ArrayIndex: + case ExpressionType.Coalesce: + case ExpressionType.Divide: + case ExpressionType.Equal: + case ExpressionType.ExclusiveOr: + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.LeftShift: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.Modulo: + case ExpressionType.Multiply: + case ExpressionType.MultiplyChecked: + case ExpressionType.NotEqual: + case ExpressionType.Or: + case ExpressionType.OrElse: + case ExpressionType.Power: + case ExpressionType.RightShift: + case ExpressionType.Subtract: + case ExpressionType.SubtractChecked: + return VisitBinary((BinaryExpression)expression); + case ExpressionType.Call: + return VisitMethodCall((MethodCallExpression)expression); + case ExpressionType.Constant: + return VisitConstant((ConstantExpression)expression); + case ExpressionType.MemberAccess: + return VisitMemberAccess((MemberExpression)expression); + case ExpressionType.Parameter: + return VisitParameter((ParameterExpression)expression); + } + + throw new ArgumentOutOfRangeException("expression", expression.NodeType.ToString()); + } + + /// + /// Visits the constance expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitConstant(ConstantExpression expression) + { + return expression; + } + + /// + /// Visits the memeber access expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitMemberAccess(MemberExpression expression) + { + return expression; + } + + /// + /// Visits the method call expression. To be implemented by user. + /// + /// + /// + protected virtual Expression VisitMethodCall(MethodCallExpression expression) + { + throw new NotImplementedException(); + } + + /// + /// Visits the binary expression. + /// + /// + /// + protected virtual Expression VisitBinary(BinaryExpression expression) + { + Visit(expression.Left); + Visit(expression.Right); + return expression; + } + + /// + /// Visits the unary expression. + /// + /// + /// + protected virtual Expression VisitUnary(UnaryExpression expression) + { + Visit(expression.Operand); + return expression; + } + + /// + /// Visits the lamda expression. + /// + /// + /// + protected virtual Expression VisitLamda(LambdaExpression lambdaExpression) + { + Visit(lambdaExpression.Body); + return lambdaExpression; + } + + private Expression VisitParameter(ParameterExpression expression) + { + return expression; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs new file mode 100644 index 000000000..8136ff427 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Dapper; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Core.Datastore +{ + public static class SqlBuilderExtensions + { + public static bool LogSql { get; set; } + private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SqlBuilderExtensions)); + + public static SqlBuilder SelectAll(this SqlBuilder builder) + { + return builder.Select("*"); + } + + public static SqlBuilder SelectCount(this SqlBuilder builder) + { + return builder.Select("COUNT(*)"); + } + + public static SqlBuilder Where(this SqlBuilder builder, Expression> filter) + { + var wb = new WhereBuilder(filter, true); + + return builder.Where(wb.ToString(), wb.Parameters); + } + + public static SqlBuilder OrWhere(this SqlBuilder builder, Expression> filter) + { + var wb = new WhereBuilder(filter, true); + + return builder.OrWhere(wb.ToString(), wb.Parameters); + } + + public static SqlBuilder Join(this SqlBuilder builder, Expression> filter) + { + var wb = new WhereBuilder(filter, false); + + var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); + + return builder.Join($"{rightTable} ON {wb.ToString()}"); + } + + public static SqlBuilder LeftJoin(this SqlBuilder builder, Expression> filter) + { + var wb = new WhereBuilder(filter, false); + + var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); + + return builder.LeftJoin($"{rightTable} ON {wb.ToString()}"); + } + + public static SqlBuilder.Template LogQuery(this SqlBuilder.Template template) + { + if (LogSql) + { + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine("==== Begin Query Trace ===="); + sb.AppendLine(); + sb.AppendLine("QUERY TEXT:"); + sb.AppendLine(template.RawSql); + sb.AppendLine(); + sb.AppendLine("PARAMETERS:"); + foreach (var p in ((DynamicParameters)template.Parameters).ToDictionary()) + { + object val = (p.Value is string) ? string.Format("\"{0}\"", p.Value) : p.Value; + sb.AppendFormat("{0} = [{1}]", p.Key, val.ToJson() ?? "NULL").AppendLine(); + } + + sb.AppendLine(); + sb.AppendLine("==== End Query Trace ===="); + sb.AppendLine(); + + Logger.Trace(sb.ToString()); + } + + return template; + } + + private static Dictionary ToDictionary(this DynamicParameters dynamicParams) + { + var argsDictionary = new Dictionary(); + var iLookup = (SqlMapper.IParameterLookup)dynamicParams; + + foreach (var paramName in dynamicParams.ParameterNames) + { + var value = iLookup[paramName]; + argsDictionary.Add(paramName, value); + } + + var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance); + if (templates != null) + { + var list = templates.GetValue(dynamicParams) as List; + if (list != null) + { + foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList())) + { + objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value)); + } + } + } + + return argsDictionary; + } + + private static Dictionary GetPropertyValuePairs(this object obj, string[] hidden = null) + { + var type = obj.GetType(); + var pairs = hidden == null + ? type.GetProperties() + .DistinctBy(propertyInfo => propertyInfo.Name) + .ToDictionary( + propertyInfo => propertyInfo.Name, + propertyInfo => propertyInfo.GetValue(obj, null)) + : type.GetProperties() + .Where(it => !hidden.Contains(it.Name)) + .DistinctBy(propertyInfo => propertyInfo.Name) + .ToDictionary( + propertyInfo => propertyInfo.Name, + propertyInfo => propertyInfo.GetValue(obj, null)); + return pairs; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs deleted file mode 100644 index 96cdd9c0d..000000000 --- a/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Reflection; -using Marr.Data; -using Marr.Data.Mapping; -using NzbDrone.Common.Reflection; -using NzbDrone.Core.ThingiProvider; - -namespace NzbDrone.Core.Datastore.Extensions -{ - public static class MappingExtensions - { - public static ColumnMapBuilder MapResultSet(this FluentMappings.MappingsFluentEntity mapBuilder) - where T : ResultSet, new() - { - return mapBuilder - .Columns - .AutoMapPropertiesWhere(IsMappableProperty); - } - - public static ColumnMapBuilder RegisterDefinition(this FluentMappings.MappingsFluentEntity mapBuilder, string tableName = null) - where T : ProviderDefinition, new() - { - return RegisterModel(mapBuilder, tableName).Ignore(c => c.ImplementationName); - } - - public static ColumnMapBuilder RegisterModel(this FluentMappings.MappingsFluentEntity mapBuilder, string tableName = null) - where T : ModelBase, new() - { - return mapBuilder.Table.MapTable(tableName) - .Columns - .AutoMapPropertiesWhere(IsMappableProperty) - .PrefixAltNames(string.Format("{0}_", typeof(T).Name)) - .For(c => c.Id) - .SetPrimaryKey() - .SetReturnValue() - .SetAutoIncrement(); - } - - public static RelationshipBuilder AutoMapChildModels(this ColumnMapBuilder mapBuilder) - { - return mapBuilder.Relationships.AutoMapPropertiesWhere(m => - m.MemberType == MemberTypes.Property && - typeof(ModelBase).IsAssignableFrom(((PropertyInfo)m).PropertyType)); - } - - public static bool IsMappableProperty(MemberInfo memberInfo) - { - var propertyInfo = memberInfo as PropertyInfo; - - if (propertyInfo == null) - { - return false; - } - - if (!propertyInfo.IsReadable() || !propertyInfo.IsWritable()) - { - return false; - } - - if (propertyInfo.PropertyType.IsSimpleType() || MapRepository.Instance.TypeConverters.ContainsKey(propertyInfo.PropertyType)) - { - return true; - } - - return false; - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Extensions/PagingSpecExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/PagingSpecExtensions.cs deleted file mode 100644 index 46d217585..000000000 --- a/src/NzbDrone.Core/Datastore/Extensions/PagingSpecExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; - -namespace NzbDrone.Core.Datastore.Extensions -{ - public static class PagingSpecExtensions - { - public static Expression> OrderByClause(this PagingSpec pagingSpec) - { - return CreateExpression(pagingSpec.SortKey); - } - - public static int PagingOffset(this PagingSpec pagingSpec) - { - return (pagingSpec.Page - 1) * pagingSpec.PageSize; - } - - public static Marr.Data.QGen.SortDirection ToSortDirection(this PagingSpec pagingSpec) - { - if (pagingSpec.SortDirection == SortDirection.Descending) - { - return Marr.Data.QGen.SortDirection.Desc; - } - - return Marr.Data.QGen.SortDirection.Asc; - } - - private static Expression> CreateExpression(string propertyName) - { - Type type = typeof(TModel); - ParameterExpression parameterExpression = Expression.Parameter(type, "x"); - Expression expressionBody = parameterExpression; - - var splitPropertyName = propertyName.Split('.').ToList(); - - foreach (var property in splitPropertyName) - { - expressionBody = Expression.Property(expressionBody, property); - } - - expressionBody = Expression.Convert(expressionBody, typeof(object)); - return Expression.Lambda>(expressionBody, parameterExpression); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs deleted file mode 100644 index 490bb49b4..000000000 --- a/src/NzbDrone.Core/Datastore/Extensions/RelationshipExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using Marr.Data; -using Marr.Data.Mapping; - -namespace NzbDrone.Core.Datastore.Extensions -{ - public static class RelationshipExtensions - { - public static RelationshipBuilder HasOne(this RelationshipBuilder relationshipBuilder, Expression>> portalExpression, Func childIdSelector) - where TParent : ModelBase - where TChild : ModelBase - { - return relationshipBuilder.For(portalExpression.GetMemberName()) - .LazyLoad( - condition: parent => childIdSelector(parent) > 0, - query: (db, parent) => - { - var id = childIdSelector(parent); - return db.Query().Where(c => c.Id == id).SingleOrDefault(); - }); - } - - public static RelationshipBuilder Relationship(this ColumnMapBuilder mapBuilder) - { - return mapBuilder.Relationships.AutoMapComplexTypeProperties(); - } - - public static RelationshipBuilder HasMany(this RelationshipBuilder relationshipBuilder, Expression>> portalExpression, Func parentIdSelector) - where TParent : ModelBase - where TChild : ModelBase - { - return relationshipBuilder.For(portalExpression.GetMemberName()) - .LazyLoad((db, parent) => db.Query().Where(c => parentIdSelector(c) == parent.Id).ToList()); - } - - private static string GetMemberName(this Expression> member) - { - var expression = member.Body as MemberExpression; - - if (expression == null) - { - expression = (MemberExpression)((UnaryExpression)member.Body).Operand; - } - - return expression.Member.Name; - } - } -} diff --git a/src/NzbDrone.Core/Datastore/LazyList.cs b/src/NzbDrone.Core/Datastore/LazyList.cs deleted file mode 100644 index 193a11812..000000000 --- a/src/NzbDrone.Core/Datastore/LazyList.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Marr.Data; - -namespace NzbDrone.Core.Datastore -{ - public class LazyList : LazyLoaded> - { - public LazyList() - : this(new List()) - { - } - - public LazyList(IEnumerable items) - : base(new List(items)) - { - } - - public static implicit operator LazyList(List val) - { - return new LazyList(val); - } - - public static implicit operator List(LazyList lazy) - { - return lazy.Value; - } - } -} diff --git a/src/NzbDrone.Core/Datastore/LogDatabase.cs b/src/NzbDrone.Core/Datastore/LogDatabase.cs index 6608a144f..f992c8bbe 100644 --- a/src/NzbDrone.Core/Datastore/LogDatabase.cs +++ b/src/NzbDrone.Core/Datastore/LogDatabase.cs @@ -1,5 +1,5 @@ using System; -using Marr.Data; +using System.Data; namespace NzbDrone.Core.Datastore { @@ -16,9 +16,9 @@ public LogDatabase(IDatabase database) _database = database; } - public IDataMapper GetDataMapper() + public IDbConnection OpenConnection() { - return _database.GetDataMapper(); + return _database.OpenConnection(); } public Version Version => _database.Version; diff --git a/src/NzbDrone.Core/Datastore/MainDatabase.cs b/src/NzbDrone.Core/Datastore/MainDatabase.cs index 5faf4497c..4a9d3298c 100644 --- a/src/NzbDrone.Core/Datastore/MainDatabase.cs +++ b/src/NzbDrone.Core/Datastore/MainDatabase.cs @@ -1,5 +1,5 @@ using System; -using Marr.Data; +using System.Data; namespace NzbDrone.Core.Datastore { @@ -16,9 +16,9 @@ public MainDatabase(IDatabase database) _database = database; } - public IDataMapper GetDataMapper() + public IDbConnection OpenConnection() { - return _database.GetDataMapper(); + return _database.OpenConnection(); } public Version Version => _database.Version; diff --git a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs index f7d3cf386..e960bb9b8 100644 --- a/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs +++ b/src/NzbDrone.Core/Datastore/Migration/036_update_with_quality_converters.cs @@ -26,7 +26,7 @@ protected override void MainDbUpgrade() private void ConvertQualityProfiles(IDbConnection conn, IDbTransaction tran) { - var qualityProfileItemConverter = new EmbeddedDocumentConverter(new QualityIntConverter()); + var qualityProfileItemConverter = new EmbeddedDocumentConverter>(new QualityIntConverter()); // Convert 'Allowed' column in QualityProfiles from Json List to Json List (int = Quality) using (IDbCommand qualityProfileCmd = conn.CreateCommand()) @@ -44,13 +44,12 @@ private void ConvertQualityProfiles(IDbConnection conn, IDbTransaction tran) var items = Quality.DefaultQualityDefinitions.OrderBy(v => v.Weight).Select(v => new ProfileQualityItem { Quality = v.Quality, Allowed = allowed.Contains(v.Quality) }).ToList(); - var allowedNewJson = qualityProfileItemConverter.ToDB(items); - using (IDbCommand updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; updateCmd.CommandText = "UPDATE QualityProfiles SET Items = ? WHERE Id = ?"; - updateCmd.AddParameter(allowedNewJson); + var param = updateCmd.CreateParameter(); + qualityProfileItemConverter.SetValue(param, items); updateCmd.AddParameter(id); updateCmd.ExecuteNonQuery(); @@ -70,7 +69,7 @@ private void ConvertQualityModels(IDbConnection conn, IDbTransaction tran) private void ConvertQualityModel(IDbConnection conn, IDbTransaction tran, string tableName) { - var qualityModelConverter = new EmbeddedDocumentConverter(new QualityIntConverter()); + var qualityModelConverter = new EmbeddedDocumentConverter(new QualityIntConverter()); using (IDbCommand qualityModelCmd = conn.CreateCommand()) { @@ -89,17 +88,18 @@ private void ConvertQualityModel(IDbConnection conn, IDbTransaction tran, string continue; } - var qualityNewJson = qualityModelConverter.ToDB(new DestinationQualityModel036 + var qualityNew = new DestinationQualityModel036 { Quality = sourceQuality.Quality.Id, Proper = sourceQuality.Proper - }); + }; using (IDbCommand updateCmd = conn.CreateCommand()) { updateCmd.Transaction = tran; updateCmd.CommandText = "UPDATE " + tableName + " SET Quality = ? WHERE Quality = ?"; - updateCmd.AddParameter(qualityNewJson); + var param = updateCmd.CreateParameter(); + qualityModelConverter.SetValue(param, qualityNew); updateCmd.AddParameter(qualityJson); updateCmd.ExecuteNonQuery(); diff --git a/src/NzbDrone.Core/Datastore/Migration/154_add_language_to_file_history_blacklist.cs b/src/NzbDrone.Core/Datastore/Migration/154_add_language_to_file_history_blacklist.cs index a5a7d66de..1dbf34f0f 100644 --- a/src/NzbDrone.Core/Datastore/Migration/154_add_language_to_file_history_blacklist.cs +++ b/src/NzbDrone.Core/Datastore/Migration/154_add_language_to_file_history_blacklist.cs @@ -33,7 +33,7 @@ protected override void MainDbUpgrade() private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) { - var languageConverter = new EmbeddedDocumentConverter(new LanguageIntConverter()); + var languageConverter = new EmbeddedDocumentConverter>(new LanguageIntConverter()); var profileLanguages = new Dictionary(); using (IDbCommand getProfileCmd = conn.CreateCommand()) @@ -79,7 +79,7 @@ private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) foreach (var group in movieLanguages.GroupBy(v => v.Value, v => v.Key)) { - var languageJson = languageConverter.ToDB(new List { Language.FindById(group.Key) }); + var language = new List { Language.FindById(group.Key) }; var movieIds = group.Select(v => v.ToString()).Join(","); @@ -87,7 +87,8 @@ private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) { updateMovieFilesCmd.Transaction = tran; updateMovieFilesCmd.CommandText = $"UPDATE MovieFiles SET Languages = ? WHERE MovieId IN ({movieIds})"; - updateMovieFilesCmd.AddParameter(languageJson); + var param = updateMovieFilesCmd.CreateParameter(); + languageConverter.SetValue(param, language); updateMovieFilesCmd.ExecuteNonQuery(); } @@ -96,7 +97,8 @@ private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) { updateHistoryCmd.Transaction = tran; updateHistoryCmd.CommandText = $"UPDATE History SET Languages = ? WHERE MovieId IN ({movieIds})"; - updateHistoryCmd.AddParameter(languageJson); + var param = updateHistoryCmd.CreateParameter(); + languageConverter.SetValue(param, language); updateHistoryCmd.ExecuteNonQuery(); } @@ -105,7 +107,8 @@ private void UpdateLanguage(IDbConnection conn, IDbTransaction tran) { updateBlacklistCmd.Transaction = tran; updateBlacklistCmd.CommandText = $"UPDATE Blacklist SET Languages = ? WHERE MovieId IN ({movieIds})"; - updateBlacklistCmd.AddParameter(languageJson); + var param = updateBlacklistCmd.CreateParameter(); + languageConverter.SetValue(param, language); updateBlacklistCmd.ExecuteNonQuery(); } diff --git a/src/NzbDrone.Core/Datastore/TableMapper.cs b/src/NzbDrone.Core/Datastore/TableMapper.cs new file mode 100644 index 000000000..8a8e19b73 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/TableMapper.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Dapper; +using NzbDrone.Common.Reflection; + +namespace NzbDrone.Core.Datastore +{ + public static class MappingExtensions + { + public static PropertyInfo GetMemberName(this Expression> member) + { + var memberExpression = member.Body as MemberExpression; + if (memberExpression == null) + { + memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression; + } + + return (PropertyInfo)memberExpression.Member; + } + } + + public class TableMapper + { + public TableMapper() + { + IgnoreList = new Dictionary>(); + TableMap = new Dictionary(); + } + + public Dictionary> IgnoreList { get; set; } + public Dictionary TableMap { get; set; } + + public ColumnMapper Entity(string tableName) + { + TableMap.Add(typeof(TEntity), tableName); + + if (IgnoreList.TryGetValue(typeof(TEntity), out var list)) + { + return new ColumnMapper(list); + } + + list = new List(); + IgnoreList[typeof(TEntity)] = list; + return new ColumnMapper(list); + } + + public List ExcludeProperties(Type x) + { + return IgnoreList.ContainsKey(x) ? IgnoreList[x] : new List(); + } + + public string TableNameMapping(Type x) + { + return TableMap.ContainsKey(x) ? TableMap[x] : null; + } + } + + public class ColumnMapper + { + private readonly List _ignoreList; + + public ColumnMapper(List ignoreList) + { + _ignoreList = ignoreList; + } + + public ColumnMapper AutoMapPropertiesWhere(Func predicate) + { + Type entityType = typeof(T); + var properties = entityType.GetProperties(); + _ignoreList.AddRange(properties.Where(x => !predicate(x))); + + return this; + } + + public ColumnMapper RegisterModel() + { + return AutoMapPropertiesWhere(IsMappableProperty); + } + + public ColumnMapper Ignore(Expression> property) + { + _ignoreList.Add(property.GetMemberName()); + return this; + } + + public static bool IsMappableProperty(MemberInfo memberInfo) + { + var propertyInfo = memberInfo as PropertyInfo; + + if (propertyInfo == null) + { + return false; + } + + if (!propertyInfo.IsReadable() || !propertyInfo.IsWritable()) + { + return false; + } + + // This is a bit of a hack but is the only way to see if a type has a handler set in Dapper +#pragma warning disable 618 + SqlMapper.LookupDbType(propertyInfo.PropertyType, "", false, out var handler); +#pragma warning restore 618 + if (propertyInfo.PropertyType.IsSimpleType() || handler != null) + { + return true; + } + + return false; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index c033e6410..d9d1f6349 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using Marr.Data; -using Marr.Data.Mapping; -using NzbDrone.Common.Disk; +using Dapper; using NzbDrone.Common.Reflection; using NzbDrone.Core.Authentication; using NzbDrone.Core.Blacklisting; @@ -10,7 +8,6 @@ using NzbDrone.Core.CustomFilters; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore.Converters; -using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Pending; using NzbDrone.Core.Extras.Metadata; @@ -38,110 +35,104 @@ using NzbDrone.Core.RootFolders; using NzbDrone.Core.Tags; using NzbDrone.Core.ThingiProvider; +using static Dapper.SqlMapper; namespace NzbDrone.Core.Datastore { public static class TableMapping { - private static readonly FluentMappings Mapper = new FluentMappings(true); + static TableMapping() + { + Mapper = new TableMapper(); + } + + public static TableMapper Mapper { get; private set; } public static void Map() { RegisterMappers(); - Mapper.Entity().RegisterModel("Config"); + Mapper.Entity("Config").RegisterModel(); - Mapper.Entity().RegisterModel("RootFolders") + Mapper.Entity("RootFolders").RegisterModel() .Ignore(r => r.Accessible) .Ignore(r => r.FreeSpace) .Ignore(r => r.TotalSpace); - Mapper.Entity().RegisterModel("ScheduledTasks"); + Mapper.Entity("ScheduledTasks").RegisterModel(); - Mapper.Entity().RegisterDefinition("Indexers") + Mapper.Entity("Indexers").RegisterModel() + .Ignore(x => x.ImplementationName) .Ignore(i => i.Enable) .Ignore(i => i.Protocol) .Ignore(i => i.SupportsRss) .Ignore(i => i.SupportsSearch) .Ignore(d => d.Tags); - Mapper.Entity().RegisterDefinition("NetImport") - .Ignore(i => i.Enable) - .Relationship() - .HasOne(n => n.Profile, n => n.ProfileId); + Mapper.Entity("NetImport").RegisterModel() + .Ignore(x => x.ImplementationName) + .Ignore(i => i.Enable); - Mapper.Entity().RegisterDefinition("Notifications") + Mapper.Entity("Notifications").RegisterModel() + .Ignore(x => x.ImplementationName) .Ignore(i => i.SupportsOnGrab) .Ignore(i => i.SupportsOnDownload) .Ignore(i => i.SupportsOnUpgrade) .Ignore(i => i.SupportsOnRename) .Ignore(i => i.SupportsOnHealthIssue); - Mapper.Entity().RegisterDefinition("Metadata") + Mapper.Entity("Metadata").RegisterModel() + .Ignore(x => x.ImplementationName) .Ignore(d => d.Tags); - Mapper.Entity().RegisterDefinition("DownloadClients") + Mapper.Entity("DownloadClients").RegisterModel() + .Ignore(x => x.ImplementationName) .Ignore(d => d.Protocol) .Ignore(d => d.Tags); - Mapper.Entity().RegisterModel("History") - .AutoMapChildModels(); + Mapper.Entity("History").RegisterModel(); - Mapper.Entity().RegisterModel("MovieFiles") - .Ignore(f => f.Path) - .Relationships.AutoMapICollectionOrComplexProperties() - .For("Movie") - .LazyLoad(condition: parent => parent.Id > 0, - query: (db, parent) => db.Query().Where(c => c.MovieFileId == parent.Id).ToList()) - .HasOne(file => file.Movie, file => file.MovieId); + Mapper.Entity("MovieFiles").RegisterModel() + .Ignore(f => f.Path); - Mapper.Entity().RegisterModel("Movies") - .Ignore(s => s.RootFolderPath) - .Ignore(m => m.Actors) - .Relationship() - .HasOne(s => s.Profile, s => s.ProfileId); + Mapper.Entity("Movies").RegisterModel() + .Ignore(s => s.RootFolderPath) + .Ignore(m => m.Actors); - //.HasOne(m => m.MovieFile, m => m.MovieFileId); - Mapper.Entity().RegisterModel("AlternativeTitles") - .For(t => t.Id) - .SetAltName("AltTitle_Id") - .Relationship() - .HasOne(t => t.Movie, t => t.MovieId); + Mapper.Entity("AlternativeTitles").RegisterModel(); - Mapper.Entity().RegisterModel("ImportExclusions"); + Mapper.Entity("ImportExclusions").RegisterModel(); - Mapper.Entity().RegisterModel("QualityDefinitions") - .Ignore(d => d.GroupName) - .Ignore(d => d.Weight) - .Relationship(); + Mapper.Entity("QualityDefinitions").RegisterModel() + .Ignore(d => d.GroupName) + .Ignore(d => d.Weight); - Mapper.Entity().RegisterModel("CustomFormats") - .Relationship(); + Mapper.Entity("CustomFormats").RegisterModel(); - Mapper.Entity().RegisterModel("Profiles"); - Mapper.Entity().RegisterModel("Logs"); - Mapper.Entity().RegisterModel("NamingConfig"); - Mapper.Entity().RegisterModel("Blacklist"); - Mapper.Entity().RegisterModel("MetadataFiles"); - Mapper.Entity().RegisterModel("SubtitleFiles"); - Mapper.Entity().RegisterModel("ExtraFiles"); + Mapper.Entity("Profiles").RegisterModel(); + Mapper.Entity("Logs").RegisterModel(); + Mapper.Entity("NamingConfig").RegisterModel(); + Mapper.Entity("Blacklist").RegisterModel(); + Mapper.Entity("MetadataFiles").RegisterModel(); + Mapper.Entity("SubtitleFiles").RegisterModel(); + Mapper.Entity("ExtraFiles").RegisterModel(); - Mapper.Entity().RegisterModel("PendingReleases") + Mapper.Entity("PendingReleases").RegisterModel() .Ignore(e => e.RemoteMovie); - Mapper.Entity().RegisterModel("RemotePathMappings"); - Mapper.Entity().RegisterModel("Tags"); - Mapper.Entity().RegisterModel("Restrictions"); + Mapper.Entity("RemotePathMappings").RegisterModel(); + Mapper.Entity("Tags").RegisterModel(); + Mapper.Entity("Restrictions").RegisterModel(); - Mapper.Entity().RegisterModel("DelayProfiles"); - Mapper.Entity().RegisterModel("Users"); - Mapper.Entity().RegisterModel("Commands") - .Ignore(c => c.Message); + Mapper.Entity("DelayProfiles").RegisterModel(); + Mapper.Entity("Users").RegisterModel(); + Mapper.Entity("Commands").RegisterModel() + .Ignore(c => c.Message); - Mapper.Entity().RegisterModel("IndexerStatus"); - Mapper.Entity().RegisterModel("DownloadClientStatus"); + Mapper.Entity("IndexerStatus").RegisterModel(); + Mapper.Entity("DownloadClientStatus").RegisterModel(); - Mapper.Entity().RegisterModel("CustomFilters"); + Mapper.Entity("CustomFilters").RegisterModel(); } private static void RegisterMappers() @@ -149,32 +140,30 @@ private static void RegisterMappers() RegisterEmbeddedConverter(); RegisterProviderSettingConverter(); - MapRepository.Instance.RegisterTypeConverter(typeof(int), new Int32Converter()); - MapRepository.Instance.RegisterTypeConverter(typeof(double), new DoubleConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(bool), new BooleanIntConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(CustomFormat), new CustomFormatIntConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new QualityIntConverter())); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new CustomFormatIntConverter())); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new QualityTagStringConverter())); - MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new CustomFormatIntConverter(), new QualityIntConverter())); - MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(IDictionary), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(List>), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(Language), new LanguageIntConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter(new LanguageIntConverter())); - MapRepository.Instance.RegisterTypeConverter(typeof(List), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(ParsedMovieInfo), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(HashSet), new EmbeddedDocumentConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(Command), new CommandConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan), new TimeSpanConverter()); - MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan?), new TimeSpanConverter()); + SqlMapper.RemoveTypeMap(typeof(DateTime)); + SqlMapper.AddTypeHandler(new DapperUtcConverter()); + SqlMapper.AddTypeHandler(new DapperQualityIntConverter()); + SqlMapper.AddTypeHandler(new DapperCustomFormatIntConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new QualityIntConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new CustomFormatIntConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new QualityTagStringConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter(new CustomFormatIntConverter(), new QualityIntConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new DapperLanguageIntConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>(new LanguageIntConverter())); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter()); + SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter>()); + SqlMapper.AddTypeHandler(new OsPathConverter()); + SqlMapper.RemoveTypeMap(typeof(Guid)); + SqlMapper.RemoveTypeMap(typeof(Guid?)); + SqlMapper.AddTypeHandler(new GuidConverter()); + SqlMapper.AddTypeHandler(new CommandConverter()); } private static void RegisterProviderSettingConverter() @@ -184,7 +173,7 @@ private static void RegisterProviderSettingConverter() var providerSettingConverter = new ProviderSettingConverter(); foreach (var embeddedType in settingTypes) { - MapRepository.Instance.RegisterTypeConverter(embeddedType, providerSettingConverter); + SqlMapper.AddTypeHandler(embeddedType, providerSettingConverter); } } @@ -192,16 +181,24 @@ private static void RegisterEmbeddedConverter() { var embeddedTypes = typeof(IEmbeddedDocument).Assembly.ImplementationsOf(); - var embeddedConvertor = new EmbeddedDocumentConverter(); + var embeddedConverterDefinition = typeof(EmbeddedDocumentConverter<>).GetGenericTypeDefinition(); var genericListDefinition = typeof(List<>).GetGenericTypeDefinition(); foreach (var embeddedType in embeddedTypes) { var embeddedListType = genericListDefinition.MakeGenericType(embeddedType); - MapRepository.Instance.RegisterTypeConverter(embeddedType, embeddedConvertor); - MapRepository.Instance.RegisterTypeConverter(embeddedListType, embeddedConvertor); + RegisterEmbeddedConverter(embeddedType, embeddedConverterDefinition); + RegisterEmbeddedConverter(embeddedListType, embeddedConverterDefinition); } } + + private static void RegisterEmbeddedConverter(Type embeddedType, Type embeddedConverterDefinition) + { + var embeddedConverterType = embeddedConverterDefinition.MakeGenericType(embeddedType); + var converter = (ITypeHandler)Activator.CreateInstance(embeddedConverterType); + + SqlMapper.AddTypeHandler(embeddedType, converter); + } } } diff --git a/src/NzbDrone.Core/Datastore/WhereBuilder.cs b/src/NzbDrone.Core/Datastore/WhereBuilder.cs new file mode 100644 index 000000000..f4c2fdd21 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/WhereBuilder.cs @@ -0,0 +1,323 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Dapper; + +namespace NzbDrone.Core.Datastore +{ + public class WhereBuilder : ExpressionVisitor + { + private const DbType EnumerableMultiParameter = (DbType)(-1); + + private readonly string _paramNamePrefix; + private int _paramCount = 0; + private bool _requireConcreteValue = false; + private bool _gotConcreteValue = false; + protected StringBuilder _sb; + + public DynamicParameters Parameters { get; private set; } + + public WhereBuilder(Expression filter, bool requireConcreteValue) + { + _paramNamePrefix = Guid.NewGuid().ToString().Replace("-", "_"); + _requireConcreteValue = requireConcreteValue; + _sb = new StringBuilder(); + + Parameters = new DynamicParameters(); + + if (filter != null) + { + Visit(filter); + } + } + + private string AddParameter(object value, DbType? dbType = null) + { + _gotConcreteValue = true; + _paramCount++; + var name = _paramNamePrefix + "_P" + _paramCount; + Parameters.Add(name, value, dbType); + return '@' + name; + } + + protected override Expression VisitBinary(BinaryExpression expression) + { + _sb.Append("("); + + Visit(expression.Left); + + _sb.AppendFormat(" {0} ", Decode(expression)); + + Visit(expression.Right); + + _sb.Append(")"); + + return expression; + } + + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + string method = expression.Method.Name; + + switch (expression.Method.Name) + { + case "Contains": + ParseContainsExpression(expression); + break; + + case "StartsWith": + ParseStartsWith(expression); + break; + + case "EndsWith": + ParseEndsWith(expression); + break; + + default: + string msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method); + throw new NotImplementedException(msg); + } + + return expression; + } + + protected override Expression VisitMemberAccess(MemberExpression expression) + { + string tableName = TableMapping.Mapper.TableNameMapping(expression.Expression.Type); + + if (tableName != null) + { + _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); + } + else + { + object value = GetRightValue(expression); + + // string is IEnumerable but we don't want to pick up that case + var type = value.GetType(); + var typeInfo = type.GetTypeInfo(); + bool isEnumerable = + type != typeof(string) && ( + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>))); + + string paramName; + if (isEnumerable) + { + paramName = AddParameter(value, EnumerableMultiParameter); + } + else + { + paramName = AddParameter(value); + } + + _sb.Append(paramName); + } + + return expression; + } + + protected override Expression VisitConstant(ConstantExpression expression) + { + if (expression.Value != null) + { + string paramName = AddParameter(expression.Value); + _sb.Append(paramName); + } + else + { + _sb.Append("NULL"); + } + + return expression; + } + + private object GetRightValue(Expression rightExpression) + { + object rightValue = null; + + var right = rightExpression as ConstantExpression; + + // Value is not directly passed in as a constant + if (right == null) + { + var rightMemberExp = rightExpression as MemberExpression; + var parentMemberExpression = rightMemberExp.Expression as MemberExpression; + + // Value is passed in as a property on a parent entity + if (parentMemberExpression != null) + { + var memberInfo = (rightMemberExp.Expression as MemberExpression).Member; + var container = ((rightMemberExp.Expression as MemberExpression).Expression as ConstantExpression).Value; + var entity = GetFieldValue(container, memberInfo); + rightValue = GetFieldValue(entity, rightMemberExp.Member); + } + else + { + // Value is passed in as a variable + var parent = (rightMemberExp.Expression as ConstantExpression).Value; + rightValue = GetFieldValue(parent, rightMemberExp.Member); + } + } + else + { + // Value is passed in directly as a constant + rightValue = right.Value; + } + + return rightValue; + } + + private object GetFieldValue(object entity, MemberInfo member) + { + if (member.MemberType == MemberTypes.Field) + { + return (member as FieldInfo).GetValue(entity); + } + + if (member.MemberType == MemberTypes.Property) + { + return (member as PropertyInfo).GetValue(entity); + } + + throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name)); + } + + private string Decode(BinaryExpression expression) + { + bool isRightSideNullConstant = expression.Right.NodeType == + ExpressionType.Constant && + ((ConstantExpression)expression.Right).Value == null; + + if (isRightSideNullConstant) + { + switch (expression.NodeType) + { + case ExpressionType.Equal: return "IS"; + case ExpressionType.NotEqual: return "IS NOT"; + } + } + + switch (expression.NodeType) + { + case ExpressionType.AndAlso: return "AND"; + case ExpressionType.And: return "AND"; + case ExpressionType.Equal: return "="; + case ExpressionType.GreaterThan: return ">"; + case ExpressionType.GreaterThanOrEqual: return ">="; + case ExpressionType.LessThan: return "<"; + case ExpressionType.LessThanOrEqual: return "<="; + case ExpressionType.NotEqual: return "<>"; + case ExpressionType.OrElse: return "OR"; + case ExpressionType.Or: return "OR"; + default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString())); + } + } + + private void ParseContainsExpression(MethodCallExpression expression) + { + var list = expression.Object; + + if (list != null && list.Type == typeof(string)) + { + ParseStringContains(expression); + return; + } + + ParseEnumerableContains(expression); + } + + private void ParseEnumerableContains(MethodCallExpression body) + { + // Fish out the list and the item to compare + // It's in a different form for arrays and Lists + var list = body.Object; + Expression item; + + if (list != null) + { + // Generic collection + item = body.Arguments[0]; + } + else + { + // Static method + // Must be Enumerable.Contains(source, item) + if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2) + { + throw new NotSupportedException("Unexpected form of Enumerable.Contains"); + } + + list = body.Arguments[0]; + item = body.Arguments[1]; + } + + _sb.Append("("); + + Visit(item); + + _sb.Append(" IN "); + + Visit(list); + + _sb.Append(")"); + } + + private void ParseStringContains(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseStartsWith(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE "); + + Visit(body.Arguments[0]); + + _sb.Append(" || '%')"); + } + + private void ParseEndsWith(MethodCallExpression body) + { + _sb.Append("("); + + Visit(body.Object); + + _sb.Append(" LIKE '%' || "); + + Visit(body.Arguments[0]); + + _sb.Append(")"); + } + + public override string ToString() + { + var sql = _sb.ToString(); + + if (_requireConcreteValue && !_gotConcreteValue) + { + var e = new InvalidOperationException("WhereBuilder requires a concrete condition"); + e.Data.Add("sql", sql); + throw e; + } + + return sql; + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs index b1f0213d9..e5dcac0f4 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionComparer.cs @@ -62,7 +62,7 @@ private int CompareAll(params int[] comparers) private int CompareQuality(DownloadDecision x, DownloadDecision y) { - return CompareAll(CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.Movie.Profile.Value.GetIndex(remoteMovie.ParsedMovieInfo.Quality.Quality)), + return CompareAll(CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.Movie.Profile.GetIndex(remoteMovie.ParsedMovieInfo.Quality.Quality)), CompareCustomFormats(x, y), CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Real), CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => remoteMovie.ParsedMovieInfo.Quality.Revision.Version)); @@ -73,8 +73,8 @@ private int CompareCustomFormats(DownloadDecision x, DownloadDecision y) var left = x.RemoteMovie.ParsedMovieInfo.Quality.CustomFormats.WithNone(); var right = y.RemoteMovie.ParsedMovieInfo.Quality.CustomFormats; - var leftIndicies = QualityModelComparer.GetIndicies(left, x.RemoteMovie.Movie.Profile.Value); - var rightIndicies = QualityModelComparer.GetIndicies(right, y.RemoteMovie.Movie.Profile.Value); + var leftIndicies = QualityModelComparer.GetIndicies(left, x.RemoteMovie.Movie.Profile); + var rightIndicies = QualityModelComparer.GetIndicies(right, y.RemoteMovie.Movie.Profile); var leftTotal = leftIndicies.Sum(); var rightTotal = rightIndicies.Sum(); @@ -87,8 +87,7 @@ private int ComparePreferredWords(DownloadDecision x, DownloadDecision y) return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie => { var title = remoteMovie.Release.Title; - remoteMovie.Movie.Profile.LazyLoad(); - var preferredWords = remoteMovie.Movie.Profile.Value.PreferredTags; + var preferredWords = remoteMovie.Movie.Profile.PreferredTags; if (preferredWords == null) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs index 3369ac0a3..5a5dff4b7 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CustomFormatAllowedByProfileSpecification.cs @@ -22,7 +22,7 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se { var formats = subject.ParsedMovieInfo.Quality.CustomFormats.WithNone(); _logger.Debug("Checking if report meets custom format requirements. {0}", formats.ToExtendedString()); - var notAllowedFormats = subject.Movie.Profile.Value.FormatItems.Where(v => v.Allowed == false).Select(f => f.Format).ToList(); + var notAllowedFormats = subject.Movie.Profile.FormatItems.Where(v => v.Allowed == false).Select(f => f.Format).ToList(); var notWantedFormats = notAllowedFormats.Intersect(formats); if (notWantedFormats.Any()) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs index 5fd790250..cfa51c120 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/CutoffSpecification.cs @@ -20,7 +20,7 @@ public CutoffSpecification(UpgradableSpecification qualityUpgradableSpecificatio public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - var profile = subject.Movie.Profile.Value; + var profile = subject.Movie.Profile; if (subject.Movie.MovieFile != null) { @@ -31,7 +31,7 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se var qualityCutoffIndex = profile.GetIndex(profile.Cutoff); var qualityCutoff = profile.Items[qualityCutoffIndex.Index]; - return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Movie.Profile.Value.Cutoff); + return Decision.Reject("Existing file meets cutoff: {0} - {1}", qualityCutoff, subject.Movie.Profile.Cutoff); } } diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs index 5631328e8..ca0af52b2 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/LanguageSpecification.cs @@ -19,7 +19,7 @@ public LanguageSpecification(Logger logger) public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - var wantedLanguage = subject.Movie.Profile.Value.Language; + var wantedLanguage = subject.Movie.Profile.Language; if (wantedLanguage == Language.Any) { diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs index 2d8e048a5..edd856688 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QualityAllowedByProfileSpecification.cs @@ -20,7 +20,7 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se { _logger.Debug("Checking if report meets quality requirements. {0}", subject.ParsedMovieInfo.Quality); - var profile = subject.Movie.Profile.Value; + var profile = subject.Movie.Profile; var qualityIndex = profile.GetIndex(subject.ParsedMovieInfo.Quality.Quality); var qualityOrGroup = profile.Items[qualityIndex.Index]; diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs index 55e6b3cb5..85d00592a 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/QueueSpecification.cs @@ -34,7 +34,7 @@ public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCrit foreach (var queueItem in matchingMovies) { var remoteMovie = queueItem.RemoteMovie; - var qualityProfile = subject.Movie.Profile.Value; + var qualityProfile = subject.Movie.Profile; _logger.Debug("Checking if existing release in queue meets cutoff. Queued quality is: {0}", remoteMovie.ParsedMovieInfo.Quality); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs index acfe1c058..c9e826d13 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RssSync/DelaySpecification.cs @@ -37,14 +37,14 @@ public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase se return Decision.Accept(); } - var profile = subject.Movie.Profile.Value; + var profile = subject.Movie.Profile; var delayProfile = _delayProfileService.BestForTags(subject.Movie.Tags); var delay = delayProfile.GetProtocolDelay(subject.Release.DownloadProtocol); var isPreferredProtocol = subject.Release.DownloadProtocol == delayProfile.PreferredProtocol; // Preferred word count var title = subject.Release.Title; - var preferredWords = subject.Movie.Profile?.Value?.PreferredTags; + var preferredWords = subject.Movie.Profile?.PreferredTags; var preferredCount = 0; if (preferredWords == null) diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs index bc0014ecc..14784effb 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradeAllowedSpecification.cs @@ -20,7 +20,7 @@ public UpgradeAllowedSpecification(UpgradableSpecification upgradableSpecificati public virtual Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) { - var qualityProfile = subject.Movie.Profile.Value; + var qualityProfile = subject.Movie.Profile; if (subject.Movie.MovieFileId != 0) { diff --git a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs index 3a6395b69..3203cf751 100644 --- a/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs +++ b/src/NzbDrone.Core/DiskSpace/DiskSpaceService.cs @@ -42,9 +42,9 @@ public List GetFreeSpace() private IEnumerable GetMoviesRootPaths() { - return _movieService.GetAllMovies() - .Where(s => _diskProvider.FolderExists(s.Path)) - .Select(s => _diskProvider.GetPathRoot(s.Path)) + return _movieService.AllMoviePaths() + .Where(s => _diskProvider.FolderExists(s)) + .Select(s => _diskProvider.GetPathRoot(s)) .Distinct(); } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs index 97869354b..bdade04f9 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseRepository.cs @@ -20,17 +20,17 @@ public PendingReleaseRepository(IMainDatabase database, IEventAggregator eventAg public void DeleteByMovieId(int movieId) { - Delete(r => r.MovieId == movieId); + Delete(movieId); } public List AllByMovieId(int movieId) { - return Query.Where(p => p.MovieId == movieId).ToList(); + return Query(x => x.MovieId == movieId); } public List WithoutFallback() { - return Query.Where(p => p.Reason != PendingReleaseReason.Fallback); + return Query(x => x.Reason != PendingReleaseReason.Fallback); } } } diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index aecdbbba9..8478713ca 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -346,7 +346,7 @@ private void RemoveGrabbed(RemoteMovie remoteMovie) return; } - var profile = remoteMovie.Movie.Profile.Value; + var profile = remoteMovie.Movie.Profile; foreach (var existingReport in existingReports) { diff --git a/src/NzbDrone.Core/Extras/ExtraService.cs b/src/NzbDrone.Core/Extras/ExtraService.cs index f87add16d..c361ffb73 100644 --- a/src/NzbDrone.Core/Extras/ExtraService.cs +++ b/src/NzbDrone.Core/Extras/ExtraService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Marr.Data; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; @@ -166,7 +165,7 @@ private List GetMovieFiles(int movieId) foreach (var movieFile in movieFiles) { - movieFile.Movie = new LazyLoaded(_movieService.GetMovie(movieId)); + movieFile.Movie = _movieService.GetMovie(movieId); } return movieFiles; diff --git a/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs b/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs index 716725b84..f05d6261f 100644 --- a/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs +++ b/src/NzbDrone.Core/Extras/Files/ExtraFileRepository.cs @@ -25,7 +25,7 @@ public ExtraFileRepository(IMainDatabase database, IEventAggregator eventAggrega public void DeleteForMovie(int movieId) { - Delete(c => c.MovieId == movieId); + Delete(movieId); } public void DeleteForMovieFile(int movieFileId) @@ -35,17 +35,17 @@ public void DeleteForMovieFile(int movieFileId) public List GetFilesByMovie(int movieId) { - return Query.Where(c => c.MovieId == movieId).ToList(); + return Query(x => x.MovieId == movieId); } public List GetFilesByMovieFile(int movieFileId) { - return Query.Where(c => c.MovieFileId == movieFileId).ToList(); + return Query(x => x.MovieFileId == movieFileId); } public TExtraFile FindByPath(string path) { - return Query.Where(c => c.RelativePath == path).SingleOrDefault(); + return Query(x => x.RelativePath == path).SingleOrDefault(); } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs index 88e13c12e..8579b73e5 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/MountCheck.cs @@ -19,11 +19,11 @@ public MountCheck(IDiskProvider diskProvider, IMovieService movieService) public override HealthCheck Check() { // Not best for optimization but due to possible symlinks and junctions, we get mounts based on series path so internals can handle mount resolution. - var mounts = _movieService.GetAllMovies() - .Select(movie => _diskProvider.GetMount(movie.Path)) - .Where(m => m != null && m.MountOptions != null && m.MountOptions.IsReadOnly) - .DistinctBy(m => m.RootDirectory) - .ToList(); + var mounts = _movieService.AllMoviePaths() + .Select(p => _diskProvider.GetMount(p)) + .Where(m => m != null && m.MountOptions != null && m.MountOptions.IsReadOnly) + .DistinctBy(m => m.RootDirectory) + .ToList(); if (mounts.Any()) { diff --git a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs index 16bc754f1..076ba64bc 100644 --- a/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs +++ b/src/NzbDrone.Core/HealthCheck/Checks/RootFolderCheck.cs @@ -20,11 +20,11 @@ public RootFolderCheck(IMovieService movieService, IDiskProvider diskProvider) public override HealthCheck Check() { - var missingRootFolders = _movieService.GetAllMovies() - .Select(s => _diskProvider.GetParentFolder(s.Path)) - .Distinct() - .Where(s => !_diskProvider.FolderExists(s)) - .ToList(); + var missingRootFolders = _movieService.AllMoviePaths() + .Select(s => _diskProvider.GetParentFolder(s)) + .Distinct() + .Where(s => !_diskProvider.FolderExists(s)) + .ToList(); if (missingRootFolders.Any()) { diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index 7be0674da..4bef354ac 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using Marr.Data.QGen; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.History @@ -14,7 +15,7 @@ public interface IHistoryRepository : IBasicRepository List GetBestQualityInHistory(int movieId); History MostRecentForDownloadId(string downloadId); List FindByDownloadId(string downloadId); - List FindDownloadHistory(int idMovieId, QualityModel quality); + List FindDownloadHistory(int movieId, QualityModel quality); List GetByMovieId(int movieId, HistoryEventType? eventType); void DeleteForMovie(int movieId); History MostRecentForMovie(int movieId); @@ -30,37 +31,35 @@ public HistoryRepository(IMainDatabase database, IEventAggregator eventAggregato public List GetBestQualityInHistory(int movieId) { - var history = Query.Where(c => c.MovieId == movieId).ToList(); + var history = Query(x => x.MovieId == movieId); return history.Select(h => h.Quality).ToList(); } public History MostRecentForDownloadId(string downloadId) { - return Query.Where(h => h.DownloadId == downloadId) - .OrderByDescending(h => h.Date) - .FirstOrDefault(); + return FindByDownloadId(downloadId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(); } public List FindByDownloadId(string downloadId) { - return Query.Where(h => h.DownloadId == downloadId).ToList(); + return Query(x => x.DownloadId == downloadId); } - public List FindDownloadHistory(int idMovieId, QualityModel quality) + public List FindDownloadHistory(int movieId, QualityModel quality) { - return Query.Where(h => - h.MovieId == idMovieId && - h.Quality == quality && - (h.EventType == HistoryEventType.Grabbed || - h.EventType == HistoryEventType.DownloadFailed || - h.EventType == HistoryEventType.DownloadFolderImported)) - .ToList(); + var allowed = new[] { HistoryEventType.Grabbed, HistoryEventType.DownloadFailed, HistoryEventType.DownloadFolderImported }; + + return Query(h => h.MovieId == movieId && + h.Quality == quality && + allowed.Contains(h.EventType)); } public List GetByMovieId(int movieId, HistoryEventType? eventType) { - var query = Query.Where(h => h.MovieId == movieId).ToList(); + var query = Query(x => x.MovieId == movieId); if (eventType.HasValue) { @@ -77,32 +76,46 @@ public void DeleteForMovie(int movieId) Delete(c => c.MovieId == movieId); } - protected override SortBuilder GetPagedQuery(QueryBuilder query, PagingSpec pagingSpec) + private IEnumerable SelectJoined(SqlBuilder.Template sql) { - var baseQuery = query.Join(JoinType.Inner, h => h.Movie, (h, e) => h.MovieId == e.Id); - - return base.GetPagedQuery(baseQuery, pagingSpec); + using (var conn = _database.OpenConnection()) + { + return conn.Query( + sql.RawSql, + (hist, movie, profile) => + { + hist.Movie = movie; + hist.Movie.Profile = profile; + return hist; + }, + sql.Parameters) + .ToList(); + } } + protected override SqlBuilder PagedBuilder() => new SqlBuilder() + .Join((h, m) => h.MovieId == m.Id) + .Join((m, p) => m.ProfileId == p.Id); + + protected override IEnumerable PagedSelector(SqlBuilder.Template sql) => SelectJoined(sql); + public History MostRecentForMovie(int movieId) { - return Query.Where(h => h.MovieId == movieId) - .OrderByDescending(h => h.Date) - .FirstOrDefault(); + return Query(x => x.MovieId == movieId) + .OrderByDescending(h => h.Date) + .FirstOrDefault(); } public List Since(DateTime date, HistoryEventType? eventType) { - var query = Query.Where(h => h.Date >= date).ToList(); + var builder = Builder().Where(x => x.Date >= date); if (eventType.HasValue) { - query = query.Where(h => h.EventType == eventType).ToList(); + builder.Where(h => h.EventType == eventType); } - query.OrderBy(h => h.Date); - - return query; + return Query(builder).OrderBy(h => h.Date).ToList(); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs index de6dcc0de..1293d4f07 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAbsolutePathMetadataFiles.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,9 +14,9 @@ public CleanupAbsolutePathMetadataFiles(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT Id FROM MetadataFiles WHERE RelativePath diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs index 8546cd37c..a1d9c56b7 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalNamingSpecs.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,9 +14,9 @@ public CleanupAdditionalNamingSpecs(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM NamingConfig + mapper.Execute(@"DELETE FROM NamingConfig WHERE ID NOT IN ( SELECT ID FROM NamingConfig LIMIT 1)"); diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs index 4064d453e..4460ac83c 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupAdditionalUsers.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,12 +14,12 @@ public CleanupAdditionalUsers(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM Users - WHERE ID NOT IN ( - SELECT ID FROM Users - LIMIT 1)"); + mapper.Execute(@"DELETE FROM Users + WHERE ID NOT IN ( + SELECT ID FROM Users + LIMIT 1)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs index a7b36639e..a1071af74 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDownloadClientUnavailablePendingReleases.cs @@ -1,4 +1,5 @@ using System; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download.Pending; @@ -15,18 +16,16 @@ public CleanupDownloadClientUnavailablePendingReleases(IMainDatabase database) public void Clean() { - var mapper = _database.GetDataMapper(); - var twoWeeksAgo = DateTime.UtcNow.AddDays(-14); + var mapper = _database.OpenConnection(); - mapper.Delete(p => p.Added < twoWeeksAgo && - (p.Reason == PendingReleaseReason.DownloadClientUnavailable || - p.Reason == PendingReleaseReason.Fallback)); - - // mapper.AddParameter("twoWeeksAgo", $"{DateTime.UtcNow.AddDays(-14).ToString("s")}Z"); - - // mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases - // WHERE Added < @twoWeeksAgo - // AND (Reason = 'DownloadClientUnavailable' OR Reason = 'Fallback')"); + mapper.Execute(@"DELETE FROM PendingReleases + WHERE Added < @TwoWeeksAgo + AND REASON IN @Reasons", + new + { + TwoWeeksAgo = DateTime.UtcNow.AddDays(-14), + Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback } + }); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs index 8ce13e95c..43bab4d8e 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupDuplicateMetadataFiles.cs @@ -1,3 +1,4 @@ +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -19,9 +20,9 @@ public void Clean() private void DeleteDuplicateMovieMetadata() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT Id FROM MetadataFiles WHERE Type = 1 @@ -33,9 +34,9 @@ HAVING COUNT(MovieId) > 1 private void DeleteDuplicateMovieFileMetadata() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT Id FROM MetadataFiles WHERE Type = 1 diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlternativeTitles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlternativeTitles.cs index 62e8de4c8..b535eab20 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlternativeTitles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedAlternativeTitles.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,9 +14,9 @@ public CleanupOrphanedAlternativeTitles(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM AlternativeTitles + mapper.Execute(@"DELETE FROM AlternativeTitles WHERE Id IN ( SELECT AlternativeTitles.Id FROM AlternativeTitles LEFT OUTER JOIN Movies diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs index 609236cb4..0218a883f 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedBlacklist.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,9 +14,9 @@ public CleanupOrphanedBlacklist(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM Blacklist + mapper.Execute(@"DELETE FROM Blacklist WHERE Id IN ( SELECT Blacklist.Id FROM Blacklist LEFT OUTER JOIN Movies diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs index 3bb631eb9..a5f584797 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedDownloadClientStatus.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,9 +14,9 @@ public CleanupOrphanedDownloadClientStatus(IMainDatabase database) public void Clean() { - var mapper = _database.GetDataMapper(); + var mapper = _database.OpenConnection(); - mapper.ExecuteNonQuery(@"DELETE FROM DownloadClientStatus + mapper.Execute(@"DELETE FROM DownloadClientStatus WHERE Id IN ( SELECT DownloadClientStatus.Id FROM DownloadClientStatus LEFT OUTER JOIN DownloadClients diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs index a8d071365..34a8d03a1 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedHistoryItems.cs @@ -1,3 +1,4 @@ +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -18,9 +19,9 @@ public void Clean() private void CleanupOrphanedByMovie() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM History + mapper.Execute(@"DELETE FROM History WHERE Id IN ( SELECT History.Id FROM History LEFT OUTER JOIN Movies diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs index 6f6a61267..039db9b52 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedIndexerStatus.cs @@ -1,3 +1,4 @@ +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -13,9 +14,9 @@ public CleanupOrphanedIndexerStatus(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM IndexerStatus + mapper.Execute(@"DELETE FROM IndexerStatus WHERE Id IN ( SELECT IndexerStatus.Id FROM IndexerStatus LEFT OUTER JOIN Indexers diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs index bf10b4fcf..4791d355b 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMetadataFiles.cs @@ -1,3 +1,4 @@ +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -20,9 +21,9 @@ public void Clean() private void DeleteOrphanedByMovie() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT MetadataFiles.Id FROM MetadataFiles LEFT OUTER JOIN Movies @@ -33,9 +34,9 @@ LEFT OUTER JOIN Movies private void DeleteOrphanedByMovieFile() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT MetadataFiles.Id FROM MetadataFiles LEFT OUTER JOIN MovieFiles @@ -47,9 +48,9 @@ WHERE MetadataFiles.MovieFileId > 0 private void DeleteWhereMovieFileIsZero() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles + mapper.Execute(@"DELETE FROM MetadataFiles WHERE Id IN ( SELECT Id FROM MetadataFiles WHERE Type IN (1, 2) diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMovieFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMovieFiles.cs index d36d1da66..ee94f857e 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMovieFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedMovieFiles.cs @@ -1,3 +1,4 @@ +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -13,14 +14,14 @@ public CleanupOrphanedMovieFiles(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM MovieFiles - WHERE Id IN ( - SELECT MovieFiles.Id FROM MovieFiles - LEFT OUTER JOIN Movies - ON MovieFiles.Id = Movies.MovieFileId - WHERE Movies.Id IS NULL)"); + mapper.Execute(@"DELETE FROM MovieFiles + WHERE Id IN ( + SELECT MovieFiles.Id FROM MovieFiles + LEFT OUTER JOIN Movies + ON MovieFiles.Id = Movies.MovieFileId + WHERE Movies.Id IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs index 71e9eded6..4cd347e30 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupOrphanedPendingReleases.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using Dapper; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers { @@ -13,14 +14,14 @@ public CleanupOrphanedPendingReleases(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases - WHERE Id IN ( - SELECT PendingReleases.Id FROM PendingReleases - LEFT OUTER JOIN Movies - ON PendingReleases.MovieId = Movies.Id - WHERE Movies.Id IS NULL)"); + mapper.Execute(@"DELETE FROM PendingReleases + WHERE Id IN ( + SELECT PendingReleases.Id FROM PendingReleases + LEFT OUTER JOIN Movies + ON PendingReleases.MovieId = Movies.Id + WHERE Movies.Id IS NULL)"); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs index 4f473b158..b12ea8563 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/CleanupUnusedTags.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; +using System.Data; using System.Linq; -using Marr.Data; -using NzbDrone.Common.Serializer; +using Dapper; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Housekeeping.Housekeepers @@ -17,7 +17,7 @@ public CleanupUnusedTags(IMainDatabase database) public void Clean() { - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { var usedTags = new[] { "Movies", "Notifications", "DelayProfiles", "Restrictions", "NetImport" } .SelectMany(v => GetUsedTags(v, mapper)) @@ -26,16 +26,16 @@ public void Clean() var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray()); - mapper.ExecuteNonQuery($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})"); + mapper.Execute($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})"); } } - private int[] GetUsedTags(string table, IDataMapper mapper) + private int[] GetUsedTags(string table, IDbConnection mapper) { - return mapper.ExecuteReader($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'", reader => reader.GetString(0)) - .SelectMany(Json.Deserialize>) - .Distinct() - .ToArray(); + return mapper.Query>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'") + .SelectMany(x => x) + .Distinct() + .ToArray(); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs index c4ed9f501..82a7af7fa 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixFutureRunScheduledTasks.cs @@ -1,4 +1,5 @@ using System; +using Dapper; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Datastore; @@ -23,13 +24,12 @@ public void Clean() _logger.Debug("Not running scheduled task last execution cleanup during debug"); } - using (var mapper = _database.GetDataMapper()) + using (var mapper = _database.OpenConnection()) { - mapper.AddParameter("time", DateTime.UtcNow); - - mapper.ExecuteNonQuery(@"UPDATE ScheduledTasks - SET LastExecution = @time - WHERE LastExecution > @time"); + mapper.Execute(@"UPDATE ScheduledTasks + SET LastExecution = @time + WHERE LastExecution > @time", + new { time = DateTime.UtcNow }); } } } diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixWronglyMatchedMovieFiles.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixWronglyMatchedMovieFiles.cs index 951c35d03..bcaf44fbb 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/FixWronglyMatchedMovieFiles.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/FixWronglyMatchedMovieFiles.cs @@ -15,7 +15,7 @@ public void Clean() { /*var mapper = _database.GetDataMapper(); - mapper.ExecuteNonQuery(@"UPDATE Movies + mapper.Execute(@"UPDATE Movies SET MovieFileId = (Select Id FROM MovieFiles WHERE Movies.Id == MovieFiles.MovieId) WHERE MovieFileId != diff --git a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs index a0c0adead..36025d64e 100644 --- a/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs +++ b/src/NzbDrone.Core/Instrumentation/ReconfigureLogging.cs @@ -4,9 +4,11 @@ using NLog.Config; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Instrumentation.Sentry; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.Instrumentation @@ -47,6 +49,10 @@ public void Reconfigure() SetMinimumLogLevel(rules, "appFileInfo", minimumLogLevel <= LogLevel.Info ? LogLevel.Info : LogLevel.Off); SetMinimumLogLevel(rules, "appFileDebug", minimumLogLevel <= LogLevel.Debug ? LogLevel.Debug : LogLevel.Off); SetMinimumLogLevel(rules, "appFileTrace", minimumLogLevel <= LogLevel.Trace ? LogLevel.Trace : LogLevel.Off); + SetLogRotation(); + + //Log Sql + SqlBuilderExtensions.LogSql = _configFileProvider.LogSql; //Sentry ReconfigureSentry(); @@ -77,6 +83,14 @@ private void SetMinimumLogLevel(LoggingRule rule, LogLevel minimumLogLevel) } } + private void SetLogRotation() + { + foreach (var target in LogManager.Configuration.AllTargets.OfType()) + { + target.MaxArchiveFiles = _configFileProvider.LogRotate; + } + } + private void ReconfigureSentry() { var sentryTarget = LogManager.Configuration.AllTargets.OfType().FirstOrDefault(); diff --git a/src/NzbDrone.Core/Jobs/ScheduledTaskRepository.cs b/src/NzbDrone.Core/Jobs/ScheduledTaskRepository.cs index 4b46b2efc..a36252004 100644 --- a/src/NzbDrone.Core/Jobs/ScheduledTaskRepository.cs +++ b/src/NzbDrone.Core/Jobs/ScheduledTaskRepository.cs @@ -20,7 +20,7 @@ public ScheduledTaskRepository(IMainDatabase database, IEventAggregator eventAgg public ScheduledTask GetDefinition(Type type) { - return Query.Where(c => c.TypeName == type.FullName).Single(); + return Query(x => x.TypeName == type.FullName).Single(); } public void SetLastExecutionTime(int id, DateTime executionTime, DateTime startTime) diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs index 4864a9ad3..e59bc8095 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileRepository.cs @@ -19,12 +19,12 @@ public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggrega public List GetFilesByMovie(int movieId) { - return Query.Where(c => c.MovieId == movieId).ToList(); + return Query(x => x.MovieId == movieId); } public List GetFilesWithoutMediaInfo() { - return Query.Where(c => c.MediaInfo == null).ToList(); + return Query(x => x.MediaInfo == null); } } } diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index 0b7f51faf..610bc8358 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using NLog; using NzbDrone.Common; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; @@ -25,29 +24,27 @@ public interface IMediaFileService public class MediaFileService : IMediaFileService, IHandleAsync { - private readonly IEventAggregator _eventAggregator; private readonly IMediaFileRepository _mediaFileRepository; - private readonly Logger _logger; + private readonly IMovieRepository _movieRepository; + private readonly IEventAggregator _eventAggregator; public MediaFileService(IMediaFileRepository mediaFileRepository, - IEventAggregator eventAggregator, - Logger logger) + IMovieRepository movieRepository, + IEventAggregator eventAggregator) { _mediaFileRepository = mediaFileRepository; + _movieRepository = movieRepository; _eventAggregator = eventAggregator; - _logger = logger; } public MovieFile Add(MovieFile movieFile) { var addedFile = _mediaFileRepository.Insert(movieFile); - addedFile.Movie.LazyLoad(); - if (addedFile.Movie == null || addedFile.Movie.Value == null) + if (addedFile.Movie == null) { - _logger.Error("Movie is null for the file {0}. Please run the houskeeping command to ensure movies and files are linked correctly."); + addedFile.Movie = _movieRepository.Get(movieFile.MovieId); } - //_movieService.SetFileId(addedFile.Movie.Value, addedFile); //Should not be necessary, but sometimes below fails? _eventAggregator.PublishEvent(new MovieFileAddedEvent(addedFile)); return addedFile; @@ -66,8 +63,12 @@ public void Update(List movieFiles) public void Delete(MovieFile movieFile, DeleteMediaFileReason reason) { //Little hack so we have the movie attached for the event consumers - movieFile.Movie.LazyLoad(); - movieFile.Path = Path.Combine(movieFile.Movie.Value.Path, movieFile.RelativePath); + if (movieFile.Movie == null) + { + movieFile.Movie = _movieRepository.Get(movieFile.MovieId); + } + + movieFile.Path = Path.Combine(movieFile.Movie.Path, movieFile.RelativePath); _mediaFileRepository.Delete(movieFile); _eventAggregator.PublishEvent(new MovieFileDeletedEvent(movieFile, reason)); diff --git a/src/NzbDrone.Core/MediaFiles/MovieFile.cs b/src/NzbDrone.Core/MediaFiles/MovieFile.cs index 24efa96db..c6ee6dc8f 100644 --- a/src/NzbDrone.Core/MediaFiles/MovieFile.cs +++ b/src/NzbDrone.Core/MediaFiles/MovieFile.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Marr.Data; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; @@ -23,7 +22,7 @@ public class MovieFile : ModelBase public List Languages { get; set; } public MediaInfoModel MediaInfo { get; set; } public string Edition { get; set; } - public LazyLoaded Movie { get; set; } + public Movie Movie { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs index 7c6460cbc..1bf894ecc 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SQLite; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -10,22 +10,16 @@ public interface ICommandRepository : IBasicRepository { void Trim(); void OrphanStarted(); - List FindCommands(string name); - List FindQueuedOrStarted(string name); List Queued(); - List Started(); void Start(CommandModel command); void End(CommandModel command); } public class CommandRepository : BasicRepository, ICommandRepository { - private readonly IMainDatabase _database; - public CommandRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { - _database = database; } public void Trim() @@ -37,38 +31,23 @@ public void Trim() public void OrphanStarted() { - using (var mapper = _database.GetDataMapper()) + var sql = @"UPDATE Commands SET Status = @Orphaned, EndedAt = @Ended WHERE Status = @Started"; + var args = new + { + Orphaned = (int)CommandStatus.Orphaned, + Started = (int)CommandStatus.Started, + Ended = DateTime.UtcNow + }; + + using (var conn = _database.OpenConnection()) { - mapper.Parameters.Add(new SQLiteParameter("@orphaned", (int)CommandStatus.Orphaned)); - mapper.Parameters.Add(new SQLiteParameter("@started", (int)CommandStatus.Started)); - mapper.Parameters.Add(new SQLiteParameter("@ended", DateTime.UtcNow)); - - mapper.ExecuteNonQuery(@"UPDATE Commands - SET Status = @orphaned, EndedAt = @ended - WHERE Status = @started"); + conn.Execute(sql, args); } } - public List FindCommands(string name) - { - return Query.Where(c => c.Name == name).ToList(); - } - - public List FindQueuedOrStarted(string name) - { - return Query.Where(c => c.Name == name) - .AndWhere("[Status] IN (0,1)") - .ToList(); - } - public List Queued() { - return Query.Where(c => c.Status == CommandStatus.Queued); - } - - public List Started() - { - return Query.Where(c => c.Status == CommandStatus.Started); + return Query(x => x.Status == CommandStatus.Queued); } public void Start(CommandModel command) diff --git a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs index 07c7db292..6360ffe23 100644 --- a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs +++ b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitle.cs @@ -1,4 +1,3 @@ -using Marr.Data; using NzbDrone.Core.Datastore; using NzbDrone.Core.Languages; using NzbDrone.Core.Parser; @@ -15,7 +14,6 @@ public class AlternativeTitle : ModelBase public int Votes { get; set; } public int VoteCount { get; set; } public Language Language { get; set; } - public LazyLoaded Movie { get; set; } public AlternativeTitle() { diff --git a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs index 6140a5d5b..1e1ede2fd 100644 --- a/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs +++ b/src/NzbDrone.Core/Movies/AlternativeTitles/AlternativeTitleRepository.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -15,27 +14,24 @@ public interface IAlternativeTitleRepository : IBasicRepository, IAlternativeTitleRepository { - protected IMainDatabase _database; - public AlternativeTitleRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { - _database = database; } public AlternativeTitle FindBySourceId(int sourceId) { - return Query.Where(t => t.SourceId == sourceId).FirstOrDefault(); + return Query(x => x.SourceId == sourceId).FirstOrDefault(); } public List FindBySourceIds(List sourceIds) { - return Query.Where(t => t.SourceId.In(sourceIds)).ToList(); + return Query(x => sourceIds.Contains(x.SourceId)); } public List FindByMovieId(int movieId) { - return Query.Where(t => t.MovieId == movieId).ToList(); + return Query(x => x.MovieId == movieId); } } } diff --git a/src/NzbDrone.Core/Movies/Movie.cs b/src/NzbDrone.Core/Movies/Movie.cs index 60eecceb1..e99d46284 100644 --- a/src/NzbDrone.Core/Movies/Movie.cs +++ b/src/NzbDrone.Core/Movies/Movie.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Marr.Data; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; @@ -47,7 +46,7 @@ public Movie() public DateTime? InCinemas { get; set; } public DateTime? PhysicalRelease { get; set; } public string PhysicalReleaseNote { get; set; } - public LazyLoaded Profile { get; set; } + public Profile Profile { get; set; } public HashSet Tags { get; set; } public AddMovieOptions AddOptions { get; set; } public MovieFile MovieFile { get; set; } diff --git a/src/NzbDrone.Core/Movies/MovieRepository.cs b/src/NzbDrone.Core/Movies/MovieRepository.cs index b76bca489..89cd95bbf 100644 --- a/src/NzbDrone.Core/Movies/MovieRepository.cs +++ b/src/NzbDrone.Core/Movies/MovieRepository.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using Marr.Data.QGen; +using Dapper; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Datastore.Extensions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies.AlternativeTitles; -using NzbDrone.Core.Parser.RomanNumerals; +using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Movies @@ -15,11 +14,11 @@ namespace NzbDrone.Core.Movies public interface IMovieRepository : IBasicRepository { bool MoviePathExists(string path); - Movie FindByTitle(string cleanTitle); - Movie FindByTitle(string cleanTitle, int year); + List FindByTitles(List titles); List FindByTitleInexact(string cleanTitle); Movie FindByImdbId(string imdbid); Movie FindByTmdbId(int tmdbid); + List FindByTmdbId(List tmdbids); Movie FindByTitleSlug(string slug); List MoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored); List MoviesWithFiles(int movieId); @@ -28,42 +27,116 @@ public interface IMovieRepository : IBasicRepository void SetFileId(int fileId, int movieId); PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); Movie FindByPath(string path); + List AllMoviePaths(); } public class MovieRepository : BasicRepository, IMovieRepository { - protected IMainDatabase _database; - - public MovieRepository(IMainDatabase database, IEventAggregator eventAggregator) + private readonly IProfileRepository _profileRepository; + public MovieRepository(IMainDatabase database, + IProfileRepository profileRepository, + IEventAggregator eventAggregator) : base(database, eventAggregator) { - _database = database; + _profileRepository = profileRepository; + } + + protected override SqlBuilder BuilderBase() => new SqlBuilder() + .Join((m, p) => m.ProfileId == p.Id) + .LeftJoin((m, t) => m.Id == t.MovieId) + .LeftJoin((m, f) => m.Id == f.MovieId); + + private Movie Map(Dictionary dict, Movie movie, Profile profile, AlternativeTitle altTitle, MovieFile movieFile) + { + Movie movieEntry; + + if (!dict.TryGetValue(movie.Id, out movieEntry)) + { + movieEntry = movie; + movieEntry.Profile = profile; + movieEntry.MovieFile = movieFile; + dict.Add(movieEntry.Id, movieEntry); + } + + if (altTitle != null) + { + movieEntry.AlternativeTitles.Add(altTitle); + } + + return movieEntry; + } + + protected override IEnumerable GetResults(SqlBuilder.Template sql) + { + var movieDictionary = new Dictionary(); + + using (var conn = _database.OpenConnection()) + { + conn.Query( + sql.RawSql, + (movie, profile, altTitle, file) => Map(movieDictionary, movie, profile, altTitle, file), + sql.Parameters); + } + + return movieDictionary.Values; + } + + public override IEnumerable All() + { + // the skips the join on profile and populates manually + // to avoid repeatedly deserializing the same profile + var noProfileTemplate = $"SELECT /**select**/ FROM {_table} /**leftjoin**/ /**where**/ /**orderby**/"; + var sql = Builder().AddTemplate(noProfileTemplate).LogQuery(); + + var movieDictionary = new Dictionary(); + var profiles = _profileRepository.All().ToDictionary(x => x.Id); + + using (var conn = _database.OpenConnection()) + { + conn.Query( + sql.RawSql, + (movie, altTitle, file) => Map(movieDictionary, movie, profiles[movie.ProfileId], altTitle, file), + sql.Parameters); + } + + return movieDictionary.Values; } public bool MoviePathExists(string path) { - return Query.Where(c => c.Path == path).Any(); + return Query(x => x.Path == path).Any(); } - public Movie FindByTitle(string cleanTitle) + public List FindByTitles(List titles) { - return FindByTitle(cleanTitle, null); + return Query(Builder().OrWhere(x => titles.Contains(x.CleanTitle)) + .OrWhere(x => titles.Contains(x.CleanTitle))); } - public Movie FindByTitle(string cleanTitle, int year) + public List FindByTitleInexact(string cleanTitle) { - return FindByTitle(cleanTitle, year as int?); + return Query(x => cleanTitle.Contains(x.CleanTitle)); } public Movie FindByImdbId(string imdbid) { var imdbIdWithPrefix = Parser.Parser.NormalizeImdbId(imdbid); - return Query.Where(s => s.ImdbId == imdbIdWithPrefix).SingleOrDefault(); + return Query(x => x.ImdbId == imdbIdWithPrefix).FirstOrDefault(); + } + + public Movie FindByTmdbId(int tmdbid) + { + return Query(x => x.TmdbId == tmdbid).FirstOrDefault(); + } + + public List FindByTmdbId(List tmdbids) + { + return Query(x => tmdbids.Contains(x.TmdbId)); } public List GetMoviesByFileId(int fileId) { - return Query.Where(m => m.MovieFileId == fileId).ToList(); + return Query(x => x.MovieFileId == fileId); } public void SetFileId(int fileId, int movieId) @@ -73,66 +146,51 @@ public void SetFileId(int fileId, int movieId) public Movie FindByTitleSlug(string slug) { - return Query.Where(m => m.TitleSlug == slug).FirstOrDefault(); + return Query(x => x.TitleSlug == slug).FirstOrDefault(); } public List MoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored) { - var query = Query.Where(m => - (m.InCinemas >= start && m.InCinemas <= end) || - (m.PhysicalRelease >= start && m.PhysicalRelease <= end)); + var builder = Builder() + .Where(m => + (m.InCinemas >= start && m.InCinemas <= end) || + (m.PhysicalRelease >= start && m.PhysicalRelease <= end)); if (!includeUnmonitored) { - query.AndWhere(e => e.Monitored == true); + builder.Where(x => x.Monitored == true); } - return query.ToList(); + return Query(builder); } public List MoviesWithFiles(int movieId) { - return Query.Join(JoinType.Inner, m => m.MovieFile, (m, mf) => m.MovieFileId == mf.Id) - .Where(m => m.Id == movieId).ToList(); + return Query(x => x.MovieFileId != 0); } + public SqlBuilder MoviesWithoutFilesBuilder() => BuilderBase().Where(x => x.MovieFileId == 0); + public PagingSpec MoviesWithoutFiles(PagingSpec pagingSpec) { - pagingSpec.TotalRecords = GetMoviesWithoutFilesQuery(pagingSpec).GetRowCount(); - pagingSpec.Records = GetMoviesWithoutFilesQuery(pagingSpec).ToList(); + pagingSpec.Records = GetPagedRecords(MoviesWithoutFilesBuilder().SelectAll(), pagingSpec, PagedSelector); + pagingSpec.TotalRecords = GetPagedRecordCount(MoviesWithoutFilesBuilder().SelectCount(), pagingSpec); return pagingSpec; } - public SortBuilder GetMoviesWithoutFilesQuery(PagingSpec pagingSpec) - { - return Query.Where(pagingSpec.FilterExpressions.FirstOrDefault()) - .AndWhere(m => m.MovieFileId == 0) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); - } + public SqlBuilder MoviesWhereCutoffUnmetBuilder(List qualitiesBelowCutoff) => BuilderBase() + .Where(x => x.MovieFileId != 0) + .Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); public PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) { - pagingSpec.TotalRecords = MoviesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).GetRowCount(); - pagingSpec.Records = MoviesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).ToList(); + pagingSpec.Records = GetPagedRecords(MoviesWhereCutoffUnmetBuilder(qualitiesBelowCutoff).SelectAll(), pagingSpec, PagedSelector); + pagingSpec.TotalRecords = GetPagedRecordCount(MoviesWhereCutoffUnmetBuilder(qualitiesBelowCutoff).SelectCount(), pagingSpec); return pagingSpec; } - private SortBuilder MoviesWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) - { - return Query - .Join(JoinType.Left, e => e.MovieFile, (e, s) => e.MovieFileId == s.Id) - .Where(pagingSpec.FilterExpressions.FirstOrDefault()) - .AndWhere(m => m.MovieFileId != 0) - .AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) - .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) - .Skip(pagingSpec.PagingOffset()) - .Take(pagingSpec.PageSize); - } - private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) { var clauses = new List(); @@ -141,89 +199,24 @@ private string BuildQualityCutoffWhereClause(List qualitie { foreach (var belowCutoff in profile.QualityIds) { - clauses.Add(string.Format("([t0].[ProfileId] = {0} AND [t2].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); + clauses.Add(string.Format($"(\"{_table}\".\"ProfileId\" = {profile.ProfileId} AND \"MovieFile\".\"Quality\" LIKE '%_quality_: {belowCutoff},%')")); } } return string.Format("({0})", string.Join(" OR ", clauses)); } - private string BuildQualityCutoffWhereClauseSpecial(List qualitiesBelowCutoff) - { - var clauses = new List(); - - foreach (var profile in qualitiesBelowCutoff) - { - foreach (var belowCutoff in profile.QualityIds) - { - clauses.Add(string.Format("(Movies.ProfileId = {0} AND MovieFiles.Quality LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); - } - } - - return string.Format("({0})", string.Join(" OR ", clauses)); - } - - private Movie FindByTitle(string cleanTitle, int? year) - { - cleanTitle = cleanTitle.ToLowerInvariant(); - string cleanTitleWithRomanNumbers = cleanTitle; - string cleanTitleWithArabicNumbers = cleanTitle; - - foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping()) - { - string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString; - string romanNumber = arabicRomanNumeral.RomanNumeral; - cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber); - cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber); - } - - Movie result = Query.Where(s => s.CleanTitle == cleanTitle).FirstWithYear(year); - - if (result == null) - { - result = Query.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers || movie.CleanTitle == cleanTitleWithRomanNumbers) - .FirstWithYear(year); - - if (result == null) - { - result = Query.Where(t => t.CleanTitle == cleanTitle || t.CleanTitle == cleanTitleWithArabicNumbers || t.CleanTitle == cleanTitleWithRomanNumbers) - .FirstWithYear(year); - } - } - - return result; - } - - public List FindByTitleInexact(string cleanTitle) - { - var mapper = _database.GetDataMapper(); - mapper.AddParameter("queryTitle", cleanTitle); - - return AddJoinQueries(mapper.Query()).Where($"instr(@queryTitle, [t0].[CleanTitle])"); - } - - public Movie FindByTmdbId(int tmdbid) - { - return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); - } - public Movie FindByPath(string path) { - return Query.Where(s => s.Path == path) - .FirstOrDefault(); + return Query(x => x.Path == path).FirstOrDefault(); } - protected override QueryBuilder AddJoinQueries(QueryBuilder baseQuery) + public List AllMoviePaths() { - baseQuery = base.AddJoinQueries(baseQuery); - baseQuery = baseQuery.Join(JoinType.Left, - m => m.AlternativeTitles, - (m, t) => m.Id == t.MovieId); - baseQuery = baseQuery.Join(JoinType.Left, - m => m.MovieFile, - (m, f) => m.Id == f.MovieId); - - return baseQuery; + using (var conn = _database.OpenConnection()) + { + return conn.Query("SELECT Path FROM Movies").ToList(); + } } } } diff --git a/src/NzbDrone.Core/Movies/MovieService.cs b/src/NzbDrone.Core/Movies/MovieService.cs index 4488f39ff..4e6e21117 100644 --- a/src/NzbDrone.Core/Movies/MovieService.cs +++ b/src/NzbDrone.Core/Movies/MovieService.cs @@ -14,6 +14,7 @@ using NzbDrone.Core.NetImport.ImportExclusions; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.RomanNumerals; namespace NzbDrone.Core.Movies { @@ -26,11 +27,13 @@ public interface IMovieService List AddMovies(List newMovies); Movie FindByImdbId(string imdbid); Movie FindByTmdbId(int tmdbid); + List FindByTmdbId(List tmdbids); Movie FindByTitle(string title); Movie FindByTitle(string title, int year); Movie FindByTitleInexact(string title, int? year); Movie FindByTitleSlug(string slug); Movie FindByPath(string path); + List AllMoviePaths(); bool MovieExists(Movie movie); Movie GetMovieByFileId(int fileId); List GetMoviesBetweenDates(DateTime start, DateTime end, bool includeUnmonitored); @@ -207,11 +210,12 @@ public List AddMovies(List newMovies) m.Added = DateTime.UtcNow; }); - var existingMovies = GetAllMovies(); var potentialMovieCount = newMovies.Count; newMovies = newMovies.DistinctBy(movie => movie.TmdbId).ToList(); // Ensure we don't add the same movie twice + var existingMovies = FindByTmdbId(newMovies.Select(x => x.TmdbId).ToList()); + newMovies = newMovies.ExceptBy(n => n.TmdbId, existingMovies, e => e.TmdbId, EqualityComparer.Default).ToList(); // Ensure we don't add a movie that already exists _movieRepository.InsertMany(newMovies); @@ -225,7 +229,49 @@ public List AddMovies(List newMovies) public Movie FindByTitle(string title) { - return _movieRepository.FindByTitle(title.CleanSeriesTitle()); + return FindByTitle(title.CleanSeriesTitle(), null); + } + + public Movie FindByTitle(string title, int year) + { + return FindByTitle(title.CleanSeriesTitle(), year as int?); + } + + private Movie FindByTitle(string cleanTitle, int? year) + { + cleanTitle = cleanTitle.ToLowerInvariant(); + string cleanTitleWithRomanNumbers = cleanTitle; + string cleanTitleWithArabicNumbers = cleanTitle; + + foreach (ArabicRomanNumeral arabicRomanNumeral in RomanNumeralParser.GetArabicRomanNumeralsMapping()) + { + string arabicNumber = arabicRomanNumeral.ArabicNumeralAsString; + string romanNumber = arabicRomanNumeral.RomanNumeral; + cleanTitleWithRomanNumbers = cleanTitleWithRomanNumbers.Replace(arabicNumber, romanNumber); + cleanTitleWithArabicNumbers = cleanTitleWithArabicNumbers.Replace(romanNumber, arabicNumber); + } + + var candidates = _movieRepository.FindByTitles(new List { cleanTitle, cleanTitleWithArabicNumbers, cleanTitleWithRomanNumbers }); + + var result = candidates.Where(x => x.CleanTitle == cleanTitle).FirstWithYear(year); + + if (result == null) + { + result = + candidates.Where(movie => movie.CleanTitle == cleanTitleWithArabicNumbers).FirstWithYear(year) ?? + candidates.Where(movie => movie.CleanTitle == cleanTitleWithRomanNumbers).FirstWithYear(year); + + if (result == null) + { + result = candidates + .Where(m => m.AlternativeTitles.Any(t => t.CleanTitle == cleanTitle || + t.CleanTitle == cleanTitleWithArabicNumbers || + t.CleanTitle == cleanTitleWithRomanNumbers)) + .FirstWithYear(year); + } + } + + return result; } public Movie FindByImdbId(string imdbid) @@ -238,6 +284,11 @@ public Movie FindByTmdbId(int tmdbid) return _movieRepository.FindByTmdbId(tmdbid); } + public List FindByTmdbId(List tmdbids) + { + return _movieRepository.FindByTmdbId(tmdbids); + } + private List FindByTitleInexactAll(string title) { // find any movie clean title within the provided release title @@ -289,16 +340,16 @@ public Movie FindByTitleInexact(string title, int? year) return FindByTitleInexactAll(title).FirstWithYear(year); } - public Movie FindByTitle(string title, int year) - { - return _movieRepository.FindByTitle(title.CleanSeriesTitle(), year); - } - public Movie FindByPath(string path) { return _movieRepository.FindByPath(path); } + public List AllMoviePaths() + { + return _movieRepository.AllMoviePaths(); + } + public void DeleteMovie(int movieId, bool deleteFiles, bool addExclusion = false) { var movie = _movieRepository.Get(movieId); @@ -369,12 +420,12 @@ public void RemoveAddOptions(Movie movie) public void Handle(MovieFileAddedEvent message) { - var movie = message.MovieFile.Movie.Value; + var movie = message.MovieFile.Movie; movie.MovieFileId = message.MovieFile.Id; _movieRepository.Update(movie); //_movieRepository.SetFileId(message.MovieFile.Id, message.MovieFile.Movie.Value.Id); - _logger.Info("Linking [{0}] > [{1}]", message.MovieFile.RelativePath, message.MovieFile.Movie.Value); + _logger.Info("Linking [{0}] > [{1}]", message.MovieFile.RelativePath, message.MovieFile.Movie); } public void SetFileId(Movie movie, MovieFile movieFile) @@ -450,7 +501,7 @@ public bool MovieExists(Movie movie) if (movie.Year > 1850) { - result = _movieRepository.FindByTitle(movie.Title.CleanSeriesTitle(), movie.Year); + result = FindByTitle(movie.Title.CleanSeriesTitle(), movie.Year); if (result != null) { return true; @@ -458,7 +509,7 @@ public bool MovieExists(Movie movie) } else { - result = _movieRepository.FindByTitle(movie.Title.CleanSeriesTitle()); + result = FindByTitle(movie.Title.CleanSeriesTitle()); if (result != null) { return true; diff --git a/src/NzbDrone.Core/Movies/QueryExtensions.cs b/src/NzbDrone.Core/Movies/QueryExtensions.cs index 17847d50a..c2876631a 100644 --- a/src/NzbDrone.Core/Movies/QueryExtensions.cs +++ b/src/NzbDrone.Core/Movies/QueryExtensions.cs @@ -1,17 +1,8 @@ using System.Collections.Generic; using System.Linq; -using Marr.Data.QGen; namespace NzbDrone.Core.Movies { - public static class QueryExtensions - { - public static Movie FirstWithYear(this SortBuilder query, int? year) - { - return year.HasValue ? query.AndWhere(movie => movie.Year == year || movie.SecondaryYear == year).FirstOrDefault() : query.FirstOrDefault(); - } - } - public static class EnumerableExtensions { public static Movie FirstWithYear(this IEnumerable query, int? year) diff --git a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs index 6e0bc33bd..9c072991b 100644 --- a/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs +++ b/src/NzbDrone.Core/NetImport/ImportExclusions/ImportExclusionsRepository.cs @@ -12,22 +12,19 @@ public interface IImportExclusionsRepository : IBasicRepository public class ImportExclusionsRepository : BasicRepository, IImportExclusionsRepository { - protected IMainDatabase _database; - public ImportExclusionsRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { - _database = database; } public bool IsMovieExcluded(int tmdbid) { - return Query.Where(ex => ex.TmdbId == tmdbid).Any(); + return Query(x => x.TmdbId == tmdbid).Any(); } public ImportExclusion GetByTmdbid(int tmdbid) { - return Query.Where(ex => ex.TmdbId == tmdbid).First(); + return Query(x => x.TmdbId == tmdbid).First(); } } } diff --git a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs index 380472246..2ca6d0f27 100644 --- a/src/NzbDrone.Core/NetImport/NetImportDefinition.cs +++ b/src/NzbDrone.Core/NetImport/NetImportDefinition.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using Marr.Data; using NzbDrone.Core.Movies; -using NzbDrone.Core.Profiles; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.NetImport @@ -18,7 +16,6 @@ public NetImportDefinition() public bool ShouldMonitor { get; set; } public MovieStatusType MinimumAvailability { get; set; } public int ProfileId { get; set; } - public LazyLoaded Profile { get; set; } public string RootFolderPath { get; set; } public override bool Enable => Enabled; } diff --git a/src/NzbDrone.Core/Profiles/ProfileRepository.cs b/src/NzbDrone.Core/Profiles/ProfileRepository.cs index b49546dc6..b50d15ed8 100644 --- a/src/NzbDrone.Core/Profiles/ProfileRepository.cs +++ b/src/NzbDrone.Core/Profiles/ProfileRepository.cs @@ -17,7 +17,7 @@ public ProfileRepository(IMainDatabase database, IEventAggregator eventAggregato public bool Exists(int id) { - return DataMapper.Query().Where(p => p.Id == id).GetRowCount() == 1; + return Query(x => x.Id == id).Count == 1; } } } diff --git a/src/NzbDrone.Core/Qualities/Revision.cs b/src/NzbDrone.Core/Qualities/Revision.cs index 4847150ff..6bdbeb53d 100644 --- a/src/NzbDrone.Core/Qualities/Revision.cs +++ b/src/NzbDrone.Core/Qualities/Revision.cs @@ -5,8 +5,9 @@ namespace NzbDrone.Core.Qualities { public class Revision : IEquatable, IComparable { - private Revision() + public Revision() { + Version = 1; } public Revision(int version = 1, int real = 0, bool isRepack = false) diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj index 254dd81ce..01e4f6b27 100644 --- a/src/NzbDrone.Core/Radarr.Core.csproj +++ b/src/NzbDrone.Core/Radarr.Core.csproj @@ -3,6 +3,8 @@ net462;netcoreapp3.1 + + @@ -15,9 +17,9 @@ + - diff --git a/src/NzbDrone.Core/Tags/TagRepository.cs b/src/NzbDrone.Core/Tags/TagRepository.cs index a35228094..037027dea 100644 --- a/src/NzbDrone.Core/Tags/TagRepository.cs +++ b/src/NzbDrone.Core/Tags/TagRepository.cs @@ -20,7 +20,7 @@ public TagRepository(IMainDatabase database, IEventAggregator eventAggregator) public Tag GetByLabel(string label) { - var model = Query.Where(c => c.Label == label).SingleOrDefault(); + var model = FindByLabel(label); if (model == null) { @@ -32,7 +32,7 @@ public Tag GetByLabel(string label) public Tag FindByLabel(string label) { - return Query.Where(c => c.Label == label).SingleOrDefault(); + return Query(x => x.Label == label).SingleOrDefault(); } } } diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs index 305f80efb..c78b29d7d 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs @@ -1,20 +1,52 @@ -using NzbDrone.Core.Datastore; +using System.Collections.Generic; +using System.Text.Json; +using Dapper; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Reflection; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.ThingiProvider { public class ProviderRepository : BasicRepository, IProviderRepository - where TProviderDefinition : ModelBase, - new() + where TProviderDefinition : ProviderDefinition, new() { protected ProviderRepository(IMainDatabase database, IEventAggregator eventAggregator) : base(database, eventAggregator) { } - // public void DeleteImplementations(string implementation) - // { - // DataMapper.Delete(c => c.Implementation == implementation); - // } + protected override IEnumerable GetResults(SqlBuilder.Template sql) + { + var results = new List(); + + using (var conn = _database.OpenConnection()) + using (var reader = conn.ExecuteReader(sql.RawSql, sql.Parameters)) + { + var parser = reader.GetRowParser(typeof(TProviderDefinition)); + var settingsIndex = reader.GetOrdinal("Settings"); + var serializerSettings = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + + while (reader.Read()) + { + var body = reader.IsDBNull(settingsIndex) ? null : reader.GetString(settingsIndex); + var item = parser(reader); + var impType = typeof(IProviderConfig).Assembly.FindTypeByName(item.ConfigContract); + + if (body.IsNullOrWhiteSpace()) + { + item.Settings = NullConfig.Instance; + } + else + { + item.Settings = (IProviderConfig)JsonSerializer.Deserialize(body, impType, serializerSettings); + } + + results.Add(item); + } + } + + return results; + } } } diff --git a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusRepository.cs b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusRepository.cs index 071f0ea9f..7a2d1bc78 100644 --- a/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/Status/ProviderStatusRepository.cs @@ -20,7 +20,7 @@ public ProviderStatusRepository(IMainDatabase database, IEventAggregator eventAg public TModel FindByProviderId(int providerId) { - return Query.Where(c => c.ProviderId == providerId).SingleOrDefault(); + return Query(x => x.ProviderId == providerId).SingleOrDefault(); } } } diff --git a/src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs b/src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs index ad8ccf7eb..1d87badb5 100644 --- a/src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/MovieAncestorValidator.cs @@ -22,7 +22,7 @@ protected override bool IsValid(PropertyValidatorContext context) return true; } - return !_movieService.GetAllMovies().Any(s => context.PropertyValue.ToString().IsParentPath(s.Path)); + return !_movieService.AllMoviePaths().Any(s => context.PropertyValue.ToString().IsParentPath(s)); } } } diff --git a/src/NzbDrone.Host.Test/ContainerFixture.cs b/src/NzbDrone.Host.Test/ContainerFixture.cs index 8a7f8e021..7db0bf259 100644 --- a/src/NzbDrone.Host.Test/ContainerFixture.cs +++ b/src/NzbDrone.Host.Test/ContainerFixture.cs @@ -6,6 +6,7 @@ using NzbDrone.Common; using NzbDrone.Common.Composition; using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; @@ -37,6 +38,11 @@ public void SetUp() // set up a dummy broadcaster to allow tests to resolve var mockBroadcaster = new Mock(); _container.Register(mockBroadcaster.Object); + + // A dummy custom format repository since this isn't a DB test + var mockCustomFormat = Mocker.GetMock(); + mockCustomFormat.Setup(x => x.All()).Returns(new List()); + _container.Register(mockCustomFormat.Object); } [Test] diff --git a/src/NzbDrone.Test.Common/NzbDroneRunner.cs b/src/NzbDrone.Test.Common/NzbDroneRunner.cs index 55fce6be9..2fc0f4cfb 100644 --- a/src/NzbDrone.Test.Common/NzbDroneRunner.cs +++ b/src/NzbDrone.Test.Common/NzbDroneRunner.cs @@ -75,11 +75,11 @@ public void Start() if (statusCall.ResponseStatus == ResponseStatus.Completed) { - Console.WriteLine("Radarr is started. Running Tests"); + TestContext.Progress.WriteLine("Radarr is started. Running Tests"); return; } - Console.WriteLine("Waiting for Radarr to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException.Message); + TestContext.Progress.WriteLine("Waiting for Radarr to start. Response Status : {0} [{1}] {2}", statusCall.ResponseStatus, statusCall.StatusDescription, statusCall.ErrorException.Message); Thread.Sleep(500); } @@ -113,7 +113,7 @@ private void Start(string outputRadarrConsoleExe) private void OnOutputDataReceived(string data) { - Console.WriteLine(data); + TestContext.Progress.WriteLine(data); if (data.Contains("Press enter to exit")) { diff --git a/src/Radarr.Api.V3/History/HistoryModule.cs b/src/Radarr.Api.V3/History/HistoryModule.cs index 8c1f57a9f..d7059bafd 100644 --- a/src/Radarr.Api.V3/History/HistoryModule.cs +++ b/src/Radarr.Api.V3/History/HistoryModule.cs @@ -44,7 +44,7 @@ protected HistoryResource MapToResource(NzbDrone.Core.History.History model, boo if (model.Movie != null) { - resource.QualityCutoffNotMet = _upgradableSpecification.CutoffNotMet(model.Movie.Profile.Value, model.Quality); + resource.QualityCutoffNotMet = _upgradableSpecification.CutoffNotMet(model.Movie.Profile, model.Quality); } return resource; diff --git a/src/Radarr.Api.V3/Indexers/ReleaseModuleBase.cs b/src/Radarr.Api.V3/Indexers/ReleaseModuleBase.cs index e37307f14..65ecadc5c 100644 --- a/src/Radarr.Api.V3/Indexers/ReleaseModuleBase.cs +++ b/src/Radarr.Api.V3/Indexers/ReleaseModuleBase.cs @@ -29,7 +29,7 @@ protected virtual ReleaseResource MapDecision(DownloadDecision decision, int ini if (decision.RemoteMovie.Movie != null) { release.QualityWeight = decision.RemoteMovie.Movie - .Profile.Value.GetIndex(release.Quality.Quality).Index * 100; + .Profile.GetIndex(release.Quality.Quality).Index * 100; } release.QualityWeight += release.Quality.Revision.Real * 10; diff --git a/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs b/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs index c6339f464..f81c8a437 100644 --- a/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs +++ b/src/Radarr.Api.V3/MovieFiles/MovieFileResource.cs @@ -70,9 +70,7 @@ public static MovieFileResource ToResource(this MovieFile model, NzbDrone.Core.M SceneName = model.SceneName, Quality = model.Quality, Languages = model.Languages, - MediaInfo = model.MediaInfo.ToResource(model.SceneName), - - // QualityCutoffNotMet = upgradableSpecification.CutoffNotMet(movie.Profile.Value, model.Quality) + MediaInfo = model.MediaInfo.ToResource(model.SceneName) }; } @@ -96,7 +94,7 @@ public static MovieFileResource ToResource(this MovieFile model, NzbDrone.Core.M Quality = model.Quality, Languages = model.Languages, MediaInfo = model.MediaInfo.ToResource(model.SceneName), - QualityCutoffNotMet = upgradableSpecification.CutoffNotMet(movie.Profile.Value, model.Quality) + QualityCutoffNotMet = upgradableSpecification.CutoffNotMet(movie.Profile, model.Quality) }; } }