diff --git a/src/EventStore.Projections.Core.Tests/Integration/specification_with_a_v8_query_posted.cs b/src/EventStore.Projections.Core.Tests/Integration/specification_with_a_v8_query_posted.cs index 5ead47ebba..de72f6fdaa 100644 --- a/src/EventStore.Projections.Core.Tests/Integration/specification_with_a_v8_query_posted.cs +++ b/src/EventStore.Projections.Core.Tests/Integration/specification_with_a_v8_query_posted.cs @@ -7,6 +7,7 @@ using EventStore.Projections.Core.Services; using EventStore.Projections.Core.Services.Processing; using EventStore.Projections.Core.Tests.Services.projections_manager; +using NUnit.Framework; namespace EventStore.Projections.Core.Tests.Integration; @@ -34,6 +35,10 @@ protected override void Given() _trackEmittedStreams = false; _emitEnabled = false; _startSystemProjections = GivenStartSystemProjections(); + if (!string.IsNullOrEmpty(_projectionSource)) + { + Assert.Ignore("Transient query projections are not supported."); + } } protected override Tuple[] GivenProcessingQueues() @@ -66,6 +71,8 @@ protected virtual bool GivenStartSystemProjections() protected Message CreateQueryMessage(string name, string source) { + Assert.Ignore("Transient query projections are not supported."); + return new ProjectionManagementMessage.Command.Post( _bus, ProjectionMode.Transient, name, ProjectionManagementMessage.RunAs.System, "JS", source, enabled: true, checkpointsEnabled: false, diff --git a/src/EventStore.Projections.Core.Tests/Services/Jint/Scenarios/specification_with_js_query_posted.cs b/src/EventStore.Projections.Core.Tests/Services/Jint/Scenarios/specification_with_js_query_posted.cs index f62a7ef350..63f9f89a34 100644 --- a/src/EventStore.Projections.Core.Tests/Services/Jint/Scenarios/specification_with_js_query_posted.cs +++ b/src/EventStore.Projections.Core.Tests/Services/Jint/Scenarios/specification_with_js_query_posted.cs @@ -8,6 +8,7 @@ using EventStore.Projections.Core.Services.Processing; using EventStore.Projections.Core.Tests.Services.projections_manager; +using NUnit.Framework; namespace EventStore.Projections.Core.Tests.Services.Jint.Scenarios; @@ -36,6 +37,10 @@ protected override void Given() _trackEmittedStreams = false; _emitEnabled = false; _startSystemProjections = GivenStartSystemProjections(); + if (!string.IsNullOrEmpty(_projectionSource)) + { + Assert.Ignore("Transient query projections are not supported."); + } } protected override Tuple[] GivenProcessingQueues() @@ -68,6 +73,8 @@ protected virtual bool GivenStartSystemProjections() protected Message CreateQueryMessage(string name, string source) { + Assert.Ignore("Transient query projections are not supported."); + return new ProjectionManagementMessage.Command.Post( _bus, ProjectionMode.Transient, name, ProjectionManagementMessage.RunAs.System, "JS", source, enabled: true, checkpointsEnabled: false, diff --git a/src/EventStore.Projections.Core.Tests/Services/grpc_service/ProjectionManagementParityTests.cs b/src/EventStore.Projections.Core.Tests/Services/grpc_service/ProjectionManagementParityTests.cs index e13d77c6f2..a47f35e422 100644 --- a/src/EventStore.Projections.Core.Tests/Services/grpc_service/ProjectionManagementParityTests.cs +++ b/src/EventStore.Projections.Core.Tests/Services/grpc_service/ProjectionManagementParityTests.cs @@ -31,6 +31,7 @@ public class ProjectionManagementParityTests : Specificat private GetConfigResp _updatedConfig; private ReadEventsResp _feedPage; private StatisticsResp.Types.Details[] _allNonTransientStatistics; + private RpcException _transientCreateFailure; public override async Task Given() { @@ -70,18 +71,6 @@ await _client.CreateAsync(new CreateReq } }, GetCallOptions()); - await _client.CreateAsync(new CreateReq - { - Options = new CreateReq.Types.Options - { - Transient = new CreateReq.Types.Options.Types.Transient - { - Name = TransientProjectionName - }, - Query = $"fromStream(\"{DebugStream}\").when({{$any:function(s,e){{return s;}}}});" - } - }, GetCallOptions()); - await PostEvent(DebugStream, "type1", "{\"value\":1}"); await PostEvent(DebugStream, "type2", "{\"value\":2}"); } @@ -153,13 +142,17 @@ await _client.UpdateConfigAsync(new UpdateConfigReq } }, GetCallOptions()); - await _client.AbortAsync(new AbortReq + _transientCreateFailure = Assert.ThrowsAsync(() => _client.CreateAsync(new CreateReq { - Options = new AbortReq.Types.Options + Options = new CreateReq.Types.Options { - Name = TransientProjectionName + Transient = new CreateReq.Types.Options.Types.Transient + { + Name = TransientProjectionName + }, + Query = $"fromStream(\"{DebugStream}\").when({{$any:function(s,e){{return s;}}}});" } - }, GetCallOptions()); + }, GetCallOptions()).ResponseAsync); _allNonTransientStatistics = await ReadStatisticsAsync(new StatisticsReq { @@ -212,11 +205,17 @@ public void read_events_returns_feed_page_details() } [Test] - public void all_non_transient_statistics_excludes_transient_projections() + public void create_rejects_transient_projections() + { + Assert.That(_transientCreateFailure.StatusCode, Is.EqualTo(StatusCode.FailedPrecondition)); + Assert.That(_transientCreateFailure.Status.Detail, Does.Contain("Transient projections are not supported")); + } + + [Test] + public void all_non_transient_statistics_returns_registered_non_transient_projections() { Assert.That(_allNonTransientStatistics.Any(x => x.Name == ProjectionName), Is.True); Assert.That(_allNonTransientStatistics.Any(x => x.Name == DisabledOneTimeProjectionName), Is.True); - Assert.That(_allNonTransientStatistics.Any(x => x.Name == TransientProjectionName), Is.False); } [OneTimeTearDown] diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/query/a_new_posted_projection.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/query/a_new_posted_projection.cs index 59db26633b..eafd8d1944 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/query/a_new_posted_projection.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/query/a_new_posted_projection.cs @@ -26,6 +26,7 @@ public abstract class Base : TestFixtureWithProjectionCor protected override void Given() { base.Given(); + Assert.Ignore("Transient query projections are not supported."); _projectionName = "test-projection"; _projectionSource = @""; diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/runas/when_posting_a_transient_projection.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/runas/when_posting_a_transient_projection.cs index 39ce302175..5d145645af 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/runas/when_posting_a_transient_projection.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/runas/when_posting_a_transient_projection.cs @@ -46,20 +46,16 @@ protected override IEnumerable When() checkpointsEnabled: true, emitEnabled: true, trackEmittedStreams: true, enableRunAs: true); } - [Test, Ignore("ignored")] - public void anonymous_cannot_retrieve_projection_query() + [Test] + public void the_operation_fails() { - GetInputQueue() - .Publish( - new ProjectionManagementMessage.Command.GetQuery( - Envelope, _projectionName, ProjectionManagementMessage.RunAs.Anonymous)); - _queue.Process(); + var failure = HandledMessages.OfType().Single(); - Assert.IsTrue(HandledMessages.OfType().Any()); + StringAssert.Contains("Transient projections are not supported", failure.Reason); } [Test] - public void projection_owner_can_retrieve_projection_query() + public void the_projection_is_not_registered() { GetInputQueue() .Publish( @@ -67,9 +63,7 @@ public void projection_owner_can_retrieve_projection_query() Envelope, _projectionName, new ProjectionManagementMessage.RunAs(_testUserPrincipal))); _queue.Process(); - var query = HandledMessages.OfType().FirstOrDefault(); - Assert.NotNull(query); - Assert.AreEqual(_projectionBody, query.Query); + Assert.IsTrue(HandledMessages.OfType().Any()); } } diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_projection.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_projection.cs new file mode 100644 index 0000000000..6dc7fb39df --- /dev/null +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_projection.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EventStore.Core.Tests; +using EventStore.Projections.Core.Messages; +using EventStore.Projections.Core.Services; +using NUnit.Framework; + +namespace EventStore.Projections.Core.Tests.Services.projections_manager; + +[TestFixture(typeof(LogFormat.V2), typeof(string))] +public class when_posting_a_transient_projection : TestFixtureWithProjectionCoreAndManagementServices +{ + private const string ProjectionName = "test-projection"; + private const string ProjectionBody = "fromAll().when({$any:function(s,e){return s;}});"; + + protected override void Given() + { + AllWritesSucceed(); + NoOtherStreams(); + } + + protected override IEnumerable When() + { + yield return new ProjectionSubsystemMessage.StartComponents(Guid.NewGuid()); + yield return new ProjectionManagementMessage.Command.Post( + GetInputQueue(), + ProjectionMode.Transient, + ProjectionName, + ProjectionManagementMessage.RunAs.System, + "JS", + ProjectionBody, + enabled: true, + checkpointsEnabled: false, + emitEnabled: false, + trackEmittedStreams: false); + } + + [Test, Category("v8")] + public void the_operation_fails() + { + var failure = HandledMessages.OfType().Single(); + + StringAssert.Contains("Transient projections are not supported", failure.Reason); + } + + [Test, Category("v8")] + public void the_projection_is_not_registered() + { + GetInputQueue().Publish(new ProjectionManagementMessage.Command.GetQuery( + Envelope, + ProjectionName, + ProjectionManagementMessage.RunAs.System)); + _queue.Process(); + + Assert.IsTrue(HandledMessages.OfType().Any()); + } +} diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_an_onetime_projection.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_query_projection.cs similarity index 80% rename from src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_an_onetime_projection.cs rename to src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_query_projection.cs index 9bf88112e1..eab9710fd8 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_an_onetime_projection.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_posting_a_transient_query_projection.cs @@ -9,7 +9,8 @@ namespace EventStore.Projections.Core.Tests.Services.projections_manager; [TestFixture(typeof(LogFormat.V2), typeof(string))] -public class when_posting_an_onetime_projection : TestFixtureWithProjectionCoreAndManagementServices +[Ignore("Transient query projections are not supported.")] +public class when_posting_a_transient_query_projection : TestFixtureWithProjectionCoreAndManagementServices { protected override void Given() { diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_onetime_projection_has_been_posted.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_transient_query_projection_has_been_posted.cs similarity index 93% rename from src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_onetime_projection_has_been_posted.cs rename to src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_transient_query_projection_has_been_posted.cs index b922af2fd4..fa94b2a9cb 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_onetime_projection_has_been_posted.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_the_transient_query_projection_has_been_posted.cs @@ -9,7 +9,8 @@ namespace EventStore.Projections.Core.Tests.Services.projections_manager; [TestFixture(typeof(LogFormat.V2), typeof(string))] -public class when_the_onetime_projection_has_been_posted : TestFixtureWithProjectionCoreAndManagementServices +[Ignore("Transient query projections are not supported.")] +public class when_the_transient_query_projection_has_been_posted : TestFixtureWithProjectionCoreAndManagementServices { private string _projectionName; private string _projectionQuery; diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_projection_query_text.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_query_projection_text.cs similarity index 93% rename from src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_projection_query_text.cs rename to src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_query_projection_text.cs index 864bc279bd..ebe4038c43 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_projection_query_text.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_query_projection_text.cs @@ -11,7 +11,8 @@ namespace EventStore.Projections.Core.Tests.Services.projections_manager; [TestFixture(typeof(LogFormat.V2), typeof(string))] -public class when_updating_an_onetime_projection_query_text : TestFixtureWithProjectionCoreAndManagementServices +[Ignore("Transient query projections are not supported.")] +public class when_updating_a_transient_query_projection_text : TestFixtureWithProjectionCoreAndManagementServices { private string _projectionName; private string _newProjectionSource; diff --git a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_system_projection_query_text.cs b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_system_query_projection_text.cs similarity index 93% rename from src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_system_projection_query_text.cs rename to src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_system_query_projection_text.cs index 049ea213eb..f1aeaea34a 100644 --- a/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_an_onetime_system_projection_query_text.cs +++ b/src/EventStore.Projections.Core.Tests/Services/projections_manager/when_updating_a_transient_system_query_projection_text.cs @@ -11,7 +11,8 @@ namespace EventStore.Projections.Core.Tests.Services.projections_manager; [TestFixture(typeof(LogFormat.V2), typeof(string))] -public class when_updating_an_onetime_system_projection_query_text : TestFixtureWithProjectionCoreAndManagementServices +[Ignore("Transient query projections are not supported.")] +public class when_updating_a_transient_system_query_projection_text : TestFixtureWithProjectionCoreAndManagementServices { private string _projectionName; private string _newProjectionSource; @@ -30,7 +31,6 @@ protected override IEnumerable When() _bus, ProjectionMode.Transient, _projectionName, ProjectionManagementMessage.RunAs.Anonymous, "native:EventStore.Projections.Core.Standard.ByCorrelationId", "{\"correlationIdProperty\":\"$myCorrelationId\"}", enabled: true, checkpointsEnabled: false, emitEnabled: false, trackEmittedStreams: true)); - // when _newProjectionSource = "{\"correlationIdProperty\":\"$updateCorrelationId\"}"; yield return (new ProjectionManagementMessage.Command.UpdateQuery( diff --git a/src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs b/src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs index 844f369e70..bb9501d97b 100644 --- a/src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs +++ b/src/EventStore.Projections.Core/Messages/ProjectionManagementMessage.cs @@ -129,7 +129,7 @@ public Post( _definitionMetadata = definitionMetadata ?? Empty.ByteArray; } - // shortcut for posting ad-hoc JS queries + // Kept so legacy ad-hoc JS callers fail through the transient projection boundary. public Post(IEnvelope envelope, RunAs runAs, string query, bool enabled) : base(envelope, runAs) { diff --git a/src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs b/src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs index 42840d7eda..273950ae7c 100644 --- a/src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs +++ b/src/EventStore.Projections.Core/Services/Management/ProjectionManager.cs @@ -298,13 +298,8 @@ public void Handle(ProjectionManagementMessage.Command.Post message) if (message.Mode == ProjectionMode.Transient) { - var transientProjection = new PendingProjection(ProjectionQueryId, message); - if (!ValidateProjections(new[] { transientProjection }, message)) - { - return; - } - - PostNewTransientProjection(transientProjection, message.Envelope); + message.Envelope.ReplyWith(new ProjectionManagementMessage.OperationFailed( + "Transient projections are not supported.")); } else { @@ -1228,14 +1223,6 @@ private ProjectionManagementMessage.Command.PostBatch.ProjectionPost CreateSyste enableRunAs: true); } - private void PostNewTransientProjection(PendingProjection projection, IEnvelope replyEnvelope) - { - var initializer = projection.CreateInitializer(replyEnvelope); - ReadProjectionPossibleStream(projection.Name, - m => ReadProjectionPossibleStreamCompleted - (m, initializer, replyEnvelope)); - } - private void PostNewProjections (IDictionary newProjections, long expectedVersion, IEnvelope replyEnvelope) { diff --git a/src/EventStore.Projections.Core/Services/Processing/Strategies/EventReaderBasedProjectionProcessingStrategy.cs b/src/EventStore.Projections.Core/Services/Processing/Strategies/EventReaderBasedProjectionProcessingStrategy.cs index 80673584bc..87bcd7906c 100644 --- a/src/EventStore.Projections.Core/Services/Processing/Strategies/EventReaderBasedProjectionProcessingStrategy.cs +++ b/src/EventStore.Projections.Core/Services/Processing/Strategies/EventReaderBasedProjectionProcessingStrategy.cs @@ -151,8 +151,7 @@ protected virtual ICoreProjectionCheckpointManager CreateCheckpointManager( { var emitAny = _projectionConfig.EmitEventEnabled; - //NOTE: not emitting one-time/transient projections are always handled by default checkpoint manager - // as they don't depend on stable event order + // Unordered reads need the multi-output checkpoint manager only when emitted output must be repeatable. if (emitAny && !readerStrategy.IsReadingOrderRepeatable) { return new MultiStreamMultiOutputCheckpointManager( diff --git a/src/EventStore.Projections.Core/Services/Processing/Subscriptions/EventReorderingReaderSubscription.cs b/src/EventStore.Projections.Core/Services/Processing/Subscriptions/EventReorderingReaderSubscription.cs index bb8bb8e852..a46b3ac211 100644 --- a/src/EventStore.Projections.Core/Services/Processing/Subscriptions/EventReorderingReaderSubscription.cs +++ b/src/EventStore.Projections.Core/Services/Processing/Subscriptions/EventReorderingReaderSubscription.cs @@ -99,7 +99,7 @@ public void Handle(ReaderSubscriptionMessage.EventReaderIdle message) protected override void EofReached() { - // flush all available events as wqe reached eof (currently onetime projections only) + // EOF means no earlier event can still arrive, so every buffered event can be released. ProcessAllFor(DateTime.MaxValue); }