mirror of https://github.com/lidarr/Lidarr
New: Postgres Database Support
Co-Authored-By: Qstick <376117+Qstick@users.noreply.github.com>
This commit is contained in:
parent
f7839adc38
commit
8f6e099794
|
@ -535,6 +535,62 @@ stages:
|
||||||
testRunTitle: '$(testName) Unit Tests'
|
testRunTitle: '$(testName) Unit Tests'
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
|
- job: Unit_LinuxCore_Postgres
|
||||||
|
displayName: Unit Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Lidarr.*.linux-core-x64.tar.gz'
|
||||||
|
artifactName: linux-x64-tests
|
||||||
|
Lidarr__Postgres__Host: 'localhost'
|
||||||
|
Lidarr__Postgres__Port: '5432'
|
||||||
|
Lidarr__Postgres__User: 'lidarr'
|
||||||
|
Lidarr__Postgres__Password: 'lidarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
timeoutInMinutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: $(artifactName)
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- bash: |
|
||||||
|
chmod a+x _tests/fpcalc
|
||||||
|
displayName: Make fpcalc Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: find ${TESTSFOLDER} -name "Lidarr.Test.Dummy" -exec chmod a+x {} \;
|
||||||
|
displayName: Make Test Dummy Executable
|
||||||
|
condition: and(succeeded(), ne(variables['osName'], 'Windows'))
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=lidarr \
|
||||||
|
-e POSTGRES_USER=lidarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
ls -lR ${TESTSFOLDER}
|
||||||
|
${TESTSFOLDER}/test.sh Linux Unit Test
|
||||||
|
displayName: Run Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
displayName: Publish Test Results
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'LinuxCore Postgres Unit Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
|
||||||
- stage: Integration
|
- stage: Integration
|
||||||
displayName: Integration
|
displayName: Integration
|
||||||
dependsOn: Packages
|
dependsOn: Packages
|
||||||
|
@ -617,6 +673,67 @@ stages:
|
||||||
failTaskOnFailedTests: true
|
failTaskOnFailedTests: true
|
||||||
displayName: Publish Test Results
|
displayName: Publish Test Results
|
||||||
|
|
||||||
|
- job: Integration_LinuxCore_Postgres
|
||||||
|
displayName: Integration Native LinuxCore with Postgres Database
|
||||||
|
dependsOn: Prepare
|
||||||
|
condition: and(succeeded(), eq(dependencies.Prepare.outputs['setVar.backendNotUpdated'], '0'))
|
||||||
|
variables:
|
||||||
|
pattern: 'Lidarr.*.linux-core-x64.tar.gz'
|
||||||
|
Lidarr__Postgres__Host: 'localhost'
|
||||||
|
Lidarr__Postgres__Port: '5432'
|
||||||
|
Lidarr__Postgres__User: 'lidarr'
|
||||||
|
Lidarr__Postgres__Password: 'lidarr'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: ${{ variables.linuxImage }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: 'Install .net core'
|
||||||
|
inputs:
|
||||||
|
version: $(dotnetVersion)
|
||||||
|
- checkout: none
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Test Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: 'linux-x64-tests'
|
||||||
|
targetPath: $(testsFolder)
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: Download Build Artifact
|
||||||
|
inputs:
|
||||||
|
buildType: 'current'
|
||||||
|
artifactName: Packages
|
||||||
|
itemPattern: '**/$(pattern)'
|
||||||
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/**/$(pattern)'
|
||||||
|
destinationFolder: '$(Build.ArtifactStagingDirectory)/bin'
|
||||||
|
displayName: Extract Package
|
||||||
|
- bash: |
|
||||||
|
mkdir -p ./bin/
|
||||||
|
cp -r -v ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin/Lidarr/. ./bin/
|
||||||
|
displayName: Move Package Contents
|
||||||
|
- bash: |
|
||||||
|
docker run -d --name=postgres14 \
|
||||||
|
-e POSTGRES_PASSWORD=lidarr \
|
||||||
|
-e POSTGRES_USER=lidarr \
|
||||||
|
-p 5432:5432/tcp \
|
||||||
|
postgres:14
|
||||||
|
displayName: Start postgres
|
||||||
|
- bash: |
|
||||||
|
chmod a+x ${TESTSFOLDER}/test.sh
|
||||||
|
${TESTSFOLDER}/test.sh Linux Integration Test
|
||||||
|
displayName: Run Integration Tests
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFormat: 'NUnit'
|
||||||
|
testResultsFiles: '**/TestResult.xml'
|
||||||
|
testRunTitle: 'Integration LinuxCore Postgres Database Integration Tests'
|
||||||
|
failTaskOnFailedTests: true
|
||||||
|
displayName: Publish Test Results
|
||||||
|
|
||||||
- job: Integration_FreeBSD
|
- job: Integration_FreeBSD
|
||||||
displayName: Integration Native FreeBSD
|
displayName: Integration Native FreeBSD
|
||||||
dependsOn: Prepare
|
dependsOn: Prepare
|
||||||
|
|
|
@ -23,6 +23,8 @@ class About extends Component {
|
||||||
isDocker,
|
isDocker,
|
||||||
runtimeVersion,
|
runtimeVersion,
|
||||||
migrationVersion,
|
migrationVersion,
|
||||||
|
databaseVersion,
|
||||||
|
databaseType,
|
||||||
appData,
|
appData,
|
||||||
startupPath,
|
startupPath,
|
||||||
mode,
|
mode,
|
||||||
|
@ -68,6 +70,11 @@ class About extends Component {
|
||||||
data={migrationVersion}
|
data={migrationVersion}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DescriptionListItem
|
||||||
|
title={translate('Database')}
|
||||||
|
data={`${titleCase(databaseType)} ${databaseVersion}`}
|
||||||
|
/>
|
||||||
|
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
title={translate('AppDataDirectory')}
|
title={translate('AppDataDirectory')}
|
||||||
data={appData}
|
data={appData}
|
||||||
|
@ -108,6 +115,8 @@ About.propTypes = {
|
||||||
runtimeVersion: PropTypes.string.isRequired,
|
runtimeVersion: PropTypes.string.isRequired,
|
||||||
isDocker: PropTypes.bool.isRequired,
|
isDocker: PropTypes.bool.isRequired,
|
||||||
migrationVersion: PropTypes.number.isRequired,
|
migrationVersion: PropTypes.number.isRequired,
|
||||||
|
databaseType: PropTypes.string.isRequired,
|
||||||
|
databaseVersion: PropTypes.string.isRequired,
|
||||||
appData: PropTypes.string.isRequired,
|
appData: PropTypes.string.isRequired,
|
||||||
startupPath: PropTypes.string.isRequired,
|
startupPath: PropTypes.string.isRequired,
|
||||||
mode: PropTypes.string.isRequired,
|
mode: PropTypes.string.isRequired,
|
||||||
|
|
|
@ -78,7 +78,8 @@ namespace Lidarr.Api.V1.System
|
||||||
Mode = _runtimeInfo.Mode,
|
Mode = _runtimeInfo.Mode,
|
||||||
Branch = _configFileProvider.Branch,
|
Branch = _configFileProvider.Branch,
|
||||||
Authentication = _configFileProvider.AuthenticationMethod,
|
Authentication = _configFileProvider.AuthenticationMethod,
|
||||||
SqliteVersion = _database.Version,
|
DatabaseType = _database.DatabaseType,
|
||||||
|
DatabaseVersion = _database.Version,
|
||||||
MigrationVersion = _database.Migration,
|
MigrationVersion = _database.Migration,
|
||||||
UrlBase = _configFileProvider.UrlBase,
|
UrlBase = _configFileProvider.UrlBase,
|
||||||
RuntimeVersion = _platformInfo.Version,
|
RuntimeVersion = _platformInfo.Version,
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace NzbDrone.Automation.Test
|
||||||
|
|
||||||
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
|
||||||
|
|
||||||
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
|
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
|
||||||
_runner.KillAll();
|
_runner.KillAll();
|
||||||
_runner.Start();
|
_runner.Start();
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,8 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||||
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
[TestCase("Hardlink '/home/mySecret/Downloads/abs.mkv' to '/media/abc.mkv' failed.")]
|
||||||
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
[TestCase("https://notifiarr.com/notifier.php: api=1234530f-422f-4aac-b6b3-01233210aaaa&radarr_health_issue_message=Download")]
|
||||||
[TestCase("/lidarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
[TestCase("/lidarr/signalr/messages/negotiate?access_token=1234530f422f4aacb6b301233210aaaa&negotiateVersion=1")]
|
||||||
|
[TestCase(@"[Info] MigrationController: *** Migrating Database=lidarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;Enlist=False ***")]
|
||||||
|
[TestCase(@"[Info] MigrationController: *** Migrating Database=lidarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||||
|
|
||||||
// Announce URLs (passkeys) Magnet & Tracker
|
// Announce URLs (passkeys) Magnet & Tracker
|
||||||
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
[TestCase(@"magnet_uri"":""magnet:?xt=urn:btih:9pr04sgkillroyimaveql2tyu8xyui&dn=&tr=https%3a%2f%2fxxx.yyy%2f9pr04sg601233210imaveql2tyu8xyui%2fannounce""}")]
|
||||||
|
@ -96,9 +98,24 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
|
|
||||||
cleansedMessage.Should().NotContain("mySecret");
|
cleansedMessage.Should().NotContain("mySecret");
|
||||||
|
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||||
cleansedMessage.Should().NotContain("01233210");
|
cleansedMessage.Should().NotContain("01233210");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(@"[Info] MigrationController: *** Migrating Database=lidarr-main;Host=postgres14;Username=mySecret;Password=mySecret;Port=5432;token=mySecret;Enlist=False&username=mySecret;mypassword=mySecret;mypass=shouldkeep1;test_token=mySecret;password=123%@%_@!#^#@;use_password=mySecret;get_token=shouldkeep2;usetoken=shouldkeep3;passwrd=mySecret;")]
|
||||||
|
public void should_keep_message(string message)
|
||||||
|
{
|
||||||
|
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||||
|
|
||||||
|
cleansedMessage.Should().NotContain("mySecret");
|
||||||
|
cleansedMessage.Should().NotContain("123%@%_@!#^#@");
|
||||||
|
cleansedMessage.Should().NotContain("01233210");
|
||||||
|
|
||||||
|
cleansedMessage.Should().Contain("shouldkeep1");
|
||||||
|
cleansedMessage.Should().Contain("shouldkeep2");
|
||||||
|
cleansedMessage.Should().Contain("shouldkeep3");
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
[TestCase(@"Some message (from 32.2.3.5 user agent)")]
|
||||||
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
[TestCase(@"Auth-Invalidated ip 32.2.3.5")]
|
||||||
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
[TestCase(@"Auth-Success ip 32.2.3.5")]
|
||||||
|
|
|
@ -4,11 +4,13 @@ using DryIoc.Microsoft.DependencyInjection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Composition.Extensions;
|
using NzbDrone.Common.Composition.Extensions;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Instrumentation.Extensions;
|
using NzbDrone.Common.Instrumentation.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Extensions;
|
using NzbDrone.Core.Datastore.Extensions;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -29,7 +31,8 @@ namespace NzbDrone.Common.Test
|
||||||
.AddDummyDatabase()
|
.AddDummyDatabase()
|
||||||
.AddStartupContext(new StartupContext("first", "second"));
|
.AddStartupContext(new StartupContext("first", "second"));
|
||||||
|
|
||||||
container.RegisterInstance<IHostLifetime>(new Mock<IHostLifetime>().Object);
|
container.RegisterInstance(new Mock<IHostLifetime>().Object);
|
||||||
|
container.RegisterInstance(new Mock<IOptions<PostgresOptions>>().Object);
|
||||||
|
|
||||||
var serviceProvider = container.GetServiceProvider();
|
var serviceProvider = container.GetServiceProvider();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace NzbDrone.Common.Instrumentation
|
||||||
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"iptorrents\.com/[/a-z0-9?&;]*?(?:[?&;](u|tp)=(?<secret>[^&=;]+?))+(?= |;|&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
new Regex(@"/fetch/[a-z0-9]{32}/(?<secret>[a-z0-9]{32})", RegexOptions.Compiled),
|
||||||
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
new Regex(@"getnzb.*?(?<=\?|&)(r)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"\b(\w*)?(_?(?<!use|get_)token|username|passwo?rd)=(?<secret>[^&=]+?)(?= |&|$|;)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||||
|
|
||||||
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
// Trackers Announce Keys; Designed for Qbit Json; should work for all in theory
|
||||||
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
new Regex(@"announce(\.php)?(/|%2f|%3fpasskey%3d)(?<secret>[a-z0-9]{16,})|(?<secret>[a-z0-9]{16,})(/|%2f)announce"),
|
||||||
|
|
|
@ -127,7 +127,18 @@ namespace NzbDrone.Common.Processes
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
_logger.Trace("Setting environment variable '{0}' to '{1}'", environmentVariable.Key, environmentVariable.Value);
|
||||||
startInfo.EnvironmentVariables.Add(environmentVariable.Key.ToString(), environmentVariable.Value.ToString());
|
|
||||||
|
var key = environmentVariable.Key.ToString();
|
||||||
|
var value = environmentVariable.Value?.ToString();
|
||||||
|
|
||||||
|
if (startInfo.EnvironmentVariables.ContainsKey(key))
|
||||||
|
{
|
||||||
|
startInfo.EnvironmentVariables[key] = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startInfo.EnvironmentVariables.Add(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void SingleOrDefault_should_return_null_on_empty_db()
|
public void SingleOrDefault_should_return_null_on_empty_db()
|
||||||
{
|
{
|
||||||
Mocker.Resolve<IDatabase>()
|
Mocker.Resolve<IDatabase>()
|
||||||
.OpenConnection().Query<Artist>("SELECT * FROM Artists")
|
.OpenConnection().Query<Artist>("SELECT * FROM \"Artists\"")
|
||||||
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
.SingleOrDefault(c => c.CleanName == "SomeTitle")
|
||||||
.Should()
|
.Should()
|
||||||
.BeNull();
|
.BeNull();
|
||||||
|
|
|
@ -2,7 +2,9 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using FluentAssertions.Equivalency;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.History;
|
using NzbDrone.Core.History;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
|
@ -13,6 +15,17 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DatabaseRelationshipFixture : DbTest
|
public class DatabaseRelationshipFixture : DbTest
|
||||||
{
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
AssertionOptions.AssertEquivalencyUsing(options =>
|
||||||
|
{
|
||||||
|
options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs<DateTime>();
|
||||||
|
options.Using<DateTime?>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs<DateTime?>();
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void one_to_one()
|
public void one_to_one()
|
||||||
{
|
{
|
||||||
|
@ -33,13 +46,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var loadedAlbum = Db.Single<AlbumRelease>().Album.Value;
|
var loadedAlbum = Db.Single<AlbumRelease>().Album.Value;
|
||||||
|
|
||||||
loadedAlbum.Should().NotBeNull();
|
loadedAlbum.Should().NotBeNull();
|
||||||
loadedAlbum.Should().BeEquivalentTo(album,
|
loadedAlbum.Should().BeEquivalentTo(album, AlbumComparerOptions);
|
||||||
options => options
|
|
||||||
.IncludingAllRuntimeProperties()
|
|
||||||
.Excluding(c => c.Artist)
|
|
||||||
.Excluding(c => c.ArtistId)
|
|
||||||
.Excluding(c => c.ArtistMetadata)
|
|
||||||
.Excluding(c => c.AlbumReleases));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -86,5 +93,9 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
|
|
||||||
returnedHistory[0].Quality.Quality.Should().Be(Quality.MP3_320);
|
returnedHistory[0].Quality.Quality.Should().Be(Quality.MP3_320);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EquivalencyAssertionOptions<Album> AlbumComparerOptions(EquivalencyAssertionOptions<Album> opts) => opts.ComparingByMembers<Album>()
|
||||||
|
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>))
|
||||||
|
.Excluding(x => x.ArtistId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void should_lazy_load_artist_for_trackfile()
|
public void should_lazy_load_artist_for_trackfile()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<TrackFile>(new SqlBuilder()).ToList();
|
var tracks = db.Query<TrackFile>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||||
|
|
||||||
Assert.IsNotEmpty(tracks);
|
Assert.IsNotEmpty(tracks);
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
|
@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void should_lazy_load_trackfile_if_not_joined()
|
public void should_lazy_load_trackfile_if_not_joined()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<Track>(new SqlBuilder()).ToList();
|
var tracks = db.Query<Track>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||||
|
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
{
|
{
|
||||||
|
@ -135,7 +135,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var files = MediaFileRepository.Query(db,
|
var files = MediaFileRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
|
.Join<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
|
||||||
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
||||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||||
|
@ -157,7 +157,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var files = db.QueryJoined<TrackFile, Album, Artist, ArtistMetadata>(
|
var files = db.QueryJoined<TrackFile, Album, Artist, ArtistMetadata>(
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
.Join<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
||||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id),
|
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id),
|
||||||
|
@ -186,7 +186,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void should_lazy_load_tracks_if_not_joined()
|
public void should_lazy_load_tracks_if_not_joined()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var release = db.Query<AlbumRelease>(new SqlBuilder().Where<AlbumRelease>(x => x.Id == 1)).SingleOrDefault();
|
var release = db.Query<AlbumRelease>(new SqlBuilder(db.DatabaseType).Where<AlbumRelease>(x => x.Id == 1)).SingleOrDefault();
|
||||||
|
|
||||||
Assert.IsFalse(release.Tracks.IsLoaded);
|
Assert.IsFalse(release.Tracks.IsLoaded);
|
||||||
Assert.IsNotNull(release.Tracks.Value);
|
Assert.IsNotNull(release.Tracks.Value);
|
||||||
|
@ -198,7 +198,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void should_lazy_load_track_if_not_joined()
|
public void should_lazy_load_track_if_not_joined()
|
||||||
{
|
{
|
||||||
var db = Mocker.Resolve<IDatabase>();
|
var db = Mocker.Resolve<IDatabase>();
|
||||||
var tracks = db.Query<TrackFile>(new SqlBuilder()).ToList();
|
var tracks = db.Query<TrackFile>(new SqlBuilder(db.DatabaseType)).ToList();
|
||||||
|
|
||||||
foreach (var track in tracks)
|
foreach (var track in tracks)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var profiles = db.Query<Profile4>("SELECT Items FROM Profiles LIMIT 1");
|
var profiles = db.Query<Profile4>("SELECT \"Items\" FROM \"Profiles\" LIMIT 1");
|
||||||
|
|
||||||
var items = profiles.First().Items;
|
var items = profiles.First().Items;
|
||||||
items.Should().HaveCount(7);
|
items.Should().HaveCount(7);
|
||||||
|
@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var profiles = db.Query<Profile4>("SELECT Items FROM Profiles LIMIT 1");
|
var profiles = db.Query<Profile4>("SELECT \"Items\" FROM \"Profiles\" LIMIT 1");
|
||||||
|
|
||||||
var items = profiles.First().Items;
|
var items = profiles.First().Items;
|
||||||
items.Should().HaveCount(7);
|
items.Should().HaveCount(7);
|
||||||
|
|
|
@ -24,8 +24,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
Status = 1,
|
Status = 1,
|
||||||
Images = "",
|
Images = "",
|
||||||
Path = $"/mnt/data/path/{name}",
|
Path = $"/mnt/data/path/{name}",
|
||||||
Monitored = 1,
|
Monitored = true,
|
||||||
AlbumFolder = 1,
|
AlbumFolder = true,
|
||||||
LanguageProfileId = 1,
|
LanguageProfileId = 1,
|
||||||
MetadataProfileId = 1
|
MetadataProfileId = 1
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
Title = title,
|
Title = title,
|
||||||
CleanTitle = title,
|
CleanTitle = title,
|
||||||
Images = "",
|
Images = "",
|
||||||
Monitored = 1,
|
Monitored = true,
|
||||||
AlbumType = "Studio",
|
AlbumType = "Studio",
|
||||||
Duration = 100,
|
Duration = 100,
|
||||||
Media = "",
|
Media = "",
|
||||||
|
@ -61,9 +61,9 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ForeignTrackId = id.ToString(),
|
ForeignTrackId = id.ToString(),
|
||||||
ArtistId = artistid,
|
ArtistId = artistid,
|
||||||
AlbumId = albumid,
|
AlbumId = albumid,
|
||||||
Explicit = 0,
|
Explicit = false,
|
||||||
Compilation = 0,
|
Compilation = false,
|
||||||
Monitored = 0,
|
Monitored = false,
|
||||||
Duration = 100,
|
Duration = 100,
|
||||||
MediumNumber = 1,
|
MediumNumber = 1,
|
||||||
AbsoluteTrackNumber = i,
|
AbsoluteTrackNumber = i,
|
||||||
|
@ -74,8 +74,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
|
||||||
private IEnumerable<AlbumRelease> VerifyAlbumReleases(IDirectDataMapper db)
|
private IEnumerable<AlbumRelease> VerifyAlbumReleases(IDirectDataMapper db)
|
||||||
{
|
{
|
||||||
var releases = db.Query<AlbumRelease>("SELECT * FROM AlbumReleases");
|
var releases = db.Query<AlbumRelease>("SELECT * FROM \"AlbumReleases\"");
|
||||||
var albums = db.Query<Album>("SELECT * FROM Albums");
|
var albums = db.Query<Album>("SELECT * FROM \"Albums\"");
|
||||||
|
|
||||||
// we only put in one release per album
|
// we only put in one release per album
|
||||||
releases.Count().Should().Be(albums.Count());
|
releases.Count().Should().Be(albums.Count());
|
||||||
|
@ -91,12 +91,12 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
|
||||||
private void VerifyTracks(IDirectDataMapper db, int albumId, int albumReleaseId, int expectedCount)
|
private void VerifyTracks(IDirectDataMapper db, int albumId, int albumReleaseId, int expectedCount)
|
||||||
{
|
{
|
||||||
var tracks = db.Query<Track>("SELECT Tracks.* FROM Tracks " +
|
var tracks = db.Query<Track>("SELECT \"Tracks\".* FROM \"Tracks\" " +
|
||||||
"JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id " +
|
"JOIN \"AlbumReleases\" ON \"Tracks\".\"AlbumReleaseId\" = \"AlbumReleases\".\"Id\" " +
|
||||||
"JOIN Albums ON AlbumReleases.AlbumId = Albums.Id " +
|
"JOIN \"Albums\" ON \"AlbumReleases\".\"AlbumId\" = \"Albums\".\"Id\" " +
|
||||||
"WHERE Albums.Id = " + albumId).ToList();
|
"WHERE \"Albums\".\"Id\" = " + albumId).ToList();
|
||||||
|
|
||||||
var album = db.Query<Album>("SELECT * FROM Albums WHERE Albums.Id = " + albumId).ToList().Single();
|
var album = db.Query<Album>("SELECT * FROM \"Albums\" WHERE \"Albums\".\"Id\" = " + albumId).ToList().Single();
|
||||||
|
|
||||||
tracks.Count.Should().Be(expectedCount);
|
tracks.Count.Should().Be(expectedCount);
|
||||||
tracks.First().AlbumReleaseId.Should().Be(albumReleaseId);
|
tracks.First().AlbumReleaseId.Should().Be(albumReleaseId);
|
||||||
|
|
|
@ -25,8 +25,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
Id = id,
|
Id = id,
|
||||||
CleanName = name,
|
CleanName = name,
|
||||||
Path = _artistPath,
|
Path = _artistPath,
|
||||||
Monitored = 1,
|
Monitored = true,
|
||||||
AlbumFolder = 1,
|
AlbumFolder = true,
|
||||||
LanguageProfileId = 1,
|
LanguageProfileId = 1,
|
||||||
MetadataProfileId = 1,
|
MetadataProfileId = 1,
|
||||||
ArtistMetadataId = id
|
ArtistMetadataId = id
|
||||||
|
@ -43,9 +43,9 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
Title = title,
|
Title = title,
|
||||||
CleanTitle = title,
|
CleanTitle = title,
|
||||||
Images = "",
|
Images = "",
|
||||||
Monitored = 1,
|
Monitored = true,
|
||||||
AlbumType = "Studio",
|
AlbumType = "Studio",
|
||||||
AnyReleaseOk = 1
|
AnyReleaseOk = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
ForeignTrackId = id.ToString(),
|
ForeignTrackId = id.ToString(),
|
||||||
Explicit = 0,
|
Explicit = false,
|
||||||
TrackFileId = id,
|
TrackFileId = id,
|
||||||
Duration = 100,
|
Duration = 100,
|
||||||
MediumNumber = 1,
|
MediumNumber = 1,
|
||||||
|
@ -102,8 +102,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
|
||||||
private void VerifyTracksFiles(IDirectDataMapper db, int albumId, List<string> expectedPaths)
|
private void VerifyTracksFiles(IDirectDataMapper db, int albumId, List<string> expectedPaths)
|
||||||
{
|
{
|
||||||
var tracks = db.Query("SELECT TrackFiles.* FROM TrackFiles " +
|
var tracks = db.Query("SELECT \"TrackFiles\".* FROM \"TrackFiles\" " +
|
||||||
"WHERE TrackFiles.AlbumId = " + albumId);
|
"WHERE \"TrackFiles\".\"AlbumId\" = " + albumId);
|
||||||
|
|
||||||
TestLogger.Debug($"Got {tracks.Count} tracks");
|
TestLogger.Debug($"Got {tracks.Count} tracks");
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ArtistMetadataId = artistMetadataId,
|
ArtistMetadataId = artistMetadataId,
|
||||||
CleanName = name,
|
CleanName = name,
|
||||||
Path = _artistPath,
|
Path = _artistPath,
|
||||||
Monitored = 1,
|
Monitored = true,
|
||||||
AlbumFolder = 1,
|
AlbumFolder = true,
|
||||||
LanguageProfileId = 1,
|
LanguageProfileId = 1,
|
||||||
MetadataProfileId = 1,
|
MetadataProfileId = 1,
|
||||||
});
|
});
|
||||||
|
@ -43,7 +43,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
|
||||||
private void VerifyArtists(IDirectDataMapper db, List<int> ids)
|
private void VerifyArtists(IDirectDataMapper db, List<int> ids)
|
||||||
{
|
{
|
||||||
var artists = db.Query("SELECT Artists.* from Artists");
|
var artists = db.Query("SELECT \"Artists\".* from \"Artists\"");
|
||||||
|
|
||||||
artists.Select(x => x["Id"]).Should().BeEquivalentTo(ids);
|
artists.Select(x => x["Id"]).Should().BeEquivalentTo(ids);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "Deluge",
|
Name = "Deluge",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Settings = new DelugeSettings36
|
Settings = new DelugeSettings36
|
||||||
|
@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(1);
|
items.Should().HaveCount(1);
|
||||||
items.First().Priority.Should().Be(1);
|
items.First().Priority.Should().Be(1);
|
||||||
|
@ -44,7 +44,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "Deluge",
|
Name = "Deluge",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Settings = new DelugeSettings36
|
Settings = new DelugeSettings36
|
||||||
|
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ConfigContract = "DelugeSettings"
|
ConfigContract = "DelugeSettings"
|
||||||
}).Row(new
|
}).Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "Deluge2",
|
Name = "Deluge2",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Settings = new DelugeSettings36
|
Settings = new DelugeSettings36
|
||||||
|
@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ConfigContract = "DelugeSettings"
|
ConfigContract = "DelugeSettings"
|
||||||
}).Row(new
|
}).Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "sab",
|
Name = "sab",
|
||||||
Implementation = "Sabnzbd",
|
Implementation = "Sabnzbd",
|
||||||
Settings = new SabnzbdSettings36
|
Settings = new SabnzbdSettings36
|
||||||
|
@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(3);
|
items.Should().HaveCount(3);
|
||||||
items[0].Priority.Should().Be(1);
|
items[0].Priority.Should().Be(1);
|
||||||
|
@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 0,
|
Enable = false,
|
||||||
Name = "Deluge",
|
Name = "Deluge",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Settings = new DelugeSettings36
|
Settings = new DelugeSettings36
|
||||||
|
@ -107,7 +107,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ConfigContract = "DelugeSettings"
|
ConfigContract = "DelugeSettings"
|
||||||
}).Row(new
|
}).Row(new
|
||||||
{
|
{
|
||||||
Enable = 0,
|
Enable = false,
|
||||||
Name = "Deluge2",
|
Name = "Deluge2",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Settings = new DelugeSettings36
|
Settings = new DelugeSettings36
|
||||||
|
@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
ConfigContract = "DelugeSettings"
|
ConfigContract = "DelugeSettings"
|
||||||
}).Row(new
|
}).Row(new
|
||||||
{
|
{
|
||||||
Enable = 0,
|
Enable = false,
|
||||||
Name = "sab",
|
Name = "sab",
|
||||||
Implementation = "Sabnzbd",
|
Implementation = "Sabnzbd",
|
||||||
Settings = new SabnzbdSettings36
|
Settings = new SabnzbdSettings36
|
||||||
|
@ -131,7 +131,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition036>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(3);
|
items.Should().HaveCount(3);
|
||||||
items[0].Priority.Should().Be(1);
|
items[0].Priority.Should().Be(1);
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<ProviderDefinition166>("SELECT * FROM Notifications");
|
var items = db.Query<ProviderDefinition166>("SELECT * FROM \"Notifications\"");
|
||||||
|
|
||||||
items.Should().HaveCount(1);
|
items.Should().HaveCount(1);
|
||||||
items.First().Implementation.Should().Be("Email");
|
items.First().Implementation.Should().Be("Email");
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "Deluge",
|
Name = "Deluge",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Priority = 1,
|
Priority = 1,
|
||||||
|
@ -34,7 +34,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(1);
|
items.Should().HaveCount(1);
|
||||||
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||||
|
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "Deluge",
|
Name = "Deluge",
|
||||||
Implementation = "Deluge",
|
Implementation = "Deluge",
|
||||||
Priority = 1,
|
Priority = 1,
|
||||||
|
@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(1);
|
items.Should().HaveCount(1);
|
||||||
items.First().RemoveCompletedDownloads.Should().BeTrue();
|
items.First().RemoveCompletedDownloads.Should().BeTrue();
|
||||||
|
@ -82,7 +82,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
{
|
{
|
||||||
c.Insert.IntoTable("DownloadClients").Row(new
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
{
|
{
|
||||||
Enable = 1,
|
Enable = true,
|
||||||
Name = "RTorrent",
|
Name = "RTorrent",
|
||||||
Implementation = "RTorrent",
|
Implementation = "RTorrent",
|
||||||
Priority = 1,
|
Priority = 1,
|
||||||
|
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM \"DownloadClients\"");
|
||||||
|
|
||||||
items.Should().HaveCount(1);
|
items.Should().HaveCount(1);
|
||||||
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||||
|
|
|
@ -11,9 +11,9 @@ using NzbDrone.Core.Test.Framework;
|
||||||
namespace NzbDrone.Core.Test.Datastore
|
namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class WhereBuilderFixture : CoreTest
|
public class WhereBuilderPostgresFixture : CoreTest
|
||||||
{
|
{
|
||||||
private WhereBuilder _subject;
|
private WhereBuilderPostgres _subject;
|
||||||
|
|
||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
public void MapTables()
|
public void MapTables()
|
||||||
|
@ -22,14 +22,14 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
Mocker.Resolve<DbFactory>();
|
Mocker.Resolve<DbFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilder Where(Expression<Func<Artist, bool>> filter)
|
private WhereBuilderPostgres Where(Expression<Func<Artist, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilder(filter, true, 0);
|
return new WhereBuilderPostgres(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WhereBuilder WhereMetadata(Expression<Func<ArtistMetadata, bool>> filter)
|
private WhereBuilderPostgres WhereMetadata(Expression<Func<ArtistMetadata, bool>> filter)
|
||||||
{
|
{
|
||||||
return new WhereBuilder(filter, true, 0);
|
return new WhereBuilderPostgres(filter, true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -76,7 +76,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
public void where_throws_without_concrete_condition_if_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilder(filter, true, 0);
|
_subject = new WhereBuilderPostgres(filter, true, 0);
|
||||||
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
Assert.Throws<InvalidOperationException>(() => _subject.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
public void where_allows_abstract_condition_if_not_requiresConcreteCondition()
|
||||||
{
|
{
|
||||||
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
|
Expression<Func<Artist, Artist, bool>> filter = (x, y) => x.Id == y.Id;
|
||||||
_subject = new WhereBuilder(filter, false, 0);
|
_subject = new WhereBuilderPostgres(filter, false, 0);
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = \"Artists\".\"Id\")");
|
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = \"Artists\".\"Id\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var test = "small";
|
var test = "small";
|
||||||
_subject = Where(x => x.CleanName.Contains(test));
|
_subject = Where(x => x.CleanName.Contains(test));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1 || '%')");
|
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE '%' || @Clause1_P1 || '%')");
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var test = "small";
|
var test = "small";
|
||||||
_subject = Where(x => test.Contains(x.CleanName));
|
_subject = Where(x => test.Contains(x.CleanName));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(@Clause1_P1 LIKE '%' || \"Artists\".\"CleanName\" || '%')");
|
_subject.ToString().Should().Be($"(@Clause1_P1 ILIKE '%' || \"Artists\".\"CleanName\" || '%')");
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var test = "small";
|
var test = "small";
|
||||||
_subject = Where(x => x.CleanName.StartsWith(test));
|
_subject = Where(x => x.CleanName.StartsWith(test));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE @Clause1_P1 || '%')");
|
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE @Clause1_P1 || '%')");
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var test = "small";
|
var test = "small";
|
||||||
_subject = Where(x => x.CleanName.EndsWith(test));
|
_subject = Where(x => x.CleanName.EndsWith(test));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" LIKE '%' || @Clause1_P1)");
|
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" ILIKE '%' || @Clause1_P1)");
|
||||||
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
_subject.Parameters.Get<string>("Clause1_P1").Should().Be(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var list = new List<int> { 1, 2, 3 };
|
var list = new List<int> { 1, 2, 3 };
|
||||||
_subject = Where(x => list.Contains(x.Id));
|
_subject = Where(x => list.Contains(x.Id));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" IN (1, 2, 3))");
|
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = ANY (('{{1, 2, 3}}')))");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -169,7 +169,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var list = new List<int> { 1, 2, 3 };
|
var list = new List<int> { 1, 2, 3 };
|
||||||
_subject = Where(x => x.CleanName == "test" && list.Contains(x.Id));
|
_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)))");
|
_subject.ToString().Should().Be($"((\"Artists\".\"CleanName\" = @Clause1_P1) AND (\"Artists\".\"Id\" = ANY (('{{1, 2, 3}}'))))");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -179,7 +179,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
|
|
||||||
_subject = Where(x => list.Contains(x.CleanName));
|
_subject = Where(x => list.Contains(x.CleanName));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" IN @Clause1_P1)");
|
_subject.ToString().Should().Be($"(\"Artists\".\"CleanName\" = ANY (@Clause1_P1))");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -187,7 +187,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
{
|
{
|
||||||
_subject = WhereMetadata(x => x.OldForeignArtistIds.Contains("foreignId"));
|
_subject = WhereMetadata(x => x.OldForeignArtistIds.Contains("foreignId"));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"OldForeignArtistIds\" LIKE '%' || @Clause1_P1 || '%')");
|
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"OldForeignArtistIds\" ILIKE '%' || @Clause1_P1 || '%')");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -204,7 +204,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var allowed = new List<ArtistStatusType> { ArtistStatusType.Continuing, ArtistStatusType.Ended };
|
var allowed = new List<ArtistStatusType> { ArtistStatusType.Continuing, ArtistStatusType.Ended };
|
||||||
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)");
|
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -213,7 +213,7 @@ namespace NzbDrone.Core.Test.Datastore
|
||||||
var allowed = new ArtistStatusType[] { ArtistStatusType.Continuing, ArtistStatusType.Ended };
|
var allowed = new ArtistStatusType[] { ArtistStatusType.Continuing, ArtistStatusType.Ended };
|
||||||
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
_subject = WhereMetadata(x => allowed.Contains(x.Status));
|
||||||
|
|
||||||
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" IN @Clause1_P1)");
|
_subject.ToString().Should().Be($"(\"ArtistMetadata\".\"Status\" = ANY (@Clause1_P1))");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
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 WhereBuilderSqliteFixture : CoreTest
|
||||||
|
{
|
||||||
|
private WhereBuilderSqlite _subject;
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void MapTables()
|
||||||
|
{
|
||||||
|
// Generate table mapping
|
||||||
|
Mocker.Resolve<DbFactory>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WhereBuilderSqlite Where(Expression<Func<Artist, bool>> filter)
|
||||||
|
{
|
||||||
|
return new WhereBuilderSqlite(filter, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WhereBuilderSqlite WhereMetadata(Expression<Func<ArtistMetadata, bool>> filter)
|
||||||
|
{
|
||||||
|
return new WhereBuilderSqlite(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 author = new Artist { Id = 10 };
|
||||||
|
_subject = Where(x => x.Id == author.Id);
|
||||||
|
|
||||||
|
_subject.Parameters.ParameterNames.Should().HaveCount(1);
|
||||||
|
_subject.ToString().Should().Be($"(\"Artists\".\"Id\" = @Clause1_P1)");
|
||||||
|
_subject.Parameters.Get<int>("Clause1_P1").Should().Be(author.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 WhereBuilderSqlite(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 WhereBuilderSqlite(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 author = new Artist { CleanName = null };
|
||||||
|
_subject = Where(x => x.CleanName == author.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))");
|
||||||
|
|
||||||
|
_subject.Parameters.ParameterNames.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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 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)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Npgsql;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using NzbDrone.Test.Common.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Framework
|
namespace NzbDrone.Core.Test.Framework
|
||||||
{
|
{
|
||||||
|
@ -49,6 +53,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
public abstract class DbTest : CoreTest
|
public abstract class DbTest : CoreTest
|
||||||
{
|
{
|
||||||
private ITestDatabase _db;
|
private ITestDatabase _db;
|
||||||
|
private DatabaseType _databaseType;
|
||||||
|
|
||||||
protected virtual MigrationType MigrationType => MigrationType.Main;
|
protected virtual MigrationType MigrationType => MigrationType.Main;
|
||||||
|
|
||||||
|
@ -101,17 +106,39 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
|
|
||||||
private IDatabase CreateDatabase(MigrationContext migrationContext)
|
private IDatabase CreateDatabase(MigrationContext migrationContext)
|
||||||
{
|
{
|
||||||
|
if (_databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
CreatePostgresDb();
|
||||||
|
}
|
||||||
|
|
||||||
var factory = Mocker.Resolve<DbFactory>();
|
var factory = Mocker.Resolve<DbFactory>();
|
||||||
|
|
||||||
// If a special migration test or log migration then create new
|
// If a special migration test or log migration then create new
|
||||||
if (migrationContext.BeforeMigration != null)
|
if (migrationContext.BeforeMigration != null || _databaseType == DatabaseType.PostgreSQL)
|
||||||
{
|
{
|
||||||
return factory.Create(migrationContext);
|
return factory.Create(migrationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return CreateSqliteDatabase(factory, migrationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePostgresDb()
|
||||||
|
{
|
||||||
|
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||||
|
PostgresDatabase.Create(options, MigrationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DropPostgresDb()
|
||||||
|
{
|
||||||
|
var options = Mocker.Resolve<IOptions<PostgresOptions>>().Value;
|
||||||
|
PostgresDatabase.Drop(options, MigrationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDatabase CreateSqliteDatabase(IDbFactory factory, MigrationContext migrationContext)
|
||||||
|
{
|
||||||
// Otherwise try to use a cached migrated db
|
// Otherwise try to use a cached migrated db
|
||||||
var cachedDb = GetCachedDatabase(migrationContext.MigrationType);
|
var cachedDb = SqliteDatabase.GetCachedDb(migrationContext.MigrationType);
|
||||||
var testDb = GetTestDb(migrationContext.MigrationType);
|
var testDb = GetTestSqliteDb(migrationContext.MigrationType);
|
||||||
if (File.Exists(cachedDb))
|
if (File.Exists(cachedDb))
|
||||||
{
|
{
|
||||||
TestLogger.Info($"Using cached initial database {cachedDb}");
|
TestLogger.Info($"Using cached initial database {cachedDb}");
|
||||||
|
@ -131,12 +158,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCachedDatabase(MigrationType type)
|
private string GetTestSqliteDb(MigrationType type)
|
||||||
{
|
|
||||||
return Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_{type}.db");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetTestDb(MigrationType type)
|
|
||||||
{
|
{
|
||||||
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
|
return type == MigrationType.Main ? TestFolderInfo.GetDatabase() : TestFolderInfo.GetLogDatabase();
|
||||||
}
|
}
|
||||||
|
@ -151,6 +173,13 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
WithTempAsAppPath();
|
WithTempAsAppPath();
|
||||||
SetupLogging();
|
SetupLogging();
|
||||||
|
|
||||||
|
// populate the possible postgres options
|
||||||
|
var postgresOptions = PostgresDatabase.GetTestOptions();
|
||||||
|
_databaseType = postgresOptions.Host.IsNotNullOrWhiteSpace() ? DatabaseType.PostgreSQL : DatabaseType.SQLite;
|
||||||
|
|
||||||
|
// Set up remaining container services
|
||||||
|
Mocker.SetConstant(Options.Create(postgresOptions));
|
||||||
|
Mocker.SetConstant<IConfigFileProvider>(Mocker.Resolve<ConfigFileProvider>());
|
||||||
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
Mocker.SetConstant<IConnectionStringFactory>(Mocker.Resolve<ConnectionStringFactory>());
|
||||||
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
Mocker.SetConstant<IMigrationController>(Mocker.Resolve<MigrationController>());
|
||||||
|
|
||||||
|
@ -170,12 +199,19 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
// Make sure there are no lingering connections. (When this happens it means we haven't disposed something properly)
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
GC.WaitForPendingFinalizers();
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
SQLiteConnection.ClearAllPools();
|
SQLiteConnection.ClearAllPools();
|
||||||
|
NpgsqlConnection.ClearAllPools();
|
||||||
|
|
||||||
if (TestFolderInfo != null)
|
if (TestFolderInfo != null)
|
||||||
{
|
{
|
||||||
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
DeleteTempFolder(TestFolderInfo.AppDataFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
DropPostgresDb();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
using NzbDrone.Test.Common.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test
|
namespace NzbDrone.Core.Test
|
||||||
{
|
{
|
||||||
|
@ -10,13 +12,13 @@ namespace NzbDrone.Core.Test
|
||||||
[OneTimeTearDown]
|
[OneTimeTearDown]
|
||||||
public void ClearCachedDatabase()
|
public void ClearCachedDatabase()
|
||||||
{
|
{
|
||||||
var mainCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Main.db");
|
var mainCache = SqliteDatabase.GetCachedDb(MigrationType.Main);
|
||||||
if (File.Exists(mainCache))
|
if (File.Exists(mainCache))
|
||||||
{
|
{
|
||||||
File.Delete(mainCache);
|
File.Delete(mainCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
var logCache = Path.Combine(TestContext.CurrentContext.TestDirectory, $"cached_Log.db");
|
var logCache = SqliteDatabase.GetCachedDb(MigrationType.Log);
|
||||||
if (File.Exists(logCache))
|
if (File.Exists(logCache))
|
||||||
{
|
{
|
||||||
File.Delete(logCache);
|
File.Delete(logCache);
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
where T : ModelBase, new();
|
where T : ModelBase, new();
|
||||||
IDirectDataMapper GetDirectDataMapper();
|
IDirectDataMapper GetDirectDataMapper();
|
||||||
IDbConnection OpenConnection();
|
IDbConnection OpenConnection();
|
||||||
|
DatabaseType DatabaseType { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestDatabase : ITestDatabase
|
public class TestDatabase : ITestDatabase
|
||||||
|
@ -30,6 +31,8 @@ namespace NzbDrone.Core.Test.Framework
|
||||||
private readonly IDatabase _dbConnection;
|
private readonly IDatabase _dbConnection;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _dbConnection.DatabaseType;
|
||||||
|
|
||||||
public TestDatabase(IDatabase dbConnection)
|
public TestDatabase(IDatabase dbConnection)
|
||||||
{
|
{
|
||||||
_eventAggregator = new Mock<IEventAggregator>().Object;
|
_eventAggregator = new Mock<IEventAggregator>().Object;
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Housekeeping.Housekeepers;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CleanupOrphanedReleasesFixture : DbTest<CleanupOrphanedReleases, AlbumRelease>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_delete_orphaned_releases()
|
||||||
|
{
|
||||||
|
var albumRelease = Builder<AlbumRelease>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(albumRelease);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_delete_unorphaned_albums()
|
||||||
|
{
|
||||||
|
var album = Builder<Album>.CreateNew()
|
||||||
|
.BuildNew();
|
||||||
|
|
||||||
|
Db.Insert(album);
|
||||||
|
|
||||||
|
var albumReleases = Builder<AlbumRelease>.CreateListOfSize(2)
|
||||||
|
.TheFirst(1)
|
||||||
|
.With(e => e.AlbumId = album.Id)
|
||||||
|
.BuildListOfNew();
|
||||||
|
|
||||||
|
Db.InsertMany(albumReleases);
|
||||||
|
Subject.Clean();
|
||||||
|
AllStoredModels.Should().HaveCount(1);
|
||||||
|
AllStoredModels.Should().Contain(e => e.AlbumId == album.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using FluentAssertions.Equivalency;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
@ -23,6 +25,13 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
|
AssertionOptions.AssertEquivalencyUsing(options =>
|
||||||
|
{
|
||||||
|
options.Using<DateTime>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.ToUniversalTime())).WhenTypeIs<DateTime>();
|
||||||
|
options.Using<DateTime?>(ctx => ctx.Subject.Should().BeCloseTo(ctx.Expectation.Value.ToUniversalTime())).WhenTypeIs<DateTime?>();
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
_artist = new Artist
|
_artist = new Artist
|
||||||
{
|
{
|
||||||
Name = "Alien Ant Farm",
|
Name = "Alien Ant Farm",
|
||||||
|
@ -184,7 +193,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
GivenMultipleAlbums();
|
GivenMultipleAlbums();
|
||||||
|
|
||||||
var result = _albumRepo.GetNextAlbums(new[] { _artist.ArtistMetadataId });
|
var result = _albumRepo.GetNextAlbums(new[] { _artist.ArtistMetadataId });
|
||||||
result.Should().BeEquivalentTo(_albums.Take(1));
|
result.Should().BeEquivalentTo(_albums.Take(1), AlbumComparerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -193,7 +202,11 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
GivenMultipleAlbums();
|
GivenMultipleAlbums();
|
||||||
|
|
||||||
var result = _albumRepo.GetLastAlbums(new[] { _artist.ArtistMetadataId });
|
var result = _albumRepo.GetLastAlbums(new[] { _artist.ArtistMetadataId });
|
||||||
result.Should().BeEquivalentTo(_albums.Skip(2).Take(1));
|
result.Should().BeEquivalentTo(_albums.Skip(2).Take(1), AlbumComparerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EquivalencyAssertionOptions<Album> AlbumComparerOptions(EquivalencyAssertionOptions<Album> opts) => opts.ComparingByMembers<Album>()
|
||||||
|
.Excluding(ctx => ctx.SelectedMemberInfo.MemberType.IsGenericType && ctx.SelectedMemberInfo.MemberType.GetGenericTypeDefinition() == typeof(LazyLoaded<>))
|
||||||
|
.Excluding(x => x.ArtistId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using FizzWare.NBuilder;
|
using FizzWare.NBuilder;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
|
using Npgsql;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Profiles.Metadata;
|
using NzbDrone.Core.Profiles.Metadata;
|
||||||
using NzbDrone.Core.Profiles.Qualities;
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
|
@ -159,7 +161,14 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests
|
||||||
_artistRepo.Insert(artist1);
|
_artistRepo.Insert(artist1);
|
||||||
|
|
||||||
Action insertDupe = () => _artistRepo.Insert(artist2);
|
Action insertDupe = () => _artistRepo.Insert(artist2);
|
||||||
|
if (Db.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
insertDupe.Should().Throw<PostgresException>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
insertDupe.Should().Throw<SQLiteException>();
|
insertDupe.Should().Throw<SQLiteException>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.BuildList();
|
.BuildList();
|
||||||
|
|
||||||
Mocker.GetMock<ITrackService>()
|
Mocker.GetMock<ITrackService>()
|
||||||
.Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny<IEnumerable<string>>()))
|
.Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny<List<string>>()))
|
||||||
.Returns(_tracks);
|
.Returns(_tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Returns(clash);
|
.Returns(clash);
|
||||||
|
|
||||||
Mocker.GetMock<ITrackService>()
|
Mocker.GetMock<ITrackService>()
|
||||||
.Setup(x => x.GetTracksForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetTracksForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(_tracks);
|
.Returns(_tracks);
|
||||||
|
|
||||||
var newInfo = existing.JsonClone();
|
var newInfo = existing.JsonClone();
|
||||||
|
@ -117,7 +117,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
newInfo.Tracks = new List<Track> { newTrack };
|
newInfo.Tracks = new List<Track> { newTrack };
|
||||||
|
|
||||||
Mocker.GetMock<ITrackService>()
|
Mocker.GetMock<ITrackService>()
|
||||||
.Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny<IEnumerable<string>>()))
|
.Setup(s => s.GetTracksForRefresh(_release.Id, It.IsAny<List<string>>()))
|
||||||
.Returns(new List<Track> { oldTrack });
|
.Returns(new List<Track> { oldTrack });
|
||||||
|
|
||||||
Subject.RefreshEntityInfo(_release, new List<AlbumRelease> { newInfo }, false, false, null);
|
Subject.RefreshEntityInfo(_release, new List<AlbumRelease> { newInfo }, false, false, null);
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Returns(_artist);
|
.Returns(_artist);
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(s => s.GetReleasesForRefresh(album1.Id, It.IsAny<IEnumerable<string>>()))
|
.Setup(s => s.GetReleasesForRefresh(album1.Id, It.IsAny<List<string>>()))
|
||||||
.Returns(new List<AlbumRelease> { release });
|
.Returns(new List<AlbumRelease> { release });
|
||||||
|
|
||||||
Mocker.GetMock<IArtistMetadataService>()
|
Mocker.GetMock<IArtistMetadataService>()
|
||||||
|
@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Returns(new List<AlbumRelease>());
|
.Returns(new List<AlbumRelease>());
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(_releases);
|
.Returns(_releases);
|
||||||
|
|
||||||
var newAlbumInfo = existing.JsonClone();
|
var newAlbumInfo = existing.JsonClone();
|
||||||
|
@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Build() as List<AlbumRelease>;
|
.Build() as List<AlbumRelease>;
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(existingReleases);
|
.Returns(existingReleases);
|
||||||
|
|
||||||
Mocker.GetMock<IProvideAlbumInfo>()
|
Mocker.GetMock<IProvideAlbumInfo>()
|
||||||
|
@ -263,7 +263,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Build() as List<AlbumRelease>;
|
.Build() as List<AlbumRelease>;
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(existingReleases);
|
.Returns(existingReleases);
|
||||||
|
|
||||||
Mocker.GetMock<IProvideAlbumInfo>()
|
Mocker.GetMock<IProvideAlbumInfo>()
|
||||||
|
@ -318,7 +318,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Build() as List<AlbumRelease>;
|
.Build() as List<AlbumRelease>;
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(existingReleases);
|
.Returns(existingReleases);
|
||||||
|
|
||||||
Mocker.GetMock<IProvideAlbumInfo>()
|
Mocker.GetMock<IProvideAlbumInfo>()
|
||||||
|
@ -376,7 +376,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
.Build() as List<AlbumRelease>;
|
.Build() as List<AlbumRelease>;
|
||||||
|
|
||||||
Mocker.GetMock<IReleaseService>()
|
Mocker.GetMock<IReleaseService>()
|
||||||
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetReleasesForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(existingReleases);
|
.Returns(existingReleases);
|
||||||
|
|
||||||
Mocker.GetMock<IProvideAlbumInfo>()
|
Mocker.GetMock<IProvideAlbumInfo>()
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
private void GivenAlbumsForRefresh(List<Album> albums)
|
private void GivenAlbumsForRefresh(List<Album> albums)
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||||
.Setup(s => s.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(s => s.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(albums);
|
.Returns(albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
|
|
||||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetAlbumsForRefresh(It.IsAny<int>(), It.IsAny<List<string>>()))
|
||||||
.Returns(new List<Album>());
|
.Returns(new List<Album>());
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
|
@ -289,7 +289,7 @@ namespace NzbDrone.Core.Test.MusicTests
|
||||||
|
|
||||||
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
Mocker.GetMock<IAlbumService>(MockBehavior.Strict)
|
||||||
.InSequence(seq)
|
.InSequence(seq)
|
||||||
.Setup(x => x.GetAlbumsForRefresh(clash.ArtistMetadataId, It.IsAny<IEnumerable<string>>()))
|
.Setup(x => x.GetAlbumsForRefresh(clash.ArtistMetadataId, It.IsAny<List<string>>()))
|
||||||
.Returns(_albums);
|
.Returns(_albums);
|
||||||
|
|
||||||
// Update called twice for a move/merge
|
// Update called twice for a move/merge
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Core.ArtistStats
|
||||||
|
|
||||||
public class ArtistStatisticsRepository : IArtistStatisticsRepository
|
public class ArtistStatisticsRepository : IArtistStatisticsRepository
|
||||||
{
|
{
|
||||||
private const string _selectTemplate = "SELECT /**select**/ FROM Tracks /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
private const string _selectTemplate = "SELECT /**select**/ FROM \"Tracks\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
|
|
||||||
private readonly IMainDatabase _database;
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
|
@ -28,12 +28,25 @@ namespace NzbDrone.Core.ArtistStats
|
||||||
public List<AlbumStatistics> ArtistStatistics()
|
public List<AlbumStatistics> ArtistStatistics()
|
||||||
{
|
{
|
||||||
var time = DateTime.UtcNow;
|
var time = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return Query(Builder().WherePostgres<Album>(x => x.ReleaseDate < time));
|
||||||
|
}
|
||||||
|
|
||||||
return Query(Builder().Where<Album>(x => x.ReleaseDate < time));
|
return Query(Builder().Where<Album>(x => x.ReleaseDate < time));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AlbumStatistics> ArtistStatistics(int artistId)
|
public List<AlbumStatistics> ArtistStatistics(int artistId)
|
||||||
{
|
{
|
||||||
var time = DateTime.UtcNow;
|
var time = DateTime.UtcNow;
|
||||||
|
|
||||||
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return Query(Builder().WherePostgres<Album>(x => x.ReleaseDate < time)
|
||||||
|
.WherePostgres<Artist>(x => x.Id == artistId));
|
||||||
|
}
|
||||||
|
|
||||||
return Query(Builder().Where<Album>(x => x.ReleaseDate < time)
|
return Query(Builder().Where<Album>(x => x.ReleaseDate < time)
|
||||||
.Where<Artist>(x => x.Id == artistId));
|
.Where<Artist>(x => x.Id == artistId));
|
||||||
}
|
}
|
||||||
|
@ -48,14 +61,16 @@ namespace NzbDrone.Core.ArtistStats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlBuilder Builder() => new SqlBuilder()
|
private SqlBuilder Builder()
|
||||||
.Select(@"Artists.Id AS ArtistId,
|
{
|
||||||
Albums.Id AS AlbumId,
|
return new SqlBuilder(_database.DatabaseType)
|
||||||
SUM(COALESCE(TrackFiles.Size, 0)) AS SizeOnDisk,
|
.Select(@"""Artists"".""Id"" AS ""ArtistId"",
|
||||||
COUNT(Tracks.Id) AS TotalTrackCount,
|
""Albums"".""Id"" AS ""AlbumId"",
|
||||||
SUM(CASE WHEN Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount,
|
SUM(COALESCE(""TrackFiles"".""Size"", 0)) AS ""SizeOnDisk"",
|
||||||
SUM(CASE WHEN Albums.Monitored = 1 OR Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount,
|
COUNT(""Tracks"".""Id"") AS ""TotalTrackCount"",
|
||||||
SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount")
|
SUM(CASE WHEN ""Tracks"".""TrackFileId"" > 0 THEN 1 ELSE 0 END) AS ""AvailableTrackCount"",
|
||||||
|
SUM(CASE WHEN ""Albums"".""Monitored"" = true 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""")
|
||||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||||
|
@ -64,4 +79,5 @@ namespace NzbDrone.Core.ArtistStats
|
||||||
.GroupBy<Artist>(x => x.Id)
|
.GroupBy<Artist>(x => x.Id)
|
||||||
.GroupBy<Album>(x => x.Id);
|
.GroupBy<Album>(x => x.Id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace NzbDrone.Core.Blocklisting
|
||||||
Delete(x => artistIds.Contains(x.ArtistId));
|
Delete(x => artistIds.Contains(x.ArtistId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join<Blocklist, Artist>((b, m) => b.ArtistId == m.Id);
|
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType).Join<Blocklist, Artist>((b, m) => b.ArtistId == m.Id);
|
||||||
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Artist>(builder, (bl, artist) =>
|
protected override IEnumerable<Blocklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blocklist, Artist>(builder, (bl, artist) =>
|
||||||
{
|
{
|
||||||
bl.Artist = artist;
|
bl.Artist = artist;
|
||||||
|
|
|
@ -4,12 +4,14 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration.Events;
|
using NzbDrone.Core.Configuration.Events;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -49,6 +51,12 @@ namespace NzbDrone.Core.Configuration
|
||||||
int SyslogPort { get; }
|
int SyslogPort { get; }
|
||||||
string SyslogLevel { get; }
|
string SyslogLevel { get; }
|
||||||
string Theme { get; }
|
string Theme { get; }
|
||||||
|
string PostgresHost { get; }
|
||||||
|
int PostgresPort { get; }
|
||||||
|
string PostgresUser { get; }
|
||||||
|
string PostgresPassword { get; }
|
||||||
|
string PostgresMainDb { get; }
|
||||||
|
string PostgresLogDb { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigFileProvider : IConfigFileProvider
|
public class ConfigFileProvider : IConfigFileProvider
|
||||||
|
@ -58,6 +66,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly ICached<string> _cache;
|
private readonly ICached<string> _cache;
|
||||||
|
private readonly PostgresOptions _postgresOptions;
|
||||||
|
|
||||||
private readonly string _configFile;
|
private readonly string _configFile;
|
||||||
|
|
||||||
|
@ -66,12 +75,14 @@ namespace NzbDrone.Core.Configuration
|
||||||
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
public ConfigFileProvider(IAppFolderInfo appFolderInfo,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IDiskProvider diskProvider)
|
IDiskProvider diskProvider,
|
||||||
|
IOptions<PostgresOptions> postgresOptions)
|
||||||
{
|
{
|
||||||
_cache = cacheManager.GetCache<string>(GetType());
|
_cache = cacheManager.GetCache<string>(GetType());
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_configFile = appFolderInfo.GetConfigPath();
|
_configFile = appFolderInfo.GetConfigPath();
|
||||||
|
_postgresOptions = postgresOptions.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, object> GetConfigDictionary()
|
public Dictionary<string, object> GetConfigDictionary()
|
||||||
|
@ -186,6 +197,12 @@ namespace NzbDrone.Core.Configuration
|
||||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||||
|
|
||||||
public string Theme => GetValue("Theme", "light", persist: false);
|
public string Theme => GetValue("Theme", "light", persist: false);
|
||||||
|
public string PostgresHost => _postgresOptions?.Host ?? GetValue("PostgresHost", string.Empty, persist: false);
|
||||||
|
public string PostgresUser => _postgresOptions?.User ?? GetValue("PostgresUser", string.Empty, persist: false);
|
||||||
|
public string PostgresPassword => _postgresOptions?.Password ?? GetValue("PostgresPassword", string.Empty, persist: false);
|
||||||
|
public string PostgresMainDb => _postgresOptions?.MainDb ?? GetValue("PostgresMainDb", "lidarr-main", persist: false);
|
||||||
|
public string PostgresLogDb => _postgresOptions?.LogDb ?? GetValue("PostgresLogDb", "lidarr-log", persist: false);
|
||||||
|
public int PostgresPort => (_postgresOptions?.Port ?? 0) != 0 ? _postgresOptions.Port : GetValueInt("PostgresPort", 5432, persist: false);
|
||||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
_updateSql = GetUpdateSql(_properties);
|
_updateSql = GetUpdateSql(_properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
protected virtual SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType);
|
||||||
|
|
||||||
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
|
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM \"{_table}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +175,11 @@ namespace NzbDrone.Core.Datastore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return $"INSERT INTO \"{_table}\" ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}) RETURNING \"Id\"";
|
||||||
|
}
|
||||||
|
|
||||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +187,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
SqlBuilderExtensions.LogQuery(_insertSql, model);
|
SqlBuilderExtensions.LogQuery(_insertSql, model);
|
||||||
var multi = connection.QueryMultiple(_insertSql, model, transaction);
|
var multi = connection.QueryMultiple(_insertSql, model, transaction);
|
||||||
var id = (int)multi.Read().First().id;
|
var multiRead = multi.Read();
|
||||||
|
var id = (int)(multiRead.First().id ?? multiRead.First().Id);
|
||||||
_keyProperty.SetValue(model, id);
|
_keyProperty.SetValue(model, id);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
|
@ -293,7 +299,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
using (var conn = _database.OpenConnection())
|
using (var conn = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
conn.Execute($"DELETE FROM [{_table}]");
|
conn.Execute($"DELETE FROM \"{_table}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vacuum)
|
if (vacuum)
|
||||||
|
@ -352,7 +358,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendFormat("UPDATE {0} SET ", _table);
|
sb.AppendFormat("UPDATE \"{0}\" SET ", _table);
|
||||||
|
|
||||||
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -420,9 +426,10 @@ namespace NzbDrone.Core.Datastore
|
||||||
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
pagingSpec.SortKey = $"{_table}.{_keyProperty.Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sortKey = TableMapping.Mapper.GetSortKey(pagingSpec.SortKey);
|
||||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||||
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
var pagingOffset = Math.Max(pagingSpec.Page - 1, 0) * pagingSpec.PageSize;
|
||||||
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
builder.OrderBy($"\"{sortKey}\" {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||||
|
|
||||||
return queryFunc(builder).ToList();
|
return queryFunc(builder).ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
|
using Npgsql;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
|
@ -14,10 +16,17 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public class ConnectionStringFactory : IConnectionStringFactory
|
public class ConnectionStringFactory : IConnectionStringFactory
|
||||||
{
|
{
|
||||||
public ConnectionStringFactory(IAppFolderInfo appFolderInfo)
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
|
public ConnectionStringFactory(IAppFolderInfo appFolderInfo, IConfigFileProvider configFileProvider)
|
||||||
{
|
{
|
||||||
MainDbConnectionString = GetConnectionString(appFolderInfo.GetDatabase());
|
_configFileProvider = configFileProvider;
|
||||||
LogDbConnectionString = GetConnectionString(appFolderInfo.GetLogDatabase());
|
|
||||||
|
MainDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresMainDb) :
|
||||||
|
GetConnectionString(appFolderInfo.GetDatabase());
|
||||||
|
|
||||||
|
LogDbConnectionString = _configFileProvider.PostgresHost.IsNotNullOrWhiteSpace() ? GetPostgresConnectionString(_configFileProvider.PostgresLogDb) :
|
||||||
|
GetConnectionString(appFolderInfo.GetLogDatabase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MainDbConnectionString { get; private set; }
|
public string MainDbConnectionString { get; private set; }
|
||||||
|
@ -48,5 +57,19 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
return connectionBuilder.ConnectionString;
|
return connectionBuilder.ConnectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetPostgresConnectionString(string dbName)
|
||||||
|
{
|
||||||
|
var connectionBuilder = new NpgsqlConnectionStringBuilder();
|
||||||
|
|
||||||
|
connectionBuilder.Database = dbName;
|
||||||
|
connectionBuilder.Host = _configFileProvider.PostgresHost;
|
||||||
|
connectionBuilder.Username = _configFileProvider.PostgresUser;
|
||||||
|
connectionBuilder.Password = _configFileProvider.PostgresPassword;
|
||||||
|
connectionBuilder.Port = _configFileProvider.PostgresPort;
|
||||||
|
connectionBuilder.Enlist = false;
|
||||||
|
|
||||||
|
return connectionBuilder.ConnectionString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
|
@ -11,6 +12,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
IDbConnection OpenConnection();
|
IDbConnection OpenConnection();
|
||||||
Version Version { get; }
|
Version Version { get; }
|
||||||
int Migration { get; }
|
int Migration { get; }
|
||||||
|
DatabaseType DatabaseType { get; }
|
||||||
void Vacuum();
|
void Vacuum();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,13 +34,44 @@ namespace NzbDrone.Core.Datastore
|
||||||
return _datamapperFactory();
|
return _datamapperFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using (var db = _datamapperFactory())
|
||||||
|
{
|
||||||
|
if (db.ConnectionString.Contains(".db"))
|
||||||
|
{
|
||||||
|
return DatabaseType.SQLite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return DatabaseType.PostgreSQL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Version Version
|
public Version Version
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
using (var db = _datamapperFactory())
|
using (var db = _datamapperFactory())
|
||||||
{
|
{
|
||||||
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
string version;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
version = db.QueryFirstOrDefault<string>("SHOW server_version");
|
||||||
|
|
||||||
|
//Postgres can return extra info about operating system on version call, ignore this
|
||||||
|
version = Regex.Replace(version, @"\(.*?\)", "");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
||||||
|
}
|
||||||
|
|
||||||
return new Version(version);
|
return new Version(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +83,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
using (var db = _datamapperFactory())
|
using (var db = _datamapperFactory())
|
||||||
{
|
{
|
||||||
return db.QueryFirstOrDefault<int>("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1");
|
return db.QueryFirstOrDefault<int>("SELECT \"Version\" from \"VersionInfo\" ORDER BY \"Version\" DESC LIMIT 1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,4 +106,10 @@ namespace NzbDrone.Core.Datastore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DatabaseType
|
||||||
|
{
|
||||||
|
SQLite,
|
||||||
|
PostgreSQL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Npgsql;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Exceptions;
|
using NzbDrone.Common.Exceptions;
|
||||||
|
@ -85,10 +87,19 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
||||||
{
|
{
|
||||||
var conn = SQLiteFactory.Instance.CreateConnection();
|
DbConnection conn;
|
||||||
conn.ConnectionString = connectionString;
|
|
||||||
conn.Open();
|
|
||||||
|
|
||||||
|
if (connectionString.Contains(".db"))
|
||||||
|
{
|
||||||
|
conn = SQLiteFactory.Instance.CreateConnection();
|
||||||
|
conn.ConnectionString = connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conn = new NpgsqlConnection(connectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Open();
|
||||||
return conn;
|
return conn;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,12 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
|
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
|
||||||
{
|
{
|
||||||
return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
|
return builder.Select(types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)
|
public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types)
|
||||||
{
|
{
|
||||||
return builder.Select("DISTINCT " + types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
|
return builder.Select("DISTINCT " + types.Select(x => $"\"{TableMapping.Mapper.TableNameMapping(x)}\".*").Join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder SelectCount(this SqlBuilder builder)
|
public static SqlBuilder SelectCount(this SqlBuilder builder)
|
||||||
|
@ -42,41 +42,48 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
|
return builder.Where(wb.ToString(), wb.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlBuilder WherePostgres<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
|
{
|
||||||
|
var wb = new WhereBuilderPostgres(filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.Where(wb.ToString(), wb.Parameters);
|
return builder.Where(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, true, builder.Sequence);
|
||||||
|
|
||||||
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
return builder.Join($"{rightTable} ON {wb.ToString()}");
|
return builder.Join($"\"{rightTable}\" ON {wb.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||||
{
|
{
|
||||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
var wb = GetWhereBuilder(builder.DatabaseType, filter, false, builder.Sequence);
|
||||||
|
|
||||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||||
|
|
||||||
return builder.LeftJoin($"{rightTable} ON {wb.ToString()}");
|
return builder.LeftJoin($"\"{rightTable}\" ON {wb.ToString()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
||||||
{
|
{
|
||||||
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
||||||
var propName = property.GetMemberName().Name;
|
var propName = property.GetMemberName().Name;
|
||||||
return builder.GroupBy($"{table}.{propName}");
|
return builder.GroupBy($"\"{table}\".\"{propName}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
|
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
|
||||||
|
@ -138,6 +145,18 @@ namespace NzbDrone.Core.Datastore
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static WhereBuilder GetWhereBuilder(DatabaseType databaseType, Expression filter, bool requireConcrete, int seq)
|
||||||
|
{
|
||||||
|
if (databaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
return new WhereBuilderPostgres(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new WhereBuilderSqlite(filter, requireConcrete, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
||||||
{
|
{
|
||||||
var argsDictionary = new Dictionary<string, object>();
|
var argsDictionary = new Dictionary<string, object>();
|
||||||
|
@ -150,13 +169,16 @@ namespace NzbDrone.Core.Datastore
|
||||||
}
|
}
|
||||||
|
|
||||||
var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance);
|
var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
if (templates != null && templates.GetValue(dynamicParams) is List<object> list)
|
if (templates != null)
|
||||||
|
{
|
||||||
|
if (templates.GetValue(dynamicParams) is List<object> list)
|
||||||
{
|
{
|
||||||
foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList()))
|
foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList()))
|
||||||
{
|
{
|
||||||
objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value));
|
objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return argsDictionary;
|
return argsDictionary;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
|
||||||
public class LogDatabase : ILogDatabase
|
public class LogDatabase : ILogDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
public LogDatabase(IDatabase database)
|
public LogDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
|
@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|
|
@ -10,10 +10,12 @@ namespace NzbDrone.Core.Datastore
|
||||||
public class MainDatabase : IMainDatabase
|
public class MainDatabase : IMainDatabase
|
||||||
{
|
{
|
||||||
private readonly IDatabase _database;
|
private readonly IDatabase _database;
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
public MainDatabase(IDatabase database)
|
public MainDatabase(IDatabase database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_databaseType = _database == null ? DatabaseType.SQLite : _database.DatabaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection OpenConnection()
|
public IDbConnection OpenConnection()
|
||||||
|
@ -25,6 +27,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public int Migration => _database.Migration;
|
public int Migration => _database.Migration;
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public void Vacuum()
|
public void Vacuum()
|
||||||
{
|
{
|
||||||
_database.Vacuum();
|
_database.Vacuum();
|
||||||
|
|
|
@ -288,8 +288,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
|
||||||
Insert.IntoTable("DelayProfiles").Row(new
|
Insert.IntoTable("DelayProfiles").Row(new
|
||||||
{
|
{
|
||||||
EnableUsenet = 1,
|
EnableUsenet = true,
|
||||||
EnableTorrent = 1,
|
EnableTorrent = true,
|
||||||
PreferredProtocol = 1,
|
PreferredProtocol = 1,
|
||||||
UsenetDelay = 0,
|
UsenetDelay = 0,
|
||||||
TorrentDelay = 0,
|
TorrentDelay = 0,
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Alter.Table("Tracks").AddColumn("MediumNumber").AsInt32().WithDefaultValue(0);
|
Alter.Table("Tracks").AddColumn("MediumNumber").AsInt32().WithDefaultValue(0);
|
||||||
Alter.Table("Tracks").AddColumn("AbsoluteTrackNumber").AsInt32().WithDefaultValue(0);
|
Alter.Table("Tracks").AddColumn("AbsoluteTrackNumber").AsInt32().WithDefaultValue(0);
|
||||||
|
|
||||||
Execute.Sql("UPDATE Tracks SET AbsoluteTrackNumber = TrackNumber");
|
Execute.Sql("UPDATE \"Tracks\" SET \"AbsoluteTrackNumber\" = \"TrackNumber\"");
|
||||||
|
|
||||||
Delete.Column("TrackNumber").FromTable("Tracks");
|
Delete.Column("TrackNumber").FromTable("Tracks");
|
||||||
Alter.Table("Tracks").AddColumn("TrackNumber").AsString().Nullable();
|
Alter.Table("Tracks").AddColumn("TrackNumber").AsString().Nullable();
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE QualityDefinitions SET Title = 'MP3-160' WHERE Quality = 5"); // Change MP3-512 to MP3-160
|
Execute.Sql("UPDATE \"QualityDefinitions\" SET \"Title\" = 'MP3-160' WHERE \"Quality\" = 5"); // Change MP3-512 to MP3-160
|
||||||
Execute.WithConnection(ConvertProfile);
|
Execute.WithConnection(ConvertProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,8 +172,17 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var updateProfileCmd = _connection.CreateCommand())
|
using (var updateProfileCmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
updateProfileCmd.Transaction = _transaction;
|
updateProfileCmd.Transaction = _transaction;
|
||||||
|
if (_connection.GetType().FullName == "Npgsql.NpgsqlConnection")
|
||||||
|
{
|
||||||
updateProfileCmd.CommandText =
|
updateProfileCmd.CommandText =
|
||||||
"UPDATE Profiles SET Name = ?, Cutoff = ?, Items = ? WHERE Id = ?";
|
"UPDATE \"Profiles\" SET \"Name\" = $1, \"Cutoff\" = $2, \"Items\" = $3 WHERE \"Id\" = $4";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateProfileCmd.CommandText =
|
||||||
|
"UPDATE \"Profiles\" SET \"Name\" = ?, \"Cutoff\" = ?, \"Items\" = ? WHERE \"Id\" = ?";
|
||||||
|
}
|
||||||
|
|
||||||
updateProfileCmd.AddParameter(profile.Name);
|
updateProfileCmd.AddParameter(profile.Name);
|
||||||
updateProfileCmd.AddParameter(profile.Cutoff);
|
updateProfileCmd.AddParameter(profile.Cutoff);
|
||||||
updateProfileCmd.AddParameter(profile.Items.ToJson());
|
updateProfileCmd.AddParameter(profile.Items.ToJson());
|
||||||
|
@ -323,7 +332,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var getProfilesCmd = _connection.CreateCommand())
|
using (var getProfilesCmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
getProfilesCmd.Transaction = _transaction;
|
getProfilesCmd.Transaction = _transaction;
|
||||||
getProfilesCmd.CommandText = @"SELECT Id, Name, Cutoff, Items FROM Profiles";
|
getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"", ""Cutoff"", ""Items"" FROM ""Profiles""";
|
||||||
|
|
||||||
using (var profileReader = getProfilesCmd.ExecuteReader())
|
using (var profileReader = getProfilesCmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Rename.Column("EnableSearch").OnTable("Indexers").To("EnableAutomaticSearch");
|
Rename.Column("EnableSearch").OnTable("Indexers").To("EnableAutomaticSearch");
|
||||||
Alter.Table("Indexers").AddColumn("EnableInteractiveSearch").AsBoolean().Nullable();
|
Alter.Table("Indexers").AddColumn("EnableInteractiveSearch").AsBoolean().Nullable();
|
||||||
|
|
||||||
Execute.Sql("UPDATE Indexers SET EnableInteractiveSearch = EnableAutomaticSearch");
|
Execute.Sql("UPDATE \"Indexers\" SET \"EnableInteractiveSearch\" = \"EnableAutomaticSearch\"");
|
||||||
|
|
||||||
Alter.Table("Indexers").AlterColumn("EnableInteractiveSearch").AsBoolean().NotNullable();
|
Alter.Table("Indexers").AlterColumn("EnableInteractiveSearch").AsBoolean().NotNullable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE QualityDefinitions SET MaxSize = CASE " +
|
IfDatabase("sqlite").Execute.Sql("UPDATE QualityDefinitions SET MaxSize = CASE " +
|
||||||
"WHEN (CAST(MaxSize AS FLOAT) / 60) * 8 * 1024 < 1500 THEN " +
|
"WHEN (CAST(MaxSize AS FLOAT) / 60) * 8 * 1024 < 1500 THEN " +
|
||||||
"ROUND((CAST(MaxSize AS FLOAT) / 60) * 8 * 1024, 0) " +
|
"ROUND((CAST(MaxSize AS FLOAT) / 60) * 8 * 1024, 0) " +
|
||||||
"ELSE NULL " +
|
"ELSE NULL " +
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var getProfilesCmd = _connection.CreateCommand())
|
using (var getProfilesCmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
getProfilesCmd.Transaction = _transaction;
|
getProfilesCmd.Transaction = _transaction;
|
||||||
getProfilesCmd.CommandText = @"SELECT Id, Name FROM MetadataProfiles";
|
getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"" FROM ""MetadataProfiles""";
|
||||||
|
|
||||||
using (var profileReader = getProfilesCmd.ExecuteReader())
|
using (var profileReader = getProfilesCmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("Notifications").AddColumn("OnAlbumDownload").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("OnAlbumDownload").AsBoolean().WithDefaultValue(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,17 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE artists SET metadataProfileId = " +
|
Execute.Sql("UPDATE \"Artists\" SET \"MetadataProfileId\" = " +
|
||||||
"CASE WHEN ((SELECT COUNT(*) FROM metadataprofiles) > 0) " +
|
"CASE WHEN ((SELECT COUNT(*) FROM \"MetadataProfiles\") > 0) " +
|
||||||
"THEN (SELECT id FROM metadataprofiles ORDER BY id ASC LIMIT 1) " +
|
"THEN (SELECT \"Id\" FROM \"MetadataProfiles\" ORDER BY \"Id\" ASC LIMIT 1) " +
|
||||||
"ELSE 0 END " +
|
"ELSE 0 END " +
|
||||||
"WHERE artists.metadataProfileId == 0");
|
"WHERE \"Artists\".\"MetadataProfileId\" = 0");
|
||||||
|
|
||||||
Execute.Sql("UPDATE artists SET languageProfileId = " +
|
Execute.Sql("UPDATE \"Artists\" SET \"LanguageProfileId\" = " +
|
||||||
"CASE WHEN ((SELECT COUNT(*) FROM languageprofiles) > 0) " +
|
"CASE WHEN ((SELECT COUNT(*) FROM \"LanguageProfiles\") > 0) " +
|
||||||
"THEN (SELECT id FROM languageprofiles ORDER BY id ASC LIMIT 1) " +
|
"THEN (SELECT \"Id\" FROM \"LanguageProfiles\" ORDER BY \"Id\" ASC LIMIT 1) " +
|
||||||
"ELSE 0 END " +
|
"ELSE 0 END " +
|
||||||
"WHERE artists.languageProfileId == 0");
|
"WHERE \"Artists\".\"LanguageProfileId\" = 0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Fanzub';");
|
Execute.Sql("DELETE FROM \"Indexers\" WHERE \"Implementation\" = 'Fanzub';");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var updateProfileCmd = _connection.CreateCommand())
|
using (var updateProfileCmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
updateProfileCmd.Transaction = _transaction;
|
updateProfileCmd.Transaction = _transaction;
|
||||||
updateProfileCmd.CommandText = "UPDATE Profiles SET Name = ?, Cutoff = ?, Items = ? WHERE Id = ?";
|
updateProfileCmd.CommandText = "UPDATE \"Profiles\" SET \"Name\" = ?, \"Cutoff\" = ?, \"Items\" = ? WHERE \"Id\" = ?";
|
||||||
updateProfileCmd.AddParameter(profile.Name);
|
updateProfileCmd.AddParameter(profile.Name);
|
||||||
updateProfileCmd.AddParameter(profile.Cutoff);
|
updateProfileCmd.AddParameter(profile.Cutoff);
|
||||||
updateProfileCmd.AddParameter(profile.Items.ToJson());
|
updateProfileCmd.AddParameter(profile.Items.ToJson());
|
||||||
|
@ -115,7 +115,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var getProfilesCmd = _connection.CreateCommand())
|
using (var getProfilesCmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
getProfilesCmd.Transaction = _transaction;
|
getProfilesCmd.Transaction = _transaction;
|
||||||
getProfilesCmd.CommandText = @"SELECT Id, Name, Cutoff, Items FROM Profiles";
|
getProfilesCmd.CommandText = @"SELECT ""Id"", ""Name"", ""Cutoff"", ""Items"" FROM ""Profiles""";
|
||||||
|
|
||||||
using (var profileReader = getProfilesCmd.ExecuteReader())
|
using (var profileReader = getProfilesCmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,11 +2,11 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dapper;
|
||||||
using FluentMigrator;
|
using FluentMigrator;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Serializer;
|
using NzbDrone.Common.Serializer;
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
using NzbDrone.Core.Music;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
|
@ -30,18 +30,18 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
.WithColumn("Members").AsString().Nullable();
|
.WithColumn("Members").AsString().Nullable();
|
||||||
|
|
||||||
// we want to preserve the artist ID. Shove all the metadata into the metadata table.
|
// we want to preserve the artist ID. Shove all the metadata into the metadata table.
|
||||||
Execute.Sql(@"INSERT INTO ArtistMetadata (ForeignArtistId, Name, Overview, Disambiguation, Type, Status, Images, Links, Genres, Ratings, Members)
|
Execute.Sql(@"INSERT INTO ""ArtistMetadata"" (""ForeignArtistId"", ""Name"", ""Overview"", ""Disambiguation"", ""Type"", ""Status"", ""Images"", ""Links"", ""Genres"", ""Ratings"", ""Members"")
|
||||||
SELECT ForeignArtistId, Name, Overview, Disambiguation, ArtistType, Status, Images, Links, Genres, Ratings, Members
|
SELECT ""ForeignArtistId"", ""Name"", ""Overview"", ""Disambiguation"", ""ArtistType"", ""Status"", ""Images"", ""Links"", ""Genres"", ""Ratings"", ""Members""
|
||||||
FROM Artists");
|
FROM ""Artists""");
|
||||||
|
|
||||||
// Add an ArtistMetadataId column to Artists
|
// Add an ArtistMetadataId column to Artists
|
||||||
Alter.Table("Artists").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0);
|
Alter.Table("Artists").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0);
|
||||||
|
|
||||||
// Update artistmetadataId
|
// Update artistmetadataId
|
||||||
Execute.Sql(@"UPDATE Artists
|
Execute.Sql(@"UPDATE ""Artists""
|
||||||
SET ArtistMetadataId = (SELECT ArtistMetadata.Id
|
SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id""
|
||||||
FROM ArtistMetadata
|
FROM ""ArtistMetadata""
|
||||||
WHERE ArtistMetadata.ForeignArtistId = Artists.ForeignArtistId)");
|
WHERE ""ArtistMetadata"".""ForeignArtistId"" = ""Artists"".""ForeignArtistId"")");
|
||||||
|
|
||||||
// ALBUM RELEASES TABLE - Do this before we mess with the Albums table
|
// ALBUM RELEASES TABLE - Do this before we mess with the Albums table
|
||||||
Create.TableForModel("AlbumReleases")
|
Create.TableForModel("AlbumReleases")
|
||||||
|
@ -68,11 +68,11 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Alter.Table("Albums").AddColumn("Links").AsString().Nullable();
|
Alter.Table("Albums").AddColumn("Links").AsString().Nullable();
|
||||||
|
|
||||||
// Set metadata ID
|
// Set metadata ID
|
||||||
Execute.Sql(@"UPDATE Albums
|
Execute.Sql(@"UPDATE ""Albums""
|
||||||
SET ArtistMetadataId = (SELECT ArtistMetadata.Id
|
SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id""
|
||||||
FROM ArtistMetadata
|
FROM ""ArtistMetadata""
|
||||||
JOIN Artists ON ArtistMetadata.Id = Artists.ArtistMetadataId
|
JOIN ""Artists"" ON ""ArtistMetadata"".""Id"" = ""Artists"".""ArtistMetadataId""
|
||||||
WHERE Albums.ArtistId = Artists.Id)");
|
WHERE ""Albums"".""ArtistId"" = ""Artists"".""Id"")");
|
||||||
|
|
||||||
// TRACKS TABLE
|
// TRACKS TABLE
|
||||||
Alter.Table("Tracks").AddColumn("ForeignRecordingId").AsString().WithDefaultValue("0");
|
Alter.Table("Tracks").AddColumn("ForeignRecordingId").AsString().WithDefaultValue("0");
|
||||||
|
@ -80,18 +80,18 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Alter.Table("Tracks").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0);
|
Alter.Table("Tracks").AddColumn("ArtistMetadataId").AsInt32().WithDefaultValue(0);
|
||||||
|
|
||||||
// Set track release to the only release we've bothered populating
|
// Set track release to the only release we've bothered populating
|
||||||
Execute.Sql(@"UPDATE Tracks
|
Execute.Sql(@"UPDATE ""Tracks""
|
||||||
SET AlbumReleaseId = (SELECT AlbumReleases.Id
|
SET ""AlbumReleaseId"" = (SELECT ""AlbumReleases"".""Id""
|
||||||
FROM AlbumReleases
|
FROM ""AlbumReleases""
|
||||||
JOIN Albums ON AlbumReleases.AlbumId = Albums.Id
|
JOIN ""Albums"" ON ""AlbumReleases"".""AlbumId"" = ""Albums"".""Id""
|
||||||
WHERE Albums.Id = Tracks.AlbumId)");
|
WHERE ""Albums"".""Id"" = ""Tracks"".""AlbumId"")");
|
||||||
|
|
||||||
// Set metadata ID
|
// Set metadata ID
|
||||||
Execute.Sql(@"UPDATE Tracks
|
Execute.Sql(@"UPDATE ""Tracks""
|
||||||
SET ArtistMetadataId = (SELECT ArtistMetadata.Id
|
SET ""ArtistMetadataId"" = (SELECT ""ArtistMetadata"".""Id""
|
||||||
FROM ArtistMetadata
|
FROM ""ArtistMetadata""
|
||||||
JOIN Albums ON ArtistMetadata.Id = Albums.ArtistMetadataId
|
JOIN ""Albums"" ON ""ArtistMetadata"".""Id"" = ""Albums"".""ArtistMetadataId""
|
||||||
WHERE Tracks.AlbumId = Albums.Id)");
|
WHERE ""Tracks"".""AlbumId"" = ""Albums"".""Id"")");
|
||||||
|
|
||||||
// CLEAR OUT OLD COLUMNS
|
// CLEAR OUT OLD COLUMNS
|
||||||
|
|
||||||
|
@ -188,15 +188,15 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
public List<string> Label { get; set; }
|
public List<string> Label { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AlbumRelease> ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran)
|
private List<AlbumRelease023> ReadReleasesFromAlbums(IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
// need to get all the old albums
|
// need to get all the old albums
|
||||||
var releases = new List<AlbumRelease>();
|
var releases = new List<AlbumRelease023>();
|
||||||
|
|
||||||
using (var getReleasesCmd = conn.CreateCommand())
|
using (var getReleasesCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
getReleasesCmd.Transaction = tran;
|
getReleasesCmd.Transaction = tran;
|
||||||
getReleasesCmd.CommandText = @"SELECT Id, CurrentRelease FROM Albums";
|
getReleasesCmd.CommandText = @"SELECT ""Id"", ""CurrentRelease"" FROM ""Albums""";
|
||||||
|
|
||||||
using (var releaseReader = getReleasesCmd.ExecuteReader())
|
using (var releaseReader = getReleasesCmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
|
@ -205,16 +205,16 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
int albumId = releaseReader.GetInt32(0);
|
int albumId = releaseReader.GetInt32(0);
|
||||||
var albumRelease = Json.Deserialize<LegacyAlbumRelease>(releaseReader.GetString(1));
|
var albumRelease = Json.Deserialize<LegacyAlbumRelease>(releaseReader.GetString(1));
|
||||||
|
|
||||||
AlbumRelease toInsert = null;
|
AlbumRelease023 toInsert = null;
|
||||||
if (albumRelease != null)
|
if (albumRelease != null)
|
||||||
{
|
{
|
||||||
var media = new List<Medium>();
|
var media = new List<Medium023>();
|
||||||
for (var i = 1; i <= Math.Max(albumRelease.MediaCount, 1); i++)
|
for (var i = 1; i <= Math.Max(albumRelease.MediaCount, 1); i++)
|
||||||
{
|
{
|
||||||
media.Add(new Medium { Number = i, Name = "", Format = albumRelease.Format ?? "Unknown" });
|
media.Add(new Medium023 { Number = i, Name = "", Format = albumRelease.Format ?? "Unknown" });
|
||||||
}
|
}
|
||||||
|
|
||||||
toInsert = new AlbumRelease
|
toInsert = new AlbumRelease023
|
||||||
{
|
{
|
||||||
AlbumId = albumId,
|
AlbumId = albumId,
|
||||||
ForeignReleaseId = albumRelease.Id.IsNotNullOrWhiteSpace() ? albumRelease.Id : albumId.ToString(),
|
ForeignReleaseId = albumRelease.Id.IsNotNullOrWhiteSpace() ? albumRelease.Id : albumId.ToString(),
|
||||||
|
@ -231,7 +231,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toInsert = new AlbumRelease
|
toInsert = new AlbumRelease023
|
||||||
{
|
{
|
||||||
AlbumId = albumId,
|
AlbumId = albumId,
|
||||||
ForeignReleaseId = albumId.ToString(),
|
ForeignReleaseId = albumId.ToString(),
|
||||||
|
@ -239,7 +239,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Status = "",
|
Status = "",
|
||||||
Label = new List<string>(),
|
Label = new List<string>(),
|
||||||
Country = new List<string>(),
|
Country = new List<string>(),
|
||||||
Media = new List<Medium> { new Medium { Name = "Unknown", Number = 1, Format = "Unknown" } },
|
Media = new List<Medium023> { new Medium023 { Name = "Unknown", Number = 1, Format = "Unknown" } },
|
||||||
Monitored = true
|
Monitored = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -252,31 +252,54 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
return releases;
|
return releases;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteReleasesToReleases(List<AlbumRelease> releases, IDbConnection conn, IDbTransaction tran)
|
private void WriteReleasesToReleases(List<AlbumRelease023> releases, IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
|
var dbReleases = new List<dynamic>();
|
||||||
|
|
||||||
foreach (var release in releases)
|
foreach (var release in releases)
|
||||||
{
|
{
|
||||||
using (var writeReleaseCmd = conn.CreateCommand())
|
dbReleases.Add(new
|
||||||
{
|
{
|
||||||
writeReleaseCmd.Transaction = tran;
|
AlbumId = release.AlbumId,
|
||||||
writeReleaseCmd.CommandText =
|
ForeignReleaseId = release.ForeignReleaseId,
|
||||||
"INSERT INTO AlbumReleases (AlbumId, ForeignReleaseId, Title, Status, Duration, Label, Disambiguation, Country, Media, TrackCount, Monitored) " +
|
Title = release.Title,
|
||||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
Status = release.Status,
|
||||||
writeReleaseCmd.AddParameter(release.AlbumId);
|
Duration = release.Duration,
|
||||||
writeReleaseCmd.AddParameter(release.ForeignReleaseId);
|
Label = release.Label.ToJson(),
|
||||||
writeReleaseCmd.AddParameter(release.Title);
|
Disambiguation = release.Disambiguation,
|
||||||
writeReleaseCmd.AddParameter(release.Status);
|
Country = release.Country.ToJson(),
|
||||||
writeReleaseCmd.AddParameter(release.Duration);
|
Media = release.Media.ToJson(),
|
||||||
writeReleaseCmd.AddParameter(release.Label.ToJson());
|
TrackCount = release.TrackCount,
|
||||||
writeReleaseCmd.AddParameter(release.Disambiguation);
|
Monitored = release.Monitored
|
||||||
writeReleaseCmd.AddParameter(release.Country.ToJson());
|
});
|
||||||
writeReleaseCmd.AddParameter(release.Media.ToJson());
|
}
|
||||||
writeReleaseCmd.AddParameter(release.TrackCount);
|
|
||||||
writeReleaseCmd.AddParameter(release.Monitored);
|
|
||||||
|
|
||||||
writeReleaseCmd.ExecuteNonQuery();
|
var updateSql = "INSERT INTO \"AlbumReleases\" (\"AlbumId\", \"ForeignReleaseId\", \"Title\", \"Status\", \"Duration\", \"Label\", \"Disambiguation\", \"Country\", \"Media\", \"TrackCount\", \"Monitored\") " +
|
||||||
|
"VALUES (@AlbumId, @ForeignReleaseId, @Title, @Status, @Duration, @Label, @Disambiguation, @Country, @Media, @TrackCount, @Monitored)";
|
||||||
|
|
||||||
|
conn.Execute(updateSql, dbReleases, transaction: tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AlbumRelease023
|
||||||
|
{
|
||||||
|
public int AlbumId { get; set; }
|
||||||
|
public string ForeignReleaseId { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public int Duration { get; set; }
|
||||||
|
public List<string> Label { get; set; }
|
||||||
|
public string Disambiguation { get; set; }
|
||||||
|
public List<string> Country { get; set; }
|
||||||
|
public List<Medium023> Media { get; set; }
|
||||||
|
public int TrackCount { get; set; }
|
||||||
|
public bool Monitored { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Medium023
|
||||||
|
{
|
||||||
|
public int Number { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Format { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
Rename.Table("Profiles").To("QualityProfiles");
|
Rename.Table("Profiles").To("QualityProfiles");
|
||||||
|
|
||||||
Alter.Table("QualityProfiles").AddColumn("UpgradeAllowed").AsInt32().Nullable();
|
Alter.Table("QualityProfiles").AddColumn("UpgradeAllowed").AsBoolean().Nullable();
|
||||||
Alter.Table("LanguageProfiles").AddColumn("UpgradeAllowed").AsInt32().Nullable();
|
Alter.Table("LanguageProfiles").AddColumn("UpgradeAllowed").AsBoolean().Nullable();
|
||||||
|
|
||||||
// Set upgrade allowed for existing profiles (default will be false for new profiles)
|
// Set upgrade allowed for existing profiles (default will be false for new profiles)
|
||||||
Update.Table("QualityProfiles").Set(new { UpgradeAllowed = true }).AllRows();
|
Update.Table("QualityProfiles").Set(new { UpgradeAllowed = true }).AllRows();
|
||||||
|
|
|
@ -9,47 +9,47 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
// Remove any artists linked to missing metadata
|
// Remove any artists linked to missing metadata
|
||||||
Execute.Sql(@"DELETE FROM Artists
|
Execute.Sql(@"DELETE FROM ""Artists""
|
||||||
WHERE Id in (
|
WHERE ""Id"" in (
|
||||||
SELECT Artists.Id from Artists
|
SELECT ""Artists"".""Id"" from ""Artists""
|
||||||
LEFT OUTER JOIN ArtistMetadata ON Artists.ArtistMetadataId = ArtistMetadata.Id
|
LEFT OUTER JOIN ""ArtistMetadata"" ON ""Artists"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
WHERE ArtistMetadata.Id IS NULL)");
|
WHERE ""ArtistMetadata"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove any albums linked to missing metadata
|
// Remove any albums linked to missing metadata
|
||||||
Execute.Sql(@"DELETE FROM Albums
|
Execute.Sql(@"DELETE FROM ""Albums""
|
||||||
WHERE Id in (
|
WHERE ""Id"" in (
|
||||||
SELECT Albums.Id from Albums
|
SELECT ""Albums"".""Id"" from ""Albums""
|
||||||
LEFT OUTER JOIN ArtistMetadata ON Albums.ArtistMetadataId = ArtistMetadata.Id
|
LEFT OUTER JOIN ""ArtistMetadata"" ON ""Albums"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
WHERE ArtistMetadata.Id IS NULL)");
|
WHERE ""ArtistMetadata"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove any album releases linked to albums that were deleted
|
// Remove any album releases linked to albums that were deleted
|
||||||
Execute.Sql(@"DELETE FROM AlbumReleases
|
Execute.Sql(@"DELETE FROM ""AlbumReleases""
|
||||||
WHERE Id in (
|
WHERE ""Id"" in (
|
||||||
SELECT AlbumReleases.Id from AlbumReleases
|
SELECT ""AlbumReleases"".""Id"" from ""AlbumReleases""
|
||||||
LEFT OUTER JOIN Albums ON Albums.Id = AlbumReleases.AlbumId
|
LEFT OUTER JOIN ""Albums"" ON ""Albums"".""Id"" = ""AlbumReleases"".""AlbumId""
|
||||||
WHERE Albums.Id IS NULL)");
|
WHERE ""Albums"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove any tracks linked to album releases that were deleted
|
// Remove any tracks linked to album releases that were deleted
|
||||||
Execute.Sql(@"DELETE FROM Tracks
|
Execute.Sql(@"DELETE FROM ""Tracks""
|
||||||
WHERE Id in (
|
WHERE ""Id"" in (
|
||||||
SELECT Tracks.Id from Tracks
|
SELECT ""Tracks"".""Id"" from ""Tracks""
|
||||||
LEFT OUTER JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
|
LEFT OUTER JOIN ""AlbumReleases"" ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id""
|
||||||
WHERE AlbumReleases.Id IS NULL)");
|
WHERE ""AlbumReleases"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove any tracks linked to the original missing metadata
|
// Remove any tracks linked to the original missing metadata
|
||||||
Execute.Sql(@"DELETE FROM Tracks
|
Execute.Sql(@"DELETE FROM ""Tracks""
|
||||||
WHERE Id in (
|
WHERE ""Id"" in (
|
||||||
SELECT Tracks.Id from Tracks
|
SELECT ""Tracks"".""Id"" from ""Tracks""
|
||||||
LEFT OUTER JOIN ArtistMetadata ON Tracks.ArtistMetadataId = ArtistMetadata.Id
|
LEFT OUTER JOIN ""ArtistMetadata"" ON ""Tracks"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
WHERE ArtistMetadata.Id IS NULL)");
|
WHERE ""ArtistMetadata"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove any trackfiles linked to the deleted tracks
|
// Remove any trackfiles linked to the deleted tracks
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles
|
Execute.Sql(@"DELETE FROM ""TrackFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT TrackFiles.Id FROM TrackFiles
|
SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles""
|
||||||
LEFT OUTER JOIN Tracks
|
LEFT OUTER JOIN ""Tracks""
|
||||||
ON TrackFiles.Id = Tracks.TrackFileId
|
ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId""
|
||||||
WHERE Tracks.Id IS NULL)");
|
WHERE ""Tracks"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("Notifications").AddColumn("OnHealthIssue").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("OnHealthIssue").AsBoolean().WithDefaultValue(false);
|
||||||
Alter.Table("Notifications").AddColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("IncludeHealthWarnings").AsBoolean().WithDefaultValue(false);
|
||||||
Alter.Table("Notifications").AddColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("OnDownloadFailure").AsBoolean().WithDefaultValue(false);
|
||||||
Alter.Table("Notifications").AddColumn("OnImportFailure").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("OnImportFailure").AsBoolean().WithDefaultValue(false);
|
||||||
Alter.Table("Notifications").AddColumn("OnTrackRetag").AsBoolean().WithDefaultValue(0);
|
Alter.Table("Notifications").AddColumn("OnTrackRetag").AsBoolean().WithDefaultValue(false);
|
||||||
|
|
||||||
Delete.Column("OnDownload").FromTable("Notifications");
|
Delete.Column("OnDownload").FromTable("Notifications");
|
||||||
|
|
||||||
|
|
|
@ -13,45 +13,45 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Alter.Table("TrackFiles").AddColumn("Path").AsString().Nullable();
|
Alter.Table("TrackFiles").AddColumn("Path").AsString().Nullable();
|
||||||
|
|
||||||
// Remove anything where RelativePath is null
|
// Remove anything where RelativePath is null
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles WHERE RelativePath IS NULL");
|
Execute.Sql(@"DELETE FROM ""TrackFiles"" WHERE ""RelativePath"" IS NULL");
|
||||||
|
|
||||||
// Remove anything not linked to a track (these shouldn't be present in version < 30)
|
// Remove anything not linked to a track (these shouldn't be present in version < 30)
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles
|
Execute.Sql(@"DELETE FROM ""TrackFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT TrackFiles.Id FROM TrackFiles
|
SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles""
|
||||||
LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId
|
LEFT JOIN ""Tracks"" ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId""
|
||||||
WHERE Tracks.Id IS NULL)");
|
WHERE ""Tracks"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Remove anything where we can't get an artist path (i.e. we don't know where it is)
|
// Remove anything where we can't get an artist path (i.e. we don't know where it is)
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles
|
Execute.Sql(@"DELETE FROM ""TrackFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT TrackFiles.Id FROM TrackFiles
|
SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles""
|
||||||
LEFT JOIN Albums ON TrackFiles.AlbumId = Albums.Id
|
LEFT JOIN ""Albums"" ON ""TrackFiles"".""AlbumId"" = ""Albums"".""Id""
|
||||||
LEFT JOIN Artists on Artists.ArtistMetadataId = Albums.ArtistMetadataId
|
LEFT JOIN ""Artists"" ON ""Artists"".""ArtistMetadataId"" = ""Albums"".""ArtistMetadataId""
|
||||||
WHERE Artists.Path IS NULL)");
|
WHERE ""Artists"".""Path"" IS NULL)");
|
||||||
|
|
||||||
// Remove anything linked to unmonitored or unidentified releases. This should ensure uniqueness of track files.
|
// Remove anything linked to unmonitored or unidentified releases. This should ensure uniqueness of track files.
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles
|
Execute.Sql(@"DELETE FROM ""TrackFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT TrackFiles.Id FROM TrackFiles
|
SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles""
|
||||||
LEFT JOIN Tracks ON TrackFiles.Id = Tracks.TrackFileId
|
LEFT JOIN ""Tracks"" ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId""
|
||||||
LEFT JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
|
LEFT JOIN ""AlbumReleases"" ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id""
|
||||||
WHERE AlbumReleases.Monitored = 0
|
WHERE ""AlbumReleases"".""Monitored"" = false
|
||||||
OR AlbumReleases.Monitored IS NULL)");
|
OR ""AlbumReleases"".""Monitored"" IS NULL)");
|
||||||
|
|
||||||
// Populate the full paths
|
// Populate the full paths
|
||||||
Execute.Sql(@"UPDATE TrackFiles
|
Execute.Sql(@"UPDATE ""TrackFiles""
|
||||||
SET Path = (SELECT Artists.Path || '" + System.IO.Path.DirectorySeparatorChar + @"' || TrackFiles.RelativePath
|
SET ""Path"" = (SELECT ""Artists"".""Path"" || '" + System.IO.Path.DirectorySeparatorChar + @"' || ""TrackFiles"".""RelativePath""
|
||||||
FROM Artists
|
FROM ""Artists""
|
||||||
JOIN Albums ON Albums.ArtistMetadataId = Artists.ArtistMetadataId
|
JOIN ""Albums"" ON ""Albums"".""ArtistMetadataId"" = ""Artists"".""ArtistMetadataId""
|
||||||
WHERE TrackFiles.AlbumId = Albums.Id)");
|
WHERE ""TrackFiles"".""AlbumId"" = ""Albums"".""Id"")");
|
||||||
|
|
||||||
// Belt and braces to ensure uniqueness
|
// Belt and braces to ensure uniqueness
|
||||||
Execute.Sql(@"DELETE FROM TrackFiles
|
Execute.Sql(@"DELETE FROM ""TrackFiles""
|
||||||
WHERE rowid NOT IN (
|
WHERE ""Id"" NOT IN (
|
||||||
SELECT min(rowid)
|
SELECT MIN(""Id"")
|
||||||
FROM TrackFiles
|
FROM ""TrackFiles""
|
||||||
GROUP BY Path
|
GROUP BY ""Path""
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// Now enforce the uniqueness constraint
|
// Now enforce the uniqueness constraint
|
||||||
|
|
|
@ -9,11 +9,11 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
// Remove any duplicate artists
|
// Remove any duplicate artists
|
||||||
Execute.Sql(@"DELETE FROM Artists
|
Execute.Sql(@"DELETE FROM ""Artists""
|
||||||
WHERE Id NOT IN (
|
WHERE ""Id"" NOT IN (
|
||||||
SELECT MIN(Artists.id) from Artists
|
SELECT MIN(""Artists"".""Id"") from ""Artists""
|
||||||
JOIN ArtistMetadata ON Artists.ArtistMetadataId = ArtistMetadata.Id
|
JOIN ""ArtistMetadata"" ON ""Artists"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
GROUP BY ArtistMetadata.Id)");
|
GROUP BY ""ArtistMetadata"".""Id"")");
|
||||||
|
|
||||||
// The index exists but will be recreated as part of unique constraint
|
// The index exists but will be recreated as part of unique constraint
|
||||||
Delete.Index().OnTable("Artists").OnColumn("ArtistMetadataId");
|
Delete.Index().OnTable("Artists").OnColumn("ArtistMetadataId");
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.WithConnection(SetConfigValue);
|
Execute.WithConnection(SetConfigValue);
|
||||||
Execute.Sql("DELETE FROM Config WHERE Key = 'autodownloadpropers'");
|
Execute.Sql("DELETE FROM \"Config\" WHERE \"Key\" = 'autodownloadpropers'");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetConfigValue(IDbConnection conn, IDbTransaction tran)
|
private void SetConfigValue(IDbConnection conn, IDbTransaction tran)
|
||||||
|
@ -18,7 +18,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var cmd = conn.CreateCommand())
|
using (var cmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
cmd.Transaction = tran;
|
cmd.Transaction = tran;
|
||||||
cmd.CommandText = "SELECT Value FROM Config WHERE Key = 'autodownloadpropers'";
|
cmd.CommandText = "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'autodownloadpropers'";
|
||||||
|
|
||||||
using (var reader = cmd.ExecuteReader())
|
using (var reader = cmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
using (var updateCmd = conn.CreateCommand())
|
using (var updateCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
updateCmd.Transaction = tran;
|
updateCmd.Transaction = tran;
|
||||||
updateCmd.CommandText = "INSERT INTO Config (key, value) VALUES ('downloadpropersandrepacks', ?)";
|
updateCmd.CommandText = "INSERT INTO \"Config\" (\"key\", \"value\") VALUES ('downloadpropersandrepacks', ?)";
|
||||||
updateCmd.AddParameter(newValue);
|
updateCmd.AddParameter(newValue);
|
||||||
|
|
||||||
updateCmd.ExecuteNonQuery();
|
updateCmd.ExecuteNonQuery();
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("NamingConfig").AddColumn("MultiDiscTrackFormat").AsString().Nullable();
|
Alter.Table("NamingConfig").AddColumn("MultiDiscTrackFormat").AsString().Nullable();
|
||||||
Execute.Sql("UPDATE NamingConfig SET MultiDiscTrackFormat = '{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}'");
|
Execute.Sql("UPDATE \"NamingConfig\" SET \"MultiDiscTrackFormat\" = '{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using Dapper;
|
||||||
using FluentMigrator;
|
using FluentMigrator;
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
[Migration(36)]
|
[Migration(036)]
|
||||||
public class add_download_client_priority : NzbDroneMigrationBase
|
public class add_download_client_priority : NzbDroneMigrationBase
|
||||||
{
|
{
|
||||||
// Need snapshot in time without having to instantiate.
|
// Need snapshot in time without having to instantiate.
|
||||||
|
@ -22,34 +24,43 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
|
||||||
private void InitPriorityForBackwardCompatibility(IDbConnection conn, IDbTransaction tran)
|
private void InitPriorityForBackwardCompatibility(IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
using (var cmd = conn.CreateCommand())
|
var downloadClients = conn.Query<DownloadClients036>($"SELECT \"Id\", \"Implementation\" FROM \"DownloadClients\" WHERE \"Enable\"");
|
||||||
{
|
|
||||||
cmd.Transaction = tran;
|
|
||||||
cmd.CommandText = "SELECT Id, Implementation FROM DownloadClients WHERE Enable = 1";
|
|
||||||
|
|
||||||
using (var reader = cmd.ExecuteReader())
|
if (!downloadClients.Any())
|
||||||
{
|
{
|
||||||
int nextUsenet = 1;
|
return;
|
||||||
int nextTorrent = 1;
|
}
|
||||||
while (reader.Read())
|
|
||||||
|
var nextUsenet = 1;
|
||||||
|
var nextTorrent = 1;
|
||||||
|
|
||||||
|
foreach (var downloadClient in downloadClients)
|
||||||
{
|
{
|
||||||
var id = reader.GetInt32(0);
|
var isUsenet = _usenetImplementations.Contains(downloadClient.Implementation);
|
||||||
var implName = reader.GetString(1);
|
|
||||||
|
|
||||||
var isUsenet = _usenetImplementations.Contains(implName);
|
|
||||||
|
|
||||||
using (var updateCmd = conn.CreateCommand())
|
using (var updateCmd = conn.CreateCommand())
|
||||||
{
|
{
|
||||||
updateCmd.Transaction = tran;
|
updateCmd.Transaction = tran;
|
||||||
updateCmd.CommandText = "UPDATE DownloadClients SET Priority = ? WHERE Id = ?";
|
if (conn.GetType().FullName == "Npgsql.NpgsqlConnection")
|
||||||
|
{
|
||||||
|
updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Priority\" = $1 WHERE \"Id\" = $2";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateCmd.CommandText = "UPDATE \"DownloadClients\" SET \"Priority\" = ? WHERE \"Id\" = ?";
|
||||||
|
}
|
||||||
|
|
||||||
updateCmd.AddParameter(isUsenet ? nextUsenet++ : nextTorrent++);
|
updateCmd.AddParameter(isUsenet ? nextUsenet++ : nextTorrent++);
|
||||||
updateCmd.AddParameter(id);
|
updateCmd.AddParameter(downloadClient.Id);
|
||||||
|
|
||||||
updateCmd.ExecuteNonQuery();
|
updateCmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class DownloadClients036
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Implementation { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Alter.Table("RootFolders").AddColumn("DefaultMonitorOption").AsInt32().WithDefaultValue(0);
|
Alter.Table("RootFolders").AddColumn("DefaultMonitorOption").AsInt32().WithDefaultValue(0);
|
||||||
Alter.Table("RootFolders").AddColumn("DefaultTags").AsString().Nullable();
|
Alter.Table("RootFolders").AddColumn("DefaultTags").AsString().Nullable();
|
||||||
|
|
||||||
Execute.WithConnection(SetDefaultOptions);
|
IfDatabase("sqlite").Execute.WithConnection(SetDefaultOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetDefaultOptions(IDbConnection conn, IDbTransaction tran)
|
private void SetDefaultOptions(IDbConnection conn, IDbTransaction tran)
|
||||||
|
|
|
@ -10,8 +10,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
Delete.Column("AlbumFolder").FromTable("Artists");
|
Delete.Column("AlbumFolder").FromTable("Artists");
|
||||||
|
|
||||||
Execute.Sql("UPDATE NamingConfig SET StandardTrackFormat = AlbumFolderFormat || '/' || StandardTrackFormat");
|
Execute.Sql("UPDATE \"NamingConfig\" SET \"StandardTrackFormat\" = \"AlbumFolderFormat\" || '/' || \"StandardTrackFormat\"");
|
||||||
Execute.Sql("UPDATE NamingConfig SET MultiDiscTrackFormat = AlbumFolderFormat || '/' || MultiDiscTrackFormat");
|
Execute.Sql("UPDATE \"NamingConfig\" SET \"MultiDiscTrackFormat\" = \"AlbumFolderFormat\" || '/' || \"MultiDiscTrackFormat\"");
|
||||||
|
|
||||||
Delete.Column("AlbumFolderFormat").FromTable("NamingConfig");
|
Delete.Column("AlbumFolderFormat").FromTable("NamingConfig");
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')");
|
IfDatabase("sqlite").Execute.Sql("DELETE FROM config WHERE Key IN ('folderchmod', 'chownuser')");
|
||||||
Execute.WithConnection(ConvertFileChmodToFolderChmod);
|
IfDatabase("sqlite").Execute.WithConnection(ConvertFileChmodToFolderChmod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertFileChmodToFolderChmod(IDbConnection conn, IDbTransaction tran)
|
private void ConvertFileChmodToFolderChmod(IDbConnection conn, IDbTransaction tran)
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("UPDATE Notifications SET Implementation = Replace(Implementation, 'DiscordNotifier', 'Notifiarr'),ConfigContract = Replace(ConfigContract, 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE Implementation = 'DiscordNotifier';");
|
Execute.Sql("UPDATE \"Notifications\" SET \"Implementation\" = Replace(\"Implementation\", 'DiscordNotifier', 'Notifiarr'),\"ConfigContract\" = Replace(\"ConfigContract\", 'DiscordNotifierSettings', 'NotifiarrSettings') WHERE \"Implementation\" = 'DiscordNotifier';");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
|
||||||
private void ChangeEmailAddressType(IDbConnection conn, IDbTransaction tran)
|
private void ChangeEmailAddressType(IDbConnection conn, IDbTransaction tran)
|
||||||
{
|
{
|
||||||
var rows = conn.Query<ProviderDefinition166>($"SELECT Id, Settings FROM Notifications WHERE Implementation = 'Email'");
|
var rows = conn.Query<ProviderDefinition166>($"SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Email'");
|
||||||
|
|
||||||
var corrected = new List<ProviderDefinition166>();
|
var corrected = new List<ProviderDefinition166>();
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateSql = "UPDATE Notifications SET Settings = @Settings WHERE Id = @Id";
|
var updateSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
|
||||||
conn.Execute(updateSql, corrected, transaction: tran);
|
conn.Execute(updateSql, corrected, transaction: tran);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
|
||||||
using FluentMigrator;
|
using FluentMigrator;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
@ -24,7 +21,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
var removeCompletedDownloads = false;
|
var removeCompletedDownloads = false;
|
||||||
var removeFailedDownloads = true;
|
var removeFailedDownloads = true;
|
||||||
|
|
||||||
using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removecompleteddownloads'"))
|
using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'removecompleteddownloads'"))
|
||||||
{
|
{
|
||||||
if ((removeCompletedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "true")
|
if ((removeCompletedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "true")
|
||||||
{
|
{
|
||||||
|
@ -32,7 +29,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removefaileddownloads'"))
|
using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT \"Value\" FROM \"Config\" WHERE \"Key\" = 'removefaileddownloads'"))
|
||||||
{
|
{
|
||||||
if ((removeFailedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "false")
|
if ((removeFailedDownloadsCmd.ExecuteScalar() as string)?.ToLower() == "false")
|
||||||
{
|
{
|
||||||
|
@ -40,14 +37,25 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation IN (\"RTorrent\", \"Flood\") THEN 0 ELSE ? END), RemoveFailedDownloads = ?"))
|
string commandText;
|
||||||
|
|
||||||
|
if (conn.GetType().FullName == "Npgsql.NpgsqlConnection")
|
||||||
{
|
{
|
||||||
updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0);
|
commandText = $"UPDATE \"DownloadClients\" SET \"RemoveCompletedDownloads\" = (CASE WHEN \"Implementation\" IN ('RTorrent', 'Flood') THEN 'false' ELSE $1 END), \"RemoveFailedDownloads\" = $2";
|
||||||
updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
commandText = $"UPDATE \"DownloadClients\" SET \"RemoveCompletedDownloads\" = (CASE WHEN \"Implementation\" IN ('RTorrent', 'Flood') THEN 'false' ELSE ? END), \"RemoveFailedDownloads\" = ?";
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var updateClientCmd = conn.CreateCommand(tran, commandText))
|
||||||
|
{
|
||||||
|
updateClientCmd.AddParameter(removeCompletedDownloads);
|
||||||
|
updateClientCmd.AddParameter(removeFailedDownloads);
|
||||||
updateClientCmd.ExecuteNonQuery();
|
updateClientCmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM Config WHERE Key IN ('removecompleteddownloads', 'removefaileddownloads')"))
|
using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM \"Config\" WHERE \"Key\" IN ('removecompleteddownloads', 'removefaileddownloads')"))
|
||||||
{
|
{
|
||||||
removeConfigCmd.ExecuteNonQuery();
|
removeConfigCmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
Create.Index().OnTable("DownloadHistory").OnColumn("ArtistId");
|
Create.Index().OnTable("DownloadHistory").OnColumn("ArtistId");
|
||||||
Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId");
|
Create.Index().OnTable("DownloadHistory").OnColumn("DownloadId");
|
||||||
|
|
||||||
Execute.WithConnection(InitialImportedDownloadHistory);
|
IfDatabase("sqlite").Execute.WithConnection(InitialImportedDownloadHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<int, int> EventTypeMap = new Dictionary<int, int>()
|
private static readonly Dictionary<int, int> EventTypeMap = new Dictionary<int, int>()
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsInt32().WithDefaultValue(1);
|
Alter.Table("ImportLists").AddColumn("ShouldSearch").AsBoolean().WithDefaultValue(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsInt32().WithDefaultValue(0);
|
Alter.Table("ImportLists").AddColumn("ShouldMonitorExisting").AsBoolean().WithDefaultValue(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Omgwtfnzbs'");
|
Delete.FromTable("Indexers").Row(new { Implementation = "Omgwtfnzbs" });
|
||||||
Execute.Sql("DELETE FROM Indexers WHERE Implementation = 'Waffles'");
|
Delete.FromTable("Indexers").Row(new { Implementation = "Waffles" });
|
||||||
|
|
||||||
Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable();
|
Alter.Table("Indexers").AddColumn("Tags").AsString().Nullable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentMigrator.Runner;
|
using FluentMigrator.Runner;
|
||||||
|
using FluentMigrator.Runner.Generators;
|
||||||
using FluentMigrator.Runner.Initialization;
|
using FluentMigrator.Runner.Initialization;
|
||||||
using FluentMigrator.Runner.Processors;
|
using FluentMigrator.Runner.Processors;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
@ -34,11 +35,16 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||||
|
|
||||||
_logger.Info("*** Migrating {0} ***", connectionString);
|
_logger.Info("*** Migrating {0} ***", connectionString);
|
||||||
|
|
||||||
var serviceProvider = new ServiceCollection()
|
ServiceProvider serviceProvider;
|
||||||
|
|
||||||
|
var db = connectionString.Contains(".db") ? "sqlite" : "postgres";
|
||||||
|
|
||||||
|
serviceProvider = new ServiceCollection()
|
||||||
.AddLogging(b => b.AddNLog())
|
.AddLogging(b => b.AddNLog())
|
||||||
.AddFluentMigratorCore()
|
.AddFluentMigratorCore()
|
||||||
.ConfigureRunner(
|
.ConfigureRunner(
|
||||||
builder => builder
|
builder => builder
|
||||||
|
.AddPostgres()
|
||||||
.AddNzbDroneSQLite()
|
.AddNzbDroneSQLite()
|
||||||
.WithGlobalConnectionString(connectionString)
|
.WithGlobalConnectionString(connectionString)
|
||||||
.WithMigrationsIn(Assembly.GetExecutingAssembly()))
|
.WithMigrationsIn(Assembly.GetExecutingAssembly()))
|
||||||
|
@ -48,6 +54,14 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||||
opt.PreviewOnly = false;
|
opt.PreviewOnly = false;
|
||||||
opt.Timeout = TimeSpan.FromSeconds(60);
|
opt.Timeout = TimeSpan.FromSeconds(60);
|
||||||
})
|
})
|
||||||
|
.Configure<SelectingProcessorAccessorOptions>(cfg =>
|
||||||
|
{
|
||||||
|
cfg.ProcessorId = db;
|
||||||
|
})
|
||||||
|
.Configure<SelectingGeneratorAccessorOptions>(cfg =>
|
||||||
|
{
|
||||||
|
cfg.GeneratorId = db;
|
||||||
|
})
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
|
|
||||||
using (var scope = serviceProvider.CreateScope())
|
using (var scope = serviceProvider.CreateScope())
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore
|
||||||
|
{
|
||||||
|
public class PostgresOptions
|
||||||
|
{
|
||||||
|
public string Host { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public string User { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public string MainDb { get; set; }
|
||||||
|
public string LogDb { get; set; }
|
||||||
|
|
||||||
|
public static PostgresOptions GetOptions()
|
||||||
|
{
|
||||||
|
var config = new ConfigurationBuilder()
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var postgresOptions = new PostgresOptions();
|
||||||
|
config.GetSection("Lidarr:Postgres").Bind(postgresOptions);
|
||||||
|
|
||||||
|
return postgresOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,9 +8,17 @@ namespace NzbDrone.Core.Datastore
|
||||||
public class SqlBuilder
|
public class SqlBuilder
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
|
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
|
||||||
|
private readonly DatabaseType _databaseType;
|
||||||
|
|
||||||
|
public SqlBuilder(DatabaseType databaseType)
|
||||||
|
{
|
||||||
|
_databaseType = databaseType;
|
||||||
|
}
|
||||||
|
|
||||||
public int Sequence { get; private set; }
|
public int Sequence { get; private set; }
|
||||||
|
|
||||||
|
public DatabaseType DatabaseType => _databaseType;
|
||||||
|
|
||||||
public Template AddTemplate(string sql, dynamic parameters = null) =>
|
public Template AddTemplate(string sql, dynamic parameters = null) =>
|
||||||
new Template(this, sql, parameters);
|
new Template(this, sql, parameters);
|
||||||
|
|
||||||
|
|
|
@ -49,17 +49,17 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
public string SelectTemplate(Type x)
|
public string SelectTemplate(Type x)
|
||||||
{
|
{
|
||||||
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DeleteTemplate(Type x)
|
public string DeleteTemplate(Type x)
|
||||||
{
|
{
|
||||||
return $"DELETE FROM {TableMap[x]} /**where**/";
|
return $"DELETE FROM \"{TableMap[x]}\" /**where**/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PageCountTemplate(Type x)
|
public string PageCountTemplate(Type x)
|
||||||
{
|
{
|
||||||
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/";
|
return $"SELECT /**select**/ FROM \"{TableMap[x]}\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsValidSortKey(string sortKey)
|
public bool IsValidSortKey(string sortKey)
|
||||||
|
@ -90,6 +90,35 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetSortKey(string sortKey)
|
||||||
|
{
|
||||||
|
string table = null;
|
||||||
|
|
||||||
|
if (sortKey.Contains('.'))
|
||||||
|
{
|
||||||
|
var split = sortKey.Split('.');
|
||||||
|
if (split.Length != 2)
|
||||||
|
{
|
||||||
|
return sortKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
table = split[0];
|
||||||
|
sortKey = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table != null && !TableMap.Values.Contains(table, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return sortKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_allowedOrderBy.Contains(sortKey))
|
||||||
|
{
|
||||||
|
return sortKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _allowedOrderBy.First(x => x.Equals(sortKey, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LazyLoadedProperty
|
public class LazyLoadedProperty
|
||||||
|
@ -154,7 +183,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
(db, parent) =>
|
(db, parent) =>
|
||||||
{
|
{
|
||||||
var id = childIdSelector(parent);
|
var id = childIdSelector(parent);
|
||||||
return db.Query<TChild>(new SqlBuilder().Where<TChild>(x => x.Id == id)).SingleOrDefault();
|
return db.Query<TChild>(new SqlBuilder(db.DatabaseType).Where<TChild>(x => x.Id == id)).SingleOrDefault();
|
||||||
},
|
},
|
||||||
parent => childIdSelector(parent) > 0);
|
parent => childIdSelector(parent) > 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,24 +105,24 @@ namespace NzbDrone.Core.Datastore
|
||||||
.HasOne(a => a.Metadata, a => a.ArtistMetadataId)
|
.HasOne(a => a.Metadata, a => a.ArtistMetadataId)
|
||||||
.HasOne(a => a.QualityProfile, a => a.QualityProfileId)
|
.HasOne(a => a.QualityProfile, a => a.QualityProfileId)
|
||||||
.HasOne(s => s.MetadataProfile, s => s.MetadataProfileId)
|
.HasOne(s => s.MetadataProfile, s => s.MetadataProfileId)
|
||||||
.LazyLoad(a => a.Albums, (db, a) => db.Query<Album>(new SqlBuilder().Where<Album>(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0);
|
.LazyLoad(a => a.Albums, (db, a) => db.Query<Album>(new SqlBuilder(db.DatabaseType).Where<Album>(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0);
|
||||||
|
|
||||||
Mapper.Entity<ArtistMetadata>("ArtistMetadata").RegisterModel();
|
Mapper.Entity<ArtistMetadata>("ArtistMetadata").RegisterModel();
|
||||||
|
|
||||||
Mapper.Entity<Album>("Albums").RegisterModel()
|
Mapper.Entity<Album>("Albums").RegisterModel()
|
||||||
.Ignore(x => x.ArtistId)
|
.Ignore(x => x.ArtistId)
|
||||||
.HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId)
|
.HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId)
|
||||||
.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.AlbumReleases, (db, album) => db.Query<AlbumRelease>(new SqlBuilder(db.DatabaseType).Where<AlbumRelease>(r => r.AlbumId == album.Id)).ToList(), a => a.Id > 0)
|
||||||
.LazyLoad(a => a.Artist,
|
.LazyLoad(a => a.Artist,
|
||||||
(db, album) => ArtistRepository.Query(db,
|
(db, album) => ArtistRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||||
.Where<Artist>(a => a.ArtistMetadataId == album.ArtistMetadataId)).SingleOrDefault(),
|
.Where<Artist>(a => a.ArtistMetadataId == album.ArtistMetadataId)).SingleOrDefault(),
|
||||||
a => a.ArtistMetadataId > 0);
|
a => a.ArtistMetadataId > 0);
|
||||||
|
|
||||||
Mapper.Entity<AlbumRelease>("AlbumReleases").RegisterModel()
|
Mapper.Entity<AlbumRelease>("AlbumReleases").RegisterModel()
|
||||||
.HasOne(r => r.Album, r => r.AlbumId)
|
.HasOne(r => r.Album, r => r.AlbumId)
|
||||||
.LazyLoad(x => x.Tracks, (db, release) => db.Query<Track>(new SqlBuilder().Where<Track>(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0);
|
.LazyLoad(x => x.Tracks, (db, release) => db.Query<Track>(new SqlBuilder(db.DatabaseType).Where<Track>(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0);
|
||||||
|
|
||||||
Mapper.Entity<Track>("Tracks").RegisterModel()
|
Mapper.Entity<Track>("Tracks").RegisterModel()
|
||||||
.Ignore(t => t.HasFile)
|
.Ignore(t => t.HasFile)
|
||||||
|
@ -131,7 +131,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
.HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId)
|
.HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId)
|
||||||
.LazyLoad(t => t.TrackFile,
|
.LazyLoad(t => t.TrackFile,
|
||||||
(db, track) => MediaFileRepository.Query(db,
|
(db, track) => MediaFileRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<TrackFile, Track>((l, r) => l.Id == r.TrackFileId)
|
.Join<TrackFile, Track>((l, r) => l.Id == r.TrackFileId)
|
||||||
.Join<TrackFile, Album>((l, r) => l.AlbumId == r.Id)
|
.Join<TrackFile, Album>((l, r) => l.AlbumId == r.Id)
|
||||||
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||||
|
@ -140,7 +140,7 @@ namespace NzbDrone.Core.Datastore
|
||||||
t => t.TrackFileId > 0)
|
t => t.TrackFileId > 0)
|
||||||
.LazyLoad(x => x.Artist,
|
.LazyLoad(x => x.Artist,
|
||||||
(db, t) => ArtistRepository.Query(db,
|
(db, t) => ArtistRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||||
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||||
.Join<Album, AlbumRelease>((l, r) => l.Id == r.AlbumId)
|
.Join<Album, AlbumRelease>((l, r) => l.Id == r.AlbumId)
|
||||||
|
@ -149,10 +149,10 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
Mapper.Entity<TrackFile>("TrackFiles").RegisterModel()
|
Mapper.Entity<TrackFile>("TrackFiles").RegisterModel()
|
||||||
.HasOne(f => f.Album, f => f.AlbumId)
|
.HasOne(f => f.Album, f => f.AlbumId)
|
||||||
.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.Tracks, (db, file) => db.Query<Track>(new SqlBuilder(db.DatabaseType).Where<Track>(t => t.TrackFileId == file.Id)).ToList(), x => x.Id > 0)
|
||||||
.LazyLoad(x => x.Artist,
|
.LazyLoad(x => x.Artist,
|
||||||
(db, f) => ArtistRepository.Query(db,
|
(db, f) => ArtistRepository.Query(db,
|
||||||
new SqlBuilder()
|
new SqlBuilder(db.DatabaseType)
|
||||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||||
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||||
.Where<Album>(a => a.Id == f.AlbumId)).SingleOrDefault(),
|
.Where<Album>(a => a.Id == f.AlbumId)).SingleOrDefault(),
|
||||||
|
|
|
@ -1,391 +1,10 @@
|
||||||
using System;
|
using Dapper;
|
||||||
using System.Collections.Generic;
|
using NzbDrone.Common.Extensions;
|
||||||
using System.Data;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using Dapper;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
public class WhereBuilder : ExpressionVisitor
|
public abstract class WhereBuilder : ExpressionVisitor
|
||||||
{
|
{
|
||||||
protected StringBuilder _sb;
|
public DynamicParameters Parameters { get; protected set; }
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,389 @@
|
||||||
|
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 WhereBuilderPostgres : WhereBuilder
|
||||||
|
{
|
||||||
|
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 WhereBuilderPostgres(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(" = ANY (");
|
||||||
|
|
||||||
|
// 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(" ILIKE '%' || ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseStartsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" ILIKE ");
|
||||||
|
|
||||||
|
Visit(body.Arguments[0]);
|
||||||
|
|
||||||
|
_sb.Append(" || '%')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseEndsWith(MethodCallExpression body)
|
||||||
|
{
|
||||||
|
_sb.Append('(');
|
||||||
|
|
||||||
|
Visit(body.Object);
|
||||||
|
|
||||||
|
_sb.Append(" ILIKE '%' || ");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,389 @@
|
||||||
|
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 WhereBuilderSqlite : WhereBuilder
|
||||||
|
{
|
||||||
|
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 WhereBuilderSqlite(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -90,11 +90,11 @@ namespace NzbDrone.Core.History
|
||||||
|
|
||||||
public List<EntityHistory> FindDownloadHistory(int idArtistId, QualityModel quality)
|
public List<EntityHistory> FindDownloadHistory(int idArtistId, QualityModel quality)
|
||||||
{
|
{
|
||||||
var allowed = new[] { EntityHistoryEventType.Grabbed, EntityHistoryEventType.DownloadFailed, EntityHistoryEventType.TrackFileImported };
|
var allowed = new[] { (int)EntityHistoryEventType.Grabbed, (int)EntityHistoryEventType.DownloadFailed, (int)EntityHistoryEventType.TrackFileImported };
|
||||||
|
|
||||||
return Query(h => h.ArtistId == idArtistId &&
|
return Query(h => h.ArtistId == idArtistId &&
|
||||||
h.Quality == quality &&
|
h.Quality == quality &&
|
||||||
allowed.Contains(h.EventType));
|
allowed.Contains((int)h.EventType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteForArtists(List<int> artistIds)
|
public void DeleteForArtists(List<int> artistIds)
|
||||||
|
@ -102,7 +102,7 @@ namespace NzbDrone.Core.History
|
||||||
Delete(c => artistIds.Contains(c.ArtistId));
|
Delete(c => artistIds.Contains(c.ArtistId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
|
protected override SqlBuilder PagedBuilder() => new SqlBuilder(_database.DatabaseType)
|
||||||
.Join<EntityHistory, Artist>((h, a) => h.ArtistId == a.Id)
|
.Join<EntityHistory, Artist>((h, a) => h.ArtistId == a.Id)
|
||||||
.Join<EntityHistory, Album>((h, a) => h.AlbumId == a.Id)
|
.Join<EntityHistory, Album>((h, a) => h.AlbumId == a.Id)
|
||||||
.LeftJoin<EntityHistory, Track>((h, t) => h.TrackId == t.Id);
|
.LeftJoin<EntityHistory, Track>((h, t) => h.TrackId == t.Id);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
@ -16,17 +16,33 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
WHERE Id IN (
|
{
|
||||||
SELECT Id FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE RelativePath
|
WHERE ""Id"" = ANY (
|
||||||
|
SELECT ""Id"" FROM ""MetadataFiles""
|
||||||
|
WHERE ""RelativePath""
|
||||||
|
LIKE '_:\\%'
|
||||||
|
OR ""RelativePath""
|
||||||
|
LIKE '\\%'
|
||||||
|
OR ""RelativePath""
|
||||||
|
LIKE '/%'
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
|
WHERE ""Id"" IN (
|
||||||
|
SELECT ""Id"" FROM ""MetadataFiles""
|
||||||
|
WHERE ""RelativePath""
|
||||||
LIKE '_:\%'
|
LIKE '_:\%'
|
||||||
OR RelativePath
|
OR ""RelativePath""
|
||||||
LIKE '\%'
|
LIKE '\%'
|
||||||
OR RelativePath
|
OR ""RelativePath""
|
||||||
LIKE '/%'
|
LIKE '/%'
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM NamingConfig
|
mapper.Execute(@"DELETE FROM ""NamingConfig""
|
||||||
WHERE ID NOT IN (
|
WHERE ""Id"" NOT IN (
|
||||||
SELECT ID FROM NamingConfig
|
SELECT ""Id"" FROM ""NamingConfig""
|
||||||
LIMIT 1)");
|
LIMIT 1)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM Users
|
mapper.Execute(@"DELETE FROM ""Users""
|
||||||
WHERE ID NOT IN (
|
WHERE ""Id"" NOT IN (
|
||||||
SELECT ID FROM Users
|
SELECT ""Id"" FROM ""Users""
|
||||||
LIMIT 1)");
|
LIMIT 1)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
@ -16,11 +16,24 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
|
||||||
public void Clean()
|
public void Clean()
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
var mapper = _database.OpenConnection();
|
||||||
|
|
||||||
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM PendingReleases
|
mapper.Execute(@"DELETE FROM ""PendingReleases""
|
||||||
WHERE Added < @TwoWeeksAgo
|
WHERE ""Added"" < @TwoWeeksAgo
|
||||||
AND REASON IN @Reasons",
|
AND ""Reason"" = ANY (@Reasons)",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
|
||||||
|
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mapper.Execute(@"DELETE FROM ""PendingReleases""
|
||||||
|
WHERE ""Added"" < @TwoWeeksAgo
|
||||||
|
AND ""REASON"" IN @Reasons",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
|
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
|
||||||
|
|
|
@ -24,12 +24,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT MIN(""Id"") FROM ""MetadataFiles""
|
||||||
WHERE Type = 1
|
WHERE ""Type"" = 1
|
||||||
GROUP BY ArtistId, Consumer
|
GROUP BY ""ArtistId"", ""Consumer""
|
||||||
HAVING COUNT(ArtistId) > 1
|
HAVING COUNT(""ArtistId"") > 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,12 +38,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT MIN(""Id"") FROM ""MetadataFiles""
|
||||||
WHERE Type = 6
|
WHERE ""Type"" = 6
|
||||||
GROUP BY AlbumId, Consumer
|
GROUP BY ""AlbumId"", ""Consumer""
|
||||||
HAVING COUNT(AlbumId) > 1
|
HAVING COUNT(""AlbumId"") > 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,12 +52,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT MIN(""Id"") FROM ""MetadataFiles""
|
||||||
WHERE Type = 2
|
WHERE ""Type"" = 2
|
||||||
GROUP BY TrackFileId, Consumer
|
GROUP BY ""TrackFileId"", ""Consumer""
|
||||||
HAVING COUNT(TrackFileId) > 1
|
HAVING COUNT(""TrackFileId"") > 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,12 +66,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT MIN(""Id"") FROM ""MetadataFiles""
|
||||||
WHERE Type = 5
|
WHERE ""Type"" = 5
|
||||||
GROUP BY TrackFileId, Consumer
|
GROUP BY ""TrackFileId"", ""Consumer""
|
||||||
HAVING COUNT(TrackFileId) > 1
|
HAVING COUNT(""TrackFileId"") > 1
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM Albums
|
mapper.Execute(@"DELETE FROM ""Albums""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Albums.Id FROM Albums
|
SELECT ""Albums"".""Id"" FROM ""Albums""
|
||||||
LEFT OUTER JOIN Artists
|
LEFT OUTER JOIN ""Artists""
|
||||||
ON Albums.ArtistMetadataId = Artists.ArtistMetadataId
|
ON ""Albums"".""ArtistMetadataId"" = ""Artists"".""ArtistMetadataId""
|
||||||
WHERE Artists.Id IS NULL)");
|
WHERE ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM ArtistMetadata
|
mapper.Execute(@"DELETE FROM ""ArtistMetadata""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT ArtistMetadata.Id FROM ArtistMetadata
|
SELECT ""ArtistMetadata"".""Id"" FROM ""ArtistMetadata""
|
||||||
LEFT OUTER JOIN Albums ON Albums.ArtistMetadataId = ArtistMetadata.Id
|
LEFT OUTER JOIN ""Albums"" ON ""Albums"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
LEFT OUTER JOIN Tracks ON Tracks.ArtistMetadataId = ArtistMetadata.Id
|
LEFT OUTER JOIN ""Tracks"" ON ""Tracks"".""ArtistMetadataId"" = ""ArtistMetadata"".""Id""
|
||||||
LEFT OUTER JOIN Artists ON Artists.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)");
|
WHERE ""Albums"".""Id"" IS NULL AND ""Tracks"".""Id"" IS NULL AND ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM Blocklist
|
mapper.Execute(@"DELETE FROM ""Blocklist""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Blocklist.Id FROM Blocklist
|
SELECT ""Blocklist"".""Id"" FROM ""Blocklist""
|
||||||
LEFT OUTER JOIN Artists
|
LEFT OUTER JOIN ""Artists""
|
||||||
ON Blocklist.ArtistId = Artists.Id
|
ON ""Blocklist"".""ArtistId"" = ""Artists"".""Id""
|
||||||
WHERE Artists.Id IS NULL)");
|
WHERE ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM DownloadClientStatus
|
mapper.Execute(@"DELETE FROM ""DownloadClientStatus""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT DownloadClientStatus.Id FROM DownloadClientStatus
|
SELECT ""DownloadClientStatus"".""Id"" FROM ""DownloadClientStatus""
|
||||||
LEFT OUTER JOIN DownloadClients
|
LEFT OUTER JOIN ""DownloadClients""
|
||||||
ON DownloadClientStatus.ProviderId = DownloadClients.Id
|
ON ""DownloadClientStatus"".""ProviderId"" = ""DownloadClients"".""Id""
|
||||||
WHERE DownloadClients.Id IS NULL)");
|
WHERE ""DownloadClients"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM History
|
mapper.Execute(@"DELETE FROM ""History""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT History.Id FROM History
|
SELECT ""History"".""Id"" FROM ""History""
|
||||||
LEFT OUTER JOIN Artists
|
LEFT OUTER JOIN ""Artists""
|
||||||
ON History.ArtistId = Artists.Id
|
ON ""History"".""ArtistId"" = ""Artists"".""Id""
|
||||||
WHERE Artists.Id IS NULL)");
|
WHERE ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,12 +35,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM History
|
mapper.Execute(@"DELETE FROM ""History""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT History.Id FROM History
|
SELECT ""History"".""Id"" FROM ""History""
|
||||||
LEFT OUTER JOIN Albums
|
LEFT OUTER JOIN ""Albums""
|
||||||
ON History.AlbumId = Albums.Id
|
ON ""History"".""AlbumId"" = ""Albums"".""Id""
|
||||||
WHERE Albums.Id IS NULL)");
|
WHERE ""Albums"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM ImportListStatus
|
mapper.Execute(@"DELETE FROM ""ImportListStatus""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT ImportListStatus.Id FROM ImportListStatus
|
SELECT ""ImportListStatus"".""Id"" FROM ""ImportListStatus""
|
||||||
LEFT OUTER JOIN ImportLists
|
LEFT OUTER JOIN ""ImportLists""
|
||||||
ON ImportListStatus.ProviderId = ImportLists.Id
|
ON ""ImportListStatus"".""ProviderId"" = ""ImportLists"".""Id""
|
||||||
WHERE ImportLists.Id IS NULL)");
|
WHERE ""ImportLists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM IndexerStatus
|
mapper.Execute(@"DELETE FROM ""IndexerStatus""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT IndexerStatus.Id FROM IndexerStatus
|
SELECT ""IndexerStatus"".""Id"" FROM ""IndexerStatus""
|
||||||
LEFT OUTER JOIN Indexers
|
LEFT OUTER JOIN ""Indexers""
|
||||||
ON IndexerStatus.ProviderId = Indexers.Id
|
ON ""IndexerStatus"".""ProviderId"" = ""Indexers"".""Id""
|
||||||
WHERE Indexers.Id IS NULL)");
|
WHERE ""Indexers"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles""
|
||||||
LEFT OUTER JOIN Artists
|
LEFT OUTER JOIN ""Artists""
|
||||||
ON MetadataFiles.ArtistId = Artists.Id
|
ON ""MetadataFiles"".""ArtistId"" = ""Artists"".""Id""
|
||||||
WHERE Artists.Id IS NULL)");
|
WHERE ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles""
|
||||||
LEFT OUTER JOIN Albums
|
LEFT OUTER JOIN ""Albums""
|
||||||
ON MetadataFiles.AlbumId = Albums.Id
|
ON ""MetadataFiles"".""AlbumId"" = ""Albums"".""Id""
|
||||||
WHERE MetadataFiles.AlbumId > 0
|
WHERE ""MetadataFiles"".""AlbumId"" > 0
|
||||||
AND Albums.Id IS NULL)");
|
AND ""Albums"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,13 +52,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
SELECT ""MetadataFiles"".""Id"" FROM ""MetadataFiles""
|
||||||
LEFT OUTER JOIN TrackFiles
|
LEFT OUTER JOIN ""TrackFiles""
|
||||||
ON MetadataFiles.TrackFileId = TrackFiles.Id
|
ON ""MetadataFiles"".""TrackFileId"" = ""TrackFiles"".""Id""
|
||||||
WHERE MetadataFiles.TrackFileId > 0
|
WHERE ""MetadataFiles"".""TrackFileId"" > 0
|
||||||
AND TrackFiles.Id IS NULL)");
|
AND ""TrackFiles"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,11 +66,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT ""Id"" FROM ""MetadataFiles""
|
||||||
WHERE Type IN (4, 6)
|
WHERE ""Type"" IN (4, 6)
|
||||||
AND AlbumId = 0)");
|
AND ""AlbumId"" = 0)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,11 +78,11 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
mapper.Execute(@"DELETE FROM ""MetadataFiles""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Id FROM MetadataFiles
|
SELECT ""Id"" FROM ""MetadataFiles""
|
||||||
WHERE Type IN (2, 5)
|
WHERE ""Type"" IN (2, 5)
|
||||||
AND TrackFileId = 0)");
|
AND ""TrackFileId"" = 0)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM PendingReleases
|
mapper.Execute(@"DELETE FROM ""PendingReleases""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT PendingReleases.Id FROM PendingReleases
|
SELECT ""PendingReleases"".""Id"" FROM ""PendingReleases""
|
||||||
LEFT OUTER JOIN Artists
|
LEFT OUTER JOIN ""Artists""
|
||||||
ON PendingReleases.ArtistId = Artists.Id
|
ON ""PendingReleases"".""ArtistId"" = ""Artists"".""Id""
|
||||||
WHERE Artists.Id IS NULL)");
|
WHERE ""Artists"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM AlbumReleases
|
mapper.Execute(@"DELETE FROM ""AlbumReleases""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT AlbumReleases.Id FROM AlbumReleases
|
SELECT ""AlbumReleases"".""Id"" FROM ""AlbumReleases""
|
||||||
LEFT OUTER JOIN Albums
|
LEFT OUTER JOIN ""Albums""
|
||||||
ON AlbumReleases.AlbumId = Albums.Id
|
ON ""AlbumReleases"".""AlbumId"" = ""Albums"".""Id""
|
||||||
WHERE Albums.Id IS NULL)");
|
WHERE ""Albums"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,22 +17,22 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
// Unlink where track no longer exists
|
// Unlink where track no longer exists
|
||||||
mapper.Execute(@"UPDATE TrackFiles
|
mapper.Execute(@"UPDATE ""TrackFiles""
|
||||||
SET AlbumId = 0
|
SET ""AlbumId"" = 0
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT TrackFiles.Id FROM TrackFiles
|
SELECT ""TrackFiles"".""Id"" FROM ""TrackFiles""
|
||||||
LEFT OUTER JOIN Tracks
|
LEFT OUTER JOIN ""Tracks""
|
||||||
ON TrackFiles.Id = Tracks.TrackFileId
|
ON ""TrackFiles"".""Id"" = ""Tracks"".""TrackFileId""
|
||||||
WHERE Tracks.Id IS NULL)");
|
WHERE ""Tracks"".""Id"" IS NULL)");
|
||||||
|
|
||||||
// Unlink Tracks where the Trackfiles entry no longer exists
|
// Unlink Tracks where the Trackfiles entry no longer exists
|
||||||
mapper.Execute(@"UPDATE Tracks
|
mapper.Execute(@"UPDATE ""Tracks""
|
||||||
SET TrackFileId = 0
|
SET ""TrackFileId"" = 0
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Tracks.Id FROM Tracks
|
SELECT ""Tracks"".""Id"" FROM ""Tracks""
|
||||||
LEFT OUTER JOIN TrackFiles
|
LEFT OUTER JOIN ""TrackFiles""
|
||||||
ON Tracks.TrackFileId = TrackFiles.Id
|
ON ""Tracks"".""TrackFileId"" = ""TrackFiles"".""Id""
|
||||||
WHERE TrackFiles.Id IS NULL)");
|
WHERE ""TrackFiles"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
{
|
{
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"DELETE FROM Tracks
|
mapper.Execute(@"DELETE FROM ""Tracks""
|
||||||
WHERE Id IN (
|
WHERE ""Id"" IN (
|
||||||
SELECT Tracks.Id FROM Tracks
|
SELECT ""Tracks"".""Id"" FROM ""Tracks""
|
||||||
LEFT OUTER JOIN AlbumReleases
|
LEFT OUTER JOIN ""AlbumReleases""
|
||||||
ON Tracks.AlbumReleaseId = AlbumReleases.Id
|
ON ""Tracks"".""AlbumReleaseId"" = ""AlbumReleases"".""Id""
|
||||||
WHERE AlbumReleases.Id IS NULL)");
|
WHERE ""AlbumReleases"".""Id"" IS NULL)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
@ -24,15 +25,29 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
|
if (usedTags.Any())
|
||||||
|
{
|
||||||
|
var usedTagsList = usedTags.Select(d => d.ToString()).Join(",");
|
||||||
|
|
||||||
mapper.Execute($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})");
|
if (_database.DatabaseType == DatabaseType.PostgreSQL)
|
||||||
|
{
|
||||||
|
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" = ANY (\'{{{usedTagsList}}}\'::int[])");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mapper.Execute($"DELETE FROM \"Tags\" WHERE NOT \"Id\" IN ({usedTagsList})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mapper.Execute("DELETE FROM \"Tags\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] GetUsedTags(string table, IDbConnection mapper)
|
private int[] GetUsedTags(string table, IDbConnection mapper)
|
||||||
{
|
{
|
||||||
return mapper.Query<List<int>>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]' AND NOT Tags IS NULL")
|
return mapper.Query<List<int>>($"SELECT DISTINCT \"Tags\" FROM \"{table}\" WHERE NOT \"Tags\" = '[]' AND NOT \"Tags\" IS NULL")
|
||||||
.SelectMany(x => x)
|
.SelectMany(x => x)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
|
@ -26,9 +26,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
|
||||||
using (var mapper = _database.OpenConnection())
|
using (var mapper = _database.OpenConnection())
|
||||||
{
|
{
|
||||||
mapper.Execute(@"UPDATE ScheduledTasks
|
mapper.Execute(@"UPDATE ""ScheduledTasks""
|
||||||
SET LastExecution = @time
|
SET ""LastExecution"" = @time
|
||||||
WHERE LastExecution > @time",
|
WHERE ""LastExecution"" > @time",
|
||||||
new { time = DateTime.UtcNow });
|
new { time = DateTime.UtcNow });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using System.Data;
|
using System;
|
||||||
|
using System.Data;
|
||||||
using System.Data.SQLite;
|
using System.Data.SQLite;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Common;
|
using NLog.Common;
|
||||||
using NLog.Config;
|
using NLog.Config;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
|
using Npgsql;
|
||||||
using NzbDrone.Common.Instrumentation;
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
@ -13,7 +15,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||||
{
|
{
|
||||||
public class DatabaseTarget : TargetWithLayout, IHandle<ApplicationShutdownRequested>
|
public class DatabaseTarget : TargetWithLayout, IHandle<ApplicationShutdownRequested>
|
||||||
{
|
{
|
||||||
private const string INSERT_COMMAND = "INSERT INTO [Logs]([Message],[Time],[Logger],[Exception],[ExceptionType],[Level]) " +
|
private const string INSERT_COMMAND = "INSERT INTO \"Logs\" (\"Message\",\"Time\",\"Logger\",\"Exception\",\"ExceptionType\",\"Level\") " +
|
||||||
"VALUES(@Message,@Time,@Logger,@Exception,@ExceptionType,@Level)";
|
"VALUES(@Message,@Time,@Logger,@Exception,@ExceptionType,@Level)";
|
||||||
|
|
||||||
private readonly IConnectionStringFactory _connectionStringFactory;
|
private readonly IConnectionStringFactory _connectionStringFactory;
|
||||||
|
@ -55,7 +57,6 @@ namespace NzbDrone.Core.Instrumentation
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = new SQLiteConnection(_connectionStringFactory.LogDbConnectionString).OpenAndReturn();
|
|
||||||
var log = new Log();
|
var log = new Log();
|
||||||
log.Time = logEvent.TimeStamp;
|
log.Time = logEvent.TimeStamp;
|
||||||
log.Message = CleanseLogMessage.Cleanse(logEvent.FormattedMessage);
|
log.Message = CleanseLogMessage.Cleanse(logEvent.FormattedMessage);
|
||||||
|
@ -84,21 +85,64 @@ namespace NzbDrone.Core.Instrumentation
|
||||||
|
|
||||||
log.Level = logEvent.Level.Name;
|
log.Level = logEvent.Level.Name;
|
||||||
|
|
||||||
var sqlCommand = new SQLiteCommand(INSERT_COMMAND, connection);
|
var connectionString = _connectionStringFactory.LogDbConnectionString;
|
||||||
|
|
||||||
|
//TODO: Probably need more robust way to differentiate what's being used
|
||||||
|
if (connectionString.Contains(".db"))
|
||||||
|
{
|
||||||
|
WriteSqliteLog(log, connectionString);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WritePostgresLog(log, connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLiteException ex)
|
||||||
|
{
|
||||||
|
InternalLogger.Error(ex, "Unable to save log event to database");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WritePostgresLog(Log log, string connectionString)
|
||||||
|
{
|
||||||
|
using (var connection =
|
||||||
|
new NpgsqlConnection(connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
using (var sqlCommand = connection.CreateCommand())
|
||||||
|
{
|
||||||
|
sqlCommand.CommandText = INSERT_COMMAND;
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("Message", DbType.String) { Value = log.Message });
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("Logger", DbType.String) { Value = log.Logger });
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("Exception", DbType.String) { Value = log.Exception == null ? DBNull.Value : log.Exception });
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("ExceptionType", DbType.String) { Value = log.ExceptionType == null ? DBNull.Value : log.ExceptionType });
|
||||||
|
sqlCommand.Parameters.Add(new NpgsqlParameter("Level", DbType.String) { Value = log.Level });
|
||||||
|
|
||||||
|
sqlCommand.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSqliteLog(Log log, string connectionString)
|
||||||
|
{
|
||||||
|
using (var connection =
|
||||||
|
SQLiteFactory.Instance.CreateConnection())
|
||||||
|
{
|
||||||
|
connection.ConnectionString = connectionString;
|
||||||
|
connection.Open();
|
||||||
|
using (var sqlCommand = connection.CreateCommand())
|
||||||
|
{
|
||||||
|
sqlCommand.CommandText = INSERT_COMMAND;
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message });
|
sqlCommand.Parameters.Add(new SQLiteParameter("Message", DbType.String) { Value = log.Message });
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
|
sqlCommand.Parameters.Add(new SQLiteParameter("Time", DbType.DateTime) { Value = log.Time.ToUniversalTime() });
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger });
|
sqlCommand.Parameters.Add(new SQLiteParameter("Logger", DbType.String) { Value = log.Logger });
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception });
|
sqlCommand.Parameters.Add(new SQLiteParameter("Exception", DbType.String) { Value = log.Exception });
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType });
|
sqlCommand.Parameters.Add(new SQLiteParameter("ExceptionType", DbType.String) { Value = log.ExceptionType });
|
||||||
sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level });
|
sqlCommand.Parameters.Add(new SQLiteParameter("Level", DbType.String) { Value = log.Level });
|
||||||
|
|
||||||
sqlCommand.ExecuteNonQuery();
|
sqlCommand.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
catch (SQLiteException ex)
|
|
||||||
{
|
|
||||||
InternalLogger.Error(ex, "Unable to save log event to database");
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||||
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
<PackageReference Include="FluentMigrator.Runner" Version="3.3.2" />
|
||||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.2" />
|
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="3.3.2" />
|
||||||
|
<PackageReference Include="FluentMigrator.Runner.Postgres" Version="3.3.2" />
|
||||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
|
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
|
||||||
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" />
|
<PackageReference Include="TagLibSharp-Lidarr" Version="2.2.0.27" />
|
||||||
<PackageReference Include="Kveer.XmlRPC" Version="1.2.0" />
|
<PackageReference Include="Kveer.XmlRPC" Version="1.2.0" />
|
||||||
|
<PackageReference Include="Npgsql" Version="6.0.3" />
|
||||||
<PackageReference Include="SpotifyAPI.Web" Version="4.2.2" />
|
<PackageReference Include="SpotifyAPI.Web" Version="4.2.2" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||||
<PackageReference Include="Equ" Version="2.3.0" />
|
<PackageReference Include="Equ" Version="2.3.0" />
|
||||||
|
|
|
@ -152,6 +152,7 @@
|
||||||
"CustomFilters": "Custom Filters",
|
"CustomFilters": "Custom Filters",
|
||||||
"CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums",
|
"CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums",
|
||||||
"CutoffUnmet": "Cutoff Unmet",
|
"CutoffUnmet": "Cutoff Unmet",
|
||||||
|
"Database": "Database",
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
"DateAdded": "Date Added",
|
"DateAdded": "Date Added",
|
||||||
"Dates": "Dates",
|
"Dates": "Dates",
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
// always join with all the other good stuff
|
// always join with all the other good stuff
|
||||||
// needed more often than not so better to load it all now
|
// needed more often than not so better to load it all now
|
||||||
protected override SqlBuilder Builder() => new SqlBuilder()
|
protected override SqlBuilder Builder() => new SqlBuilder(_database.DatabaseType)
|
||||||
.LeftJoin<TrackFile, Track>((t, x) => t.Id == x.TrackFileId)
|
.LeftJoin<TrackFile, Track>((t, x) => t.Id == x.TrackFileId)
|
||||||
.LeftJoin<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
.LeftJoin<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
||||||
.LeftJoin<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
.LeftJoin<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||||
|
@ -90,7 +90,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
//x.Id == null is converted to SQL, so warning incorrect
|
//x.Id == null is converted to SQL, so warning incorrect
|
||||||
#pragma warning disable CS0472
|
#pragma warning disable CS0472
|
||||||
return _database.Query<TrackFile>(new SqlBuilder().Select(typeof(TrackFile))
|
return _database.Query<TrackFile>(new SqlBuilder(_database.DatabaseType).Select(typeof(TrackFile))
|
||||||
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
|
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
|
||||||
.Where<Track>(t => t.Id == null)).ToList();
|
.Where<Track>(t => t.Id == null)).ToList();
|
||||||
#pragma warning restore CS0472
|
#pragma warning restore CS0472
|
||||||
|
@ -117,7 +117,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
// ensure path ends with a single trailing path separator to avoid matching partial paths
|
// ensure path ends with a single trailing path separator to avoid matching partial paths
|
||||||
var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||||
return _database.Query<TrackFile>(new SqlBuilder().Where<TrackFile>(x => x.Path.StartsWith(safePath))).ToList();
|
return _database.Query<TrackFile>(new SqlBuilder(_database.DatabaseType).Where<TrackFile>(x => x.Path.StartsWith(safePath))).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackFile GetFileWithPath(string path)
|
public TrackFile GetFileWithPath(string path)
|
||||||
|
@ -128,7 +128,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
public List<TrackFile> GetFileWithPath(List<string> paths)
|
public List<TrackFile> GetFileWithPath(List<string> paths)
|
||||||
{
|
{
|
||||||
// use more limited join for speed
|
// use more limited join for speed
|
||||||
var builder = new SqlBuilder()
|
var builder = new SqlBuilder(_database.DatabaseType)
|
||||||
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId);
|
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId);
|
||||||
|
|
||||||
var dict = new Dictionary<int, TrackFile>();
|
var dict = new Dictionary<int, TrackFile>();
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue