Swap to dapper with lazyload

pull/1689/head
ta264 4 years ago committed by Qstick
parent 20269aba9b
commit 53c0ffd129

@ -3,7 +3,6 @@
<TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj" />
<ProjectReference Include="..\Lidarr.Http\Lidarr.Http.csproj" />
<ProjectReference Include="..\NzbDrone.Common\Lidarr.Common.csproj" />
<ProjectReference Include="..\NzbDrone.Core\Lidarr.Core.csproj" />

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
@ -10,34 +11,230 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore
{
[TestFixture]
public class
BasicRepositoryFixture : DbTest<BasicRepository<ScheduledTask>, ScheduledTask>
public class BasicRepositoryFixture : DbTest<BasicRepository<ScheduledTask>, ScheduledTask>
{
private ScheduledTask _basicType;
private List<ScheduledTask> _basicList;
[SetUp]
public void Setup()
{
_basicType = Builder<ScheduledTask>
.CreateNew()
.With(c => c.Id = 0)
.With(c => c.LastExecution = DateTime.UtcNow)
.Build();
_basicList = Builder<ScheduledTask>
.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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<int>()).Should().BeEquivalentTo(new List<ScheduledTask>());
}
[Test]
public void get_many_should_throw_if_not_all_found()
{
Subject.InsertMany(_basicList);
Assert.Throws<ApplicationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<ScheduledTask>.CreateListOfSize(10).BuildListOfNew());
Subject.InsertMany(_basicList);
AllStoredModels.Should().HaveCount(10);
AllStoredModels.Should().HaveCount(5);
Subject.Purge();
@ -45,29 +242,29 @@ namespace NzbDrone.Core.Test.Datastore
}
[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<InvalidOperationException>(() => 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 @@ namespace NzbDrone.Core.Test.Datastore
}
[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<ScheduledTask>() { 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));
}
}
}

@ -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<Core.Datastore.Converters.BooleanIntConverter>
{
[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<ConversionException>(() => Subject.FromDB(context));
}
}
}

@ -1,10 +1,8 @@
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.Messaging.Commands;
using NzbDrone.Core.Music.Commands;
@ -15,67 +13,50 @@ namespace NzbDrone.Core.Test.Datastore.Converters
[TestFixture]
public class CommandConverterFixture : CoreTest<CommandConverter>
{
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 RefreshArtistCommand();
Subject.ToDB(command).Should().BeOfType<string>();
Subject.SetValue(_param, command);
_param.Value.Should().BeOfType<string>();
}
[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<IDataRecord>();
dataRecordMock.Setup(s => s.GetOrdinal("Name")).Returns(0);
dataRecordMock.Setup(s => s.GetString(0)).Returns("RefreshArtist");
var context = new ConverterContext
{
DataRecord = dataRecordMock.Object,
DbValue = new RefreshArtistCommand().ToJson()
};
var data = "{\"name\": \"RefreshArtist\"}";
Subject.FromDB(context).Should().BeOfType<RefreshArtistCommand>();
Subject.Parse(data).Should().BeOfType<RefreshArtistCommand>();
}
[Test]
public void should_return_unknown_command_when_getting_json_from_db()
{
var dataRecordMock = new Mock<IDataRecord>();
dataRecordMock.Setup(s => s.GetOrdinal("Name")).Returns(0);
dataRecordMock.Setup(s => s.GetString(0)).Returns("MockRemovedCommand");
var data = "{\"name\": \"EnsureMediaCovers\"}";
var context = new ConverterContext
{
DataRecord = dataRecordMock.Object,
DbValue = new RefreshArtistCommand(2).ToJson()
};
Subject.FromDB(context).Should().BeOfType<UnknownCommand>();
Subject.Parse(data).Should().BeOfType<UnknownCommand>();
}
[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();
}
}
}

@ -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<EmbeddedDocumentConverter<Dictionary<string, string>>>
{
private SQLiteParameter _param;
[SetUp]
public void Setup()
{
_param = new SQLiteParameter();
}
[Test]
public void should_serialize_in_camel_case()
{
var dict = new Dictionary<string, string>
{
{ "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");
}
}
}

@ -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<DoubleConverter>
{
[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);
}
}
}

@ -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.Music;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class EnumIntConverterFixture : CoreTest<Core.Datastore.Converters.EnumIntConverter>
{
[Test]
public void should_return_int_when_saving_enum_to_db()
{
Subject.ToDB(ArtistStatusType.Continuing).Should().Be((int)ArtistStatusType.Continuing);
}
[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<MemberInfo>();
mockMemberInfo.SetupGet(s => s.DeclaringType).Returns(typeof(ArtistMetadata));
mockMemberInfo.SetupGet(s => s.Name).Returns("Status");
var expected = ArtistStatusType.Continuing;
var context = new ConverterContext
{
ColumnMap = new ColumnMap(mockMemberInfo.Object) { FieldType = typeof(ArtistStatusType) },
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);
}
}
}

@ -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<GuidConverter>
{
[Test]
public void should_return_string_when_saving_guid_to_db()
{
var guid = Guid.NewGuid();
private SQLiteParameter _param;
Subject.ToDB(guid).Should().Be(guid.ToString());
[SetUp]
public void Setup()
{
_param = new SQLiteParameter();
}
[Test]
public void should_return_db_null_for_null_value_when_saving_to_db()
public void should_return_string_when_saving_guid_to_db()
{
Subject.ToDB(null).Should().Be(DBNull.Value);
var guid = Guid.NewGuid();
Subject.SetValue(_param, guid);
_param.Value.Should().Be(guid.ToString());
}
[Test]
@ -29,23 +32,13 @@ namespace NzbDrone.Core.Test.Datastore.Converters
{
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);
}
}
}

@ -1,56 +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<Int32Converter>
{
[Test]
public void should_return_int_when_saving_int_to_db()
{
Subject.ToDB(5).Should().Be(5);
}
[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);
}
}
}

@ -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,38 +11,37 @@ namespace NzbDrone.Core.Test.Datastore.Converters
[TestFixture]
public class OsPathConverterFixture : CoreTest<OsPathConverter>
{
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\Music".AsOsAgnostic();
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]
public void should_return_os_path_when_getting_string_from_db()
{
var path = @"C:\Test\Music".AsOsAgnostic();
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();
}
}
}

@ -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 @@ using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[Ignore("To reinstate once dapper changes worked out")]
[TestFixture]
public class ProviderSettingConverterFixture : CoreTest<ProviderSettingConverter>
{
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);
}
}
}

@ -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 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class QualityIntConverterFixture : CoreTest<QualityIntConverter>
public class QualityIntConverterFixture : CoreTest<DapperQualityIntConverter>
{
private SQLiteParameter _param;
[SetUp]
public void Setup()
{
_param = new SQLiteParameter();
}
[Test]
public void should_return_int_when_saving_quality_to_db()
{
var quality = Quality.FLAC;
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<InvalidOperationException>(() => Subject.ToDB("Not a quality"));
Subject.SetValue(_param, null);
_param.Value.Should().Be(0);
}
[Test]
@ -36,23 +39,13 @@ namespace NzbDrone.Core.Test.Datastore.Converters
{
var quality = Quality.FLAC;
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);
}
}
}

@ -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<TimeSpanConverter>
{
[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);
}
}
}

@ -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,44 +8,31 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class UtcConverterFixture : CoreTest<UtcConverter>
public class UtcConverterFixture : CoreTest<DapperUtcConverter>
{
[Test]
public void should_return_date_time_when_saving_date_time_to_db()
{
var dateTime = DateTime.Now;
private SQLiteParameter _param;
Subject.ToDB(dateTime).Should().Be(dateTime.ToUniversalTime());
}
[Test]
public void should_return_db_null_when_saving_db_null_to_db()
[SetUp]
public void Setup()
{
Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value);
_param = new SQLiteParameter();
}
[Test]
public void should_return_time_span_when_getting_time_span_from_db()
public void should_return_date_time_when_saving_date_time_to_db()
{
var dateTime = DateTime.Now.ToUniversalTime();
var context = new ConverterContext
{
DbValue = dateTime
};
var dateTime = DateTime.Now;
Subject.FromDB(context).Should().Be(dateTime);
Subject.SetValue(_param, dateTime);
_param.Value.Should().Be(dateTime.ToUniversalTime());
}
[Test]
public void should_return_db_null_for_db_null_value_when_getting_from_db()
public void should_return_time_span_when_getting_time_span_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
var dateTime = DateTime.Now.ToUniversalTime();
Subject.FromDB(context).Should().Be(DBNull.Value);
Subject.Parse(dateTime).Should().Be(dateTime);
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Linq;
using Dapper;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
@ -14,14 +15,14 @@ namespace NzbDrone.Core.Test.Datastore
public void SingleOrDefault_should_return_null_on_empty_db()
{
Mocker.Resolve<IDatabase>()
.GetDataMapper().Query<Artist>()
.OpenConnection().Query<Artist>("SELECT * FROM Artists")
.SingleOrDefault(c => c.CleanName == "SomeTitle")
.Should()
.BeNull();
}
[Test]
public void vaccume()
public void vacuum()
{
Mocker.Resolve<IDatabase>().Vacuum();
}

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Dapper;
using FizzWare.NBuilder;
using Marr.Data.QGen;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
@ -13,11 +13,13 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore
{
[TestFixture]
public class MarrDataLazyLoadingFixture : DbTest
public class LazyLoadingFixture : DbTest
{
[SetUp]
public void Setup()
{
SqlBuilderExtensions.LogSql = true;
var profile = new QualityProfile
{
Name = "Test",
@ -81,69 +83,12 @@ namespace NzbDrone.Core.Test.Datastore
Db.InsertMany(tracks);
}
[Test]
public void should_join_artist_when_query_for_albums()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var albums = dataMapper.Query<Album>()
.Join<Album, Artist>(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.ToList();
foreach (var album in albums)
{
Assert.IsNotNull(album.Artist);
}
}
[Test]
public void should_lazy_load_profile_if_not_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<Track>()
.Join<Track, AlbumRelease>(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.ToList();
foreach (var track in tracks)
{
Assert.IsTrue(track.AlbumRelease.IsLoaded);
Assert.IsTrue(track.AlbumRelease.Value.Album.IsLoaded);
Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.IsLoaded);
Assert.IsNotNull(track.AlbumRelease.Value.Album.Value.Artist.Value);
Assert.IsFalse(track.AlbumRelease.Value.Album.Value.Artist.Value.QualityProfile.IsLoaded);
}
}
[Test]
public void should_explicit_load_trackfile_if_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<Track>()
.Join<Track, TrackFile>(JoinType.Inner, v => v.TrackFile, (l, r) => l.TrackFileId == r.Id)
.ToList();
foreach (var track in tracks)
{
Assert.IsFalse(track.Artist.IsLoaded);
Assert.IsTrue(track.TrackFile.IsLoaded);
}
}
[Test]
public void should_lazy_load_artist_for_track()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var db = Mocker.Resolve<TrackRepository>();
var tracks = dataMapper.Query<Track>()
.ToList();
var tracks = db.All();
Assert.IsNotEmpty(tracks);
foreach (var track in tracks)
@ -159,10 +104,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_lazy_load_artist_for_trackfile()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<TrackFile>()
.ToList();
var tracks = db.Query<TrackFile>(new SqlBuilder()).ToList();
Assert.IsNotEmpty(tracks);
foreach (var track in tracks)
@ -178,10 +120,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_lazy_load_trackfile_if_not_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<Track>()
.ToList();
var tracks = db.Query<Track>(new SqlBuilder()).ToList();
foreach (var track in tracks)
{
@ -195,14 +134,12 @@ namespace NzbDrone.Core.Test.Datastore
public void should_explicit_load_everything_if_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var files = dataMapper.Query<TrackFile>()
.Join<TrackFile, Track>(JoinType.Inner, f => f.Tracks, (f, t) => f.Id == t.TrackFileId)
.Join<TrackFile, Album>(JoinType.Inner, t => t.Album, (t, a) => t.AlbumId == a.Id)
.Join<TrackFile, Artist>(JoinType.Inner, t => t.Artist, (t, a) => t.Album.Value.ArtistMetadataId == a.ArtistMetadataId)
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.ToList();
var files = MediaFileRepository.Query(db,
new SqlBuilder()
.Join<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id));
Assert.IsNotEmpty(files);
foreach (var file in files)
@ -219,13 +156,18 @@ namespace NzbDrone.Core.Test.Datastore
public void should_lazy_load_tracks_if_not_joined_to_trackfile()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var files = dataMapper.Query<TrackFile>()
.Join<TrackFile, Album>(JoinType.Inner, t => t.Album, (t, a) => t.AlbumId == a.Id)
.Join<TrackFile, Artist>(JoinType.Inner, t => t.Artist, (t, a) => t.Album.Value.ArtistMetadataId == a.ArtistMetadataId)
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.ToList();
var files = db.QueryJoined<TrackFile, Album, Artist, ArtistMetadata>(
new SqlBuilder()
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id),
(file, album, artist, metadata) =>
{
file.Album = album;
file.Artist = artist;
file.Artist.Value.Metadata = metadata;
return file;
});
Assert.IsNotEmpty(files);
foreach (var file in files)
@ -244,9 +186,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_lazy_load_tracks_if_not_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var release = dataMapper.Query<AlbumRelease>().Where(x => x.Id == 1).SingleOrDefault();
var release = db.Query<AlbumRelease>(new SqlBuilder().Where<AlbumRelease>(x => x.Id == 1)).SingleOrDefault();
Assert.IsFalse(release.Tracks.IsLoaded);
Assert.IsNotNull(release.Tracks.Value);
@ -258,10 +198,7 @@ namespace NzbDrone.Core.Test.Datastore
public void should_lazy_load_track_if_not_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<TrackFile>()
.ToList();
var tracks = db.Query<TrackFile>(new SqlBuilder()).ToList();
foreach (var track in tracks)
{
@ -270,28 +207,5 @@ namespace NzbDrone.Core.Test.Datastore
Assert.IsTrue(track.Tracks.IsLoaded);
}
}
[Test]
public void should_explicit_load_profile_if_joined()
{
var db = Mocker.Resolve<IDatabase>();
var dataMapper = db.GetDataMapper();
var tracks = dataMapper.Query<Track>()
.Join<Track, AlbumRelease>(JoinType.Inner, v => v.AlbumRelease, (l, r) => l.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>(JoinType.Inner, v => v.Album, (l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>(JoinType.Inner, v => v.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Artist, QualityProfile>(JoinType.Inner, v => v.QualityProfile, (l, r) => l.QualityProfileId == r.Id)
.ToList();
foreach (var track in tracks)
{
Assert.IsTrue(track.AlbumRelease.IsLoaded);
Assert.IsTrue(track.AlbumRelease.Value.Album.IsLoaded);
Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.IsLoaded);
Assert.IsNotNull(track.AlbumRelease.Value.Album.Value.Artist.Value);
Assert.IsTrue(track.AlbumRelease.Value.Album.Value.Artist.Value.QualityProfile.IsLoaded);
}
}
}
}

@ -1,28 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Music;
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<Album>
{
Page = page,
PageSize = pageSize,
SortDirection = SortDirection.Ascending,
SortKey = "ReleaseDate"
};
pagingSpec.PagingOffset().Should().Be(expected);
}
}
}

@ -1,53 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Test.Datastore.PagingSpecExtensionsTests
{
public class ToSortDirectionFixture
{
[Test]
public void should_convert_default_to_asc()
{
var pagingSpec = new PagingSpec<Album>
{
Page = 1,
PageSize = 10,
SortDirection = SortDirection.Default,
SortKey = "ReleaseDate"
};
pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Asc);
}
[Test]
public void should_convert_ascending_to_asc()
{
var pagingSpec = new PagingSpec<Album>
{
Page = 1,
PageSize = 10,
SortDirection = SortDirection.Ascending,
SortKey = "ReleaseDate"
};
pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Asc);
}
[Test]
public void should_convert_descending_to_desc()
{
var pagingSpec = new PagingSpec<Album>
{
Page = 1,
PageSize = 10,
SortDirection = SortDirection.Descending,
SortKey = "ReleaseDate"
};
pagingSpec.ToSortDirection().Should().Be(Marr.Data.QGen.SortDirection.Desc);
}
}
}

@ -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()
{
}*/
}
}

@ -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.Music;
namespace NzbDrone.Core.Test.Datastore
{
[TestFixture]
public class MappingExtensionFixture
public class TableMapperFixture
{
public class EmbeddedType : IEmbeddedDocument
{
@ -37,9 +36,8 @@ namespace NzbDrone.Core.Test.Datastore
[SetUp]
public void Setup()
{
MapRepository.Instance.RegisterTypeConverter(typeof(List<EmbeddedType>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(EmbeddedType), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(int), new Int32Converter());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<EmbeddedType>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<EmbeddedType>());
}
[Test]
@ -47,7 +45,7 @@ namespace NzbDrone.Core.Test.Datastore
{
var properties = typeof(TypeWithAllMappableProperties).GetProperties();
properties.Should().NotBeEmpty();
properties.Should().OnlyContain(c => MappingExtensions.IsMappableProperty(c));
properties.Should().OnlyContain(c => c.IsMappableProperty());
}
[Test]
@ -55,7 +53,7 @@ namespace NzbDrone.Core.Test.Datastore
{
var properties = typeof(TypeWithNoMappableProperties).GetProperties();
properties.Should().NotBeEmpty();
properties.Should().NotContain(c => MappingExtensions.IsMappableProperty(c));
properties.Should().NotContain(c => c.IsMappableProperty());
}
}
}

@ -0,0 +1,219 @@
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.Music;
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<DbFactory>();
}
private WhereBuilder Where(Expression<Func<Artist, bool>> filter)
{
return new WhereBuilder(filter, true, 0);
}
private WhereBuilder WhereMetadata(Expression<Func<ArtistMetadata, bool>> filter)
{
return new WhereBuilder(filter, true, 0);
}
[Test]
public void where_equal_const()
{
_subject = Where(x => x.Id == 10);
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)");
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(10);
}
[Test]
public void where_equal_variable()
{
var id = 10;
_subject = Where(x => x.Id == id);
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)");
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(id);
}
[Test]
public void where_equal_property()
{
var artist = new Artist { Id = 10 };
_subject = Where(x => x.Id == artist.Id);
_subject.Parameters.ParameterNames.Should().HaveCount(1);
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)");
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(artist.Id);
}
[Test]
public void where_equal_lazy_property()
{
_subject = Where(x => x.QualityProfile.Value.Id == 1);
_subject.Parameters.ParameterNames.Should().HaveCount(1);
_subject.ToString().Should().Be($"(\"QualityProfiles\".\"Id\" = @Clause1_P1)");
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(1);
}
[Test]
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
{
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
_subject = new WhereBuilder(filter, true, 0);
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
}
[Test]
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
{
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
_subject = new WhereBuilder(filter, false, 0);
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = \"Artists\".\"Id\")");
}
[Test]
public void where_string_is_null()
{
_subject = Where(x => x.CleanName == null);
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)");
}
[Test]
public void where_string_is_null_value()
{
string imdb = null;
_subject = Where(x => x.CleanName == imdb);
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)");
}
[Test]
public void where_equal_null_property()
{
var artist = new Artist { CleanName = null };
_subject = Where(x => x.CleanName == artist.CleanName);
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IS NULL)");
}
[Test]
public void where_column_contains_string()
{
var test = "small";
_subject = Where(x => x.CleanName.Contains(test));
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1 || '%')");
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
}
[Test]
public void where_string_contains_column()
{
var test = "small";
_subject = Where(x => test.Contains(x.CleanName));
_subject.ToString().Should().Be($"(@Clause1_P1 LIKE '%' || \"Artists\".\"CleanName\" || '%')");
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
}
[Test]
public void where_column_starts_with_string()
{
var test = "small";
_subject = Where(x => x.CleanName.StartsWith(test));
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE @Clause1_P1 || '%')");
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
}
[Test]
public void where_column_ends_with_string()
{
var test = "small";
_subject = Where(x => x.CleanName.EndsWith(test));
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1)");
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
}
[Test]
public void where_in_list()
{
var list = new List<int> { 1, 2, 3 };
_subject = Where(x => list.Contains(x.Id));
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" IN (1, 2, 3))");
}
[Test]
public void where_in_list_2()
{
var list = new List<int> { 1, 2, 3 };
_subject = Where(x => x.CleanName == "test" && list.Contains(x.Id));
_subject.ToString().Should().Be($"((\"Artists\".\"CleanName\" = @Clause1_P1) AND (\"Artists\".\"Id\" IN (1, 2, 3)))");
}
[Test]
public void where_in_string_list()
{
var list = new List<string> { "first", "second", "third" };
_subject = Where(x => list.Contains(x.CleanName));
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IN @Clause1_P1)");
}
[Test]
public void where_in_string_list_column()
{
_subject = WhereMetadata(x => x.OldForeignArtistIds.Contains("foreignId"));
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"OldForeignArtistIds\" LIKE '%' || @Clause1_P1 || '%')");
}
[Test]
public void enum_as_int()
{
_subject = WhereMetadata(x => x.Status == ArtistStatusType.Continuing);
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = @Clause1_P1)");
}
[Test]
public void enum_in_list()
{
var allowed = new List<ArtistStatusType> { ArtistStatusType.Continuing, ArtistStatusType.Ended };
_subject = WhereMetadata(x => allowed.Contains(x.Status));
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)");
}
[Test]
public void enum_in_array()
{
var allowed = new ArtistStatusType[] { ArtistStatusType.Continuing, ArtistStatusType.Ended };
_subject = WhereMetadata(x => allowed.Contains(x.Status));
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)");
}
}
}

@ -2,9 +2,9 @@ using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.IndexerSearch.Definitions;

@ -1,6 +1,5 @@
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Music;

@ -2,10 +2,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Music;

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;

@ -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 @@ namespace NzbDrone.Core.Test.Framework
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
MapRepository.Instance.EnableTraceLogging = true;
SqlBuilderExtensions.LogSql = true;
}
[SetUp]

@ -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;
@ -13,32 +12,20 @@ namespace NzbDrone.Core.Test.Framework
List<Dictionary<string, object>> Query(string sql);
List<T> Query<T>(string sql)
where T : new();
T QueryScalar<T>(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())
{
@ -65,13 +52,6 @@ namespace NzbDrone.Core.Test.Framework
return dataTable.Rows.Cast<DataRow>().Select(MapToObject<T>).ToList();
}
public T QueryScalar<T>(string sql)
{
var dataTable = GetDataTable(sql);
return dataTable.Rows.Cast<DataRow>().Select(d => MapValue(d, 0, typeof(T))).Cast<T>().FirstOrDefault();
}
protected Dictionary<string, object> MapToDictionary(DataRow dataRow)
{
var item = new Dictionary<string, object>();
@ -118,28 +98,24 @@ namespace NzbDrone.Core.Test.Framework
propertyType = propertyType.GetGenericArguments()[0];
}
object value = MapValue(dataRow, i, propertyType);
object value;
if (dataRow.ItemArray[i] == DBNull.Value)
{
value = null;
}
else if (dataRow.Table.Columns[i].DataType == typeof(string) && propertyType != typeof(string))
{
value = Json.Deserialize((string)dataRow.ItemArray[i], propertyType);
}
else
{
value = Convert.ChangeType(dataRow.ItemArray[i], propertyType);
}
propertyInfo.SetValue(item, value, null);
}
return item;
}
private object MapValue(DataRow dataRow, int i, Type targetType)
{
if (dataRow.ItemArray[i] == DBNull.Value)
{
return null;
}
else if (dataRow.Table.Columns[i].DataType == typeof(string) && targetType != typeof(string))
{
return Json.Deserialize((string)dataRow.ItemArray[i], targetType);
}
else
{
return Convert.ChangeType(dataRow.ItemArray[i], targetType);
}
}
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Moq;
using NzbDrone.Core.Datastore;
@ -21,6 +22,7 @@ namespace NzbDrone.Core.Test.Framework
void Delete<T>(T childModel)
where T : ModelBase, new();
IDirectDataMapper GetDirectDataMapper();
IDbConnection OpenConnection();
}
public class TestDatabase : ITestDatabase
@ -74,5 +76,10 @@ namespace NzbDrone.Core.Test.Framework
{
return new DirectDataMapper(_dbConnection);
}
public IDbConnection OpenConnection()
{
return _dbConnection.OpenConnection();
}
}
}

@ -14,7 +14,11 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
[Test]
public void should_delete_unused_tags()
{
var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
var tags = Builder<Tag>
.CreateListOfSize(2)
.All()
.With(x => x.Id = 0)
.BuildList();
Db.InsertMany(tags);
Subject.Clean();
@ -24,11 +28,17 @@ namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
[Test]
public void should_not_delete_used_tags()
{
var tags = Builder<Tag>.CreateListOfSize(2).BuildList();
var tags = Builder<Tag>
.CreateListOfSize(2)
.All()
.With(x => x.Id = 0)
.BuildList();
Db.InsertMany(tags);
var restrictions = Builder<ReleaseProfile>.CreateListOfSize(2)
.All()
.With(x => x.Id = 0)
.With(v => v.Tags.Add(tags[0].Id))
.BuildList();
Db.InsertMany(restrictions);

@ -1,7 +1,6 @@
using System;
using System.Threading;
using FluentAssertions;
using Marr.Data;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.Instrumentation;
@ -64,22 +63,6 @@ namespace NzbDrone.Core.Test.Instrumentation
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()
{

@ -1,8 +1,8 @@
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport.Specifications;
using NzbDrone.Core.Music;

@ -1,9 +1,9 @@
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport.Specifications;
using NzbDrone.Core.Music;

@ -2,10 +2,10 @@ using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
using NzbDrone.Core.Parser.Model;

@ -29,10 +29,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
Monitored = true,
ForeignArtistId = "this is a fake id",
Id = 1,
Metadata = new ArtistMetadata
{
Id = 1
}
ArtistMetadataId = 1
};
_albumRepo = Mocker.Resolve<AlbumRepository>();

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests
public void Setup()
{
_artistMetadataRepo = Mocker.Resolve<ArtistMetadataRepository>();
_metadataList = Builder<ArtistMetadata>.CreateListOfSize(10).BuildList();
_metadataList = Builder<ArtistMetadata>.CreateListOfSize(10).All().With(x => x.Id = 0).BuildList();
}
[Test]

@ -4,9 +4,9 @@ using System.Reflection;
using AutoFixture;
using Equ;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Music;
using NzbDrone.Test.Common;

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Dapper;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.ArtistStats
{
@ -13,6 +16,8 @@ namespace NzbDrone.Core.ArtistStats
public class ArtistStatisticsRepository : IArtistStatisticsRepository
{
private const string _selectTemplate = "SELECT /**select**/ FROM Tracks /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
private readonly IMainDatabase _database;
public ArtistStatisticsRepository(IMainDatabase database)
@ -22,57 +27,41 @@ namespace NzbDrone.Core.ArtistStats
public List<AlbumStatistics> ArtistStatistics()
{
var mapper = _database.GetDataMapper();
mapper.AddParameter("currentDate", DateTime.UtcNow);
var sb = new StringBuilder();
sb.AppendLine(GetSelectClause());
sb.AppendLine("AND Albums.ReleaseDate < @currentDate");
sb.AppendLine(GetGroupByClause());
var queryText = sb.ToString();
return mapper.Query<AlbumStatistics>(queryText);
var time = DateTime.UtcNow;
return Query(Builder().Where<Album>(x => x.ReleaseDate < time));
}
public List<AlbumStatistics> ArtistStatistics(int artistId)
{
var mapper = _database.GetDataMapper();
mapper.AddParameter("currentDate", DateTime.UtcNow);
mapper.AddParameter("artistId", artistId);
var time = DateTime.UtcNow;
return Query(Builder().Where<Album>(x => x.ReleaseDate < time)
.Where<Artist>(x => x.Id == artistId));
}
var sb = new StringBuilder();
sb.AppendLine(GetSelectClause());
sb.AppendLine("AND Artists.Id = @artistId");
sb.AppendLine("AND Albums.ReleaseDate < @currentDate");
sb.AppendLine(GetGroupByClause());
var queryText = sb.ToString();
private List<AlbumStatistics> Query(SqlBuilder builder)
{
var sql = builder.AddTemplate(_selectTemplate).LogQuery();
return mapper.Query<AlbumStatistics>(queryText);
using (var conn = _database.OpenConnection())
{
return conn.Query<AlbumStatistics>(sql.RawSql, sql.Parameters).ToList();
}
}
private string GetSelectClause()
{
return @"SELECT
Artists.Id AS ArtistId,
private SqlBuilder Builder() => new SqlBuilder()
.Select(@"Artists.Id AS ArtistId,
Albums.Id AS AlbumId,
SUM(COALESCE(TrackFiles.Size, 0)) AS SizeOnDisk,
COUNT(Tracks.Id) AS TotalTrackCount,
SUM(CASE WHEN Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount,
SUM(CASE WHEN Albums.Monitored = 1 OR Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount,
SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount
FROM Tracks
JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
JOIN Albums ON AlbumReleases.AlbumId = Albums.Id
JOIN Artists on Albums.ArtistMetadataId = Artists.ArtistMetadataId
LEFT OUTER JOIN TrackFiles ON Tracks.TrackFileId = TrackFiles.Id
WHERE AlbumReleases.Monitored = 1";
}
private string GetGroupByClause()
{
return "GROUP BY Artists.Id, Albums.Id";
}
SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount")
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
.LeftJoin<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
.Where<AlbumRelease>(x => x.Monitored == true)
.GroupBy<Artist>(x => x.Id)
.GroupBy<Album>(x => x.Id);
}
}

@ -20,12 +20,12 @@ namespace NzbDrone.Core.Authentication
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();
}
}
}

@ -21,7 +21,12 @@ namespace NzbDrone.Core.Backup
public void BackupDatabase(IDatabase database, string targetDirectory)
{
var sourceConnectionString = database.GetDataMapper().ConnectionString;
var sourceConnectionString = "";
using (var db = database.OpenConnection())
{
sourceConnectionString = db.ConnectionString;
}
var backupConnectionStringBuilder = new SQLiteConnectionStringBuilder(sourceConnectionString);
backupConnectionStringBuilder.DataSource = Path.Combine(targetDirectory, Path.GetFileName(backupConnectionStringBuilder.DataSource));

@ -1,5 +1,5 @@
using System.Collections.Generic;
using Marr.Data.QGen;
using Dapper;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
@ -22,26 +22,24 @@ namespace NzbDrone.Core.Blacklisting
public List<Blacklist> BlacklistedByTitle(int artistId, string sourceTitle)
{
return Query.Where(e => e.ArtistId == artistId)
.AndWhere(e => e.SourceTitle.Contains(sourceTitle));
return Query(e => e.ArtistId == artistId && e.SourceTitle.Contains(sourceTitle));
}
public List<Blacklist> BlacklistedByTorrentInfoHash(int artistId, string torrentInfoHash)
{
return Query.Where(e => e.ArtistId == artistId)
.AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash));
return Query(e => e.ArtistId == artistId && e.TorrentInfoHash.Contains(torrentInfoHash));
}
public List<Blacklist> BlacklistedByArtist(int artistId)
{
return Query.Where(b => b.ArtistId == artistId);
return Query(b => b.ArtistId == artistId);
}
protected override SortBuilder<Blacklist> GetPagedQuery(QueryBuilder<Blacklist> query, PagingSpec<Blacklist> pagingSpec)
{
var baseQuery = query.Join<Blacklist, Artist>(JoinType.Inner, h => h.Artist, (h, s) => h.ArtistId == s.Id);
return base.GetPagedQuery(baseQuery, pagingSpec);
}
protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join<Blacklist, Artist>((b, m) => b.ArtistId == m.Id);
protected override IEnumerable<Blacklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blacklist, Artist>(builder, (bl, artist) =>
{
bl.Artist = artist;
return bl;
});
}
}

@ -32,6 +32,8 @@ namespace NzbDrone.Core.Configuration
bool AnalyticsEnabled { get; }
string LogLevel { get; }
string ConsoleLogLevel { get; }
bool LogSql { get; }
int LogRotate { get; }
bool FilterSentryEvents { get; }
string Branch { get; }
string ApiKey { get; }
@ -177,6 +179,8 @@ namespace NzbDrone.Core.Configuration
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", "");
@ -204,9 +208,9 @@ namespace NzbDrone.Core.Configuration
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)

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Configuration
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)

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using Marr.Data;
using Marr.Data.QGen;
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
@ -17,59 +17,79 @@ namespace NzbDrone.Core.Datastore
IEnumerable<TModel> All();
int Count();
TModel Get(int id);
IEnumerable<TModel> Get(IEnumerable<int> 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<Func<TModel, object>>[] properties);
void Delete(TModel model);
void Delete(int id);
IEnumerable<TModel> Get(IEnumerable<int> ids);
void InsertMany(IList<TModel> model);
void UpdateMany(IList<TModel> model);
void SetFields(IList<TModel> models, params Expression<Func<TModel, object>>[] properties);
void DeleteMany(List<TModel> model);
void DeleteMany(IEnumerable<int> ids);
void Purge(bool vacuum = false);
bool HasItems();
void DeleteMany(IEnumerable<int> ids);
void SetFields(TModel model, params Expression<Func<TModel, object>>[] properties);
void SetFields(IEnumerable<TModel> models, params Expression<Func<TModel, object>>[] properties);
TModel Single();
TModel SingleOrDefault();
PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec);
}
public class BasicRepository<TModel> : IBasicRepository<TModel>
where TModel : ModelBase, new()
{
private readonly IDatabase _database;
private readonly IEventAggregator _eventAggregator;
private readonly PropertyInfo _keyProperty;
private readonly List<PropertyInfo> _properties;
private readonly string _updateSql;
private readonly string _insertSql;
protected IDataMapper DataMapper => _database.GetDataMapper();
protected readonly IDatabase _database;
protected readonly string _table;
public BasicRepository(IDatabase database, IEventAggregator eventAggregator)
{
_database = database;
_eventAggregator = eventAggregator;
}
protected virtual QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
var type = typeof(TModel);
protected void Delete(Expression<Func<TModel, bool>> filter)
{
DataMapper.Delete(filter);
_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 => x.IsMappableProperty() && !excluded.Contains(x.Name)).ToList();
_insertSql = GetInsertSql();
_updateSql = GetUpdateSql(_properties);
}
public IEnumerable<TModel> All()
protected virtual SqlBuilder Builder() => new SqlBuilder();
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
protected List<TModel> Query(Expression<Func<TModel, bool>> where) => Query(Builder().Where(where));
protected virtual List<TModel> QueryDistinct(SqlBuilder builder) => _database.QueryDistinct<TModel>(builder).ToList();
public int Count()
{
return Query.ToList();
using (var conn = _database.OpenConnection())
{
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
}
}
public int Count()
public virtual IEnumerable<TModel> All()
{
return DataMapper.Query<TModel>().GetRowCount();
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 +101,16 @@ namespace NzbDrone.Core.Datastore
public IEnumerable<TModel> Get(IEnumerable<int> ids)
{
var idList = ids.ToList();
var query = string.Format("[t0].[Id] IN ({0})", string.Join(",", idList));
var result = Query.Where(query).ToList();
if (!ids.Any())
{
return new List<TModel>();
}
var result = Query(x => ids.Contains(x.Id));
if (result.Count != idList.Count())
if (result.Count != ids.Count())
{
throw new ApplicationException($"Expected query to return {idList.Count} rows but returned {result.Count}");
throw new ApplicationException($"Expected query to return {ids.Count()} rows but returned {result.Count}");
}
return result;
@ -110,111 +133,161 @@ namespace NzbDrone.Core.Datastore
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;
}
public TModel Update(TModel model)
private string GetInsertSql()
{
if (model.Id == 0)
var sbColumnList = new StringBuilder(null);
for (var i = 0; i < _properties.Count; i++)
{
throw new InvalidOperationException("Can't update model with ID 0");
var property = _properties[i];
sbColumnList.AppendFormat("\"{0}\"", property.Name);
if (i < _properties.Count - 1)
{
sbColumnList.Append(", ");
}
}
DataMapper.Update(model, c => c.Id == model.Id);
ModelUpdated(model);
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 model;
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
}
public void Delete(TModel model)
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
{
Delete(model.Id);
SqlBuilderExtensions.LogQuery(_insertSql, 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<TModel> models)
{
using (var unitOfWork = new UnitOfWork(() => DataMapper))
if (models.Any(x => x.Id != 0))
{
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
throw new InvalidOperationException("Can't insert model with existing ID != 0");
}
foreach (var model in models)
using (var conn = _database.OpenConnection())
{
using (IDbTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted))
{
unitOfWork.DB.Insert(model);
}
foreach (var model in models)
{
Insert(conn, tran, model);
}
unitOfWork.Commit();
tran.Commit();
}
}
}
public void UpdateMany(IList<TModel> models)
public TModel Update(TModel model)
{
using (var unitOfWork = new UnitOfWork(() => DataMapper))
if (model.Id == 0)
{
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
throw new InvalidOperationException("Can't update model with ID 0");
}
foreach (var model in models)
{
var localModel = model;
using (var conn = _database.OpenConnection())
{
UpdateFields(conn, null, model, _properties);
}
if (model.Id == 0)
{
throw new InvalidOperationException("Can't update model with ID 0");
}
ModelUpdated(model);
unitOfWork.DB.Update(model, c => c.Id == localModel.Id);
}
return model;
}
unitOfWork.Commit();
public void UpdateMany(IList<TModel> 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);
}
}
public void DeleteMany(List<TModel> models)
protected void Delete(Expression<Func<TModel, bool>> where)
{
DeleteMany(models.Select(m => m.Id));
Delete(Builder().Where<TModel>(where));
}
public TModel Upsert(TModel model)
protected void Delete(SqlBuilder builder)
{
if (model.Id == 0)
var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
using (var conn = _database.OpenConnection())
{
Insert(model);
return model;
conn.Execute(sql.RawSql, sql.Parameters);
}
}
Update(model);
return model;
public void Delete(TModel model)
{
Delete(model.Id);
}
public void Delete(int id)
{
DataMapper.Delete<TModel>(c => c.Id == id);
Delete(x => x.Id == id);
}
public void DeleteMany(IEnumerable<int> ids)
{
using (var unitOfWork = new UnitOfWork(() => DataMapper))
if (ids.Any())
{
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
foreach (var id in ids)
{
var localId = id;
Delete(x => ids.Contains(x.Id));
}
}
unitOfWork.DB.Delete<TModel>(c => c.Id == localId);
}
public void DeleteMany(List<TModel> models)
{
DeleteMany(models.Select(m => m.Id));
}
unitOfWork.Commit();
public TModel Upsert(TModel model)
{
if (model.Id == 0)
{
Insert(model);
return model;
}
Update(model);
return model;
}
public void Purge(bool vacuum = false)
{
DataMapper.Delete<TModel>(c => c.Id > -1);
using (var conn = _database.OpenConnection())
{
conn.Execute($"DELETE FROM [{_table}]");
}
if (vacuum)
{
Vacuum();
@ -235,67 +308,130 @@ namespace NzbDrone.Core.Datastore
{
if (model.Id == 0)
{
throw new InvalidOperationException("Attempted to updated model without ID");
throw new InvalidOperationException("Attempted to update model without ID");
}
DataMapper.Update<TModel>()
.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(IEnumerable<TModel> models, params Expression<Func<TModel, object>>[] properties)
public void SetFields(IList<TModel> models, params Expression<Func<TModel, object>>[] properties)
{
using (var unitOfWork = new UnitOfWork(() => DataMapper))
if (models.Any(x => x.Id == 0))
{
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
throw new InvalidOperationException("Attempted to update model without ID");
}
foreach (var model in models)
{
if (model.Id == 0)
{
throw new InvalidOperationException("Can't update model with ID 0");
}
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<PropertyInfo> propertiesToUpdate)
{
var sb = new StringBuilder();
sb.AppendFormat("UPDATE {0} SET ", _table);
unitOfWork.DB.Update<TModel>()
.Where(c => c.Id == model.Id)
.ColumnsIncluding(properties)
.Entity(model)
.Execute();
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(", ");
}
}
unitOfWork.Commit();
sb.Append($" WHERE \"{_keyProperty.Name}\" = @{_keyProperty.Name}");
return sb.ToString();
}
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, TModel model, List<PropertyInfo> propertiesToUpdate)
{
var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate);
SqlBuilderExtensions.LogQuery(sql, model);
connection.Execute(sql, model, transaction: transaction);
}
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate)
{
var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate);
foreach (var model in models)
{
SqlBuilderExtensions.LogQuery(sql, model);
}
connection.Execute(sql, models, transaction: transaction);
}
protected virtual SqlBuilder PagedBuilder() => Builder();
protected virtual IEnumerable<TModel> PagedQuery(SqlBuilder sql) => Query(sql);
public virtual PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec)
{
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
pagingSpec.Records = GetPagedRecords(PagedBuilder(), pagingSpec, PagedQuery);
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().SelectCount(), pagingSpec);
return pagingSpec;
}
protected virtual SortBuilder<TModel> GetPagedQuery(QueryBuilder<TModel> query, PagingSpec<TModel> pagingSpec)
private void AddFilters(SqlBuilder builder, PagingSpec<TModel> 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<TModel>(filter);
}
}
protected List<TModel> GetPagedRecords(SqlBuilder builder, PagingSpec<TModel> pagingSpec, Func<SqlBuilder, IEnumerable<TModel>> 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}");
return queryFunc(builder).ToList();
}
return sortQuery.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
.Skip(pagingSpec.PagingOffset())
.Take(pagingSpec.PageSize);
protected int GetPagedRecordCount(SqlBuilder builder, PagingSpec<TModel> pagingSpec, string template = null)
{
AddFilters(builder, pagingSpec);
SqlBuilder.Template sql;
if (template != null)
{
sql = builder.AddTemplate(template).LogQuery();
}
else
{
sql = builder.AddPageCountTemplate(typeof(TModel));
}
using (var conn = _database.OpenConnection())
{
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
}
}
protected void ModelCreated(TModel model)

@ -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);
}
}

@ -1,42 +1,47 @@
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<Command>
{
public override object FromDB(ConverterContext context)
public override Command Parse(object value)
{
if (context.DbValue == DBNull.Value)
var stringValue = (string)value;
if (stringValue.IsNullOrWhiteSpace())
{
return null;
}
var stringValue = (string)context.DbValue;
if (stringValue.IsNullOrWhiteSpace())
string contract;
using (JsonDocument body = JsonDocument.Parse(stringValue))
{
return null;
contract = body.RootElement.GetProperty("name").GetString();
}
var ordinal = context.DataRecord.GetOrdinal("Name");
var contract = context.DataRecord.GetString(ordinal);
var impType = typeof(Command).Assembly.FindTypeByName(contract + "Command");
if (impType == null)
{
var result = Json.Deserialize<UnknownCommand>(stringValue);
var result = JsonSerializer.Deserialize<UnknownCommand>(stringValue, SerializerSettings);
result.ContractName = contract;
return result;
}
return Json.Deserialize(stringValue, impType);
return (Command)JsonSerializer.Deserialize(stringValue, impType, SerializerSettings);
}
public override void SetValue(IDbDataParameter parameter, Command value)
{
// Cast to object to get all properties written out
// https://github.com/dotnet/corefx/issues/38650
parameter.Value = value == null ? null : JsonSerializer.Serialize((object)value, SerializerSettings);
}
}
}

@ -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; }
}
}

@ -1,73 +1,52 @@
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<T> : SqlMapper.TypeHandler<T>
{
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 = true,
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());
foreach (var converter in converters)
{
_serializerSetting.Converters.Add(converter);
}
SerializerSettings = serializerSettings;
}
public virtual object FromDB(ConverterContext context)
public EmbeddedDocumentConverter(params JsonConverter[] converters)
: this()
{
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
var stringValue = (string)context.DbValue;
if (string.IsNullOrWhiteSpace(stringValue))
foreach (var converter in converters)
{
return null;
SerializerSettings.Converters.Add(converter);
}
return JsonConvert.DeserializeObject(stringValue, context.ColumnMap.FieldType, _serializerSetting);
}
public object FromDB(ColumnMap map, object dbValue)
public override void SetValue(IDbDataParameter parameter, T value)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
// Cast to object to get all properties written out
// https://github.com/dotnet/corefx/issues/38650
parameter.Value = JsonSerializer.Serialize((object)value, SerializerSettings);
}
public object ToDB(object clrValue)
public override T Parse(object value)
{
if (clrValue == null)
{
return null;
}
if (clrValue == DBNull.Value)
{
return DBNull.Value;
}
return JsonConvert.SerializeObject(clrValue, _serializerSetting);
return JsonSerializer.Deserialize<T>((string)value, SerializerSettings);
}
public Type DbType => typeof(string);
}
}

@ -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;
}
}
}

@ -1,40 +1,24 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using System;
using System.Data;
using Dapper;
namespace NzbDrone.Core.Datastore.Converters
{
public class GuidConverter : IConverter
public class GuidConverter : SqlMapper.TypeHandler<Guid>
{
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);
}
}

@ -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; }
}
}

@ -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<OsPath>
{
public object FromDB(ConverterContext context)
public override void SetValue(IDbDataParameter parameter, OsPath value)
{
if (context.DbValue == DBNull.Value)
{
return DBNull.Value;
}
var value = (string)context.DbValue;
return new OsPath(value);
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
parameter.Value = value.FullPath;
}
public object ToDB(object clrValue)
public override OsPath Parse(object value)
{
var value = (OsPath)clrValue;
if (value == null || value is DBNull)
{
return new OsPath(null);
}
return value.FullPath;
return new OsPath((string)value);
}
public Type DbType => typeof(string);
}
}

@ -1,62 +1,21 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Datastore.Converters
{
public class PrimaryAlbumTypeIntConverter : JsonConverter, IConverter
public class PrimaryAlbumTypeIntConverter : JsonConverter<PrimaryAlbumType>
{
public object FromDB(ConverterContext context)
public override PrimaryAlbumType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (context.DbValue == DBNull.Value)
{
return PrimaryAlbumType.Album;
}
var val = Convert.ToInt32(context.DbValue);
return (PrimaryAlbumType)val;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue == DBNull.Value)
{
return 0;
}
if (clrValue as PrimaryAlbumType == null)
{
throw new InvalidOperationException("Attempted to save an album type that isn't really an album type");
}
var primType = (PrimaryAlbumType)clrValue;
return (int)primType;
}
public Type DbType => typeof(int);
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PrimaryAlbumType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var item = reader.Value;
return (PrimaryAlbumType)Convert.ToInt32(item);
var item = reader.GetInt32();
return (PrimaryAlbumType)item;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, PrimaryAlbumType value, JsonSerializerOptions options)
{
writer.WriteValue(ToDB(value));
writer.WriteNumberValue((int)value);
}
}
}

@ -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<IProviderConfig>
{
public override object FromDB(ConverterContext context)
public override IProviderConfig Parse(object value)
{
if (context.DbValue == DBNull.Value)
{
return NullConfig.Instance;
}
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);
}
// We can't deserialize based on another column, happens in ProviderRepository instead
return null;
}
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);
}
}
}

@ -1,62 +1,36 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using Newtonsoft.Json;
using System;
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<Quality>
{
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 });
}
public object ToDB(object clrValue)
{
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);
writer.WriteNumberValue((int)value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
public class DapperQualityIntConverter : SqlMapper.TypeHandler<Quality>
{
public override void SetValue(IDbDataParameter parameter, Quality value)
{
var item = reader.Value;
return (Quality)Convert.ToInt32(item);
parameter.Value = value == null ? 0 : (int)value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override Quality Parse(object value)
{
writer.WriteValue(ToDB(value));
return (Quality)Convert.ToInt32(value);
}
}
}

@ -1,62 +1,21 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Datastore.Converters
{
public class ReleaseStatusIntConverter : JsonConverter, IConverter
public class ReleaseStatusIntConverter : JsonConverter<ReleaseStatus>
{
public object FromDB(ConverterContext context)
public override ReleaseStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (context.DbValue == DBNull.Value)
{
return ReleaseStatus.Official;
}
var val = Convert.ToInt32(context.DbValue);
return (ReleaseStatus)val;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue == DBNull.Value)
{
return 0;
}
if (clrValue as ReleaseStatus == null)
{
throw new InvalidOperationException("Attempted to save a release status that isn't really a release status");
}
var releaseStatus = (ReleaseStatus)clrValue;
return (int)releaseStatus;
}
public Type DbType => typeof(int);
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ReleaseStatus);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var item = reader.Value;
return (ReleaseStatus)Convert.ToInt32(item);
var item = reader.GetInt32();
return (ReleaseStatus)item;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, ReleaseStatus value, JsonSerializerOptions options)
{
writer.WriteValue(ToDB(value));
writer.WriteNumberValue((int)value);
}
}
}

@ -1,65 +1,21 @@
using System;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using Newtonsoft.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Datastore.Converters
{
public class SecondaryAlbumTypeIntConverter : JsonConverter, IConverter
public class SecondaryAlbumTypeIntConverter : JsonConverter<SecondaryAlbumType>
{
public object FromDB(ConverterContext context)
public override SecondaryAlbumType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (context.DbValue == DBNull.Value)
{
return SecondaryAlbumType.Studio;
}
var val = Convert.ToInt32(context.DbValue);
return (SecondaryAlbumType)val;
}
public object FromDB(ColumnMap map, object dbValue)
{
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
}
public object ToDB(object clrValue)
{
if (clrValue == DBNull.Value)
{
return 0;
}
if (clrValue as SecondaryAlbumType == null)
{
throw new InvalidOperationException("Attempted to save an album type that isn't really an album type");
}
var secType = (SecondaryAlbumType)clrValue;
return (int)secType;
}
public Type DbType => typeof(int);
public override bool CanConvert(Type objectType)
{
return objectType == typeof(SecondaryAlbumType);
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
var item = reader.Value;
return (SecondaryAlbumType)Convert.ToInt32(item);
var item = reader.GetInt32();
return (SecondaryAlbumType)item;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, SecondaryAlbumType value, JsonSerializerOptions options)
{
writer.WriteValue(ToDB(value));
writer.WriteNumberValue((int)value);
}
}
}

@ -1,43 +1,19 @@
using System;
using System.Globalization;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using NzbDrone.Common.Extensions;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace NzbDrone.Core.Datastore.Converters
{
public class TimeSpanConverter : IConverter
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
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; }
}
}

@ -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<DateTime>
{
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 object ToDB(object clrValue)
public class UtcConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (clrValue == DBNull.Value)
{
return clrValue;
}
var dateTime = (DateTime)clrValue;
return dateTime.ToUniversalTime();
return DateTime.Parse(reader.GetString());
}
public Type DbType => typeof(DateTime);
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"));
}
}
}

@ -1,5 +1,6 @@
using System;
using Marr.Data;
using System;
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 @@ namespace NzbDrone.Core.Datastore
public class Database : IDatabase
{
private readonly string _databaseName;
private readonly Func<IDataMapper> _datamapperFactory;
private readonly Func<IDbConnection> _datamapperFactory;
private readonly Logger _logger = NzbDroneLogger.GetLogger(typeof(Database));
public Database(string databaseName, Func<IDataMapper> datamapperFactory)
public Database(string databaseName, Func<IDbConnection> datamapperFactory)
{
_databaseName = databaseName;
_datamapperFactory = datamapperFactory;
}
public IDataMapper GetDataMapper()
public IDbConnection OpenConnection()
{
return _datamapperFactory();
}
@ -35,8 +36,11 @@ namespace NzbDrone.Core.Datastore
{
get
{
var version = _datamapperFactory().ExecuteScalar("SELECT sqlite_version()").ToString();
return new Version(version);
using (var db = _datamapperFactory())
{
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
return new Version(version);
}
}
}
@ -44,9 +48,10 @@ namespace NzbDrone.Core.Datastore
{
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<int>("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1");
}
}
}
@ -55,7 +60,11 @@ namespace NzbDrone.Core.Datastore
try
{
_logger.Info("Vacuuming {0} database", _databaseName);
_datamapperFactory().ExecuteNonQuery("Vacuum;");
using (var db = _datamapperFactory())
{
db.Execute("Vacuum;");
}
_logger.Info("{0} database compressed", _databaseName);
}
catch (Exception e)

@ -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 @@ namespace NzbDrone.Core.Datastore
{
InitializeEnvironment();
MapRepository.Instance.ReflectionStrategy = new SimpleReflectionStrategy();
TableMapping.Map();
}
@ -99,12 +96,11 @@ namespace NzbDrone.Core.Datastore
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;
@ -123,7 +119,7 @@ namespace NzbDrone.Core.Datastore
if (OsInfo.IsOsx)
{
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-use-lidarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
}
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);

@ -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
{
/// <summary>
/// Expression visitor
/// </summary>
public class ExpressionVisitor
{
/// <summary>
/// Visits expression and delegates call to different to branch.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
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());
}
/// <summary>
/// Visits the constance expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitConstant(ConstantExpression expression)
{
return expression;
}
/// <summary>
/// Visits the memeber access expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitMemberAccess(MemberExpression expression)
{
return expression;
}
/// <summary>
/// Visits the method call expression. To be implemented by user.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitMethodCall(MethodCallExpression expression)
{
throw new NotImplementedException();
}
/// <summary>
/// Visits the binary expression.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitBinary(BinaryExpression expression)
{
Visit(expression.Left);
Visit(expression.Right);
return expression;
}
/// <summary>
/// Visits the unary expression.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
protected virtual Expression VisitUnary(UnaryExpression expression)
{
Visit(expression.Operand);
return expression;
}
/// <summary>
/// Visits the lamda expression.
/// </summary>
/// <param name="lambdaExpression"></param>
/// <returns></returns>
protected virtual Expression VisitLamda(LambdaExpression lambdaExpression)
{
Visit(lambdaExpression.Body);
return lambdaExpression;
}
private Expression VisitParameter(ParameterExpression expression)
{
return expression;
}
}
}

@ -0,0 +1,172 @@
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
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SqlBuilderExtensions));
public static bool LogSql { get; set; }
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
{
return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
}
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)
{
return builder.Select("DISTINCT " + types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
}
public static SqlBuilder SelectCount(this SqlBuilder builder)
{
return builder.Select("COUNT(*)");
}
public static SqlBuilder SelectCountDistinct<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
{
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
var propName = property.GetMemberName().Name;
return builder.Select($"COUNT(DISTINCT \"{table}\".\"{propName}\")");
}
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
{
var wb = new WhereBuilder(filter, true, builder.Sequence);
return builder.Where(wb.ToString(), wb.Parameters);
}
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
{
var wb = new WhereBuilder(filter, true, builder.Sequence);
return builder.OrWhere(wb.ToString(), wb.Parameters);
}
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
{
var wb = new WhereBuilder(filter, false, builder.Sequence);
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
return builder.Join($"{rightTable} ON {wb.ToString()}");
}
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
{
var wb = new WhereBuilder(filter, false, builder.Sequence);
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
return builder.LeftJoin($"{rightTable} ON {wb.ToString()}");
}
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
{
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
var propName = property.GetMemberName().Name;
return builder.GroupBy($"{table}.{propName}");
}
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
{
return builder.AddTemplate(TableMapping.Mapper.SelectTemplate(type)).LogQuery();
}
public static SqlBuilder.Template AddPageCountTemplate(this SqlBuilder builder, Type type)
{
return builder.AddTemplate(TableMapping.Mapper.PageCountTemplate(type)).LogQuery();
}
public static SqlBuilder.Template AddDeleteTemplate(this SqlBuilder builder, Type type)
{
return builder.AddTemplate(TableMapping.Mapper.DeleteTemplate(type)).LogQuery();
}
public static SqlBuilder.Template LogQuery(this SqlBuilder.Template template)
{
if (LogSql)
{
LogQuery(template.RawSql, (DynamicParameters)template.Parameters);
}
return template;
}
public static void LogQuery(string sql, object parameters)
{
if (LogSql)
{
LogQuery(sql, new DynamicParameters(parameters));
}
}
private static void LogQuery(string sql, DynamicParameters parameters)
{
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==== Begin Query Trace ====");
sb.AppendLine();
sb.AppendLine("QUERY TEXT:");
sb.AppendLine(sql);
sb.AppendLine();
sb.AppendLine("PARAMETERS:");
foreach (var p in parameters.ToDictionary())
{
var 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());
}
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
{
var argsDictionary = new Dictionary<string, object>();
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 && templates.GetValue(dynamicParams) is List<object> list)
{
foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList()))
{
objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value));
}
}
return argsDictionary;
}
private static Dictionary<string, object> GetPropertyValuePairs(this object obj)
{
var type = obj.GetType();
var pairs = type.GetProperties().Where(x => x.IsMappableProperty())
.DistinctBy(propertyInfo => propertyInfo.Name)
.ToDictionary(
propertyInfo => propertyInfo.Name,
propertyInfo => propertyInfo.GetValue(obj, null));
return pairs;
}
}
}

@ -1,48 +1,24 @@
using System.Reflection;
using Marr.Data;
using Marr.Data.Mapping;
using System;
using System.Linq.Expressions;
using System.Reflection;
using Dapper;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Datastore.Extensions
namespace NzbDrone.Core.Datastore
{
public static class MappingExtensions
{
public static ColumnMapBuilder<T> MapResultSet<T>(this FluentMappings.MappingsFluentEntity<T> mapBuilder)
where T : ResultSet, new()
public static PropertyInfo GetMemberName<T, TChild>(this Expression<Func<T, TChild>> member)
{
return mapBuilder
.Columns
.AutoMapPropertiesWhere(IsMappableProperty);
}
public static ColumnMapBuilder<T> RegisterDefinition<T>(this FluentMappings.MappingsFluentEntity<T> mapBuilder, string tableName = null)
where T : ProviderDefinition, new()
{
return RegisterModel(mapBuilder, tableName).Ignore(c => c.ImplementationName);
}
public static ColumnMapBuilder<T> RegisterModel<T>(this FluentMappings.MappingsFluentEntity<T> 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();
}
if (!(member.Body is MemberExpression memberExpression))
{
memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression;
}
public static RelationshipBuilder<T> AutoMapChildModels<T>(this ColumnMapBuilder<T> mapBuilder)
{
return mapBuilder.Relationships.AutoMapPropertiesWhere(m =>
m.MemberType == MemberTypes.Property &&
typeof(ModelBase).IsAssignableFrom(((PropertyInfo)m).PropertyType));
return (PropertyInfo)memberExpression.Member;
}
public static bool IsMappableProperty(MemberInfo memberInfo)
public static bool IsMappableProperty(this MemberInfo memberInfo)
{
var propertyInfo = memberInfo as PropertyInfo;
@ -56,7 +32,11 @@ namespace NzbDrone.Core.Datastore.Extensions
return false;
}
if (propertyInfo.PropertyType.IsSimpleType() || MapRepository.Instance.TypeConverters.ContainsKey(propertyInfo.PropertyType))
// 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;
}

@ -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<Func<TModel, object>> OrderByClause<TModel>(this PagingSpec<TModel> pagingSpec)
{
return CreateExpression<TModel>(pagingSpec.SortKey);
}
public static int PagingOffset<TModel>(this PagingSpec<TModel> pagingSpec)
{
return (pagingSpec.Page - 1) * pagingSpec.PageSize;
}
public static Marr.Data.QGen.SortDirection ToSortDirection<TModel>(this PagingSpec<TModel> pagingSpec)
{
if (pagingSpec.SortDirection == SortDirection.Descending)
{
return Marr.Data.QGen.SortDirection.Desc;
}
return Marr.Data.QGen.SortDirection.Asc;
}
private static Expression<Func<TModel, object>> CreateExpression<TModel>(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<Func<TModel, object>>(expressionBody, parameterExpression);
}
}
}

@ -1,42 +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<TParent> HasOne<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyLoaded<TChild>>> portalExpression, Func<TParent, int> 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<TChild>().Where(c => c.Id == id).SingleOrDefault();
});
}
public static RelationshipBuilder<TParent> Relationship<TParent>(this ColumnMapBuilder<TParent> mapBuilder)
{
return mapBuilder.Relationships.MapProperties<TParent>();
}
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)
{
var expression = member.Body as MemberExpression;
if (expression == null)
{
expression = (MemberExpression)((UnaryExpression)member.Body).Operand;
}
return expression.Member.Name;
}
}
}

@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Data;
using Dapper;
namespace NzbDrone.Core.Datastore
{
public static class SqlMapperExtensions
{
public static IEnumerable<T> Query<T>(this IDatabase db, string sql, object param = null)
{
using (var conn = db.OpenConnection())
{
var items = SqlMapper.Query<T>(conn, sql, param);
if (TableMapping.Mapper.LazyLoadList.TryGetValue(typeof(T), out var lazyProperties))
{
foreach (var item in items)
{
ApplyLazyLoad(db, item, lazyProperties);
}
}
return items;
}
}
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
TReturn MapWithLazy(TFirst first, TSecond second)
{
ApplyLazyLoad(db, first);
ApplyLazyLoad(db, second);
return map(first, second);
}
IEnumerable<TReturn> result = null;
using (var conn = db.OpenConnection())
{
result = SqlMapper.Query<TFirst, TSecond, TReturn>(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
}
return result;
}
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
TReturn MapWithLazy(TFirst first, TSecond second, TThird third)
{
ApplyLazyLoad(db, first);
ApplyLazyLoad(db, second);
ApplyLazyLoad(db, third);
return map(first, second, third);
}
IEnumerable<TReturn> result = null;
using (var conn = db.OpenConnection())
{
result = SqlMapper.Query<TFirst, TSecond, TThird, TReturn>(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
}
return result;
}
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
TReturn MapWithLazy(TFirst first, TSecond second, TThird third, TFourth fourth)
{
ApplyLazyLoad(db, first);
ApplyLazyLoad(db, second);
ApplyLazyLoad(db, third);
ApplyLazyLoad(db, fourth);
return map(first, second, third, fourth);
}
IEnumerable<TReturn> result = null;
using (var conn = db.OpenConnection())
{
result = SqlMapper.Query<TFirst, TSecond, TThird, TFourth, TReturn>(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
}
return result;
}
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
TReturn MapWithLazy(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth)
{
ApplyLazyLoad(db, first);
ApplyLazyLoad(db, second);
ApplyLazyLoad(db, third);
ApplyLazyLoad(db, fourth);
ApplyLazyLoad(db, fifth);
return map(first, second, third, fourth, fifth);
}
IEnumerable<TReturn> result = null;
using (var conn = db.OpenConnection())
{
result = SqlMapper.Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
}
return result;
}
public static IEnumerable<T> Query<T>(this IDatabase db, SqlBuilder builder)
{
var type = typeof(T);
var sql = builder.Select(type).AddSelectTemplate(type);
return db.Query<T>(sql.RawSql, sql.Parameters);
}
public static IEnumerable<T> QueryDistinct<T>(this IDatabase db, SqlBuilder builder)
{
var type = typeof(T);
var sql = builder.SelectDistinct(type).AddSelectTemplate(type);
return db.Query<T>(sql.RawSql, sql.Parameters);
}
public static IEnumerable<T> QueryJoined<T, T2>(this IDatabase db, SqlBuilder builder, Func<T, T2, T> mapper)
{
var type = typeof(T);
var sql = builder.Select(type, typeof(T2)).AddSelectTemplate(type);
return db.Query(sql.RawSql, mapper, sql.Parameters);
}
public static IEnumerable<T> QueryJoined<T, T2, T3>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T> mapper)
{
var type = typeof(T);
var sql = builder.Select(type, typeof(T2), typeof(T3)).AddSelectTemplate(type);
return db.Query(sql.RawSql, mapper, sql.Parameters);
}
public static IEnumerable<T> QueryJoined<T, T2, T3, T4>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T4, T> mapper)
{
var type = typeof(T);
var sql = builder.Select(type, typeof(T2), typeof(T3), typeof(T4)).AddSelectTemplate(type);
return db.Query(sql.RawSql, mapper, sql.Parameters);
}
public static IEnumerable<T> QueryJoined<T, T2, T3, T4, T5>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T4, T5, T> mapper)
{
var type = typeof(T);
var sql = builder.Select(type, typeof(T2), typeof(T3), typeof(T4), typeof(T5)).AddSelectTemplate(type);
return db.Query(sql.RawSql, mapper, sql.Parameters);
}
private static void ApplyLazyLoad<TModel>(IDatabase db, TModel model)
{
if (TableMapping.Mapper.LazyLoadList.TryGetValue(typeof(TModel), out var lazyProperties))
{
ApplyLazyLoad(db, model, lazyProperties);
}
}
private static void ApplyLazyLoad<TModel>(IDatabase db, TModel model, List<LazyLoadedProperty> lazyProperties)
{
if (model == null)
{
return;
}
foreach (var lazyProperty in lazyProperties)
{
var lazy = (ILazyLoaded)lazyProperty.LazyLoad.Clone();
lazy.Prepare(db, model);
lazyProperty.Property.SetValue(model, lazy);
}
}
}
}

@ -1,28 +0,0 @@
using System.Collections.Generic;
using Marr.Data;
namespace NzbDrone.Core.Datastore
{
public class LazyList<T> : LazyLoaded<List<T>>
{
public LazyList()
: this(new List<T>())
{
}
public LazyList(IEnumerable<T> items)
: base(new List<T>(items))
{
}
public static implicit operator LazyList<T>(List<T> val)
{
return new LazyList<T>(val);
}
public static implicit operator List<T>(LazyList<T> lazy)
{
return lazy.Value;
}
}
}

@ -0,0 +1,129 @@
using System;
namespace NzbDrone.Core.Datastore
{
public interface ILazyLoaded : ICloneable
{
bool IsLoaded { get; }
void Prepare(IDatabase database, object parent);
void LazyLoad();
}
/// <summary>
/// Allows a field to be lazy loaded.
/// </summary>
/// <typeparam name="TChild"></typeparam>
public class LazyLoaded<TChild> : ILazyLoaded
{
protected TChild _value;
public LazyLoaded()
{
}
public LazyLoaded(TChild val)
{
_value = val;
IsLoaded = true;
}
public TChild Value
{
get
{
LazyLoad();
return _value;
}
}
public bool IsLoaded { get; protected set; }
public static implicit operator LazyLoaded<TChild>(TChild val)
{
return new LazyLoaded<TChild>(val);
}
public static implicit operator TChild(LazyLoaded<TChild> lazy)
{
return lazy.Value;
}
public virtual void Prepare(IDatabase database, object parent)
{
}
public virtual void LazyLoad()
{
}
public object Clone()
{
return MemberwiseClone();
}
public bool ShouldSerializeValue()
{
return IsLoaded;
}
}
/// <summary>
/// This is the lazy loading proxy.
/// </summary>
/// <typeparam name="TParent">The parent entity that contains the lazy loaded entity.</typeparam>
/// <typeparam name="TChild">The child entity that is being lazy loaded.</typeparam>
internal class LazyLoaded<TParent, TChild> : LazyLoaded<TChild>
{
private readonly Func<IDatabase, TParent, TChild> _query;
private readonly Func<TParent, bool> _condition;
private IDatabase _database;
private TParent _parent;
public LazyLoaded(TChild val)
: base(val)
{
_value = val;
IsLoaded = true;
}
internal LazyLoaded(Func<IDatabase, TParent, TChild> query, Func<TParent, bool> condition = null)
{
_query = query;
_condition = condition;
}
public static implicit operator LazyLoaded<TParent, TChild>(TChild val)
{
return new LazyLoaded<TParent, TChild>(val);
}
public static implicit operator TChild(LazyLoaded<TParent, TChild> lazy)
{
return lazy.Value;
}
public override void Prepare(IDatabase database, object parent)
{
_database = database;
_parent = (TParent)parent;
}
public override void LazyLoad()
{
if (!IsLoaded)
{
if (_condition != null && _condition(_parent))
{
_value = _query(_database, _parent);
}
else
{
_value = default;
}
IsLoaded = true;
}
}
}
}

@ -1,5 +1,5 @@
using System;
using Marr.Data;
using System;
using System.Data;
namespace NzbDrone.Core.Datastore
{
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Datastore
_database = database;
}
public IDataMapper GetDataMapper()
public IDbConnection OpenConnection()
{
return _database.GetDataMapper();
return _database.OpenConnection();
}
public Version Version => _database.Version;

@ -1,5 +1,5 @@
using System;
using Marr.Data;
using System;
using System.Data;
namespace NzbDrone.Core.Datastore
{
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Datastore
_database = database;
}
public IDataMapper GetDataMapper()
public IDbConnection OpenConnection()
{
return _database.GetDataMapper();
return _database.OpenConnection();
}
public Version Version => _database.Version;

@ -0,0 +1,168 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Dapper;
namespace NzbDrone.Core.Datastore
{
public class SqlBuilder
{
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
public int Sequence { get; private set; }
public Template AddTemplate(string sql, dynamic parameters = null) =>
new Template(this, sql, parameters);
public SqlBuilder Intersect(string sql, dynamic parameters = null) =>
AddClause("intersect", sql, parameters, "\nINTERSECT\n ", "\n ", "\n", false);
public SqlBuilder InnerJoin(string sql, dynamic parameters = null) =>
AddClause("innerjoin", sql, parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false);
public SqlBuilder LeftJoin(string sql, dynamic parameters = null) =>
AddClause("leftjoin", sql, parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false);
public SqlBuilder RightJoin(string sql, dynamic parameters = null) =>
AddClause("rightjoin", sql, parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false);
public SqlBuilder Where(string sql, dynamic parameters = null) =>
AddClause("where", sql, parameters, " AND ", "WHERE ", "\n", false);
public SqlBuilder OrWhere(string sql, dynamic parameters = null) =>
AddClause("where", sql, parameters, " OR ", "WHERE ", "\n", true);
public SqlBuilder OrderBy(string sql, dynamic parameters = null) =>
AddClause("orderby", sql, parameters, " , ", "ORDER BY ", "\n", false);
public SqlBuilder Select(string sql, dynamic parameters = null) =>
AddClause("select", sql, parameters, " , ", "", "\n", false);
public SqlBuilder AddParameters(dynamic parameters) =>
AddClause("--parameters", "", parameters, "", "", "", false);
public SqlBuilder Join(string sql, dynamic parameters = null) =>
AddClause("join", sql, parameters, "\nJOIN ", "\nJOIN ", "\n", false);
public SqlBuilder GroupBy(string sql, dynamic parameters = null) =>
AddClause("groupby", sql, parameters, " , ", "\nGROUP BY ", "\n", false);
public SqlBuilder Having(string sql, dynamic parameters = null) =>
AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n", false);
protected SqlBuilder AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false)
{
if (!_data.TryGetValue(name, out var clauses))
{
clauses = new Clauses(joiner, prefix, postfix);
_data[name] = clauses;
}
clauses.Add(new Clause { Sql = sql, Parameters = parameters, IsInclusive = isInclusive });
Sequence++;
return this;
}
public class Template
{
private static readonly Regex _regex = new Regex(@"\/\*\*.+?\*\*\/", RegexOptions.Compiled | RegexOptions.Multiline);
private readonly string _sql;
private readonly SqlBuilder _builder;
private readonly object _initParams;
private int _dataSeq = -1; // Unresolved
private string _rawSql;
private object _parameters;
public Template(SqlBuilder builder, string sql, dynamic parameters)
{
_initParams = parameters;
_sql = sql;
_builder = builder;
}
public string RawSql
{
get
{
ResolveSql();
return _rawSql;
}
}
public object Parameters
{
get
{
ResolveSql();
return _parameters;
}
}
private void ResolveSql()
{
if (_dataSeq != _builder.Sequence)
{
var p = new DynamicParameters(_initParams);
_rawSql = _sql;
foreach (var pair in _builder._data)
{
_rawSql = _rawSql.Replace("/**" + pair.Key + "**/", pair.Value.ResolveClauses(p));
}
_parameters = p;
// replace all that is left with empty
_rawSql = _regex.Replace(_rawSql, "");
_dataSeq = _builder.Sequence;
}
}
}
private class Clause
{
public string Sql { get; set; }
public object Parameters { get; set; }
public bool IsInclusive { get; set; }
}
private class Clauses : List<Clause>
{
private readonly string _joiner;
private readonly string _prefix;
private readonly string _postfix;
public Clauses(string joiner, string prefix = "", string postfix = "")
{
_joiner = joiner;
_prefix = prefix;
_postfix = postfix;
}
public string ResolveClauses(DynamicParameters p)
{
foreach (var item in this)
{
p.AddDynamicParams(item.Parameters);
}
return this.Any(a => a.IsInclusive)
? _prefix +
string.Join(_joiner,
this.Where(a => !a.IsInclusive)
.Select(c => c.Sql)
.Union(new[]
{
" ( " +
string.Join(" OR ", this.Where(a => a.IsInclusive).Select(c => c.Sql).ToArray()) +
" ) "
}).ToArray()) + _postfix
: _prefix + string.Join(_joiner, this.Select(c => c.Sql).ToArray()) + _postfix;
}
}
}
}

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace NzbDrone.Core.Datastore
{
public class TableMapper
{
public TableMapper()
{
IgnoreList = new Dictionary<Type, List<PropertyInfo>>();
LazyLoadList = new Dictionary<Type, List<LazyLoadedProperty>>();
TableMap = new Dictionary<Type, string>();
}
public Dictionary<Type, List<PropertyInfo>> IgnoreList { get; set; }
public Dictionary<Type, List<LazyLoadedProperty>> LazyLoadList { get; set; }
public Dictionary<Type, string> TableMap { get; set; }
public ColumnMapper<TEntity> Entity<TEntity>(string tableName)
where TEntity : ModelBase
{
var type = typeof(TEntity);
TableMap.Add(type, tableName);
if (IgnoreList.TryGetValue(type, out var list))
{
return new ColumnMapper<TEntity>(list, LazyLoadList[type]);
}
IgnoreList[type] = new List<PropertyInfo>();
LazyLoadList[type] = new List<LazyLoadedProperty>();
return new ColumnMapper<TEntity>(IgnoreList[type], LazyLoadList[type]);
}
public List<PropertyInfo> ExcludeProperties(Type x)
{
return IgnoreList.ContainsKey(x) ? IgnoreList[x] : new List<PropertyInfo>();
}
public string TableNameMapping(Type x)
{
return TableMap.ContainsKey(x) ? TableMap[x] : null;
}
public string SelectTemplate(Type x)
{
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
}
public string DeleteTemplate(Type x)
{
return $"DELETE FROM {TableMap[x]} /**where**/";
}
public string PageCountTemplate(Type x)
{
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/";
}
}
public class LazyLoadedProperty
{
public PropertyInfo Property { get; set; }
public ILazyLoaded LazyLoad { get; set; }
}
public class ColumnMapper<T>
where T : ModelBase
{
private readonly List<PropertyInfo> _ignoreList;
private readonly List<LazyLoadedProperty> _lazyLoadList;
public ColumnMapper(List<PropertyInfo> ignoreList, List<LazyLoadedProperty> lazyLoadList)
{
_ignoreList = ignoreList;
_lazyLoadList = lazyLoadList;
}
public ColumnMapper<T> AutoMapPropertiesWhere(Func<PropertyInfo, bool> predicate)
{
var properties = typeof(T).GetProperties();
_ignoreList.AddRange(properties.Where(x => !predicate(x)));
return this;
}
public ColumnMapper<T> RegisterModel()
{
return AutoMapPropertiesWhere(x => x.IsMappableProperty());
}
public ColumnMapper<T> Ignore(Expression<Func<T, object>> property)
{
_ignoreList.Add(property.GetMemberName());
return this;
}
public ColumnMapper<T> LazyLoad<TChild>(Expression<Func<T, LazyLoaded<TChild>>> property, Func<IDatabase, T, TChild> query, Func<T, bool> condition)
{
var lazyLoad = new LazyLoaded<T, TChild>(query, condition);
var item = new LazyLoadedProperty
{
Property = property.GetMemberName(),
LazyLoad = lazyLoad
};
_lazyLoadList.Add(item);
return this;
}
public ColumnMapper<T> HasOne<TChild>(Expression<Func<T, LazyLoaded<TChild>>> portalExpression, Func<T, int> childIdSelector)
where TChild : ModelBase
{
return LazyLoad(portalExpression,
(db, parent) =>
{
var id = childIdSelector(parent);
return db.Query<TChild>(new SqlBuilder().Where<TChild>(x => x.Id == id)).SingleOrDefault();
},
parent => childIdSelector(parent) > 0);
}
}
}

@ -1,18 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Marr.Data.Mapping;
using Marr.Data.QGen;
using NzbDrone.Common.Disk;
using Dapper;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ArtistStats;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Blacklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.CustomFilters;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Datastore.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Pending;
using NzbDrone.Core.Extras.Lyrics;
@ -39,38 +34,47 @@ using NzbDrone.Core.RemotePathMappings;
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<Config>().RegisterModel("Config");
Mapper.Entity<Config>("Config").RegisterModel();
Mapper.Entity<RootFolder>().RegisterModel("RootFolders")
Mapper.Entity<RootFolder>("RootFolders").RegisterModel()
.Ignore(r => r.Accessible)
.Ignore(r => r.FreeSpace)
.Ignore(r => r.TotalSpace);
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
Mapper.Entity<ScheduledTask>("ScheduledTasks").RegisterModel();
Mapper.Entity<IndexerDefinition>().RegisterDefinition("Indexers")
Mapper.Entity<IndexerDefinition>("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<ImportListDefinition>().RegisterDefinition("ImportLists")
Mapper.Entity<ImportListDefinition>("ImportLists").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.Enable)
.Ignore(i => i.ListType);
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnReleaseImport)
.Ignore(i => i.SupportsOnUpgrade)
@ -80,118 +84,110 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsOnImportFailure)
.Ignore(i => i.SupportsOnTrackRetag);
Mapper.Entity<MetadataDefinition>().RegisterDefinition("Metadata")
Mapper.Entity<MetadataDefinition>("Metadata").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(d => d.Tags);
Mapper.Entity<DownloadClientDefinition>().RegisterDefinition("DownloadClients")
Mapper.Entity<DownloadClientDefinition>("DownloadClients").RegisterModel()
.Ignore(x => x.ImplementationName)
.Ignore(d => d.Protocol)
.Ignore(d => d.Tags);
Mapper.Entity<History.History>().RegisterModel("History")
.AutoMapChildModels();
Mapper.Entity<History.History>("History").RegisterModel();
Mapper.Entity<Artist>().RegisterModel("Artists")
Mapper.Entity<Artist>("Artists")
.Ignore(s => s.RootFolderPath)
.Ignore(s => s.Name)
.Ignore(s => s.ForeignArtistId)
.Relationship()
.HasOne(a => a.Metadata, a => a.ArtistMetadataId)
.HasOne(a => a.QualityProfile, a => a.QualityProfileId)
.HasOne(s => s.MetadataProfile, s => s.MetadataProfileId)
.For(a => a.Albums)
.LazyLoad(condition: a => a.Id > 0, query: (db, a) => db.Query<Album>().Where(rg => rg.ArtistMetadataId == a.Id).ToList());
.LazyLoad(a => a.Albums, (db, a) => db.Query<Album>(new SqlBuilder().Where<Album>(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0);
Mapper.Entity<ArtistMetadata>().RegisterModel("ArtistMetadata");
Mapper.Entity<ArtistMetadata>("ArtistMetadata").RegisterModel();
Mapper.Entity<Album>().RegisterModel("Albums")
.Ignore(r => r.ArtistId)
.Relationship()
Mapper.Entity<Album>("Albums").RegisterModel()
.Ignore(x => x.ArtistId)
.HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId)
.For(rg => rg.AlbumReleases)
.LazyLoad(condition: rg => rg.Id > 0, query: (db, rg) => db.Query<AlbumRelease>().Where(r => r.AlbumId == rg.Id).ToList())
.For(rg => rg.Artist)
.LazyLoad(condition: rg => rg.ArtistMetadataId > 0,
query: (db, rg) => db.Query<Artist>()
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.Where(a => a.ArtistMetadataId == rg.ArtistMetadataId).SingleOrDefault());
Mapper.Entity<AlbumRelease>().RegisterModel("AlbumReleases")
.Relationship()
.LazyLoad(a => a.AlbumReleases, (db, album) => db.Query<AlbumRelease>(new SqlBuilder().Where<AlbumRelease>(r => r.AlbumId == album.Id)).ToList(), a => a.Id > 0)
.LazyLoad(a => a.Artist,
(db, album) => ArtistRepository.Query(db,
new SqlBuilder()
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
.Where<Artist>(a => a.ArtistMetadataId == album.ArtistMetadataId)).SingleOrDefault(),
a => a.ArtistMetadataId > 0);
Mapper.Entity<AlbumRelease>("AlbumReleases").RegisterModel()
.HasOne(r => r.Album, r => r.AlbumId)
.For(r => r.Tracks)
.LazyLoad(condition: r => r.Id > 0, query: (db, r) => db.Query<Track>().Where(t => t.AlbumReleaseId == r.Id).ToList());
.LazyLoad(x => x.Tracks, (db, release) => db.Query<Track>(new SqlBuilder().Where<Track>(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0);
Mapper.Entity<Track>().RegisterModel("Tracks")
Mapper.Entity<Track>("Tracks").RegisterModel()
.Ignore(t => t.HasFile)
.Ignore(t => t.AlbumId)
.Ignore(t => t.Album)
.Relationship()
.HasOne(track => track.AlbumRelease, track => track.AlbumReleaseId)
.HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId)
.For(track => track.TrackFile)
.LazyLoad(condition: track => track.TrackFileId > 0,
query: (db, track) => db.Query<TrackFile>()
.Join<TrackFile, Track>(JoinType.Inner, t => t.Tracks, (t, x) => t.Id == x.TrackFileId)
.Join<TrackFile, Album>(JoinType.Inner, t => t.Album, (t, a) => t.AlbumId == a.Id)
.Join<TrackFile, Artist>(JoinType.Inner, t => t.Artist, (t, a) => t.Album.Value.ArtistMetadataId == a.ArtistMetadataId)
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.Where(t => t.Id == track.TrackFileId)
.SingleOrDefault())
.For(t => t.Artist)
.LazyLoad(condition: t => t.AlbumReleaseId > 0, query: (db, t) => db.Query<Artist>()
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.Join<Artist, Album>(JoinType.Inner, a => a.Albums, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Album, AlbumRelease>(JoinType.Inner, a => a.AlbumReleases, (l, r) => l.Id == r.AlbumId)
.Where<AlbumRelease>(r => r.Id == t.AlbumReleaseId)
.SingleOrDefault());
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
.Relationship()
.LazyLoad(t => t.TrackFile,
(db, track) => MediaFileRepository.Query(db,
new SqlBuilder()
.Join<TrackFile, Track>((l, r) => l.Id == r.TrackFileId)
.Join<TrackFile, Album>((l, r) => l.AlbumId == r.Id)
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Artist, ArtistMetadata>((l, r) => l.ArtistMetadataId == r.Id)
.Where<TrackFile>(t => t.Id == track.TrackFileId)).SingleOrDefault(),
t => t.TrackFileId > 0)
.LazyLoad(x => x.Artist,
(db, t) => ArtistRepository.Query(db,
new SqlBuilder()
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Join<Album, AlbumRelease>((l, r) => l.Id == r.AlbumId)
.Where<AlbumRelease>(r => r.Id == t.AlbumReleaseId)).SingleOrDefault(),
t => t.Id > 0);
Mapper.Entity<TrackFile>("TrackFiles").RegisterModel()
.HasOne(f => f.Album, f => f.AlbumId)
.For(f => f.Tracks)
.LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query<Track>()
.Where(x => x.TrackFileId == f.Id)
.ToList())
.For(t => t.Artist)
.LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query<Artist>()
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
.Join<Artist, Album>(JoinType.Inner, a => a.Albums, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Where<Album>(r => r.Id == f.AlbumId)
.SingleOrDefault());
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions")
.LazyLoad(x => x.Tracks, (db, file) => db.Query<Track>(new SqlBuilder().Where<Track>(t => t.TrackFileId == file.Id)).ToList(), x => x.Id > 0)
.LazyLoad(x => x.Artist,
(db, f) => ArtistRepository.Query(db,
new SqlBuilder()
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
.Where<Album>(a => a.Id == f.AlbumId)).SingleOrDefault(),
t => t.Id > 0);
Mapper.Entity<QualityDefinition>("QualityDefinitions").RegisterModel()
.Ignore(d => d.GroupName)
.Ignore(d => d.GroupWeight)
.Ignore(d => d.Weight);
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
Mapper.Entity<MetadataProfile>().RegisterModel("MetadataProfiles");
Mapper.Entity<Log>().RegisterModel("Logs");
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
Mapper.Entity<AlbumStatistics>().MapResultSet();
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
Mapper.Entity<LyricFile>().RegisterModel("LyricFiles");
Mapper.Entity<OtherExtraFile>().RegisterModel("ExtraFiles");
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
Mapper.Entity<QualityProfile>("QualityProfiles").RegisterModel();
Mapper.Entity<MetadataProfile>("MetadataProfiles").RegisterModel();
Mapper.Entity<Log>("Logs").RegisterModel();
Mapper.Entity<NamingConfig>("NamingConfig").RegisterModel();
Mapper.Entity<Blacklist>("Blacklist").RegisterModel();
Mapper.Entity<MetadataFile>("MetadataFiles").RegisterModel();
Mapper.Entity<LyricFile>("LyricFiles").RegisterModel();
Mapper.Entity<OtherExtraFile>("ExtraFiles").RegisterModel();
Mapper.Entity<PendingRelease>("PendingReleases").RegisterModel()
.Ignore(e => e.RemoteAlbum);
Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings");
Mapper.Entity<Tag>().RegisterModel("Tags");
Mapper.Entity<ReleaseProfile>().RegisterModel("ReleaseProfiles");
Mapper.Entity<RemotePathMapping>("RemotePathMappings").RegisterModel();
Mapper.Entity<Tag>("Tags").RegisterModel();
Mapper.Entity<ReleaseProfile>("ReleaseProfiles").RegisterModel();
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
Mapper.Entity<User>().RegisterModel("Users");
Mapper.Entity<CommandModel>().RegisterModel("Commands")
.Ignore(c => c.Message);
Mapper.Entity<DelayProfile>("DelayProfiles").RegisterModel();
Mapper.Entity<User>("Users").RegisterModel();
Mapper.Entity<CommandModel>("Commands").RegisterModel()
.Ignore(c => c.Message);
Mapper.Entity<IndexerStatus>().RegisterModel("IndexerStatus");
Mapper.Entity<DownloadClientStatus>().RegisterModel("DownloadClientStatus");
Mapper.Entity<ImportListStatus>().RegisterModel("ImportListStatus");
Mapper.Entity<IndexerStatus>("IndexerStatus").RegisterModel();
Mapper.Entity<DownloadClientStatus>("DownloadClientStatus").RegisterModel();
Mapper.Entity<ImportListStatus>("ImportListStatus").RegisterModel();
Mapper.Entity<CustomFilter>().RegisterModel("CustomFilters");
Mapper.Entity<ImportListExclusion>().RegisterModel("ImportListExclusions");
Mapper.Entity<CustomFilter>("CustomFilters").RegisterModel();
Mapper.Entity<ImportListExclusion>("ImportListExclusions").RegisterModel();
}
private static void RegisterMappers()
@ -199,40 +195,40 @@ namespace NzbDrone.Core.Datastore
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(List<QualityProfileQualityItem>), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<KeyValuePair<string, int>>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfilePrimaryAlbumTypeItem>), new EmbeddedDocumentConverter(new PrimaryAlbumTypeIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfileSecondaryAlbumTypeItem>), new EmbeddedDocumentConverter(new SecondaryAlbumTypeIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfileReleaseStatusItem>), new EmbeddedDocumentConverter(new ReleaseStatusIntConverter()));
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedAlbumInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedTrackInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), 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 EmbeddedDocumentConverter<List<QualityProfileQualityItem>>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<QualityModel>(new QualityIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<KeyValuePair<string, int>>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<KeyValuePair<string, int>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfilePrimaryAlbumTypeItem>>(new PrimaryAlbumTypeIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileSecondaryAlbumTypeItem>>(new SecondaryAlbumTypeIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileReleaseStatusItem>>(new ReleaseStatusIntConverter()));
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedAlbumInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedTrackInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
SqlMapper.AddTypeHandler(new OsPathConverter());
SqlMapper.RemoveTypeMap(typeof(Guid));
SqlMapper.RemoveTypeMap(typeof(Guid?));
SqlMapper.AddTypeHandler(new GuidConverter());
SqlMapper.AddTypeHandler(new CommandConverter());
}
private static void RegisterProviderSettingConverter()
{
var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf<IProviderConfig>();
var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf<IProviderConfig>()
.Where(x => !x.ContainsGenericParameters);
var providerSettingConverter = new ProviderSettingConverter();
foreach (var embeddedType in settingTypes)
{
MapRepository.Instance.RegisterTypeConverter(embeddedType, providerSettingConverter);
SqlMapper.AddTypeHandler(embeddedType, providerSettingConverter);
}
}
@ -240,16 +236,24 @@ namespace NzbDrone.Core.Datastore
{
var embeddedTypes = typeof(IEmbeddedDocument).Assembly.ImplementationsOf<IEmbeddedDocument>();
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);
}
}
}

@ -0,0 +1,391 @@
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
{
protected StringBuilder _sb;
private const DbType EnumerableMultiParameter = (DbType)(-1);
private readonly string _paramNamePrefix;
private readonly bool _requireConcreteValue = false;
private int _paramCount = 0;
private bool _gotConcreteValue = false;
public WhereBuilder(Expression filter, bool requireConcreteValue, int seq)
{
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
_requireConcreteValue = requireConcreteValue;
_sb = new StringBuilder();
Parameters = new DynamicParameters();
if (filter != null)
{
Visit(filter);
}
}
public DynamicParameters Parameters { get; private set; }
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)
{
var method = expression.Method.Name;
switch (expression.Method.Name)
{
case "Contains":
ParseContainsExpression(expression);
break;
case "StartsWith":
ParseStartsWith(expression);
break;
case "EndsWith":
ParseEndsWith(expression);
break;
default:
var 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)
{
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
var gotValue = TryGetRightValue(expression, out var value);
// Only use the SQL condition if the expression didn't resolve to an actual value
if (tableName != null && !gotValue)
{
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
}
else
{
if (value != null)
{
// string is IEnumerable<Char> but we don't want to pick up that case
var type = value.GetType();
var typeInfo = type.GetTypeInfo();
var isEnumerable =
type != typeof(string) && (
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
}
return expression;
}
protected override Expression VisitConstant(ConstantExpression expression)
{
if (expression.Value != null)
{
var paramName = AddParameter(expression.Value);
_sb.Append(paramName);
}
else
{
_gotConcreteValue = true;
_sb.Append("NULL");
}
return expression;
}
private bool TryGetConstantValue(Expression expression, out object result)
{
result = null;
if (expression is ConstantExpression constExp)
{
result = constExp.Value;
return true;
}
return false;
}
private bool TryGetPropertyValue(MemberExpression expression, out object result)
{
result = null;
if (expression.Expression is MemberExpression nested)
{
// Value is passed in as a property on a parent entity
var container = (nested.Expression as ConstantExpression)?.Value;
if (container == null)
{
return false;
}
var entity = GetFieldValue(container, nested.Member);
result = GetFieldValue(entity, expression.Member);
return true;
}
return false;
}
private bool TryGetVariableValue(MemberExpression expression, out object result)
{
result = null;
// Value is passed in as a variable
if (expression.Expression is ConstantExpression nested)
{
result = GetFieldValue(nested.Value, expression.Member);
return true;
}
return false;
}
private bool TryGetRightValue(Expression expression, out object value)
{
value = null;
if (TryGetConstantValue(expression, out value))
{
return true;
}
var memberExp = expression as MemberExpression;
if (TryGetPropertyValue(memberExp, out value))
{
return true;
}
if (TryGetVariableValue(memberExp, out value))
{
return true;
}
return false;
}
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 bool IsNullVariable(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant &&
TryGetConstantValue(expression, out var constResult) &&
constResult == null)
{
return true;
}
if (expression.NodeType == ExpressionType.MemberAccess &&
expression is MemberExpression member &&
((TryGetPropertyValue(member, out var result) && result == null) ||
(TryGetVariableValue(member, out result) && result == null)))
{
return true;
}
return false;
}
private string Decode(BinaryExpression expression)
{
if (IsNullVariable(expression.Right))
{
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) ||
(list.Type == typeof(List<string>) && !TryGetRightValue(list, out var _))))
{
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 ");
// hardcode the integer list if it exists to bypass parameter limit
if (item.Type == typeof(int) && TryGetRightValue(list, out var value))
{
var items = (IEnumerable<int>)value;
_sb.Append("(");
_sb.Append(string.Join(", ", items));
_sb.Append(")");
_gotConcreteValue = true;
}
else
{
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;
}
}
}

@ -20,17 +20,17 @@ namespace NzbDrone.Core.Download.Pending
public void DeleteByArtistId(int artistId)
{
Delete(r => r.ArtistId == artistId);
Delete(artistId);
}
public List<PendingRelease> AllByArtistId(int artistId)
{
return Query.Where(p => p.ArtistId == artistId);
return Query(p => p.ArtistId == artistId);
}
public List<PendingRelease> WithoutFallback()
{
return Query.Where(p => p.Reason != PendingReleaseReason.Fallback);
return Query(p => p.Reason != PendingReleaseReason.Fallback);
}
}
}

@ -173,7 +173,7 @@ namespace NzbDrone.Core.Extras
foreach (var trackFile in trackFiles)
{
var localTrackFile = trackFile;
trackFile.Tracks = new LazyList<Track>(tracks.Where(e => e.TrackFileId == localTrackFile.Id));
trackFile.Tracks = tracks.Where(e => e.TrackFileId == localTrackFile.Id).ToList();
}
return trackFiles;

@ -42,22 +42,22 @@ namespace NzbDrone.Core.Extras.Files
public List<TExtraFile> GetFilesByArtist(int artistId)
{
return Query.Where(c => c.ArtistId == artistId);
return Query(c => c.ArtistId == artistId);
}
public List<TExtraFile> GetFilesByAlbum(int artistId, int albumId)
{
return Query.Where(c => c.ArtistId == artistId && c.AlbumId == albumId);
return Query(c => c.ArtistId == artistId && c.AlbumId == albumId);
}
public List<TExtraFile> GetFilesByTrackFile(int trackFileId)
{
return Query.Where(c => c.TrackFileId == trackFileId);
return Query(c => c.TrackFileId == trackFileId);
}
public TExtraFile FindByPath(string path)
{
return Query.Where(c => c.RelativePath == path).SingleOrDefault();
return Query(c => c.RelativePath == path).SingleOrDefault();
}
}
}

@ -1,7 +1,7 @@
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.Music;
@ -30,63 +30,72 @@ namespace NzbDrone.Core.History
public History MostRecentForAlbum(int albumId)
{
return Query.Where(h => h.AlbumId == albumId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
return Query(h => h.AlbumId == albumId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
}
public History MostRecentForDownloadId(string downloadId)
{
return Query.Where(h => h.DownloadId == downloadId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
return Query(h => h.DownloadId == downloadId)
.OrderByDescending(h => h.Date)
.FirstOrDefault();
}
public List<History> FindByDownloadId(string downloadId)
{
return Query.Join<History, Artist>(JoinType.Left, h => h.Artist, (h, a) => h.ArtistId == a.Id)
.Join<History, Album>(JoinType.Left, h => h.Album, (h, r) => h.AlbumId == r.Id)
.Where(h => h.DownloadId == downloadId);
return _database.QueryJoined<History, Artist, Album>(
Builder()
.Join<History, Artist>((h, a) => h.ArtistId == a.Id)
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
.Where<History>(h => h.DownloadId == downloadId),
(history, artist, album) =>
{
history.Artist = artist;
history.Album = album;
return history;
}).ToList();
}
public List<History> GetByArtist(int artistId, HistoryEventType? eventType)
{
var query = Query.Where(h => h.ArtistId == artistId);
var builder = Builder().Where<History>(h => h.ArtistId == artistId);
if (eventType.HasValue)
{
query.AndWhere(h => h.EventType == eventType);
builder.Where<History>(h => h.EventType == eventType);
}
query.OrderByDescending(h => h.Date);
return query;
return Query(builder).OrderByDescending(h => h.Date).ToList();
}
public List<History> GetByAlbum(int albumId, HistoryEventType? eventType)
{
var query = Query.Join<History, Album>(JoinType.Inner, h => h.Album, (h, e) => h.AlbumId == e.Id)
.Where(h => h.AlbumId == albumId);
var builder = Builder()
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
.Where<History>(h => h.AlbumId == albumId);
if (eventType.HasValue)
{
query.AndWhere(h => h.EventType == eventType);
builder.Where<History>(h => h.EventType == eventType);
}
query.OrderByDescending(h => h.Date);
return query;
return _database.QueryJoined<History, Album>(
builder,
(history, album) =>
{
history.Album = album;
return history;
}).OrderByDescending(h => h.Date).ToList();
}
public List<History> FindDownloadHistory(int idArtistId, QualityModel quality)
{
return Query.Where(h =>
h.ArtistId == idArtistId &&
h.Quality == quality &&
(h.EventType == HistoryEventType.Grabbed ||
h.EventType == HistoryEventType.DownloadFailed ||
h.EventType == HistoryEventType.TrackFileImported))
.ToList();
var allowed = new[] { HistoryEventType.Grabbed, HistoryEventType.DownloadFailed, HistoryEventType.TrackFileImported };
return Query(h => h.ArtistId == idArtistId &&
h.Quality == quality &&
allowed.Contains(h.EventType));
}
public void DeleteForArtist(int artistId)
@ -94,27 +103,29 @@ namespace NzbDrone.Core.History
Delete(c => c.ArtistId == artistId);
}
protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
{
var baseQuery = query.Join<History, Artist>(JoinType.Inner, h => h.Artist, (h, a) => h.ArtistId == a.Id)
.Join<History, Album>(JoinType.Inner, h => h.Album, (h, r) => h.AlbumId == r.Id)
.Join<History, Track>(JoinType.Left, h => h.Track, (h, t) => h.TrackId == t.Id);
return base.GetPagedQuery(baseQuery, pagingSpec);
}
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
.Join<History, Artist>((h, a) => h.ArtistId == a.Id)
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
.LeftJoin<History, Track>((h, t) => h.TrackId == t.Id);
protected override IEnumerable<History> PagedQuery(SqlBuilder builder) =>
_database.QueryJoined<History, Artist, Album, Track>(builder, (history, artist, album, track) =>
{
history.Artist = artist;
history.Album = album;
history.Track = track;
return history;
});
public List<History> Since(DateTime date, HistoryEventType? eventType)
{
var query = Query.Where(h => h.Date >= date);
var builder = Builder().Where<History>(x => x.Date >= date);
if (eventType.HasValue)
{
query.AndWhere(h => h.EventType == eventType);
builder.Where<History>(h => h.EventType == eventType);
}
query.OrderBy(h => h.Date);
return query;
return Query(builder).OrderBy(h => h.Date).ToList();
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -13,9 +14,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE RelativePath
@ -25,6 +26,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
OR RelativePath
LIKE '/%'
)");
}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -13,12 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM NamingConfig
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM NamingConfig
WHERE ID NOT IN (
SELECT ID FROM NamingConfig
LIMIT 1)");
}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -13,12 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM Users
WHERE ID NOT IN (
SELECT ID FROM Users
LIMIT 1)");
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM Users
WHERE ID NOT IN (
SELECT ID FROM Users
LIMIT 1)");
}
}
}
}

@ -1,4 +1,5 @@
using System;
using System;
using Dapper;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.Pending;
@ -15,18 +16,17 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
var twoWeeksAgo = DateTime.UtcNow.AddDays(-14);
mapper.Delete<PendingRelease>(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')");
using (var mapper = _database.OpenConnection())
{
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 }
});
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -21,54 +22,58 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void DeleteDuplicateArtistMetadata()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type = 1
GROUP BY ArtistId, Consumer
HAVING COUNT(ArtistId) > 1
)");
}
}
private void DeleteDuplicateAlbumMetadata()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
WHERE Id IN (
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type = 6
GROUP BY AlbumId, Consumer
HAVING COUNT(AlbumId) > 1
)");
}
}
private void DeleteDuplicateTrackMetadata()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
WHERE Id IN (
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type = 2
GROUP BY TrackFileId, Consumer
HAVING COUNT(TrackFileId) > 1
)");
}
}
private void DeleteDuplicateTrackImages()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
WHERE Id IN (
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type = 5
GROUP BY TrackFileId, Consumer
HAVING COUNT(TrackFileId) > 1
)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM Albums
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM Albums
WHERE Id IN (
SELECT Albums.Id FROM Albums
LEFT OUTER JOIN Artists
ON Albums.ArtistMetadataId = Artists.ArtistMetadataId
WHERE Artists.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,15 +14,16 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM ArtistMetadata
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ArtistMetadata
WHERE Id IN (
SELECT ArtistMetadata.Id FROM ArtistMetadata
LEFT OUTER JOIN Albums ON Albums.ArtistMetadataId = ArtistMetadata.Id
LEFT OUTER JOIN Tracks ON Tracks.ArtistMetadataId = ArtistMetadata.Id
LEFT OUTER JOIN Artists ON Artists.ArtistMetadataId = ArtistMetadata.Id
WHERE Albums.Id IS NULL AND Tracks.Id IS NULL AND Artists.Id IS NULL)");
}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM Blacklist
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM Blacklist
WHERE Id IN (
SELECT Blacklist.Id FROM Blacklist
LEFT OUTER JOIN Artists
ON Blacklist.ArtistId = Artists.Id
WHERE Artists.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM DownloadClientStatus
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM DownloadClientStatus
WHERE Id IN (
SELECT DownloadClientStatus.Id FROM DownloadClientStatus
LEFT OUTER JOIN DownloadClients
ON DownloadClientStatus.ProviderId = DownloadClients.Id
WHERE DownloadClients.Id IS NULL)");
}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -19,26 +20,28 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void CleanupOrphanedByArtist()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM History
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM History
WHERE Id IN (
SELECT History.Id FROM History
LEFT OUTER JOIN Artists
ON History.ArtistId = Artists.Id
WHERE Artists.Id IS NULL)");
}
}
private void CleanupOrphanedByAlbum()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM History
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM History
WHERE Id IN (
SELECT History.Id FROM History
LEFT OUTER JOIN Albums
ON History.AlbumId = Albums.Id
WHERE Albums.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM ImportListStatus
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM ImportListStatus
WHERE Id IN (
SELECT ImportListStatus.Id FROM ImportListStatus
LEFT OUTER JOIN ImportLists
ON ImportListStatus.ProviderId = ImportLists.Id
WHERE ImportLists.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM IndexerStatus
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM IndexerStatus
WHERE Id IN (
SELECT IndexerStatus.Id FROM IndexerStatus
LEFT OUTER JOIN Indexers
ON IndexerStatus.ProviderId = Indexers.Id
WHERE Indexers.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -22,62 +23,67 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
private void DeleteOrphanedByArtist()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT MetadataFiles.Id FROM MetadataFiles
LEFT OUTER JOIN Artists
ON MetadataFiles.ArtistId = Artists.Id
WHERE Artists.Id IS NULL)");
}
}
private void DeleteOrphanedByAlbum()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT MetadataFiles.Id FROM MetadataFiles
LEFT OUTER JOIN Albums
ON MetadataFiles.AlbumId = Albums.Id
WHERE MetadataFiles.AlbumId > 0
AND Albums.Id IS NULL)");
}
}
private void DeleteOrphanedByTrackFile()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT MetadataFiles.Id FROM MetadataFiles
LEFT OUTER JOIN TrackFiles
ON MetadataFiles.TrackFileId = TrackFiles.Id
WHERE MetadataFiles.TrackFileId > 0
AND TrackFiles.Id IS NULL)");
}
}
private void DeleteWhereAlbumIdIsZero()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type IN (4, 6)
AND AlbumId = 0)");
}
}
private void DeleteWhereTrackFileIsZero()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM MetadataFiles
WHERE Id IN (
SELECT Id FROM MetadataFiles
WHERE Type IN (2, 5)
AND TrackFileId = 0)");
}
}
}
}

@ -1,4 +1,5 @@
using NzbDrone.Core.Datastore;
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM PendingReleases
WHERE Id IN (
SELECT PendingReleases.Id FROM PendingReleases
LEFT OUTER JOIN Artists
ON PendingReleases.ArtistId = Artists.Id
WHERE Artists.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM AlbumReleases
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM AlbumReleases
WHERE Id IN (
SELECT AlbumReleases.Id FROM AlbumReleases
LEFT OUTER JOIN Albums
ON AlbumReleases.AlbumId = Albums.Id
WHERE Albums.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,10 +14,10 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
// Unlink where track no longer exists
mapper.ExecuteNonQuery(@"UPDATE TrackFiles
using (var mapper = _database.OpenConnection())
{
// Unlink where track no longer exists
mapper.Execute(@"UPDATE TrackFiles
SET AlbumId = 0
WHERE Id IN (
SELECT TrackFiles.Id FROM TrackFiles
@ -24,14 +25,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
ON TrackFiles.Id = Tracks.TrackFileId
WHERE Tracks.Id IS NULL)");
// Unlink Tracks where the Trackfiles entry no longer exists
mapper.ExecuteNonQuery(@"UPDATE Tracks
// Unlink Tracks where the Trackfiles entry no longer exists
mapper.Execute(@"UPDATE Tracks
SET TrackFileId = 0
WHERE Id IN (
SELECT Tracks.Id FROM Tracks
LEFT OUTER JOIN TrackFiles
ON Tracks.TrackFileId = TrackFiles.Id
WHERE TrackFiles.Id IS NULL)");
}
}
}
}

@ -1,3 +1,4 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
mapper.ExecuteNonQuery(@"DELETE FROM Tracks
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"DELETE FROM Tracks
WHERE Id IN (
SELECT Tracks.Id FROM Tracks
LEFT OUTER JOIN AlbumReleases
ON Tracks.AlbumReleaseId = AlbumReleases.Id
WHERE AlbumReleases.Id IS NULL)");
}
}
}
}

@ -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,24 +17,25 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
public void Clean()
{
var mapper = _database.GetDataMapper();
var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles" }
using (var mapper = _database.OpenConnection())
{
var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles" }
.SelectMany(v => GetUsedTags(v, mapper))
.Distinct()
.ToArray();
.Distinct()
.ToArray();
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
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<List<int>>)
.Distinct()
.ToArray();
return mapper.Query<List<int>>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'")
.SelectMany(x => x)
.Distinct()
.ToArray();
}
}
}

@ -1,4 +1,5 @@
using System;
using Dapper;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore;
@ -23,12 +24,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
_logger.Debug("Not running scheduled task last execution cleanup during debug");
}
var mapper = _database.GetDataMapper();
mapper.AddParameter("time", DateTime.UtcNow);
mapper.ExecuteNonQuery(@"UPDATE ScheduledTasks
SET LastExecution = @time
WHERE LastExecution > @time");
using (var mapper = _database.OpenConnection())
{
mapper.Execute(@"UPDATE ScheduledTasks
SET LastExecution = @time
WHERE LastExecution > @time",
new { time = DateTime.UtcNow });
}
}
}
}

@ -20,12 +20,14 @@ namespace NzbDrone.Core.ImportLists.Exclusions
public ImportListExclusion FindByForeignId(string foreignId)
{
return Query.Where<ImportListExclusion>(m => m.ForeignId == foreignId).SingleOrDefault();
return Query(m => m.ForeignId == foreignId).SingleOrDefault();
}
public List<ImportListExclusion> FindByForeignId(List<string> ids)
{
return Query.Where($"[ForeignId] IN ('{string.Join("', '", ids)}')").ToList();
// Using Enumerable.Contains forces the builder to create an 'IN'
// and not a string 'LIKE' expression
return Query(x => Enumerable.Contains(ids, x.ForeignId));
}
}
}

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
public int MinimumSeeders { get; set; }
[FieldDefinition(4)]
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
public int? EarlyReleaseLimit { get; set; }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save