diff --git a/src/NuGet.Services.AzureSearch/DependencyInjectionExtensions.cs b/src/NuGet.Services.AzureSearch/DependencyInjectionExtensions.cs index a42be7c99..c2179dc83 100644 --- a/src/NuGet.Services.AzureSearch/DependencyInjectionExtensions.cs +++ b/src/NuGet.Services.AzureSearch/DependencyInjectionExtensions.cs @@ -217,6 +217,7 @@ public static IServiceCollection AddAzureSearch(this IServiceCollection services services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/NuGet.Services.AzureSearch/NuGet.Services.AzureSearch.csproj b/src/NuGet.Services.AzureSearch/NuGet.Services.AzureSearch.csproj index ef2f4c77f..d64ad9490 100644 --- a/src/NuGet.Services.AzureSearch/NuGet.Services.AzureSearch.csproj +++ b/src/NuGet.Services.AzureSearch/NuGet.Services.AzureSearch.csproj @@ -47,6 +47,8 @@ + + diff --git a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/DatabaseOwnerFetcher.cs b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/DatabaseOwnerFetcher.cs new file mode 100644 index 000000000..8295ae539 --- /dev/null +++ b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/DatabaseOwnerFetcher.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using NuGet.Jobs; +using NuGet.Jobs.Configuration; + +namespace NuGet.Services.AzureSearch.Owners2AzureSearch +{ + public class DatabaseOwnerFetcher : IDatabaseOwnerFetcher + { + private readonly ISqlConnectionFactory _connectionFactory; + private readonly ILogger _logger; + + private const string Sql = @" +SELECT + pr.Id, + u.Username +FROM PackageRegistrations pr (NOLOCK) +INNER JOIN PackageRegistrationOwners pro (NOLOCK) ON pro.PackageRegistrationKey = pr.[Key] +INNER JOIN Users u (NOLOCK) ON pro.UserKey = u.[Key] +"; + + public DatabaseOwnerFetcher( + ISqlConnectionFactory connectionFactory, + ILogger logger) + { + _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); + _logger = logger; + } + + public async Task>> GetPackageIdToOwnersAsync() + { + using (var connection = await _connectionFactory.OpenAsync()) + using (var command = connection.CreateCommand()) + { + command.CommandText = Sql; + + using (var reader = await command.ExecuteReaderAsync()) + { + var builder = new PackageIdToOwnersBuilder(_logger); + while (await reader.ReadAsync()) + { + var id = reader.GetString(0); + var username = reader.GetString(1); + + builder.Add(id, username); + } + + return builder.GetResult(); + } + } + } + } +} + diff --git a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IDatabaseOwnerFetcher.cs b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IDatabaseOwnerFetcher.cs new file mode 100644 index 000000000..ff4e45c92 --- /dev/null +++ b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IDatabaseOwnerFetcher.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NuGet.Services.AzureSearch.Owners2AzureSearch +{ + /// + /// Fetches the current owner information from the database. + /// + public interface IDatabaseOwnerFetcher + { + /// + /// Fetch a mapping from package ID to set of owners for each package registration (i.e. package ID) in the + /// gallery database. + /// + Task>> GetPackageIdToOwnersAsync(); + } +} \ No newline at end of file diff --git a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IOwnerSetComparer.cs b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IOwnerSetComparer.cs index a0cb3af24..4b1162e0a 100644 --- a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IOwnerSetComparer.cs +++ b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/IOwnerSetComparer.cs @@ -5,8 +5,18 @@ namespace NuGet.Services.AzureSearch.Owners2AzureSearch { + /// + /// Used to compare two sets of owners to determine the changes. + /// public interface IOwnerSetComparer { + /// + /// Compares two sets of owners to determine the package IDs that have changed. The returned dictionary + /// contains owners that were added, removed, and changed. For the "added" and "changed" cases, the owner set + /// is the new data. For the "removed" case, the set is empty. + /// + /// The old owner information, typically from storage. + /// The new owner information, typically from gallery DB. SortedDictionary Compare( SortedDictionary> oldData, SortedDictionary> newData); diff --git a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/OwnerDataClient.cs b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/OwnerDataClient.cs index 5e0e9b343..a1d25b855 100644 --- a/src/NuGet.Services.AzureSearch/Owners2AzureSearch/OwnerDataClient.cs +++ b/src/NuGet.Services.AzureSearch/Owners2AzureSearch/OwnerDataClient.cs @@ -20,13 +20,13 @@ public class OwnerDataClient : IOwnerDataClient private readonly ICloudBlobClient _cloudBlobClient; private readonly IOptionsSnapshot _options; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Lazy _lazyContainer; public OwnerDataClient( ICloudBlobClient cloudBlobClient, IOptionsSnapshot options, - ILogger logger) + ILogger logger) { _cloudBlobClient = cloudBlobClient ?? throw new ArgumentNullException(nameof(cloudBlobClient)); _options = options ?? throw new ArgumentNullException(nameof(cloudBlobClient)); diff --git a/tests/NuGet.Services.AzureSearch.Tests/Owners2AzureSearch/OwnerDataClientFacts.cs b/tests/NuGet.Services.AzureSearch.Tests/Owners2AzureSearch/OwnerDataClientFacts.cs index 38baec372..f284ba39f 100644 --- a/tests/NuGet.Services.AzureSearch.Tests/Owners2AzureSearch/OwnerDataClientFacts.cs +++ b/tests/NuGet.Services.AzureSearch.Tests/Owners2AzureSearch/OwnerDataClientFacts.cs @@ -379,7 +379,7 @@ public Facts(ITestOutputHelper output) CloudBlobContainer = new Mock(); CloudBlob = new Mock(); Options = new Mock>(); - Logger = output.GetLogger(); + Logger = output.GetLogger(); Config = new AzureSearchJobConfiguration { StorageContainer = "unit-test-container", @@ -422,7 +422,7 @@ public Facts(ITestOutputHelper output) public Mock CloudBlobContainer { get; } public Mock CloudBlob { get; } public Mock> Options { get; } - public RecordingLogger Logger { get; } + public RecordingLogger Logger { get; } public AzureSearchJobConfiguration Config { get; } public string ETag { get; } public Mock AccessCondition { get; }