Skip to content

Commit 7db2192

Browse files
committed
Refs: #61 Oracle CommitStampStart recursion fix and add related tests
1 parent 8128dda commit 7db2192

7 files changed

Lines changed: 133 additions & 6 deletions

File tree

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
- Fix cross-bucket contamination in snapshot candidate query [#59](https://github.com/NEventStore/NEventStore.Persistence.SQL/issues/59)
1616
- Fix async paged-query infinite loop when PageSize is zero [#60](https://github.com/NEventStore/NEventStore.Persistence.SQL/issues/60)
17+
- Fix Oracle CommitStampStart recursion [#61](https://github.com/NEventStore/NEventStore.Persistence.SQL/issues/61)
1718

1819
### Breaking Changes
1920

docs/Project-Analysis-Issue-Drafts.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ Validation:
282282

283283
### Issue #60: Async Infinite Page Size Empty Reads
284284

285-
Status: completed locally on 2026-06-11. GitHub issue update is pending review.
285+
Status: completed on 2026-06-11.
286286

287287
Async paged reads now complete when the configured page size is `0`. For pageable dialects, `ExecutePagedQueryAsync` still binds the SQL limit parameter with an effectively unbounded value, but keeps the local paging size at `0` so continuation does not request another page.
288288

@@ -303,3 +303,29 @@ Validation:
303303
- Result: 2 passed, 0 failed, 0 skipped.
304304
- `dotnet build .\src\NEventStore.Persistence.Sql.Core.sln -c Release --no-restore /p:ContinuousIntegrationBuild=true -m:1 -nr:false`
305305
- Result: passed.
306+
307+
### Issue #61: Oracle CommitStampStart Recursion
308+
309+
Status: completed on 2026-06-11. GitHub issue updated in comment `4681420070`.
310+
311+
Oracle date-range reads no longer recurse while binding `CommitStampStart`. `OracleNativeDialect.CommitStampStart` now wraps `base.CommitStampStart`, matching the other Oracle parameter overrides. The Oracle dialect also now provides an Oracle-native `GetCommitsFromToInstant` statement, because the new integration coverage showed that after the recursion was fixed, Oracle was still falling back to the common `LIMIT/OFFSET` date-range SQL.
312+
313+
Implementation notes:
314+
315+
- Added Oracle regression coverage under `src/NEventStore.Persistence.Oracle.Tests/OracleCommitStampStartTests.cs`.
316+
- The direct dialect test verifies `new OracleNativeDialect().CommitStampStart` returns `:CommitStampStart`.
317+
- The Oracle integration test commits two events in one bucket and verifies `GetFromTo(bucketId, startDate, endDate)` returns only the commit inside the requested timestamp range.
318+
- The production fix is scoped to Oracle dialect parameter and date-range SQL behavior.
319+
320+
Validation:
321+
322+
- Test-first check: the new Oracle date-range regression crashed the test host before the fix with a stack overflow in `OracleNativeDialect.get_CommitStampStart`.
323+
- After the getter fix, the same regression exposed Oracle fallback to common `LIMIT/OFFSET` date-range SQL; adding the Oracle-native date-range statement fixed that provider issue.
324+
- `dotnet test .\src\NEventStore.Persistence.Oracle.Tests\NEventStore.Persistence.Oracle.Core.Tests.csproj -c Release -f net8.0 --no-build --filter "FullyQualifiedName~when_getting_the_oracle_commit_stamp_start_parameter"`
325+
- Result: 1 passed, 0 failed, 0 skipped.
326+
- `dotnet test .\src\NEventStore.Persistence.Oracle.Tests\NEventStore.Persistence.Oracle.Core.Tests.csproj -c Release -f net8.0 --no-restore --filter "FullyQualifiedName~CommitStampStart|FullyQualifiedName~when_reading_oracle_commits_between_commit_stamps"`
327+
- Result: 2 passed, 0 failed, 0 skipped.
328+
- `dotnet test .\src\NEventStore.Persistence.Oracle.Tests\NEventStore.Persistence.Oracle.Core.Tests.csproj -c Release -f net8.0 --no-build`
329+
- Result: 146 passed, 0 failed, 0 skipped.
330+
- `dotnet build .\src\NEventStore.Persistence.Sql.Core.sln -c Release --no-restore /p:ContinuousIntegrationBuild=true -m:1 -nr:false`
331+
- Result: passed.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#pragma warning disable IDE1006 // Naming Styles
2+
3+
using FluentAssertions;
4+
using NEventStore.Persistence.AcceptanceTests;
5+
using NEventStore.Persistence.AcceptanceTests.BDD;
6+
using NEventStore.Persistence.Sql.SqlDialects;
7+
using NUnit.Framework;
8+
9+
namespace NEventStore.Persistence.Oracle.Tests
10+
{
11+
public class when_getting_the_oracle_commit_stamp_start_parameter
12+
{
13+
private Exception? _thrown;
14+
private string? _parameterName;
15+
16+
[SetUp]
17+
public void SetUp()
18+
{
19+
_thrown = Catch.Exception(() => _parameterName = new OracleNativeDialect().CommitStampStart);
20+
}
21+
22+
[Test]
23+
public void should_return_the_oracle_parameter_name_without_recursing()
24+
{
25+
_thrown.Should().BeNull();
26+
_parameterName.Should().Be(":CommitStampStart");
27+
}
28+
}
29+
}
30+
31+
namespace NEventStore.Persistence.AcceptanceTests
32+
{
33+
public class when_reading_oracle_commits_between_commit_stamps : PersistenceEngineConcern
34+
{
35+
private const string BucketId = "oracle-date-range";
36+
private CommitAttempt? _insideRange;
37+
private CommitAttempt? _outsideRange;
38+
private ICommit[]? _commits;
39+
private Exception? _thrown;
40+
41+
protected override void Context()
42+
{
43+
DateTime start = SystemTime.UtcNow.AddYears(1);
44+
_insideRange = Guid.NewGuid().ToString().BuildAttempt(start.AddSeconds(1), BucketId);
45+
_outsideRange = Guid.NewGuid().ToString().BuildAttempt(start.AddMinutes(5), BucketId);
46+
47+
Persistence.Commit(_insideRange);
48+
Persistence.Commit(_outsideRange);
49+
}
50+
51+
protected override void Because()
52+
{
53+
DateTime startDate = _insideRange!.CommitStamp.AddSeconds(-1);
54+
DateTime endDate = _insideRange.CommitStamp.AddSeconds(1);
55+
56+
_thrown = Catch.Exception(() =>
57+
_commits = Persistence.GetFromTo(BucketId, startDate, endDate).ToArray());
58+
}
59+
60+
[Test]
61+
public void should_query_the_date_range_without_recursing()
62+
{
63+
_thrown.Should().BeNull();
64+
}
65+
66+
[Test]
67+
public void should_return_only_commits_inside_the_requested_range()
68+
{
69+
_commits.Should().ContainSingle(x => x.CommitId == _insideRange!.CommitId);
70+
_commits.Should().NotContain(x => x.CommitId == _outsideRange!.CommitId);
71+
}
72+
}
73+
}
74+
75+
#pragma warning restore IDE1006 // Naming Styles

src/NEventStore.Persistence.Sql/SqlDialects/OracleNativeDialect.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public override string CommitStampEnd
5454
/// <inheritdoc/>
5555
public override string CommitStampStart
5656
{
57-
get { return MakeOracleParameter(CommitStampStart); }
57+
get { return MakeOracleParameter(base.CommitStampStart); }
5858
}
5959
/// <inheritdoc/>
6060
public override string DuplicateCommit
@@ -77,6 +77,11 @@ public override string GetCommitsFromInstant
7777
get { return OraclePaging(OracleNativeStatements.GetCommitsFromInstant); }
7878
}
7979
/// <inheritdoc/>
80+
public override string GetCommitsFromToInstant
81+
{
82+
get { return OraclePaging(OracleNativeStatements.GetCommitsFromToInstant); }
83+
}
84+
/// <inheritdoc/>
8085
public override string GetCommitsFromCheckpoint
8186
{
8287
get { return OraclePaging(OracleNativeStatements.GetCommitsSinceCheckpoint); }

src/NEventStore.Persistence.Sql/SqlDialects/OracleNativeStatements.Designer.cs

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NEventStore.Persistence.Sql/SqlDialects/OracleNativeStatements.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,15 @@ WHERE (
165165
SELECT BucketId, StreamId, StreamIdOriginal, StreamRevision, CommitId, CommitSequence, CommitStamp, CheckpointNumber, Headers, Payload
166166
FROM Commits
167167
WHERE BucketId = :BucketId AND CommitStamp &gt;= :CommitStamp
168+
ORDER BY CheckpointNumber</value>
169+
</data>
170+
<data name="GetCommitsFromToInstant" xml:space="preserve">
171+
<value>/*GetCommitsFromToInstant*/
172+
SELECT BucketId, StreamId, StreamIdOriginal, StreamRevision, CommitId, CommitSequence, CommitStamp, CheckpointNumber, Headers, Payload
173+
FROM Commits
174+
WHERE BucketId = :BucketId
175+
AND CommitStamp &gt;= :CommitStampStart
176+
AND CommitStamp &lt; :CommitStampEnd
168177
ORDER BY CheckpointNumber</value>
169178
</data>
170179
<data name="GetCommitsFromStartingRevision" xml:space="preserve">

src/packages.config

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)