Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for retrieving submit/rank date from local metadata cache in version 2 #29553

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 119 additions & 30 deletions osu.Game/Beatmaps/LocalCachedBeatmapMetadataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ private bool shouldFetchCache()

public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata)
{
Debug.Assert(beatmapInfo.BeatmapSet != null);

if (!Available)
{
onlineMetadata = null;
Expand All @@ -94,43 +96,21 @@ public bool TryLookup(BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? online
return false;
}

Debug.Assert(beatmapInfo.BeatmapSet != null);

try
{
using (var db = new SqliteConnection(string.Concat(@"Data Source=", storage.GetFullPath(@"online.db", true))))
{
db.Open();

using (var cmd = db.CreateCommand())
switch (getCacheVersion(db))
{
cmd.CommandText =
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";

cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));

using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo}.");

onlineMetadata = new OnlineBeatmapMetadata
{
BeatmapSetID = reader.GetInt32(0),
BeatmapID = reader.GetInt32(1),
BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2),
BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2),
AuthorID = reader.GetInt32(3),
MD5Hash = reader.GetString(4),
LastUpdated = reader.GetDateTimeOffset(5),
// TODO: DateSubmitted and DateRanked are not provided by local cache.
};
return true;
}
}
case 1:
// will eventually become irrelevant due to the monthly recycling of local caches
// can be removed 20250221
return queryCacheVersion1(db, beatmapInfo, out onlineMetadata);

case 2:
return queryCacheVersion2(db, beatmapInfo, out onlineMetadata);
}
}
}
Expand Down Expand Up @@ -211,6 +191,115 @@ private void prepareLocalCache()
});
}

private int getCacheVersion(SqliteConnection connection)
{
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = @"SELECT COUNT(1) FROM `sqlite_master` WHERE `type` = 'table' AND `name` = 'schema_version'";

using var reader = cmd.ExecuteReader();

if (!reader.Read())
throw new InvalidOperationException("Error when attempting to check for existence of `schema_version` table.");

// No versioning table means that this is the very first version of the schema.
if (reader.GetInt32(0) == 0)
return 1;
}

using (var cmd = connection.CreateCommand())
{
cmd.CommandText = @"SELECT `number` FROM `schema_version`";

using var reader = cmd.ExecuteReader();

if (!reader.Read())
throw new InvalidOperationException("Error when attempting to query schema version.");

return reader.GetInt32(0);
}
}

private bool queryCacheVersion1(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata)
{
Debug.Assert(beatmapInfo.BeatmapSet != null);

using var cmd = db.CreateCommand();

cmd.CommandText =
@"SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";

cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));

using var reader = cmd.ExecuteReader();

if (reader.Read())
{
logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 1).");

onlineMetadata = new OnlineBeatmapMetadata
{
BeatmapSetID = reader.GetInt32(0),
BeatmapID = reader.GetInt32(1),
BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2),
BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2),
AuthorID = reader.GetInt32(3),
MD5Hash = reader.GetString(4),
LastUpdated = reader.GetDateTimeOffset(5),
// TODO: DateSubmitted and DateRanked are not provided by local cache in this version.
};
return true;
}

onlineMetadata = null;
return false;
}

private bool queryCacheVersion2(SqliteConnection db, BeatmapInfo beatmapInfo, out OnlineBeatmapMetadata? onlineMetadata)
{
Debug.Assert(beatmapInfo.BeatmapSet != null);

using var cmd = db.CreateCommand();

cmd.CommandText =
"""
SELECT `b`.`beatmapset_id`, `b`.`beatmap_id`, `b`.`approved`, `b`.`user_id`, `b`.`checksum`, `b`.`last_update`, `s`.`submit_date`, `s`.`approved_date`
FROM `osu_beatmaps` AS `b`
JOIN `osu_beatmapsets` AS `s` ON `s`.`beatmapset_id` = `b`.`beatmapset_id`
WHERE `b`.`checksum` = @MD5Hash OR `b`.`beatmap_id` = @OnlineID OR `b`.`filename` = @Path
""";
Comment on lines +267 to +272
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all I've ever wanted. Fixes all the issues with @"" quite well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'll point out which is more relevant for server code, is that this still puts lines on a new line. I've been told in the past the reason we use "x" \n + "y" is because it's sometimes filtered on via proxysql and that makes things easier somehow.

Otherwise I'm all for this syntax.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, generally when laying out like this I want the newlines in the SQL for logging purposes too. So this would always be something I consider during review.


cmd.Parameters.Add(new SqliteParameter(@"@MD5Hash", beatmapInfo.MD5Hash));
cmd.Parameters.Add(new SqliteParameter(@"@OnlineID", beatmapInfo.OnlineID));
cmd.Parameters.Add(new SqliteParameter(@"@Path", beatmapInfo.Path));

using var reader = cmd.ExecuteReader();

if (reader.Read())
{
logForModel(beatmapInfo.BeatmapSet, $@"Cached local retrieval for {beatmapInfo} (cache version 2).");

onlineMetadata = new OnlineBeatmapMetadata
{
BeatmapSetID = reader.GetInt32(0),
BeatmapID = reader.GetInt32(1),
BeatmapStatus = (BeatmapOnlineStatus)reader.GetByte(2),
BeatmapSetStatus = (BeatmapOnlineStatus)reader.GetByte(2),
AuthorID = reader.GetInt32(3),
MD5Hash = reader.GetString(4),
LastUpdated = reader.GetDateTimeOffset(5),
DateSubmitted = reader.GetDateTimeOffset(6),
DateRanked = reader.GetDateTimeOffset(7),
};
return true;
}

onlineMetadata = null;
return false;
}

private static void log(string message)
=> Logger.Log($@"[{nameof(LocalCachedBeatmapMetadataSource)}] {message}", LoggingTarget.Database);

Expand Down
Loading