2019-01-24 08:43:03 +00:00
|
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using osu.Framework.Logging;
|
2019-07-02 04:40:40 +00:00
|
|
|
|
using osu.Framework.Statistics;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
using osu.Game.Beatmaps;
|
|
|
|
|
using osu.Game.Configuration;
|
|
|
|
|
using osu.Game.IO;
|
|
|
|
|
using osu.Game.Rulesets;
|
2018-11-28 07:47:10 +00:00
|
|
|
|
using osu.Game.Scoring;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
using osu.Game.Skinning;
|
2022-02-15 04:41:10 +00:00
|
|
|
|
using SQLitePCL;
|
|
|
|
|
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Database
|
|
|
|
|
{
|
|
|
|
|
public class OsuDbContext : DbContext
|
|
|
|
|
{
|
2021-11-19 09:59:14 +00:00
|
|
|
|
public DbSet<EFBeatmapInfo> EFBeatmapInfo { get; set; }
|
|
|
|
|
public DbSet<EFBeatmapDifficulty> BeatmapDifficulty { get; set; }
|
|
|
|
|
public DbSet<EFBeatmapMetadata> BeatmapMetadata { get; set; }
|
|
|
|
|
public DbSet<EFBeatmapSetInfo> EFBeatmapSetInfo { get; set; }
|
2018-04-13 09:19:50 +00:00
|
|
|
|
public DbSet<FileInfo> FileInfo { get; set; }
|
2021-11-19 09:59:14 +00:00
|
|
|
|
public DbSet<EFRulesetInfo> RulesetInfo { get; set; }
|
2021-11-23 06:31:07 +00:00
|
|
|
|
public DbSet<EFSkinInfo> SkinInfo { get; set; }
|
2021-12-06 06:31:40 +00:00
|
|
|
|
public DbSet<EFScoreInfo> ScoreInfo { get; set; }
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
2021-01-14 07:31:18 +00:00
|
|
|
|
// migrated to realm
|
2021-09-15 06:22:43 +00:00
|
|
|
|
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
|
2021-01-14 07:31:18 +00:00
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
private readonly string connectionString;
|
|
|
|
|
|
|
|
|
|
private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory());
|
|
|
|
|
|
2019-07-02 04:40:40 +00:00
|
|
|
|
private static readonly GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
|
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
static OsuDbContext()
|
|
|
|
|
{
|
|
|
|
|
// required to initialise native SQLite libraries on some platforms.
|
2022-02-15 04:41:10 +00:00
|
|
|
|
Batteries_V2.Init();
|
2019-07-06 03:32:16 +00:00
|
|
|
|
|
|
|
|
|
// https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678
|
2022-02-15 04:41:10 +00:00
|
|
|
|
raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/);
|
2018-04-13 09:19:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create a new in-memory OsuDbContext instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public OsuDbContext()
|
|
|
|
|
: this("DataSource=:memory:")
|
|
|
|
|
{
|
|
|
|
|
// required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0).
|
|
|
|
|
|
|
|
|
|
Migrate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Create a new OsuDbContext instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="connectionString">A valid SQLite connection string.</param>
|
|
|
|
|
public OsuDbContext(string connectionString)
|
|
|
|
|
{
|
|
|
|
|
this.connectionString = connectionString;
|
|
|
|
|
|
|
|
|
|
var connection = Database.GetDbConnection();
|
2019-04-01 03:16:05 +00:00
|
|
|
|
|
2018-06-03 17:07:02 +00:00
|
|
|
|
try
|
2018-04-13 09:19:50 +00:00
|
|
|
|
{
|
2018-06-03 17:07:02 +00:00
|
|
|
|
connection.Open();
|
|
|
|
|
|
|
|
|
|
using (var cmd = connection.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
cmd.CommandText = "PRAGMA journal_mode=WAL;";
|
|
|
|
|
cmd.ExecuteNonQuery();
|
2021-07-05 06:41:48 +00:00
|
|
|
|
|
|
|
|
|
cmd.CommandText = "PRAGMA foreign_keys=OFF;";
|
|
|
|
|
cmd.ExecuteNonQuery();
|
2018-06-03 17:07:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-25 08:36:17 +00:00
|
|
|
|
catch
|
2018-06-03 17:07:02 +00:00
|
|
|
|
{
|
|
|
|
|
connection.Close();
|
|
|
|
|
throw;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
}
|
2019-07-02 04:40:40 +00:00
|
|
|
|
|
|
|
|
|
contexts.Value++;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 05:07:52 +00:00
|
|
|
|
~OsuDbContext()
|
|
|
|
|
{
|
|
|
|
|
// DbContext does not contain a finalizer (https://github.com/aspnet/EntityFrameworkCore/issues/8872)
|
|
|
|
|
// This is used to clean up previous contexts when fresh contexts are exposed via DatabaseContextFactory
|
|
|
|
|
Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-02 04:40:40 +00:00
|
|
|
|
private bool isDisposed;
|
|
|
|
|
|
|
|
|
|
public override void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (isDisposed) return;
|
|
|
|
|
|
|
|
|
|
isDisposed = true;
|
|
|
|
|
|
|
|
|
|
base.Dispose();
|
|
|
|
|
|
|
|
|
|
contexts.Value--;
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
|
|
|
{
|
|
|
|
|
base.OnConfiguring(optionsBuilder);
|
|
|
|
|
optionsBuilder
|
2021-03-21 10:01:06 +00:00
|
|
|
|
// this is required for the time being due to the way we are querying in places like BeatmapStore.
|
|
|
|
|
// if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled.
|
|
|
|
|
.UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10))
|
2018-04-13 09:19:50 +00:00
|
|
|
|
.UseLoggerFactory(logger.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
|
|
|
{
|
|
|
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
|
|
2021-11-19 09:59:14 +00:00
|
|
|
|
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
|
|
|
|
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.MD5Hash);
|
|
|
|
|
modelBuilder.Entity<EFBeatmapInfo>().HasIndex(b => b.Hash);
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
2021-11-19 09:59:14 +00:00
|
|
|
|
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
|
|
|
|
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.DeletePending);
|
|
|
|
|
modelBuilder.Entity<EFBeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
2021-11-23 06:31:07 +00:00
|
|
|
|
modelBuilder.Entity<EFSkinInfo>().HasIndex(b => b.Hash).IsUnique();
|
|
|
|
|
modelBuilder.Entity<EFSkinInfo>().HasIndex(b => b.DeletePending);
|
2021-11-30 06:17:16 +00:00
|
|
|
|
modelBuilder.Entity<EFSkinInfo>().HasMany(s => s.Files).WithOne(f => f.SkinInfo);
|
2018-11-28 10:15:56 +00:00
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
modelBuilder.Entity<DatabasedSetting>().HasIndex(b => new { b.RulesetID, b.Variant });
|
|
|
|
|
|
|
|
|
|
modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique();
|
|
|
|
|
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
|
|
|
|
|
|
2021-11-19 09:59:14 +00:00
|
|
|
|
modelBuilder.Entity<EFRulesetInfo>().HasIndex(b => b.Available);
|
|
|
|
|
modelBuilder.Entity<EFRulesetInfo>().HasIndex(b => b.ShortName).IsUnique();
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
2021-11-19 09:59:14 +00:00
|
|
|
|
modelBuilder.Entity<EFBeatmapInfo>().HasOne(b => b.BaseDifficulty);
|
2018-11-28 09:52:57 +00:00
|
|
|
|
|
2021-12-14 15:31:35 +00:00
|
|
|
|
modelBuilder.Entity<EFScoreInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
2018-04-13 09:19:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class OsuDbLoggerFactory : ILoggerFactory
|
|
|
|
|
{
|
|
|
|
|
#region Disposal
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
|
|
|
|
|
|
|
|
|
|
public void AddProvider(ILoggerProvider provider)
|
|
|
|
|
{
|
|
|
|
|
// no-op. called by tooling.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class OsuDbLogger : ILogger
|
|
|
|
|
{
|
|
|
|
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
|
|
|
|
{
|
|
|
|
|
if (logLevel < LogLevel.Information)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Framework.Logging.LogLevel frameworkLogLevel;
|
|
|
|
|
|
|
|
|
|
switch (logLevel)
|
|
|
|
|
{
|
|
|
|
|
default:
|
|
|
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Debug;
|
|
|
|
|
break;
|
2019-04-01 03:16:05 +00:00
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
case LogLevel.Warning:
|
|
|
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Important;
|
|
|
|
|
break;
|
2019-04-01 03:16:05 +00:00
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
case LogLevel.Error:
|
|
|
|
|
case LogLevel.Critical:
|
|
|
|
|
frameworkLogLevel = Framework.Logging.LogLevel.Error;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsEnabled(LogLevel logLevel)
|
|
|
|
|
{
|
|
|
|
|
#if DEBUG_DATABASE
|
|
|
|
|
return logLevel > LogLevel.Debug;
|
|
|
|
|
#else
|
|
|
|
|
return logLevel > LogLevel.Information;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IDisposable BeginScope<TState>(TState state) => null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-15 04:38:42 +00:00
|
|
|
|
public void Migrate() => Database.Migrate();
|
2018-04-13 09:19:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|