blob: 153e1dc154cb6ffb1c79c2c2494fde38d7dc0c43 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Providers.Sqlite;
using Jellyfin.Server.Implementations.Tests.Item;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace Jellyfin.Server.Implementations.Tests.Data;
/// <summary>
/// Statistics taken on a freshly created database describe every table as a single row, and SQLite then plans
/// the user data and series queries of a filled library as full scans (#17886).
/// </summary>
public sealed class SqliteDatabaseStatisticsTests : SqliteDbTestFixture
{
private readonly SqliteDatabaseProvider _provider;
public SqliteDatabaseStatisticsTests()
{
_provider = new SqliteDatabaseProvider(ApplicationPaths, NullLogger<SqliteDatabaseProvider>.Instance)
{
DbContextFactory = CreateDbContextFactory()
};
}
[Fact]
public async Task RunScheduledOptimisation_EmptyLibrary_RecordsNoStatistics()
{
SeedFolders(3);
await _provider.RunScheduledOptimisation(CancellationToken.None);
Assert.Null(ReadAnalyzedItemCount());
}
[Fact]
public async Task RunScheduledOptimisation_LibraryWithItems_RecordsStatistics()
{
SeedFolders(1);
SeedEpisodes(4);
await _provider.RunScheduledOptimisation(CancellationToken.None);
Assert.Equal(CountItems(), ReadAnalyzedItemCount());
}
[Fact]
public async Task RefreshStatistics_NoStatistics_Analyzes()
{
SeedEpisodes(5);
await _provider.RefreshStatistics(CancellationToken.None);
Assert.Equal(CountItems(), ReadAnalyzedItemCount());
}
[Fact]
public async Task RefreshStatistics_LibraryChanged_Reanalyzes()
{
SeedEpisodes(10);
Analyze();
SeedEpisodes(5);
await _provider.RefreshStatistics(CancellationToken.None);
Assert.Equal(CountItems(), ReadAnalyzedItemCount());
}
[Fact]
public async Task RefreshStatistics_EmptyLibrary_RecordsNoStatistics()
{
SeedFolders(2);
await _provider.RefreshStatistics(CancellationToken.None);
Assert.Null(ReadAnalyzedItemCount());
}
private void SeedFolders(int count)
{
using var context = CreateDbContext();
context.BaseItems.AddRange(Enumerable.Range(0, count).Select(_ => new BaseItemEntity
{
Id = Guid.NewGuid(),
Type = "MediaBrowser.Controller.Entities.Folder",
IsFolder = true
}));
context.SaveChanges();
}
private void SeedEpisodes(int count)
{
using var context = CreateDbContext();
context.BaseItems.AddRange(Enumerable.Range(0, count).Select(_ => new BaseItemEntity
{
Id = Guid.NewGuid(),
Type = "MediaBrowser.Controller.Entities.TV.Episode",
IsFolder = false
}));
context.SaveChanges();
}
private void Analyze()
{
using var context = CreateDbContext();
context.Database.ExecuteSqlRaw("ANALYZE");
}
private long CountItems()
{
using var context = CreateDbContext();
return context.BaseItems.LongCount();
}
private long? ReadAnalyzedItemCount()
{
using var context = CreateDbContext();
var hasStatistics = context.Database
.SqlQueryRaw<long>("SELECT count(*) AS \"Value\" FROM sqlite_schema WHERE type = 'table' AND name = 'sqlite_stat1'")
.Single();
if (hasStatistics == 0)
{
return null;
}
return context.Database
.SqlQueryRaw<long?>("SELECT max(CAST(stat AS INTEGER)) AS \"Value\" FROM sqlite_stat1 WHERE tbl = 'BaseItems'")
.Single();
}
}
|