diff --git a/src/ProjectReferences.Persisters.Primary.props b/src/ProjectReferences.Persisters.Primary.props
index 255b45ed5c..0841fa81c2 100644
--- a/src/ProjectReferences.Persisters.Primary.props
+++ b/src/ProjectReferences.Persisters.Primary.props
@@ -2,6 +2,7 @@
+
\ No newline at end of file
diff --git a/src/ServiceControl.Persistence.Sql/.editorconfig b/src/ServiceControl.Persistence.Sql/.editorconfig
new file mode 100644
index 0000000000..103f58ff80
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/.editorconfig
@@ -0,0 +1,3 @@
+[*.cs]
+dotnet_diagnostic.CA2007.severity = none
+dotnet_diagnostic.IDE0060.severity = none
diff --git a/src/ServiceControl.Persistence.Sql/NoOpArchiveMessages.cs b/src/ServiceControl.Persistence.Sql/NoOpArchiveMessages.cs
new file mode 100644
index 0000000000..86bd5c2cec
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpArchiveMessages.cs
@@ -0,0 +1,27 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ServiceControl.Persistence.Recoverability;
+using ServiceControl.Recoverability;
+
+class NoOpArchiveMessages : IArchiveMessages
+{
+ public Task ArchiveAllInGroup(string groupId) => Task.CompletedTask;
+
+ public Task UnarchiveAllInGroup(string groupId) => Task.CompletedTask;
+
+ public bool IsOperationInProgressFor(string groupId, ArchiveType archiveType) => false;
+
+ public bool IsArchiveInProgressFor(string groupId) => false;
+
+ public void DismissArchiveOperation(string groupId, ArchiveType archiveType)
+ {
+ }
+
+ public Task StartArchiving(string groupId, ArchiveType archiveType) => Task.CompletedTask;
+
+ public Task StartUnarchiving(string groupId, ArchiveType archiveType) => Task.CompletedTask;
+
+ public IEnumerable GetArchivalOperations() => [];
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpBodyStorage.cs b/src/ServiceControl.Persistence.Sql/NoOpBodyStorage.cs
new file mode 100644
index 0000000000..a04adf2be3
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpBodyStorage.cs
@@ -0,0 +1,10 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System.Threading.Tasks;
+using ServiceControl.Operations.BodyStorage;
+
+class NoOpBodyStorage : IBodyStorage
+{
+ public Task TryFetch(string bodyId) =>
+ Task.FromResult(new MessageBodyStreamResult { HasResult = false });
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpCustomChecksDataStore.cs b/src/ServiceControl.Persistence.Sql/NoOpCustomChecksDataStore.cs
new file mode 100644
index 0000000000..64ed378310
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpCustomChecksDataStore.cs
@@ -0,0 +1,22 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ServiceControl.Contracts.CustomChecks;
+using ServiceControl.CustomChecks;
+using ServiceControl.Persistence;
+using ServiceControl.Persistence.Infrastructure;
+
+class NoOpCustomChecksDataStore : ICustomChecksDataStore
+{
+ public Task UpdateCustomCheckStatus(CustomCheckDetail detail) =>
+ Task.FromResult(CheckStateChange.Unchanged);
+
+ public Task>> GetStats(PagingInfo paging, string status = null) =>
+ Task.FromResult(new QueryResult>([], QueryStatsInfo.Zero));
+
+ public Task DeleteCustomCheck(Guid id) => Task.CompletedTask;
+
+ public Task GetNumberOfFailedChecks() => Task.FromResult(0);
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpEndpointSettingsStore.cs b/src/ServiceControl.Persistence.Sql/NoOpEndpointSettingsStore.cs
new file mode 100644
index 0000000000..7ed0c634bc
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpEndpointSettingsStore.cs
@@ -0,0 +1,20 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using ServiceControl.Operations;
+using ServiceControl.Persistence;
+
+class NoOpEndpointSettingsStore : IEndpointSettingsStore
+{
+ public async IAsyncEnumerable GetAllEndpointSettings()
+ {
+ await Task.CompletedTask;
+ yield break;
+ }
+
+ public Task UpdateEndpointSettings(EndpointSettings settings, CancellationToken token) => Task.CompletedTask;
+
+ public Task Delete(string name, CancellationToken cancellationToken) => Task.CompletedTask;
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpErrorMessageDataStore.cs b/src/ServiceControl.Persistence.Sql/NoOpErrorMessageDataStore.cs
new file mode 100644
index 0000000000..2672518630
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpErrorMessageDataStore.cs
@@ -0,0 +1,161 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ServiceControl.CompositeViews.Messages;
+using ServiceControl.EventLog;
+using ServiceControl.MessageFailures;
+using ServiceControl.MessageFailures.Api;
+using ServiceControl.Notifications;
+using ServiceControl.Operations;
+using ServiceControl.Persistence;
+using ServiceControl.Persistence.Infrastructure;
+using ServiceControl.Recoverability;
+
+class NoOpErrorMessageDataStore : IErrorMessageDataStore
+{
+ static readonly QueryResult> EmptyMessagesViewResult =
+ new([], QueryStatsInfo.Zero);
+
+ static readonly QueryResult> EmptyFailedMessageViewResult =
+ new([], QueryStatsInfo.Zero);
+
+ static readonly QueryResult> EmptyFailureGroupViewResult =
+ new([], QueryStatsInfo.Zero);
+
+ static readonly QueryStatsInfo EmptyQueryStatsInfo = QueryStatsInfo.Zero;
+
+ public Task>> GetAllMessages(PagingInfo pagingInfo, SortInfo sortInfo,
+ bool includeSystemMessages, DateTimeRange timeSentRange = null) =>
+ Task.FromResult(EmptyMessagesViewResult);
+
+ public Task>> GetAllMessagesForEndpoint(string endpointName,
+ PagingInfo pagingInfo, SortInfo sortInfo, bool includeSystemMessages, DateTimeRange timeSentRange = null) =>
+ Task.FromResult(EmptyMessagesViewResult);
+
+ public Task>> GetAllMessagesByConversation(string conversationId,
+ PagingInfo pagingInfo, SortInfo sortInfo, bool includeSystemMessages) =>
+ Task.FromResult(EmptyMessagesViewResult);
+
+ public Task>> GetAllMessagesForSearch(string searchTerms, PagingInfo pagingInfo,
+ SortInfo sortInfo, DateTimeRange timeSentRange = null) =>
+ Task.FromResult(EmptyMessagesViewResult);
+
+ public Task>> SearchEndpointMessages(string endpointName, string searchKeyword,
+ PagingInfo pagingInfo, SortInfo sortInfo, DateTimeRange timeSentRange = null) =>
+ Task.FromResult(EmptyMessagesViewResult);
+
+ public Task FailedMessageMarkAsArchived(string failedMessageId) => Task.CompletedTask;
+
+ public Task FailedMessagesFetch(Guid[] ids) => Task.FromResult(Array.Empty());
+
+ public Task StoreFailedErrorImport(FailedErrorImport failure) => Task.CompletedTask;
+
+ public Task CreateEditFailedMessageManager() =>
+ Task.FromResult(new NoOpEditFailedMessagesManager());
+
+ public Task> GetFailureGroupView(string groupId, string status, string modified) =>
+ Task.FromResult(new QueryResult(null, EmptyQueryStatsInfo));
+
+ public Task> GetFailureGroupsByClassifier(string classifier) =>
+ Task.FromResult>([]);
+
+ public Task>> ErrorGet(string status, string modified, string queueAddress,
+ PagingInfo pagingInfo, SortInfo sortInfo) =>
+ Task.FromResult(EmptyFailedMessageViewResult);
+
+ public Task ErrorsHead(string status, string modified, string queueAddress) =>
+ Task.FromResult(EmptyQueryStatsInfo);
+
+ public Task>> ErrorsByEndpointName(string status, string endpointName,
+ string modified, PagingInfo pagingInfo, SortInfo sortInfo) =>
+ Task.FromResult(EmptyFailedMessageViewResult);
+
+ public Task> ErrorsSummary() =>
+ Task.FromResult>(new Dictionary());
+
+ public Task ErrorLastBy(string failedMessageId) =>
+ Task.FromResult(null);
+
+ public Task ErrorBy(string failedMessageId) =>
+ Task.FromResult(null);
+
+ public Task CreateNotificationsManager() =>
+ Task.FromResult(new NoOpNotificationsManager());
+
+ public Task EditComment(string groupId, string comment) => Task.CompletedTask;
+
+ public Task DeleteComment(string groupId) => Task.CompletedTask;
+
+ public Task>> GetGroupErrors(string groupId, string status, string modified,
+ SortInfo sortInfo, PagingInfo pagingInfo) =>
+ Task.FromResult(EmptyFailedMessageViewResult);
+
+ public Task GetGroupErrorsCount(string groupId, string status, string modified) =>
+ Task.FromResult(EmptyQueryStatsInfo);
+
+ public Task>> GetGroup(string groupId, string status, string modified) =>
+ Task.FromResult(EmptyFailureGroupViewResult);
+
+ public Task MarkMessageAsResolved(string failedMessageId) => Task.FromResult(false);
+
+ public Task ProcessPendingRetries(DateTime periodFrom, DateTime periodTo, string queueAddress,
+ Func processCallback) => Task.CompletedTask;
+
+ public Task UnArchiveMessagesByRange(DateTime from, DateTime to) =>
+ Task.FromResult(Array.Empty());
+
+ public Task UnArchiveMessages(IEnumerable failedMessageIds) =>
+ Task.FromResult(Array.Empty());
+
+ public Task RevertRetry(string messageUniqueId) => Task.CompletedTask;
+
+ public Task RemoveFailedMessageRetryDocument(string uniqueMessageId) => Task.CompletedTask;
+
+ public Task GetRetryPendingMessages(DateTime from, DateTime to, string queueAddress) =>
+ Task.FromResult(Array.Empty());
+
+ public Task FetchFromFailedMessage(string uniqueMessageId) =>
+ Task.FromResult(null);
+
+ public Task StoreEventLogItem(EventLogItem logItem) => Task.CompletedTask;
+
+ public Task StoreFailedMessagesForTestsOnly(params FailedMessage[] failedMessages) => Task.CompletedTask;
+
+ class NoOpEditFailedMessagesManager : IEditFailedMessagesManager
+ {
+ public void Dispose()
+ {
+ }
+
+ public Task GetFailedMessage(string failedMessageId) =>
+ Task.FromResult(null);
+
+ public Task GetCurrentEditingRequestId(string failedMessageId) =>
+ Task.FromResult(null);
+
+ public Task SetCurrentEditingRequestId(string editingMessageId) => Task.CompletedTask;
+
+ public Task SetFailedMessageAsResolved() => Task.CompletedTask;
+
+ public Task UpdateFailedMessageBody(string uniqueMessageId, byte[] newBody) => Task.CompletedTask;
+
+ public Task SaveChanges() => Task.CompletedTask;
+ }
+
+ class NoOpNotificationsManager : INotificationsManager
+ {
+ public void Dispose()
+ {
+ }
+
+ public Task LoadSettings(TimeSpan? cacheTimeout = null) =>
+ Task.FromResult(null);
+
+ public Task UpdateFailedMessageGroupDetails(string groupId, string title, FailedMessageStatus status) =>
+ Task.CompletedTask;
+
+ public Task SaveChanges() => Task.CompletedTask;
+ }
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpEventLogDataStore.cs b/src/ServiceControl.Persistence.Sql/NoOpEventLogDataStore.cs
new file mode 100644
index 0000000000..6df1410891
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpEventLogDataStore.cs
@@ -0,0 +1,15 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ServiceControl.EventLog;
+using ServiceControl.Persistence;
+using ServiceControl.Persistence.Infrastructure;
+
+class NoOpEventLogDataStore : IEventLogDataStore
+{
+ public Task Add(EventLogItem logItem) => Task.CompletedTask;
+
+ public Task<(IList items, long total, string version)> GetEventLogItems(PagingInfo pagingInfo) =>
+ Task.FromResult<(IList, long, string)>(([], 0, string.Empty));
+}
diff --git a/src/ServiceControl.Persistence.Sql/NoOpExternalIntegrationRequestsDataStore.cs b/src/ServiceControl.Persistence.Sql/NoOpExternalIntegrationRequestsDataStore.cs
new file mode 100644
index 0000000000..805176e820
--- /dev/null
+++ b/src/ServiceControl.Persistence.Sql/NoOpExternalIntegrationRequestsDataStore.cs
@@ -0,0 +1,20 @@
+namespace ServiceControl.Persistence.Sql;
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using ServiceControl.ExternalIntegrations;
+using ServiceControl.Persistence;
+
+class NoOpExternalIntegrationRequestsDataStore : IExternalIntegrationRequestsDataStore
+{
+ public void Subscribe(Func